水印视频失败再使用合成的视频

This commit is contained in:
北枳 2025-10-10 20:18:47 +08:00
parent 56f6fa0ef1
commit 87f171e683
6 changed files with 44 additions and 576 deletions

View File

@ -17,30 +17,12 @@ 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 { exportVideoWithRetry } from '@/utils/export-service';
import { EditPoint as EditPointType } from './work-flow/video-edit/types';
import { useDeviceType } from '@/hooks/useDeviceType';
import { H5ProgressToastProvider, useH5ProgressToast } from '@/components/ui/h5-progress-toast';
const WorkFlow = React.memo(function WorkFlow() {
const { isMobile, isTablet, isDesktop } = useDeviceType();
// 通过全局事件桥接 H5ProgressToastProvider 在本组件 JSX 中,逻辑层无法直接使用 hook
const emitToastShow = useCallback((params: { title?: string; progress?: number }) => {
window.dispatchEvent(new CustomEvent('h5Toast:show', { detail: params }));
}, []);
const emitToastUpdate = useCallback((params: { title?: string; progress?: number }) => {
window.dispatchEvent(new CustomEvent('h5Toast:update', { detail: params }));
}, []);
const emitToastHide = useCallback(() => {
window.dispatchEvent(new CustomEvent('h5Toast:hide'));
}, []);
useEffect(() => {
console.log("init-WorkFlow");
return () => {
console.log("unmount-WorkFlow");
// 不在卸载时强制隐藏,避免严格模式下二次卸载导致刚显示就被关闭
};
}, [emitToastHide]);
const containerRef = useRef<HTMLDivElement>(null);
const [isEditModalOpen, setIsEditModalOpen] = React.useState(false);
const [activeEditTab, setActiveEditTab] = React.useState('1');
@ -53,22 +35,6 @@ const WorkFlow = React.memo(function WorkFlow() {
const [selectedView, setSelectedView] = React.useState<'final' | 'video' | null>(null);
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 [exportProgress, setExportProgress] = React.useState<{
status: 'processing' | 'completed' | 'failed';
percentage: number;
message: string;
stage?: string;
taskId?: string;
} | null>(null);
const editingTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const editingProgressIntervalRef = useRef<NodeJS.Timeout | null>(null);
const editingProgressStartRef = useRef<number>(0);
const searchParams = useSearchParams();
const episodeId = searchParams.get('episodeId') || '';
@ -76,133 +42,6 @@ const WorkFlow = React.memo(function WorkFlow() {
const userId = JSON.parse(localStorage.getItem("currentUser") || '{}').id || NaN;
SaveEditUseCase.setProjectId(episodeId);
const [isHandleEdit, setIsHandleEdit] = React.useState(false);
// 使用 ref 存储 handleTestExport 避免循环依赖
const handleTestExportRef = useRef<(() => Promise<any>) | 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('✨ 编辑计划生成完成开始AI剪辑');
setIsHandleEdit(true);
setEditingStatus('idle');
// setIsEditingInProgress(true); // 已移除该状态变量
isEditingInProgressRef.current = true;
// 改为调用测试剪辑计划导出按钮方法
// aiEditingButtonRef.current?.handleAIEditing();
// 使用 ref 调用避免循环依赖
setTimeout(() => {
handleTestExportRef.current?.();
}, 0);
const title = isMobile ? 'editing...' : 'Performing intelligent editing...';
// 显示进度提示并启动超时定时器
// emitToastShow({ title: title, progress: 0 });
// 启动自动推进到 90% 的进度8分钟
if (editingProgressIntervalRef.current) clearInterval(editingProgressIntervalRef.current);
editingProgressStartRef.current = Date.now();
const totalMs = 8 * 60 * 1000;
// editingProgressIntervalRef.current = setInterval(() => {
// const elapsed = Date.now() - editingProgressStartRef.current;
// const pct = Math.min(90, Math.max(0, Math.floor((elapsed / totalMs) * 90)));
// emitToastUpdate({ progress: pct });
// }, 250);
if (editingTimeoutRef.current) clearTimeout(editingTimeoutRef.current);
editingTimeoutRef.current = setTimeout(() => {
console.log('❌ Editing timeout - retrying...');
localStorage.removeItem(`isLoaded_plan_${episodeId}`);
if (editingProgressIntervalRef.current) {
clearInterval(editingProgressIntervalRef.current);
editingProgressIntervalRef.current = null;
}
// emitToastHide();
setTimeout(() => {
// emitToastShow({ title: 'Retry intelligent editing...', progress: 0 });
// 重试阶段自动推进5分钟到 90%
if (editingProgressIntervalRef.current) clearInterval(editingProgressIntervalRef.current);
editingProgressStartRef.current = Date.now();
const retryTotalMs = 5 * 60 * 1000;
editingProgressIntervalRef.current = setInterval(() => {
const elapsed = Date.now() - editingProgressStartRef.current;
const pct = Math.min(90, Math.max(0, Math.floor((elapsed / retryTotalMs) * 90)));
// emitToastUpdate({ progress: pct });
}, 250);
if (editingTimeoutRef.current) clearTimeout(editingTimeoutRef.current);
editingTimeoutRef.current = setTimeout(() => {
console.log('Editing retry failed');
localStorage.removeItem(`isLoaded_plan_${episodeId}`);
setTimeout(() => {
setEditingStatus('error');
setIsEditingInProgress(false);
isEditingInProgressRef.current = false;
if (editingProgressIntervalRef.current) {
clearInterval(editingProgressIntervalRef.current);
editingProgressIntervalRef.current = null;
}
// emitToastHide();
}, 5000);
}, 5 * 60 * 1000);
}, 200);
}, 8 * 60 * 1000);
}, [episodeId, emitToastHide, emitToastShow, emitToastUpdate]); // 移除 isEditingInProgress 依赖
/** 处理导出失败 */
const handleExportFailed = useCallback(() => {
console.log('Export failed, setting error status');
setEditingStatus('error');
// setIsEditingInProgress(false); // 已移除该状态变量
isEditingInProgressRef.current = false;
if (editingTimeoutRef.current) {
clearTimeout(editingTimeoutRef.current);
editingTimeoutRef.current = null;
}
if (editingProgressIntervalRef.current) {
clearInterval(editingProgressIntervalRef.current);
editingProgressIntervalRef.current = null;
}
// emitToastHide();
}, [emitToastHide]);
// 使用自定义 hooks 管理状态
const {
@ -222,12 +61,8 @@ const WorkFlow = React.memo(function WorkFlow() {
showGotoCutButton,
generateEditPlan,
handleRetryVideo,
isShowAutoEditing,
aspectRatio
} = useWorkflowData({
onEditPlanGenerated: handleEditPlanGenerated,
editingStatus: editingStatus,
onExportFailed: handleExportFailed
});
const {
@ -251,63 +86,11 @@ const WorkFlow = React.memo(function WorkFlow() {
}
}, [taskObject?.final?.url]);
// 监听粗剪是否完成
useEffect(() => {
console.log('🎬 final video useEffect triggered:', {
finalUrl: taskObject.final.url,
isHandleEdit
});
if (taskObject.final.url && isHandleEdit) {
console.log('🎉 显示编辑完成通知');
// 完成:推进到 100 并清理超时计时器
if (editingTimeoutRef.current) {
clearTimeout(editingTimeoutRef.current);
editingTimeoutRef.current = null;
}
if (editingProgressIntervalRef.current) {
clearInterval(editingProgressIntervalRef.current);
editingProgressIntervalRef.current = null;
}
// emitToastUpdate({ title: 'Editing successful', progress: 100 });
console.log('Editing successful');
localStorage.setItem(`isLoaded_plan_${episodeId}`, 'true');
setEditingStatus('success');
setIsEditingInProgress(false);
isEditingInProgressRef.current = false;
// setTimeout(() => {
// emitToastHide();
// }, 3000);
}
}, [taskObject.final, isHandleEdit, episodeId, emitToastHide, emitToastUpdate]);
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 handleVideoEditDescriptionSubmit = useCallback((editPoint: EditPointType, description: string) => {
console.log('🎬 视频编辑描述提交:', { editPoint, description });
@ -339,77 +122,6 @@ Please process this video editing request.`;
});
}, [currentSketchIndex, isSmartChatBoxOpen]);
// 测试导出接口的处理函数(使用封装的导出服务)
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);
// 收到显式进度时停止自动推进,防止倒退
if (editingProgressIntervalRef.current) {
clearInterval(editingProgressIntervalRef.current);
editingProgressIntervalRef.current = null;
}
emitToastUpdate({ title: message, progress });
}, [emitToastUpdate]);
*/
return (
<H5ProgressToastProvider>
<H5ToastBridge />
@ -441,7 +153,6 @@ Please process this video editing request.`;
currentLoadingText={currentLoadingText}
roles={taskObject.roles.data}
isPauseWorkFlow={isPauseWorkFlow}
showGotoCutButton={showGotoCutButton || editingStatus !== 'idle'}
onGotoCut={generateEditPlan}
setIsPauseWorkFlow={setIsPauseWorkFlow}
/>
@ -478,7 +189,7 @@ Please process this video editing request.`;
setPreviewVideoUrl(url);
setPreviewVideoId(id);
}}
showGotoCutButton={showGotoCutButton || editingStatus !== 'idle'}
showGotoCutButton={showGotoCutButton}
onGotoCut={generateEditPlan}
isSmartChatBoxOpen={isSmartChatBoxOpen}
onRetryVideo={(video_id) => handleRetryVideo(video_id)}
@ -535,8 +246,6 @@ Please process this video editing request.`;
setPreviewVideoUrl(url);
setPreviewVideoId(id);
}}
showGotoCutButton={showGotoCutButton || editingStatus !== 'idle'}
onGotoCut={generateEditPlan}
isSmartChatBoxOpen={isSmartChatBoxOpen}
onRetryVideo={(video_id) => handleRetryVideo(video_id)}
onSelectView={(view) => setSelectedView(view)}
@ -585,68 +294,6 @@ Please process this video editing request.`;
</div>
</div>
{/* AI剪辑按钮 - 已注释不加载iframe */}
{/*
{
isShowAutoEditing && (
<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") || ""}
userId={userId.toString()}
size="md"
onComplete={handleIframeAIEditingComplete}
onError={handleIframeAIEditingError}
onProgress={handleIframeAIEditingProgress}
autoStart={process.env.NODE_ENV !== 'development'}
/>
</Tooltip>
</div>
)
}
*/}
{/* 导出进度显示 - 已注释 */}
{/*
{exportProgress && exportProgress.status === 'processing' && (
<div className="fixed right-[1rem] bottom-[20rem] z-[49]">
<div className="backdrop-blur-lg bg-black/30 border border-white/20 rounded-lg p-4 max-w-xs">
<div className="text-white text-sm mb-2">
: {exportProgress.percentage}%
</div>
<div className="w-full bg-gray-700 rounded-full h-2 mb-2">
<div
className="bg-blue-500 h-2 rounded-full transition-all duration-300"
style={{ width: `${exportProgress.percentage}%` }}
/>
</div>
<div className="text-gray-300 text-xs">
{exportProgress.message}
{exportProgress.stage && ` (${exportProgress.stage})`}
</div>
</div>
</div>
)}
*/}
{/* 测试导出接口按钮 - 隐藏显示(仍可通过逻辑调用) */}
<div
className="fixed right-[1rem] bottom-[16rem] z-[49]"
style={{ display: 'none' }}
>
<Tooltip title="测试剪辑计划导出接口" placement="left">
<GlassIconButton
icon={TestTube}
size='md'
onClick={handleTestExport}
className="backdrop-blur-lg"
/>
</Tooltip>
</div>
{/* 智能对话按钮 */}
<div
className={`fixed right-[1rem] z-[49] ${isMobile ? 'bottom-[9rem]' : 'bottom-[10rem]'}`}

View File

@ -35,10 +35,6 @@ interface H5MediaViewerProps {
onOpenChat?: () => void;
/** 设置聊天预览视频 */
setVideoPreview?: (url: string, id: string) => void;
/** 显示跳转至剪辑平台按钮 */
showGotoCutButton?: boolean;
/** 跳转至剪辑平台 */
onGotoCut?: () => void;
/** 智能对话是否打开H5可忽略布局调整仅占位 */
isSmartChatBoxOpen?: boolean;
/** 失败重试生成视频 */
@ -210,8 +206,6 @@ export function H5MediaViewer({
setCurrentSketchIndex,
onOpenChat,
setVideoPreview,
showGotoCutButton,
onGotoCut,
isSmartChatBoxOpen,
onRetryVideo,
onSelectView,

View File

@ -27,8 +27,16 @@ const H5ProgressBar: React.FC<H5ProgressBarProps> = ({
currentLoadingText,
className
}) => {
/** Check if task has failed */
const isFailed = taskObject.status === 'FAILED'
/** Calculate current stage based on taskObject state */
const currentStage = useMemo(() => {
/** If task failed, show at final stage */
if (isFailed) {
return 3 /** Final stage to show failure */
}
/** Check if roles & scenes are completed */
const rolesCompleted =
taskObject.roles?.total_count > 0 &&
@ -71,6 +79,7 @@ const H5ProgressBar: React.FC<H5ProgressBarProps> = ({
return 0 /** Default to script stage */
}, [
isFailed,
taskObject.currentStage,
taskObject.roles?.data,
taskObject.roles?.total_count,
@ -134,6 +143,10 @@ const H5ProgressBar: React.FC<H5ProgressBarProps> = ({
return Math.min(Math.round((videosCount / videosTotal) * 100), 95)
case 3: /** Final video stage */
/** If task failed, show 60% progress */
if (isFailed) {
return 60
}
/** If final.url exists, show 100% */
if (taskObject.final?.url) {
return 100
@ -166,6 +179,7 @@ const H5ProgressBar: React.FC<H5ProgressBarProps> = ({
})
}, [
currentStage,
isFailed,
scriptData,
taskObject.roles?.data,
taskObject.roles?.total_count,
@ -184,6 +198,8 @@ const H5ProgressBar: React.FC<H5ProgressBarProps> = ({
<div data-alt="progress-segments" className="flex items-center gap-1 relative">
{segments.map(({ stage, config, isCompleted, isCurrent, segmentProgress }) => {
const Icon = config.icon
/** Check if this is the last stage and task has failed */
const isFailedStage = isFailed && stage === 3
return (
<div
@ -196,7 +212,7 @@ const H5ProgressBar: React.FC<H5ProgressBarProps> = ({
data-alt="progress-fill"
className="absolute inset-0 rounded-full z-0 backdrop-blur-md"
style={{
background: `${config.color}80`
background: isFailedStage ? '#ef444480' : `${config.color}80`
}}
initial={{ width: '0%' }}
animate={{ width: `${segmentProgress}%` }}
@ -218,13 +234,13 @@ const H5ProgressBar: React.FC<H5ProgressBarProps> = ({
x: '-50%',
opacity: 1,
scale: 1,
rotate: [0, 360],
rotate: isFailedStage ? 0 : [0, 360],
transition: {
left: { duration: 0.6, ease: 'easeInOut' },
x: { duration: 0 },
opacity: { duration: 0.3 },
scale: { duration: 0.3 },
rotate: { duration: 2, repeat: Infinity, ease: 'linear' }
rotate: isFailedStage ? { duration: 0 } : { duration: 2, repeat: Infinity, ease: 'linear' }
}
}}
exit={{
@ -237,8 +253,8 @@ const H5ProgressBar: React.FC<H5ProgressBarProps> = ({
data-alt="icon-wrapper"
className="w-3 h-3 rounded-full bg-white/90 backdrop-blur-sm flex items-center justify-center shadow-lg"
style={{
boxShadow: `0 0 8px ${config.color}80`,
background: `${config.color}`
boxShadow: isFailedStage ? '0 0 8px #ef444480' : `0 0 8px ${config.color}80`,
background: isFailedStage ? '#ef4444' : `${config.color}`
}}
>
{/* <Icon className="w-2 h-2" style={{ color: config.color }} /> */}
@ -249,7 +265,7 @@ const H5ProgressBar: React.FC<H5ProgressBarProps> = ({
</AnimatePresence>
{/* Glow effect for current stage */}
{isCurrent && segmentProgress < 100 && (
{isCurrent && segmentProgress < 100 && !isFailedStage && (
<motion.div
data-alt="glow-effect"
className="absolute inset-0 rounded-full z-10"

View File

@ -21,7 +21,6 @@ interface TaskInfoProps {
currentLoadingText: string;
roles: any[];
isPauseWorkFlow: boolean;
showGotoCutButton: boolean;
onGotoCut?: () => void;
setIsPauseWorkFlow: (isPauseWorkFlow: boolean) => void;
}
@ -136,7 +135,6 @@ export function TaskInfo({
currentLoadingText,
roles,
isPauseWorkFlow,
showGotoCutButton,
onGotoCut,
setIsPauseWorkFlow
}: TaskInfoProps) {
@ -151,6 +149,7 @@ export function TaskInfo({
// 监听 currentLoadingText
useEffect(() => {
console.log('currentLoadingText', currentLoadingText);
// 清理之前的定时器
if (timerRef.current) {
clearTimeout(timerRef.current);
@ -358,42 +357,6 @@ export function TaskInfo({
/>
</motion.div>
</motion.div>
{/* <motion.div
className="w-1.5 h-1.5 rounded-full"
style={{ backgroundColor: stageColor }}
animate={!isPauseWorkFlow ? {
scale: [1, 1.5, 1],
opacity: [1, 0.5, 1],
transition: {
duration: 1,
repeat: Infinity,
repeatDelay: 0.2,
delay: 0.3
}
} : {}}
/>
<motion.div
className="w-1.5 h-1.5 rounded-full"
style={{ backgroundColor: stageColor }}
animate={!isPauseWorkFlow ? {
scale: [1, 1.5, 1],
opacity: [1, 0.5, 1],
transition: {
duration: 1,
repeat: Infinity,
repeatDelay: 0.2,
delay: 0.3
}
} : {}}
/> */}
{/* //
{showGotoCutButton && (
<Tooltip placement="top" title='AI-powered editing platform'>
<GlassIconButton icon={Scissors} size='sm' onClick={onGotoCut} />
</Tooltip>
)} */}
</motion.div>
)
)

View File

@ -118,7 +118,6 @@ export function ThumbnailGrid({
}
}
console.log('changedIndex_thumbnail-grid', changedIndex);
if (changedIndex !== -1) {

View File

@ -7,49 +7,24 @@ import { useScriptService } from "@/app/service/Interaction/ScriptService";
import { useUpdateEffect } from '@/app/hooks/useUpdateEffect';
import { LOADING_TEXT_MAP, TaskObject, Status, Stage } from '@/api/DTO/movieEdit';
import { AspectRatioValue } from '@/components/ChatInputBox/AspectRatioSelector';
import { useDeviceType } from '@/hooks/useDeviceType';
import { cutUrlTo, errorConfig } from '@/lib/env';
interface UseWorkflowDataProps {
onEditPlanGenerated?: () => void;
editingStatus?: string;
onExportFailed?: () => void;
}
export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFailed }: UseWorkflowDataProps = {}) {
export function useWorkflowData({}: UseWorkflowDataProps = {}) {
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;
// H5进度提示事件桥接
const emitToastShow = (params: { title?: string; progress?: number }) => {
if (typeof window !== 'undefined') {
window.dispatchEvent(new CustomEvent('h5Toast:show', { detail: params }));
}
};
const emitToastUpdate = (params: { title?: string; progress?: number }) => {
if (typeof window !== 'undefined') {
window.dispatchEvent(new CustomEvent('h5Toast:update', { detail: params }));
}
};
const emitToastHide = () => {
if (typeof window !== 'undefined') {
window.dispatchEvent(new CustomEvent('h5Toast:hide'));
}
};
const { isMobile, isTablet, isDesktop } = useDeviceType();
const cutUrl = cutUrlTo;
console.log('cutUrl', cutUrl);
useEffect(() => {
console.log("init-useWorkflowData");
return () => {
console.log("unmount-useWorkflowData");
// 组件卸载时隐藏H5进度提示
// emitToastHide();
};
}, []);
// 查看缓存中 是否已经 加载过 这个项目的 剪辑计划
@ -90,7 +65,6 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
const [canGoToCut, setCanGoToCut] = useState(false);
const [isShowError, setIsShowError] = useState(false);
const [isAnalyzing, setIsAnalyzing] = useState(false);
const [isGenerateEditPlan, setIsGenerateEditPlan] = useState(false);
const [retryCount, setRetryCount] = useState(0);
const [isLoadingGenerateEditPlan, setIsLoadingGenerateEditPlan] = useState(false);
const [state, setState] = useState({
@ -156,102 +130,13 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
}
}, [taskObject.currentStage]);
const generateEditPlan = useCallback(async (retryCount: number) => {
if (isLoadedRef.current) {
return;
}
// 显示生成剪辑计划进度提示
// !emitToastShow({ title: isMobile ? 'Preparing for editing...' : `Generating intelligent editing plan... ${retryCount ? 'Retry Time: ' + retryCount : ''}`, progress: 0 });
// 平滑推进到 80%,后续阶段接管
const start = Date.now();
const duration = 3 * 60 * 1000; // 3分钟推进到 80%
let interval: NodeJS.Timeout | null = null;
const stop = () => { if (interval) { clearInterval(interval); interval = null; } };
interval = setInterval(() => {
const elapsed = Date.now() - start;
const pct = Math.min(80, Math.floor((elapsed / duration) * 80));
// emitToastUpdate({ progress: pct });
if (pct >= 80) stop();
}, 300);
// 先停止轮询
await new Promise(resolve => {
setNeedStreamData(false);
resolve(true);
});
setIsLoadingGenerateEditPlan(true);
try {
const response = await getGenerateEditPlan({ project_id: episodeId });
if (!response.data.editing_plan) {
throw new Error(response.message);
}
console.error('生成剪辑计划成功');
setIsGenerateEditPlan(true);
isLoadedRef.current = 'true';
setNeedStreamData(true);
// 触发回调,通知父组件计划生成完成
console.log('📞 calling onEditPlanGenerated callback');
onEditPlanGenerated?.();
setIsLoadingGenerateEditPlan(false);
stop();
} catch (error) {
console.error('生成剪辑计划失败:', error);
setNeedStreamData(true);
setIsGenerateEditPlan(false);
setTimeout(() => {
// emitToastHide();
setIsLoadingGenerateEditPlan(false);
}, 8000);
stop();
}
}, [episodeId, onEditPlanGenerated]);
const openEditPlan = useCallback(async () => {
window.open(`${cutUrl}/ai-editor/${episodeId}?token=${token}&user_id=${useid}`, '_target');
}, [episodeId, cutUrl, token, useid]);
// useEffect(() => {
// // 主动触发剪辑
// if (canGoToCut && taskObject.currentStage === 'video' && !isShowError) {
// generateEditPlan(retryCount - 1);
// }
// }, [canGoToCut, taskObject.currentStage, isShowError, generateEditPlan, retryCount]);
useEffect(() => {
// 加载剪辑计划结束 并且 失败了 重试
if (!isLoadingGenerateEditPlan && !isGenerateEditPlan) {
setRetryCount((r) => r + 1);
}
}, [isLoadingGenerateEditPlan, isGenerateEditPlan]);
useEffect(() => {
if (isShowError) {
window.msg.error('Too many failed storyboards, unable to execute automatic editing.', 8000);
setCurrentLoadingText(LOADING_TEXT_MAP.toManyFailed);
// 停止轮询
setNeedStreamData(false);
// emitToastHide();
}
if (editingStatus === 'error') {
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);
// emitToastHide();
}
}, [isShowError, editingStatus]);
useUpdateEffect(() => {
console.log('-----look-taskObject_find_changed-----', taskObject);
if (taskObject.currentStage === 'script') {
if (scriptBlocksMemo.length > 0) {
console.log('应用剧本');
// 自动模式下 应用剧本;手动模式 需要点击 下一步 触发
// 确保仅自动触发一次
// state.mode.includes('auto') && loadingText.current !== LOADING_TEXT_MAP.character && applyScript();
loadingText.current = LOADING_TEXT_MAP.character;
} else {
loadingText.current = LOADING_TEXT_MAP.script;
@ -288,10 +173,18 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
if (taskObject.status === 'COMPLETED') {
loadingText.current = LOADING_TEXT_MAP.complete;
}
setCurrentLoadingText(loadingText.current);
}, [scriptBlocksMemo, taskObject.currentStage, taskObject.scenes.data, taskObject.roles.data, taskObject.videos.data, taskObject.status], {mode: 'none'});
if (taskObject.status === 'FAILED') {
if (isShowError) {
loadingText.current = LOADING_TEXT_MAP.toManyFailed;
window.msg.error('Too many failed storyboards, unable to execute automatic editing.', 8000);
} else {
loadingText.current = LOADING_TEXT_MAP.editingError;
window.msg.error('Editing failed, Please click the scissors button to go to the intelligent editing platform.', 8000);
}
}
// 将 sketchCount 和 videoCount 放到 redux 中 每一次变化也要更新
setCurrentLoadingText(loadingText.current);
}, [isShowError, scriptBlocksMemo, taskObject.currentStage, taskObject.scenes.data, taskObject.roles.data, taskObject.videos.data, taskObject.status], {mode: 'none'});
// 添加手动播放控制
const handleManualPlay = useCallback(async () => {
@ -314,9 +207,6 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
const { current: taskCurrent } = tempTaskObject;
let combinerVideoUrl = '';
console.log('---look-all_task_data', all_task_data);
console.log('---look-tempTaskObject', taskCurrent);
// 收集所有需要更新的状态
let stateUpdates = JSON.stringify(taskCurrent);
@ -354,7 +244,6 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
if (task.task_status === 'COMPLETED') {
realSketchResultData = taskCurrent.scenes.data.filter((item: any) => item.status !== 0);
}
console.log('---look-realSketchResultData', realSketchResultData);
taskCurrent.scenes.total_count = task.task_result.total_count;
if (task.task_status !== 'COMPLETED' || taskCurrent.scenes.total_count !== realSketchResultData.length) {
taskCurrent.currentStage = 'scene';
@ -403,39 +292,18 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
});
}
taskCurrent.videos.data = videoList;
console.log('----------正在生成视频中', realTaskResultData.length);
break;
}
if (task.task_status === 'COMPLETED') {
console.log('----------视频生成完成');
// 视频生成完成
// 暂时没有音频生成 直接跳过
// 视频分析
const error_totle = taskCurrent.videos.data.filter((item: any) => item.video_status === 2).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_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);
// 显示准备剪辑计划的提示
// emitToastShow({ title: isMobile ? 'Preparing for editing...' : 'Preparing intelligent editing plan...', progress: 0 });
}
if (analyze_video_total_count && analyze_video_completed_count === analyze_video_total_count) {
// 视频分析完成
if(error_totle !== total_count) {
setCanGoToCut(true);
// 重置进度条,显示生成剪辑计划进度
setIsAnalyzing(false);
// 不主动隐藏,交由后续阶段覆盖标题与进度
} else {
if(error_totle === total_count) {
setIsShowError(true);
setIsAnalyzing(false);
// emitToastHide();
}
taskCurrent.status = 'FAILED';
setNeedStreamData(false);
}
}
}
@ -447,11 +315,6 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
}
if (task.task_status === 'FAILED' || task.task_status === 'ERROR') {
taskCurrent.status = 'FAILED';
// 触发导出失败回调
if (onExportFailed) {
onExportFailed();
}
// 停止轮询
setNeedStreamData(false);
}
@ -492,13 +355,7 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
}
}
console.log('-----look-tempTaskObject-----', loadingText.current);
// 设置最终的状态更新
setCurrentLoadingText(loadingText.current);
if (JSON.stringify(taskCurrent) !== stateUpdates) {
console.log('-----look-tempTaskObject-changed-----', taskCurrent);
// 强制更新,使用新的对象引用确保触发更新
setTaskObject(prev => {
const newState = JSON.parse(JSON.stringify({...prev, ...taskCurrent}));
@ -509,7 +366,7 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
} catch (error) {
console.error('获取数据失败:', error);
}
}, [episodeId, needStreamData, onExportFailed, errorConfig, isAnalyzing]);
}, [episodeId, needStreamData, errorConfig, isAnalyzing]);
// 轮询获取流式数据
useUpdateEffect(() => {
@ -563,7 +420,6 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
setCurrentLoadingText(LOADING_TEXT_MAP.initializing);
// 如果没有标题,轮询获取
const titleResponse = await getScriptTitle({ project_id: episodeId });
console.log('titleResponse', titleResponse);
if (titleResponse.successful) {
taskCurrent.title = titleResponse.data.name;
taskCurrent.tags = titleResponse.data.description.tags || [];
@ -625,10 +481,8 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
taskCurrent.videos.total_count = data.video.total_count;
const videoList = [];
let videoUrls: string[] = [];
console.log('----------data.video.data', data.video.data);
for (const video of data.video.data) {
videoUrls = video.urls ? video.urls.filter((url: null | string) => url !== null) : [];
console.log('----------videoUrls', videoUrls);
let video_status = video.video_status === undefined ? (videoUrls.length > 0 ? 1 : 0) : video.video_status;
// 每一项 video 有多个视频 默认取存在的项
videoList.push({
@ -679,10 +533,7 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
taskCurrent.status = 'COMPLETED';
}
console.log('---look-taskData', taskCurrent);
if (taskCurrent.currentStage === 'script') {
console.log('开始初始化剧本', original_text,episodeId);
// TODO 为什么一开始没项目id
original_text && initializeFromProject(episodeId, original_text).then(() => {
});
@ -701,8 +552,7 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
});
// 设置是否需要获取流式数据
// setNeedStreamData(taskCurrent.status !== 'COMPLETED');
setNeedStreamData(true);
setNeedStreamData(taskCurrent.status !== 'COMPLETED');
} catch (error) {
console.error('初始化失败:', error);
@ -789,10 +639,9 @@ export function useWorkflowData({ onEditPlanGenerated, editingStatus, onExportFa
applyScript,
fallbackToStep,
originalText: state.originalText,
showGotoCutButton: (canGoToCut && (isGenerateEditPlan || taskObject.currentStage === 'final_video') || isShowError) ? true : false,
showGotoCutButton: (taskObject.status === 'FAILED' || taskObject.status === 'COMPLETED') ? true : false,
generateEditPlan: openEditPlan,
handleRetryVideo,
isShowAutoEditing: canGoToCut && taskObject.currentStage !== 'final_video' && isGenerateEditPlan && !isShowError ? true : false,
aspectRatio: state.aspectRatio
};
}