video-flow-b/components/pages/work-flow/use-workoffice-data.tsx
2025-07-25 11:41:12 +08:00

363 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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<ScriptContent>({
acts: [],
characters: [],
dialogue: undefined,
themes: [],
dramaticLine: undefined
});
const [thinkingColor, setThinkingColor] = useState<string>('');
const [thinkingText, setThinkingText] = useState<string>('');
const searchParams = useSearchParams();
const project_id = searchParams.get('episodeId') || '';
const [sceneData, setSceneData] = useState<any>([]);
const [shotSketchData, setShotSketchData] = useState<any>([]);
const [videoData, setVideoData] = useState<any>([]);
const [storyboardData, setStoryboardData] = useState<any>({});
const [sketchType, setSketchType] = useState<string>('');
const { sketchCount, videoCount } = useAppSelector((state) => state.workflow);
// 使用 ref 存储临时数据,避免重复创建对象
const tempDataRef = useRef<ScriptContent>({
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<ScriptContent>) => {
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<ScriptContent> = {};
// 三幕结构
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
};
}