video-flow-b/components/pages/work-flow/use-workflow-data.tsx

682 lines
25 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 { useState, useEffect, useCallback, useRef, useMemo } 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';
import { TaskObject, Status, Stage } from '@/api/DTO/movieEdit';
// 步骤映射
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}/${total}...`,
sketchComplete: 'Sketch generation complete',
character: 'Drawing characters...',
newCharacter: (count: number, total: number) => `Drawing character ${count}/${total}...`,
getShotSketchStatus: 'Getting shot sketch status...',
shotSketch: (count: number, total: number) => `Generating shot sketch ${count}/${total}...`,
getVideoStatus: 'Getting video status...',
video: (count: number, total: number) => `Generating video ${count}/${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;
export function useWorkflowData() {
useEffect(() => {
console.log("init-useWorkflowData");
return () => console.log("unmount-useWorkflowData");
}, []);
const searchParams = useSearchParams();
const episodeId = searchParams.get('episodeId') || '';
let tempTaskObject = useRef<TaskObject>({
title: '',
tags: [],
currentStage: 'script',
status: 'IN_PROGRESS' as Status,
roles: {
data: [],
total_count: -1
},
scenes: {
data: [],
total_count: -1
},
shot_sketch: {
data: [],
total_count: -1
},
videos: {
data: [],
total_count: -1
},
final: {
url: '',
note: ''
}
});
let loadingText: any = useRef(LOADING_TEXT_MAP.initializing);
// 更新 taskObject 的类型
const [taskObject, setTaskObject] = useState<TaskObject>(tempTaskObject.current);
const [taskSketch, setTaskSketch] = useState<any[]>([]);
const [taskScenes, setTaskScenes] = useState<any[]>([]);
const [taskShotSketch, setTaskShotSketch] = useState<any[]>([]);
const [taskVideos, setTaskVideos] = useState<any[]>([]);
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 [state, setState] = useState({
mode: 'automatic' as 'automatic' | 'manual' | 'auto',
originalText: '',
isLoading: true
});
const dispatch = useAppDispatch();
const { sketchCount, videoCount } = useAppSelector((state) => state.workflow);
const {
scriptBlocksMemo, // 渲染剧本数据
initializeFromProject,
setAnyAttribute,
applyScript
} = useScriptService();
// 监听剧本加载完毕
const scriptData = useMemo(() => {
return scriptBlocksMemo.length > 0 ? scriptBlocksMemo : null;
}, [scriptBlocksMemo]);
// 监听继续 请求更新数据
useUpdateEffect(() => {
if (taskObject.status !== 'IN_PROGRESS') {
return;
}
if (isPauseWorkFlow) {
pauseMovieProjectPlan({ project_id: episodeId });
} else {
resumeMovieProjectPlan({ project_id: episodeId });
}
}, [isPauseWorkFlow], { mode: "debounce", delay: 1000 });
// 自动开始播放一轮
const autoPlaySketch = useCallback((length: number) => {
return new Promise<void>((resolve) => {
let currentIndex = 0;
const interval = 2000; // 每个草图显示2秒
const playNext = () => {
if (currentIndex < length) {
console.log('自动播放设置索引:', currentIndex);
setCurrentSketchIndex(currentIndex);
currentIndex++;
setTimeout(playNext, interval);
} else {
// 播放完成后重置到第一个
setTimeout(() => {
console.log('自动播放完成重置索引到0');
setCurrentSketchIndex(0);
resolve();
}, 500); // 短暂延迟后重置
}
};
// 开始播放
playNext();
});
}, []);
useEffect(() => {
if (['video', 'shot_sketch', 'sketch'].includes(taskObject.currentStage)) {
setCurrentSketchIndex(0);
}
}, [taskObject.currentStage]);
useUpdateEffect(() => {
console.log('-----look-taskObject_find_changed-----', taskObject);
if (taskObject.currentStage === 'script') {
if (scriptBlocksMemo.length > 0) {
loadingText.current = LOADING_TEXT_MAP.getSketchStatus;
} else {
loadingText.current = LOADING_TEXT_MAP.script;
}
}
if (taskObject.currentStage === 'scene') {
const realSketchResultData = taskObject.scenes.data.filter((item: any) => item.status !== 0);
if (taskObject.scenes.total_count > realSketchResultData.length) {
loadingText.current = LOADING_TEXT_MAP.sketch(realSketchResultData.length, taskObject.scenes.total_count);
} else {
loadingText.current = LOADING_TEXT_MAP.character;
}
}
if (taskObject.currentStage === 'character') {
const realCharacterResultData = taskObject.roles.data.filter((item: any) => item.status !== 0);
if (taskObject.roles.total_count > realCharacterResultData.length) {
loadingText.current = LOADING_TEXT_MAP.newCharacter(realCharacterResultData.length, taskObject.roles.total_count);
} else {
loadingText.current = LOADING_TEXT_MAP.getShotSketchStatus;
}
}
if (taskObject.currentStage === 'shot_sketch') {
const realShotResultData = taskObject.shot_sketch.data.filter((item: any) => item.status !== 0);
if (taskObject.shot_sketch.total_count > realShotResultData.length) {
loadingText.current = LOADING_TEXT_MAP.shotSketch(realShotResultData.length, taskObject.shot_sketch.total_count);
} else {
loadingText.current = LOADING_TEXT_MAP.getVideoStatus;
}
}
if (taskObject.currentStage === 'video') {
const realTaskResultData = taskObject.videos.data.filter((item: any) => item.video_status !== 0);
if (taskObject.videos.total_count > realTaskResultData.length) {
loadingText.current = LOADING_TEXT_MAP.video(realTaskResultData.length, taskObject.videos.total_count);
} else {
loadingText.current = LOADING_TEXT_MAP.postProduction('generating rough cut video...');
}
}
if (taskObject.currentStage === 'final_video') {
loadingText.current = LOADING_TEXT_MAP.postProduction('generating fine-grained video clips...');
}
if (taskObject.status === 'COMPLETED') {
loadingText.current = LOADING_TEXT_MAP.complete;
}
setCurrentLoadingText(loadingText.current);
}, [scriptBlocksMemo, taskObject.currentStage, taskObject.scenes.data, taskObject.roles.data, taskObject.shot_sketch.data, taskObject.videos.data], {mode: 'none'});
// 更新 setSketchCount
const updateSketchCount = useCallback((count: number) => {
dispatch(setSketchCount(count));
}, [dispatch]);
// 更新 setVideoCount
const updateVideoCount = useCallback((count: number) => {
dispatch(setVideoCount(count));
}, [dispatch]);
// 将 sketchCount 和 videoCount 放到 redux 中 每一次变化也要更新
// 添加手动播放控制
const handleManualPlay = useCallback(async () => {
if (taskObject.currentStage === 'scene' && taskObject.scenes.data.length > 0) {
await autoPlaySketch(taskObject.scenes.data.length);
}
if (taskObject.currentStage === 'shot_sketch' && taskObject.shot_sketch.data.length > 0) {
await autoPlaySketch(taskObject.shot_sketch.data.length);
}
}, [taskObject.currentStage, taskObject.scenes.data, taskObject.shot_sketch.data, 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;
const { current: taskCurrent } = tempTaskObject;
console.log('---look-all_task_data', all_task_data);
console.log('---look-tempTaskObject', taskCurrent);
// 收集所有需要更新的状态
let stateUpdates = JSON.stringify(taskCurrent);
for (const task of all_task_data) {
// 如果有已完成的数据,同步到状态
if (task.task_name === 'generate_sketch' && task.task_result && task.task_result.data) {
let realSketchResultData = task.task_result.data.filter((item: any) => item.image_path);
if (task.task_status === 'COMPLETED') {
realSketchResultData = taskCurrent.scenes.data.filter((item: any) => item.status !== 0);
}
console.log('---look-realSketchResultData', realSketchResultData);
taskCurrent.scenes.total_count = task.task_result.total_count;
if (task.task_status !== 'COMPLETED' || taskCurrent.scenes.total_count !== realSketchResultData.length) {
taskCurrent.currentStage = 'scene';
// 正在生成草图中 替换 sketch 数据
const sketchList = [];
for (const sketch of task.task_result.data) {
sketchList.push({
url: sketch.image_path,
script: sketch.sketch_name,
status: sketch.image_path ? 1 : (task.task_status === 'COMPLETED' ? 2 : 0)
});
}
taskCurrent.scenes.data = sketchList;
if (task.task_status === 'COMPLETED') {
// 草图生成完成
}
break;
}
}
if (task.task_name === 'generate_character' && task.task_result && task.task_result.data) {
let realCharacterResultData = task.task_result.data.filter((item: any) => item.image_path);
if (task.task_status === 'COMPLETED') {
realCharacterResultData = taskCurrent.roles.data.filter((item: any) => item.status !== 0);
}
taskCurrent.roles.total_count = task.task_result.total_count;
if (task.task_status !== 'COMPLETED' || taskCurrent.roles.total_count !== realCharacterResultData.length) {
taskCurrent.currentStage = 'character';
// 正在生成角色中 替换角色数据
const characterList = [];
for (const character of task.task_result.data) {
characterList.push({
name: character.character_name,
url: character.image_path,
status: character.image_path ? 1 : (task.task_status === 'COMPLETED' ? 2 : 0)
});
}
taskCurrent.roles.data = characterList;
if (task.task_status === 'COMPLETED') {
// 角色生成完成
}
break;
}
}
// debugger;
if (task.task_name === 'generate_shot_sketch' && task.task_result && task.task_result.data) {
let realShotResultData = task.task_result.data.filter((item: any) => item.url);
if (task.task_status === 'COMPLETED') {
realShotResultData = taskCurrent.shot_sketch.data.filter((item: any) => item.status !== 0);
}
taskCurrent.shot_sketch.total_count = task.task_result.total_count;
if (task.task_status !== 'COMPLETED' || taskCurrent.shot_sketch.total_count !== realShotResultData.length) {
taskCurrent.currentStage = 'shot_sketch';
console.log('----------正在生成草图中 替换 sketch 数据', realShotResultData.length);
// 正在生成草图中 替换 sketch 数据
const sketchList = [];
for (const sketch of task.task_result.data) {
sketchList.push({
url: sketch.url,
script: sketch.description,
status: sketch.url ? 1 : (task.task_status === 'COMPLETED' ? 2 : 0)
});
}
taskCurrent.shot_sketch.data = sketchList;
if (task.task_status === 'COMPLETED') {
// 草图生成完成
}
break;
}
}
if (task.task_name === 'generate_videos' && task.task_result && task.task_result.data) {
let realTaskResultData = task.task_result.data.filter((item: any) => (item.urls || (item.video_status !== 0 && item.video_status !== undefined)));
taskCurrent.videos.total_count = task.task_result.total_count;
if (task.task_status === 'COMPLETED') {
realTaskResultData = taskCurrent.videos.data.filter((item: any) => item.video_status !== 0);
}
if (task.task_status !== 'COMPLETED' || taskCurrent.videos.total_count !== realTaskResultData.length) {
taskCurrent.currentStage = 'video';
// 正在生成视频中 替换视频数据
const videoList = [];
let video_status = 0;
for (const video of task.task_result.data) {
// 适配旧数据
video_status = video.video_status === undefined ? (video.urls ? 1 : 0) : video.video_status;
// 完成 还是 0 就是 生成失败
video_status = task.task_status === 'COMPLETED' && video_status === 0 ? 2 : video_status;
// 每一项 video 有多个视频 先默认取第一个
videoList.push({
urls: video.urls,
video_id: video.video_id,
video_status: video_status, // 0 生成中 1 生成完成 2 生成失败
});
}
taskCurrent.videos.data = videoList;
console.log('----------正在生成视频中', realTaskResultData.length);
if (task.task_status === 'COMPLETED') {
console.log('----------视频生成完成');
// 视频生成完成
// 暂时没有音频生成 直接跳过
}
break;
}
}
// 粗剪
if (task.task_name === 'generate_final_simple_video') {
if (task.task_result && task.task_result.video) {
taskCurrent.currentStage = 'final_video';
taskCurrent.final.url = task.task_result.video;
taskCurrent.final.note = 'simple';
}
}
// 最终剪辑
if (task.task_name === 'generate_final_video') {
if (task.task_result && task.task_result.video) {
taskCurrent.currentStage = 'final_video';
taskCurrent.final.url = task.task_result.video;
taskCurrent.final.note = 'final';
taskCurrent.status = 'COMPLETED';
// 停止轮询
setNeedStreamData(false);
}
}
}
console.log('-----look-tempTaskObject-----', loadingText.current);
// 设置最终的状态更新
setCurrentLoadingText(loadingText.current);
if (JSON.stringify(taskCurrent) !== stateUpdates) {
console.log('-----look-tempTaskObject-changed-----', taskCurrent);
// 强制更新,使用新的对象引用确保触发更新
setTaskObject(prev => {
const newState = JSON.parse(JSON.stringify({...prev, ...taskCurrent}));
return newState;
});
}
} catch (error) {
console.error('获取数据失败:', error);
}
}, [episodeId, needStreamData]);
// 轮询获取流式数据
useUpdateEffect(() => {
let interval: NodeJS.Timeout;
if (needStreamData) {
interval = setInterval(fetchStreamData, 10000);
fetchStreamData(); // 立即执行一次
}
return () => {
if (interval) {
clearInterval(interval);
}
};
}, [needStreamData, fetchStreamData], {mode: 'none'});
// 初始化数据
const initializeWorkflow = async () => {
if (!episodeId) {
setDataLoadError('缺少必要的参数');
return;
}
try {
setState({
mode: 'automatic' as 'automatic' | 'manual' | 'auto',
originalText: '',
isLoading: 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;
const { current: taskCurrent } = tempTaskObject;
taskCurrent.title = name || 'generating...';
taskCurrent.tags = tags || [];
taskCurrent.status = status as Status;
// 设置标题
if (!name) {
// 如果没有标题,轮询获取
const titleResponse = await getScriptTitle({ project_id: episodeId });
console.log('titleResponse', titleResponse);
if (titleResponse.successful) {
taskCurrent.title = titleResponse.data.title;
taskCurrent.tags = titleResponse.data.tags || [];
}
}
if (status === 'COMPLETED') {
loadingText = LOADING_TEXT_MAP.complete;
taskCurrent.currentStage = 'final_video';
}
// 如果有已完成的数据,同步到状态
if (data) {
if (data.sketch && data.sketch.data) {
taskCurrent.currentStage = 'scene';
const realSketchResultData = data.sketch.data.filter((item: any) => item.image_path);
const sketchList = [];
for (const sketch of data.sketch.data) {
sketchList.push({
url: sketch.image_path,
script: sketch.sketch_name,
status: sketch.image_path ? 1 : (data.sketch.task_status === 'COMPLETED' ? 2 : 0)
});
}
taskCurrent.scenes.data = sketchList;
taskCurrent.scenes.total_count = data.sketch.total_count;
// 设置为最后一个草图
if (data.sketch.total_count > realSketchResultData.length) {
// 场景生成中
setIsGeneratingSketch(true);
} else {
// 场景生成完成
}
}
if (data.character && data.character.data && data.character.data.length > 0) {
taskCurrent.currentStage = 'character';
const characterList = [];
for (const character of data.character.data) {
characterList.push({
name: character.character_name,
url: character.image_path,
status: character.image_path ? 1 : (data.character.task_status === 'COMPLETED' ? 2 : 0)
});
}
taskCurrent.roles.data = characterList;
taskCurrent.roles.total_count = data.character.total_count;
if (data.character.total_count > data.character.data.length) {
// 角色生成中
} else {
// 角色生成完成
}
}
if (data.shot_sketch && data.shot_sketch.data) {
taskCurrent.currentStage = 'shot_sketch';
const realShotResultData = data.shot_sketch.data.filter((item: any) => item.url);
const sketchList = [];
for (const sketch of data.shot_sketch.data) {
sketchList.push({
url: sketch.url,
script: sketch.description,
status: sketch.url ? 1 : (data.shot_sketch.task_status === 'COMPLETED' ? 2 : 0)
});
}
taskCurrent.shot_sketch.data = sketchList;
taskCurrent.shot_sketch.total_count = data.shot_sketch.total_count;
// 设置为最后一个草图
if (data.shot_sketch.total_count > realShotResultData.length) {
// 草图生成中
} else {
// 草图生成完成
}
}
if (data.video.data) {
const realDataVideoData = data.video.data.filter((item: any) => (item.urls || (item.video_status !== 0 && item.video_status !== undefined)));
taskCurrent.currentStage = 'video';
taskCurrent.videos.total_count = data.video.total_count;
const videoList = [];
console.log('----------data.video.data', data.video.data);
for (const video of data.video.data) {
let video_status = video.video_status === undefined ? (video.urls ? 1 : 0) : video.video_status;
video_status = data.video.task_status === 'COMPLETED' && video_status === 0 ? 2 : video_status;
// 每一项 video 有多个视频 默认取存在的项
videoList.push({
urls: video.urls,
video_id: video.video_id,
video_status: video_status, // 0 生成中 1 生成完成 2 生成失败
});
}
taskCurrent.videos.data = videoList;
// 如果在视频步骤,设置为最后一个视频
if (data.video.total_count > realDataVideoData.length) {
// 视频生成中
} else {
// 视频生成完成
// 暂时没有音频生成 直接跳过
}
}
// 粗剪
if (data.final_simple_video && data.final_simple_video.video) {
taskCurrent.currentStage = 'final_video';
taskCurrent.final.url = data.final_simple_video.video;
taskCurrent.final.note = 'simple';
}
if (data.final_video && data.final_video.video) {
taskCurrent.currentStage = 'final_video';
taskCurrent.final.url = data.final_video.video;
taskCurrent.final.note = 'final';
}
}
console.log('---look-taskData', taskCurrent);
if (taskCurrent.currentStage === 'script') {
console.log('开始初始化剧本', original_text,episodeId);
// TODO 为什么一开始没项目id
original_text && initializeFromProject(episodeId, original_text).then(() => {
console.log('应用剧本');
// 自动模式下 应用剧本;手动模式 需要点击 下一步 触发
mode.includes('auto') && applyScript();
});
}
setState({
mode: mode as 'automatic' | 'manual' | 'auto',
originalText: original_text,
isLoading: false
});
// 设置步骤
setTaskObject(prev => {
const newState = JSON.parse(JSON.stringify({...prev, ...taskCurrent}));
return newState;
});
// 设置是否需要获取流式数据
setNeedStreamData(status !== 'COMPLETED');
} catch (error) {
console.error('初始化失败:', error);
setDataLoadError('加载失败,请重试');
setState({
mode: 'automatic' as 'automatic' | 'manual' | 'auto',
originalText: '',
isLoading: false
});
}
};
// 回退到 指定状态 重新获取数据
const fallbackToStep = (step: string) => {
console.log('fallbackToStep', step);
setNeedStreamData(true);
tempTaskObject.current.currentStage = step as Stage;
// 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: state.isLoading,
currentStep,
currentSketchIndex,
isGeneratingSketch,
isGeneratingVideo,
currentLoadingText,
totalSketchCount,
roles,
music,
final,
dataLoadError,
setCurrentSketchIndex,
retryLoadData,
handleManualPlay,
isPauseWorkFlow,
mode: state.mode,
setIsPauseWorkFlow,
setAnyAttribute,
applyScript,
fallbackToStep,
originalText: state.originalText
};
}