diff --git a/components/pages/work-flow.tsx b/components/pages/work-flow.tsx index d6a8ce1..aea2244 100644 --- a/components/pages/work-flow.tsx +++ b/components/pages/work-flow.tsx @@ -9,6 +9,7 @@ import { motion, AnimatePresence } from "framer-motion"; import { debounce } from "lodash"; import { GlassIconButton } from "@/components/ui/glass-icon-button"; import { EditModal } from "@/components/ui/edit-modal"; +import { ProgressiveReveal, presets } from '@/components/ui/progressive-reveal'; const MOCK_SKETCH_URLS = [ 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg', @@ -31,6 +32,12 @@ const MOCK_VIDEO_URLS = [ const MOCK_SKETCH_COUNT = 8; +const MOCK_FINAL_VIDEO = { + id: 'final-video', + url: 'https://cdn.qikongjian.com/videos/1750389908_37d4fffa-8516-43a3-a423-fc0274f40e8a_text_to_video_0.mp4', + thumbnail: 'https://d3phaj0sisr2ct.cloudfront.net/app/gen4/object-reference/welcome-ref-1.jpg', +}; + export default function WorkFlow() { const [taskObject, setTaskObject] = useState(null); const [projectObject, setProjectObject] = useState(null); @@ -48,6 +55,7 @@ export default function WorkFlow() { const [scrollLeft, setScrollLeft] = useState(0); const [showControls, setShowControls] = useState(false); const [isEditModalOpen, setIsEditModalOpen] = useState(false); + const [activeEditTab, setActiveEditTab] = useState('1'); const [taskVideos, setTaskVideos] = useState([]); const [isGeneratingVideo, setIsGeneratingVideo] = useState(false); const mainVideoRef = useRef(null); @@ -92,6 +100,32 @@ export default function WorkFlow() { setCurrentStep('3'); // 获取绘制角色后,开始获取分镜视频 await getTaskVideo(taskId); + await new Promise(resolve => setTimeout(resolve, 2000)); + // 首先修改 taskObject 下的 taskStatus 为 '4' + setTaskObject((prev: any) => ({ + ...prev, + taskStatus: '4' + })); + setCurrentStep('4'); + // 获取分镜视频后,开始获取背景音 + await getTaskBackgroundAudio(taskId); + await new Promise(resolve => setTimeout(resolve, 2000)); + // 首先修改 taskObject 下的 taskStatus 为 '5' + setTaskObject((prev: any) => ({ + ...prev, + taskStatus: '5' + })); + setCurrentStep('5'); + // 获取背景音后,开始获取最终成品 + await getTaskFinalProduct(taskId); + await new Promise(resolve => setTimeout(resolve, 2000)); + // 首先修改 taskObject 下的 taskStatus 为 '6' + setTaskObject((prev: any) => ({ + ...prev, + taskStatus: '6' + })); + setCurrentStep('6'); + // 获取最终成品后,任务完成 }); }, []); @@ -109,6 +143,15 @@ export default function WorkFlow() { } }, [currentSketchIndex, taskSketch.length]); + const handleEditModalOpen = (tab: string) => { + // 停止循环播放 + setIsPlaying(false); + // 停止分镜视频播放 + setIsVideoPlaying(false); + setActiveEditTab(tab); + setIsEditModalOpen(true); + } + // 处理鼠标/触摸拖动事件 const handleMouseDown = (e: React.MouseEvent) => { setIsDragging(true); @@ -156,7 +199,7 @@ export default function WorkFlow() { taskDescription: "Task 1 Description", taskStatus: "1", // '1' 绘制分镜、'2' 绘制角色、'3' 生成分镜视频、'4' 视频后期制作、'5' 最终成品 taskProgress: 0, - mode: 'auto', // 托管模式、人工干预模式 + mode: 'auto', // 全自动模式、人工干预模式 resolution: '1080p', // 1080p、2160p taskCreatedAt: new Date().toISOString(), taskUpdatedAt: new Date().toISOString(), @@ -202,6 +245,16 @@ export default function WorkFlow() { await new Promise(resolve => setTimeout(resolve, 2000)); // 模拟2秒延迟 } + // 模拟接口请求 获取背景音 + const getTaskBackgroundAudio = async (taskId: string) => { + await new Promise(resolve => setTimeout(resolve, 2000)); // 模拟2秒延迟 + } + + // 模拟接口请求 获取最终成品 + const getTaskFinalProduct = async (taskId: string) => { + await new Promise(resolve => setTimeout(resolve, 2000)); // 模拟2秒延迟 + } + // 模拟接口请求 每次获取一个分镜视频 轮询获取 const getTaskVideo = async (taskId: string) => { setIsGeneratingVideo(true); @@ -209,7 +262,7 @@ export default function WorkFlow() { // 模拟分批获取分镜视频 for (let i = 0; i < MOCK_SKETCH_COUNT; i++) { - await new Promise(resolve => setTimeout(resolve, 2000)); // 模拟2秒延迟 + await new Promise(resolve => setTimeout(resolve, 5000)); // 模拟5秒延迟 const newVideo = { id: `video-${i}`, @@ -242,80 +295,107 @@ export default function WorkFlow() { // 缓存渲染的缩略图列表 const renderedSketches = useMemo(() => taskSketch.map((sketch, index) => ( - !isDragging && setCurrentSketchIndex(index)} - initial={false} - animate={{ - scale: currentSketchIndex === index ? 1.05 : 1, - rotateY: currentSketchIndex === index ? 5 : 0, - rotateX: currentSketchIndex === index ? -5 : 0, - translateZ: currentSketchIndex === index ? '20px' : '0px', - transition: { - type: "spring", - stiffness: 300, - damping: 20 - } - }} - style={{ - transformStyle: 'preserve-3d', - perspective: '1000px' - }} > - + +
+ {`缩略图 +
+
场景 {index + 1}
-
+ )), [taskSketch, currentSketchIndex, isDragging] ); // 缓存渲染的视频缩略图列表 const renderedVideos = useMemo(() => taskVideos.map((video, index) => ( - !isDragging && setCurrentSketchIndex(index)} - initial={false} - animate={{ - scale: currentSketchIndex === index ? 1.05 : 1, - rotateY: currentSketchIndex === index ? 5 : 0, - rotateX: currentSketchIndex === index ? -5 : 0, - translateZ: currentSketchIndex === index ? '20px' : '0px', - transition: { - type: "spring", - stiffness: 300, - damping: 20 - } - }} - style={{ - transformStyle: 'preserve-3d', - perspective: '1000px' - }} > - + )), [taskVideos, currentSketchIndex, isDragging, taskSketch] ); @@ -353,6 +433,14 @@ export default function WorkFlow() { // 处理视频播放/暂停 const toggleVideoPlay = useCallback(() => { + if (mainVideoRef.current) { + if (isVideoPlaying) { + mainVideoRef.current.pause(); + } else { + // 从暂停位置继续播放 + mainVideoRef.current.play(); + } + } setIsVideoPlaying(prev => !prev); }, []); @@ -361,7 +449,10 @@ export default function WorkFlow() { if (isVideoPlaying && taskVideos.length > 0) { // 确保当前视频开始播放 if (mainVideoRef.current) { - mainVideoRef.current.play(); + mainVideoRef.current.play().catch(error => { + console.log('视频播放失败:', error); + setIsVideoPlaying(false); + }); } } else { // 暂停当前视频 @@ -384,12 +475,16 @@ export default function WorkFlow() { // 当切换视频时重置视频播放 useEffect(() => { if (mainVideoRef.current) { + // 只有在切换视频时才重置时间 mainVideoRef.current.currentTime = 0; if (isVideoPlaying) { - mainVideoRef.current.play(); + mainVideoRef.current.play().catch(error => { + console.log('视频播放失败:', error); + setIsVideoPlaying(false); + }); } } - }, [currentSketchIndex, isVideoPlaying]); + }, [currentSketchIndex]); // 当切换到分镜草图模式时,停止视频播放 useEffect(() => { @@ -419,6 +514,12 @@ export default function WorkFlow() { } else { setCurrentLoadingText('分镜视频生成完成'); } + } else if (currentStep === '4') { + setCurrentLoadingText('正在生成背景音...'); + } else if (currentStep === '5') { + setCurrentLoadingText('正在生成最终成品...'); + } else { + setCurrentLoadingText('任务完成'); } }, [isLoading, currentStep, isGeneratingSketch, sketchCount, isGeneratingVideo, taskVideos.length, taskSketch.length]); @@ -463,16 +564,19 @@ export default function WorkFlow() { ); } - if (currentStep === '3') { + if (Number(currentStep) > 2 && Number(currentStep) < 6) { return (
setShowControls(true)} onMouseLeave={() => setShowControls(false)} > {taskVideos[currentSketchIndex] ? ( - - + + ) : ( -
+
+ {/* 动态渐变背景 */} + {/* 动态光效 */} + +
-
setIsEditModalOpen(true)} + onClick={() => handleEditModalOpen('3')} /> {/* + +
+ {/* 最终成片视频 */} +
+
+
+ ); + } + return (
setShowControls(true)} onMouseLeave={() => setShowControls(false)} > {taskSketch[currentSketchIndex] ? ( - + + {`分镜草图 + ) : ( -
+
+ {/* 动态渐变背景 */} + {/* 动态光效 */} + +
-
setIsEditModalOpen(true)} + onClick={() => handleEditModalOpen('1')} /> {/* setIsDragging(false)} > - {currentStep === '3' ? ( + {(Number(currentStep) > 2 && Number(currentStep) < 6) ? ( <> {renderedVideos} {isGeneratingVideo && taskVideos.length < taskSketch.length && ( + {/* 动态渐变背景 */} + + {/* 动态光效 */} +
-
@@ -911,20 +1164,69 @@ export default function WorkFlow() {
)} + ) : Number(currentStep) === 6 ? ( + + ) : ( <> {renderedSketches} {isGeneratingSketch && sketchCount < MOCK_SKETCH_COUNT && ( + {/* 动态渐变背景 */} + + {/* 动态光效 */} +
-
@@ -974,9 +1275,11 @@ export default function WorkFlow() { setIsEditModalOpen(false)} taskStatus={taskObject?.taskStatus || '1'} taskSketch={taskSketch} + sketchVideo={taskVideos} currentSketchIndex={currentSketchIndex} onSketchSelect={setCurrentSketchIndex} /> diff --git a/components/ui/character-tab-content.tsx b/components/ui/character-tab-content.tsx new file mode 100644 index 0000000..90ac3a4 --- /dev/null +++ b/components/ui/character-tab-content.tsx @@ -0,0 +1,292 @@ +import React, { useState, useRef } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { Upload, Library, Play, Pause, RefreshCw, Wand2 } from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { GlassIconButton } from './glass-icon-button'; +import { ReplaceCharacterModal } from './replace-character-modal'; + +interface CharacterTabContentProps { + taskSketch: any[]; + currentSketchIndex: number; + onSketchSelect: (index: number) => void; +} + +// 模拟角色数据 +const MOCK_CHARACTERS = [ + { + id: 1, + name: '雪 (YUKI)', + avatar: '/assets/3dr_chihiro.png', + voiceDescription: '年轻女性,温柔而坚定的声线,语速适中,带有轻微的感性色彩。', + characterDescription: '一位接近二十岁或二十出头的年轻女性,东亚裔,拥有深色长发和刘海,五官柔和且富有表现力。', + voiceUrl: 'https://example.com/voice-sample.mp3' + }, + { + id: 2, + name: '春 (HARU)', + avatar: '/assets/3dr_mono.png', + voiceDescription: '年轻男性,清澈而温和的声线,语速从容,带有知性的特质。', + characterDescription: '一位接近二十岁或二十出头的年轻男性,东亚裔,拥有深色、发型整洁的中长发和深思的气质。', + voiceUrl: 'https://example.com/voice-sample.mp3' + }, +]; + +export function CharacterTabContent({ + taskSketch, + currentSketchIndex, + onSketchSelect +}: CharacterTabContentProps) { + const [selectedCharacterIndex, setSelectedCharacterIndex] = useState(0); + const [isReplaceModalOpen, setIsReplaceModalOpen] = useState(false); + const [activeReplaceMethod, setActiveReplaceMethod] = useState('upload'); + const [isPlaying, setIsPlaying] = useState(false); + const [progress, setProgress] = useState(0); + const [editingField, setEditingField] = useState<{ + type: 'name' | 'voiceDescription' | 'characterDescription' | null; + value: string; + }>({ type: null, value: '' }); + const audioRef = useRef(null); + + // 处理音频播放进度 + const handleTimeUpdate = () => { + if (audioRef.current) { + const progress = (audioRef.current.currentTime / audioRef.current.duration) * 100; + setProgress(progress); + } + }; + + // 处理播放/暂停 + const togglePlay = () => { + if (audioRef.current) { + if (isPlaying) { + audioRef.current.pause(); + } else { + audioRef.current.play(); + } + setIsPlaying(!isPlaying); + } + }; + + // 处理进度条点击 + const handleProgressClick = (e: React.MouseEvent) => { + if (audioRef.current) { + const rect = e.currentTarget.getBoundingClientRect(); + const x = e.clientX - rect.left; + const percentage = (x / rect.width) * 100; + const time = (percentage / 100) * audioRef.current.duration; + audioRef.current.currentTime = time; + setProgress(percentage); + } + }; + + return ( +
+ {/* 上部分:角色缩略图 */} + +
+
+ {MOCK_CHARACTERS.map((character, index) => ( + setSelectedCharacterIndex(index)} + whileHover={{ scale: 1.05 }} + whileTap={{ scale: 0.95 }} + > + {character.name} +
+ {character.name} +
+
+ ))} +
+
+
+ + {/* 中间部分:替换角色 */} + +

替换角色

+
+ { + setActiveReplaceMethod('upload'); + setIsReplaceModalOpen(true); + }} + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.98 }} + > + + 上传角色 + + + { + setActiveReplaceMethod('library'); + setIsReplaceModalOpen(true); + }} + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.98 }} + > + + 角色库 + +
+
+ + {/* 下部分:角色详情 */} + + {/* 左列:角色信息 */} +
+ {/* 角色姓名 */} +
+ + console.log('name changed:', e.target.value)} + className="w-full px-3 py-2 bg-white/5 border border-white/10 rounded-lg + focus:outline-none focus:border-blue-500" + /> +
+ {/* 声音描述 */} +
+ +