forked from 77media/video-flow
阶段loading
This commit is contained in:
parent
74e678767e
commit
f135938d59
@ -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>
|
||||
)}
|
||||
|
||||
@ -117,6 +117,7 @@ export default function WorkFlow() {
|
||||
taskObject={taskObject}
|
||||
currentLoadingText={currentLoadingText}
|
||||
dataLoadError={dataLoadError}
|
||||
roles={roles}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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' ? (
|
||||
|
||||
@ -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>
|
||||
)}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
</>
|
||||
) : (
|
||||
|
||||
@ -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
|
||||
}
|
||||
};
|
||||
@ -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',
|
||||
|
||||
@ -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 ? (
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user