forked from 77media/video-flow
剪辑 最后一次修改
This commit is contained in:
parent
bfa4c69269
commit
237b8b4389
@ -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';
|
||||
|
||||
@ -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") || ""}
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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' }}>
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user