'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(localStorage.getItem(`isLoaded_plan_${episodeId}`)); let tempTaskObject = useRef({ 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(tempTaskObject.current); const [currentSketchIndex, setCurrentSketchIndex] = useState(0); const [currentLoadingText, setCurrentLoadingText] = useState(LOADING_TEXT_MAP.getInfo); const [dataLoadError, setDataLoadError] = useState(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((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' || task.task_status === 'FAILED') { 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) { if (task.task_status === 'FAILED' && taskCurrent.roles.total_count === realCharacterResultData.length) { continue; } 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' || task.task_status === 'FAILED') ? 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' || task.task_status === 'FAILED') { 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) { if (task.task_status === 'FAILED' && taskCurrent.scenes.total_count === realSketchResultData.length) { continue; } 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' || task.task_status === 'FAILED') ? 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_urls?.[0] || '' }); } 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') { taskCurrent.currentStage = 'final_video'; taskCurrent.final.url = task.task_result.video_url; taskCurrent.final.note = 'combiner'; taskCurrent.final.snapshot_url = task.task_result.snapshot_url; taskCurrent.status = 'COMPLETED'; // 停止轮询 setNeedStreamData(false); } if (task.task_status === 'FAILED' || task.task_status === 'ERROR') { taskCurrent.status = 'FAILED'; // 停止轮询 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_simple_video_snapshot_url, 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' || data.character.task_status === 'FAILED') ? 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' || data.sketch.task_status === 'FAILED') ? 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.snapshot_url = final_simple_video_snapshot_url; 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; }); // 设置是否需要获取流式数据 如果任务已完成 或者 有正在生成的视频 则需要获取流式数据 // 判断是否有正在生成的视频 const isGeneratingVideo = taskCurrent.videos.data.some((item: any) => item.video_status === 0); setNeedStreamData(taskCurrent.status !== 'COMPLETED' || isGeneratingVideo); } 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 }; }