阶段loading

This commit is contained in:
北枳 2025-07-18 07:05:47 +08:00
parent 74e678767e
commit f135938d59
13 changed files with 473 additions and 458 deletions

View File

@ -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'}
</span>
</div>
@ -439,7 +439,7 @@ export function CreateToVideo2() {
{/* 内容区域 */}
<div className="p-4">
<h3 className="text-white font-medium text-sm mb-2 line-clamp-2 group-hover:text-blue-300 transition-colors">
{episode.name || '未命名剧集'}
{episode.name || episode.title || 'Unnamed episode'}
</h3>
{/* 元数据 */}
@ -518,7 +518,7 @@ export function CreateToVideo2() {
<div className="flex justify-center py-12">
<div className="flex items-center gap-3 px-6 py-3 bg-white/[0.05] backdrop-blur-[20px] border border-white/[0.08] rounded-xl">
<Loader2 className="w-5 h-5 animate-spin text-blue-400" />
<span className="text-white/70 font-medium">...</span>
<span className="text-white/70 font-medium">Loading more episodes...</span>
</div>
</div>
)}
@ -530,7 +530,7 @@ export function CreateToVideo2() {
<div className="w-12 h-12 bg-white/[0.05] backdrop-blur-[20px] border border-white/[0.08] rounded-xl flex items-center justify-center mx-auto mb-3">
<Check className="w-6 h-6 text-green-400" />
</div>
<p className="text-white/50 text-sm"></p>
<p className="text-white/50 text-sm">All episodes loaded</p>
</div>
</div>
)}

View File

@ -117,6 +117,7 @@ export default function WorkFlow() {
taskObject={taskObject}
currentLoadingText={currentLoadingText}
dataLoadError={dataLoadError}
roles={roles}
/>
</ErrorBoundary>
</div>

View File

@ -358,7 +358,7 @@ export function MediaViewer({
ease: "easeInOut"
}}
/>
<span className="text-sm font-medium text-white/90">Final product</span>
<span className="text-sm font-medium text-white/90">{currentStep === '6' ? 'Final product' : 'Trailer Video'}</span>
</div>
</div>
</motion.div>
@ -735,7 +735,7 @@ export function MediaViewer({
<img
key={currentSketchIndex}
src={currentSketch.url}
alt={`Sketch ${currentSketchIndex + 1}`}
alt={`NG-Sketch ${currentSketchIndex + 1}`}
className="w-full h-full rounded-lg object-cover"
/>
)}
@ -803,7 +803,7 @@ export function MediaViewer({
};
// 根据当前步骤渲染对应内容
if (Number(currentStep) === 6) {
if (Number(currentStep) === 6 || Number(currentStep) === 5.5) {
return renderFinalVideo();
}

View File

@ -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<NodeJS.Timeout | null>(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({
<span>
{taskObject?.title || 'loading project info...'}
</span>
<div className="flex items-center gap-2">
<motion.div
whileHover={{ scale: 1.1 }}
{isShowScriptIcon && (
<div className="flex items-center gap-2">
<motion.div
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
onClick={() => setIsScriptModalOpen(true)}
>
<Airplay className="w-4 h-4 text-blue-500 cursor-pointer" />
</motion.div>
</div>
</div>
)}
</>
) : 'loading project info...'}
</div>
@ -342,6 +319,7 @@ export function TaskInfo({
setIsScriptModalOpen(false);
}}
currentStage={currentStage}
roles={roles}
/>
{currentLoadingText === 'Task completed' ? (

View File

@ -316,7 +316,7 @@ export function ThumbnailGrid({
<img
className="w-full h-full object-cover"
src={sketch.url}
alt={`Thumbnail ${index + 1}`}
alt={`NG ${index + 1}`}
/>
</div>
</ProgressiveReveal>
@ -326,7 +326,7 @@ export function ThumbnailGrid({
<img
className="w-full h-full object-cover"
src={sketch.url}
alt={`Thumbnail ${index + 1}`}
alt={`NG ${index + 1}`}
/>
</div>
)}

View File

@ -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

View File

@ -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<number | null>(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 (
<AnimatePresence mode="wait">
{isOpen && (
@ -70,6 +152,32 @@ export function ScriptModal({ isOpen, onClose, currentStage = 0 }: ScriptModalPr
<X className="w-5 h-5 text-gray-600 dark:text-gray-300" />
</motion.button>
{/* 进度条和时间显示 */}
<div className="absolute left-[2rem] right-[2rem] bottom-[3rem] z-50">
<div className="flex items-center justify-between mb-2">
<span className="text-gray-700 dark:text-gray-300 font-medium">
{stages[currentStage].title}
</span>
<div className="flex items-center space-x-4">
<span className="text-gray-600 dark:text-gray-400 text-sm">
Remaining time: {getRemainingTime()}
</span>
<span className="text-gray-600 dark:text-gray-400 text-sm">
{Math.round(progress)}% completed
</span>
</div>
</div>
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
<motion.div
className="h-2 rounded-full transition-all duration-300"
style={{
width: `${progress}%`,
backgroundColor: stages[currentStage].color
}}
/>
</div>
</div>
{/* 内容区域 */}
<motion.div
className="h-full overflow-auto p-6 relative"
@ -77,7 +185,7 @@ export function ScriptModal({ isOpen, onClose, currentStage = 0 }: ScriptModalPr
animate={{ opacity: 1 }}
transition={{ delay: 0.1, duration: 0.2 }}
>
<WorkOffice initialStage={currentStage} />
<WorkOffice initialStage={currentStage} roles={roles} />
</motion.div>
</motion.div>
</motion.div>

View File

@ -65,30 +65,30 @@ const Editor: React.FC<EditorProps> = ({ currentContent, isPlaying }) => {
{/* 剪辑节奏把控 */}
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span></span>
<IconLoading icon={Scissors} isActive={!currentContent.rhythm && isPlaying} color="#f59e0b" />
<span>Editing rhythm control</span>
<IconLoading icon={Scissors} isActive={isPlaying} color="#f59e0b" />
</h3>
{currentContent.rhythm ? (
<ContentCard className="space-y-3">
<div>
<div className="text-amber-300 text-sm font-medium mb-1"></div>
<div className="text-amber-300 text-sm font-medium mb-1">Rhythm Concept</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={currentContent.rhythm.concept} stableId={`${currentContent.rhythm.stableId}-concept`} />
</div>
</div>
<div>
<div className="text-amber-300 text-sm font-medium mb-1"></div>
<div className="text-amber-300 text-sm font-medium mb-1">Practical Application</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={currentContent.rhythm.application} stableId={`${currentContent.rhythm.stableId}-application`} />
</div>
</div>
<div>
<div className="text-amber-300 text-sm font-medium mb-1"></div>
<div className="text-amber-300 text-sm font-medium mb-1">Current Status</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={currentContent.rhythm.current} stableId={`${currentContent.rhythm.stableId}-current`} />
</div>
</div>
<ProgressBar progress={currentContent.rhythm.progress} color="#f59e0b" label="节奏分析" />
<ProgressBar progress={currentContent.rhythm.progress} color="#f59e0b" label="Rhythm Analysis" />
</ContentCard>
) : (
<div className="space-y-3">
@ -103,8 +103,8 @@ const Editor: React.FC<EditorProps> = ({ currentContent, isPlaying }) => {
{/* 音画关系处理 */}
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span></span>
<IconLoading icon={Scissors} isActive={!currentContent.audioVideo && isPlaying} color="#f59e0b" />
<span>Audio and video relationship processing</span>
<IconLoading icon={Scissors} isActive={isPlaying} color="#f59e0b" />
</h3>
<div className="space-y-3">
{currentContent.audioVideo ? (
@ -118,8 +118,8 @@ const Editor: React.FC<EditorProps> = ({ currentContent, isPlaying }) => {
<TypewriterText text={av.details} stableId={av.stableId} />
</div>
<div className="text-amber-200 text-xs mb-2">
{av.sync && `同步精度: ${av.sync}`}
{av.balance && `平衡策略: ${av.balance}`}
{av.sync && `Synchronization accuracy: ${av.sync}`}
{av.balance && `Balanced Strategy: ${av.balance}`}
</div>
<ProgressBar progress={av.progress} color="#f59e0b" />
</ContentCard>
@ -136,30 +136,30 @@ const Editor: React.FC<EditorProps> = ({ currentContent, isPlaying }) => {
{/* 情绪递进调校 */}
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span></span>
<IconLoading icon={Scissors} isActive={!currentContent.emotionProgression && isPlaying} color="#f59e0b" />
<span>Emotional Progressive Adjustment</span>
<IconLoading icon={Scissors} isActive={isPlaying} color="#f59e0b" />
</h3>
{currentContent.emotionProgression ? (
<ContentCard className="space-y-3">
<div>
<div className="text-amber-300 text-sm font-medium mb-1"></div>
<div className="text-amber-300 text-sm font-medium mb-1">The progressive stage</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={currentContent.emotionProgression.stages} stableId={`${currentContent.emotionProgression.stableId}-stages`} />
</div>
</div>
<div>
<div className="text-amber-300 text-sm font-medium mb-1"></div>
<div className="text-amber-300 text-sm font-medium mb-1">Adjustment Techniques</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={currentContent.emotionProgression.techniques} stableId={`${currentContent.emotionProgression.stableId}-techniques`} />
</div>
</div>
<div>
<div className="text-amber-300 text-sm font-medium mb-1"></div>
<div className="text-amber-300 text-sm font-medium mb-1">Current progress</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={currentContent.emotionProgression.current} stableId={`${currentContent.emotionProgression.stableId}-current`} />
</div>
</div>
<ProgressBar progress={currentContent.emotionProgression.progress} color="#f59e0b" label="情绪调校" />
<ProgressBar progress={currentContent.emotionProgression.progress} color="#f59e0b" label="Emotional Adjustment" />
</ContentCard>
) : (
<div className="space-y-3">
@ -174,8 +174,8 @@ const Editor: React.FC<EditorProps> = ({ currentContent, isPlaying }) => {
{/* 转场效果选择 */}
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span></span>
<IconLoading icon={Scissors} isActive={!currentContent.transitions && isPlaying} color="#f59e0b" />
<span>Transition effect selection</span>
<IconLoading icon={Scissors} isActive={isPlaying} color="#f59e0b" />
</h3>
<div className="space-y-2">
{currentContent.transitions ? (
@ -202,24 +202,24 @@ const Editor: React.FC<EditorProps> = ({ currentContent, isPlaying }) => {
{/* 整体风格统一 */}
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span></span>
<IconLoading icon={Scissors} isActive={!currentContent.styleUnity && isPlaying} color="#f59e0b" />
<span>Unified overall style</span>
<IconLoading icon={Scissors} isActive={isPlaying} color="#f59e0b" />
</h3>
{currentContent.styleUnity ? (
<ContentCard className="space-y-3">
<div>
<div className="text-amber-300 text-sm font-medium mb-1"></div>
<div className="text-amber-300 text-sm font-medium mb-1">Color uniformity</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={currentContent.styleUnity.colorGrading} stableId={`${currentContent.styleUnity.stableId}-colorGrading`} />
</div>
</div>
<div>
<div className="text-amber-300 text-sm font-medium mb-1">线</div>
<div className="text-amber-300 text-sm font-medium mb-1">Tone Curve</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={currentContent.styleUnity.toneCurve} stableId={`${currentContent.styleUnity.stableId}-toneCurve`} />
</div>
</div>
<ProgressBar progress={currentContent.styleUnity.progress} color="#f59e0b" label="风格统一" />
<ProgressBar progress={currentContent.styleUnity.progress} color="#f59e0b" label="Unified style" />
</ContentCard>
) : (
<div className="space-y-3">
@ -234,7 +234,7 @@ const Editor: React.FC<EditorProps> = ({ currentContent, isPlaying }) => {
{/* 最终输出 */}
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span></span>
<span>Final Output</span>
{currentContent.finalOutput ? (
<DotLoading isActive={currentContent.finalOutput.progress < 100} color="#f59e0b" size="medium" />
) : (
@ -246,9 +246,9 @@ const Editor: React.FC<EditorProps> = ({ currentContent, isPlaying }) => {
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
{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]) => (
<div key={key} className="flex justify-between text-xs">
<span className="text-gray-400">{key}:</span>
@ -258,9 +258,9 @@ const Editor: React.FC<EditorProps> = ({ currentContent, isPlaying }) => {
</div>
<div className="space-y-2">
{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]) => (
<div key={key} className="flex justify-between text-xs">
<span className="text-gray-400">{key}:</span>
@ -270,7 +270,7 @@ const Editor: React.FC<EditorProps> = ({ currentContent, isPlaying }) => {
</div>
</div>
<div className="mt-4">
<ProgressBar progress={currentContent.finalOutput.progress} color="#f59e0b" label="输出进度" />
<ProgressBar progress={currentContent.finalOutput.progress} color="#f59e0b" label="Output progress" />
</div>
</>
) : (

View File

@ -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
}
};

View File

@ -58,7 +58,7 @@ const CustomTooltip = ({ active, payload, label }: any) => {
return (
<div className="bg-black/80 backdrop-blur-sm p-2 rounded-lg border border-purple-500/30">
<p className="text-purple-300 text-xs font-medium">{payload[0].payload.title}</p>
<p className="text-white text-xs">{`情感强度: ${payload[0].value}`}</p>
<p className="text-white text-xs">{`Emotional intensity: ${payload[0].value}`}</p>
</div>
);
}
@ -73,8 +73,8 @@ const Scriptwriter: React.FC<ScriptwriterProps> = ({ currentContent, isPlaying }
{/* 三幕结构 */}
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span></span>
<IconLoading icon={Heart} isActive={!currentContent.acts && isPlaying} color="#8b5cf6" />
<span>Three-act structure</span>
<IconLoading icon={Heart} isActive={isPlaying} color="#8b5cf6" />
</h3>
<div className="space-y-3">
{currentContent.acts && currentContent.acts.length > 0 ? (
@ -109,8 +109,8 @@ const Scriptwriter: React.FC<ScriptwriterProps> = ({ currentContent, isPlaying }
{/* 角色弧光设计 */}
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span></span>
<IconLoading icon={Heart} isActive={!currentContent.characters && isPlaying} color="#8b5cf6" />
<span>Character arc design</span>
<IconLoading icon={Heart} isActive={isPlaying} color="#8b5cf6" />
</h3>
<div className="space-y-3">
{currentContent.characters && currentContent.characters.length > 0 ? (
@ -149,19 +149,19 @@ const Scriptwriter: React.FC<ScriptwriterProps> = ({ currentContent, isPlaying }
{/* 对白节奏感 */}
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span></span>
<IconLoading icon={Heart} isActive={!currentContent.dialogue && isPlaying} color="#8b5cf6" />
<span>Dialogue rhythm</span>
<IconLoading icon={Heart} isActive={isPlaying} color="#8b5cf6" />
</h3>
{currentContent.dialogue ? (
<div className="space-y-3">
<div>
<div className="text-purple-300 text-sm font-medium mb-1"></div>
<div className="text-purple-300 text-sm font-medium mb-1">Rhythm control</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={currentContent.dialogue.rhythm} stableId={`${currentContent.dialogue.stableId}-rhythm`} />
</div>
</div>
<div>
<div className="text-purple-300 text-sm font-medium mb-1"></div>
<div className="text-purple-300 text-sm font-medium mb-1">Expression style</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={currentContent.dialogue.style} stableId={`${currentContent.dialogue.stableId}-style`} />
</div>
@ -175,8 +175,8 @@ const Scriptwriter: React.FC<ScriptwriterProps> = ({ currentContent, isPlaying }
{/* 主题深化过程 */}
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span></span>
<IconLoading icon={Heart} isActive={!currentContent.themes && isPlaying} color="#8b5cf6" />
<span>Theme deepening process</span>
<IconLoading icon={Heart} isActive={isPlaying} color="#8b5cf6" />
</h3>
<div className="space-y-3">
{currentContent.themes ? (
@ -207,8 +207,8 @@ const Scriptwriter: React.FC<ScriptwriterProps> = ({ currentContent, isPlaying }
{/* 剧情起伏戏演线 */}
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span>线</span>
<IconLoading icon={Heart} isActive={!currentContent.dramaticLine && isPlaying} color="#8b5cf6" />
<span>Dramatic line</span>
<IconLoading icon={Heart} isActive={isPlaying} color="#8b5cf6" />
</h3>
{currentContent.dramaticLine ? (
<div className="space-y-4">
@ -230,7 +230,7 @@ const Scriptwriter: React.FC<ScriptwriterProps> = ({ currentContent, isPlaying }
tick={{ fill: '#9CA3AF', fontSize: 10 }}
stroke="#4B5563"
label={{
value: '情感强度',
value: 'Emotional intensity',
angle: -90,
position: 'insideLeft',
fill: '#9CA3AF',

View File

@ -53,8 +53,8 @@ const StoryboardArtist: React.FC<StoryboardArtistProps> = ({ currentContent, isP
{/* 镜头语言选择 */}
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span></span>
<IconLoading icon={Camera} isActive={!currentContent.shotLanguage && isPlaying} color="#06b6d4" />
<span>Shot language selection</span>
<IconLoading icon={Camera} isActive={isPlaying} color="#06b6d4" />
</h3>
<div className="space-y-3">
{currentContent.shotLanguage && currentContent.shotLanguage.length > 0 ? (
@ -83,25 +83,25 @@ const StoryboardArtist: React.FC<StoryboardArtistProps> = ({ currentContent, isP
{/* 构图美学运用 */}
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span></span>
<IconLoading icon={Camera} isActive={!currentContent.composition && isPlaying} color="#06b6d4" />
<span>Composition aesthetics</span>
<IconLoading icon={Camera} isActive={isPlaying} color="#06b6d4" />
</h3>
{currentContent.composition ? (
<ContentCard className="space-y-3">
<div>
<div className="text-cyan-300 text-sm font-medium mb-1"></div>
<div className="text-cyan-300 text-sm font-medium mb-1">Composition principles</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={currentContent.composition.principles} stableId={`${currentContent.composition.stableId}-principles`} />
</div>
</div>
<div>
<div className="text-cyan-300 text-sm font-medium mb-1"></div>
<div className="text-cyan-300 text-sm font-medium mb-1">Aesthetics expression</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={currentContent.composition.aesthetics} stableId={`${currentContent.composition.stableId}-aesthetics`} />
</div>
</div>
<div>
<div className="text-cyan-300 text-sm font-medium mb-1"></div>
<div className="text-cyan-300 text-sm font-medium mb-1">Frame setting</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={currentContent.composition.framing} stableId={`${currentContent.composition.stableId}-framing`} />
</div>
@ -122,8 +122,8 @@ const StoryboardArtist: React.FC<StoryboardArtistProps> = ({ currentContent, isP
{/* 摄影机运动设计 */}
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span></span>
<IconLoading icon={Camera} isActive={!currentContent.cameraMovement && isPlaying} color="#06b6d4" />
<span>Camera movement design</span>
<IconLoading icon={Camera} isActive={isPlaying} color="#06b6d4" />
</h3>
<div className="space-y-3">
{currentContent.cameraMovement ? (
@ -152,25 +152,25 @@ const StoryboardArtist: React.FC<StoryboardArtistProps> = ({ currentContent, isP
{/* 视觉叙事逻辑 */}
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span></span>
<IconLoading icon={Camera} isActive={!currentContent.visualNarrative && isPlaying} color="#06b6d4" />
<span>Visual storytelling logic</span>
<IconLoading icon={Camera} isActive={isPlaying} color="#06b6d4" />
</h3>
{currentContent.visualNarrative ? (
<ContentCard className="space-y-3">
<div>
<div className="text-cyan-300 text-sm font-medium mb-1"></div>
<div className="text-cyan-300 text-sm font-medium mb-1">Storytelling logic</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={currentContent.visualNarrative.logic} stableId={`${currentContent.visualNarrative.stableId}-logic`} />
</div>
</div>
<div>
<div className="text-cyan-300 text-sm font-medium mb-1"></div>
<div className="text-cyan-300 text-sm font-medium mb-1">Progression</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={currentContent.visualNarrative.progression} stableId={`${currentContent.visualNarrative.stableId}-progression`} />
</div>
</div>
<div>
<div className="text-cyan-300 text-sm font-medium mb-1"></div>
<div className="text-cyan-300 text-sm font-medium mb-1">Key emphasis</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={currentContent.visualNarrative.emphasis} stableId={`${currentContent.visualNarrative.stableId}-emphasis`} />
</div>
@ -188,8 +188,8 @@ const StoryboardArtist: React.FC<StoryboardArtistProps> = ({ currentContent, isP
{/* 剪辑点预设 */}
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span></span>
<IconLoading icon={Camera} isActive={!currentContent.editingPoints && isPlaying} color="#06b6d4" />
<span>Editing point preset</span>
<IconLoading icon={Camera} isActive={isPlaying} color="#06b6d4" />
</h3>
<div className="space-y-2">
{currentContent.editingPoints ? (

View File

@ -63,8 +63,8 @@ const VisualDirector: React.FC<VisualDirectorProps> = ({ currentContent, isPlayi
{/* 画面构成要素 */}
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span></span>
<IconLoading icon={Film} isActive={!currentContent.composition && isPlaying} color="#10b981" />
<span>Element of composition</span>
<IconLoading icon={Film} isActive={isPlaying} color="#10b981" />
</h3>
<div className="space-y-3">
{currentContent.composition && currentContent.composition.length > 0 ? (
@ -94,30 +94,30 @@ const VisualDirector: React.FC<VisualDirectorProps> = ({ currentContent, isPlayi
{/* 光影氛围营造 */}
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span></span>
<IconLoading icon={Film} isActive={!currentContent.lighting && isPlaying} color="#10b981" />
<span>Lighting atmosphere</span>
<IconLoading icon={Film} isActive={isPlaying} color="#10b981" />
</h3>
{currentContent.lighting ? (
<ContentCard className="space-y-3">
<div>
<div className="text-emerald-300 text-sm font-medium mb-1"></div>
<div className="text-emerald-300 text-sm font-medium mb-1">Environment light design</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={currentContent.lighting.ambient} stableId={`${currentContent.lighting.stableId}-ambient`} />
</div>
</div>
<div>
<div className="text-emerald-300 text-sm font-medium mb-1"></div>
<div className="text-emerald-300 text-sm font-medium mb-1">Artificial light layout</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={currentContent.lighting.artificial} stableId={`${currentContent.lighting.stableId}-artificial`} />
</div>
</div>
<div>
<div className="text-emerald-300 text-sm font-medium mb-1"></div>
<div className="text-emerald-300 text-sm font-medium mb-1">Emotional atmosphere</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={currentContent.lighting.mood} stableId={`${currentContent.lighting.stableId}-mood`} />
</div>
</div>
<ProgressBar progress={currentContent.lighting.progress} color="#10b981" label="光影渲染" />
<ProgressBar progress={currentContent.lighting.progress} color="#10b981" label="Lighting rendering" />
</ContentCard>
) : (
<div className="space-y-3">
@ -135,8 +135,8 @@ const VisualDirector: React.FC<VisualDirectorProps> = ({ currentContent, isPlayi
{/* 角色表演捕捉 */}
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span></span>
<IconLoading icon={Film} isActive={!currentContent.performance && isPlaying} color="#10b981" />
<span>Character performance capture</span>
<IconLoading icon={Film} isActive={isPlaying} color="#10b981" />
</h3>
<div className="space-y-3">
{currentContent.performance ? (
@ -164,30 +164,30 @@ const VisualDirector: React.FC<VisualDirectorProps> = ({ currentContent, isPlayi
{/* 场景细节渲染 */}
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span></span>
<IconLoading icon={Film} isActive={!currentContent.sceneDetails && isPlaying} color="#10b981" />
<span>Scene detail rendering</span>
<IconLoading icon={Film} isActive={isPlaying} color="#10b981" />
</h3>
{currentContent.sceneDetails ? (
<ContentCard className="space-y-3">
<div>
<div className="text-emerald-300 text-sm font-medium mb-1"></div>
<div className="text-emerald-300 text-sm font-medium mb-1">Material texture</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={currentContent.sceneDetails.textures} stableId={`${currentContent.sceneDetails.stableId}-textures`} />
</div>
</div>
<div>
<div className="text-emerald-300 text-sm font-medium mb-1"></div>
<div className="text-emerald-300 text-sm font-medium mb-1">Object details</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={currentContent.sceneDetails.objects} stableId={`${currentContent.sceneDetails.stableId}-objects`} />
</div>
</div>
<div>
<div className="text-emerald-300 text-sm font-medium mb-1"></div>
<div className="text-emerald-300 text-sm font-medium mb-1">Atmospheric effect</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={currentContent.sceneDetails.atmosphere} stableId={`${currentContent.sceneDetails.stableId}-atmosphere`} />
</div>
</div>
<ProgressBar progress={currentContent.sceneDetails.progress} color="#10b981" label="细节渲染" />
<ProgressBar progress={currentContent.sceneDetails.progress} color="#10b981" label="Detail rendering" />
</ContentCard>
) : (
<div className="space-y-3">
@ -202,8 +202,8 @@ const VisualDirector: React.FC<VisualDirectorProps> = ({ currentContent, isPlayi
{/* 技术参数控制 */}
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span></span>
<IconLoading icon={Film} isActive={!currentContent.technical && isPlaying} color="#10b981" />
<span>Technical parameter control</span>
<IconLoading icon={Film} isActive={isPlaying} color="#10b981" />
</h3>
{currentContent.technical ? (
<>
@ -230,14 +230,14 @@ const VisualDirector: React.FC<VisualDirectorProps> = ({ currentContent, isPlayi
<div className="text-2xl font-mono text-emerald-400 mb-1">
{Math.round((currentContent.renderOutput.currentFrame / currentContent.renderOutput.totalFrames) * 100)}%
</div>
<div className="text-sm text-gray-400"></div>
<div className="text-sm text-gray-400">Rendering completed</div>
</div>
<div className="text-xs text-center space-y-1">
<div className="text-white">
: {currentContent.renderOutput.currentFrame}/{currentContent.renderOutput.totalFrames}
Frame: {currentContent.renderOutput.currentFrame}/{currentContent.renderOutput.totalFrames}
</div>
<div className="text-emerald-400">
: {currentContent.renderOutput.quality} | {currentContent.renderOutput.estimated}
Quality: {currentContent.renderOutput.quality} | {currentContent.renderOutput.estimated}
</div>
</div>
</div>

View File

@ -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<WorkOfficeProps> = ({ 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<Record<string, any>>(scriptwriterData);
const [thinkingText, setThinkingText] = useState(`${stages[0].profession}正在思考...`);
const [startTime, setStartTime] = useState<number | null>(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<WorkOfficeProps> = ({ 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<WorkOfficeProps> = ({ 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 (
<div className="h-full rounded-2xl overflow-hidden shadow-2xl relative">
{/* 正在加载的部分 文字显示 */}
@ -195,58 +149,9 @@ const WorkOffice: React.FC<WorkOfficeProps> = ({ initialStage = 0 }) => {
</div>
{/* 工作台内容区域 */}
<div className="absolute left-0 right-0 top-[2rem] w-full aspect-video overflow-y-auto" style={{height: 'calc(100% - 11rem'}}>
<div className="absolute left-0 right-0 top-[2rem] w-full aspect-video overflow-y-auto" style={{height: 'calc(100% - 7rem'}}>
{renderCurrentWorkstation()}
</div>
{/* 底部控制栏 */}
<div className="p-6 absolute bottom-0 left-0 right-0">
<div className="mb-6">
<div className="flex items-center justify-between mb-3">
<span className="text-white font-medium">
{stages[currentStage].title}
</span>
<div className="flex items-center space-x-4">
<span className="text-gray-400 text-sm">
: {getRemainingTime()}
</span>
<span className="text-gray-400 text-sm">
{Math.round(progress)}%
</span>
</div>
</div>
<div className="w-full bg-gray-700 rounded-full h-3">
<motion.div
className="h-3 rounded-full transition-all duration-300"
style={{
width: `${progress}%`,
backgroundColor: stages[currentStage].color
}}
/>
</div>
</div>
{/* 工作台切换按钮组 */}
<div className="flex justify-center space-x-4">
{stages.map((stage, index) => (
<button
key={stage.id}
onClick={() => {
if (!isPlaying) {
setCurrentStage(index);
}
}}
disabled={isPlaying}
className={`w-12 h-12 rounded-full flex items-center justify-center transition-all ${
currentStage === index ? 'ring-2 ring-white/50' : 'hover:bg-white/10'
} ${isPlaying ? 'opacity-50 cursor-not-allowed' : ''}`}
style={{ backgroundColor: `${stage.color}40` }}
>
<stage.icon className="w-6 h-6 text-white" />
</button>
))}
</div>
</div>
</div>
);
};