'use client'; import { useEffect, useState, useRef, useCallback } from "react"; import { streamJsonPost } from "@/api/request"; import { useSearchParams } from "next/navigation"; import { Heart, Camera, Film, Scissors } from "lucide-react"; import { getSceneJson, getShotSketchJson, getVideoJson } from "@/api/video_flow"; import { useAppSelector } from "@/lib/store/hooks"; interface ScriptContent { acts?: Array<{ id: string; stableId: string; title: string; desc: string; beats: string[]; }>; characters?: Array<{ id: string; stableId: string; name: string; role: string; arc: string; desc: string; color: string; }>; dialogue?: { stableId: string; rhythm: string; style: string; }; themes?: Array<{ id: string; stableId: string; theme: string; desc: string; depth: string; }>; dramaticLine?: { stableId: string; points: Array<{ id: string; stableId: string; title: string; desc: string; intensity: number; // 0-100 情感强度 }>; }; } interface Stage { id: string; title: string; icon: React.ElementType; color: string; profession: string; } const stages: Stage[] = [ { id: 'script', title: 'Scriptwriter', icon: Heart, color: '#8b5cf6', profession: 'Scriptwriter' }, { id: 'storyboard', title: 'Storyboard artist', icon: Camera, color: '#06b6d4', profession: 'Storyboard artist' }, { id: 'production', title: 'Visual director', icon: Film, color: '#10b981', profession: 'Visual director' }, { id: 'editing', title: 'Editor', icon: Scissors, color: '#f59e0b', profession: 'Editor' } ]; const detailThinkingText = [ { default: 'is thinking...', acts: 'is thinking about the story structure...', characters: 'is thinking about the characters...', dialogue: 'is thinking about the dialogue...', themes: 'is thinking about the themes...', dramaticLine: 'is thinking about the dramatic line...', another: 'is thinking about the another...' }, { default: 'is thinking...', }, { default: 'is thinking...', }, { default: 'is thinking...', } ] export function useWorkofficeData(currentStage: number, isOpen: boolean, currentLoadingText: string) { const [scriptwriterData, setScriptwriterData] = useState({ acts: [], characters: [], dialogue: undefined, themes: [], dramaticLine: undefined }); const [thinkingColor, setThinkingColor] = useState(''); const [thinkingText, setThinkingText] = useState(''); const searchParams = useSearchParams(); const project_id = searchParams.get('episodeId') || ''; const [sceneData, setSceneData] = useState([]); const [shotSketchData, setShotSketchData] = useState([]); const [videoData, setVideoData] = useState([]); const [storyboardData, setStoryboardData] = useState({}); const [sketchType, setSketchType] = useState(''); const { sketchCount, videoCount } = useAppSelector((state) => state.workflow); // 使用 ref 存储临时数据,避免重复创建对象 const tempDataRef = useRef({ acts: [], characters: [], dialogue: undefined, themes: [], dramaticLine: undefined }); // 深度比较两个对象是否相等 const isEqual = (obj1: any, obj2: any): boolean => { if (obj1 === obj2) return true; if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return obj1 === obj2; if (obj1 === null || obj2 === null) return obj1 === obj2; const keys1 = Object.keys(obj1); const keys2 = Object.keys(obj2); if (keys1.length !== keys2.length) return false; for (const key of keys1) { if (!isEqual(obj1[key], obj2[key])) return false; } return true; }; // 更新思考文本的函数 const updateThinkingText = useCallback((data: ScriptContent) => { console.log('updateThinkingText', currentStage, isOpen); if (currentStage === 0) { if (!data.acts || data.acts.length === 0) { return `${stages[currentStage].profession} ${detailThinkingText[currentStage].acts}`; } if (!data.characters || data.characters.length === 0) { return `${stages[currentStage].profession} ${detailThinkingText[currentStage].characters}`; } if (!data.dialogue) { return `${stages[currentStage].profession} ${detailThinkingText[currentStage].dialogue}`; } if (!data.themes || data.themes.length === 0) { return `${stages[currentStage].profession} ${detailThinkingText[currentStage].themes}`; } if (!data.dramaticLine) { return `${stages[currentStage].profession} ${detailThinkingText[currentStage].dramaticLine}`; } return `${stages[currentStage].profession} ${detailThinkingText[currentStage].another}`; } return `${stages[currentStage].profession} ${detailThinkingText[currentStage].default}`; }, [currentStage]); useEffect(() => { console.log('currentStage---changed', currentStage); if (!isOpen) return; setThinkingColor(stages[currentStage].color); if (currentStage !== 0) return; // 暂时仅支持剧本创作阶段获取真实数据 // 剧本创作阶段 if (currentStage === 0) { // 重置临时数据 tempDataRef.current = { acts: [], characters: [], dialogue: undefined, themes: [], dramaticLine: undefined }; handleStreamData(); } else if (currentStage === 1) { // 不需要每次isOpen为true都请求,只需要请求一次,所以这里需要判断是否已经请求过 // 场景设计、分镜设计 if (!sceneData.length) { let tempSceneData: any[] = []; // 场景设计 getSceneJson({ project_id: project_id }).then((res: any) => { console.log('sceneJson', res); if (res.successful) { for (const [index, scene] of res.data.sketches.entries()) { tempSceneData.push({ id: `SC-${index + 1}`, location: scene.sketch_name, description: scene.sketch_description, core_atmosphere: scene.core_atmosphere }); } setSceneData(tempSceneData); } }); } if (!shotSketchData.length) { let tempShotSketchData: any[] = []; // 分镜设计 getShotSketchJson({ project_id: project_id }).then((res: any) => { console.log('shotSketchJson', res); if (res.successful) { for (const [index, shot] of res.data.shot_sketches.entries()) { tempShotSketchData.push({ id: index + 1, shotLanguage: shot.shot_type.split(', '), frame_description: shot.frame_description, atmosphere: shot.atmosphere.split(', '), camera_motion: shot.cinematography_blueprint_camera_motion.split(', '), composition: shot.cinematography_blueprint_composition, key_action: shot.key_action, dialogue_performance: { speaker: shot.dialogue_performance_speaker, language: shot.dialogue_performance_language, delivery: shot.dialogue_performance_delivery, line: shot.dialogue_performance_line } }); } setShotSketchData(tempShotSketchData); } }); } } else if (currentStage === 2) { // 视觉导演 getVideoJson({ project_id: project_id }).then((res: any) => { console.log('videoJson', res); }); } }, [currentStage, isOpen]); useEffect(() => { const newThinkingText = updateThinkingText(scriptwriterData); if (newThinkingText !== thinkingText) { setThinkingText(newThinkingText); } }, [scriptwriterData, updateThinkingText]); useEffect(() => { console.log('------sketchCount, sceneData, shotSketchData', sketchCount, sceneData, shotSketchData); if (!isOpen) return; if (!currentLoadingText.includes('shot sketch')) { if (!sceneData.length || sketchCount > sceneData.length) return; setSketchType('scene'); setStoryboardData(sceneData[sketchCount ? sketchCount - 1 : 0]); setThinkingText(`Drawing scene sketch: ${sceneData[sketchCount ? sketchCount - 1 : 0].id}...`); } else { if (!shotSketchData.length || sketchCount > shotSketchData.length) return; setSketchType('shot'); setStoryboardData(shotSketchData[sketchCount ? sketchCount - 1 : 0]); setThinkingText(`Drawing shot sketch: ${shotSketchData[sketchCount ? sketchCount - 1 : 0].id}...`); } }, [sceneData, shotSketchData, currentLoadingText, sketchCount, isOpen]); const updateStateIfChanged = useCallback((newData: Partial) => { const hasChanges = Object.entries(newData).some(([key, value]) => { return !isEqual(value, tempDataRef.current[key as keyof ScriptContent]); }); if (hasChanges) { const updatedData = { ...tempDataRef.current, ...newData }; // 只有在数据真正变化时才更新状态 if (!isEqual(updatedData, tempDataRef.current)) { tempDataRef.current = updatedData; setScriptwriterData(updatedData); } } }, []); async function handleStreamData() { await streamJsonPost('/movie/analyze_movie_script_stream', { project_id: project_id }, (data: any) => { console.log('workoffice---chunk', data); const newData: Partial = {}; // 三幕结构 if (data.name === 'Three-act Structure') { newData.acts = data.acts.map((act: any, index: number) => ({ id: String(index), stableId: `act${index}`, title: act.name, desc: act.description, beats: act.tags })); } // 人物设定 if (data.name === 'Character Arc Design') { newData.characters = data.characters.map((protagonist: any, index: number) => ({ id: String(index), stableId: `protagonist${index}`, name: protagonist.name, role: protagonist.role, arc: 'Growth and transformation', desc: protagonist.description, color: protagonist.gender === 'male' ? '#8b5cf6' : '#ec4899' })); } // 对话节奏 if (data.name === 'Dialogue Rhythm') { newData.dialogue = { stableId: 'dialogue1', rhythm: data.rhythm_and_pacing, style: data.style_and_voice }; } // 主题深化过程 if (data.name === 'Thematic Development') { newData.themes = data.themes.map((theme: any, index: number) => ({ id: String(index), stableId: `theme${index}`, theme: theme.name, desc: theme.desc, depth: theme.depth })); } // 戏演线 if (data.name === 'Dramatic Line') { newData.dramaticLine = { stableId: 'dramaticLine1', points: data.stages.map((point: any, index: number) => ({ id: String(index), stableId: `point${index}`, title: point.stage, desc: point.desc, intensity: point.score })) }; } // 只在数据真正变化时更新状态 if (Object.keys(newData).length > 0) { updateStateIfChanged(newData); } }); } return { scriptwriterData, thinkingText, thinkingColor, storyboardData, sketchType }; }