"use client" import React, { useRef, useEffect, useCallback } from "react"; import "./style/work-flow.css"; import { EditModal } from "@/components/ui/edit-modal"; import { TaskInfo } from "./work-flow/task-info"; import { MediaViewer } from "./work-flow/media-viewer"; import { ThumbnailGrid } from "./work-flow/thumbnail-grid"; import { useWorkflowData } from "./work-flow/use-workflow-data"; import { usePlaybackControls } from "./work-flow/use-playback-controls"; import { Bot, TestTube } from "lucide-react"; import { GlassIconButton } from '@/components/ui/glass-icon-button'; import { SaveEditUseCase } from "@/app/service/usecase/SaveEditUseCase"; 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 { exportVideoWithRetry } from '@/utils/export-service'; const WorkFlow = React.memo(function WorkFlow() { useEffect(() => { console.log("init-WorkFlow"); return () => { console.log("unmount-WorkFlow"); // 销毁编辑通知 if (editingNotificationKey.current) { notification.destroy(editingNotificationKey.current); } }; }, []); const containerRef = useRef(null); const [isEditModalOpen, setIsEditModalOpen] = React.useState(false); const [activeEditTab, setActiveEditTab] = React.useState('1'); const [isSmartChatBoxOpen, setIsSmartChatBoxOpen] = React.useState(true); const [previewVideoUrl, setPreviewVideoUrl] = React.useState(null); const [previewVideoId, setPreviewVideoId] = React.useState(null); const [isFocusChatInput, setIsFocusChatInput] = React.useState(false); const [aiEditingResult, setAiEditingResult] = React.useState(null); // const aiEditingButtonRef = useRef<{ handleAIEditing: () => Promise }>(null); const [editingStatus, setEditingStatus] = React.useState<'initial' | 'idle' | 'success' | 'error'>('initial'); // const [iframeAiEditingKey, setIframeAiEditingKey] = React.useState(`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') || ''; const userId = JSON.parse(localStorage.getItem("currentUser") || '{}').id || NaN; SaveEditUseCase.setProjectId(episodeId); let editingNotificationKey = useRef(`editing-${Date.now()}`); const [isHandleEdit, setIsHandleEdit] = React.useState(false); // 使用 ref 存储 handleTestExport 避免循环依赖 const handleTestExportRef = useRef<(() => Promise) | 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); // 防止重复调用 - 使用 ref 避免依赖项变化 if (isEditingInProgressRef.current) { console.log('⚠️ 编辑已在进行中,跳过重复调用'); return; } console.log('✨ 编辑计划生成完成,开始AI剪辑'); setIsHandleEdit(true); setEditingStatus('idle'); // setIsEditingInProgress(true); // 已移除该状态变量 isEditingInProgressRef.current = true; // 改为调用测试剪辑计划导出按钮方法 // aiEditingButtonRef.current?.handleAIEditing(); // 使用 ref 调用避免循环依赖 setTimeout(() => { handleTestExportRef.current?.(); }, 0); editingNotificationKey.current = `editing-${Date.now()}`; showEditingNotification({ description: 'Performing intelligent editing...', successDescription: 'Editing successful', timeoutDescription: 'Editing failed. Please click the scissors button to go to the intelligent editing platform.', timeout: 8 * 60 * 1000, key: editingNotificationKey.current, onFail: () => { console.log('❌ onFail callback triggered - Editing failed, retrying...'); // 清缓存 生成计划 视频重新分析 localStorage.removeItem(`isLoaded_plan_${episodeId}`); // 先销毁当前通知 if (editingNotificationKey.current) { notification.destroy(editingNotificationKey.current); } // 重新生成 iframeAiEditingKey 触发重新渲染 // setIframeAiEditingKey(`iframe-ai-editing-${Date.now()}`); // 延时200ms后显示重试通知,确保之前的通知已销毁 setTimeout(() => { editingNotificationKey.current = `editing-${Date.now()}`; showEditingNotification({ description: 'Retry intelligent editing...', successDescription: 'Editing successful', timeoutDescription: 'Editing failed. Please click the scissors button to go to the intelligent editing platform.', timeout: 5 * 60 * 1000, // 5分钟超时 key: editingNotificationKey.current, onFail: () => { console.log('Editing retry failed'); // 清缓存 localStorage.removeItem(`isLoaded_plan_${episodeId}`); // 5秒后关闭通知并设置错误状态 setTimeout(() => { setEditingStatus('error'); setIsEditingInProgress(false); // 重置编辑状态 isEditingInProgressRef.current = false; // 重置 ref if (editingNotificationKey.current) { notification.destroy(editingNotificationKey.current); } }, 5000); } }); }, 200); } }); }, [episodeId]); // handleTestExport 在内部调用,无需作为依赖 /** 处理导出失败 */ const handleExportFailed = useCallback(() => { console.log('Export failed, setting error status'); setEditingStatus('error'); // setIsEditingInProgress(false); // 已移除该状态变量 isEditingInProgressRef.current = false; // 销毁当前编辑通知 if (editingNotificationKey.current) { notification.destroy(editingNotificationKey.current); } }, []); // 使用自定义 hooks 管理状态 const { taskObject, scriptData, isLoading, currentSketchIndex, currentLoadingText, setCurrentSketchIndex, isPauseWorkFlow, mode, setIsPauseWorkFlow, setAnyAttribute, applyScript, fallbackToStep, originalText, showGotoCutButton, generateEditPlan, handleRetryVideo, isShowAutoEditing } = useWorkflowData({ onEditPlanGenerated: handleEditPlanGenerated, editingStatus: editingStatus, onExportFailed: handleExportFailed }); const { isVideoPlaying, toggleVideoPlay, } = usePlaybackControls(taskObject.videos.data, taskObject.currentStage); useEffect(() => { console.log('changedIndex_work-flow', currentSketchIndex, taskObject); }, [currentSketchIndex, taskObject]); // 监听粗剪是否完成,如果完成 更新 showEditingNotification 的状态 为完成,延时 3s 并关闭 useEffect(() => { console.log('🎬 final video useEffect triggered:', { finalUrl: taskObject.final.url, notificationKey: editingNotificationKey.current, isHandleEdit }); if (taskObject.final.url && editingNotificationKey.current && isHandleEdit) { console.log('🎉 显示编辑完成通知'); // 更新通知状态为完成 showEditingNotification({ isCompleted: true, description: 'Performing intelligent editing...', successDescription: 'Editing successful', timeoutDescription: 'Editing failed, please try again', timeout: 5 * 60 * 1000, key: editingNotificationKey.current, onComplete: () => { console.log('Editing successful'); localStorage.setItem(`isLoaded_plan_${episodeId}`, 'true'); setEditingStatus('success'); setIsEditingInProgress(false); // 重置编辑状态 isEditingInProgressRef.current = false; // 重置 ref // 3秒后关闭通知 setTimeout(() => { if (editingNotificationKey.current) { notification.destroy(editingNotificationKey.current); } }, 3000); }, }); } }, [taskObject.final, isHandleEdit, episodeId]); const handleEditModalOpen = useCallback((tab: string) => { setActiveEditTab(tab); setIsEditModalOpen(true); }, []); // AI剪辑回调函数 const handleAIEditingComplete = useCallback((finalVideoUrl: string) => { console.log('🎉 AI剪辑完成,最终视频URL:', finalVideoUrl); // 更新任务对象的最终视频状态 setAnyAttribute('final', { url: finalVideoUrl, note: 'ai_edited' }); // 切换到最终视频阶段 setAnyAttribute('currentStage', 'final_video'); // setAiEditingInProgress(false); // 已移除该状态变量 }, [setAnyAttribute]); const handleAIEditingError = useCallback((error: string) => { console.error('❌ AI剪辑失败:', error); // 这里可以显示错误提示 // setAiEditingInProgress(false); // 已移除该状态变量 }, []); // 测试导出接口的处理函数(使用封装的导出服务) const handleTestExport = useCallback(async () => { console.log('🧪 开始测试导出接口...'); console.log('📊 当前taskObject状态:', { currentStage: taskObject.currentStage, videosCount: taskObject.videos?.data?.length || 0, completedVideos: taskObject.videos?.data?.filter(v => v.video_status === 1).length || 0 }); try { // 使用封装的导出服务,传递进度回调 const result = await exportVideoWithRetry(episodeId, taskObject, handleExportProgress); console.log('🎉 导出服务完成,结果:', result); return result; } catch (error) { console.error('❌ 导出服务失败:', error); throw error; } }, [episodeId, taskObject, handleExportProgress]); // 将 handleTestExport 赋值给 ref React.useEffect(() => { handleTestExportRef.current = handleTestExport; }, [handleTestExport]); // iframe智能剪辑回调函数 - 已注释 /* const handleIframeAIEditingComplete = useCallback((result: any) => { console.log('🎉 iframe AI剪辑完成,结果:', result); // 保存剪辑结果 setAiEditingResult(result); // 更新任务对象的最终视频状态 setAnyAttribute('final', { url: result.videoUrl, note: 'ai_edited_iframe' }); // 切换到最终视频阶段 setAnyAttribute('currentStage', 'final_video'); // 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 (
setIsSmartChatBoxOpen(true)} setVideoPreview={(url, id) => { setPreviewVideoUrl(url); setPreviewVideoId(id); }} showGotoCutButton={showGotoCutButton || editingStatus !== 'idle'} onGotoCut={generateEditPlan} isSmartChatBoxOpen={isSmartChatBoxOpen} onRetryVideo={(video_id) => handleRetryVideo(video_id)} />
{taskObject.currentStage !== 'script' && (
)}
{/* AI剪辑按钮 - 已注释,不加载iframe */} {/* { isShowAutoEditing && (
) } */} {/* 导出进度显示 */} {exportProgress && exportProgress.status === 'processing' && (
导出进度: {exportProgress.percentage}%
{exportProgress.message} {exportProgress.stage && ` (${exportProgress.stage})`}
)} {/* 测试导出接口按钮 - 隐藏显示(仍可通过逻辑调用) */}
{/* 智能对话按钮 */}
setIsSmartChatBoxOpen(true)} className="backdrop-blur-lg" />
{/* 智能对话弹窗 */} setIsSmartChatBoxOpen(false)} > { setPreviewVideoUrl(null); setPreviewVideoId(null); }} aiEditingResult={aiEditingResult} /> { SaveEditUseCase.clearData(); setIsEditModalOpen(false) }} taskObject={taskObject} currentSketchIndex={currentSketchIndex} roles={taskObject.roles.data} setIsPauseWorkFlow={setIsPauseWorkFlow} isPauseWorkFlow={isPauseWorkFlow} fallbackToStep={fallbackToStep} originalText={originalText} />
) }); export default WorkFlow;