forked from 77media/video-flow
提示进度
This commit is contained in:
parent
bac656d6f7
commit
7b7a93832d
@ -14,7 +14,7 @@ const darkGlassStyle = {
|
|||||||
const studioContainerStyle = {
|
const studioContainerStyle = {
|
||||||
position: 'relative' as const,
|
position: 'relative' as const,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '120px',
|
height: '100px',
|
||||||
marginBottom: '16px',
|
marginBottom: '16px',
|
||||||
background: 'rgba(26, 27, 30, 0.6)',
|
background: 'rgba(26, 27, 30, 0.6)',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
|
|||||||
@ -56,9 +56,13 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
aiEditingButtonRef.current?.handleAIEditing();
|
aiEditingButtonRef.current?.handleAIEditing();
|
||||||
editingNotificationKey.current = `editing-${Date.now()}`;
|
editingNotificationKey.current = `editing-${Date.now()}`;
|
||||||
showEditingNotification({
|
showEditingNotification({
|
||||||
|
description: 'Performing intelligent editing...',
|
||||||
|
successDescription: 'Editing successful',
|
||||||
|
timeoutDescription: 'Editing failed, please try again',
|
||||||
|
timeout: 5 * 60 * 1000,
|
||||||
key: editingNotificationKey.current,
|
key: editingNotificationKey.current,
|
||||||
onFail: () => {
|
onFail: () => {
|
||||||
console.log('编辑失败');
|
console.log('Editing failed');
|
||||||
// 清缓存 生成计划 视频重新分析
|
// 清缓存 生成计划 视频重新分析
|
||||||
localStorage.removeItem(`isLoaded_plan_${episodeId}`);
|
localStorage.removeItem(`isLoaded_plan_${episodeId}`);
|
||||||
// 3秒后关闭通知
|
// 3秒后关闭通知
|
||||||
@ -109,7 +113,10 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
if (taskObject.final.url && editingNotificationKey.current && isHandleEdit) {
|
if (taskObject.final.url && editingNotificationKey.current && isHandleEdit) {
|
||||||
// 更新通知状态为完成
|
// 更新通知状态为完成
|
||||||
showEditingNotification({
|
showEditingNotification({
|
||||||
isCompleted: true,
|
description: 'Performing intelligent editing...',
|
||||||
|
successDescription: 'Editing successful',
|
||||||
|
timeoutDescription: 'Editing failed, please try again',
|
||||||
|
timeout: 5 * 60 * 1000,
|
||||||
key: editingNotificationKey.current,
|
key: editingNotificationKey.current,
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
console.log('编辑完成');
|
console.log('编辑完成');
|
||||||
|
|||||||
@ -36,14 +36,22 @@ const descriptionStyle = {
|
|||||||
interface EditingNotificationProps {
|
interface EditingNotificationProps {
|
||||||
/** 编辑是否完成 */
|
/** 编辑是否完成 */
|
||||||
isCompleted?: boolean;
|
isCompleted?: boolean;
|
||||||
/** 自定义描述 */
|
/** 初始描述文案 */
|
||||||
description?: string;
|
description?: string;
|
||||||
|
/** 编辑成功时的描述文案 */
|
||||||
|
successDescription?: string;
|
||||||
|
/** 编辑超时失败时的描述文案 */
|
||||||
|
timeoutDescription?: string;
|
||||||
/** key */
|
/** key */
|
||||||
key?: string;
|
key?: string;
|
||||||
/** 完成时的回调 */
|
/** 完成时的回调 */
|
||||||
onComplete?: () => void;
|
onComplete?: () => void;
|
||||||
/** 失败时的回调 */
|
/** 失败时的回调 */
|
||||||
onFail?: () => void;
|
onFail?: () => void;
|
||||||
|
/** 超时时间(毫秒),默认10分钟 */
|
||||||
|
timeout?: number;
|
||||||
|
/** 是否显示关闭按钮,默认false */
|
||||||
|
showCloseIcon?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -54,9 +62,13 @@ export const showEditingNotification = (props: EditingNotificationProps) => {
|
|||||||
const {
|
const {
|
||||||
isCompleted = false,
|
isCompleted = false,
|
||||||
description = 'The intelligent editing platform is currently editing the videos.',
|
description = 'The intelligent editing platform is currently editing the videos.',
|
||||||
|
successDescription = 'The editing is complete, and the updated video has been displayed on the page.',
|
||||||
|
timeoutDescription = 'The editing timed out, please try again.',
|
||||||
key,
|
key,
|
||||||
onComplete,
|
onComplete,
|
||||||
onFail,
|
onFail,
|
||||||
|
timeout = 8 * 60 * 1000, // 默认8分钟
|
||||||
|
showCloseIcon = false,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const NotificationContent = () => {
|
const NotificationContent = () => {
|
||||||
@ -66,6 +78,19 @@ export const showEditingNotification = (props: EditingNotificationProps) => {
|
|||||||
const timerRef = useRef<NodeJS.Timeout>();
|
const timerRef = useRef<NodeJS.Timeout>();
|
||||||
const startTimeRef = useRef(Date.now());
|
const startTimeRef = useRef(Date.now());
|
||||||
|
|
||||||
|
// 重置进度条
|
||||||
|
const resetProgress = () => {
|
||||||
|
setProgress(0);
|
||||||
|
setStatus('active');
|
||||||
|
setCurrentDescription(description);
|
||||||
|
startTimeRef.current = Date.now();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 将重置方法暴露给外部
|
||||||
|
if (props.key && typeof window !== 'undefined') {
|
||||||
|
(window as any)[`resetProgress_${props.key}`] = resetProgress;
|
||||||
|
}
|
||||||
|
|
||||||
const scissorsIcon = useMemo(() => (
|
const scissorsIcon = useMemo(() => (
|
||||||
<motion.div
|
<motion.div
|
||||||
style={{ display: 'inline-flex', marginRight: '8px' }}
|
style={{ display: 'inline-flex', marginRight: '8px' }}
|
||||||
@ -86,7 +111,7 @@ export const showEditingNotification = (props: EditingNotificationProps) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const updateProgress = () => {
|
const updateProgress = () => {
|
||||||
const elapsed = Date.now() - startTimeRef.current;
|
const elapsed = Date.now() - startTimeRef.current;
|
||||||
const timeLimit = 10 * 60 * 1000; // 10分钟
|
const timeLimit = timeout; // 使用传入的超时时间
|
||||||
|
|
||||||
if (isCompleted) {
|
if (isCompleted) {
|
||||||
// 如果完成了,快速增加到100%
|
// 如果完成了,快速增加到100%
|
||||||
@ -94,7 +119,7 @@ export const showEditingNotification = (props: EditingNotificationProps) => {
|
|||||||
const next = prev + (100 - prev) / 10;
|
const next = prev + (100 - prev) / 10;
|
||||||
if (next >= 99.9) {
|
if (next >= 99.9) {
|
||||||
setStatus('success');
|
setStatus('success');
|
||||||
setCurrentDescription('The editing is complete, and the updated video has been displayed on the page.');
|
setCurrentDescription(successDescription);
|
||||||
onComplete?.();
|
onComplete?.();
|
||||||
clearInterval(timerRef.current);
|
clearInterval(timerRef.current);
|
||||||
return 100;
|
return 100;
|
||||||
@ -104,7 +129,7 @@ export const showEditingNotification = (props: EditingNotificationProps) => {
|
|||||||
} else if (elapsed >= timeLimit) {
|
} else if (elapsed >= timeLimit) {
|
||||||
// 超时失败
|
// 超时失败
|
||||||
setStatus('exception');
|
setStatus('exception');
|
||||||
setCurrentDescription('The editing timed out, please try again.');
|
setCurrentDescription(timeoutDescription);
|
||||||
onFail?.();
|
onFail?.();
|
||||||
clearInterval(timerRef.current);
|
clearInterval(timerRef.current);
|
||||||
return;
|
return;
|
||||||
@ -166,7 +191,7 @@ export const showEditingNotification = (props: EditingNotificationProps) => {
|
|||||||
placement: 'topRight',
|
placement: 'topRight',
|
||||||
style: darkGlassStyle,
|
style: darkGlassStyle,
|
||||||
className: 'dark-glass-notification',
|
className: 'dark-glass-notification',
|
||||||
closeIcon: null
|
closeIcon: showCloseIcon ? undefined : null
|
||||||
});
|
});
|
||||||
|
|
||||||
// 返回key以便外部可以手动关闭通知
|
// 返回key以便外部可以手动关闭通知
|
||||||
|
|||||||
@ -582,9 +582,8 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
)}
|
)}
|
||||||
{currentSketch.status === 2 && (
|
{currentSketch.status === 2 && (
|
||||||
<div className="absolute inset-0 bg-red-500/10 flex items-center justify-center">
|
<div className="absolute inset-0 bg-red-500/10 flex items-center justify-center">
|
||||||
<div className="text-red-500 text-2xl font-bold flex items-center gap-2">
|
<div className="text-[#813b9dcc] text-2xl font-bold flex items-center gap-2">
|
||||||
<X className="w-10 h-10" />
|
<X className="w-10 h-10" />
|
||||||
<span>Failed</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
|
import { notification } from 'antd';
|
||||||
|
import { showEditingNotification } from '@/components/pages/work-flow/editing-notification';
|
||||||
import { detailScriptEpisodeNew, getScriptTitle, getRunningStreamData, pauseMovieProjectPlan, resumeMovieProjectPlan, getGenerateEditPlan, regenerateVideoNew } from '@/api/video_flow';
|
import { detailScriptEpisodeNew, getScriptTitle, getRunningStreamData, pauseMovieProjectPlan, resumeMovieProjectPlan, getGenerateEditPlan, regenerateVideoNew } from '@/api/video_flow';
|
||||||
import { useScriptService } from "@/app/service/Interaction/ScriptService";
|
import { useScriptService } from "@/app/service/Interaction/ScriptService";
|
||||||
import { useUpdateEffect } from '@/app/hooks/useUpdateEffect';
|
import { useUpdateEffect } from '@/app/hooks/useUpdateEffect';
|
||||||
@ -13,16 +15,25 @@ interface UseWorkflowDataProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useWorkflowData({ onEditPlanGenerated }: UseWorkflowDataProps = {}) {
|
export function useWorkflowData({ onEditPlanGenerated }: UseWorkflowDataProps = {}) {
|
||||||
useEffect(() => {
|
|
||||||
console.log("init-useWorkflowData");
|
|
||||||
return () => console.log("unmount-useWorkflowData");
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const episodeId = searchParams.get('episodeId') || '';
|
const episodeId = searchParams.get('episodeId') || '';
|
||||||
const from = searchParams.get('from') || '';
|
const from = searchParams.get('from') || '';
|
||||||
const token = localStorage.getItem('token') || '';
|
const token = localStorage.getItem('token') || '';
|
||||||
const useid = JSON.parse(localStorage.getItem("currentUser") || '{}').id || NaN;
|
const useid = JSON.parse(localStorage.getItem("currentUser") || '{}').id || NaN;
|
||||||
|
const notificationKey = useMemo(() => `video-workflow-${episodeId}`, [episodeId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log("init-useWorkflowData");
|
||||||
|
return () => {
|
||||||
|
console.log("unmount-useWorkflowData");
|
||||||
|
// 组件卸载时销毁通知
|
||||||
|
notification.destroy(notificationKey);
|
||||||
|
// 清理window上的重置函数
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
delete (window as any)[`resetProgress_${notificationKey}`];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [notificationKey]);
|
||||||
// 查看缓存中 是否已经 加载过 这个项目的 剪辑计划
|
// 查看缓存中 是否已经 加载过 这个项目的 剪辑计划
|
||||||
let isLoadedRef = useRef<string | null>(localStorage.getItem(`isLoaded_plan_${episodeId}`));
|
let isLoadedRef = useRef<string | null>(localStorage.getItem(`isLoaded_plan_${episodeId}`));
|
||||||
|
|
||||||
@ -61,6 +72,7 @@ export function useWorkflowData({ onEditPlanGenerated }: UseWorkflowDataProps =
|
|||||||
const [isPauseWorkFlow, setIsPauseWorkFlow] = useState(false);
|
const [isPauseWorkFlow, setIsPauseWorkFlow] = useState(false);
|
||||||
const [canGoToCut, setCanGoToCut] = useState(false);
|
const [canGoToCut, setCanGoToCut] = useState(false);
|
||||||
const [isShowError, setIsShowError] = useState(false);
|
const [isShowError, setIsShowError] = useState(false);
|
||||||
|
const [isAnalyzing, setIsAnalyzing] = useState(false);
|
||||||
const [isGenerateEditPlan, setIsGenerateEditPlan] = useState(false);
|
const [isGenerateEditPlan, setIsGenerateEditPlan] = useState(false);
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
mode: 'automatic' as 'automatic' | 'manual' | 'auto',
|
mode: 'automatic' as 'automatic' | 'manual' | 'auto',
|
||||||
@ -127,6 +139,19 @@ export function useWorkflowData({ onEditPlanGenerated }: UseWorkflowDataProps =
|
|||||||
if (isLoadedRef.current) {
|
if (isLoadedRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 调用重置方法
|
||||||
|
const resetFunc = (window as any)[`resetProgress_${notificationKey}`];
|
||||||
|
if (resetFunc) {
|
||||||
|
resetFunc();
|
||||||
|
}
|
||||||
|
// 更新通知内容
|
||||||
|
showEditingNotification({
|
||||||
|
key: notificationKey,
|
||||||
|
description: 'Generating intelligent editing plan...',
|
||||||
|
successDescription: 'Generating successful',
|
||||||
|
timeoutDescription: 'Generating failed, please try again',
|
||||||
|
timeout: 3 * 60 * 1000
|
||||||
|
});
|
||||||
// 先停止轮询
|
// 先停止轮询
|
||||||
await new Promise(resolve => {
|
await new Promise(resolve => {
|
||||||
setNeedStreamData(false);
|
setNeedStreamData(false);
|
||||||
@ -138,14 +163,38 @@ export function useWorkflowData({ onEditPlanGenerated }: UseWorkflowDataProps =
|
|||||||
setIsGenerateEditPlan(true);
|
setIsGenerateEditPlan(true);
|
||||||
isLoadedRef.current = 'true';
|
isLoadedRef.current = 'true';
|
||||||
setNeedStreamData(true);
|
setNeedStreamData(true);
|
||||||
|
|
||||||
|
// 显示成功通知3秒
|
||||||
|
showEditingNotification({
|
||||||
|
key: notificationKey,
|
||||||
|
isCompleted: true,
|
||||||
|
description: 'Generating intelligent editing plan...',
|
||||||
|
successDescription: 'Generating successful',
|
||||||
|
timeout: 3000
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
notification.destroy(notificationKey);
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
// 触发回调,通知父组件计划生成完成
|
// 触发回调,通知父组件计划生成完成
|
||||||
onEditPlanGenerated?.();
|
onEditPlanGenerated?.();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('生成剪辑计划失败:', error);
|
console.error('生成剪辑计划失败:', error);
|
||||||
setNeedStreamData(true);
|
setNeedStreamData(true);
|
||||||
setIsGenerateEditPlan(false);
|
setIsGenerateEditPlan(false);
|
||||||
|
|
||||||
|
// 显示失败通知3秒
|
||||||
|
showEditingNotification({
|
||||||
|
key: notificationKey,
|
||||||
|
description: 'Generating intelligent editing plan...',
|
||||||
|
timeoutDescription: 'Generating failed, please try again',
|
||||||
|
timeout: 3000
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
notification.destroy(notificationKey);
|
||||||
|
}, 3000);
|
||||||
}
|
}
|
||||||
}, [episodeId, onEditPlanGenerated]);
|
}, [episodeId, onEditPlanGenerated, notificationKey]);
|
||||||
|
|
||||||
const openEditPlan = useCallback(async () => {
|
const openEditPlan = useCallback(async () => {
|
||||||
window.open(`https://smartcut.movieflow.ai/ai-editor/${episodeId}?token=${token}&user_id=${useid}`, '_target');
|
window.open(`https://smartcut.movieflow.ai/ai-editor/${episodeId}?token=${token}&user_id=${useid}`, '_target');
|
||||||
@ -336,11 +385,35 @@ export function useWorkflowData({ onEditPlanGenerated }: UseWorkflowDataProps =
|
|||||||
const total_count = taskCurrent.videos.data.length;
|
const total_count = taskCurrent.videos.data.length;
|
||||||
let analyze_video_completed_count = all_task_data.filter((item: any) => item.task_name === 'generate_analyze_video' && item.task_status !== 'INIT' && item.task_status !== 'RUNNING').length;
|
let analyze_video_completed_count = all_task_data.filter((item: any) => item.task_name === 'generate_analyze_video' && item.task_status !== 'INIT' && item.task_status !== 'RUNNING').length;
|
||||||
let analyze_video_total_count = all_task_data.filter((item: any) => item.task_name === 'generate_analyze_video').length;
|
let analyze_video_total_count = all_task_data.filter((item: any) => item.task_name === 'generate_analyze_video').length;
|
||||||
|
|
||||||
|
// 检查是否需要开始显示视频分析进度
|
||||||
|
// 只在第一次检测到视频分析任务时显示通知
|
||||||
|
if (analyze_video_total_count > 0 && !isAnalyzing && analyze_video_completed_count !== analyze_video_total_count) {
|
||||||
|
setIsAnalyzing(true);
|
||||||
|
// 如果是第一次显示通知,才调用showEditingNotification
|
||||||
|
const resetFunc = (window as any)[`resetProgress_${notificationKey}`];
|
||||||
|
if (!resetFunc) {
|
||||||
|
showEditingNotification({
|
||||||
|
key: notificationKey,
|
||||||
|
description: 'Preparing intelligent editing plan...',
|
||||||
|
successDescription: 'Preparing successful',
|
||||||
|
timeoutDescription: 'Preparing failed, please try again',
|
||||||
|
timeout: 3 * 60 * 1000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (analyze_video_total_count && analyze_video_completed_count === analyze_video_total_count) {
|
if (analyze_video_total_count && analyze_video_completed_count === analyze_video_total_count) {
|
||||||
if(error_totle < total_count * errorConfig)
|
// 视频分析完成
|
||||||
|
if(error_totle < total_count * errorConfig) {
|
||||||
setCanGoToCut(true);
|
setCanGoToCut(true);
|
||||||
else
|
// 重置进度条,显示生成剪辑计划进度
|
||||||
|
setIsAnalyzing(false);
|
||||||
|
} else {
|
||||||
setIsShowError(true);
|
setIsShowError(true);
|
||||||
|
notification.destroy(notificationKey);
|
||||||
|
setIsAnalyzing(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -644,7 +717,7 @@ export function useWorkflowData({ onEditPlanGenerated }: UseWorkflowDataProps =
|
|||||||
fallbackToStep,
|
fallbackToStep,
|
||||||
originalText: state.originalText,
|
originalText: state.originalText,
|
||||||
// showGotoCutButton: from && currentLoadingText.includes('Post-production') ? true : false,
|
// showGotoCutButton: from && currentLoadingText.includes('Post-production') ? true : false,
|
||||||
showGotoCutButton: canGoToCut && (isGenerateEditPlan || taskObject.currentStage === 'final_video') ? true : false,
|
showGotoCutButton: (canGoToCut && (isGenerateEditPlan || taskObject.currentStage === 'final_video') || isShowError) ? true : false,
|
||||||
generateEditPlan: openEditPlan,
|
generateEditPlan: openEditPlan,
|
||||||
handleRetryVideo
|
handleRetryVideo
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user