video-flow-b/components/pages/work-flow/use-workflow-data.tsx
2025-10-14 22:30:19 +08:00

661 lines
24 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, getGenerateEditPlan, regenerateVideoNew } from '@/api/video_flow';
import { useScriptService } from "@/app/service/Interaction/ScriptService";
import { useUpdateEffect } from '@/app/hooks/useUpdateEffect';
import { LOADING_TEXT_MAP, TaskObject, Status, Stage } from '@/api/DTO/movieEdit';
import { AspectRatioValue } from '@/components/ChatInputBox/AspectRatioSelector';
import { cutUrlTo, errorConfig } from '@/lib/env';
interface UseWorkflowDataProps {
}
export function useWorkflowData({}: UseWorkflowDataProps = {}) {
const searchParams = useSearchParams();
const episodeId = searchParams.get('episodeId') || '';
const token = localStorage.getItem('token') || '';
const useid = JSON.parse(localStorage.getItem("currentUser") || '{}').id || NaN;
const cutUrl = cutUrlTo;
useEffect(() => {
console.log("init-useWorkflowData");
return () => {
console.log("unmount-useWorkflowData");
};
}, []);
// 查看缓存中 是否已经 加载过 这个项目的 剪辑计划
let isLoadedRef = useRef<string | null>(localStorage.getItem(`isLoaded_plan_${episodeId}`));
let tempTaskObject = useRef<TaskObject>({
title: '',
tags: [],
currentStage: 'init' as Stage,
status: 'IN_PROGRESS' as Status,
roles: {
data: [],
total_count: -1
},
scenes: {
data: [],
total_count: -1
},
videos: {
data: [],
total_count: -1
},
final: {
url: '',
note: ''
}
});
let loadingText: any = useRef(LOADING_TEXT_MAP.getInfo);
// 更新 taskObject 的类型
const [taskObject, setTaskObject] = useState<TaskObject>(tempTaskObject.current);
const [currentSketchIndex, setCurrentSketchIndex] = useState(0);
const [currentLoadingText, setCurrentLoadingText] = useState<string>(LOADING_TEXT_MAP.getInfo);
const [dataLoadError, setDataLoadError] = useState<string | null>(null);
const [needStreamData, setNeedStreamData] = useState(false);
const [isPauseWorkFlow, setIsPauseWorkFlow] = useState(false);
const [canGoToCut, setCanGoToCut] = useState(false);
const [isShowError, setIsShowError] = useState(false);
const [isAnalyzing, setIsAnalyzing] = useState(false);
const [retryCount, setRetryCount] = useState(0);
const [isLoadingGenerateEditPlan, setIsLoadingGenerateEditPlan] = useState(false);
const [state, setState] = useState({
mode: 'automatic' as 'automatic' | 'manual' | 'auto',
aspectRatio: 'VIDEO_ASPECT_RATIO_LANDSCAPE' as AspectRatioValue,
originalText: '',
isLoading: true
});
const {
scriptBlocksMemo, // 渲染剧本数据
initializeFromProject,
setAnyAttribute,
applyScript
} = useScriptService();
// 监听剧本加载完毕
const scriptData = useMemo(() => {
console.log('scriptData', scriptBlocksMemo);
return scriptBlocksMemo.length > 0 ? scriptBlocksMemo : null;
}, [scriptBlocksMemo]);
// 监听继续 请求更新数据
useUpdateEffect(() => {
if (taskObject.status === 'COMPLETED' || taskObject.status === 'FAILED') {
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', 'sketch'].includes(taskObject.currentStage)) {
setCurrentSketchIndex(0);
}
}, [taskObject.currentStage]);
const openEditPlan = useCallback(async () => {
window.open(`${cutUrl}/ai-editor/${episodeId}?token=${token}&user_id=${useid}`, '_target');
}, [episodeId, cutUrl, token, useid]);
useUpdateEffect(() => {
if (taskObject.currentStage === 'script') {
if (scriptBlocksMemo.length > 0) {
loadingText.current = LOADING_TEXT_MAP.character;
} else {
loadingText.current = LOADING_TEXT_MAP.script;
}
}
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.getSketchStatus;
}
}
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.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('AI-powered video editing in progress…');
}
}
if (taskObject.currentStage === 'final_video' && taskObject.status !== 'COMPLETED') {
loadingText.current = LOADING_TEXT_MAP.postProduction('generating fine-grained video clips...');
}
if (taskObject.status === 'COMPLETED') {
loadingText.current = LOADING_TEXT_MAP.complete;
}
if (taskObject.status === 'FAILED') {
if (isShowError) {
loadingText.current = LOADING_TEXT_MAP.toManyFailed;
window.msg.error('Too many failed storyboards, unable to execute automatic editing.', 8000);
} else {
loadingText.current = LOADING_TEXT_MAP.editingError;
window.msg.error('Editing failed, Please click the scissors button to go to the intelligent editing platform.', 8000);
}
}
setCurrentLoadingText(loadingText.current);
}, [isShowError, scriptBlocksMemo, taskObject.currentStage, taskObject.scenes.data, taskObject.roles.data, taskObject.videos.data, taskObject.status], {mode: 'none'});
// 添加手动播放控制
const handleManualPlay = useCallback(async () => {
if (taskObject.currentStage === 'scene' && taskObject.scenes.data.length > 0) {
await autoPlaySketch(taskObject.scenes.data.length);
}
}, [taskObject.currentStage, taskObject.scenes.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);
}
const all_task_data = response.data;
const { current: taskCurrent } = tempTaskObject;
let combinerVideoUrl = '';
let combinerVideoPoster = '';
// 收集所有需要更新的状态
let stateUpdates = JSON.stringify(taskCurrent);
for (const task of all_task_data) {
// 如果有已完成的数据,同步到状态
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),
type: 'role'
});
}
taskCurrent.roles.data = characterList;
if (task.task_status === 'COMPLETED') {
// 角色生成完成
}
break;
}
}
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);
}
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),
type: 'scene'
});
}
taskCurrent.scenes.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 videoUrls: string[] = [];
let video_status = 0;
for (const video of task.task_result.data) {
videoUrls = video.urls ? video.urls.filter((url: null | string) => url !== null) : [];
// 适配旧数据
video_status = video.video_status === undefined ? (videoUrls.length > 0 ? 1 : 0) : video.video_status;
// 每一项 video 有多个视频 先默认取第一个
videoList.push({
urls: videoUrls,
video_id: video.video_id,
video_status: video_status, // 0 生成中 1 生成完成 2 生成失败
type: 'video',
snapshot_url: video.snapshot_url
});
}
taskCurrent.videos.data = videoList;
break;
}
if (task.task_status === 'COMPLETED') {
// 视频生成完成
// 暂时没有音频生成 直接跳过
// 视频分析
const error_totle = taskCurrent.videos.data.filter((item: any) => item.video_status === 2).length;
const total_count = taskCurrent.videos.data.length;
if(error_totle === total_count) {
setIsShowError(true);
taskCurrent.status = 'FAILED';
setNeedStreamData(false);
}
}
}
// 合成视频
if (task.task_name === 'combiner_videos') {
if (task.task_status === 'COMPLETED') {
combinerVideoUrl = task.task_result.video_url;
combinerVideoPoster = task.task_result.snapshot_url;
}
if (task.task_status === 'FAILED' || task.task_status === 'ERROR') {
taskCurrent.status = 'FAILED';
// 停止轮询
setNeedStreamData(false);
}
}
// 水印视频
if (task.task_name === 'watermark_videos') {
if (task.task_status === 'COMPLETED') {
taskCurrent.currentStage = 'final_video';
const videoUrl = task.task_result.video_url;
taskCurrent.final.url = (videoUrl && typeof videoUrl === 'object')
? videoUrl.result?.watermarked_url
: videoUrl;
taskCurrent.final.note = 'watermark';
taskCurrent.final.snapshot_url = combinerVideoPoster;
taskCurrent.status = 'COMPLETED';
// 停止轮询
setNeedStreamData(false);
}
if (task.task_status === 'FAILED' || task.task_status === 'ERROR') {
// 使用合成视频地址
taskCurrent.currentStage = 'final_video';
taskCurrent.final.url = combinerVideoUrl;
taskCurrent.final.note = 'combiner';
taskCurrent.final.snapshot_url = combinerVideoPoster;
taskCurrent.status = 'COMPLETED';
// 停止轮询
setNeedStreamData(false);
}
}
// 最终剪辑
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.final.snapshot_url = combinerVideoPoster;
taskCurrent.status = 'COMPLETED';
// 停止轮询
setNeedStreamData(false);
}
}
}
if (JSON.stringify(taskCurrent) !== stateUpdates) {
// 强制更新,使用新的对象引用确保触发更新
setTaskObject(prev => {
const newState = JSON.parse(JSON.stringify({...prev, ...taskCurrent}));
return newState;
});
}
} catch (error) {
console.error('获取数据失败:', error);
}
}, [episodeId, needStreamData, errorConfig, isAnalyzing]);
// 轮询获取流式数据
useUpdateEffect(() => {
let interval: NodeJS.Timeout;
if (needStreamData) {
interval = setInterval(fetchStreamData, 10000);
fetchStreamData(); // 立即执行一次
}
return () => {
if (interval) {
clearInterval(interval);
}
};
}, [needStreamData, fetchStreamData], {mode: 'none'});
// 初始化数据
const initializeWorkflow = useCallback(async () => {
if (!episodeId) {
setDataLoadError('缺少必要的参数');
return;
}
try {
setState({
mode: 'automatic' as 'automatic' | 'manual' | 'auto',
aspectRatio: 'VIDEO_ASPECT_RATIO_LANDSCAPE' as AspectRatioValue,
originalText: '',
isLoading: true
});
setCurrentLoadingText(LOADING_TEXT_MAP.getInfo);
// 获取剧集详情
const response = await detailScriptEpisodeNew({ project_id: episodeId });
if (!response.successful) {
throw new Error(response.message);
}
const { status, data, tags, mode, original_text, aspect_ratio, name, final_simple_video, final_video } = response.data;
const { current: taskCurrent } = tempTaskObject;
taskCurrent.title = name || 'generating...';
taskCurrent.tags = tags || [];
taskCurrent.status = status as Status;
taskCurrent.currentStage = 'script';
// 设置标题
if (!name) {
setCurrentLoadingText(LOADING_TEXT_MAP.initializing);
// 如果没有标题,轮询获取
const titleResponse = await getScriptTitle({ project_id: episodeId });
if (titleResponse.successful) {
taskCurrent.title = titleResponse.data.name;
taskCurrent.tags = titleResponse.data.description.tags || [];
}
}
if (status === 'COMPLETED') {
loadingText.current = LOADING_TEXT_MAP.complete;
taskCurrent.currentStage = 'final_video';
setCanGoToCut(true);
}
// 如果有已完成的数据,同步到状态
if (data) {
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),
type: 'role'
});
}
taskCurrent.roles.data = characterList;
taskCurrent.roles.total_count = data.character.total_count;
if (data.character.total_count > data.character.data.length) {
// 角色生成中
} else {
// 角色生成完成
}
}
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),
type: 'scene'
});
}
taskCurrent.scenes.data = sketchList;
taskCurrent.scenes.total_count = data.sketch.total_count;
// 设置为最后一个草图
if (data.sketch.total_count > realSketchResultData.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 = [];
let videoUrls: string[] = [];
for (const video of data.video.data) {
videoUrls = video.urls ? video.urls.filter((url: null | string) => url !== null) : [];
let video_status = video.video_status === undefined ? (videoUrls.length > 0 ? 1 : 0) : video.video_status;
// 每一项 video 有多个视频 默认取存在的项
videoList.push({
urls: videoUrls,
video_id: video.video_id,
video_status: video_status, // 0 生成中 1 生成完成 2 生成失败
type: 'video',
snapshot_url: video.snapshot_urls?.[0] || ''
});
}
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';
taskCurrent.status = 'COMPLETED';
}
if (data.final_video && data.final_video.video) {
taskCurrent.currentStage = 'final_video';
taskCurrent.final.url = data.final_video.video;
taskCurrent.final.note = 'final';
taskCurrent.status = 'COMPLETED';
}
}
// 粗剪
if (final_simple_video) {
taskCurrent.currentStage = 'final_video';
taskCurrent.final.url = final_simple_video;
taskCurrent.final.note = 'simple';
taskCurrent.status = 'COMPLETED';
}
if (final_video) {
taskCurrent.currentStage = 'final_video';
taskCurrent.final.url = final_video;
taskCurrent.final.note = 'final';
taskCurrent.status = 'COMPLETED';
}
if (taskCurrent.currentStage === 'script') {
// TODO 为什么一开始没项目id
original_text && initializeFromProject(episodeId, original_text).then(() => {
});
}
setState({
mode: mode as 'automatic' | 'manual' | 'auto',
aspectRatio: aspect_ratio as AspectRatioValue,
originalText: original_text,
isLoading: false
});
// 设置步骤
setTaskObject(prev => {
const newState = JSON.parse(JSON.stringify({...prev, ...taskCurrent}));
return newState;
});
// 设置是否需要获取流式数据
// setNeedStreamData(taskCurrent.status !== 'COMPLETED');
setNeedStreamData(true);
} catch (error) {
console.error('初始化失败:', error);
setDataLoadError('加载失败,请重试');
// 设置是否需要获取流式数据
setNeedStreamData(true);
setState({
mode: 'automatic' as 'automatic' | 'manual' | 'auto',
aspectRatio: 'VIDEO_ASPECT_RATIO_LANDSCAPE' as AspectRatioValue,
originalText: '',
isLoading: false
});
}
}, [episodeId]);
// 重试生成视频
const handleRetryVideo = async (video_id: string) => {
try {
// 先停止轮询
await new Promise(resolve => {
setNeedStreamData(false);
resolve(true);
});
// 重置视频状态为生成中
await new Promise(resolve => {
const { current: taskCurrent } = tempTaskObject;
taskCurrent.videos.data.find((v: any) => v.video_id === video_id)!.video_status = 0;
setTaskObject(prev => {
const newState = JSON.parse(JSON.stringify(prev));
const videoIndex = newState.videos.data.findIndex((v: any) => v.video_id === video_id);
if (videoIndex !== -1) {
newState.videos.data[videoIndex].video_status = 0;
}
return newState;
});
resolve(true);
});
// 调用重新生成接口
await regenerateVideoNew({ project_id: episodeId, video_id: video_id });
// 重新开启轮询
setNeedStreamData(true);
} catch (error) {
console.error('重试生成视频失败:', error);
// 发生错误时也要恢复轮询状态
setNeedStreamData(true);
}
};
// 回退到 指定状态 重新获取数据
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);
setCurrentSketchIndex(0);
// 重新初始化
initializeWorkflow();
};
// 初始化
useEffect(() => {
initializeWorkflow();
}, [initializeWorkflow]);
return {
taskObject,
scriptData,
isLoading: state.isLoading,
currentSketchIndex,
currentLoadingText,
dataLoadError,
setCurrentSketchIndex,
retryLoadData,
handleManualPlay,
isPauseWorkFlow,
mode: state.mode,
setIsPauseWorkFlow,
setAnyAttribute,
applyScript,
fallbackToStep,
originalText: state.originalText,
showGotoCutButton: (taskObject.status === 'FAILED' || taskObject.status === 'COMPLETED') ? true : false,
generateEditPlan: openEditPlan,
handleRetryVideo,
aspectRatio: state.aspectRatio
};
}