forked from 77media/video-flow
视频首帧、下载loading等
This commit is contained in:
parent
eee3026699
commit
08ca156174
@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { Loader2, Check, Pencil, Trash, Download } from 'lucide-react';
|
||||
import { Loader2, Download } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import './style/create-to-video2.css';
|
||||
|
||||
@ -28,6 +28,7 @@ export default function CreateToVideo2() {
|
||||
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
const [userId, setUserId] = useState<number>(0);
|
||||
const [isLoadingDownloadBtn, setIsLoadingDownloadBtn] = useState(false);
|
||||
|
||||
// 添加一个 ref 来跟踪当前正在加载的页码
|
||||
const loadingPageRef = useRef<number | null>(null);
|
||||
@ -193,23 +194,24 @@ export default function CreateToVideo2() {
|
||||
<div
|
||||
key={project.project_id}
|
||||
className="group flex flex-col bg-black/20 rounded-lg overflow-hidden cursor-pointer hover:bg-white/5 transition-all duration-300"
|
||||
onClick={() => router.push(`/movies/work-flow?episodeId=${project.project_id}`)}
|
||||
onMouseEnter={() => handleMouseEnter(project.project_id)}
|
||||
onMouseLeave={() => handleMouseLeave(project.project_id)}
|
||||
data-alt="project-card"
|
||||
>
|
||||
{/* 视频/图片区域 */}
|
||||
<div className="relative aspect-video">
|
||||
{(project.final_video_url || project.final_simple_video_url) ? (
|
||||
<div className="relative aspect-video" onClick={() => router.push(`/movies/work-flow?episodeId=${project.project_id}`)}>
|
||||
{(project.final_video_url || project.final_simple_video_url || project.video_urls) ? (
|
||||
<video
|
||||
ref={(el) => setVideoRef(project.project_id, el)}
|
||||
src={project.final_video_url || project.final_simple_video_url}
|
||||
src={project.final_video_url || project.final_simple_video_url || project.video_urls}
|
||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
|
||||
muted
|
||||
loop
|
||||
playsInline
|
||||
preload="none"
|
||||
poster={`${project.final_video_url || project.final_simple_video_url}?vframe/jpg/offset/1`}
|
||||
poster={
|
||||
project.final_video_url || project.final_simple_video_url ? `${project.final_video_url || project.final_simple_video_url}?vframe/jpg/offset/1` : `${project.video_urls}?x-oss-process=video/snapshot,t_1000,f_jpg`
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
@ -224,9 +226,14 @@ export default function CreateToVideo2() {
|
||||
{(project.final_video_url || project.final_simple_video_url) && (
|
||||
<div className="absolute top-1 right-1">
|
||||
<Tooltip placement="top" title="Download">
|
||||
<Button size="small" type="text" className="w-[2.5rem] h-[2.5rem] rounded-full items-center justify-center p-0 hidden group-hover:flex transition-all duration-300 hover:bg-white/15" onClick={() => {
|
||||
downloadVideo(project.final_video_url || project.final_simple_video_url);
|
||||
}}><Download className="w-4 h-4 text-white" /></Button>
|
||||
<Button size="small" type="text" disabled={isLoadingDownloadBtn} className="w-[2.5rem] h-[2.5rem] rounded-full items-center justify-center p-0 hidden group-hover:flex transition-all duration-300 hover:bg-white/15" onClick={async (e) => {
|
||||
e.stopPropagation(); // 阻止事件冒泡
|
||||
setIsLoadingDownloadBtn(true);
|
||||
await downloadVideo(project.final_video_url || project.final_simple_video_url);
|
||||
setIsLoadingDownloadBtn(false);
|
||||
}}>
|
||||
{isLoadingDownloadBtn ? <Loader2 className="w-4 h-4 animate-spin text-white" /> : <Download className="w-4 h-4 text-white" />}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -50,18 +50,8 @@ const WorkFlow = React.memo(function WorkFlow() {
|
||||
aiEditingButtonRef.current?.handleAIEditing();
|
||||
editingNotificationKey.current = `editing-${Date.now()}`;
|
||||
showEditingNotification({
|
||||
title: '视频编辑中', // 可选
|
||||
description: 'AI正在为您编辑视频...', // 可选
|
||||
key: editingNotificationKey.current,
|
||||
onComplete: () => {
|
||||
console.log('编辑完成');
|
||||
// 3秒后关闭通知
|
||||
setTimeout(() => {
|
||||
if (editingNotificationKey.current) {
|
||||
notification.destroy(editingNotificationKey.current);
|
||||
}
|
||||
}, 3000);
|
||||
},
|
||||
onFail: () => {
|
||||
console.log('编辑失败');
|
||||
// 清缓存 生成计划 视频重新分析
|
||||
@ -115,7 +105,16 @@ const WorkFlow = React.memo(function WorkFlow() {
|
||||
// 更新通知状态为完成
|
||||
showEditingNotification({
|
||||
isCompleted: true,
|
||||
key: editingNotificationKey.current
|
||||
key: editingNotificationKey.current,
|
||||
onComplete: () => {
|
||||
console.log('编辑完成');
|
||||
// 3秒后关闭通知
|
||||
setTimeout(() => {
|
||||
if (editingNotificationKey.current) {
|
||||
notification.destroy(editingNotificationKey.current);
|
||||
}
|
||||
}, 3000);
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [taskObject.final, isHandleEdit]);
|
||||
|
||||
@ -489,6 +489,7 @@ export const AIEditingIframeButton = React.forwardRef<
|
||||
// 暴露 handleAIEditing 方法
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
handleAIEditing: async () => {
|
||||
console.log('handleAIEditing', iframeRef.current);
|
||||
iframeRef.current?.startAIEditing();
|
||||
}
|
||||
}));
|
||||
|
||||
@ -34,8 +34,6 @@ const descriptionStyle = {
|
||||
interface EditingNotificationProps {
|
||||
/** 编辑是否完成 */
|
||||
isCompleted?: boolean;
|
||||
/** 自定义标题 */
|
||||
title?: string;
|
||||
/** 自定义描述 */
|
||||
description?: string;
|
||||
/** key */
|
||||
@ -53,7 +51,6 @@ interface EditingNotificationProps {
|
||||
export const showEditingNotification = (props: EditingNotificationProps) => {
|
||||
const {
|
||||
isCompleted = false,
|
||||
title = 'AI Video Editing',
|
||||
description = 'Your video is being edited by AI...',
|
||||
key,
|
||||
onComplete,
|
||||
@ -125,15 +122,14 @@ export const showEditingNotification = (props: EditingNotificationProps) => {
|
||||
|
||||
return (
|
||||
<div data-alt="editing-notification" style={{ minWidth: '300px' }}>
|
||||
<h3 style={messageStyle}>
|
||||
{scissorsIcon}
|
||||
{title}
|
||||
</h3>
|
||||
<p style={{
|
||||
<h3 style={{
|
||||
...descriptionStyle,
|
||||
color: status === 'exception' ? '#ff4d4f' :
|
||||
'rgba(255, 255, 255, 0.65)',
|
||||
}}>{currentDescription}</p>
|
||||
}}>
|
||||
{scissorsIcon}
|
||||
{currentDescription}
|
||||
</h3>
|
||||
<Progress
|
||||
percent={Math.round(progress)}
|
||||
status={status}
|
||||
|
||||
@ -64,6 +64,7 @@ export const MediaViewer = React.memo(function MediaViewer({
|
||||
const [finalVideoReady, setFinalVideoReady] = useState(false);
|
||||
const [userHasInteracted, setUserHasInteracted] = useState(false);
|
||||
const [toosBtnRight, setToodsBtnRight] = useState('1rem');
|
||||
const [isLoadingDownloadBtn, setIsLoadingDownloadBtn] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isSmartChatBoxOpen) {
|
||||
@ -365,8 +366,10 @@ export const MediaViewer = React.memo(function MediaViewer({
|
||||
</Tooltip>
|
||||
{/* 下载按钮 */}
|
||||
<Tooltip placement="top" title="Download video">
|
||||
<GlassIconButton icon={Download} size='sm' onClick={() => {
|
||||
downloadVideo(taskObject.final.url);
|
||||
<GlassIconButton icon={Download} loading={isLoadingDownloadBtn} size='sm' onClick={async () => {
|
||||
setIsLoadingDownloadBtn(true);
|
||||
await downloadVideo(taskObject.final.url);
|
||||
setIsLoadingDownloadBtn(false);
|
||||
}} />
|
||||
</Tooltip>
|
||||
{showGotoCutButton && (
|
||||
@ -492,10 +495,12 @@ export const MediaViewer = React.memo(function MediaViewer({
|
||||
</Tooltip>
|
||||
{/* 下载按钮 */}
|
||||
<Tooltip placement="top" title="Download video">
|
||||
<GlassIconButton icon={Download} size='sm' onClick={() => {
|
||||
<GlassIconButton icon={Download} loading={isLoadingDownloadBtn} size='sm' onClick={async () => {
|
||||
const currentVideo = taskObject.videos.data[currentSketchIndex];
|
||||
if (currentVideo && currentVideo.urls && currentVideo.urls.length > 0) {
|
||||
downloadVideo(currentVideo.urls[0]);
|
||||
setIsLoadingDownloadBtn(true);
|
||||
await downloadVideo(currentVideo.urls[0]);
|
||||
setIsLoadingDownloadBtn(false);
|
||||
}
|
||||
}} />
|
||||
</Tooltip>
|
||||
|
||||
@ -648,7 +648,7 @@ export function useWorkflowData({ onEditPlanGenerated }: UseWorkflowDataProps =
|
||||
fallbackToStep,
|
||||
originalText: state.originalText,
|
||||
// showGotoCutButton: from && currentLoadingText.includes('Post-production') ? true : false,
|
||||
showGotoCutButton: canGoToCut && isGenerateEditPlan ? true : false,
|
||||
showGotoCutButton: canGoToCut && (isGenerateEditPlan || taskObject.currentStage === 'final_video') ? true : false,
|
||||
generateEditPlan: openEditPlan,
|
||||
handleRetryVideo
|
||||
};
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import React, { forwardRef } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { LucideIcon } from 'lucide-react';
|
||||
import { LucideIcon, Loader2 } from 'lucide-react';
|
||||
import { cn } from '@/public/lib/utils';
|
||||
|
||||
// Define props without the ref
|
||||
@ -14,6 +14,7 @@ interface GlassIconButtonProps {
|
||||
className?: string;
|
||||
[key: string]: any; // To allow spreading other props
|
||||
text?: string;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
const variantStyles = {
|
||||
@ -38,12 +39,12 @@ const iconSizes = {
|
||||
const MotionButton = motion.button;
|
||||
|
||||
export const GlassIconButton = forwardRef<HTMLButtonElement, GlassIconButtonProps>(
|
||||
({ icon: Icon, tooltip, variant = 'secondary', size = 'md', className, text, ...props }, ref) => {
|
||||
({ icon: Icon, tooltip, variant = 'secondary', size = 'md', className, text, loading = false, ...props }, ref) => {
|
||||
return (
|
||||
<MotionButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'relative rounded-full backdrop-blur-md transition-colors shadow-lg border flex items-center gap-2',
|
||||
'relative rounded-full backdrop-blur-md transition-colors shadow-lg border flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed',
|
||||
variantStyles[variant],
|
||||
sizeStyles[size],
|
||||
className
|
||||
@ -59,12 +60,13 @@ export const GlassIconButton = forwardRef<HTMLButtonElement, GlassIconButtonProp
|
||||
perspective: '1000px'
|
||||
}}
|
||||
{...props}
|
||||
disabled={loading}
|
||||
>
|
||||
<Icon className={cn('text-white', iconSizes[size])} />
|
||||
{text && (
|
||||
{loading ? <Loader2 className={cn('text-white', iconSizes[size])} /> : <Icon className={cn('text-white', iconSizes[size])} />}
|
||||
{text && !loading && (
|
||||
<span className="text-white text-xs">{text}</span>
|
||||
)}
|
||||
{tooltip && (
|
||||
{tooltip && !loading && (
|
||||
<div className="absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 text-xs
|
||||
bg-black/80 text-white rounded-md opacity-0 group-hover:opacity-100 transition-opacity
|
||||
pointer-events-none whitespace-nowrap">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user