forked from 77media/video-flow
去除加载iframe然后统一走exportVideoWithRetry智能剪辑逻辑
This commit is contained in:
parent
e18f0d2018
commit
a09308e57c
@ -15,7 +15,7 @@ import { useSearchParams } from "next/navigation";
|
||||
import SmartChatBox from "@/components/SmartChatBox/SmartChatBox";
|
||||
import { Drawer, Tooltip, notification } from 'antd';
|
||||
import { showEditingNotification } from "@/components/pages/work-flow/editing-notification";
|
||||
import { AIEditingIframeButton } from './work-flow/ai-editing-iframe';
|
||||
// import { AIEditingIframeButton } from './work-flow/ai-editing-iframe';
|
||||
import { exportVideoWithRetry } from '@/utils/export-service';
|
||||
|
||||
const WorkFlow = React.memo(function WorkFlow() {
|
||||
@ -38,11 +38,20 @@ const WorkFlow = React.memo(function WorkFlow() {
|
||||
const [isFocusChatInput, setIsFocusChatInput] = React.useState(false);
|
||||
|
||||
const [aiEditingResult, setAiEditingResult] = React.useState<any>(null);
|
||||
const aiEditingButtonRef = useRef<{ handleAIEditing: () => Promise<void> }>(null);
|
||||
// const aiEditingButtonRef = useRef<{ handleAIEditing: () => Promise<void> }>(null);
|
||||
const [editingStatus, setEditingStatus] = React.useState<'initial' | 'idle' | 'success' | 'error'>('initial');
|
||||
const [iframeAiEditingKey, setIframeAiEditingKey] = React.useState<string>(`iframe-ai-editing-${Date.now()}`);
|
||||
// const [iframeAiEditingKey, setIframeAiEditingKey] = React.useState<string>(`iframe-ai-editing-${Date.now()}`);
|
||||
const [isEditingInProgress, setIsEditingInProgress] = React.useState(false);
|
||||
const isEditingInProgressRef = useRef(false);
|
||||
|
||||
// 导出进度状态
|
||||
const [exportProgress, setExportProgress] = React.useState<{
|
||||
status: 'processing' | 'completed' | 'failed';
|
||||
percentage: number;
|
||||
message: string;
|
||||
stage?: string;
|
||||
taskId?: string;
|
||||
} | null>(null);
|
||||
|
||||
const searchParams = useSearchParams();
|
||||
const episodeId = searchParams.get('episodeId') || '';
|
||||
@ -52,6 +61,48 @@ const WorkFlow = React.memo(function WorkFlow() {
|
||||
SaveEditUseCase.setProjectId(episodeId);
|
||||
let editingNotificationKey = useRef<string>(`editing-${Date.now()}`);
|
||||
const [isHandleEdit, setIsHandleEdit] = React.useState(false);
|
||||
|
||||
// 使用 ref 存储 handleTestExport 避免循环依赖
|
||||
const handleTestExportRef = useRef<(() => Promise<any>) | null>(null);
|
||||
|
||||
// 导出进度回调处理
|
||||
const handleExportProgress = useCallback((progressData: {
|
||||
status: 'processing' | 'completed' | 'failed';
|
||||
percentage: number;
|
||||
message: string;
|
||||
stage?: string;
|
||||
taskId?: string;
|
||||
}) => {
|
||||
console.log('📊 导出进度更新:', progressData);
|
||||
setExportProgress(progressData);
|
||||
|
||||
// 根据状态显示不同的通知
|
||||
if (progressData.status === 'processing') {
|
||||
notification.info({
|
||||
message: '导出进度',
|
||||
description: `${progressData.message} (${progressData.percentage}%)`,
|
||||
placement: 'topRight',
|
||||
duration: 2,
|
||||
key: 'export-progress'
|
||||
});
|
||||
} else if (progressData.status === 'completed') {
|
||||
notification.success({
|
||||
message: '导出成功',
|
||||
description: progressData.message,
|
||||
placement: 'topRight',
|
||||
duration: 5,
|
||||
key: 'export-progress'
|
||||
});
|
||||
} else if (progressData.status === 'failed') {
|
||||
notification.error({
|
||||
message: '导出失败',
|
||||
description: progressData.message,
|
||||
placement: 'topRight',
|
||||
duration: 8,
|
||||
key: 'export-progress'
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
// 处理编辑计划生成完成的回调
|
||||
const handleEditPlanGenerated = useCallback(() => {
|
||||
console.log('🚀 handleEditPlanGenerated called, current ref:', isEditingInProgressRef.current);
|
||||
@ -70,7 +121,10 @@ const WorkFlow = React.memo(function WorkFlow() {
|
||||
|
||||
// 改为调用测试剪辑计划导出按钮方法
|
||||
// aiEditingButtonRef.current?.handleAIEditing();
|
||||
handleTestExport();
|
||||
// 使用 ref 调用避免循环依赖
|
||||
setTimeout(() => {
|
||||
handleTestExportRef.current?.();
|
||||
}, 0);
|
||||
editingNotificationKey.current = `editing-${Date.now()}`;
|
||||
showEditingNotification({
|
||||
description: 'Performing intelligent editing...',
|
||||
@ -89,7 +143,7 @@ const WorkFlow = React.memo(function WorkFlow() {
|
||||
}
|
||||
|
||||
// 重新生成 iframeAiEditingKey 触发重新渲染
|
||||
setIframeAiEditingKey(`iframe-ai-editing-${Date.now()}`);
|
||||
// setIframeAiEditingKey(`iframe-ai-editing-${Date.now()}`);
|
||||
|
||||
// 延时200ms后显示重试通知,确保之前的通知已销毁
|
||||
setTimeout(() => {
|
||||
@ -118,7 +172,7 @@ const WorkFlow = React.memo(function WorkFlow() {
|
||||
}, 200);
|
||||
}
|
||||
});
|
||||
}, [episodeId]); // 移除 isEditingInProgress 依赖
|
||||
}, [episodeId]); // handleTestExport 在内部调用,无需作为依赖
|
||||
|
||||
/** 处理导出失败 */
|
||||
const handleExportFailed = useCallback(() => {
|
||||
@ -239,22 +293,28 @@ const WorkFlow = React.memo(function WorkFlow() {
|
||||
});
|
||||
|
||||
try {
|
||||
// 使用封装的导出服务
|
||||
const result = await exportVideoWithRetry(episodeId, taskObject);
|
||||
// 使用封装的导出服务,传递进度回调
|
||||
const result = await exportVideoWithRetry(episodeId, taskObject, handleExportProgress);
|
||||
console.log('🎉 导出服务完成,结果:', result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('❌ 导出服务失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}, [episodeId, taskObject]);
|
||||
}, [episodeId, taskObject, handleExportProgress]);
|
||||
|
||||
// 将 handleTestExport 赋值给 ref
|
||||
React.useEffect(() => {
|
||||
handleTestExportRef.current = handleTestExport;
|
||||
}, [handleTestExport]);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// iframe智能剪辑回调函数
|
||||
// iframe智能剪辑回调函数 - 已注释
|
||||
/*
|
||||
const handleIframeAIEditingComplete = useCallback((result: any) => {
|
||||
console.log('🎉 iframe AI剪辑完成,结果:', result);
|
||||
|
||||
@ -272,16 +332,21 @@ const WorkFlow = React.memo(function WorkFlow() {
|
||||
|
||||
// setAiEditingInProgress(false); // 已移除该状态变量
|
||||
}, [setAnyAttribute]);
|
||||
*/
|
||||
|
||||
/*
|
||||
const handleIframeAIEditingError = useCallback((error: string) => {
|
||||
console.error('❌ iframe AI剪辑失败:', error);
|
||||
// setAiEditingInProgress(false); // 已移除该状态变量
|
||||
}, []);
|
||||
*/
|
||||
|
||||
/*
|
||||
const handleIframeAIEditingProgress = useCallback((progress: number, message: string) => {
|
||||
console.log(`📊 AI剪辑进度: ${progress}% - ${message}`);
|
||||
// setAiEditingInProgress(true); // 已移除该状态变量
|
||||
}, []);
|
||||
*/
|
||||
|
||||
return (
|
||||
<div className="w-full overflow-hidden h-full px-[1rem] pb-[1rem]">
|
||||
@ -346,7 +411,8 @@ const WorkFlow = React.memo(function WorkFlow() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* AI剪辑按钮 - 当可以跳转剪辑时显示 */}
|
||||
{/* AI剪辑按钮 - 已注释,不加载iframe */}
|
||||
{/*
|
||||
{
|
||||
isShowAutoEditing && (
|
||||
<div className="fixed right-[2rem] top-[8rem] z-[49]">
|
||||
@ -367,6 +433,28 @@ const WorkFlow = React.memo(function WorkFlow() {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
*/}
|
||||
|
||||
{/* 导出进度显示 */}
|
||||
{exportProgress && exportProgress.status === 'processing' && (
|
||||
<div className="fixed right-[1rem] bottom-[20rem] z-[49]">
|
||||
<div className="backdrop-blur-lg bg-black/30 border border-white/20 rounded-lg p-4 max-w-xs">
|
||||
<div className="text-white text-sm mb-2">
|
||||
导出进度: {exportProgress.percentage}%
|
||||
</div>
|
||||
<div className="w-full bg-gray-700 rounded-full h-2 mb-2">
|
||||
<div
|
||||
className="bg-blue-500 h-2 rounded-full transition-all duration-300"
|
||||
style={{ width: `${exportProgress.percentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-gray-300 text-xs">
|
||||
{exportProgress.message}
|
||||
{exportProgress.stage && ` (${exportProgress.stage})`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 测试导出接口按钮 - 隐藏显示(仍可通过逻辑调用) */}
|
||||
<div
|
||||
|
||||
15
docs/导出进度.md
Normal file
15
docs/导出进度.md
Normal file
@ -0,0 +1,15 @@
|
||||
{task_id: "90a0d810-5bc7-4491-889d-b018df41903a", status: "processing",…}
|
||||
progress:
|
||||
{percentage: 10.665, message: "正在上传高清视频到云存储...", stage: "synthesis",…}
|
||||
message: "正在上传高清视频到云存储..."
|
||||
percentage: 10.665
|
||||
stage:
|
||||
"synthesis"
|
||||
timestamp:
|
||||
"2025-09-17T10:10:21.146770"
|
||||
project_id:
|
||||
"e9be9495-fe4b-41da-9d73-9b7179cd72a6"
|
||||
stage: "synthesis"
|
||||
status: "processing"
|
||||
task_id: "90a0d810-5bc7-4491-889d-b018df41903a"
|
||||
updated_at: "2025-09-17T10:10:21.146770"
|
||||
@ -71,6 +71,20 @@ interface ExportOptions {
|
||||
subtitleMode: 'hard' | 'soft';
|
||||
}
|
||||
|
||||
// 导出进度状态类型
|
||||
type ExportStatus = 'processing' | 'completed' | 'failed';
|
||||
|
||||
// 导出进度回调接口
|
||||
interface ExportProgressCallback {
|
||||
onProgress?: (data: {
|
||||
status: ExportStatus;
|
||||
percentage: number;
|
||||
message: string;
|
||||
stage?: string;
|
||||
taskId?: string;
|
||||
}) => void;
|
||||
}
|
||||
|
||||
// 导出服务配置
|
||||
interface ExportServiceConfig {
|
||||
maxRetries?: number;
|
||||
@ -436,7 +450,7 @@ export class VideoExportService {
|
||||
* - status: 'failed' 时抛出 EXPORT_FAILED 错误,触发重新调用 api/export/stream
|
||||
* - 其他状态继续轮询,最多轮询10分钟(5秒间隔)
|
||||
*/
|
||||
private async pollExportProgress(taskId: string): Promise<ExportResult> {
|
||||
private async pollExportProgress(taskId: string, progressCallback?: ExportProgressCallback['onProgress']): Promise<ExportResult> {
|
||||
console.log('🔄 开始轮询导出进度,任务ID:', taskId);
|
||||
const maxAttempts = 120; // 最多轮询10分钟(5秒间隔)
|
||||
let attempts = 0;
|
||||
@ -460,6 +474,18 @@ export class VideoExportService {
|
||||
|
||||
if (status === 'completed') {
|
||||
console.log('🎉 导出任务完成!', progress);
|
||||
|
||||
// 触发完成状态回调
|
||||
if (progressCallback) {
|
||||
progressCallback({
|
||||
status: 'completed',
|
||||
percentage: 100,
|
||||
message: progress?.message || '导出完成',
|
||||
stage: 'completed',
|
||||
taskId
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
task_id: taskId,
|
||||
status: status,
|
||||
@ -472,6 +498,18 @@ export class VideoExportService {
|
||||
};
|
||||
} else if (status === 'failed') {
|
||||
console.log('❌ 导出任务失败,需要重新调用 api/export/stream');
|
||||
|
||||
// 触发失败状态回调
|
||||
if (progressCallback) {
|
||||
progressCallback({
|
||||
status: 'failed',
|
||||
percentage: 0,
|
||||
message: progress?.message || '导出任务失败',
|
||||
stage: 'failed',
|
||||
taskId
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error(`EXPORT_FAILED: ${progress?.message || '导出任务失败'}`);
|
||||
} else if (status === 'error') {
|
||||
throw new Error(`导出任务错误: ${progress?.message || '未知错误'}`);
|
||||
@ -483,6 +521,17 @@ export class VideoExportService {
|
||||
|
||||
console.log(`⏳ 导出进度: ${percentage}% - ${stage} - ${message}`);
|
||||
|
||||
// 触发处理中状态回调
|
||||
if (progressCallback) {
|
||||
progressCallback({
|
||||
status: 'processing',
|
||||
percentage,
|
||||
message,
|
||||
stage,
|
||||
taskId
|
||||
});
|
||||
}
|
||||
|
||||
// 等待5秒后继续轮询
|
||||
await new Promise(resolve => setTimeout(resolve, this.config.pollInterval));
|
||||
attempts++;
|
||||
@ -504,7 +553,7 @@ export class VideoExportService {
|
||||
/**
|
||||
* 主要的导出方法 - 支持重试机制
|
||||
*/
|
||||
public async exportVideo(episodeId: string, taskObject: any): Promise<ExportResult> {
|
||||
public async exportVideo(episodeId: string, taskObject: any, progressCallback?: ExportProgressCallback['onProgress']): Promise<ExportResult> {
|
||||
let currentAttempt = 1;
|
||||
|
||||
try {
|
||||
@ -581,7 +630,7 @@ export class VideoExportService {
|
||||
console.log('🔄 开始轮询导出进度,任务ID:', taskId);
|
||||
|
||||
try {
|
||||
const finalExportResult = await this.pollExportProgress(taskId);
|
||||
const finalExportResult = await this.pollExportProgress(taskId, progressCallback);
|
||||
|
||||
// 导出成功
|
||||
console.log('🎉 导出成功完成!');
|
||||
@ -727,8 +776,12 @@ export const videoExportService = new VideoExportService({
|
||||
/**
|
||||
* 便捷的导出函数
|
||||
*/
|
||||
export async function exportVideoWithRetry(episodeId: string, taskObject: any): Promise<ExportResult> {
|
||||
return videoExportService.exportVideo(episodeId, taskObject);
|
||||
export async function exportVideoWithRetry(
|
||||
episodeId: string,
|
||||
taskObject: any,
|
||||
progressCallback?: ExportProgressCallback['onProgress']
|
||||
): Promise<ExportResult> {
|
||||
return videoExportService.exportVideo(episodeId, taskObject, progressCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user