2025-07-18 01:58:46 +08:00

519 lines
16 KiB
TypeScript

'use client';
import React, { useState, useEffect, useRef, useMemo } from 'react';
import { motion } from 'framer-motion';
import { ScriptModal } from '@/components/ui/script-modal';
import {
Image,
Video,
CheckCircle,
Music,
Loader2,
User,
Scissors,
Tv,
Airplay
} from 'lucide-react';
interface TaskInfoProps {
isLoading: boolean;
taskObject: any;
currentLoadingText: string;
dataLoadError?: string | null;
currentStep: string;
sketchCount: number;
isGeneratingVideo: boolean;
taskVideos: any[];
}
// 根据加载文本返回对应的图标
const getStageIcon = (loadingText: string) => {
const text = loadingText.toLowerCase();
if (text.includes('sketch') || text.includes('草图')) {
return Image;
} else if (text.includes('video') || text.includes('视频')) {
return Video;
} else if (text.includes('character') || text.includes('角色')) {
return User;
} else if (text.includes('audio') || text.includes('音频')) {
return Music;
} else if (text.includes('post') || text.includes('后期')) {
return Scissors;
} else if (text.includes('final') || text.includes('最终')) {
return Tv;
} else if (text.includes('complete') || text.includes('完成')) {
return CheckCircle;
} else {
return Loader2;
}
};
const TAG_COLORS = ['#FF5733', '#126821', '#8d3913', '#FF33A1', '#A133FF', '#FF3333', '#3333FF', '#A1A1A1', '#a1115e', '#30527f'];
export function TaskInfo({
isLoading,
taskObject,
currentLoadingText,
dataLoadError,
currentStep,
sketchCount,
isGeneratingVideo,
taskVideos
}: 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 timerRef = useRef<NodeJS.Timeout | null>(null);
// 清理定时器
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') {
console.log('Closing modal at completion');
setIsScriptModalOpen(false);
}
}, [currentStep]);
// 弹窗状态变化时的日志
useEffect(() => {
console.log('Modal State Changed:', {
isOpen: isScriptModalOpen,
currentStage,
currentStep,
sketchCount
});
}, [isScriptModalOpen, currentStage, currentStep, sketchCount]);
// 使用 useMemo 缓存标签颜色映射
const tagColors = useMemo(() => {
if (!taskObject?.tags) return {};
return taskObject.tags.reduce((acc: Record<string, string>, tag: string) => {
acc[tag] = TAG_COLORS[Math.floor(Math.random() * TAG_COLORS.length)];
return acc;
}, {});
}, [taskObject?.tags]); // 只在 tags 改变时重新计算
// 自动触发打开 剧本 弹窗 延迟5秒
// useEffect(() => {
// if (taskObject?.title && currentLoadingText !== 'Task completed') {
// setTimeout(() => {
// setIsScriptModalOpen(true);
// }, 5000);
// }
// }, [taskObject?.title]);
if (isLoading) {
return (
<>
<motion.div
className="title-JtMejk text-center"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
>
{taskObject?.title || 'loading project info...'}
</motion.div>
{/* 加载状态显示 */}
<motion.div
className="flex items-center gap-2 justify-center mt-2"
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
<motion.div
className="w-1.5 h-1.5 rounded-full bg-blue-500"
animate={{
scale: [1, 1.5, 1],
opacity: [1, 0.5, 1]
}}
transition={{
duration: 1,
repeat: Infinity,
repeatDelay: 0.2
}}
/>
{/* 阶段图标 */}
<motion.div
className="flex items-center gap-2"
key={currentLoadingText}
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 10 }}
transition={{ duration: 0.3 }}
>
<motion.div
className="text-blue-500"
animate={{
rotate: [0, 360],
scale: [1, 1.1, 1]
}}
transition={{
rotate: { duration: 2, repeat: Infinity, ease: "linear" },
scale: { duration: 1.5, repeat: Infinity, ease: "easeInOut" }
}}
>
<StageIcon className="w-5 h-5" />
</motion.div>
<motion.span
className="normalS400 subtitle-had8uE text-transparent bg-clip-text bg-gradient-to-r from-blue-600 via-cyan-500 to-purple-600"
animate={{
backgroundPosition: ["0% 50%", "100% 50%", "0% 50%"],
}}
transition={{
duration: 3,
repeat: Infinity,
ease: "linear"
}}
style={{
backgroundSize: "300% 300%",
}}
>
{currentLoadingText}
</motion.span>
</motion.div>
<motion.div
className="w-1.5 h-1.5 rounded-full bg-blue-500"
animate={{
scale: [1, 1.5, 1],
opacity: [1, 0.5, 1]
}}
transition={{
duration: 1,
repeat: Infinity,
repeatDelay: 0.2,
delay: 0.3
}}
/>
</motion.div>
{/* 错误提示 */}
{dataLoadError && (
<motion.div
className="mt-3 text-orange-600 text-sm text-center flex items-center justify-center gap-2"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
<span className="w-2 h-2 bg-orange-500 rounded-full animate-pulse"></span>
{dataLoadError}
</motion.div>
)}
</>
);
}
return (
<>
<div className="title-JtMejk flex items-center justify-center gap-2">
{taskObject?.title ? (
<>
<span>
{taskObject?.title || 'loading project info...'}
</span>
<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>
</>
) : 'loading project info...'}
</div>
{/* 主题 彩色标签tags */}
<div className="flex items-center justify-center gap-2">
{taskObject?.tags?.map((tag: string) => (
<div
key={tag}
className="text-sm text-white rounded-full px-2 py-1"
style={{ backgroundColor: tagColors[tag] }}
>
{tag}
</div>
))}
</div>
<ScriptModal
isOpen={isScriptModalOpen}
onClose={() => {
console.log('Modal manually closed');
setIsScriptModalOpen(false);
}}
currentStage={currentStage}
/>
{currentLoadingText === 'Task completed' ? (
<motion.div
className="flex items-center gap-3 justify-center"
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<div className="flex items-center gap-2">
<CheckCircle className="w-5 h-5 text-emerald-500" />
<span className="text-emerald-500 font-medium">{currentLoadingText}</span>
</div>
</motion.div>
) : (
<motion.div
className="flex items-center gap-2 justify-center"
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
<motion.div
className="w-1.5 h-1.5 rounded-full bg-blue-500"
animate={{
scale: [1, 1.5, 1],
opacity: [1, 0.5, 1]
}}
transition={{
duration: 1,
repeat: Infinity,
repeatDelay: 0.2
}}
/>
{/* 阶段图标 */}
<motion.div
className="flex items-center gap-2"
key={currentLoadingText}
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 10 }}
transition={{ duration: 0.3 }}
>
<motion.div
className="text-blue-500"
animate={{
rotate: [0, 360],
scale: [1, 1.1, 1]
}}
transition={{
scale: { duration: 1.5, repeat: Infinity, ease: "easeInOut" }
}}
>
<StageIcon className="w-5 h-5" />
</motion.div>
<motion.div
className="relative"
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 10 }}
transition={{ duration: 0.3 }}
>
{/* 背景发光效果 */}
<motion.div
className="absolute inset-0 text-transparent bg-clip-text bg-gradient-to-r from-blue-400 via-cyan-400 to-purple-400 blur-sm"
animate={{
backgroundPosition: ["0% 50%", "100% 50%", "0% 50%"],
}}
transition={{
duration: 2,
repeat: Infinity,
ease: "linear"
}}
style={{
backgroundSize: "200% 200%",
}}
>
<span className="normalS400 subtitle-had8uE">{currentLoadingText}</span>
</motion.div>
{/* 主文字 - 颜色填充动画 */}
<motion.div
className="relative z-10"
animate={{
scale: [1, 1.02, 1],
}}
transition={{
duration: 1.5,
repeat: Infinity,
ease: "easeInOut"
}}
>
<motion.span
className="normalS400 subtitle-had8uE text-transparent bg-clip-text bg-gradient-to-r from-blue-600 via-cyan-500 to-purple-600"
animate={{
backgroundPosition: ["0% 50%", "100% 50%", "0% 50%"],
}}
transition={{
duration: 3,
repeat: Infinity,
ease: "linear"
}}
style={{
backgroundSize: "300% 300%",
}}
>
{currentLoadingText}
</motion.span>
</motion.div>
{/* 动态光点效果 */}
<motion.div
className="absolute left-0 top-1/2 transform -translate-y-1/2 w-2 h-2 bg-gradient-to-r from-cyan-400 to-blue-500 rounded-full blur-sm"
animate={{
x: [0, 200, 0],
opacity: [0, 1, 0],
scale: [0.5, 1, 0.5],
}}
transition={{
duration: 2.5,
repeat: Infinity,
ease: "easeInOut",
}}
/>
{/* 文字底部装饰线 */}
<motion.div
className="absolute bottom-0 left-0 h-0.5 bg-gradient-to-r from-blue-500 via-cyan-400 to-purple-500"
animate={{
width: ["0%", "100%", "0%"],
backgroundPosition: ["0% 50%", "100% 50%", "0% 50%"],
}}
transition={{
width: { duration: 2, repeat: Infinity, ease: "easeInOut" },
backgroundPosition: { duration: 1.5, repeat: Infinity, ease: "linear" }
}}
style={{
backgroundSize: "200% 200%",
}}
/>
</motion.div>
</motion.div>
<motion.div
className="w-1.5 h-1.5 rounded-full bg-blue-500"
animate={{
scale: [1, 1.5, 1],
opacity: [1, 0.5, 1]
}}
transition={{
duration: 1,
repeat: Infinity,
repeatDelay: 0.2,
delay: 0.3
}}
/>
<motion.div
className="w-1.5 h-1.5 rounded-full bg-blue-500"
animate={{
scale: [1, 1.5, 1],
opacity: [1, 0.5, 1]
}}
transition={{
duration: 1,
repeat: Infinity,
repeatDelay: 0.2,
delay: 0.6
}}
/>
</motion.div>
)}
</>
);
}