video-flow-b/components/pages/work-flow/use-workflow-data.tsx
2025-08-14 17:03:47 +08:00

722 lines
27 KiB
TypeScript

'use client';
import { useState, useEffect, useCallback } from 'react';
import { useSearchParams } from 'next/navigation';
import { detailScriptEpisodeNew, getScriptTitle, getRunningStreamData, pauseMovieProjectPlan, resumeMovieProjectPlan } from '@/api/video_flow';
import { useAppDispatch, useAppSelector } from '@/lib/store/hooks';
import { setSketchCount, setVideoCount } from '@/lib/store/workflowSlice';
import { useScriptService } from "@/app/service/Interaction/ScriptService";
import { useUpdateEffect } from '@/app/hooks/useUpdateEffect';
// 步骤映射
const STEP_MAP = {
'initializing': '0',
'sketch': '1',
'character': '2',
'video': '3',
'music': '4',
'final_video': '6'
} as const;
// 执行loading文字映射
const LOADING_TEXT_MAP = {
initializing: 'initializing...',
script: 'Generating script...',
getSketchStatus: 'Getting sketch status...',
sketch: (count: number, total: number) => `Generating sketch ${count + 1 > total ? total : count + 1}/${total}...`,
sketchComplete: 'Sketch generation complete',
character: 'Drawing characters...',
newCharacter: (count: number, total: number) => `Drawing character ${count + 1 > total ? total : count + 1}/${total}...`,
getShotSketchStatus: 'Getting shot sketch status...',
shotSketch: (count: number, total: number) => `Generating shot sketch ${count + 1 > total ? total : count + 1}/${total}...`,
getVideoStatus: 'Getting video status...',
video: (count: number, total: number) => `Generating video ${count + 1 > total ? total : count + 1}/${total}...`,
videoComplete: 'Video generation complete',
audio: 'Generating background audio...',
postProduction: (step: string) => `Post-production: ${step}...`,
final: 'Generating final product...',
complete: 'Task completed'
} as const;
type ApiStep = keyof typeof STEP_MAP;
// 添加 TaskObject 接口
interface TaskObject {
taskStatus: string;
title: string;
currentLoadingText: string;
sketchCount?: number;
totalSketchCount?: number;
isGeneratingSketch?: boolean;
isGeneratingVideo?: boolean;
roles?: any[];
music?: any[];
final?: any;
tags?: any[];
}
export function useWorkflowData() {
console.log('98877766777777888888990')
const searchParams = useSearchParams();
const episodeId = searchParams.get('episodeId') || '';
// 更新 taskObject 的类型
const [taskObject, setTaskObject] = useState<TaskObject | null>(null);
const [originalText, setOriginalText] = useState<string>('');
const [scriptData, setScriptData] = useState<any>(null);
const [taskSketch, setTaskSketch] = useState<any[]>([]);
const [taskScenes, setTaskScenes] = useState<any[]>([]);
const [taskShotSketch, setTaskShotSketch] = useState<any[]>([]);
const [taskVideos, setTaskVideos] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [currentStep, setCurrentStep] = useState('0');
const [currentSketchIndex, setCurrentSketchIndex] = useState(0);
const [isGeneratingSketch, setIsGeneratingSketch] = useState(false);
const [isGeneratingVideo, setIsGeneratingVideo] = useState(false);
const [currentLoadingText, setCurrentLoadingText] = useState('loading project info...');
const [totalSketchCount, setTotalSketchCount] = useState(0);
const [roles, setRoles] = useState<any[]>([]);
const [music, setMusic] = useState<any[]>([]);
const [final, setFinal] = useState<any>(null);
const [dataLoadError, setDataLoadError] = useState<string | null>(null);
const [needStreamData, setNeedStreamData] = useState(false);
const [isPauseWorkFlow, setIsPauseWorkFlow] = useState(false);
const [mode, setMode] = useState<'automatic' | 'manual' | 'auto'>('automatic');
let taskData: any = {
sketch: { data: [], total_count: -1 },
character: { data: [], total_count: -1 },
shot_sketch: { data: [], total_count: -1 },
video: { data: [], total_count: -1 },
status: '0'
};
let loadingText: any = LOADING_TEXT_MAP.initializing;
const dispatch = useAppDispatch();
const { sketchCount, videoCount } = useAppSelector((state) => state.workflow);
const {
scriptBlocksMemo, // 渲染剧本数据
initializeFromProject,
setAnyAttribute,
applyScript
} = useScriptService();
// 初始化剧本
useUpdateEffect(() => {
console.log('开始初始化剧本', originalText,episodeId);
// TODO 为什么一开始没项目id
originalText && initializeFromProject(episodeId, originalText).then(() => {
console.log('应用剧本');
// 自动模式下 应用剧本;手动模式 需要点击 下一步 触发
mode.includes('auto') && applyScript();
});
}, [originalText], {mode: 'none'});
// 监听剧本加载完毕
useEffect(() => {
if (scriptBlocksMemo.length > 0) {
console.log('scriptBlocksMemo 更新:', scriptBlocksMemo);
setScriptData(scriptBlocksMemo);
setCurrentLoadingText(LOADING_TEXT_MAP.script);
}
}, [scriptBlocksMemo]);
// 监听继续 请求更新数据
useUpdateEffect(() => {
if (currentStep === '6') {
return;
}
if (isPauseWorkFlow) {
pauseMovieProjectPlan({ project_id: episodeId });
} else {
resumeMovieProjectPlan({ project_id: episodeId });
}
}, [isPauseWorkFlow], { mode: "debounce", delay: 1000 });
// 自动开始播放一轮
const autoPlaySketch = useCallback(() => {
return new Promise<void>((resolve) => {
let currentIndex = 0;
const interval = 2000; // 每个草图显示2秒
const playNext = () => {
if (currentIndex < taskSketch.length) {
setCurrentSketchIndex(currentIndex);
currentIndex++;
setTimeout(playNext, interval);
} else {
// 播放完成后重置到第一个
setTimeout(() => {
setCurrentSketchIndex(0);
resolve();
}, 500); // 短暂延迟后重置
}
};
// 开始播放
playNext();
});
}, [taskSketch.length]);
// 草图生成完毕后自动播放一轮
useEffect(() => {
const handleAutoPlay = async () => {
if (!isGeneratingSketch && taskSketch.length > 0 && sketchCount === totalSketchCount && currentStep === '3' && !taskVideos.length) {
await autoPlaySketch();
}
};
handleAutoPlay();
}, [sketchCount, totalSketchCount, isGeneratingSketch, autoPlaySketch]);
// 更新 setSketchCount
const updateSketchCount = useCallback((count: number) => {
dispatch(setSketchCount(count));
}, [dispatch]);
// 更新 setVideoCount
const updateVideoCount = useCallback((count: number) => {
dispatch(setVideoCount(count));
}, [dispatch]);
// 替换原有的 setSketchCount 和 setVideoCount 调用
useEffect(() => {
console.log('sketchCount 已更新:', sketchCount);
currentStep !== '3' && setCurrentSketchIndex(sketchCount - 1);
}, [sketchCount]);
useEffect(() => {
console.log('videoCount 已更新:', videoCount);
setCurrentSketchIndex(videoCount - 1);
}, [videoCount]);
// 将 sketchCount 和 videoCount 放到 redux 中 每一次变化也要更新
// 添加手动播放控制
const handleManualPlay = useCallback(async () => {
if (!isGeneratingSketch && taskSketch.length > 0) {
await autoPlaySketch();
}
}, [isGeneratingSketch, taskSketch.length, autoPlaySketch]);
// 获取流式数据
const fetchStreamData = useCallback(async () => {
if (!episodeId || !needStreamData) return;
try {
const response = await getRunningStreamData({ project_id: episodeId });
if (!response.successful) {
throw new Error(response.message);
}
let sketchCount = 0;
const all_task_data = response.data;
// all_task_data 下标0 和 下标1 换位置
const temp = all_task_data[0];
all_task_data[0] = all_task_data[1];
all_task_data[1] = temp;
console.log('---look-all_task_data', all_task_data);
console.log('---look-taskData', taskData);
// 收集所有需要更新的状态
let stateUpdates: {
taskSketch?: any[];
taskScenes?: any[];
taskShotSketch?: any[];
roles?: any[];
taskVideos?: any[];
isGeneratingSketch?: boolean;
isGeneratingVideo?: boolean;
currentStep?: string;
currentLoadingText?: string;
final?: any;
needStreamData?: boolean;
totalSketchCount?: number;
} = {};
for (const task of all_task_data) {
// 如果有已完成的数据,同步到状态
if (task.task_name === 'generate_sketch' && (task.task_status !== 'COMPLETED' || taskData.sketch.total_count !== taskData.sketch.data.length)) {
taskData.status = '1';
const realSketchResultData = task.task_result.data.filter((item: any) => item.image_path);
if (realSketchResultData.length >= 0) {
// 正在生成草图中 替换 sketch 数据
const sketchList = [];
for (const sketch of realSketchResultData) {
sketchList.push({
url: sketch.image_path,
script: sketch.sketch_name
});
}
taskData.sketch.data = sketchList;
stateUpdates.taskSketch = sketchList;
stateUpdates.taskScenes = sketchList;
stateUpdates.isGeneratingSketch = true;
stateUpdates.totalSketchCount = task.task_result.total_count;
loadingText = LOADING_TEXT_MAP.sketch(sketchList.length, task.task_result.total_count);
}
if (task.task_status === 'COMPLETED') {
// 草图生成完成
taskData.sketch.total_count = taskData.sketch.data.length;
stateUpdates.isGeneratingSketch = false;
sketchCount = task.task_result.total_count;
loadingText = LOADING_TEXT_MAP.sketchComplete;
taskData.status = '2';
}
stateUpdates.currentStep = taskData.status;
break;
}
if (task.task_name === 'generate_character' && (task.task_status !== 'COMPLETED' || taskData.character.total_count !== taskData.character.data.length)) {
if (task.task_result.data.length >= 0 && roles.length !== task.task_result.data.length) {
// 正在生成角色中 替换角色数据
const characterList = [];
for (const character of task.task_result.data) {
characterList.push({
name: character.character_name,
url: character.image_path,
sound: null,
soundDescription: '',
roleDescription: character.character_description
});
}
taskData.character.data = characterList;
stateUpdates.roles = characterList;
loadingText = LOADING_TEXT_MAP.newCharacter(characterList.length, task.task_result.total_count);
}
if (task.task_status === 'COMPLETED') {
console.log('----------角色生成完成,有几个分镜', sketchCount);
// 角色生成完成
taskData.character.total_count = taskData.character.data.length;
taskData.status = '3';
loadingText = LOADING_TEXT_MAP.getShotSketchStatus;
}
stateUpdates.currentStep = taskData.status;
break;
}
if (task.task_name === 'generate_shot_sketch' && (task.task_status !== 'COMPLETED' || taskData.shot_sketch.total_count !== taskData.shot_sketch.data.length)) {
const realShotResultData = task.task_result.data.filter((item: any) => item.url);
if (realShotResultData.length >= 0) {
taskData.status = '1';
console.log('----------正在生成草图中 替换 sketch 数据', taskShotSketch.length, realShotResultData.length);
// 正在生成草图中 替换 sketch 数据
const sketchList = [];
for (const sketch of realShotResultData) {
sketchList.push({
url: sketch.url,
script: sketch.description
});
}
taskData.shot_sketch.data = sketchList;
stateUpdates.taskSketch = sketchList;
stateUpdates.taskShotSketch = sketchList;
stateUpdates.isGeneratingSketch = true;
stateUpdates.totalSketchCount = task.task_result.total_count;
loadingText = LOADING_TEXT_MAP.shotSketch(sketchList.length, task.task_result.total_count);
}
if (task.task_status === 'COMPLETED') {
// 草图生成完成
taskData.shot_sketch.total_count = taskData.shot_sketch.data.length;
stateUpdates.isGeneratingSketch = false;
stateUpdates.isGeneratingVideo = true;
sketchCount = task.task_result.total_count;
console.log('----------草图生成完成', sketchCount);
loadingText = LOADING_TEXT_MAP.getVideoStatus;
taskData.status = '3';
}
stateUpdates.currentStep = taskData.status;
break;
}
if (task.task_name === 'generate_videos' && (task.task_status !== 'COMPLETED' || taskData.video.total_count !== taskData.video.data.length)) {
const realTaskResultData = task.task_result.data.filter((item: any) => item.urls && item.urls.length > 0);
if (realTaskResultData.length >= 0) {
console.log('----------正在生成视频中', realTaskResultData.length);
// 正在生成视频中 替换视频数据
const videoList = [];
for (const video of realTaskResultData) {
// 每一项 video 有多个视频 先默认取第一个
videoList.push({
url: video.urls && video.urls.length > 0 ? video.urls.find((url: string) => url) : null,
script: video.description,
audio: null,
video_id: video.video_id,
});
}
taskData.video.data = videoList;
stateUpdates.taskVideos = videoList;
stateUpdates.isGeneratingVideo = true;
loadingText = LOADING_TEXT_MAP.video(videoList.length, task.task_result.total_count);
}
if (task.task_status === 'COMPLETED') {
console.log('----------视频生成完成');
// 视频生成完成
taskData.video.total_count = taskData.video.data.length;
stateUpdates.isGeneratingVideo = false;
taskData.status = '4';
// 暂时没有音频生成 直接跳过
taskData.status = '5';
loadingText = LOADING_TEXT_MAP.postProduction('generating rough cut video...');
}
stateUpdates.currentStep = taskData.status;
break;
}
// 粗剪
if (task.task_name === 'generate_final_simple_video') {
if (task.task_result && task.task_result.video) {
stateUpdates.final = {
url: task.task_result.video,
};
taskData.status = '5.5';
loadingText = LOADING_TEXT_MAP.postProduction('generating fine-grained video clips...');
}
}
// 最终剪辑
if (task.task_name === 'generate_final_video') {
if (task.task_result && task.task_result.video) {
stateUpdates.final = {
url: task.task_result.video,
};
taskData.status = '6';
loadingText = LOADING_TEXT_MAP.complete;
// 停止轮询
stateUpdates.needStreamData = false;
}
}
}
console.log('-----look-finalStep-----', taskData.status);
// 设置最终的状态更新
stateUpdates.currentStep = taskData.status;
stateUpdates.currentLoadingText = loadingText;
// 批量更新所有状态 因为上面的循环 会执行很多次更新 影响性能
if (stateUpdates.taskSketch) setTaskSketch(stateUpdates.taskSketch);
if (stateUpdates.taskScenes) setTaskScenes(stateUpdates.taskScenes);
if (stateUpdates.taskShotSketch) setTaskShotSketch(stateUpdates.taskShotSketch);
if (stateUpdates.roles) setRoles(stateUpdates.roles);
if (stateUpdates.taskVideos) setTaskVideos(stateUpdates.taskVideos);
if (stateUpdates.isGeneratingSketch !== undefined) setIsGeneratingSketch(stateUpdates.isGeneratingSketch);
if (stateUpdates.isGeneratingVideo !== undefined) setIsGeneratingVideo(stateUpdates.isGeneratingVideo);
if (stateUpdates.currentStep) setCurrentStep(stateUpdates.currentStep);
if (stateUpdates.currentLoadingText) setCurrentLoadingText(stateUpdates.currentLoadingText);
if (stateUpdates.final) setFinal(stateUpdates.final);
if (stateUpdates.needStreamData !== undefined) setNeedStreamData(stateUpdates.needStreamData);
if (stateUpdates.totalSketchCount) setTotalSketchCount(stateUpdates.totalSketchCount);
// Redux 更新放在最后,避免触发额外的 useEffect
if (stateUpdates.taskSketch) updateSketchCount(stateUpdates.taskSketch.length);
if (stateUpdates.taskVideos) updateVideoCount(stateUpdates.taskVideos.length);
// 更新 taskObject
if (stateUpdates.currentStep) {
setTaskObject(prev => {
if (!prev) return null;
return {
...prev,
taskStatus: stateUpdates.currentStep!
};
});
}
} catch (error) {
console.error('获取数据失败:', error);
}
}, [episodeId, needStreamData, roles.length, taskShotSketch.length]);
// 轮询获取流式数据
useEffect(() => {
let interval: NodeJS.Timeout;
if (needStreamData) {
interval = setInterval(fetchStreamData, 10000);
fetchStreamData(); // 立即执行一次
}
return () => {
if (interval) {
clearInterval(interval);
}
};
}, [needStreamData, fetchStreamData]);
// 初始化数据
const initializeWorkflow = async () => {
if (!episodeId) {
setDataLoadError('缺少必要的参数');
setIsLoading(false);
return;
}
try {
setIsLoading(true);
setCurrentLoadingText('loading project info...');
// 获取剧集详情
const response = await detailScriptEpisodeNew({ project_id: episodeId });
if (!response.successful) {
throw new Error(response.message);
}
const { name, status, data, tags, mode, original_text } = response.data;
setMode(mode as 'automatic' | 'manual' | 'auto');
setOriginalText(original_text);
setIsLoading(false);
// 设置初始数据
setTaskObject({
taskStatus: '0',
title: name || 'generating...',
currentLoadingText: status === 'COMPLETED' ? LOADING_TEXT_MAP.complete : LOADING_TEXT_MAP.initializing,
tags: tags || []
});
// 设置标题
if (!name) {
// 如果没有标题,轮询获取
const titleResponse = await getScriptTitle({ project_id: episodeId });
console.log('titleResponse', titleResponse);
if (titleResponse.successful) {
setTaskObject((prev: TaskObject | null) => ({
...(prev || {}),
title: titleResponse.data.title,
tags: titleResponse.data.tags || []
} as TaskObject));
}
}
if (status === 'COMPLETED') {
loadingText = LOADING_TEXT_MAP.complete;
taskData.status = '6';
}
// 如果有已完成的数据,同步到状态
if (data) {
if (data.sketch && data.sketch.data) {
taskData.status = '1';
const realSketchResultData = data.sketch.data.filter((item: any) => item.image_path);
const sketchList = [];
for (const sketch of realSketchResultData) {
sketchList.push({
url: sketch.image_path,
script: sketch.sketch_name,
});
}
taskData.sketch.data = sketchList;
taskData.sketch.total_count = data.sketch.total_count;
setTaskSketch(sketchList);
setTaskScenes(sketchList);
updateSketchCount(sketchList.length);
// 设置为最后一个草图
if (data.sketch.total_count > realSketchResultData.length) {
setIsGeneratingSketch(true);
loadingText = LOADING_TEXT_MAP.sketch(realSketchResultData.length, data.sketch.total_count);
} else {
taskData.status = '2';
if (!data.character || !data.character.data || !data.character.data.length) {
loadingText = LOADING_TEXT_MAP.newCharacter(0, data.character.total_count);
}
}
}
if (data.character && data.character.data && data.character.data.length > 0) {
const characterList = [];
for (const character of data.character.data) {
characterList.push({
name: character.character_name,
url: character.image_path,
sound: null,
soundDescription: '',
roleDescription: character.character_description
});
}
taskData.character.data = characterList;
taskData.character.total_count = data.character.total_count;
setRoles(characterList);
if (data.character.total_count > data.character.data.length) {
loadingText = LOADING_TEXT_MAP.newCharacter(data.character.data.length, data.character.total_count);
} else {
taskData.status = '3';
if (!data.video || !data.video.data || !data.video.data.length) {
loadingText = LOADING_TEXT_MAP.getShotSketchStatus;
}
}
}
if (data.shot_sketch && data.shot_sketch.data) {
const realShotResultData = data.shot_sketch.data.filter((item: any) => item.url);
const sketchList = [];
for (const sketch of realShotResultData) {
sketchList.push({
url: sketch.url,
script: sketch.description,
});
}
taskData.shot_sketch.data = sketchList;
taskData.shot_sketch.total_count = data.shot_sketch.total_count;
setTaskSketch(sketchList);
setTaskShotSketch(sketchList);
updateSketchCount(sketchList.length);
// 设置为最后一个草图
if (data.shot_sketch.total_count > realShotResultData.length) {
setIsGeneratingSketch(true);
loadingText = LOADING_TEXT_MAP.shotSketch(realShotResultData.length, data.shot_sketch.total_count);
} else {
taskData.status = '3';
setIsGeneratingVideo(true);
if (!data.character || !data.character.data || !data.character.data.length) {
loadingText = LOADING_TEXT_MAP.getVideoStatus;
}
}
}
if (data.video.data) {
const realDataVideoData = data.video.data.filter((item: any) => item.urls && item.urls.length > 0);
if (realDataVideoData.length === 0 && taskData.status === '3') {
loadingText = LOADING_TEXT_MAP.video(0, data.video.total_count);
}
if (realDataVideoData.length > 0) {
const videoList = [];
console.log('----------data.video.data', data.video.data);
for (const video of realDataVideoData) {
// 每一项 video 有多个视频 默认取存在的项
videoList.push({
url: video.urls && video.urls.length > 0 ? video.urls.find((url: string) => url) : null,
script: video.description,
audio: null,
video_id: video.video_id,
});
}
taskData.video.data = videoList;
taskData.video.total_count = data.video.total_count;
setTaskVideos(videoList);
updateVideoCount(videoList.length);
// 如果在视频步骤,设置为最后一个视频
if (data.video.total_count > realDataVideoData.length) {
setIsGeneratingVideo(true);
loadingText = LOADING_TEXT_MAP.video(realDataVideoData.length, data.video.total_count);
} else {
taskData.status = '4';
loadingText = LOADING_TEXT_MAP.audio;
// 暂时没有音频生成 直接跳过
taskData.status = '5';
loadingText = LOADING_TEXT_MAP.postProduction('generating rough cut video...');
}
}
}
// 粗剪
if ((data as any).final_simple_video && (data as any).final_simple_video.video) {
setFinal({
url: (data as any).final_simple_video.video
});
taskData.status = '5.5';
loadingText = LOADING_TEXT_MAP.postProduction('generating fine-grained video clips...');
}
if (data.final_video && data.final_video.video) {
setFinal({
url: data.final_video.video
});
taskData.status = '6';
loadingText = LOADING_TEXT_MAP.complete;
}
}
console.log('---look-taskData', taskData);
// 设置步骤
setCurrentStep(taskData.status);
setTaskObject(prev => {
if (!prev) return null;
return {
...prev,
taskStatus: taskData.status
};
});
console.log('---------loadingText', loadingText);
setCurrentLoadingText(loadingText);
// 设置是否需要获取流式数据
setNeedStreamData(status !== 'COMPLETED' && taskData.status !== '6');
} catch (error) {
console.error('初始化失败:', error);
setDataLoadError('加载失败,请重试');
setIsLoading(false);
}
};
// 回退到 指定状态 重新获取数据
const fallbackToStep = (step: string) => {
console.log('fallbackToStep', step);
setCurrentStep(step);
setNeedStreamData(true);
taskData = {
sketch: { data: [], total_count: -1 },
character: { data: [], total_count: -1 },
shot_sketch: { data: [], total_count: -1 },
video: { data: [], total_count: -1 },
status: step
};
// loadingText = LOADING_TEXT_MAP.initializing;
}
// 重试加载数据
const retryLoadData = () => {
setDataLoadError(null);
// 重置所有状态
setTaskSketch([]);
setTaskScenes([]);
setTaskVideos([]);
updateSketchCount(0);
updateVideoCount(0);
setRoles([]);
setMusic([]);
setFinal(null);
setCurrentSketchIndex(0);
setCurrentStep('0');
// 重新初始化
initializeWorkflow();
};
// 初始化
useEffect(() => {
initializeWorkflow();
}, [episodeId]);
return {
taskObject,
scriptData,
taskSketch,
taskScenes,
taskShotSketch,
taskVideos,
sketchCount,
isLoading,
currentStep,
currentSketchIndex,
isGeneratingSketch,
isGeneratingVideo,
currentLoadingText,
totalSketchCount,
roles,
music,
final,
dataLoadError,
setCurrentSketchIndex,
retryLoadData,
handleManualPlay,
isPauseWorkFlow,
mode,
setIsPauseWorkFlow,
setAnyAttribute,
applyScript,
fallbackToStep,
originalText
};
}