剪辑 最后一次修改

This commit is contained in:
北枳 2025-09-11 20:04:04 +08:00
parent bfa4c69269
commit 237b8b4389
5 changed files with 125 additions and 32 deletions

View File

@ -670,8 +670,8 @@ export const LOADING_TEXT_MAP = {
postProduction: (step: string) => `Post-production: ${step}...`,
final: 'Generating final product...',
complete: 'Task completed',
toManyFailed: 'Too many failed storyboards, Please click the edit button to go to the intelligent editing platform.',
editingError: 'Editing failed. Please refresh the page and try again.'
toManyFailed: 'Too many failed storyboards, Please click the scissors button to go to the intelligent editing platform.',
editingError: 'Editing failed. Please click the scissors button to go to the intelligent editing platform.'
} as const;
export type Status = 'IN_PROGRESS' | 'COMPLETED' | 'FAILED';

View File

@ -41,6 +41,9 @@ const WorkFlow = React.memo(function WorkFlow() {
const [aiEditingResult, setAiEditingResult] = React.useState<any>(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 [isEditingInProgress, setIsEditingInProgress] = React.useState(false);
const isEditingInProgressRef = useRef(false);
const searchParams = useSearchParams();
const episodeId = searchParams.get('episodeId') || '';
@ -52,30 +55,81 @@ const WorkFlow = React.memo(function WorkFlow() {
const [isHandleEdit, setIsHandleEdit] = React.useState(false);
// 处理编辑计划生成完成的回调
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();
editingNotificationKey.current = `editing-${Date.now()}`;
showEditingNotification({
description: 'Performing intelligent editing...',
successDescription: 'Editing successful',
timeoutDescription: 'Editing failed. Please click the edit button to go to the smart editing platform.',
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('Editing failed');
console.log('❌ onFail callback triggered - Editing failed, retrying...');
// 清缓存 生成计划 视频重新分析
localStorage.removeItem(`isLoaded_plan_${episodeId}`);
// 3秒后关闭通知
setTimeout(() => {
setEditingStatus('error');
// 先销毁当前通知
if (editingNotificationKey.current) {
notification.destroy(editingNotificationKey.current);
}
}, 3000);
// 重新生成 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]); // 移除 isEditingInProgress 依赖
/** 处理导出失败 */
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 管理状态
@ -99,7 +153,8 @@ const WorkFlow = React.memo(function WorkFlow() {
isShowAutoEditing
} = useWorkflowData({
onEditPlanGenerated: handleEditPlanGenerated,
editingStatus: editingStatus
editingStatus: editingStatus,
onExportFailed: handleExportFailed
});
const {
@ -113,7 +168,14 @@ const WorkFlow = React.memo(function WorkFlow() {
// 监听粗剪是否完成,如果完成 更新 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,
@ -126,6 +188,8 @@ const WorkFlow = React.memo(function WorkFlow() {
console.log('Editing successful');
localStorage.setItem(`isLoaded_plan_${episodeId}`, 'true');
setEditingStatus('success');
setIsEditingInProgress(false); // 重置编辑状态
isEditingInProgressRef.current = false; // 重置 ref
// 3秒后关闭通知
setTimeout(() => {
if (editingNotificationKey.current) {
@ -135,7 +199,7 @@ const WorkFlow = React.memo(function WorkFlow() {
},
});
}
}, [taskObject.final, isHandleEdit]);
}, [taskObject.final, isHandleEdit, episodeId]);
const handleEditModalOpen = useCallback((tab: string) => {
setActiveEditTab(tab);
@ -266,6 +330,7 @@ const WorkFlow = React.memo(function WorkFlow() {
<div className="fixed right-[2rem] top-[8rem] z-[49]">
<Tooltip title="AI智能剪辑" placement="left">
<AIEditingIframeButton
key={iframeAiEditingKey}
ref={aiEditingButtonRef}
projectId={episodeId}
token={localStorage.getItem("token") || ""}

View File

@ -38,6 +38,8 @@ interface AIEditingIframeHandle {
}
interface AIEditingIframeProps {
/** 组件key */
key: string;
/** 项目ID */
projectId: string;
/** 认证token */
@ -70,6 +72,7 @@ interface ProgressState {
*/
export const AIEditingIframe = React.forwardRef<AIEditingIframeHandle, AIEditingIframeProps>((props, ref) => {
const {
key,
projectId,
token,
userId,
@ -418,6 +421,7 @@ export const AIEditingIframe = React.forwardRef<AIEditingIframeHandle, AIEditing
{/* 隐藏的iframe - 在后台运行 */}
<div className="fixed -top-[9999px] -left-[9999px] w-1 h-1 opacity-0 pointer-events-none">
<iframe
key={key}
ref={iframeRef}
src={aiEditingUrl}
className="w-full h-full border-0"

View File

@ -63,6 +63,8 @@ interface EditingNotificationProps {
* @param props EditingNotificationProps
*/
export const showEditingNotification = (props: EditingNotificationProps) => {
console.log('🔔 showEditingNotification called:', { key: props.key, description: props.description, isCompleted: props.isCompleted });
const {
isCompleted = false,
description = 'The intelligent editing platform is currently editing the videos.',
@ -89,7 +91,7 @@ export const showEditingNotification = (props: EditingNotificationProps) => {
const newValue = typeof value === 'function' ? value(progress) : value;
setProgressInternal(newValue);
onGetProgress?.(newValue);
}, [progress]); // 添加 progress 作为依赖
}, [progress, onGetProgress]); // 恢复原来的依赖
// 监听外部设置进度值
useEffect(() => {
@ -147,20 +149,23 @@ export const showEditingNotification = (props: EditingNotificationProps) => {
return next;
});
} else if (elapsed >= timeLimit) {
// 超时失败但继续更新进度
// 超时失败,只调用一次 onFail
if (status !== 'exception') {
console.log('⏰ Timeout reached, calling onFail');
setStatus('exception');
setCurrentDescription(timeoutDescription);
onFail?.();
// 不清除定时器,继续更新进度
}
// 继续更新进度但不再重复调用 onFail
setProgress((prev: number) => {
const next = Math.min(prev + 0.2, 90); // 失败后继续缓慢增加但限制在90%以内
return next;
});
} else {
// 正常进度,缓慢增加到90%
// 增加到90%
setProgress((prev: number) => {
const targetProgress = (elapsed / timeLimit) * 90;
const next = Math.min(prev + 0.5, targetProgress);
const next = Math.min(prev + 2, targetProgress);
return next;
});
}
@ -168,7 +173,7 @@ export const showEditingNotification = (props: EditingNotificationProps) => {
timerRef.current = setInterval(updateProgress, 100);
return () => clearInterval(timerRef.current);
}, [isCompleted, setProgress, successDescription, timeoutDescription, timeout]);
}, [isCompleted, setProgress, successDescription, timeoutDescription, timeout]); // 恢复原来的依赖
return (
<div data-alt="editing-notification" style={{ minWidth: '300px' }}>

View File

@ -12,9 +12,10 @@ import { LOADING_TEXT_MAP, TaskObject, Status, Stage } from '@/api/DTO/movieEdit
interface UseWorkflowDataProps {
onEditPlanGenerated?: () => void;
editingStatus?: string;
onExportFailed?: () => void;
}
export function useWorkflowData({ onEditPlanGenerated, editingStatus }: UseWorkflowDataProps = {}) {
export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFailed }: UseWorkflowDataProps = {}) {
const searchParams = useSearchParams();
const episodeId = searchParams.get('episodeId') || '';
const from = searchParams.get('from') || '';
@ -174,17 +175,11 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus }: UseWorkf
isLoadedRef.current = 'true';
setNeedStreamData(true);
// 显示成功通知3秒
showEditingNotification({
key: notificationKey,
isCompleted: true,
description: `Generating intelligent editing plan... ${retryCount ? 'Retry Time: ' + retryCount : ''}`,
successDescription: 'Editing plan generated successfully.',
timeout: 3000
});
// 销毁生成计划的通知
notification.destroy(notificationKey);
// 触发回调,通知父组件计划生成完成
console.log('📞 calling onEditPlanGenerated callback');
onEditPlanGenerated?.();
setIsLoadingGenerateEditPlan(false);
} catch (error) {
@ -232,7 +227,7 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus }: UseWorkf
setNeedStreamData(false);
}
if (editingStatus === 'error') {
window.msg.error('Editing failed, Please click the edit button to go to the intelligent editing platform.', 8000);
window.msg.error('Editing failed, Please click the scissors button to go to the intelligent editing platform.', 8000);
setCurrentLoadingText(LOADING_TEXT_MAP.editingError);
// 停止轮询
setNeedStreamData(false);
@ -454,6 +449,30 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus }: UseWorkf
}
}
// generate_export_video
if (task.task_name === 'generate_export_video') {
if (task.task_status === 'COMPLETED') {
taskCurrent.currentStage = 'final_video';
taskCurrent.final.url = task.task_result.video;
taskCurrent.final.note = 'export';
taskCurrent.status = 'COMPLETED';
// 停止轮询
setNeedStreamData(false);
}
if (task.task_status === 'FAILED' || task.task_status === 'ERROR') {
console.log('----------视频导出失败');
taskCurrent.status = 'FAILED';
// 触发导出失败回调
if (onExportFailed) {
onExportFailed();
}
// 停止轮询
setNeedStreamData(false);
}
}
// 最终剪辑
if (task.task_name === 'generate_final_video') {
if (task.task_result && task.task_result.video) {
@ -485,7 +504,7 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus }: UseWorkf
} catch (error) {
console.error('获取数据失败:', error);
}
}, [episodeId, needStreamData]);
}, [episodeId, needStreamData, notificationKey, onExportFailed]);
// 轮询获取流式数据
useUpdateEffect(() => {