video-flow-b/components/pages/work-flow/use-workflow-data.tsx
2025-09-06 16:58:21 +08:00

738 lines
27 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 { notification } from 'antd';
import { showEditingNotification } from '@/components/pages/work-flow/editing-notification';
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';
interface UseWorkflowDataProps {
onEditPlanGenerated?: () => void;
}
export function useWorkflowData({ onEditPlanGenerated }: UseWorkflowDataProps = {}) {
const searchParams = useSearchParams();
const episodeId = searchParams.get('episodeId') || '';
const from = searchParams.get('from') || '';
const token = localStorage.getItem('token') || '';
const useid = JSON.parse(localStorage.getItem("currentUser") || '{}').id || NaN;
const notificationKey = useMemo(() => `video-workflow-${episodeId}`, [episodeId]);
useEffect(() => {
console.log("init-useWorkflowData");
return () => {
console.log("unmount-useWorkflowData");
// 组件卸载时销毁通知
notification.destroy(notificationKey);
// 清理window上的重置函数
if (typeof window !== 'undefined') {
delete (window as any)[`resetProgress_${notificationKey}`];
}
};
}, [notificationKey]);
// 查看缓存中 是否已经 加载过 这个项目的 剪辑计划
let isLoadedRef = useRef<string | null>(localStorage.getItem(`isLoaded_plan_${episodeId}`));
let tempTaskObject = useRef<TaskObject>({
title: '',
tags: [],
currentStage: 'script' 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.initializing);
const errorConfig = Number(process.env.NEXT_PUBLIC_ERROR_CONFIG);
// 更新 taskObject 的类型
const [taskObject, setTaskObject] = useState<TaskObject>(tempTaskObject.current);
const [currentSketchIndex, setCurrentSketchIndex] = useState(0);
const [currentLoadingText, setCurrentLoadingText] = useState('loading project info...');
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 [isGenerateEditPlan, setIsGenerateEditPlan] = useState(false);
const [state, setState] = useState({
mode: 'automatic' as 'automatic' | 'manual' | 'auto',
originalText: '',
isLoading: true
});
const {
scriptBlocksMemo, // 渲染剧本数据
initializeFromProject,
setAnyAttribute,
applyScript
} = useScriptService();
// 监听剧本加载完毕
const scriptData = useMemo(() => {
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 generateEditPlan = useCallback(async () => {
if (isLoadedRef.current) {
return;
}
// 调用重置方法
const resetFunc = (window as any)[`resetProgress_${notificationKey}`];
if (resetFunc) {
resetFunc();
}
// 更新通知内容
showEditingNotification({
key: notificationKey,
description: 'Generating intelligent editing plan...',
successDescription: '剪辑计划生成完成',
timeoutDescription: '剪辑计划生成失败,请重试',
timeout: 3 * 60 * 1000
});
// 先停止轮询
await new Promise(resolve => {
setNeedStreamData(false);
resolve(true);
});
try {
await getGenerateEditPlan({ project_id: episodeId });
console.error('生成剪辑计划成功');
setIsGenerateEditPlan(true);
isLoadedRef.current = 'true';
setNeedStreamData(true);
// 显示成功通知3秒
showEditingNotification({
key: notificationKey,
isCompleted: true,
description: '正在生成剪辑计划...',
successDescription: '剪辑计划生成完成',
timeout: 3000
});
setTimeout(() => {
notification.destroy(notificationKey);
}, 3000);
// 触发回调,通知父组件计划生成完成
onEditPlanGenerated?.();
} catch (error) {
console.error('生成剪辑计划失败:', error);
setNeedStreamData(true);
setIsGenerateEditPlan(false);
// 显示失败通知3秒
showEditingNotification({
key: notificationKey,
description: '正在生成剪辑计划...',
timeoutDescription: '剪辑计划生成失败,请重试',
timeout: 3000
});
setTimeout(() => {
notification.destroy(notificationKey);
}, 3000);
}
}, [episodeId, onEditPlanGenerated, notificationKey]);
const openEditPlan = useCallback(async () => {
window.open(`https://smartcut.movieflow.ai/ai-editor/${episodeId}?token=${token}&user_id=${useid}`, '_target');
}, [episodeId]);
useEffect(() => {
// 主动触发剪辑
if (canGoToCut && taskObject.currentStage === 'video') {
generateEditPlan();
}
}, [canGoToCut, taskObject.currentStage]);
useEffect(() => {
if (isShowError) {
window.msg.error('Too many failed storyboards, unable to execute automatic editing.', 3000);
}
}, [isShowError]);
useUpdateEffect(() => {
console.log('-----look-taskObject_find_changed-----', taskObject);
if (taskObject.currentStage === 'script') {
if (scriptBlocksMemo.length > 0) {
console.log('应用剧本');
// 自动模式下 应用剧本;手动模式 需要点击 下一步 触发
// 确保仅自动触发一次
// state.mode.includes('auto') && loadingText.current !== LOADING_TEXT_MAP.character && applyScript();
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;
}
setCurrentLoadingText(loadingText.current);
}, [scriptBlocksMemo, taskObject.currentStage, taskObject.scenes.data, taskObject.roles.data, taskObject.videos.data, taskObject.status], {mode: 'none'});
// 将 sketchCount 和 videoCount 放到 redux 中 每一次变化也要更新
// 添加手动播放控制
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;
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_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);
}
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),
type: 'scene'
});
}
taskCurrent.scenes.data = sketchList;
if (task.task_status === 'COMPLETED') {
// 草图生成完成
}
break;
}
}
// debugger;
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'
});
}
taskCurrent.videos.data = videoList;
console.log('----------正在生成视频中', realTaskResultData.length);
break;
}
if (task.task_status === 'COMPLETED') {
console.log('----------视频生成完成');
// 视频生成完成
// 暂时没有音频生成 直接跳过
// 视频分析
const error_totle = taskCurrent.videos.data.filter((item: any) => item.video_status === 2).length;
const total_count = taskCurrent.videos.data.length;
let analyze_video_completed_count = all_task_data.filter((item: any) => item.task_name === 'generate_analyze_video' && item.task_status !== 'INIT' && item.task_status !== 'RUNNING').length;
let analyze_video_total_count = all_task_data.filter((item: any) => item.task_name === 'generate_analyze_video').length;
// 检查是否需要开始显示视频分析进度
// 只在第一次检测到视频分析任务时显示通知
if (analyze_video_total_count > 0 && !isAnalyzing && analyze_video_completed_count !== analyze_video_total_count) {
setIsAnalyzing(true);
// 如果是第一次显示通知才调用showEditingNotification
const resetFunc = (window as any)[`resetProgress_${notificationKey}`];
if (!resetFunc) {
showEditingNotification({
key: notificationKey,
description: 'Preparing intelligent editing plan...',
successDescription: 'Preparing successful',
timeoutDescription: 'Preparing failed, please try again',
timeout: 3 * 60 * 1000
});
}
}
if (analyze_video_total_count && analyze_video_completed_count === analyze_video_total_count) {
// 视频分析完成
if(error_totle < total_count * errorConfig) {
setCanGoToCut(true);
// 重置进度条,显示生成剪辑计划进度
setIsAnalyzing(false);
} else {
setIsShowError(true);
notification.destroy(notificationKey);
setIsAnalyzing(false);
}
}
}
}
// 粗剪
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';
taskCurrent.status = 'COMPLETED';
}
}
// 最终剪辑
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 { status, data, tags, mode, original_text, title, name, final_simple_video, final_video } = 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.name;
taskCurrent.tags = titleResponse.data.description.tags || [];
}
}
if (status === 'COMPLETED') {
loadingText = 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[] = [];
console.log('----------data.video.data', data.video.data);
for (const video of data.video.data) {
videoUrls = video.urls ? video.urls.filter((url: null | string) => url !== null) : [];
console.log('----------videoUrls', videoUrls);
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'
});
}
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';
}
console.log('---look-taskData', taskCurrent);
if (taskCurrent.currentStage === 'script') {
console.log('开始初始化剧本', original_text,episodeId);
// TODO 为什么一开始没项目id
original_text && initializeFromProject(episodeId, original_text).then(() => {
});
}
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(taskCurrent.status !== 'COMPLETED');
} catch (error) {
console.error('初始化失败:', error);
setDataLoadError('加载失败,请重试');
setState({
mode: 'automatic' as 'automatic' | 'manual' | 'auto',
originalText: '',
isLoading: false
});
}
};
// 重试生成视频
const handleRetryVideo = async (video_id: string) => {
try {
// 先停止轮询
await new Promise(resolve => {
setNeedStreamData(false);
resolve(true);
});
// 重置视频状态为生成中
await new Promise(resolve => {
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();
}, [episodeId]);
return {
taskObject,
scriptData,
isLoading: state.isLoading,
currentSketchIndex,
currentLoadingText,
dataLoadError,
setCurrentSketchIndex,
retryLoadData,
handleManualPlay,
isPauseWorkFlow,
mode: state.mode,
setIsPauseWorkFlow,
setAnyAttribute,
applyScript,
fallbackToStep,
originalText: state.originalText,
showGotoCutButton: (canGoToCut && (isGenerateEditPlan || taskObject.currentStage === 'final_video') || isShowError) ? true : false,
generateEditPlan: openEditPlan,
handleRetryVideo
};
}