diff --git a/.env.production b/.env.production index 1720734..24e6f58 100644 --- a/.env.production +++ b/.env.production @@ -1,16 +1,16 @@ # 测试 -NEXT_PUBLIC_JAVA_URL = https://auth.test.movieflow.ai -NEXT_PUBLIC_BASE_URL = https://77.smartvideo.py.qikongjian.com -NEXT_PUBLIC_CUT_URL = https://77.smartcut.py.qikongjian.com -NEXT_PUBLIC_GOOGLE_REDIRECT_URI=https://www.movieflow.net/api/auth/google/callback -NEXT_PUBLIC_CUT_URL_TO = https://smartcut.huiying.video +# NEXT_PUBLIC_JAVA_URL = https://auth.test.movieflow.ai +# NEXT_PUBLIC_BASE_URL = https://77.smartvideo.py.qikongjian.com +# NEXT_PUBLIC_CUT_URL = https://77.smartcut.py.qikongjian.com +# NEXT_PUBLIC_GOOGLE_REDIRECT_URI=https://www.movieflow.net/api/auth/google/callback +# NEXT_PUBLIC_CUT_URL_TO = https://smartcut.huiying.video # 生产 -# NEXT_PUBLIC_JAVA_URL = https://auth.movieflow.ai -# NEXT_PUBLIC_BASE_URL = https://api.video.movieflow.ai -# NEXT_PUBLIC_CUT_URL = https://smartcut.api.movieflow.ai -# NEXT_PUBLIC_GOOGLE_REDIRECT_URI=https://www.movieflow.ai/api/auth/google/callback -# NEXT_PUBLIC_CUT_URL_TO = https://smartcut.movieflow.ai +NEXT_PUBLIC_JAVA_URL = https://auth.movieflow.ai +NEXT_PUBLIC_BASE_URL = https://api.video.movieflow.ai +NEXT_PUBLIC_CUT_URL = https://smartcut.api.movieflow.ai +NEXT_PUBLIC_GOOGLE_REDIRECT_URI=https://www.movieflow.ai/api/auth/google/callback +NEXT_PUBLIC_CUT_URL_TO = https://smartcut.movieflow.ai # 通用 # 当前域名配置 NEXT_PUBLIC_FRONTEND_URL = https://www.movieflow.ai diff --git a/api/movie_queue.ts b/api/movie_queue.ts index a20d442..30a80cd 100644 --- a/api/movie_queue.ts +++ b/api/movie_queue.ts @@ -1,6 +1,5 @@ import { ApiResponse } from './common'; -import { showH5QueueNotification } from '../components/QueueBox/H5QueueNotication'; -import { notification } from 'antd'; +import { showQueueNotification } from '../components/QueueBox/QueueNotication'; /** 队列状态枚举 */ export enum QueueStatus { @@ -75,7 +74,6 @@ export async function withQueuePolling( const cancel = () => { isCancelled = true; try { closeModal?.(); } catch {} - notification.destroy(); // 兼容旧弹层 onCancel?.(); cancelTokens.delete(pollId); }; @@ -104,14 +102,14 @@ export async function withQueuePolling( position !== undefined && waiting !== undefined) { // 打开或更新 H5 弹窗(仅允许 Cancel 关闭,Refresh 触发刷新) try { closeModal?.(); } catch {} - closeModal = showH5QueueNotification( + closeModal = showQueueNotification( position, waiting, status, cancel, async () => { // 触发一次立刻刷新:重置 attempts 的等待,直接递归调用 poll() - // 不关闭弹窗,由 showH5QueueNotification 保持打开 + // 不关闭弹窗,由 showQueueNotification 保持打开 attempts = Math.max(0, attempts - 1); } ); @@ -123,7 +121,6 @@ export async function withQueuePolling( // 检查是否达到最大尝试次数 if (attempts >= maxAttempts) { - notification.destroy(); // 关闭通知 throw new Error('Exceeded the maximum polling limit'); } @@ -135,17 +132,14 @@ export async function withQueuePolling( // 如果状态为ready,结束轮询 if (response.code !== 202 && response.data) { try { closeModal?.(); } catch {} - notification.destroy(); // 兼容旧弹层 onSuccess?.(response.data); return response; } try { closeModal?.(); } catch {} - notification.destroy(); // 兼容旧弹层 return response; } catch (error) { try { closeModal?.(); } catch {} - notification.destroy(); // 兼容旧弹层 if (error instanceof Error) { onError?.(error); } diff --git a/components/QueueBox/H5QueueNotication.tsx b/components/QueueBox/QueueNotication.tsx similarity index 95% rename from components/QueueBox/H5QueueNotication.tsx rename to components/QueueBox/QueueNotication.tsx index 4fd062a..0f32745 100644 --- a/components/QueueBox/H5QueueNotication.tsx +++ b/components/QueueBox/QueueNotication.tsx @@ -14,7 +14,7 @@ interface H5QueueNotificationProps { onClose?: () => void; } -function H5QueueNotificationModal(props: H5QueueNotificationProps) { +function QueueNotificationModal(props: H5QueueNotificationProps) { const { position, estimatedMinutes, status, onCancel, onClose } = props; const containerRef = useRef(null); @@ -39,11 +39,11 @@ function H5QueueNotificationModal(props: H5QueueNotificationProps) { return (
{/* 去除右上角关闭按钮,避免除取消以外的关闭路径 */} @@ -116,7 +116,7 @@ function H5QueueNotificationModal(props: H5QueueNotificationProps) { * @param {() => void} onCancel - Callback when user confirms cancel. * @returns {() => void} - Close function to dismiss the modal programmatically. */ -export function showH5QueueNotification( +export function showQueueNotification( position: number, estimatedMinutes: number, status: QueueStatus, @@ -128,7 +128,7 @@ export function showH5QueueNotification( } const mount = document.createElement('div'); - mount.setAttribute('data-alt', 'h5-queue-root'); + mount.setAttribute('data-alt', 'queue-root'); document.body.appendChild(mount); let root: Root | null = null; @@ -151,7 +151,7 @@ export function showH5QueueNotification( }; root.render( - ( -
- - {/* AI导演的圆形头部 */} - - {/* 眼睛 */} - - - {/* 笑容 */} - - {/* 导演帽 */} - - {/* 身体 */} - - {/* 手臂 - 动画中会移动 */} - - - -
-); - -/** 工作进度条组件 */ -const ProgressTimeline = () => ( -
-
-
-); - -/** 工作台元素组件 */ -const Workstation = () => ( -
- {/* 小型场景图标,会在动画中浮动 */} - {[...Array(3)].map((_, i) => ( -
- ))} -
-); - -/** - * 显示队列等待通知 - * @param position - 当前队列位置 - * @param estimatedMinutes - 预计等待分钟数 - */ -export const showQueueNotification = ( - position: number, - estimatedMinutes: number, - status: string, - onCancel: () => void -) => { - const notificationKey = 'queueNotification'; - - // 创建或更新通知内容 - const notificationContent = ( -
- {/* AI导演工作室场景 */} -
- - - -
- - {/* 队列信息 */} -
- 🎬 - {status === 'process' ? `Your work is being produced. Please wait until it is completed before creating a new work.` : `Your work is waiting for production at the ${position} position`} -
- - {/* 预计等待时间 */} -
- {status !== 'process' && `Estimated waiting time: about ${estimatedMinutes} minutes`} -
- - {/* 取消按钮 */} - -
- ); - - // 打开或更新通知 - notification.open({ - key: notificationKey, - message: null, - description: notificationContent, - duration: 0, - placement: 'topRight', - style: { - ...darkGlassStyle, - border: '1px solid rgba(246, 178, 102, 0.2)', - }, - className: 'director-studio-notification', - closeIcon: null, - }); -}; - -// 添加必要的CSS动画 -const styles = ` - .ai-director { - animation: bounce 2s ease-in-out infinite; - } - - .director-arm { - transform-origin: center; - animation: wave 1s ease-in-out infinite alternate; - } - - @keyframes bounce { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(-5px); } - } - - @keyframes wave { - 0% { transform: rotate(-5deg); } - 100% { transform: rotate(5deg); } - } - - @keyframes float { - 0% { transform: translateY(0); } - 100% { transform: translateY(-10px); } - } - - @keyframes progress { - 0% { width: 0%; } - 50% { width: 60%; } - 100% { width: 30%; } - } - - .director-studio-notification { - animation: slideIn 0.3s ease-out; - } - - @keyframes slideIn { - from { transform: translateX(100%); opacity: 0; } - to { transform: translateX(0); opacity: 1; } - } - - .scene-0 { animation-delay: 0s; } - .scene-1 { animation-delay: 0.2s; } - .scene-2 { animation-delay: 0.4s; } -`; - -// 将样式注入到页面 -if (typeof document !== 'undefined') { - const styleSheet = document.createElement('style'); - styleSheet.textContent = styles; - document.head.appendChild(styleSheet); -} - -// 配置通知 -notification.config({ - maxCount: 3, -}); \ No newline at end of file diff --git a/components/pages/work-flow.tsx b/components/pages/work-flow.tsx index 972b034..8bf7ab5 100644 --- a/components/pages/work-flow.tsx +++ b/components/pages/work-flow.tsx @@ -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(); - // 通过全局事件桥接 H5ProgressToast(Provider 在本组件 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(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(null); - // const aiEditingButtonRef = useRef<{ handleAIEditing: () => Promise }>(null); - const [editingStatus, setEditingStatus] = React.useState<'initial' | 'idle' | 'success' | 'error'>('initial'); - // const [iframeAiEditingKey, setIframeAiEditingKey] = React.useState(`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(null); - const editingProgressIntervalRef = useRef(null); - const editingProgressStartRef = useRef(0); const searchParams = useSearchParams(); const episodeId = searchParams.get('episodeId') || ''; @@ -76,134 +42,7 @@ 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) | 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 { taskObject, @@ -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 ( @@ -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.`;
- {/* AI剪辑按钮 - 已注释,不加载iframe */} - {/* - { - isShowAutoEditing && ( -
- - - -
- ) - } - */} - - {/* 导出进度显示 - 已注释 */} - {/* - {exportProgress && exportProgress.status === 'processing' && ( -
-
-
- 导出进度: {exportProgress.percentage}% -
-
-
-
-
- {exportProgress.message} - {exportProgress.stage && ` (${exportProgress.stage})`} -
-
-
- )} - */} - - {/* 测试导出接口按钮 - 隐藏显示(仍可通过逻辑调用) */} -
- - - -
- {/* 智能对话按钮 */}
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, @@ -369,9 +363,6 @@ export function H5MediaViewer({