提示进度

This commit is contained in:
北枳 2025-09-05 01:25:17 +08:00
parent bac656d6f7
commit 7b7a93832d
5 changed files with 123 additions and 19 deletions

View File

@ -14,7 +14,7 @@ const darkGlassStyle = {
const studioContainerStyle = {
position: 'relative' as const,
width: '100%',
height: '120px',
height: '100px',
marginBottom: '16px',
background: 'rgba(26, 27, 30, 0.6)',
borderRadius: '8px',

View File

@ -56,9 +56,13 @@ const WorkFlow = React.memo(function WorkFlow() {
aiEditingButtonRef.current?.handleAIEditing();
editingNotificationKey.current = `editing-${Date.now()}`;
showEditingNotification({
description: 'Performing intelligent editing...',
successDescription: 'Editing successful',
timeoutDescription: 'Editing failed, please try again',
timeout: 5 * 60 * 1000,
key: editingNotificationKey.current,
onFail: () => {
console.log('编辑失败');
console.log('Editing failed');
// 清缓存 生成计划 视频重新分析
localStorage.removeItem(`isLoaded_plan_${episodeId}`);
// 3秒后关闭通知
@ -109,7 +113,10 @@ const WorkFlow = React.memo(function WorkFlow() {
if (taskObject.final.url && editingNotificationKey.current && isHandleEdit) {
// 更新通知状态为完成
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('编辑完成');

View File

@ -36,14 +36,22 @@ const descriptionStyle = {
interface EditingNotificationProps {
/** 编辑是否完成 */
isCompleted?: boolean;
/** 自定义描述 */
/** 初始描述文案 */
description?: string;
/** 编辑成功时的描述文案 */
successDescription?: string;
/** 编辑超时失败时的描述文案 */
timeoutDescription?: string;
/** key */
key?: string;
/** 完成时的回调 */
onComplete?: () => void;
/** 失败时的回调 */
onFail?: () => void;
/** 超时时间毫秒默认10分钟 */
timeout?: number;
/** 是否显示关闭按钮默认false */
showCloseIcon?: boolean;
}
/**
@ -54,9 +62,13 @@ export const showEditingNotification = (props: EditingNotificationProps) => {
const {
isCompleted = false,
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,
onComplete,
onFail,
timeout = 8 * 60 * 1000, // 默认8分钟
showCloseIcon = false,
} = props;
const NotificationContent = () => {
@ -66,6 +78,19 @@ export const showEditingNotification = (props: EditingNotificationProps) => {
const timerRef = useRef<NodeJS.Timeout>();
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(() => (
<motion.div
style={{ display: 'inline-flex', marginRight: '8px' }}
@ -86,7 +111,7 @@ export const showEditingNotification = (props: EditingNotificationProps) => {
useEffect(() => {
const updateProgress = () => {
const elapsed = Date.now() - startTimeRef.current;
const timeLimit = 10 * 60 * 1000; // 10分钟
const timeLimit = timeout; // 使用传入的超时时间
if (isCompleted) {
// 如果完成了快速增加到100%
@ -94,7 +119,7 @@ export const showEditingNotification = (props: EditingNotificationProps) => {
const next = prev + (100 - prev) / 10;
if (next >= 99.9) {
setStatus('success');
setCurrentDescription('The editing is complete, and the updated video has been displayed on the page.');
setCurrentDescription(successDescription);
onComplete?.();
clearInterval(timerRef.current);
return 100;
@ -104,7 +129,7 @@ export const showEditingNotification = (props: EditingNotificationProps) => {
} else if (elapsed >= timeLimit) {
// 超时失败
setStatus('exception');
setCurrentDescription('The editing timed out, please try again.');
setCurrentDescription(timeoutDescription);
onFail?.();
clearInterval(timerRef.current);
return;
@ -166,7 +191,7 @@ export const showEditingNotification = (props: EditingNotificationProps) => {
placement: 'topRight',
style: darkGlassStyle,
className: 'dark-glass-notification',
closeIcon: null
closeIcon: showCloseIcon ? undefined : null
});
// 返回key以便外部可以手动关闭通知

View File

@ -582,9 +582,8 @@ export const MediaViewer = React.memo(function MediaViewer({
)}
{currentSketch.status === 2 && (
<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" />
<span>Failed</span>
</div>
</div>
)}

View File

@ -2,6 +2,8 @@
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
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 { useScriptService } from "@/app/service/Interaction/ScriptService";
import { useUpdateEffect } from '@/app/hooks/useUpdateEffect';
@ -13,16 +15,25 @@ interface UseWorkflowDataProps {
}
export function useWorkflowData({ onEditPlanGenerated }: UseWorkflowDataProps = {}) {
useEffect(() => {
console.log("init-useWorkflowData");
return () => console.log("unmount-useWorkflowData");
}, []);
const searchParams = useSearchParams();
const episodeId = searchParams.get('episodeId') || '';
const from = searchParams.get('from') || '';
const token = localStorage.getItem('token') || '';
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}`));
@ -61,6 +72,7 @@ export function useWorkflowData({ onEditPlanGenerated }: UseWorkflowDataProps =
const [isPauseWorkFlow, setIsPauseWorkFlow] = useState(false);
const [canGoToCut, setCanGoToCut] = useState(false);
const [isShowError, setIsShowError] = useState(false);
const [isAnalyzing, setIsAnalyzing] = useState(false);
const [isGenerateEditPlan, setIsGenerateEditPlan] = useState(false);
const [state, setState] = useState({
mode: 'automatic' as 'automatic' | 'manual' | 'auto',
@ -127,6 +139,19 @@ export function useWorkflowData({ onEditPlanGenerated }: UseWorkflowDataProps =
if (isLoadedRef.current) {
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 => {
setNeedStreamData(false);
@ -138,14 +163,38 @@ export function useWorkflowData({ onEditPlanGenerated }: UseWorkflowDataProps =
setIsGenerateEditPlan(true);
isLoadedRef.current = '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?.();
} catch (error) {
console.error('生成剪辑计划失败:', error);
setNeedStreamData(true);
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 () => {
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;
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;
// 检查是否需要开始显示视频分析进度
// 只在第一次检测到视频分析任务时显示通知
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(error_totle < total_count * errorConfig)
// 视频分析完成
if(error_totle < total_count * errorConfig) {
setCanGoToCut(true);
else
// 重置进度条,显示生成剪辑计划进度
setIsAnalyzing(false);
} else {
setIsShowError(true);
notification.destroy(notificationKey);
setIsAnalyzing(false);
}
}
}
}
@ -644,7 +717,7 @@ export function useWorkflowData({ onEditPlanGenerated }: UseWorkflowDataProps =
fallbackToStep,
originalText: state.originalText,
// 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,
handleRetryVideo
};