forked from 77media/video-flow
463 lines
16 KiB
TypeScript
463 lines
16 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { useSearchParams } from 'next/navigation';
|
|
import { detailScriptEpisodeNew, getScriptTitle, getRunningStreamData } from '@/api/video_flow';
|
|
|
|
// 步骤映射
|
|
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...',
|
|
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() {
|
|
const searchParams = useSearchParams();
|
|
const episodeId = searchParams.get('episodeId');
|
|
|
|
// 更新 taskObject 的类型
|
|
const [taskObject, setTaskObject] = useState<TaskObject | null>(null);
|
|
const [taskSketch, setTaskSketch] = useState<any[]>([]);
|
|
const [taskVideos, setTaskVideos] = useState<any[]>([]);
|
|
const [sketchCount, setSketchCount] = useState(0);
|
|
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('正在加载项目数据...');
|
|
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 fetchStreamData = async () => {
|
|
if (!episodeId || !needStreamData) return;
|
|
|
|
try {
|
|
const response = await getRunningStreamData({ project_id: episodeId });
|
|
if (!response.successful) {
|
|
throw new Error(response.message);
|
|
}
|
|
|
|
let loadingText: any = LOADING_TEXT_MAP.initializing;
|
|
let finalStep = '1', sketchCount = 0, isChange = false;
|
|
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('all_task_data', all_task_data);
|
|
for (const task of all_task_data) {
|
|
|
|
// 如果有已完成的数据,同步到状态
|
|
if (task.task_name === 'generate_sketch' && task.task_result) {
|
|
if (task.task_result.data.length >= 0 && taskSketch.length < task.task_result.data.length) {
|
|
// 正在生成草图中 替换 sketch 数据
|
|
const sketchList = [];
|
|
for (const sketch of task.task_result.data) {
|
|
sketchList.push({
|
|
url: sketch.image_path,
|
|
script: sketch.sketch_name
|
|
});
|
|
}
|
|
setTaskSketch(sketchList);
|
|
setSketchCount(sketchList.length);
|
|
setIsGeneratingSketch(true);
|
|
setCurrentSketchIndex(sketchList.length - 1);
|
|
loadingText = LOADING_TEXT_MAP.sketch(sketchList.length, task.task_result.total_count);
|
|
}
|
|
if (task.task_status === 'COMPLETED') {
|
|
// 草图生成完成
|
|
setIsGeneratingSketch(false);
|
|
sketchCount = task.task_result.total_count;
|
|
console.log('----------草图生成完成', sketchCount);
|
|
loadingText = LOADING_TEXT_MAP.sketchComplete;
|
|
finalStep = '2';
|
|
}
|
|
setTotalSketchCount(task.task_result.total_count);
|
|
}
|
|
if (task.task_name === 'generate_character' && task.task_result) {
|
|
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
|
|
});
|
|
}
|
|
setRoles(characterList);
|
|
loadingText = LOADING_TEXT_MAP.newCharacter(characterList.length, task.task_result.total_count);
|
|
}
|
|
if (task.task_status === 'COMPLETED') {
|
|
console.log('----------角色生成完成,有几个分镜', sketchCount);
|
|
// 角色生成完成
|
|
finalStep = '3';
|
|
|
|
loadingText = LOADING_TEXT_MAP.getShotSketchStatus;
|
|
}
|
|
}
|
|
if (task.task_name === 'generate_shot_sketch' && task.task_result) {
|
|
if (task.task_result.data.length >= 0 && taskSketch.length < task.task_result.data.length) {
|
|
console.log('----------正在生成草图中 替换 sketch 数据');
|
|
// 正在生成草图中 替换 sketch 数据
|
|
const sketchList = [];
|
|
for (const sketch of task.task_result.data) {
|
|
sketchList.push({
|
|
url: sketch.url,
|
|
script: sketch.description
|
|
});
|
|
}
|
|
setTaskSketch(sketchList);
|
|
setSketchCount(sketchList.length);
|
|
setIsGeneratingSketch(true);
|
|
setCurrentSketchIndex(sketchList.length - 1);
|
|
loadingText = LOADING_TEXT_MAP.shotSketch(sketchList.length, task.task_result.total_count);
|
|
}
|
|
if (task.task_status === 'COMPLETED') {
|
|
// 草图生成完成
|
|
setIsGeneratingSketch(false);
|
|
sketchCount = task.task_result.total_count;
|
|
console.log('----------草图生成完成', sketchCount);
|
|
loadingText = LOADING_TEXT_MAP.video(0, task.task_result.total_count);
|
|
finalStep = '2';
|
|
}
|
|
setTotalSketchCount(task.task_result.total_count);
|
|
}
|
|
if (task.task_name === 'generate_videos' && task.task_result) {
|
|
if (task.task_result.data.length >= 0 && taskVideos.length !== task.task_result.data.length) {
|
|
console.log('----------正在生成视频中-发生变化才触发');
|
|
// 正在生成视频中 替换视频数据
|
|
const videoList = [];
|
|
for (const video of task.task_result.data) {
|
|
// 每一项 video 有多个视频 先默认取第一个
|
|
videoList.push({
|
|
url: video.urls[0],
|
|
script: video.description,
|
|
audio: null,
|
|
});
|
|
}
|
|
setTaskVideos(videoList);
|
|
setIsGeneratingVideo(true);
|
|
setCurrentSketchIndex(videoList.length - 1);
|
|
loadingText = LOADING_TEXT_MAP.video(videoList.length, task.task_result.total_count);
|
|
}
|
|
if (task.task_status === 'COMPLETED') {
|
|
console.log('----------视频生成完成');
|
|
// 视频生成完成
|
|
setIsGeneratingVideo(false);
|
|
finalStep = '4';
|
|
|
|
// 暂时没有音频生成 直接跳过
|
|
finalStep = '5';
|
|
loadingText = LOADING_TEXT_MAP.postProduction('post-production...');
|
|
}
|
|
}
|
|
if (task.task_name === 'generate_final_video') {
|
|
if (task.task_result && task.task_result.video) {
|
|
setFinal({
|
|
url: task.task_result.video,
|
|
})
|
|
finalStep = '6';
|
|
loadingText = LOADING_TEXT_MAP.complete;
|
|
|
|
// 停止轮询
|
|
setNeedStreamData(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log('----------finalStep', finalStep);
|
|
// 设置步骤
|
|
setCurrentStep(finalStep);
|
|
setTaskObject(prev => {
|
|
if (!prev) return null;
|
|
return {
|
|
...prev,
|
|
taskStatus: finalStep
|
|
};
|
|
});
|
|
setCurrentLoadingText(loadingText);
|
|
|
|
} catch (error) {
|
|
console.error('获取数据失败:', error);
|
|
}
|
|
};
|
|
|
|
// 轮询获取流式数据
|
|
useEffect(() => {
|
|
let interval: NodeJS.Timeout;
|
|
|
|
if (needStreamData) {
|
|
interval = setInterval(fetchStreamData, 10000);
|
|
fetchStreamData(); // 立即执行一次
|
|
}
|
|
|
|
return () => {
|
|
if (interval) {
|
|
clearInterval(interval);
|
|
}
|
|
};
|
|
}, [needStreamData]);
|
|
|
|
// 初始化数据
|
|
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 } = response.data;
|
|
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));
|
|
}
|
|
}
|
|
|
|
let loadingText: any = LOADING_TEXT_MAP.initializing;
|
|
if (status === 'COMPLETED') {
|
|
loadingText = LOADING_TEXT_MAP.complete;
|
|
}
|
|
|
|
// 如果有已完成的数据,同步到状态
|
|
let finalStep = '1';
|
|
if (data) {
|
|
if (data.sketch && data.sketch.data && data.sketch.data.length > 0) {
|
|
const sketchList = [];
|
|
for (const sketch of data.sketch.data) {
|
|
sketchList.push({
|
|
url: sketch.image_path,
|
|
script: sketch.sketch_name,
|
|
});
|
|
}
|
|
setTaskSketch(sketchList);
|
|
setSketchCount(sketchList.length);
|
|
setTotalSketchCount(data.sketch.total_count);
|
|
// 设置为最后一个草图
|
|
if (data.sketch.total_count > data.sketch.data.length) {
|
|
setIsGeneratingSketch(true);
|
|
setCurrentSketchIndex(data.sketch.data.length - 1);
|
|
loadingText = LOADING_TEXT_MAP.sketch(data.sketch.data.length, data.sketch.total_count);
|
|
} else {
|
|
finalStep = '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
|
|
});
|
|
}
|
|
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 {
|
|
finalStep = '3';
|
|
if (!data.video || !data.video.data || !data.video.data.length) {
|
|
loadingText = LOADING_TEXT_MAP.getShotSketchStatus;
|
|
}
|
|
}
|
|
}
|
|
if (data.shot_sketch && data.shot_sketch.data && data.shot_sketch.data.length > 0) {
|
|
const sketchList = [];
|
|
for (const sketch of data.shot_sketch.data) {
|
|
sketchList.push({
|
|
url: sketch.url,
|
|
script: sketch.description,
|
|
});
|
|
}
|
|
setTaskSketch(sketchList);
|
|
setSketchCount(sketchList.length);
|
|
setTotalSketchCount(data.shot_sketch.total_count);
|
|
// 设置为最后一个草图
|
|
if (data.shot_sketch.total_count > data.shot_sketch.data.length) {
|
|
setIsGeneratingSketch(true);
|
|
setCurrentSketchIndex(data.shot_sketch.data.length - 1);
|
|
loadingText = LOADING_TEXT_MAP.shotSketch(data.shot_sketch.data.length, data.shot_sketch.total_count);
|
|
} else {
|
|
finalStep = '2';
|
|
if (!data.character || !data.character.data || !data.character.data.length) {
|
|
loadingText = LOADING_TEXT_MAP.video(0, data.shot_sketch.data.length);
|
|
}
|
|
}
|
|
}
|
|
if (data.video && data.video.data && data.video.data.length > 0) {
|
|
const videoList = [];
|
|
for (const video of data.video.data) {
|
|
// 每一项 video 有多个视频 先默认取第一个
|
|
videoList.push({
|
|
url: video.urls[0],
|
|
script: video.description,
|
|
audio: null,
|
|
});
|
|
}
|
|
setTaskVideos(videoList);
|
|
// 如果在视频步骤,设置为最后一个视频
|
|
if (data.video.total_count > data.video.data.length) {
|
|
setIsGeneratingVideo(true);
|
|
setCurrentSketchIndex(data.video.data.length - 1);
|
|
loadingText = LOADING_TEXT_MAP.video(data.video.data.length, data.video.total_count);
|
|
} else {
|
|
finalStep = '4';
|
|
loadingText = LOADING_TEXT_MAP.audio;
|
|
|
|
// 暂时没有音频生成 直接跳过
|
|
finalStep = '5';
|
|
loadingText = LOADING_TEXT_MAP.postProduction('post-production...');
|
|
}
|
|
}
|
|
if (data.final_video && data.final_video.video) {
|
|
setFinal({
|
|
url: data.final_video.video
|
|
});
|
|
finalStep = '6';
|
|
loadingText = LOADING_TEXT_MAP.complete;
|
|
}
|
|
}
|
|
|
|
// 设置步骤
|
|
setCurrentStep(finalStep);
|
|
setTaskObject(prev => {
|
|
if (!prev) return null;
|
|
return {
|
|
...prev,
|
|
taskStatus: finalStep
|
|
};
|
|
});
|
|
console.log('---------loadingText', loadingText);
|
|
setCurrentLoadingText(loadingText);
|
|
|
|
// 设置是否需要获取流式数据
|
|
setNeedStreamData(status !== 'COMPLETED' && finalStep !== '6');
|
|
|
|
} catch (error) {
|
|
console.error('初始化失败:', error);
|
|
setDataLoadError('加载失败,请重试');
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
// 重试加载数据
|
|
const retryLoadData = () => {
|
|
setDataLoadError(null);
|
|
// 重置所有状态
|
|
setTaskSketch([]);
|
|
setTaskVideos([]);
|
|
setSketchCount(0);
|
|
setTotalSketchCount(0);
|
|
setRoles([]);
|
|
setMusic([]);
|
|
setFinal(null);
|
|
setCurrentSketchIndex(0);
|
|
setCurrentStep('0');
|
|
// 重新初始化
|
|
initializeWorkflow();
|
|
};
|
|
|
|
// 初始化
|
|
useEffect(() => {
|
|
initializeWorkflow();
|
|
}, [episodeId]);
|
|
|
|
return {
|
|
taskObject,
|
|
taskSketch,
|
|
taskVideos,
|
|
sketchCount,
|
|
isLoading,
|
|
currentStep,
|
|
currentSketchIndex,
|
|
isGeneratingSketch,
|
|
isGeneratingVideo,
|
|
currentLoadingText,
|
|
totalSketchCount,
|
|
roles,
|
|
music,
|
|
final,
|
|
dataLoadError,
|
|
setCurrentSketchIndex,
|
|
retryLoadData,
|
|
};
|
|
}
|