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' :
|
episode.status !== 'COMPLETED' ? 'bg-yellow-500/80 text-white' :
|
||||||
'bg-gray-500/80 text-white'
|
'bg-gray-500/80 text-white'
|
||||||
}`}>
|
}`}>
|
||||||
{episode.status === 'COMPLETED' ? '已完成' : '进行中'}
|
{episode.status === 'COMPLETED' ? 'finished' : 'processing'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -439,7 +439,7 @@ export function CreateToVideo2() {
|
|||||||
{/* 内容区域 */}
|
{/* 内容区域 */}
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<h3 className="text-white font-medium text-sm mb-2 line-clamp-2 group-hover:text-blue-300 transition-colors">
|
<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>
|
</h3>
|
||||||
|
|
||||||
{/* 元数据 */}
|
{/* 元数据 */}
|
||||||
@ -518,7 +518,7 @@ export function CreateToVideo2() {
|
|||||||
<div className="flex justify-center py-12">
|
<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">
|
<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" />
|
<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>
|
||||||
</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">
|
<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" />
|
<Check className="w-6 h-6 text-green-400" />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-white/50 text-sm">已加载全部剧集</p>
|
<p className="text-white/50 text-sm">All episodes loaded</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -117,6 +117,7 @@ export default function WorkFlow() {
|
|||||||
taskObject={taskObject}
|
taskObject={taskObject}
|
||||||
currentLoadingText={currentLoadingText}
|
currentLoadingText={currentLoadingText}
|
||||||
dataLoadError={dataLoadError}
|
dataLoadError={dataLoadError}
|
||||||
|
roles={roles}
|
||||||
/>
|
/>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -358,7 +358,7 @@ export function MediaViewer({
|
|||||||
ease: "easeInOut"
|
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>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -735,7 +735,7 @@ export function MediaViewer({
|
|||||||
<img
|
<img
|
||||||
key={currentSketchIndex}
|
key={currentSketchIndex}
|
||||||
src={currentSketch.url}
|
src={currentSketch.url}
|
||||||
alt={`Sketch ${currentSketchIndex + 1}`}
|
alt={`NG-Sketch ${currentSketchIndex + 1}`}
|
||||||
className="w-full h-full rounded-lg object-cover"
|
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();
|
return renderFinalVideo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
import React, { useState, useEffect, useMemo, useRef } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { ScriptModal } from '@/components/ui/script-modal';
|
import { ScriptModal } from '@/components/ui/script-modal';
|
||||||
import {
|
import {
|
||||||
@ -20,10 +20,7 @@ interface TaskInfoProps {
|
|||||||
taskObject: any;
|
taskObject: any;
|
||||||
currentLoadingText: string;
|
currentLoadingText: string;
|
||||||
dataLoadError?: string | null;
|
dataLoadError?: string | null;
|
||||||
currentStep: string;
|
roles: any[];
|
||||||
sketchCount: number;
|
|
||||||
isGeneratingVideo: boolean;
|
|
||||||
taskVideos: any[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据加载文本返回对应的图标
|
// 根据加载文本返回对应的图标
|
||||||
@ -56,128 +53,106 @@ export function TaskInfo({
|
|||||||
taskObject,
|
taskObject,
|
||||||
currentLoadingText,
|
currentLoadingText,
|
||||||
dataLoadError,
|
dataLoadError,
|
||||||
currentStep,
|
roles
|
||||||
sketchCount,
|
|
||||||
isGeneratingVideo,
|
|
||||||
taskVideos
|
|
||||||
}: TaskInfoProps) {
|
}: TaskInfoProps) {
|
||||||
const StageIcon = getStageIcon(currentLoadingText);
|
const StageIcon = getStageIcon(currentLoadingText);
|
||||||
const [isScriptModalOpen, setIsScriptModalOpen] = useState(false);
|
const [isScriptModalOpen, setIsScriptModalOpen] = useState(false);
|
||||||
const [currentStage, setCurrentStage] = useState(0);
|
const [currentStage, setCurrentStage] = useState(0);
|
||||||
const prevSketchCountRef = useRef(sketchCount);
|
const [isShowScriptIcon, setIsShowScriptIcon] = useState(true);
|
||||||
const prevIsGeneratingVideoRef = useRef(isGeneratingVideo);
|
|
||||||
const prevTaskVideosLengthRef = useRef(taskVideos?.length || 0);
|
|
||||||
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
// 清理定时器
|
// 监听 currentLoadingText
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
// 清理之前的定时器
|
||||||
if (timerRef.current) {
|
|
||||||
clearTimeout(timerRef.current);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 监听 currentStep 为 '1' 的情况
|
|
||||||
useEffect(() => {
|
|
||||||
console.log('Current Step Effect:', { currentStep, hasTitle: !!taskObject?.title });
|
|
||||||
|
|
||||||
// 清除之前的定时器
|
|
||||||
if (timerRef.current) {
|
if (timerRef.current) {
|
||||||
clearTimeout(timerRef.current);
|
clearTimeout(timerRef.current);
|
||||||
timerRef.current = null;
|
timerRef.current = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentStep === '1' && taskObject?.title) {
|
if (currentLoadingText.includes('Task completed')) {
|
||||||
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') {
|
|
||||||
console.log('Closing modal at completion');
|
console.log('Closing modal at completion');
|
||||||
setIsScriptModalOpen(false);
|
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);
|
||||||
|
|
||||||
// 弹窗状态变化时的日志
|
// 延迟8s 再次打开
|
||||||
useEffect(() => {
|
timerRef.current = setTimeout(() => {
|
||||||
console.log('Modal State Changed:', {
|
setIsScriptModalOpen(true);
|
||||||
isOpen: isScriptModalOpen,
|
setCurrentStage(2);
|
||||||
currentStage,
|
}, 8000);
|
||||||
currentStep,
|
} else {
|
||||||
sketchCount
|
setIsScriptModalOpen(true);
|
||||||
});
|
setCurrentStage(2);
|
||||||
}, [isScriptModalOpen, currentStage, currentStep, sketchCount]);
|
}
|
||||||
|
}
|
||||||
|
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 缓存标签颜色映射
|
// 使用 useMemo 缓存标签颜色映射
|
||||||
const tagColors = useMemo(() => {
|
const tagColors = useMemo(() => {
|
||||||
@ -309,15 +284,17 @@ export function TaskInfo({
|
|||||||
<span>
|
<span>
|
||||||
{taskObject?.title || 'loading project info...'}
|
{taskObject?.title || 'loading project info...'}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex items-center gap-2">
|
{isShowScriptIcon && (
|
||||||
<motion.div
|
<div className="flex items-center gap-2">
|
||||||
whileHover={{ scale: 1.1 }}
|
<motion.div
|
||||||
|
whileHover={{ scale: 1.1 }}
|
||||||
whileTap={{ scale: 0.9 }}
|
whileTap={{ scale: 0.9 }}
|
||||||
onClick={() => setIsScriptModalOpen(true)}
|
onClick={() => setIsScriptModalOpen(true)}
|
||||||
>
|
>
|
||||||
<Airplay className="w-4 h-4 text-blue-500 cursor-pointer" />
|
<Airplay className="w-4 h-4 text-blue-500 cursor-pointer" />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : 'loading project info...'}
|
) : 'loading project info...'}
|
||||||
</div>
|
</div>
|
||||||
@ -342,6 +319,7 @@ export function TaskInfo({
|
|||||||
setIsScriptModalOpen(false);
|
setIsScriptModalOpen(false);
|
||||||
}}
|
}}
|
||||||
currentStage={currentStage}
|
currentStage={currentStage}
|
||||||
|
roles={roles}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{currentLoadingText === 'Task completed' ? (
|
{currentLoadingText === 'Task completed' ? (
|
||||||
|
|||||||
@ -316,7 +316,7 @@ export function ThumbnailGrid({
|
|||||||
<img
|
<img
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
src={sketch.url}
|
src={sketch.url}
|
||||||
alt={`Thumbnail ${index + 1}`}
|
alt={`NG ${index + 1}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ProgressiveReveal>
|
</ProgressiveReveal>
|
||||||
@ -326,7 +326,7 @@ export function ThumbnailGrid({
|
|||||||
<img
|
<img
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
src={sketch.url}
|
src={sketch.url}
|
||||||
alt={`Thumbnail ${index + 1}`}
|
alt={`NG ${index + 1}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -247,6 +247,17 @@ export function useWorkflowData() {
|
|||||||
loadingText = LOADING_TEXT_MAP.postProduction('post-production...');
|
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_name === 'generate_final_video') {
|
||||||
if (task.task_result && task.task_result.video) {
|
if (task.task_result && task.task_result.video) {
|
||||||
setFinal({
|
setFinal({
|
||||||
@ -414,33 +425,45 @@ export function useWorkflowData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const realDataVideoData = data.video.data.filter((item: any) => item.urls && item.urls.length > 0);
|
if (data.video.data) {
|
||||||
if (realDataVideoData.length > 0) {
|
const realDataVideoData = data.video.data.filter((item: any) => item.urls && item.urls.length > 0);
|
||||||
const videoList = [];
|
if (realDataVideoData.length > 0) {
|
||||||
console.log('----------data.video.data', data.video.data);
|
const videoList = [];
|
||||||
for (const video of realDataVideoData) {
|
console.log('----------data.video.data', data.video.data);
|
||||||
// 每一项 video 有多个视频 默认取存在的项
|
for (const video of realDataVideoData) {
|
||||||
videoList.push({
|
// 每一项 video 有多个视频 默认取存在的项
|
||||||
url: video.urls && video.urls.length > 0 ? video.urls.find((url: string) => url) : null,
|
videoList.push({
|
||||||
script: video.description,
|
url: video.urls && video.urls.length > 0 ? video.urls.find((url: string) => url) : null,
|
||||||
audio: null,
|
script: video.description,
|
||||||
});
|
audio: null,
|
||||||
}
|
});
|
||||||
setTaskVideos(videoList);
|
}
|
||||||
// 如果在视频步骤,设置为最后一个视频
|
setTaskVideos(videoList);
|
||||||
if (data.video.total_count > realDataVideoData.length) {
|
// 如果在视频步骤,设置为最后一个视频
|
||||||
setIsGeneratingVideo(true);
|
if (data.video.total_count > realDataVideoData.length) {
|
||||||
setCurrentSketchIndex(realDataVideoData.length - 1);
|
setIsGeneratingVideo(true);
|
||||||
loadingText = LOADING_TEXT_MAP.video(realDataVideoData.length, data.video.total_count);
|
setCurrentSketchIndex(realDataVideoData.length - 1);
|
||||||
} else {
|
loadingText = LOADING_TEXT_MAP.video(realDataVideoData.length, data.video.total_count);
|
||||||
finalStep = '4';
|
} else {
|
||||||
loadingText = LOADING_TEXT_MAP.audio;
|
finalStep = '4';
|
||||||
|
loadingText = LOADING_TEXT_MAP.audio;
|
||||||
|
|
||||||
// 暂时没有音频生成 直接跳过
|
// 暂时没有音频生成 直接跳过
|
||||||
finalStep = '5';
|
finalStep = '5';
|
||||||
loadingText = LOADING_TEXT_MAP.postProduction('post-production...');
|
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) {
|
if (data.final_video && data.final_video.video) {
|
||||||
setFinal({
|
setFinal({
|
||||||
url: data.final_video.video
|
url: data.final_video.video
|
||||||
|
|||||||
@ -3,14 +3,96 @@
|
|||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { X } from 'lucide-react';
|
import { X } from 'lucide-react';
|
||||||
import WorkOffice from '@/components/workflow/work-office/work-office';
|
import WorkOffice from '@/components/workflow/work-office/work-office';
|
||||||
|
import { useState, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
interface ScriptModalProps {
|
interface ScriptModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
currentStage?: number;
|
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 (
|
return (
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
{isOpen && (
|
{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" />
|
<X className="w-5 h-5 text-gray-600 dark:text-gray-300" />
|
||||||
</motion.button>
|
</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
|
<motion.div
|
||||||
className="h-full overflow-auto p-6 relative"
|
className="h-full overflow-auto p-6 relative"
|
||||||
@ -77,7 +185,7 @@ export function ScriptModal({ isOpen, onClose, currentStage = 0 }: ScriptModalPr
|
|||||||
animate={{ opacity: 1 }}
|
animate={{ opacity: 1 }}
|
||||||
transition={{ delay: 0.1, duration: 0.2 }}
|
transition={{ delay: 0.1, duration: 0.2 }}
|
||||||
>
|
>
|
||||||
<WorkOffice initialStage={currentStage} />
|
<WorkOffice initialStage={currentStage} roles={roles} />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</motion.div>
|
</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">
|
<div className="bg-black/30 rounded-lg p-4">
|
||||||
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
||||||
<span>剪辑节奏把控</span>
|
<span>Editing rhythm control</span>
|
||||||
<IconLoading icon={Scissors} isActive={!currentContent.rhythm && isPlaying} color="#f59e0b" />
|
<IconLoading icon={Scissors} isActive={isPlaying} color="#f59e0b" />
|
||||||
</h3>
|
</h3>
|
||||||
{currentContent.rhythm ? (
|
{currentContent.rhythm ? (
|
||||||
<ContentCard className="space-y-3">
|
<ContentCard className="space-y-3">
|
||||||
<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">Rhythm Concept</div>
|
||||||
<div className="text-gray-300 text-xs">
|
<div className="text-gray-300 text-xs">
|
||||||
<TypewriterText text={currentContent.rhythm.concept} stableId={`${currentContent.rhythm.stableId}-concept`} />
|
<TypewriterText text={currentContent.rhythm.concept} stableId={`${currentContent.rhythm.stableId}-concept`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
<div className="text-gray-300 text-xs">
|
||||||
<TypewriterText text={currentContent.rhythm.application} stableId={`${currentContent.rhythm.stableId}-application`} />
|
<TypewriterText text={currentContent.rhythm.application} stableId={`${currentContent.rhythm.stableId}-application`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
<div className="text-gray-300 text-xs">
|
||||||
<TypewriterText text={currentContent.rhythm.current} stableId={`${currentContent.rhythm.stableId}-current`} />
|
<TypewriterText text={currentContent.rhythm.current} stableId={`${currentContent.rhythm.stableId}-current`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ProgressBar progress={currentContent.rhythm.progress} color="#f59e0b" label="节奏分析" />
|
<ProgressBar progress={currentContent.rhythm.progress} color="#f59e0b" label="Rhythm Analysis" />
|
||||||
</ContentCard>
|
</ContentCard>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-3">
|
<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">
|
<div className="bg-black/30 rounded-lg p-4">
|
||||||
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
||||||
<span>音画关系处理</span>
|
<span>Audio and video relationship processing</span>
|
||||||
<IconLoading icon={Scissors} isActive={!currentContent.audioVideo && isPlaying} color="#f59e0b" />
|
<IconLoading icon={Scissors} isActive={isPlaying} color="#f59e0b" />
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{currentContent.audioVideo ? (
|
{currentContent.audioVideo ? (
|
||||||
@ -118,8 +118,8 @@ const Editor: React.FC<EditorProps> = ({ currentContent, isPlaying }) => {
|
|||||||
<TypewriterText text={av.details} stableId={av.stableId} />
|
<TypewriterText text={av.details} stableId={av.stableId} />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-amber-200 text-xs mb-2">
|
<div className="text-amber-200 text-xs mb-2">
|
||||||
{av.sync && `同步精度: ${av.sync}`}
|
{av.sync && `Synchronization accuracy: ${av.sync}`}
|
||||||
{av.balance && `平衡策略: ${av.balance}`}
|
{av.balance && `Balanced Strategy: ${av.balance}`}
|
||||||
</div>
|
</div>
|
||||||
<ProgressBar progress={av.progress} color="#f59e0b" />
|
<ProgressBar progress={av.progress} color="#f59e0b" />
|
||||||
</ContentCard>
|
</ContentCard>
|
||||||
@ -136,30 +136,30 @@ const Editor: React.FC<EditorProps> = ({ currentContent, isPlaying }) => {
|
|||||||
{/* 情绪递进调校 */}
|
{/* 情绪递进调校 */}
|
||||||
<div className="bg-black/30 rounded-lg p-4">
|
<div className="bg-black/30 rounded-lg p-4">
|
||||||
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
||||||
<span>情绪递进调校</span>
|
<span>Emotional Progressive Adjustment</span>
|
||||||
<IconLoading icon={Scissors} isActive={!currentContent.emotionProgression && isPlaying} color="#f59e0b" />
|
<IconLoading icon={Scissors} isActive={isPlaying} color="#f59e0b" />
|
||||||
</h3>
|
</h3>
|
||||||
{currentContent.emotionProgression ? (
|
{currentContent.emotionProgression ? (
|
||||||
<ContentCard className="space-y-3">
|
<ContentCard className="space-y-3">
|
||||||
<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">The progressive stage</div>
|
||||||
<div className="text-gray-300 text-xs">
|
<div className="text-gray-300 text-xs">
|
||||||
<TypewriterText text={currentContent.emotionProgression.stages} stableId={`${currentContent.emotionProgression.stableId}-stages`} />
|
<TypewriterText text={currentContent.emotionProgression.stages} stableId={`${currentContent.emotionProgression.stableId}-stages`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
<div className="text-gray-300 text-xs">
|
||||||
<TypewriterText text={currentContent.emotionProgression.techniques} stableId={`${currentContent.emotionProgression.stableId}-techniques`} />
|
<TypewriterText text={currentContent.emotionProgression.techniques} stableId={`${currentContent.emotionProgression.stableId}-techniques`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
<div className="text-gray-300 text-xs">
|
||||||
<TypewriterText text={currentContent.emotionProgression.current} stableId={`${currentContent.emotionProgression.stableId}-current`} />
|
<TypewriterText text={currentContent.emotionProgression.current} stableId={`${currentContent.emotionProgression.stableId}-current`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ProgressBar progress={currentContent.emotionProgression.progress} color="#f59e0b" label="情绪调校" />
|
<ProgressBar progress={currentContent.emotionProgression.progress} color="#f59e0b" label="Emotional Adjustment" />
|
||||||
</ContentCard>
|
</ContentCard>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-3">
|
<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">
|
<div className="bg-black/30 rounded-lg p-4">
|
||||||
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
||||||
<span>转场效果选择</span>
|
<span>Transition effect selection</span>
|
||||||
<IconLoading icon={Scissors} isActive={!currentContent.transitions && isPlaying} color="#f59e0b" />
|
<IconLoading icon={Scissors} isActive={isPlaying} color="#f59e0b" />
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{currentContent.transitions ? (
|
{currentContent.transitions ? (
|
||||||
@ -202,24 +202,24 @@ const Editor: React.FC<EditorProps> = ({ currentContent, isPlaying }) => {
|
|||||||
{/* 整体风格统一 */}
|
{/* 整体风格统一 */}
|
||||||
<div className="bg-black/30 rounded-lg p-4">
|
<div className="bg-black/30 rounded-lg p-4">
|
||||||
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
||||||
<span>整体风格统一</span>
|
<span>Unified overall style</span>
|
||||||
<IconLoading icon={Scissors} isActive={!currentContent.styleUnity && isPlaying} color="#f59e0b" />
|
<IconLoading icon={Scissors} isActive={isPlaying} color="#f59e0b" />
|
||||||
</h3>
|
</h3>
|
||||||
{currentContent.styleUnity ? (
|
{currentContent.styleUnity ? (
|
||||||
<ContentCard className="space-y-3">
|
<ContentCard className="space-y-3">
|
||||||
<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">Color uniformity</div>
|
||||||
<div className="text-gray-300 text-xs">
|
<div className="text-gray-300 text-xs">
|
||||||
<TypewriterText text={currentContent.styleUnity.colorGrading} stableId={`${currentContent.styleUnity.stableId}-colorGrading`} />
|
<TypewriterText text={currentContent.styleUnity.colorGrading} stableId={`${currentContent.styleUnity.stableId}-colorGrading`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
<div className="text-gray-300 text-xs">
|
||||||
<TypewriterText text={currentContent.styleUnity.toneCurve} stableId={`${currentContent.styleUnity.stableId}-toneCurve`} />
|
<TypewriterText text={currentContent.styleUnity.toneCurve} stableId={`${currentContent.styleUnity.stableId}-toneCurve`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ProgressBar progress={currentContent.styleUnity.progress} color="#f59e0b" label="风格统一" />
|
<ProgressBar progress={currentContent.styleUnity.progress} color="#f59e0b" label="Unified style" />
|
||||||
</ContentCard>
|
</ContentCard>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-3">
|
<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">
|
<div className="bg-black/30 rounded-lg p-4">
|
||||||
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
||||||
<span>最终输出</span>
|
<span>Final Output</span>
|
||||||
{currentContent.finalOutput ? (
|
{currentContent.finalOutput ? (
|
||||||
<DotLoading isActive={currentContent.finalOutput.progress < 100} color="#f59e0b" size="medium" />
|
<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="grid grid-cols-2 gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{Object.entries({
|
{Object.entries({
|
||||||
'格式': currentContent.finalOutput.format,
|
'Format': currentContent.finalOutput.format,
|
||||||
'分辨率': currentContent.finalOutput.resolution,
|
'Resolution': currentContent.finalOutput.resolution,
|
||||||
'码率': currentContent.finalOutput.bitrate
|
'Bitrate': currentContent.finalOutput.bitrate
|
||||||
}).map(([key, value]) => (
|
}).map(([key, value]) => (
|
||||||
<div key={key} className="flex justify-between text-xs">
|
<div key={key} className="flex justify-between text-xs">
|
||||||
<span className="text-gray-400">{key}:</span>
|
<span className="text-gray-400">{key}:</span>
|
||||||
@ -258,9 +258,9 @@ const Editor: React.FC<EditorProps> = ({ currentContent, isPlaying }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{Object.entries({
|
{Object.entries({
|
||||||
'音频': currentContent.finalOutput.audio,
|
'Audio': currentContent.finalOutput.audio,
|
||||||
'时长': currentContent.finalOutput.duration,
|
'Duration': currentContent.finalOutput.duration,
|
||||||
'状态': currentContent.finalOutput.status
|
'Status': currentContent.finalOutput.status
|
||||||
}).map(([key, value]) => (
|
}).map(([key, value]) => (
|
||||||
<div key={key} className="flex justify-between text-xs">
|
<div key={key} className="flex justify-between text-xs">
|
||||||
<span className="text-gray-400">{key}:</span>
|
<span className="text-gray-400">{key}:</span>
|
||||||
@ -270,7 +270,7 @@ const Editor: React.FC<EditorProps> = ({ currentContent, isPlaying }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<ProgressBar progress={currentContent.finalOutput.progress} color="#f59e0b" label="输出进度" />
|
<ProgressBar progress={currentContent.finalOutput.progress} color="#f59e0b" label="Output progress" />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -4,64 +4,64 @@ export const scriptwriterData = {
|
|||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
stableId: 'act1',
|
stableId: 'act1',
|
||||||
title: '第一幕:开端',
|
title: 'Act I: The Beginning',
|
||||||
desc: '故事背景设定与主要人物介绍',
|
desc: 'Story background setting and main character introduction',
|
||||||
beats: ['人物登场', '冲突埋设', '情节推进']
|
beats: ['Character introduction', 'conflict setting', 'plot advancement']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
stableId: 'act2',
|
stableId: 'act2',
|
||||||
title: '第二幕:发展',
|
title: 'Act II: Development',
|
||||||
desc: '矛盾冲突的展开与升级',
|
desc: 'The development and escalation of conflicts',
|
||||||
beats: ['冲突加剧', '危机显现', '情节转折']
|
beats: ['Conflict intensifies', 'Crisis emerges', 'Plot twist']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '3',
|
id: '3',
|
||||||
stableId: 'act3',
|
stableId: 'act3',
|
||||||
title: '第三幕:高潮',
|
title: 'Act III: Climax',
|
||||||
desc: '故事达到最高潮并迎来结局',
|
desc: 'The story reaches its climax and ends',
|
||||||
beats: ['最终对决', '问题解决', '情节收束']
|
beats: ['Final showdown', 'Problem solved', 'Plot closure']
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
characters: [
|
characters: [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
stableId: 'char1',
|
stableId: 'char1',
|
||||||
name: '主角',
|
name: 'main character',
|
||||||
role: '核心人物',
|
role: 'Key Figures',
|
||||||
arc: '成长蜕变',
|
arc: 'Growth and transformation',
|
||||||
desc: '内心独白与行为动机的深入刻画',
|
desc: 'In-depth portrayal of inner monologue and behavioral motivation',
|
||||||
color: '#8b5cf6'
|
color: '#8b5cf6'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
stableId: 'char2',
|
stableId: 'char2',
|
||||||
name: '对手',
|
name: 'opponent',
|
||||||
role: '反派角色',
|
role: 'Villain',
|
||||||
arc: '阴谋败露',
|
arc: 'The conspiracy was exposed',
|
||||||
desc: '反派形象的立体化塑造',
|
desc: 'Three-dimensional creation of the villain image',
|
||||||
color: '#ec4899'
|
color: '#ec4899'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
dialogue: {
|
dialogue: {
|
||||||
stableId: 'dialogue1',
|
stableId: 'dialogue1',
|
||||||
rhythm: '对白节奏的快慢变化',
|
rhythm: 'Changes in the speed of dialogue',
|
||||||
style: '人物语言风格的统一'
|
style: 'The unity of character language style'
|
||||||
},
|
},
|
||||||
themes: [
|
themes: [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
stableId: 'theme1',
|
stableId: 'theme1',
|
||||||
theme: '主题探索',
|
theme: 'Theme Exploration',
|
||||||
desc: '故事核心主题的层层深入',
|
desc: 'The core theme of the story is gradually deepened',
|
||||||
depth: '通过情节与对白的双重展现'
|
depth: 'Through the dual presentation of plot and dialogue'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
stableId: 'theme2',
|
stableId: 'theme2',
|
||||||
theme: '情感刻画',
|
theme: 'Theme Exploration',
|
||||||
desc: '人物情感的细腻表达',
|
desc: 'The core theme of the story is gradually deepened',
|
||||||
depth: '借助环境与氛围的烘托'
|
depth: 'Through the dual presentation of plot and dialogue'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
dramaticLine: {
|
dramaticLine: {
|
||||||
@ -70,43 +70,43 @@ export const scriptwriterData = {
|
|||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
stableId: 'point1',
|
stableId: 'point1',
|
||||||
title: '开场',
|
title: 'Opening',
|
||||||
desc: '故事开始',
|
desc: 'Story begins',
|
||||||
intensity: 20
|
intensity: 20
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
stableId: 'point2',
|
stableId: 'point2',
|
||||||
title: '引子',
|
title: 'Introduction',
|
||||||
desc: '背景介绍',
|
desc: 'Background introduction',
|
||||||
intensity: 35
|
intensity: 35
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '3',
|
id: '3',
|
||||||
stableId: 'point3',
|
stableId: 'point3',
|
||||||
title: '发展',
|
title: 'Development',
|
||||||
desc: '冲突显现',
|
desc: 'Conflict appears',
|
||||||
intensity: 60
|
intensity: 60
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '4',
|
id: '4',
|
||||||
stableId: 'point4',
|
stableId: 'point4',
|
||||||
title: '高潮',
|
title: 'Climax',
|
||||||
desc: '矛盾爆发',
|
desc: 'Contradiction breaks out',
|
||||||
intensity: 85
|
intensity: 85
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '5',
|
id: '5',
|
||||||
stableId: 'point5',
|
stableId: 'point5',
|
||||||
title: '结局',
|
title: 'Conclusion',
|
||||||
desc: '问题解决',
|
desc: 'Problem solved',
|
||||||
intensity: 45
|
intensity: 45
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '6',
|
id: '6',
|
||||||
stableId: 'point6',
|
stableId: 'point6',
|
||||||
title: '尾声',
|
title: 'Epilogue',
|
||||||
desc: '故事收束',
|
desc: 'Story ends',
|
||||||
intensity: 30
|
intensity: 30
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -119,58 +119,58 @@ export const storyboardData = {
|
|||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
stableId: 'shot1',
|
stableId: 'shot1',
|
||||||
type: '远景镜头',
|
type: 'Long shot',
|
||||||
purpose: '展现场景全貌',
|
purpose: 'Show the overall scene',
|
||||||
usage: '用于开场和转场,建立空间感和氛围'
|
usage: 'Used for开场和转场,建立空间感和氛围'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
stableId: 'shot2',
|
stableId: 'shot2',
|
||||||
type: '特写镜头',
|
type: 'Close-up',
|
||||||
purpose: '突出细节表情',
|
purpose: 'Highlight details',
|
||||||
usage: '用于情感渲染和关键道具展示'
|
usage: 'Used for emotional rendering and key prop display'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
composition: {
|
composition: {
|
||||||
stableId: 'comp1',
|
stableId: 'comp1',
|
||||||
principles: '黄金分割和三分法则的运用',
|
principles: 'The use of the golden section and the three-point rule',
|
||||||
aesthetics: '画面构图的美感营造',
|
aesthetics: 'The aesthetic creation of the picture composition',
|
||||||
framing: '取景框的合理设置'
|
framing: 'The reasonable setting of the framing'
|
||||||
},
|
},
|
||||||
cameraMovement: [
|
cameraMovement: [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
stableId: 'cam1',
|
stableId: 'cam1',
|
||||||
type: '推轨',
|
type: 'Track',
|
||||||
purpose: '渲染情绪',
|
purpose: 'Render emotions',
|
||||||
application: '角色情感的空间表达'
|
application: 'The spatial expression of character emotions'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
stableId: 'cam2',
|
stableId: 'cam2',
|
||||||
type: '摇臂',
|
type: 'Dolly',
|
||||||
purpose: '场景转换',
|
purpose: 'Scene transition',
|
||||||
application: '空间层次的流畅过渡'
|
application: 'Smooth transition of spatial hierarchy'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
visualNarrative: {
|
visualNarrative: {
|
||||||
stableId: 'visual1',
|
stableId: 'visual1',
|
||||||
logic: '视觉叙事的连贯性',
|
logic: 'The coherence of visual storytelling',
|
||||||
progression: '故事节奏的视觉把控',
|
progression: 'The visual control of story rhythm',
|
||||||
emphasis: '重点情节的视觉强调'
|
emphasis: 'The visual emphasis of key plot points'
|
||||||
},
|
},
|
||||||
editingPoints: [
|
editingPoints: [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
stableId: 'edit1',
|
stableId: 'edit1',
|
||||||
moment: '场景转换',
|
moment: 'Scene transition',
|
||||||
cut: '通过物体运动的自然切换'
|
cut: 'The natural switching through object movement'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
stableId: 'edit2',
|
stableId: 'edit2',
|
||||||
moment: '情感高潮',
|
moment: 'Emotional climax',
|
||||||
cut: '快速剪辑的节奏渲染'
|
cut: 'The rapid editing of rhythm rendering'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@ -181,74 +181,74 @@ export const productionData = {
|
|||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
stableId: 'comp1',
|
stableId: 'comp1',
|
||||||
element: '场景布局',
|
element: 'Scene layout',
|
||||||
details: '场景元素的空间排布与层次关系',
|
details: 'The spatial arrangement and hierarchy of scene elements',
|
||||||
status: '渲染中',
|
status: 'Rendering',
|
||||||
progress: 65
|
progress: 65
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
stableId: 'comp2',
|
stableId: 'comp2',
|
||||||
element: '角色位置',
|
element: 'Character position',
|
||||||
details: '人物在场景中的站位与动线设计',
|
details: 'The position and movement line design of characters in the scene',
|
||||||
status: '优化中',
|
status: 'Optimization',
|
||||||
progress: 80
|
progress: 80
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
lighting: {
|
lighting: {
|
||||||
stableId: 'light1',
|
stableId: 'light1',
|
||||||
ambient: '自然光效果的模拟与调节',
|
ambient: 'The simulation and adjustment of natural light effects',
|
||||||
artificial: '人工光源的位置布置',
|
artificial: 'The layout of artificial light sources',
|
||||||
mood: '通过光影营造场景氛围',
|
mood: 'The creation of scene atmosphere through light and shadow',
|
||||||
progress: 75
|
progress: 75
|
||||||
},
|
},
|
||||||
performance: [
|
performance: [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
stableId: 'perf1',
|
stableId: 'perf1',
|
||||||
aspect: '面部表情',
|
aspect: 'Facial expressions',
|
||||||
details: '细微表情的精确捕捉',
|
details: 'The precise capture of subtle expressions',
|
||||||
quality: '高品质',
|
quality: 'High quality',
|
||||||
progress: 90
|
progress: 90
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
stableId: 'perf2',
|
stableId: 'perf2',
|
||||||
aspect: '肢体动作',
|
aspect: 'Body movements',
|
||||||
details: '动作的流畅性与自然度',
|
details: 'The fluency and naturalness of body movements',
|
||||||
quality: '优化中',
|
quality: 'Optimization',
|
||||||
progress: 85
|
progress: 85
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
sceneDetails: {
|
sceneDetails: {
|
||||||
stableId: 'scene1',
|
stableId: 'scene1',
|
||||||
textures: '材质细节的精细处理',
|
textures: 'The fine processing of material details',
|
||||||
objects: '场景道具的细节优化',
|
objects: 'The detailed optimization of scene props',
|
||||||
atmosphere: '整体氛围的烘托渲染',
|
atmosphere: 'The overall atmosphere of the scene',
|
||||||
progress: 70
|
progress: 70
|
||||||
},
|
},
|
||||||
technical: [
|
technical: [
|
||||||
{
|
{
|
||||||
param: '分辨率',
|
param: 'Resolution',
|
||||||
value: '4K',
|
value: '4K',
|
||||||
status: 'optimized'
|
status: 'Optimized'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
param: '帧率',
|
param: 'Frame rate',
|
||||||
value: '60fps',
|
value: '60fps',
|
||||||
status: 'processing'
|
status: 'Processing'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
param: '渲染引擎',
|
param: 'Rendering engine',
|
||||||
value: 'Cycles',
|
value: 'Cycles',
|
||||||
status: 'active'
|
status: 'Active'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
renderOutput: {
|
renderOutput: {
|
||||||
currentFrame: 1500,
|
currentFrame: 1500,
|
||||||
totalFrames: 2400,
|
totalFrames: 2400,
|
||||||
quality: '最终质量',
|
quality: 'Final quality',
|
||||||
estimated: '预计15分钟'
|
estimated: 'Estimated 15 minutes'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -256,65 +256,65 @@ export const productionData = {
|
|||||||
export const editorData = {
|
export const editorData = {
|
||||||
rhythm: {
|
rhythm: {
|
||||||
stableId: 'rhythm1',
|
stableId: 'rhythm1',
|
||||||
concept: '节奏的整体规划与设计',
|
concept: 'The overall planning and design of rhythm',
|
||||||
application: '快慢节奏的合理搭配',
|
application: 'The reasonable combination of fast and slow rhythms',
|
||||||
current: '正在优化转场节奏',
|
current: 'Optimizing the transition rhythm',
|
||||||
progress: 85
|
progress: 85
|
||||||
},
|
},
|
||||||
audioVideo: [
|
audioVideo: [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
stableId: 'av1',
|
stableId: 'av1',
|
||||||
aspect: '音画同步',
|
aspect: 'Audio-visual synchronization',
|
||||||
details: '确保声画精确匹配',
|
details: 'Ensure accurate matching of sound and image',
|
||||||
sync: '帧级精度',
|
sync: 'Frame level accuracy',
|
||||||
balance: '优',
|
balance: 'Good',
|
||||||
progress: 90
|
progress: 90
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
stableId: 'av2',
|
stableId: 'av2',
|
||||||
aspect: '音效处理',
|
aspect: 'Audio processing',
|
||||||
details: '环境音效的自然融合',
|
details: 'The natural fusion of environmental sound effects',
|
||||||
sync: '毫秒级',
|
sync: 'Millisecond level',
|
||||||
balance: '良好',
|
balance: 'Good',
|
||||||
progress: 85
|
progress: 85
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
emotionProgression: {
|
emotionProgression: {
|
||||||
stableId: 'emotion1',
|
stableId: 'emotion1',
|
||||||
stages: '情感递进的节奏控制',
|
stages: 'The gradual progression of emotions',
|
||||||
techniques: '通过剪辑手法强化情感',
|
techniques: 'The reinforcement of emotions through editing techniques',
|
||||||
current: '高潮段落调校中',
|
current: 'The adjustment of the climax segment',
|
||||||
progress: 75
|
progress: 75
|
||||||
},
|
},
|
||||||
transitions: [
|
transitions: [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
stableId: 'trans1',
|
stableId: 'trans1',
|
||||||
type: '淡入淡出',
|
type: 'Fade in and fade out',
|
||||||
usage: '用于时空转换的柔和过渡'
|
usage: 'The gentle transition for temporal transformation'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
stableId: 'trans2',
|
stableId: 'trans2',
|
||||||
type: '快速切换',
|
type: 'Quick switch',
|
||||||
usage: '用于紧张氛围的营造'
|
usage: 'Used to create a tense atmosphere'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
styleUnity: {
|
styleUnity: {
|
||||||
stableId: 'style1',
|
stableId: 'style1',
|
||||||
colorGrading: '色彩基调的统一处理',
|
colorGrading: 'The unified handling of color tones',
|
||||||
toneCurve: '明暗对比的整体调校',
|
toneCurve: 'The overall adjustment of contrast',
|
||||||
progress: 80
|
progress: 80
|
||||||
},
|
},
|
||||||
finalOutput: {
|
finalOutput: {
|
||||||
format: 'MP4 H.265',
|
format: 'MP4 H.265',
|
||||||
resolution: '4K UHD',
|
resolution: '4K UHD',
|
||||||
bitrate: '50Mbps',
|
bitrate: '50Mbps',
|
||||||
audio: '5.1声道',
|
audio: '5.1 channels',
|
||||||
duration: '15:30',
|
duration: '15:30',
|
||||||
status: '渲染中',
|
status: 'Rendering',
|
||||||
progress: 65
|
progress: 65
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -58,7 +58,7 @@ const CustomTooltip = ({ active, payload, label }: any) => {
|
|||||||
return (
|
return (
|
||||||
<div className="bg-black/80 backdrop-blur-sm p-2 rounded-lg border border-purple-500/30">
|
<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-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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -73,8 +73,8 @@ const Scriptwriter: React.FC<ScriptwriterProps> = ({ currentContent, isPlaying }
|
|||||||
{/* 三幕结构 */}
|
{/* 三幕结构 */}
|
||||||
<div className="bg-black/30 rounded-lg p-4">
|
<div className="bg-black/30 rounded-lg p-4">
|
||||||
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
||||||
<span>三幕结构搭建</span>
|
<span>Three-act structure</span>
|
||||||
<IconLoading icon={Heart} isActive={!currentContent.acts && isPlaying} color="#8b5cf6" />
|
<IconLoading icon={Heart} isActive={isPlaying} color="#8b5cf6" />
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{currentContent.acts && currentContent.acts.length > 0 ? (
|
{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">
|
<div className="bg-black/30 rounded-lg p-4">
|
||||||
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
||||||
<span>角色弧光设计</span>
|
<span>Character arc design</span>
|
||||||
<IconLoading icon={Heart} isActive={!currentContent.characters && isPlaying} color="#8b5cf6" />
|
<IconLoading icon={Heart} isActive={isPlaying} color="#8b5cf6" />
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{currentContent.characters && currentContent.characters.length > 0 ? (
|
{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">
|
<div className="bg-black/30 rounded-lg p-4">
|
||||||
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
||||||
<span>对白节奏感</span>
|
<span>Dialogue rhythm</span>
|
||||||
<IconLoading icon={Heart} isActive={!currentContent.dialogue && isPlaying} color="#8b5cf6" />
|
<IconLoading icon={Heart} isActive={isPlaying} color="#8b5cf6" />
|
||||||
</h3>
|
</h3>
|
||||||
{currentContent.dialogue ? (
|
{currentContent.dialogue ? (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<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">Rhythm control</div>
|
||||||
<div className="text-gray-300 text-xs">
|
<div className="text-gray-300 text-xs">
|
||||||
<TypewriterText text={currentContent.dialogue.rhythm} stableId={`${currentContent.dialogue.stableId}-rhythm`} />
|
<TypewriterText text={currentContent.dialogue.rhythm} stableId={`${currentContent.dialogue.stableId}-rhythm`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
<div className="text-gray-300 text-xs">
|
||||||
<TypewriterText text={currentContent.dialogue.style} stableId={`${currentContent.dialogue.stableId}-style`} />
|
<TypewriterText text={currentContent.dialogue.style} stableId={`${currentContent.dialogue.stableId}-style`} />
|
||||||
</div>
|
</div>
|
||||||
@ -175,8 +175,8 @@ const Scriptwriter: React.FC<ScriptwriterProps> = ({ currentContent, isPlaying }
|
|||||||
{/* 主题深化过程 */}
|
{/* 主题深化过程 */}
|
||||||
<div className="bg-black/30 rounded-lg p-4">
|
<div className="bg-black/30 rounded-lg p-4">
|
||||||
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
||||||
<span>主题深化过程</span>
|
<span>Theme deepening process</span>
|
||||||
<IconLoading icon={Heart} isActive={!currentContent.themes && isPlaying} color="#8b5cf6" />
|
<IconLoading icon={Heart} isActive={isPlaying} color="#8b5cf6" />
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{currentContent.themes ? (
|
{currentContent.themes ? (
|
||||||
@ -207,8 +207,8 @@ const Scriptwriter: React.FC<ScriptwriterProps> = ({ currentContent, isPlaying }
|
|||||||
{/* 剧情起伏戏演线 */}
|
{/* 剧情起伏戏演线 */}
|
||||||
<div className="bg-black/30 rounded-lg p-4">
|
<div className="bg-black/30 rounded-lg p-4">
|
||||||
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
||||||
<span>剧情起伏戏演线</span>
|
<span>Dramatic line</span>
|
||||||
<IconLoading icon={Heart} isActive={!currentContent.dramaticLine && isPlaying} color="#8b5cf6" />
|
<IconLoading icon={Heart} isActive={isPlaying} color="#8b5cf6" />
|
||||||
</h3>
|
</h3>
|
||||||
{currentContent.dramaticLine ? (
|
{currentContent.dramaticLine ? (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
@ -230,7 +230,7 @@ const Scriptwriter: React.FC<ScriptwriterProps> = ({ currentContent, isPlaying }
|
|||||||
tick={{ fill: '#9CA3AF', fontSize: 10 }}
|
tick={{ fill: '#9CA3AF', fontSize: 10 }}
|
||||||
stroke="#4B5563"
|
stroke="#4B5563"
|
||||||
label={{
|
label={{
|
||||||
value: '情感强度',
|
value: 'Emotional intensity',
|
||||||
angle: -90,
|
angle: -90,
|
||||||
position: 'insideLeft',
|
position: 'insideLeft',
|
||||||
fill: '#9CA3AF',
|
fill: '#9CA3AF',
|
||||||
|
|||||||
@ -53,8 +53,8 @@ const StoryboardArtist: React.FC<StoryboardArtistProps> = ({ currentContent, isP
|
|||||||
{/* 镜头语言选择 */}
|
{/* 镜头语言选择 */}
|
||||||
<div className="bg-black/30 rounded-lg p-4">
|
<div className="bg-black/30 rounded-lg p-4">
|
||||||
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
||||||
<span>镜头语言选择</span>
|
<span>Shot language selection</span>
|
||||||
<IconLoading icon={Camera} isActive={!currentContent.shotLanguage && isPlaying} color="#06b6d4" />
|
<IconLoading icon={Camera} isActive={isPlaying} color="#06b6d4" />
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{currentContent.shotLanguage && currentContent.shotLanguage.length > 0 ? (
|
{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">
|
<div className="bg-black/30 rounded-lg p-4">
|
||||||
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
||||||
<span>构图美学运用</span>
|
<span>Composition aesthetics</span>
|
||||||
<IconLoading icon={Camera} isActive={!currentContent.composition && isPlaying} color="#06b6d4" />
|
<IconLoading icon={Camera} isActive={isPlaying} color="#06b6d4" />
|
||||||
</h3>
|
</h3>
|
||||||
{currentContent.composition ? (
|
{currentContent.composition ? (
|
||||||
<ContentCard className="space-y-3">
|
<ContentCard className="space-y-3">
|
||||||
<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">Composition principles</div>
|
||||||
<div className="text-gray-300 text-xs">
|
<div className="text-gray-300 text-xs">
|
||||||
<TypewriterText text={currentContent.composition.principles} stableId={`${currentContent.composition.stableId}-principles`} />
|
<TypewriterText text={currentContent.composition.principles} stableId={`${currentContent.composition.stableId}-principles`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
<div className="text-gray-300 text-xs">
|
||||||
<TypewriterText text={currentContent.composition.aesthetics} stableId={`${currentContent.composition.stableId}-aesthetics`} />
|
<TypewriterText text={currentContent.composition.aesthetics} stableId={`${currentContent.composition.stableId}-aesthetics`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
<div className="text-gray-300 text-xs">
|
||||||
<TypewriterText text={currentContent.composition.framing} stableId={`${currentContent.composition.stableId}-framing`} />
|
<TypewriterText text={currentContent.composition.framing} stableId={`${currentContent.composition.stableId}-framing`} />
|
||||||
</div>
|
</div>
|
||||||
@ -122,8 +122,8 @@ const StoryboardArtist: React.FC<StoryboardArtistProps> = ({ currentContent, isP
|
|||||||
{/* 摄影机运动设计 */}
|
{/* 摄影机运动设计 */}
|
||||||
<div className="bg-black/30 rounded-lg p-4">
|
<div className="bg-black/30 rounded-lg p-4">
|
||||||
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
||||||
<span>摄影机运动设计</span>
|
<span>Camera movement design</span>
|
||||||
<IconLoading icon={Camera} isActive={!currentContent.cameraMovement && isPlaying} color="#06b6d4" />
|
<IconLoading icon={Camera} isActive={isPlaying} color="#06b6d4" />
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{currentContent.cameraMovement ? (
|
{currentContent.cameraMovement ? (
|
||||||
@ -152,25 +152,25 @@ const StoryboardArtist: React.FC<StoryboardArtistProps> = ({ currentContent, isP
|
|||||||
{/* 视觉叙事逻辑 */}
|
{/* 视觉叙事逻辑 */}
|
||||||
<div className="bg-black/30 rounded-lg p-4">
|
<div className="bg-black/30 rounded-lg p-4">
|
||||||
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
||||||
<span>视觉叙事逻辑</span>
|
<span>Visual storytelling logic</span>
|
||||||
<IconLoading icon={Camera} isActive={!currentContent.visualNarrative && isPlaying} color="#06b6d4" />
|
<IconLoading icon={Camera} isActive={isPlaying} color="#06b6d4" />
|
||||||
</h3>
|
</h3>
|
||||||
{currentContent.visualNarrative ? (
|
{currentContent.visualNarrative ? (
|
||||||
<ContentCard className="space-y-3">
|
<ContentCard className="space-y-3">
|
||||||
<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">Storytelling logic</div>
|
||||||
<div className="text-gray-300 text-xs">
|
<div className="text-gray-300 text-xs">
|
||||||
<TypewriterText text={currentContent.visualNarrative.logic} stableId={`${currentContent.visualNarrative.stableId}-logic`} />
|
<TypewriterText text={currentContent.visualNarrative.logic} stableId={`${currentContent.visualNarrative.stableId}-logic`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
<div className="text-gray-300 text-xs">
|
||||||
<TypewriterText text={currentContent.visualNarrative.progression} stableId={`${currentContent.visualNarrative.stableId}-progression`} />
|
<TypewriterText text={currentContent.visualNarrative.progression} stableId={`${currentContent.visualNarrative.stableId}-progression`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
<div className="text-gray-300 text-xs">
|
||||||
<TypewriterText text={currentContent.visualNarrative.emphasis} stableId={`${currentContent.visualNarrative.stableId}-emphasis`} />
|
<TypewriterText text={currentContent.visualNarrative.emphasis} stableId={`${currentContent.visualNarrative.stableId}-emphasis`} />
|
||||||
</div>
|
</div>
|
||||||
@ -188,8 +188,8 @@ const StoryboardArtist: React.FC<StoryboardArtistProps> = ({ currentContent, isP
|
|||||||
{/* 剪辑点预设 */}
|
{/* 剪辑点预设 */}
|
||||||
<div className="bg-black/30 rounded-lg p-4">
|
<div className="bg-black/30 rounded-lg p-4">
|
||||||
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
||||||
<span>剪辑点预设</span>
|
<span>Editing point preset</span>
|
||||||
<IconLoading icon={Camera} isActive={!currentContent.editingPoints && isPlaying} color="#06b6d4" />
|
<IconLoading icon={Camera} isActive={isPlaying} color="#06b6d4" />
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{currentContent.editingPoints ? (
|
{currentContent.editingPoints ? (
|
||||||
|
|||||||
@ -63,8 +63,8 @@ const VisualDirector: React.FC<VisualDirectorProps> = ({ currentContent, isPlayi
|
|||||||
{/* 画面构成要素 */}
|
{/* 画面构成要素 */}
|
||||||
<div className="bg-black/30 rounded-lg p-4">
|
<div className="bg-black/30 rounded-lg p-4">
|
||||||
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
||||||
<span>画面构成要素</span>
|
<span>Element of composition</span>
|
||||||
<IconLoading icon={Film} isActive={!currentContent.composition && isPlaying} color="#10b981" />
|
<IconLoading icon={Film} isActive={isPlaying} color="#10b981" />
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{currentContent.composition && currentContent.composition.length > 0 ? (
|
{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">
|
<div className="bg-black/30 rounded-lg p-4">
|
||||||
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
||||||
<span>光影氛围营造</span>
|
<span>Lighting atmosphere</span>
|
||||||
<IconLoading icon={Film} isActive={!currentContent.lighting && isPlaying} color="#10b981" />
|
<IconLoading icon={Film} isActive={isPlaying} color="#10b981" />
|
||||||
</h3>
|
</h3>
|
||||||
{currentContent.lighting ? (
|
{currentContent.lighting ? (
|
||||||
<ContentCard className="space-y-3">
|
<ContentCard className="space-y-3">
|
||||||
<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">Environment light design</div>
|
||||||
<div className="text-gray-300 text-xs">
|
<div className="text-gray-300 text-xs">
|
||||||
<TypewriterText text={currentContent.lighting.ambient} stableId={`${currentContent.lighting.stableId}-ambient`} />
|
<TypewriterText text={currentContent.lighting.ambient} stableId={`${currentContent.lighting.stableId}-ambient`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
<div className="text-gray-300 text-xs">
|
||||||
<TypewriterText text={currentContent.lighting.artificial} stableId={`${currentContent.lighting.stableId}-artificial`} />
|
<TypewriterText text={currentContent.lighting.artificial} stableId={`${currentContent.lighting.stableId}-artificial`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
<div className="text-gray-300 text-xs">
|
||||||
<TypewriterText text={currentContent.lighting.mood} stableId={`${currentContent.lighting.stableId}-mood`} />
|
<TypewriterText text={currentContent.lighting.mood} stableId={`${currentContent.lighting.stableId}-mood`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ProgressBar progress={currentContent.lighting.progress} color="#10b981" label="光影渲染" />
|
<ProgressBar progress={currentContent.lighting.progress} color="#10b981" label="Lighting rendering" />
|
||||||
</ContentCard>
|
</ContentCard>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-3">
|
<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">
|
<div className="bg-black/30 rounded-lg p-4">
|
||||||
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
||||||
<span>角色表演捕捉</span>
|
<span>Character performance capture</span>
|
||||||
<IconLoading icon={Film} isActive={!currentContent.performance && isPlaying} color="#10b981" />
|
<IconLoading icon={Film} isActive={isPlaying} color="#10b981" />
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{currentContent.performance ? (
|
{currentContent.performance ? (
|
||||||
@ -164,30 +164,30 @@ const VisualDirector: React.FC<VisualDirectorProps> = ({ currentContent, isPlayi
|
|||||||
{/* 场景细节渲染 */}
|
{/* 场景细节渲染 */}
|
||||||
<div className="bg-black/30 rounded-lg p-4">
|
<div className="bg-black/30 rounded-lg p-4">
|
||||||
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
||||||
<span>场景细节渲染</span>
|
<span>Scene detail rendering</span>
|
||||||
<IconLoading icon={Film} isActive={!currentContent.sceneDetails && isPlaying} color="#10b981" />
|
<IconLoading icon={Film} isActive={isPlaying} color="#10b981" />
|
||||||
</h3>
|
</h3>
|
||||||
{currentContent.sceneDetails ? (
|
{currentContent.sceneDetails ? (
|
||||||
<ContentCard className="space-y-3">
|
<ContentCard className="space-y-3">
|
||||||
<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">Material texture</div>
|
||||||
<div className="text-gray-300 text-xs">
|
<div className="text-gray-300 text-xs">
|
||||||
<TypewriterText text={currentContent.sceneDetails.textures} stableId={`${currentContent.sceneDetails.stableId}-textures`} />
|
<TypewriterText text={currentContent.sceneDetails.textures} stableId={`${currentContent.sceneDetails.stableId}-textures`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
<div className="text-gray-300 text-xs">
|
||||||
<TypewriterText text={currentContent.sceneDetails.objects} stableId={`${currentContent.sceneDetails.stableId}-objects`} />
|
<TypewriterText text={currentContent.sceneDetails.objects} stableId={`${currentContent.sceneDetails.stableId}-objects`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
<div className="text-gray-300 text-xs">
|
||||||
<TypewriterText text={currentContent.sceneDetails.atmosphere} stableId={`${currentContent.sceneDetails.stableId}-atmosphere`} />
|
<TypewriterText text={currentContent.sceneDetails.atmosphere} stableId={`${currentContent.sceneDetails.stableId}-atmosphere`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ProgressBar progress={currentContent.sceneDetails.progress} color="#10b981" label="细节渲染" />
|
<ProgressBar progress={currentContent.sceneDetails.progress} color="#10b981" label="Detail rendering" />
|
||||||
</ContentCard>
|
</ContentCard>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-3">
|
<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">
|
<div className="bg-black/30 rounded-lg p-4">
|
||||||
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
||||||
<span>技术参数控制</span>
|
<span>Technical parameter control</span>
|
||||||
<IconLoading icon={Film} isActive={!currentContent.technical && isPlaying} color="#10b981" />
|
<IconLoading icon={Film} isActive={isPlaying} color="#10b981" />
|
||||||
</h3>
|
</h3>
|
||||||
{currentContent.technical ? (
|
{currentContent.technical ? (
|
||||||
<>
|
<>
|
||||||
@ -230,14 +230,14 @@ const VisualDirector: React.FC<VisualDirectorProps> = ({ currentContent, isPlayi
|
|||||||
<div className="text-2xl font-mono text-emerald-400 mb-1">
|
<div className="text-2xl font-mono text-emerald-400 mb-1">
|
||||||
{Math.round((currentContent.renderOutput.currentFrame / currentContent.renderOutput.totalFrames) * 100)}%
|
{Math.round((currentContent.renderOutput.currentFrame / currentContent.renderOutput.totalFrames) * 100)}%
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-400">渲染完成</div>
|
<div className="text-sm text-gray-400">Rendering completed</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-center space-y-1">
|
<div className="text-xs text-center space-y-1">
|
||||||
<div className="text-white">
|
<div className="text-white">
|
||||||
帧数: {currentContent.renderOutput.currentFrame}/{currentContent.renderOutput.totalFrames}
|
Frame: {currentContent.renderOutput.currentFrame}/{currentContent.renderOutput.totalFrames}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-emerald-400">
|
<div className="text-emerald-400">
|
||||||
质量: {currentContent.renderOutput.quality} | {currentContent.renderOutput.estimated}
|
Quality: {currentContent.renderOutput.quality} | {currentContent.renderOutput.estimated}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -13,43 +13,44 @@ interface Stage {
|
|||||||
icon: React.ElementType;
|
icon: React.ElementType;
|
||||||
color: string;
|
color: string;
|
||||||
profession: string;
|
profession: string;
|
||||||
duration: number; // 加载持续时间(毫秒)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const stages: Stage[] = [
|
const stages: Stage[] = [
|
||||||
{
|
{
|
||||||
id: 'script',
|
id: 'script',
|
||||||
title: '编剧工作台',
|
title: 'Scriptwriter',
|
||||||
icon: Heart,
|
icon: Heart,
|
||||||
color: '#8b5cf6',
|
color: '#8b5cf6',
|
||||||
profession: '编剧',
|
profession: 'Scriptwriter'
|
||||||
duration: 3 * 60 * 1000 // 3分钟
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'storyboard',
|
id: 'storyboard',
|
||||||
title: '分镜设计台',
|
title: 'Storyboard artist',
|
||||||
icon: Camera,
|
icon: Camera,
|
||||||
color: '#06b6d4',
|
color: '#06b6d4',
|
||||||
profession: '分镜师',
|
profession: 'Storyboard artist'
|
||||||
duration: 8 * 60 * 1000 // 8分钟
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'production',
|
id: 'production',
|
||||||
title: '制作渲染台',
|
title: 'Visual director',
|
||||||
icon: Film,
|
icon: Film,
|
||||||
color: '#10b981',
|
color: '#10b981',
|
||||||
profession: '视觉导演',
|
profession: 'Visual director'
|
||||||
duration: 10 * 60 * 1000 // 10分钟
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'editing',
|
id: 'editing',
|
||||||
title: '剪辑调色台',
|
title: 'Editor',
|
||||||
icon: Scissors,
|
icon: Scissors,
|
||||||
color: '#f59e0b',
|
color: '#f59e0b',
|
||||||
profession: '剪辑师',
|
profession: 'Editor'
|
||||||
duration: 15 * 60 * 1000 // 15分钟
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
const actionsText = [
|
||||||
|
'is thinking...',
|
||||||
|
'is drawing...',
|
||||||
|
'is directing...',
|
||||||
|
'is editing...'
|
||||||
|
]
|
||||||
|
|
||||||
// 思考指示器组件
|
// 思考指示器组件
|
||||||
const ThinkingDots = ({ show, text, color }: { show: boolean; text: string; color: string }) => {
|
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 {
|
interface WorkOfficeProps {
|
||||||
initialStage?: number;
|
initialStage?: number;
|
||||||
|
roles: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const WorkOffice: React.FC<WorkOfficeProps> = ({ initialStage = 0 }) => {
|
const WorkOffice: React.FC<WorkOfficeProps> = ({ initialStage = 0 }) => {
|
||||||
const [currentStage, setCurrentStage] = useState(initialStage);
|
const [currentStage, setCurrentStage] = useState(initialStage);
|
||||||
const [isPlaying, setIsPlaying] = useState(true);
|
const [isPlaying, setIsPlaying] = useState(false);
|
||||||
const [progress, setProgress] = useState(0);
|
|
||||||
const [currentContent, setCurrentContent] = useState<Record<string, any>>(scriptwriterData);
|
const [currentContent, setCurrentContent] = useState<Record<string, any>>(scriptwriterData);
|
||||||
const [thinkingText, setThinkingText] = useState(`${stages[0].profession}正在思考...`);
|
const [thinkingText, setThinkingText] = useState(`${stages[0].profession} ${actionsText[0]}`);
|
||||||
const [startTime, setStartTime] = useState<number | null>(null);
|
|
||||||
|
|
||||||
// 模拟数据加载过程
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isPlaying) return;
|
// currentStage 更新 重新渲染当前工作台组件
|
||||||
|
setCurrentStage(initialStage);
|
||||||
// 记录开始时间
|
}, [initialStage]);
|
||||||
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]);
|
|
||||||
|
|
||||||
// 根据当前阶段加载对应数据
|
// 根据当前阶段加载对应数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -138,19 +115,10 @@ const WorkOffice: React.FC<WorkOfficeProps> = ({ initialStage = 0 }) => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置状态并开始新的加载
|
|
||||||
setProgress(0);
|
|
||||||
setIsPlaying(true);
|
setIsPlaying(true);
|
||||||
setStartTime(Date.now());
|
setCurrentContent(data);
|
||||||
setCurrentContent({});
|
setThinkingText(`${stages[currentStage].profession} ${actionsText[currentStage]}`);
|
||||||
setThinkingText(`${stages[currentStage].profession}正在思考...`);
|
|
||||||
|
|
||||||
// 模拟数据加载延迟
|
|
||||||
const loadingTimeout = setTimeout(() => {
|
|
||||||
setCurrentContent(data);
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
return () => clearTimeout(loadingTimeout);
|
|
||||||
}, [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 (
|
return (
|
||||||
<div className="h-full rounded-2xl overflow-hidden shadow-2xl relative">
|
<div className="h-full rounded-2xl overflow-hidden shadow-2xl relative">
|
||||||
{/* 正在加载的部分 文字显示 */}
|
{/* 正在加载的部分 文字显示 */}
|
||||||
@ -195,58 +149,9 @@ const WorkOffice: React.FC<WorkOfficeProps> = ({ initialStage = 0 }) => {
|
|||||||
</div>
|
</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()}
|
{renderCurrentWorkstation()}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user