去除加载iframe然后统一走exportVideoWithRetry智能剪辑逻辑

This commit is contained in:
qikongjian 2025-09-17 11:00:15 +08:00
parent e18f0d2018
commit a09308e57c
3 changed files with 172 additions and 16 deletions

View File

@ -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
View 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"

View File

@ -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
* - 105
*/
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);
}
/**