From f135938d59321ca9d777923fb10c6dcad383ffda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8C=97=E6=9E=B3?= <7854742+wang_rumeng@user.noreply.gitee.com> Date: Fri, 18 Jul 2025 07:05:47 +0800 Subject: [PATCH] =?UTF-8?q?=E9=98=B6=E6=AE=B5loading?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/pages/create-to-video2.tsx | 8 +- components/pages/work-flow.tsx | 1 + components/pages/work-flow/media-viewer.tsx | 6 +- components/pages/work-flow/task-info.tsx | 208 ++++++++--------- components/pages/work-flow/thumbnail-grid.tsx | 4 +- .../pages/work-flow/use-workflow-data.tsx | 71 ++++-- components/ui/script-modal.tsx | 112 ++++++++- components/workflow/work-office/editor.tsx | 62 ++--- components/workflow/work-office/mock-data.ts | 214 +++++++++--------- .../workflow/work-office/scriptwriter.tsx | 28 +-- .../work-office/storyboard-artist.tsx | 32 +-- .../workflow/work-office/visual-director.tsx | 42 ++-- .../workflow/work-office/work-office.tsx | 143 ++---------- 13 files changed, 473 insertions(+), 458 deletions(-) diff --git a/components/pages/create-to-video2.tsx b/components/pages/create-to-video2.tsx index 9e9c982..418c77d 100644 --- a/components/pages/create-to-video2.tsx +++ b/components/pages/create-to-video2.tsx @@ -422,7 +422,7 @@ export function CreateToVideo2() { episode.status !== 'COMPLETED' ? 'bg-yellow-500/80 text-white' : 'bg-gray-500/80 text-white' }`}> - {episode.status === 'COMPLETED' ? '已完成' : '进行中'} + {episode.status === 'COMPLETED' ? 'finished' : 'processing'} @@ -439,7 +439,7 @@ export function CreateToVideo2() { {/* 内容区域 */}

- {episode.name || '未命名剧集'} + {episode.name || episode.title || 'Unnamed episode'}

{/* 元数据 */} @@ -518,7 +518,7 @@ export function CreateToVideo2() {
- 加载更多剧集中... + Loading more episodes...
)} @@ -530,7 +530,7 @@ export function CreateToVideo2() {
-

已加载全部剧集

+

All episodes loaded

)} diff --git a/components/pages/work-flow.tsx b/components/pages/work-flow.tsx index 124691b..62e1f8f 100644 --- a/components/pages/work-flow.tsx +++ b/components/pages/work-flow.tsx @@ -117,6 +117,7 @@ export default function WorkFlow() { taskObject={taskObject} currentLoadingText={currentLoadingText} dataLoadError={dataLoadError} + roles={roles} /> diff --git a/components/pages/work-flow/media-viewer.tsx b/components/pages/work-flow/media-viewer.tsx index e17fe8d..058b36d 100644 --- a/components/pages/work-flow/media-viewer.tsx +++ b/components/pages/work-flow/media-viewer.tsx @@ -358,7 +358,7 @@ export function MediaViewer({ ease: "easeInOut" }} /> - Final product + {currentStep === '6' ? 'Final product' : 'Trailer Video'} @@ -735,7 +735,7 @@ export function MediaViewer({ {`Sketch )} @@ -803,7 +803,7 @@ export function MediaViewer({ }; // 根据当前步骤渲染对应内容 - if (Number(currentStep) === 6) { + if (Number(currentStep) === 6 || Number(currentStep) === 5.5) { return renderFinalVideo(); } diff --git a/components/pages/work-flow/task-info.tsx b/components/pages/work-flow/task-info.tsx index c1385ce..9ce2777 100644 --- a/components/pages/work-flow/task-info.tsx +++ b/components/pages/work-flow/task-info.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { useState, useEffect, useRef, useMemo } from 'react'; +import React, { useState, useEffect, useMemo, useRef } from 'react'; import { motion } from 'framer-motion'; import { ScriptModal } from '@/components/ui/script-modal'; import { @@ -20,10 +20,7 @@ interface TaskInfoProps { taskObject: any; currentLoadingText: string; dataLoadError?: string | null; - currentStep: string; - sketchCount: number; - isGeneratingVideo: boolean; - taskVideos: any[]; + roles: any[]; } // 根据加载文本返回对应的图标 @@ -56,128 +53,106 @@ export function TaskInfo({ taskObject, currentLoadingText, dataLoadError, - currentStep, - sketchCount, - isGeneratingVideo, - taskVideos + roles }: TaskInfoProps) { const StageIcon = getStageIcon(currentLoadingText); const [isScriptModalOpen, setIsScriptModalOpen] = useState(false); const [currentStage, setCurrentStage] = useState(0); - const prevSketchCountRef = useRef(sketchCount); - const prevIsGeneratingVideoRef = useRef(isGeneratingVideo); - const prevTaskVideosLengthRef = useRef(taskVideos?.length || 0); + const [isShowScriptIcon, setIsShowScriptIcon] = useState(true); const timerRef = useRef(null); - // 清理定时器 + // 监听 currentLoadingText useEffect(() => { - return () => { - if (timerRef.current) { - clearTimeout(timerRef.current); - } - }; - }, []); - - // 监听 currentStep 为 '1' 的情况 - useEffect(() => { - console.log('Current Step Effect:', { currentStep, hasTitle: !!taskObject?.title }); - - // 清除之前的定时器 + // 清理之前的定时器 if (timerRef.current) { clearTimeout(timerRef.current); timerRef.current = null; } - if (currentStep === '1' && taskObject?.title) { - console.log('Setting up timer for script modal...'); - timerRef.current = setTimeout(() => { - console.log('Opening script modal with stage 0'); - setIsScriptModalOpen(true); - setCurrentStage(0); - }, 5000); - } - }, [currentStep, taskObject?.title]); - - // 监听 sketchCount 变化 - useEffect(() => { - console.log('SketchCount Effect:', { - prevCount: prevSketchCountRef.current, - currentCount: sketchCount, - isModalOpen: isScriptModalOpen - }); - - // 从 0 变为 1 时关闭弹窗 - if (prevSketchCountRef.current === 0 && sketchCount === 1) { - console.log('Closing modal: sketchCount changed from 0 to 1'); - setIsScriptModalOpen(false); - } - // 从 1 变为其他非 0 值时切换弹窗 - else if (prevSketchCountRef.current === 1 && sketchCount !== 0 && sketchCount !== 1) { - console.log('Toggling modal: sketchCount changed from 1 to other value'); - setIsScriptModalOpen(prev => { - const newState = !prev; - if (newState) { - setCurrentStage(1); - } - return newState; - }); - } - - prevSketchCountRef.current = sketchCount; - }, [sketchCount]); - - // 监听视频生成状态变化 - useEffect(() => { - const currentTaskVideosLength = taskVideos?.length || 0; - console.log('Video Generation Effect:', { - prevGenerating: prevIsGeneratingVideoRef.current, - currentGenerating: isGeneratingVideo, - prevVideosLength: prevTaskVideosLengthRef.current, - currentVideosLength: currentTaskVideosLength, - isModalOpen: isScriptModalOpen - }); - - if ( - (prevIsGeneratingVideoRef.current === false && isGeneratingVideo === true) || - prevTaskVideosLengthRef.current !== currentTaskVideosLength - ) { - console.log('Toggling modal due to video generation changes'); - setIsScriptModalOpen(prev => { - const newState = !prev; - if (newState) { - setCurrentStage(2); - } - return newState; - }); - } - - prevIsGeneratingVideoRef.current = isGeneratingVideo; - prevTaskVideosLengthRef.current = currentTaskVideosLength; - }, [isGeneratingVideo, taskVideos]); - - // 监听最终步骤 - useEffect(() => { - console.log('Final Steps Effect:', { currentStep, isModalOpen: isScriptModalOpen }); - - if (currentStep === '5') { - console.log('Opening modal for final review'); - setIsScriptModalOpen(true); - setCurrentStage(3); - } else if (currentStep === '6') { + if (currentLoadingText.includes('Task completed')) { console.log('Closing modal at completion'); setIsScriptModalOpen(false); + setIsShowScriptIcon(false); } - }, [currentStep]); + if (currentLoadingText.includes('Post-production')) { + if (isScriptModalOpen) { + setIsScriptModalOpen(false); + } + console.log('isScriptModalOpen-Post-production', currentLoadingText, isScriptModalOpen); + setIsScriptModalOpen(true); + setCurrentStage(3); + } + if (currentLoadingText.includes('video')) { + console.log('isScriptModalOpen-video', currentLoadingText, isScriptModalOpen); + if (isScriptModalOpen) { + setIsScriptModalOpen(false); - // 弹窗状态变化时的日志 - useEffect(() => { - console.log('Modal State Changed:', { - isOpen: isScriptModalOpen, - currentStage, - currentStep, - sketchCount - }); - }, [isScriptModalOpen, currentStage, currentStep, sketchCount]); + // 延迟8s 再次打开 + timerRef.current = setTimeout(() => { + setIsScriptModalOpen(true); + setCurrentStage(2); + }, 8000); + } else { + setIsScriptModalOpen(true); + setCurrentStage(2); + } + } + if (currentLoadingText.includes('video status')) { + if (isScriptModalOpen) { + setIsScriptModalOpen(false); + } + setIsScriptModalOpen(true); + setCurrentStage(2); + } + if (currentLoadingText.includes('sketch')) { + console.log('isScriptModalOpen-sketch', currentLoadingText, isScriptModalOpen); + if (isScriptModalOpen) { + setIsScriptModalOpen(false); + + // 延迟8s 再次打开 + timerRef.current = setTimeout(() => { + setIsScriptModalOpen(true); + setCurrentStage(1); + }, 8000); + } else { + setIsScriptModalOpen(true); + setCurrentStage(1); + } + } + if (currentLoadingText.includes('sketch status')) { + if (isScriptModalOpen) { + setIsScriptModalOpen(false); + } + setIsScriptModalOpen(true); + setCurrentStage(1); + } + if (currentLoadingText.includes('character')) { + console.log('isScriptModalOpen-character', currentLoadingText, isScriptModalOpen); + if (isScriptModalOpen) { + setIsScriptModalOpen(false); + + // 延迟8s 再次打开 + timerRef.current = setTimeout(() => { + setIsScriptModalOpen(true); + setCurrentStage(0); + }, 8000); + } else { + setIsScriptModalOpen(true); + setCurrentStage(0); + } + } + if (currentLoadingText.includes('initializing')) { + console.log('isScriptModalOpen-initializing', currentLoadingText, isScriptModalOpen); + setIsScriptModalOpen(true); + setCurrentStage(0); + } + return () => { + if (timerRef.current) { + clearTimeout(timerRef.current); + timerRef.current = null; + } + } + }, [currentLoadingText]); // 使用 useMemo 缓存标签颜色映射 const tagColors = useMemo(() => { @@ -309,15 +284,17 @@ export function TaskInfo({ {taskObject?.title || 'loading project info...'} -
- + setIsScriptModalOpen(true)} > -
+ + )} ) : 'loading project info...'} @@ -342,6 +319,7 @@ export function TaskInfo({ setIsScriptModalOpen(false); }} currentStage={currentStage} + roles={roles} /> {currentLoadingText === 'Task completed' ? ( diff --git a/components/pages/work-flow/thumbnail-grid.tsx b/components/pages/work-flow/thumbnail-grid.tsx index 462e81f..c2de74e 100644 --- a/components/pages/work-flow/thumbnail-grid.tsx +++ b/components/pages/work-flow/thumbnail-grid.tsx @@ -316,7 +316,7 @@ export function ThumbnailGrid({ {`Thumbnail @@ -326,7 +326,7 @@ export function ThumbnailGrid({ {`Thumbnail )} diff --git a/components/pages/work-flow/use-workflow-data.tsx b/components/pages/work-flow/use-workflow-data.tsx index eb7902b..55eef57 100644 --- a/components/pages/work-flow/use-workflow-data.tsx +++ b/components/pages/work-flow/use-workflow-data.tsx @@ -247,6 +247,17 @@ export function useWorkflowData() { loadingText = LOADING_TEXT_MAP.postProduction('post-production...'); } } + // 粗剪 + if (task.task_name === 'generate_final_simple_video') { + if (task.task_result && task.task_result.video) { + setFinal({ + url: task.task_result.video, + }) + finalStep = '5.5'; + loadingText = LOADING_TEXT_MAP.postProduction('post-production...'); + } + } + // 最终剪辑 if (task.task_name === 'generate_final_video') { if (task.task_result && task.task_result.video) { setFinal({ @@ -414,33 +425,45 @@ export function useWorkflowData() { } } } - const realDataVideoData = data.video.data.filter((item: any) => item.urls && item.urls.length > 0); - if (realDataVideoData.length > 0) { - const videoList = []; - console.log('----------data.video.data', data.video.data); - for (const video of realDataVideoData) { - // 每一项 video 有多个视频 默认取存在的项 - videoList.push({ - url: video.urls && video.urls.length > 0 ? video.urls.find((url: string) => url) : null, - script: video.description, - audio: null, - }); - } - setTaskVideos(videoList); - // 如果在视频步骤,设置为最后一个视频 - if (data.video.total_count > realDataVideoData.length) { - setIsGeneratingVideo(true); - setCurrentSketchIndex(realDataVideoData.length - 1); - loadingText = LOADING_TEXT_MAP.video(realDataVideoData.length, data.video.total_count); - } else { - finalStep = '4'; - loadingText = LOADING_TEXT_MAP.audio; + if (data.video.data) { + const realDataVideoData = data.video.data.filter((item: any) => item.urls && item.urls.length > 0); + if (realDataVideoData.length > 0) { + const videoList = []; + console.log('----------data.video.data', data.video.data); + for (const video of realDataVideoData) { + // 每一项 video 有多个视频 默认取存在的项 + videoList.push({ + url: video.urls && video.urls.length > 0 ? video.urls.find((url: string) => url) : null, + script: video.description, + audio: null, + }); + } + setTaskVideos(videoList); + // 如果在视频步骤,设置为最后一个视频 + if (data.video.total_count > realDataVideoData.length) { + setIsGeneratingVideo(true); + setCurrentSketchIndex(realDataVideoData.length - 1); + loadingText = LOADING_TEXT_MAP.video(realDataVideoData.length, data.video.total_count); + } else { + finalStep = '4'; + loadingText = LOADING_TEXT_MAP.audio; - // 暂时没有音频生成 直接跳过 - finalStep = '5'; - loadingText = LOADING_TEXT_MAP.postProduction('post-production...'); + // 暂时没有音频生成 直接跳过 + finalStep = '5'; + loadingText = LOADING_TEXT_MAP.postProduction('post-production...'); + } } } + + // 粗剪 + if (data.final_simple_video && data.final_simple_video.video) { + setFinal({ + url: data.final_simple_video.video + }); + finalStep = '5.5'; + loadingText = LOADING_TEXT_MAP.postProduction('post-production...'); + } + if (data.final_video && data.final_video.video) { setFinal({ url: data.final_video.video diff --git a/components/ui/script-modal.tsx b/components/ui/script-modal.tsx index 15f12d7..218cfe0 100644 --- a/components/ui/script-modal.tsx +++ b/components/ui/script-modal.tsx @@ -3,14 +3,96 @@ import { motion, AnimatePresence } from 'framer-motion'; import { X } from 'lucide-react'; import WorkOffice from '@/components/workflow/work-office/work-office'; +import { useState, useEffect, useRef } from 'react'; interface ScriptModalProps { isOpen: boolean; onClose: () => void; currentStage?: number; + roles: any[]; } -export function ScriptModal({ isOpen, onClose, currentStage = 0 }: ScriptModalProps) { +const stages = [ + { + id: 'script', + title: 'Scriptwriter', + color: '#8b5cf6', + duration: 3 * 60 * 1000 // 3分钟 + }, + { + id: 'storyboard', + title: 'Storyboard artist', + color: '#06b6d4', + duration: 4 * 60 * 1000 // 4分钟 + }, + { + id: 'production', + title: 'Visual director', + color: '#10b981', + duration: 5 * 60 * 1000 // 5分钟 + }, + { + id: 'editing', + title: 'Editor', + color: '#f59e0b', + duration: 15 * 60 * 1000 // 15分钟 + } +]; + +export function ScriptModal({ isOpen, onClose, currentStage = 0, roles }: ScriptModalProps) { + const [isPlaying, setIsPlaying] = useState(true); + const [progress, setProgress] = useState(0); + const [startTime, setStartTime] = useState(null); + const prevStageRef = useRef(currentStage); + + // 每次打开都重置进度条和时间 + useEffect(() => { + setProgress(0); + setIsPlaying(true); + setStartTime(Date.now()); + }, [isOpen, currentStage]); + + // 处理进度条和时间更新 + useEffect(() => { + if (!isPlaying || !isOpen) return; + + if (startTime === null) { + setStartTime(Date.now()); + } + + const currentDuration = stages[currentStage].duration; + const updateInterval = 50; // 更新间隔(毫秒) + + const interval = setInterval(() => { + const now = Date.now(); + const elapsed = now - (startTime || now); + const newProgress = Math.min((elapsed / currentDuration) * 100, 100); + + setProgress(newProgress); + + if (newProgress >= 100) { + setIsPlaying(false); + setStartTime(null); + } + }, updateInterval); + + return () => clearInterval(interval); + }, [isPlaying, currentStage, startTime, isOpen]); + + // 计算剩余时间 + const getRemainingTime = () => { + if (!isPlaying || startTime === null) return '0:00'; + + const currentDuration = stages[currentStage].duration; + const elapsed = Date.now() - startTime; + const remaining = Math.max(0, currentDuration - elapsed); + + const minutes = Math.floor(remaining / 60000); + const seconds = Math.floor((remaining % 60000) / 1000); + + return `${minutes}:${seconds.toString().padStart(2, '0')}`; + }; + return ( {isOpen && ( @@ -70,6 +152,32 @@ export function ScriptModal({ isOpen, onClose, currentStage = 0 }: ScriptModalPr + {/* 进度条和时间显示 */} +
+
+ + {stages[currentStage].title} + +
+ + Remaining time: {getRemainingTime()} + + + {Math.round(progress)}% completed + +
+
+
+ +
+
+ {/* 内容区域 */} - + diff --git a/components/workflow/work-office/editor.tsx b/components/workflow/work-office/editor.tsx index 4c690d1..e051a74 100644 --- a/components/workflow/work-office/editor.tsx +++ b/components/workflow/work-office/editor.tsx @@ -65,30 +65,30 @@ const Editor: React.FC = ({ currentContent, isPlaying }) => { {/* 剪辑节奏把控 */}

- 剪辑节奏把控 - + Editing rhythm control +

{currentContent.rhythm ? (
-
节奏理念
+
Rhythm Concept
-
实际应用
+
Practical Application
-
当前状态
+
Current Status
- +
) : (
@@ -103,8 +103,8 @@ const Editor: React.FC = ({ currentContent, isPlaying }) => { {/* 音画关系处理 */}

- 音画关系处理 - + Audio and video relationship processing +

{currentContent.audioVideo ? ( @@ -118,8 +118,8 @@ const Editor: React.FC = ({ currentContent, isPlaying }) => {
- {av.sync && `同步精度: ${av.sync}`} - {av.balance && `平衡策略: ${av.balance}`} + {av.sync && `Synchronization accuracy: ${av.sync}`} + {av.balance && `Balanced Strategy: ${av.balance}`}
@@ -136,30 +136,30 @@ const Editor: React.FC = ({ currentContent, isPlaying }) => { {/* 情绪递进调校 */}

- 情绪递进调校 - + Emotional Progressive Adjustment +

{currentContent.emotionProgression ? (
-
递进阶段
+
The progressive stage
-
调校技法
+
Adjustment Techniques
-
当前进度
+
Current progress
- +
) : (
@@ -174,8 +174,8 @@ const Editor: React.FC = ({ currentContent, isPlaying }) => { {/* 转场效果选择 */}

- 转场效果选择 - + Transition effect selection +

{currentContent.transitions ? ( @@ -202,24 +202,24 @@ const Editor: React.FC = ({ currentContent, isPlaying }) => { {/* 整体风格统一 */}

- 整体风格统一 - + Unified overall style +

{currentContent.styleUnity ? (
-
调色统一
+
Color uniformity
-
色调曲线
+
Tone Curve
- +
) : (
@@ -234,7 +234,7 @@ const Editor: React.FC = ({ currentContent, isPlaying }) => { {/* 最终输出 */}

- 最终输出 + Final Output {currentContent.finalOutput ? ( ) : ( @@ -246,9 +246,9 @@ const Editor: React.FC = ({ currentContent, isPlaying }) => {
{Object.entries({ - '格式': currentContent.finalOutput.format, - '分辨率': currentContent.finalOutput.resolution, - '码率': currentContent.finalOutput.bitrate + 'Format': currentContent.finalOutput.format, + 'Resolution': currentContent.finalOutput.resolution, + 'Bitrate': currentContent.finalOutput.bitrate }).map(([key, value]) => (
{key}: @@ -258,9 +258,9 @@ const Editor: React.FC = ({ currentContent, isPlaying }) => {
{Object.entries({ - '音频': currentContent.finalOutput.audio, - '时长': currentContent.finalOutput.duration, - '状态': currentContent.finalOutput.status + 'Audio': currentContent.finalOutput.audio, + 'Duration': currentContent.finalOutput.duration, + 'Status': currentContent.finalOutput.status }).map(([key, value]) => (
{key}: @@ -270,7 +270,7 @@ const Editor: React.FC = ({ currentContent, isPlaying }) => {
- +
) : ( diff --git a/components/workflow/work-office/mock-data.ts b/components/workflow/work-office/mock-data.ts index 728d986..cb71f5c 100644 --- a/components/workflow/work-office/mock-data.ts +++ b/components/workflow/work-office/mock-data.ts @@ -4,64 +4,64 @@ export const scriptwriterData = { { id: '1', stableId: 'act1', - title: '第一幕:开端', - desc: '故事背景设定与主要人物介绍', - beats: ['人物登场', '冲突埋设', '情节推进'] + title: 'Act I: The Beginning', + desc: 'Story background setting and main character introduction', + beats: ['Character introduction', 'conflict setting', 'plot advancement'] }, { id: '2', stableId: 'act2', - title: '第二幕:发展', - desc: '矛盾冲突的展开与升级', - beats: ['冲突加剧', '危机显现', '情节转折'] + title: 'Act II: Development', + desc: 'The development and escalation of conflicts', + beats: ['Conflict intensifies', 'Crisis emerges', 'Plot twist'] }, { id: '3', stableId: 'act3', - title: '第三幕:高潮', - desc: '故事达到最高潮并迎来结局', - beats: ['最终对决', '问题解决', '情节收束'] + title: 'Act III: Climax', + desc: 'The story reaches its climax and ends', + beats: ['Final showdown', 'Problem solved', 'Plot closure'] } ], characters: [ { id: '1', stableId: 'char1', - name: '主角', - role: '核心人物', - arc: '成长蜕变', - desc: '内心独白与行为动机的深入刻画', + name: 'main character', + role: 'Key Figures', + arc: 'Growth and transformation', + desc: 'In-depth portrayal of inner monologue and behavioral motivation', color: '#8b5cf6' }, { id: '2', stableId: 'char2', - name: '对手', - role: '反派角色', - arc: '阴谋败露', - desc: '反派形象的立体化塑造', + name: 'opponent', + role: 'Villain', + arc: 'The conspiracy was exposed', + desc: 'Three-dimensional creation of the villain image', color: '#ec4899' } ], dialogue: { stableId: 'dialogue1', - rhythm: '对白节奏的快慢变化', - style: '人物语言风格的统一' + rhythm: 'Changes in the speed of dialogue', + style: 'The unity of character language style' }, themes: [ { id: '1', stableId: 'theme1', - theme: '主题探索', - desc: '故事核心主题的层层深入', - depth: '通过情节与对白的双重展现' + theme: 'Theme Exploration', + desc: 'The core theme of the story is gradually deepened', + depth: 'Through the dual presentation of plot and dialogue' }, { id: '2', stableId: 'theme2', - theme: '情感刻画', - desc: '人物情感的细腻表达', - depth: '借助环境与氛围的烘托' + theme: 'Theme Exploration', + desc: 'The core theme of the story is gradually deepened', + depth: 'Through the dual presentation of plot and dialogue' } ], dramaticLine: { @@ -70,43 +70,43 @@ export const scriptwriterData = { { id: '1', stableId: 'point1', - title: '开场', - desc: '故事开始', + title: 'Opening', + desc: 'Story begins', intensity: 20 }, { id: '2', stableId: 'point2', - title: '引子', - desc: '背景介绍', + title: 'Introduction', + desc: 'Background introduction', intensity: 35 }, { id: '3', stableId: 'point3', - title: '发展', - desc: '冲突显现', + title: 'Development', + desc: 'Conflict appears', intensity: 60 }, { id: '4', stableId: 'point4', - title: '高潮', - desc: '矛盾爆发', + title: 'Climax', + desc: 'Contradiction breaks out', intensity: 85 }, { id: '5', stableId: 'point5', - title: '结局', - desc: '问题解决', + title: 'Conclusion', + desc: 'Problem solved', intensity: 45 }, { id: '6', stableId: 'point6', - title: '尾声', - desc: '故事收束', + title: 'Epilogue', + desc: 'Story ends', intensity: 30 } ] @@ -119,58 +119,58 @@ export const storyboardData = { { id: '1', stableId: 'shot1', - type: '远景镜头', - purpose: '展现场景全貌', - usage: '用于开场和转场,建立空间感和氛围' + type: 'Long shot', + purpose: 'Show the overall scene', + usage: 'Used for开场和转场,建立空间感和氛围' }, { id: '2', stableId: 'shot2', - type: '特写镜头', - purpose: '突出细节表情', - usage: '用于情感渲染和关键道具展示' + type: 'Close-up', + purpose: 'Highlight details', + usage: 'Used for emotional rendering and key prop display' } ], composition: { stableId: 'comp1', - principles: '黄金分割和三分法则的运用', - aesthetics: '画面构图的美感营造', - framing: '取景框的合理设置' + principles: 'The use of the golden section and the three-point rule', + aesthetics: 'The aesthetic creation of the picture composition', + framing: 'The reasonable setting of the framing' }, cameraMovement: [ { id: '1', stableId: 'cam1', - type: '推轨', - purpose: '渲染情绪', - application: '角色情感的空间表达' + type: 'Track', + purpose: 'Render emotions', + application: 'The spatial expression of character emotions' }, { id: '2', stableId: 'cam2', - type: '摇臂', - purpose: '场景转换', - application: '空间层次的流畅过渡' + type: 'Dolly', + purpose: 'Scene transition', + application: 'Smooth transition of spatial hierarchy' } ], visualNarrative: { stableId: 'visual1', - logic: '视觉叙事的连贯性', - progression: '故事节奏的视觉把控', - emphasis: '重点情节的视觉强调' + logic: 'The coherence of visual storytelling', + progression: 'The visual control of story rhythm', + emphasis: 'The visual emphasis of key plot points' }, editingPoints: [ { id: '1', stableId: 'edit1', - moment: '场景转换', - cut: '通过物体运动的自然切换' + moment: 'Scene transition', + cut: 'The natural switching through object movement' }, { id: '2', stableId: 'edit2', - moment: '情感高潮', - cut: '快速剪辑的节奏渲染' + moment: 'Emotional climax', + cut: 'The rapid editing of rhythm rendering' } ] }; @@ -181,74 +181,74 @@ export const productionData = { { id: '1', stableId: 'comp1', - element: '场景布局', - details: '场景元素的空间排布与层次关系', - status: '渲染中', + element: 'Scene layout', + details: 'The spatial arrangement and hierarchy of scene elements', + status: 'Rendering', progress: 65 }, { id: '2', stableId: 'comp2', - element: '角色位置', - details: '人物在场景中的站位与动线设计', - status: '优化中', + element: 'Character position', + details: 'The position and movement line design of characters in the scene', + status: 'Optimization', progress: 80 } ], lighting: { stableId: 'light1', - ambient: '自然光效果的模拟与调节', - artificial: '人工光源的位置布置', - mood: '通过光影营造场景氛围', + ambient: 'The simulation and adjustment of natural light effects', + artificial: 'The layout of artificial light sources', + mood: 'The creation of scene atmosphere through light and shadow', progress: 75 }, performance: [ { id: '1', stableId: 'perf1', - aspect: '面部表情', - details: '细微表情的精确捕捉', - quality: '高品质', + aspect: 'Facial expressions', + details: 'The precise capture of subtle expressions', + quality: 'High quality', progress: 90 }, { id: '2', stableId: 'perf2', - aspect: '肢体动作', - details: '动作的流畅性与自然度', - quality: '优化中', + aspect: 'Body movements', + details: 'The fluency and naturalness of body movements', + quality: 'Optimization', progress: 85 } ], sceneDetails: { stableId: 'scene1', - textures: '材质细节的精细处理', - objects: '场景道具的细节优化', - atmosphere: '整体氛围的烘托渲染', + textures: 'The fine processing of material details', + objects: 'The detailed optimization of scene props', + atmosphere: 'The overall atmosphere of the scene', progress: 70 }, technical: [ { - param: '分辨率', + param: 'Resolution', value: '4K', - status: 'optimized' + status: 'Optimized' }, { - param: '帧率', + param: 'Frame rate', value: '60fps', - status: 'processing' + status: 'Processing' }, { - param: '渲染引擎', + param: 'Rendering engine', value: 'Cycles', - status: 'active' + status: 'Active' } ], renderOutput: { currentFrame: 1500, totalFrames: 2400, - quality: '最终质量', - estimated: '预计15分钟' + quality: 'Final quality', + estimated: 'Estimated 15 minutes' } }; @@ -256,65 +256,65 @@ export const productionData = { export const editorData = { rhythm: { stableId: 'rhythm1', - concept: '节奏的整体规划与设计', - application: '快慢节奏的合理搭配', - current: '正在优化转场节奏', + concept: 'The overall planning and design of rhythm', + application: 'The reasonable combination of fast and slow rhythms', + current: 'Optimizing the transition rhythm', progress: 85 }, audioVideo: [ { id: '1', stableId: 'av1', - aspect: '音画同步', - details: '确保声画精确匹配', - sync: '帧级精度', - balance: '优', + aspect: 'Audio-visual synchronization', + details: 'Ensure accurate matching of sound and image', + sync: 'Frame level accuracy', + balance: 'Good', progress: 90 }, { id: '2', stableId: 'av2', - aspect: '音效处理', - details: '环境音效的自然融合', - sync: '毫秒级', - balance: '良好', + aspect: 'Audio processing', + details: 'The natural fusion of environmental sound effects', + sync: 'Millisecond level', + balance: 'Good', progress: 85 } ], emotionProgression: { stableId: 'emotion1', - stages: '情感递进的节奏控制', - techniques: '通过剪辑手法强化情感', - current: '高潮段落调校中', + stages: 'The gradual progression of emotions', + techniques: 'The reinforcement of emotions through editing techniques', + current: 'The adjustment of the climax segment', progress: 75 }, transitions: [ { id: '1', stableId: 'trans1', - type: '淡入淡出', - usage: '用于时空转换的柔和过渡' + type: 'Fade in and fade out', + usage: 'The gentle transition for temporal transformation' }, { id: '2', stableId: 'trans2', - type: '快速切换', - usage: '用于紧张氛围的营造' + type: 'Quick switch', + usage: 'Used to create a tense atmosphere' } ], styleUnity: { stableId: 'style1', - colorGrading: '色彩基调的统一处理', - toneCurve: '明暗对比的整体调校', + colorGrading: 'The unified handling of color tones', + toneCurve: 'The overall adjustment of contrast', progress: 80 }, finalOutput: { format: 'MP4 H.265', resolution: '4K UHD', bitrate: '50Mbps', - audio: '5.1声道', + audio: '5.1 channels', duration: '15:30', - status: '渲染中', + status: 'Rendering', progress: 65 } }; \ No newline at end of file diff --git a/components/workflow/work-office/scriptwriter.tsx b/components/workflow/work-office/scriptwriter.tsx index ba507f3..cc4a2d5 100644 --- a/components/workflow/work-office/scriptwriter.tsx +++ b/components/workflow/work-office/scriptwriter.tsx @@ -58,7 +58,7 @@ const CustomTooltip = ({ active, payload, label }: any) => { return (

{payload[0].payload.title}

-

{`情感强度: ${payload[0].value}`}

+

{`Emotional intensity: ${payload[0].value}`}

); } @@ -73,8 +73,8 @@ const Scriptwriter: React.FC = ({ currentContent, isPlaying } {/* 三幕结构 */}

- 三幕结构搭建 - + Three-act structure +

{currentContent.acts && currentContent.acts.length > 0 ? ( @@ -109,8 +109,8 @@ const Scriptwriter: React.FC = ({ currentContent, isPlaying } {/* 角色弧光设计 */}

- 角色弧光设计 - + Character arc design +

{currentContent.characters && currentContent.characters.length > 0 ? ( @@ -149,19 +149,19 @@ const Scriptwriter: React.FC = ({ currentContent, isPlaying } {/* 对白节奏感 */}

- 对白节奏感 - + Dialogue rhythm +

{currentContent.dialogue ? (
-
节奏控制
+
Rhythm control
-
表达风格
+
Expression style
@@ -175,8 +175,8 @@ const Scriptwriter: React.FC = ({ currentContent, isPlaying } {/* 主题深化过程 */}

- 主题深化过程 - + Theme deepening process +

{currentContent.themes ? ( @@ -207,8 +207,8 @@ const Scriptwriter: React.FC = ({ currentContent, isPlaying } {/* 剧情起伏戏演线 */}

- 剧情起伏戏演线 - + Dramatic line +

{currentContent.dramaticLine ? (
@@ -230,7 +230,7 @@ const Scriptwriter: React.FC = ({ currentContent, isPlaying } tick={{ fill: '#9CA3AF', fontSize: 10 }} stroke="#4B5563" label={{ - value: '情感强度', + value: 'Emotional intensity', angle: -90, position: 'insideLeft', fill: '#9CA3AF', diff --git a/components/workflow/work-office/storyboard-artist.tsx b/components/workflow/work-office/storyboard-artist.tsx index 203f21c..243a69d 100644 --- a/components/workflow/work-office/storyboard-artist.tsx +++ b/components/workflow/work-office/storyboard-artist.tsx @@ -53,8 +53,8 @@ const StoryboardArtist: React.FC = ({ currentContent, isP {/* 镜头语言选择 */}

- 镜头语言选择 - + Shot language selection +

{currentContent.shotLanguage && currentContent.shotLanguage.length > 0 ? ( @@ -83,25 +83,25 @@ const StoryboardArtist: React.FC = ({ currentContent, isP {/* 构图美学运用 */}

- 构图美学运用 - + Composition aesthetics +

{currentContent.composition ? (
-
构图原则
+
Composition principles
-
美学表达
+
Aesthetics expression
-
画面框架
+
Frame setting
@@ -122,8 +122,8 @@ const StoryboardArtist: React.FC = ({ currentContent, isP {/* 摄影机运动设计 */}

- 摄影机运动设计 - + Camera movement design +

{currentContent.cameraMovement ? ( @@ -152,25 +152,25 @@ const StoryboardArtist: React.FC = ({ currentContent, isP {/* 视觉叙事逻辑 */}

- 视觉叙事逻辑 - + Visual storytelling logic +

{currentContent.visualNarrative ? (
-
叙事逻辑
+
Storytelling logic
-
推进节奏
+
Progression
-
重点强调
+
Key emphasis
@@ -188,8 +188,8 @@ const StoryboardArtist: React.FC = ({ currentContent, isP {/* 剪辑点预设 */}

- 剪辑点预设 - + Editing point preset +

{currentContent.editingPoints ? ( diff --git a/components/workflow/work-office/visual-director.tsx b/components/workflow/work-office/visual-director.tsx index aaf97fd..938043e 100644 --- a/components/workflow/work-office/visual-director.tsx +++ b/components/workflow/work-office/visual-director.tsx @@ -63,8 +63,8 @@ const VisualDirector: React.FC = ({ currentContent, isPlayi {/* 画面构成要素 */}

- 画面构成要素 - + Element of composition +

{currentContent.composition && currentContent.composition.length > 0 ? ( @@ -94,30 +94,30 @@ const VisualDirector: React.FC = ({ currentContent, isPlayi {/* 光影氛围营造 */}

- 光影氛围营造 - + Lighting atmosphere +

{currentContent.lighting ? (
-
环境光设计
+
Environment light design
-
人工光布置
+
Artificial light layout
-
情绪氛围
+
Emotional atmosphere
- +
) : (
@@ -135,8 +135,8 @@ const VisualDirector: React.FC = ({ currentContent, isPlayi {/* 角色表演捕捉 */}

- 角色表演捕捉 - + Character performance capture +

{currentContent.performance ? ( @@ -164,30 +164,30 @@ const VisualDirector: React.FC = ({ currentContent, isPlayi {/* 场景细节渲染 */}

- 场景细节渲染 - + Scene detail rendering +

{currentContent.sceneDetails ? (
-
材质质感
+
Material texture
-
物体细节
+
Object details
-
大气效果
+
Atmospheric effect
- +
) : (
@@ -202,8 +202,8 @@ const VisualDirector: React.FC = ({ currentContent, isPlayi {/* 技术参数控制 */}

- 技术参数控制 - + Technical parameter control +

{currentContent.technical ? ( <> @@ -230,14 +230,14 @@ const VisualDirector: React.FC = ({ currentContent, isPlayi
{Math.round((currentContent.renderOutput.currentFrame / currentContent.renderOutput.totalFrames) * 100)}%
-
渲染完成
+
Rendering completed
- 帧数: {currentContent.renderOutput.currentFrame}/{currentContent.renderOutput.totalFrames} + Frame: {currentContent.renderOutput.currentFrame}/{currentContent.renderOutput.totalFrames}
- 质量: {currentContent.renderOutput.quality} | {currentContent.renderOutput.estimated} + Quality: {currentContent.renderOutput.quality} | {currentContent.renderOutput.estimated}
diff --git a/components/workflow/work-office/work-office.tsx b/components/workflow/work-office/work-office.tsx index d632707..6662262 100644 --- a/components/workflow/work-office/work-office.tsx +++ b/components/workflow/work-office/work-office.tsx @@ -13,43 +13,44 @@ interface Stage { icon: React.ElementType; color: string; profession: string; - duration: number; // 加载持续时间(毫秒) } const stages: Stage[] = [ { id: 'script', - title: '编剧工作台', + title: 'Scriptwriter', icon: Heart, color: '#8b5cf6', - profession: '编剧', - duration: 3 * 60 * 1000 // 3分钟 + profession: 'Scriptwriter' }, { id: 'storyboard', - title: '分镜设计台', + title: 'Storyboard artist', icon: Camera, color: '#06b6d4', - profession: '分镜师', - duration: 8 * 60 * 1000 // 8分钟 + profession: 'Storyboard artist' }, { id: 'production', - title: '制作渲染台', + title: 'Visual director', icon: Film, color: '#10b981', - profession: '视觉导演', - duration: 10 * 60 * 1000 // 10分钟 + profession: 'Visual director' }, { id: 'editing', - title: '剪辑调色台', + title: 'Editor', icon: Scissors, color: '#f59e0b', - profession: '剪辑师', - duration: 15 * 60 * 1000 // 15分钟 + profession: 'Editor' } ]; +const actionsText = [ + 'is thinking...', + 'is drawing...', + 'is directing...', + 'is editing...' +] // 思考指示器组件 const ThinkingDots = ({ show, text, color }: { show: boolean; text: string; color: string }) => { @@ -82,43 +83,19 @@ const ThinkingDots = ({ show, text, color }: { show: boolean; text: string; colo interface WorkOfficeProps { initialStage?: number; + roles: any[]; } const WorkOffice: React.FC = ({ initialStage = 0 }) => { const [currentStage, setCurrentStage] = useState(initialStage); - const [isPlaying, setIsPlaying] = useState(true); - const [progress, setProgress] = useState(0); + const [isPlaying, setIsPlaying] = useState(false); const [currentContent, setCurrentContent] = useState>(scriptwriterData); - const [thinkingText, setThinkingText] = useState(`${stages[0].profession}正在思考...`); - const [startTime, setStartTime] = useState(null); + const [thinkingText, setThinkingText] = useState(`${stages[0].profession} ${actionsText[0]}`); - // 模拟数据加载过程 useEffect(() => { - if (!isPlaying) return; - - // 记录开始时间 - if (startTime === null) { - setStartTime(Date.now()); - } - - const currentDuration = stages[currentStage].duration; - const updateInterval = 50; // 更新间隔(毫秒) - - const interval = setInterval(() => { - const now = Date.now(); - const elapsed = now - (startTime || now); - const newProgress = Math.min((elapsed / currentDuration) * 100, 100); - - setProgress(newProgress); - - if (newProgress >= 100) { - setIsPlaying(false); - setStartTime(null); - } - }, updateInterval); - - return () => clearInterval(interval); - }, [isPlaying, currentStage, startTime]); + // currentStage 更新 重新渲染当前工作台组件 + setCurrentStage(initialStage); + }, [initialStage]); // 根据当前阶段加载对应数据 useEffect(() => { @@ -138,19 +115,10 @@ const WorkOffice: React.FC = ({ initialStage = 0 }) => { break; } - // 重置状态并开始新的加载 - setProgress(0); setIsPlaying(true); - setStartTime(Date.now()); - setCurrentContent({}); - setThinkingText(`${stages[currentStage].profession}正在思考...`); - - // 模拟数据加载延迟 - const loadingTimeout = setTimeout(() => { - setCurrentContent(data); - }, 1000); - - return () => clearTimeout(loadingTimeout); + setCurrentContent(data); + setThinkingText(`${stages[currentStage].profession} ${actionsText[currentStage]}`); + }, [currentStage]); // 渲染当前工作台组件 @@ -169,20 +137,6 @@ const WorkOffice: React.FC = ({ initialStage = 0 }) => { } }; - // 计算剩余时间 - const getRemainingTime = () => { - if (!isPlaying || startTime === null) return '0:00'; - - const currentDuration = stages[currentStage].duration; - const elapsed = Date.now() - startTime; - const remaining = Math.max(0, currentDuration - elapsed); - - const minutes = Math.floor(remaining / 60000); - const seconds = Math.floor((remaining % 60000) / 1000); - - return `${minutes}:${seconds.toString().padStart(2, '0')}`; - }; - return (
{/* 正在加载的部分 文字显示 */} @@ -195,58 +149,9 @@ const WorkOffice: React.FC = ({ initialStage = 0 }) => {
{/* 工作台内容区域 */} -
+
{renderCurrentWorkstation()}
- - {/* 底部控制栏 */} -
-
-
- - {stages[currentStage].title} - -
- - 剩余时间: {getRemainingTime()} - - - {Math.round(progress)}% 完成 - -
-
-
- -
-
- - {/* 工作台切换按钮组 */} -
- {stages.map((stage, index) => ( - - ))} -
-
); };