forked from 77media/video-flow
240 lines
6.3 KiB
TypeScript
240 lines
6.3 KiB
TypeScript
import { useState, useEffect, useCallback } from 'react';
|
||
import { detailScriptEpisodeNew, getScriptTitle, getRunningStreamData, StreamData } from '@/api/video_flow';
|
||
import { useSearchParams } from 'next/navigation';
|
||
import { ApiResponse } from '@/api/common';
|
||
|
||
// 步骤映射
|
||
const STEP_MAP = {
|
||
'sketch': '1',
|
||
'character': '2',
|
||
'video': '3',
|
||
'music': '4',
|
||
'final_video': '6'
|
||
} as const;
|
||
|
||
type ApiStep = keyof typeof STEP_MAP;
|
||
|
||
interface TaskData {
|
||
sketch?: Array<{
|
||
url: string;
|
||
script: string;
|
||
bg_rgb: string[];
|
||
}>;
|
||
roles?: Array<{
|
||
name: string;
|
||
url: string;
|
||
sound: string;
|
||
soundDescription: string;
|
||
roleDescription: string;
|
||
}>;
|
||
videos?: Array<{
|
||
url: string;
|
||
script: string;
|
||
audio: string;
|
||
}>;
|
||
music?: Array<{
|
||
url: string;
|
||
script: string;
|
||
name: string;
|
||
duration: string;
|
||
totalDuration: string;
|
||
isLooped: boolean;
|
||
}>;
|
||
final?: {
|
||
url: string;
|
||
};
|
||
}
|
||
|
||
export const useApiData = () => {
|
||
const searchParams = useSearchParams();
|
||
const episodeId = searchParams.get('episodeId');
|
||
|
||
const [title, setTitle] = useState<string>('');
|
||
const [currentStep, setCurrentStep] = useState<string>('1');
|
||
const [currentLoadingText, setCurrentLoadingText] = useState<string>('');
|
||
const [needStreamData, setNeedStreamData] = useState<boolean>(false);
|
||
const [taskData, setTaskData] = useState<TaskData>({});
|
||
const [streamInterval, setStreamInterval] = useState<NodeJS.Timeout | null>(null);
|
||
|
||
// 处理流式数据
|
||
const handleStreamData = useCallback((streamData: ApiResponse<StreamData>) => {
|
||
const { category, message, data, status } = streamData.data;
|
||
|
||
// 更新加载文本
|
||
setCurrentLoadingText(message);
|
||
|
||
// 根据类别更新任务数据
|
||
setTaskData(prevData => {
|
||
const newData = { ...prevData };
|
||
|
||
switch (category) {
|
||
case 'sketch':
|
||
if (!newData.sketch) newData.sketch = [];
|
||
// 更新或追加分镜数据
|
||
const existingSketchIndex = newData.sketch.findIndex(s => s.url === data.url);
|
||
if (existingSketchIndex === -1) {
|
||
newData.sketch.push(data);
|
||
} else {
|
||
newData.sketch[existingSketchIndex] = data;
|
||
}
|
||
break;
|
||
|
||
case 'character':
|
||
if (!newData.roles) newData.roles = [];
|
||
// 更新或追加角色数据
|
||
const existingRoleIndex = newData.roles.findIndex(r => r.name === data.name);
|
||
if (existingRoleIndex === -1) {
|
||
newData.roles.push(data);
|
||
} else {
|
||
newData.roles[existingRoleIndex] = data;
|
||
}
|
||
break;
|
||
|
||
case 'video':
|
||
if (!newData.videos) newData.videos = [];
|
||
// 更新或追加视频数据
|
||
const existingVideoIndex = newData.videos.findIndex(v => v.url === data.url);
|
||
if (existingVideoIndex === -1) {
|
||
newData.videos.push(data);
|
||
} else {
|
||
newData.videos[existingVideoIndex] = data;
|
||
}
|
||
break;
|
||
|
||
case 'music':
|
||
if (!newData.music) newData.music = [];
|
||
newData.music = [data];
|
||
break;
|
||
|
||
case 'final_video':
|
||
newData.final = data;
|
||
break;
|
||
}
|
||
|
||
return newData;
|
||
});
|
||
|
||
// 如果状态为 completed,停止获取流式数据
|
||
if (status === 'completed' || streamData.data.all_completed) {
|
||
setNeedStreamData(false);
|
||
}
|
||
}, []);
|
||
|
||
// 获取流式数据
|
||
const fetchStreamData = useCallback(async () => {
|
||
if (!episodeId || !needStreamData) return;
|
||
|
||
try {
|
||
const streamData = await getRunningStreamData({ project_id: episodeId });
|
||
handleStreamData(streamData);
|
||
} catch (error) {
|
||
console.error('获取流式数据失败:', error);
|
||
}
|
||
}, [episodeId, needStreamData, handleStreamData]);
|
||
|
||
// 启动流式数据轮询
|
||
useEffect(() => {
|
||
if (needStreamData && !streamInterval) {
|
||
const interval = setInterval(fetchStreamData, 10000); // 修改为10秒
|
||
setStreamInterval(interval);
|
||
} else if (!needStreamData && streamInterval) {
|
||
clearInterval(streamInterval);
|
||
setStreamInterval(null);
|
||
}
|
||
|
||
return () => {
|
||
if (streamInterval) {
|
||
clearInterval(streamInterval);
|
||
}
|
||
};
|
||
}, [needStreamData, fetchStreamData, streamInterval]);
|
||
|
||
// 获取标题的轮询函数
|
||
const pollTitle = useCallback(async () => {
|
||
if (!episodeId) return;
|
||
|
||
try {
|
||
const response = await getScriptTitle({ project_id: episodeId });
|
||
if (response.successful && response.data) {
|
||
setTitle(response.data.name);
|
||
return true;
|
||
}
|
||
} catch (error) {
|
||
console.error('获取标题失败:', error);
|
||
}
|
||
return false;
|
||
}, [episodeId]);
|
||
|
||
// 获取剧集详情
|
||
const fetchEpisodeDetail = useCallback(async () => {
|
||
if (!episodeId) return;
|
||
|
||
try {
|
||
const response = await detailScriptEpisodeNew({ project_id: episodeId });
|
||
if (response.successful) {
|
||
const { name, status, step, last_message, data } = response.data;
|
||
|
||
// 设置标题
|
||
if (name) {
|
||
setTitle(name);
|
||
}
|
||
|
||
// 设置步骤
|
||
if (step && STEP_MAP[step as ApiStep]) {
|
||
setCurrentStep(STEP_MAP[step as ApiStep]);
|
||
}
|
||
|
||
// 设置加载文本
|
||
setCurrentLoadingText(last_message || '');
|
||
|
||
// 设置是否需要流式数据
|
||
setNeedStreamData(status === 'running');
|
||
|
||
// 设置任务数据
|
||
if (data) {
|
||
setTaskData(data);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
} catch (error) {
|
||
console.error('获取剧集详情失败:', error);
|
||
}
|
||
return false;
|
||
}, [episodeId]);
|
||
|
||
// 初始化数据
|
||
useEffect(() => {
|
||
let titleInterval: NodeJS.Timeout;
|
||
|
||
const initData = async () => {
|
||
const detailSuccess = await fetchEpisodeDetail();
|
||
|
||
// 如果详情接口没有返回标题,开始轮询标题
|
||
if (detailSuccess && !title) {
|
||
titleInterval = setInterval(async () => {
|
||
const success = await pollTitle();
|
||
if (success) {
|
||
clearInterval(titleInterval);
|
||
}
|
||
}, 3000);
|
||
}
|
||
};
|
||
|
||
initData();
|
||
|
||
return () => {
|
||
if (titleInterval) {
|
||
clearInterval(titleInterval);
|
||
}
|
||
};
|
||
}, [episodeId, fetchEpisodeDetail, pollTitle, title]);
|
||
|
||
return {
|
||
title,
|
||
currentStep,
|
||
currentLoadingText,
|
||
needStreamData,
|
||
taskData
|
||
};
|
||
};
|