diff --git a/app/globals.css b/app/globals.css index b052a78..8be86cf 100644 --- a/app/globals.css +++ b/app/globals.css @@ -97,7 +97,7 @@ body { } .hide-scrollbar::-webkit-scrollbar { - display: none; + display: none !important; } @layer base { diff --git a/app/layout.tsx b/app/layout.tsx index 6566ec0..bce797f 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -10,6 +10,12 @@ const OAuthCallbackHandler = dynamic( { ssr: false } ); +// Import dev helper in development environment only +const DevHelper = dynamic( + () => import('@/utils/dev-helper').then(() => ({ default: () => null })), + { ssr: false } +); + export const metadata: Metadata = { title: 'AI Movie Flow - Create Amazing Videos with AI', description: 'Professional AI-powered video creation platform with advanced editing tools', @@ -32,6 +38,7 @@ export default function RootLayout({ {children} + {process.env.NODE_ENV === 'development' && } diff --git a/components/ai-suggestion-bar.tsx b/components/ai-suggestion-bar.tsx index fdfd3a7..58952a9 100644 --- a/components/ai-suggestion-bar.tsx +++ b/components/ai-suggestion-bar.tsx @@ -88,7 +88,7 @@ export function AISuggestionBar({
- {/* 智能预设词条 */} + {/* 智能预设词条 英文 */} {showSuggestions && !isCollapsed && ( - 智能预设词条 + Smart preset tags
{suggestions.map((suggestion, index) => ( diff --git a/components/pages/style/work-flow.css b/components/pages/style/work-flow.css index 365c211..282e5ba 100644 --- a/components/pages/style/work-flow.css +++ b/components/pages/style/work-flow.css @@ -34,7 +34,7 @@ color: var(--text-primary); text-align: center; letter-spacing: -.32px; - font-size: 32px; + font-size: 1.25rem; font-weight: 600; line-height: 36px; } diff --git a/components/pages/work-flow.tsx b/components/pages/work-flow.tsx index ddbcf0c..9bdf622 100644 --- a/components/pages/work-flow.tsx +++ b/components/pages/work-flow.tsx @@ -4,6 +4,7 @@ import "./style/work-flow.css"; import { Skeleton } from "@/components/ui/skeleton"; import { AISuggestionBar } from "@/components/ai-suggestion-bar"; import { EditModal } from "@/components/ui/edit-modal"; +import { ErrorBoundary } from "@/components/ui/error-boundary"; import { TaskInfo } from "./work-flow/task-info"; import { MediaViewer } from "./work-flow/media-viewer"; import { ThumbnailGrid } from "./work-flow/thumbnail-grid"; @@ -27,6 +28,10 @@ export default function WorkFlow() { isGeneratingSketch, isGeneratingVideo, currentLoadingText, + totalSketchCount, + roles, + music, + final, setCurrentSketchIndex, } = useWorkflowData(); @@ -35,29 +40,97 @@ export default function WorkFlow() { isVideoPlaying, showControls, setShowControls, + setIsPlaying, togglePlay, toggleVideoPlay, playTimerRef, } = usePlaybackControls(taskSketch, taskVideos, currentStep); + // 跟踪是否已经自动开始播放过,避免重复触发 + const hasAutoStartedRef = useRef(false); + + // 调试:监控关键状态变化 + useEffect(() => { + console.log('工作流状态:', { + currentStep, + isGeneratingSketch, + isGeneratingVideo, + isPlaying, + taskSketchLength: taskSketch.length, + sketchCount, + totalSketchCount + }); + }, [isGeneratingSketch, taskSketch.length, sketchCount, totalSketchCount, currentStep, isPlaying]); + + // 专门监控isPlaying状态变化 + useEffect(() => { + console.log('播放状态变化:', isPlaying ? '开始播放' : '停止播放'); + }, [isPlaying]); + + // 检查分镜数据 + useEffect(() => { + if (taskSketch.length > 0) { + console.log('分镜数据:', `${taskSketch.length}个分镜,当前索引:${currentSketchIndex}`); + } + }, [taskSketch.length, currentSketchIndex]); + + // 第一个分镜视频生成完成时停止循环播放并切换到第一个 + useEffect(() => { + if (taskVideos.length === 1 && isPlaying) { + console.log('第一个分镜视频生成完成,停止循环播放并切换到第一个分镜'); + setIsPlaying(false); // 停止循环播放 + setCurrentSketchIndex(0); // 切换到第一个分镜 + } + }, [taskVideos.length, isPlaying, setIsPlaying, setCurrentSketchIndex]); + + // 分镜草图生成完毕后自动开始播放 + useEffect(() => { + if ( + !isGeneratingSketch && // 分镜草图生成完毕 + taskSketch.length > 0 && // 有分镜草图数据 + sketchCount === totalSketchCount && // 确保所有分镜草图都生成完毕 + (currentStep === '1' || currentStep === '2') && // 允许在步骤1或步骤2初期触发 + !hasAutoStartedRef.current && // 还没有自动开始过播放 + !isPlaying // 当前没有播放 + ) { + console.log('所有分镜草图生成完毕,自动开始播放'); + // 添加小延迟确保状态完全更新 + setTimeout(() => { + hasAutoStartedRef.current = true; + setIsPlaying(true); // 自动开始播放 + }, 500); + } + + // 当切换到步骤3及以后时重置标记 + if (Number(currentStep) >= 3) { + hasAutoStartedRef.current = false; + } + }, [isGeneratingSketch, taskSketch.length, sketchCount, totalSketchCount, currentStep, isPlaying, setIsPlaying]); + // 处理自动播放的分镜切换逻辑 useEffect(() => { - if (isPlaying && taskSketch.length > 0 && playTimerRef.current) { + if (isPlaying && taskSketch.length > 0) { + console.log('开始自动切换分镜,总数:', taskSketch.length); const interval = setInterval(() => { - setCurrentSketchIndex((prev: number) => (prev + 1) % taskSketch.length); - }, 2000); + setCurrentSketchIndex((prev: number) => { + const nextIndex = (prev + 1) % taskSketch.length; + return nextIndex; + }); + }, 1000); - return () => clearInterval(interval); + return () => { + clearInterval(interval); + }; } }, [isPlaying, taskSketch.length, setCurrentSketchIndex]); - // 模拟 AI 建议 + // 模拟 AI 建议 英文 const mockSuggestions = [ - "优化场景转场效果", - "调整画面构图", - "改进角色动作设计", - "增加环境氛围", - "调整镜头语言" + "Refine scene transitions", + "Adjust scene composition", + "Improve character action design", + "Add environmental atmosphere", + "Adjust lens language" ]; const handleEditModalOpen = (tab: string) => { @@ -74,80 +147,99 @@ export default function WorkFlow() { }; return ( -
-
-
-
-
-
- + +
+
+
+
+
+
+ + + +
-
-
-
- {isLoading ? ( - - ) : ( -
- +
+ {isLoading ? ( + + ) : ( +
+ + + +
+ )} +
+
+ + -
- )} -
-
- + +
+ + {/* AI 建议栏 */} + + + + + + setIsEditModalOpen(false)} + taskStatus={taskObject?.taskStatus || '1'} + taskSketch={taskSketch} + sketchVideo={taskVideos} + currentSketchIndex={currentSketchIndex} + onSketchSelect={setCurrentSketchIndex} + roles={roles} + music={music} + /> +
- - {/* AI 建议栏 */} - - - setIsEditModalOpen(false)} - taskStatus={taskObject?.taskStatus || '1'} - taskSketch={taskSketch} - sketchVideo={taskVideos} - currentSketchIndex={currentSketchIndex} - onSketchSelect={setCurrentSketchIndex} - /> -
+ ) } \ No newline at end of file diff --git a/components/pages/work-flow/media-viewer.tsx b/components/pages/work-flow/media-viewer.tsx index f0cf42c..eb424d1 100644 --- a/components/pages/work-flow/media-viewer.tsx +++ b/components/pages/work-flow/media-viewer.tsx @@ -14,18 +14,15 @@ interface MediaViewerProps { isVideoPlaying: boolean; isPlaying: boolean; showControls: boolean; + isGeneratingSketch: boolean; + isGeneratingVideo: boolean; onControlsChange: (show: boolean) => void; onEditModalOpen: (tab: string) => void; onToggleVideoPlay: () => void; onTogglePlay: () => void; + final?: any; } -const MOCK_FINAL_VIDEO = { - id: 'final-video', - url: 'https://cdn.qikongjian.com/videos/1750389908_37d4fffa-8516-43a3-a423-fc0274f40e8a_text_to_video_0.mp4', - thumbnail: 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg', -}; - export function MediaViewer({ currentStep, currentSketchIndex, @@ -34,10 +31,13 @@ export function MediaViewer({ isVideoPlaying, isPlaying, showControls, + isGeneratingSketch, + isGeneratingVideo, onControlsChange, onEditModalOpen, onToggleVideoPlay, - onTogglePlay + onTogglePlay, + final }: MediaViewerProps) { const mainVideoRef = useRef(null); @@ -67,140 +67,211 @@ export function MediaViewer({ }, [currentSketchIndex]); // 渲染最终成片 - const renderFinalVideo = () => ( -
onControlsChange(true)} - onMouseLeave={() => onControlsChange(false)} - > -
- {/* 背景模糊的视频 */} - - - - {/* 最终成片视频 */} - - + const renderFinalVideo = () => { + // 使用真实的final数据,如果没有则使用默认值 + const finalVideo = final || { + url: 'https://cdn.qikongjian.com/videos/1750389908_37d4fffa-8516-43a3-a423-fc0274f40e8a_text_to_video_0.mp4' + }; + + return ( +
onControlsChange(true)} + onMouseLeave={() => onControlsChange(false)} + > +
+ {/* 背景模糊的视频 */} + + + + {/* 最终成片视频 */} + + - {/* 操作按钮组 */} - - {showControls && ( - - onEditModalOpen('4')} - /> - - )} - - - {/* 视频信息浮层 */} - -
-
+ {/* 操作按钮组 */} + + {showControls && ( - 最终成片 + className="absolute top-4 right-4 z-10 flex gap-2" + initial={{ opacity: 0, y: -10 }} + animate={{ opacity: 1, y: 0 }} + exit={{ opacity: 0, y: -10 }} + transition={{ duration: 0.2 }} + > + onEditModalOpen('4')} + /> + + )} + + + {/* 视频信息浮层 */} + +
+
+ + Final product +
-
- + - {/* 完成标记 */} - - 制作完成 - + {/* 完成标记 */} + + Task completed + +
-
- ); + ); + }; // 渲染视频内容 - const renderVideoContent = () => ( -
onControlsChange(true)} - onMouseLeave={() => onControlsChange(false)} - > - {taskVideos[currentSketchIndex] ? ( - + const renderVideoContent = () => { + const currentSketch = taskSketch[currentSketchIndex]; + const defaultBgColors = ['RGB(45, 50, 70)', 'RGB(75, 80, 100)', 'RGB(105, 110, 130)']; + const bgColors = currentSketch?.bg_rgb || defaultBgColors; + + return ( +
onControlsChange(true)} + onMouseLeave={() => onControlsChange(false)} + > + {/* 只在生成过程中或没有视频时使用ProgressiveReveal */} + {(isGeneratingVideo || !taskVideos[currentSketchIndex]) ? ( + taskVideos[currentSketchIndex] ? ( + +
+ {/* 背景模糊的图片 */} +
+ background +
+ + {/* 视频 */} + + +
+
+ ) : ( +
+ {`Sketch +
+ ) + ) : ( + /* 生成完成后直接显示视频,不使用ProgressiveReveal */
{/* 背景模糊的图片 */}
@@ -211,218 +282,281 @@ export function MediaViewer({ />
- {/* 视频 */} + {/* 视频 修复播放没有声音 */} +
+ )} + + {/* 操作按钮组 */} + + {showControls && ( - -
-
- ) : ( -
- {`分镜草图 -
- )} + )} + - {/* 操作按钮组 */} - - {showControls && ( + {/* 底部播放按钮 */} + - onEditModalOpen('3')} - /> + + {/* 播放时的发光效果 */} + {isVideoPlaying && ( + + )} + + - )} - - - {/* 底部播放按钮 */} - - - - - - - -
- ); + +
+ ); + }; // 渲染分镜草图 - const renderSketchContent = () => ( -
onControlsChange(true)} - onMouseLeave={() => onControlsChange(false)} - > - {taskSketch[currentSketchIndex] ? ( - + const renderSketchContent = () => { + const currentSketch = taskSketch[currentSketchIndex]; + const defaultBgColors = ['RGB(45, 50, 70)', 'RGB(75, 80, 100)', 'RGB(105, 110, 130)']; + const bgColors = currentSketch?.bg_rgb || defaultBgColors; + + return ( +
onControlsChange(true)} + onMouseLeave={() => onControlsChange(false)} + > + {/* 只在生成过程中或没有分镜图片时使用ProgressiveReveal */} + {(isGeneratingSketch || !currentSketch) ? ( + currentSketch ? ( + + {`Sketch + + ) : ( +
+ {/* 动态渐变背景 */} + + {/* 动态光效 */} + + +
+ +
+
+
+ ) + + ) : ( + /* 生成完成后直接显示图片,不使用ProgressiveReveal */ {`分镜草图 - - ) : ( -
- {/* 动态渐变背景 */} - - {/* 动态光效 */} - - -
- -
-
-
- )} + )} - {/* 操作按钮组 */} - - {showControls && ( + {/* 操作按钮组 */} + + {showControls && ( + + onEditModalOpen('1')} + /> + + )} + + + {/* 底部播放按钮 */} + - onEditModalOpen('1')} - /> + + {/* 播放时的发光效果 */} + {isPlaying && ( + + )} + + - )} - + - {/* 底部播放按钮 */} - - - - + {isPlaying && ( + - - - - - {/* 播放进度指示器 */} - - {isPlaying && ( - - )} - -
- ); + )} + +
+ ); + }; // 根据当前步骤渲染对应内容 if (Number(currentStep) === 6) { diff --git a/components/pages/work-flow/task-info.tsx b/components/pages/work-flow/task-info.tsx index 6f035b4..b438efc 100644 --- a/components/pages/work-flow/task-info.tsx +++ b/components/pages/work-flow/task-info.tsx @@ -26,7 +26,7 @@ export function TaskInfo({ isLoading, taskObject, currentLoadingText }: TaskInfo {taskObject?.projectName}:{taskObject?.taskName}
- {currentLoadingText === '任务完成' ? ( + {currentLoadingText === 'Task completed' ? ( - - {currentLoadingText} - + {/* 背景发光效果 */} + + {currentLoadingText} + + + {/* 主文字 - 颜色填充动画 */} + + + {currentLoadingText} + + + + {/* 动态光点效果 */} + + + {/* 文字底部装饰线 */} + + void; } -const MOCK_SKETCH_COUNT = 8; - export function ThumbnailGrid({ isLoading, currentStep, @@ -25,7 +25,9 @@ export function ThumbnailGrid({ taskSketch, taskVideos, isGeneratingSketch, + isGeneratingVideo, sketchCount, + totalSketchCount, onSketchSelect }: ThumbnailGridProps) { const thumbnailsRef = useRef(null); @@ -99,187 +101,244 @@ export function ThumbnailGrid({ } // 渲染生成中的缩略图 - const renderGeneratingThumbnail = () => ( - - {/* 动态渐变背景 */} + const renderGeneratingThumbnail = () => { + const currentSketch = taskSketch[currentSketchIndex]; + const defaultBgColors = ['RGB(45, 50, 70)', 'RGB(75, 80, 100)', 'RGB(105, 110, 130)']; + const bgColors = currentSketch?.bg_rgb || defaultBgColors; + + return ( - {/* 动态光效 */} - -
-
- + className="relative aspect-video rounded-lg overflow-hidden" + initial={{ opacity: 0, scale: 0.8 }} + animate={{ opacity: 1, scale: 1 }} + transition={{ duration: 0.3 }} + > + {/* 动态渐变背景 */} + + {/* 动态光效 */} + +
+
+ +
-
-
- 场景 {sketchCount + 1} -
- - ); +
+ Scene {sketchCount + 1} +
+ + ); + }; // 渲染视频阶段的缩略图 const renderVideoThumbnails = () => ( - taskSketch.map((sketch, index) => ( -
!isDragging && onSketchSelect(index)} - > - { + const defaultBgColors = ['RGB(45, 50, 70)', 'RGB(75, 80, 100)', 'RGB(105, 110, 130)']; + const bgColors = sketch?.bg_rgb || defaultBgColors; + + return ( +
!isDragging && onSketchSelect(index)} > + {/* 底层草图,始终显示 */}
- {taskVideos[index] ? ( -
+ + {/* 视频层,只在有视频时用ProgressiveReveal动画显示 */} + {taskVideos[index] && ( +
+ {isGeneratingVideo ? ( + +
+
+
+ ) : ( + /* 生成完成后直接显示视频,不使用ProgressiveReveal */ +
+
+ )} +
+ )} + +
+ Scene {index + 1}
- -
- 场景 {index + 1}
-
- )) + ); + }) ); // 渲染分镜草图阶段的缩略图 const renderSketchThumbnails = () => ( <> - {taskSketch.map((sketch, index) => ( -
!isDragging && onSketchSelect(index)} - > - { + const defaultBgColors = ['RGB(45, 50, 70)', 'RGB(75, 80, 100)', 'RGB(105, 110, 130)']; + const bgColors = sketch?.bg_rgb || defaultBgColors; + + return ( +
!isDragging && onSketchSelect(index)} > -
- {`缩略图 + {/* 只在生成过程中或没有分镜图片时使用ProgressiveReveal */} + {(isGeneratingSketch || !sketch) ? ( + +
+ {`Thumbnail +
+
+ ) : ( + /* 生成完成后直接显示,不使用ProgressiveReveal */ +
+ {`Thumbnail +
+ )} +
+ Scene {index + 1}
- -
- 场景 {index + 1}
-
- ))} - {isGeneratingSketch && sketchCount < MOCK_SKETCH_COUNT && renderGeneratingThumbnail()} + ); + })} + {isGeneratingSketch && sketchCount < totalSketchCount && renderGeneratingThumbnail()} ); return (
!prev); }, []); - // 自动播放逻辑 - 分镜草图 - useEffect(() => { - if (isPlaying && taskSketch.length > 0) { - playTimerRef.current = setInterval(() => { - // 这里的切换逻辑需要在父组件中处理 - // 因为需要访问 setCurrentSketchIndex - }, 2000); - } else if (playTimerRef.current) { - clearInterval(playTimerRef.current); - } + // 自动播放逻辑 - 分镜草图(移除重复的定时器逻辑,由主组件处理) + // useEffect(() => { + // if (isPlaying && taskSketch.length > 0) { + // playTimerRef.current = setInterval(() => { + // // 这里的切换逻辑需要在父组件中处理 + // // 因为需要访问 setCurrentSketchIndex + // }, 1000); + // } else if (playTimerRef.current) { + // clearInterval(playTimerRef.current); + // } - return () => { - if (playTimerRef.current) { - clearInterval(playTimerRef.current); - } - }; - }, [isPlaying, taskSketch.length]); + // return () => { + // if (playTimerRef.current) { + // clearInterval(playTimerRef.current); + // } + // }; + // }, [isPlaying, taskSketch.length]); // 视频自动播放逻辑 useEffect(() => { @@ -55,12 +55,13 @@ export function usePlaybackControls(taskSketch: any[], taskVideos: any[], curren }; }, [isVideoPlaying, taskVideos.length]); - // 当切换到视频模式时,停止播放 - useEffect(() => { - if (currentStep === '3') { - setIsPlaying(false); - } - }, [currentStep]); + // 当切换到视频模式时,停止分镜草图播放(注释掉,让用户手动控制) + // useEffect(() => { + // if (Number(currentStep) >= 3) { + // console.log('切换到步骤3+,停止分镜草图播放'); + // setIsPlaying(false); + // } + // }, [currentStep]); // 当切换到分镜草图模式时,停止视频播放 useEffect(() => { @@ -74,6 +75,7 @@ export function usePlaybackControls(taskSketch: any[], taskVideos: any[], curren isVideoPlaying, showControls, setShowControls, + setIsPlaying, togglePlay, toggleVideoPlay, playTimerRef, // 暴露给父组件使用 diff --git a/components/pages/work-flow/use-workflow-data.tsx b/components/pages/work-flow/use-workflow-data.tsx index 88e789c..a1e85b9 100644 --- a/components/pages/work-flow/use-workflow-data.tsx +++ b/components/pages/work-flow/use-workflow-data.tsx @@ -1,29 +1,10 @@ 'use client'; import { useState, useEffect, useRef, useCallback } from 'react'; +import { getRandomMockData, STEP_MESSAGES } from '@/components/work-flow/constants'; -const MOCK_SKETCH_URLS = [ - 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg', - 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-2.jpg', - 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-3.jpg', - 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-4.jpg', -]; - -const MOCK_SKETCH_SCRIPT = [ - 'script-123', - 'script-123', - 'script-123', - 'script-123', -]; - -const MOCK_VIDEO_URLS = [ - 'https://cdn.qikongjian.com/videos/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp4', - 'https://cdn.qikongjian.com/videos/1750389908_37d4fffa-8516-43a3-a423-fc0274f40e8a_text_to_video_0.mp4', - 'https://cdn.qikongjian.com/videos/1750384661_d8e30b79-828e-48cd-9025-ab62a996717c_text_to_video_0.mp4', - 'https://cdn.qikongjian.com/videos/1750320040_4b47996e-7c70-490e-8433-80c7df990fdd_text_to_video_0.mp4', -]; - -const MOCK_SKETCH_COUNT = 8; +// 当前选择的mock数据 +let selectedMockData = getRandomMockData(); export function useWorkflowData() { const [taskObject, setTaskObject] = useState(null); @@ -35,20 +16,23 @@ export function useWorkflowData() { const [currentSketchIndex, setCurrentSketchIndex] = useState(0); const [isGeneratingSketch, setIsGeneratingSketch] = useState(false); const [isGeneratingVideo, setIsGeneratingVideo] = useState(false); - const [currentLoadingText, setCurrentLoadingText] = useState('加载中...'); + const [currentLoadingText, setCurrentLoadingText] = useState('Loading task information...'); // 模拟接口请求 获取任务详情 const getTaskDetail = async (taskId: string) => { + // 每次进入页面时重新随机选择数据 + selectedMockData = getRandomMockData(); + const data = { - projectId: 'projectId-123', - projectName: "Project 1", + projectId: selectedMockData.detail.projectId, + projectName: selectedMockData.detail.projectName, taskId: taskId, - taskName: "Task 1", - taskDescription: "Task 1 Description", - taskStatus: "1", + taskName: selectedMockData.detail.taskName, + taskDescription: selectedMockData.detail.taskDescription, + taskStatus: selectedMockData.detail.taskStatus, taskProgress: 0, - mode: 'auto', - resolution: '1080p', + mode: selectedMockData.detail.mode, + resolution: selectedMockData.detail.resolution.toString(), }; return data; }; @@ -60,14 +44,18 @@ export function useWorkflowData() { setIsGeneratingSketch(true); setTaskSketch([]); + const sketchData = selectedMockData.sketch; + const totalSketches = sketchData.length; + // 模拟分批获取分镜草图 - for (let i = 0; i < MOCK_SKETCH_COUNT; i++) { - await new Promise(resolve => setTimeout(resolve, 2000)); + for (let i = 0; i < totalSketches; i++) { + await new Promise(resolve => setTimeout(resolve, 10000)); const newSketch = { id: `sketch-${i}`, - url: MOCK_SKETCH_URLS[i % MOCK_SKETCH_URLS.length], - script: MOCK_SKETCH_SCRIPT[i % MOCK_SKETCH_SCRIPT.length], + url: sketchData[i].url, + script: sketchData[i].script, + bg_rgb: sketchData[i].bg_rgb, status: 'done' }; @@ -81,22 +69,24 @@ export function useWorkflowData() { setSketchCount(i + 1); } + // 等待最后一个动画完成再设置生成状态为false + await new Promise(resolve => setTimeout(resolve, 1500)); setIsGeneratingSketch(false); }; // 模拟接口请求 每次获取一个角色 轮询获取 const getTaskRole = async (taskId: string) => { - await new Promise(resolve => setTimeout(resolve, 2000)); + await new Promise(resolve => setTimeout(resolve, 30000 * selectedMockData.roles.length)); // 延长到30秒 }; // 模拟接口请求 获取背景音 const getTaskBackgroundAudio = async (taskId: string) => { - await new Promise(resolve => setTimeout(resolve, 2000)); + await new Promise(resolve => setTimeout(resolve, 10000)); // 10s }; // 模拟接口请求 获取最终成品 const getTaskFinalProduct = async (taskId: string) => { - await new Promise(resolve => setTimeout(resolve, 2000)); + await new Promise(resolve => setTimeout(resolve, 50000)); // 50s }; // 模拟接口请求 每次获取一个分镜视频 轮询获取 @@ -104,14 +94,17 @@ export function useWorkflowData() { setIsGeneratingVideo(true); setTaskVideos([]); + const videoData = selectedMockData.video; + const totalVideos = videoData.length; + // 模拟分批获取分镜视频 - for (let i = 0; i < MOCK_SKETCH_COUNT; i++) { - await new Promise(resolve => setTimeout(resolve, 5000)); + for (let i = 0; i < totalVideos; i++) { + await new Promise(resolve => setTimeout(resolve, 60000)); // 60s const newVideo = { id: `video-${i}`, - url: MOCK_VIDEO_URLS[i % MOCK_VIDEO_URLS.length], - script: MOCK_SKETCH_SCRIPT[i % MOCK_SKETCH_SCRIPT.length], + url: videoData[i].url, + script: videoData[i].script, status: 'done' }; @@ -124,36 +117,40 @@ export function useWorkflowData() { setCurrentSketchIndex(i); } + // 等待最后一个动画完成再设置生成状态为false + await new Promise(resolve => setTimeout(resolve, 1500)); setIsGeneratingVideo(false); }; // 更新加载文字 useEffect(() => { if (isLoading) { - setCurrentLoadingText('正在加载任务信息...'); + setCurrentLoadingText(STEP_MESSAGES.loading); return; } + const totalSketches = selectedMockData.sketch.length; + if (currentStep === '1') { if (isGeneratingSketch) { - setCurrentLoadingText(`正在生成分镜草图 ${sketchCount + 1}/${MOCK_SKETCH_COUNT}...`); + setCurrentLoadingText(STEP_MESSAGES.sketch(sketchCount, totalSketches)); } else { - setCurrentLoadingText('分镜草图生成完成'); + setCurrentLoadingText(STEP_MESSAGES.sketchComplete); } } else if (currentStep === '2') { - setCurrentLoadingText('正在绘制角色...'); + setCurrentLoadingText(STEP_MESSAGES.character); } else if (currentStep === '3') { if (isGeneratingVideo) { - setCurrentLoadingText(`正在生成分镜视频 ${taskVideos.length + 1}/${taskSketch.length}...`); + setCurrentLoadingText(STEP_MESSAGES.video(taskVideos.length, totalSketches)); } else { - setCurrentLoadingText('分镜视频生成完成'); + setCurrentLoadingText(STEP_MESSAGES.videoComplete); } } else if (currentStep === '4') { - setCurrentLoadingText('正在生成背景音...'); + setCurrentLoadingText(STEP_MESSAGES.audio); } else if (currentStep === '5') { - setCurrentLoadingText('正在生成最终成品...'); + setCurrentLoadingText(STEP_MESSAGES.final); } else { - setCurrentLoadingText('任务完成'); + setCurrentLoadingText(STEP_MESSAGES.complete); } }, [isLoading, currentStep, isGeneratingSketch, sketchCount, isGeneratingVideo, taskVideos.length, taskSketch.length]); @@ -167,7 +164,6 @@ export function useWorkflowData() { // 只在任务详情加载完成后获取分镜草图 await getTaskSketch(taskId); - await new Promise(resolve => setTimeout(resolve, 2000)); // 修改 taskObject 下的 taskStatus 为 '2' setTaskObject((prev: any) => ({ @@ -178,7 +174,6 @@ export function useWorkflowData() { // 获取分镜草图后,开始绘制角色 await getTaskRole(taskId); - await new Promise(resolve => setTimeout(resolve, 2000)); // 修改 taskObject 下的 taskStatus 为 '3' setTaskObject((prev: any) => ({ @@ -189,7 +184,6 @@ export function useWorkflowData() { // 获取绘制角色后,开始获取分镜视频 await getTaskVideo(taskId); - await new Promise(resolve => setTimeout(resolve, 2000)); // 修改 taskObject 下的 taskStatus 为 '4' setTaskObject((prev: any) => ({ @@ -208,7 +202,15 @@ export function useWorkflowData() { taskStatus: '5' })); setCurrentStep('5'); - + // 后期制作:抽卡中 对口型中 配音中 一致性处理中 + setCurrentLoadingText(STEP_MESSAGES.postProduction('Selecting optimal frames')); + await new Promise(resolve => setTimeout(resolve, 10000)); + setCurrentLoadingText(STEP_MESSAGES.postProduction('Aligning lip sync')); + await new Promise(resolve => setTimeout(resolve, 10000)); + setCurrentLoadingText(STEP_MESSAGES.postProduction('Adding background audio')); + await new Promise(resolve => setTimeout(resolve, 10000)); + setCurrentLoadingText(STEP_MESSAGES.postProduction('Consistency processing')); + await new Promise(resolve => setTimeout(resolve, 10000)); // 获取背景音后,开始获取最终成品 await getTaskFinalProduct(taskId); await new Promise(resolve => setTimeout(resolve, 2000)); @@ -234,6 +236,10 @@ export function useWorkflowData() { isGeneratingSketch, isGeneratingVideo, currentLoadingText, + totalSketchCount: selectedMockData.sketch.length, + roles: selectedMockData.roles, + music: selectedMockData.music, + final: selectedMockData.final, // 操作方法 setCurrentSketchIndex, }; diff --git a/components/ui/audio-visualizer.tsx b/components/ui/audio-visualizer.tsx index b8e4f95..a3d1f3f 100644 --- a/components/ui/audio-visualizer.tsx +++ b/components/ui/audio-visualizer.tsx @@ -207,13 +207,10 @@ export function AudioVisualizer({
- {title} - {hasError && (Demo)} + Audio & SFX
-
Audio track
-
{volume}%
{/* 波形可视化 */} @@ -293,26 +290,6 @@ export function AudioVisualizer({ {formatTime(currentTime)} / {formatTime(duration)}
- - {/* 音量控制 */} -
- - { - const newVolume = parseInt(e.target.value); - onVolumeChange?.(newVolume); - }} - className="w-16 h-1 bg-white/20 rounded-lg appearance-none cursor-pointer" - style={{ - background: `linear-gradient(to right, #3b82f6 0%, #3b82f6 ${volume}%, rgba(255,255,255,0.2) ${volume}%, rgba(255,255,255,0.2) 100%)` - }} - /> - {volume}% -
{/* 播放状态指示器 */} @@ -328,13 +305,6 @@ export function AudioVisualizer({ }} /> )} - - {/* 错误提示 */} - {hasError && ( -
- 演示模式 - 使用模拟音频数据 -
- )}
); diff --git a/components/ui/character-tab-content.tsx b/components/ui/character-tab-content.tsx index 469bf5f..bf87756 100644 --- a/components/ui/character-tab-content.tsx +++ b/components/ui/character-tab-content.tsx @@ -1,40 +1,30 @@ import React, { useState, useRef } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; -import { Upload, Library, Play, Pause, RefreshCw, Wand2 } from 'lucide-react'; +import { Upload, Library, Play, Pause, RefreshCw, Wand2, Users } from 'lucide-react'; import { cn } from '@/public/lib/utils'; import { GlassIconButton } from './glass-icon-button'; import { ReplaceCharacterModal } from './replace-character-modal'; +interface Role { + name: string; + url: string; + sound: string; + soundDescription: string; + roleDescription: string; +} + interface CharacterTabContentProps { taskSketch: any[]; currentSketchIndex: number; onSketchSelect: (index: number) => void; + roles: Role[]; } -// 模拟角色数据 -const MOCK_CHARACTERS = [ - { - id: 1, - name: '雪 (YUKI)', - avatar: '/assets/3dr_chihiro.png', - voiceDescription: '年轻女性,温柔而坚定的声线,语速适中,带有轻微的感性色彩。', - characterDescription: '一位接近二十岁或二十出头的年轻女性,东亚裔,拥有深色长发和刘海,五官柔和且富有表现力。', - voiceUrl: 'https://example.com/voice-sample.mp3' - }, - { - id: 2, - name: '春 (HARU)', - avatar: '/assets/3dr_mono.png', - voiceDescription: '年轻男性,清澈而温和的声线,语速从容,带有知性的特质。', - characterDescription: '一位接近二十岁或二十出头的年轻男性,东亚裔,拥有深色、发型整洁的中长发和深思的气质。', - voiceUrl: 'https://example.com/voice-sample.mp3' - }, -]; - export function CharacterTabContent({ taskSketch, currentSketchIndex, - onSketchSelect + onSketchSelect, + roles = [] }: CharacterTabContentProps) { const [selectedCharacterIndex, setSelectedCharacterIndex] = useState(0); const [isReplaceModalOpen, setIsReplaceModalOpen] = useState(false); @@ -47,6 +37,16 @@ export function CharacterTabContent({ }>({ type: null, value: '' }); const audioRef = useRef(null); + // 如果没有角色数据,显示占位内容 + if (!roles || roles.length === 0) { + return ( +
+ +

No character data

+
+ ); + } + // 处理音频播放进度 const handleTimeUpdate = () => { if (audioRef.current) { @@ -79,6 +79,9 @@ export function CharacterTabContent({ } }; + // 获取当前选中的角色 + const currentRole = roles[selectedCharacterIndex]; + return (
{/* 上部分:角色缩略图 */} @@ -89,9 +92,9 @@ export function CharacterTabContent({ >
- {MOCK_CHARACTERS.map((character, index) => ( + {roles.map((role, index) => ( {character.name}
- {character.name} + {role.name}
))} @@ -122,7 +125,7 @@ export function CharacterTabContent({ animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.1 }} > -

替换角色

+

Replace character

- 上传角色 + Upload character - 角色库 + Character library
@@ -163,10 +166,10 @@ export function CharacterTabContent({
{/* 角色姓名 */}
- + console.log('name changed:', e.target.value)} className="w-full px-3 py-2 bg-white/5 border border-white/10 rounded-lg focus:outline-none focus:border-blue-500" @@ -174,9 +177,9 @@ export function CharacterTabContent({
{/* 声音描述 */}
- +