"use client" import React, { useRef, useEffect, useCallback } from "react"; import "./style/work-flow.css"; import { EditModal } from "@/components/ui/edit-modal"; import { TaskInfo } from "./work-flow/task-info"; import H5TaskInfo from "./work-flow/H5TaskInfo"; import H5ProgressBar from "./work-flow/H5ProgressBar"; import H5MediaViewer from "./work-flow/H5MediaViewer"; import { MediaViewer } from "./work-flow/media-viewer"; import { ThumbnailGrid } from "./work-flow/thumbnail-grid"; import { useWorkflowData } from "./work-flow/use-workflow-data"; import { usePlaybackControls } from "./work-flow/use-playback-controls"; import { Bot, TestTube, MessageCircle } from "lucide-react"; import { GlassIconButton } from '@/components/ui/glass-icon-button'; 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 { 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(); const containerRef = useRef(null); const [isEditModalOpen, setIsEditModalOpen] = React.useState(false); const [activeEditTab, setActiveEditTab] = React.useState('1'); const [isSmartChatBoxOpen, setIsSmartChatBoxOpen] = React.useState(true); const [chatTip, setChatTip] = React.useState(null); const [hasUnread, setHasUnread] = React.useState(false); const [previewVideoUrl, setPreviewVideoUrl] = React.useState(null); const [previewVideoId, setPreviewVideoId] = React.useState(null); const [isFocusChatInput, setIsFocusChatInput] = React.useState(false); const [selectedView, setSelectedView] = React.useState<'final' | 'video' | null>(null); const [aiEditingResult, setAiEditingResult] = React.useState(null); const searchParams = useSearchParams(); const episodeId = searchParams.get('episodeId') || ''; const userId = JSON.parse(localStorage.getItem("currentUser") || '{}').id || NaN; SaveEditUseCase.setProjectId(episodeId); // 使用自定义 hooks 管理状态 const { taskObject, scriptData, isLoading, currentSketchIndex, currentLoadingText, setCurrentSketchIndex, isPauseWorkFlow, mode, setIsPauseWorkFlow, setAnyAttribute, applyScript, fallbackToStep, originalText, showGotoCutButton, generateEditPlan, handleRetryVideo, aspectRatio } = useWorkflowData({ }); const { isVideoPlaying, toggleVideoPlay, } = usePlaybackControls(taskObject.videos.data, taskObject.currentStage); useEffect(() => { if (isMobile) { setIsSmartChatBoxOpen(false); } }, [isMobile]); useEffect(() => { console.log('changedIndex_work-flow', currentSketchIndex, taskObject); }, [currentSketchIndex, taskObject]); // 当最终视频出现时,强制切换到最终视频 useEffect(() => { if (taskObject?.final?.url) { setSelectedView('final'); } }, [taskObject?.final?.url]); const handleEditModalOpen = useCallback((tab: string) => { setActiveEditTab(tab); setIsEditModalOpen(true); }, []); // 视频编辑描述提交处理函数 const handleVideoEditDescriptionSubmit = useCallback((editPoint: EditPointType, description: string) => { console.log('🎬 视频编辑描述提交:', { editPoint, description }); // 构造编辑消息发送到SmartChatBox const editMessage = `📝 Video Edit Request 🎯 **Position**: ${Math.round(editPoint.position.x)}%, ${Math.round(editPoint.position.y)}% ⏰ **Timestamp**: ${Math.floor(editPoint.timestamp)}s 🎬 **Video**: Shot ${currentSketchIndex + 1} **Edit Description:** ${description} Please process this video editing request.`; // 如果SmartChatBox开启,自动发送消息 if (isSmartChatBoxOpen) { // 这里可以通过SmartChatBox的API发送消息 // 或者通过全局状态管理来处理 console.log('📤 发送编辑请求到聊天框:', editMessage); } // 显示成功通知 notification.success({ message: 'Edit Request Submitted', description: `Your edit request for timestamp ${Math.floor(editPoint.timestamp)}s has been submitted successfully.`, duration: 3 }); }, [currentSketchIndex, isSmartChatBoxOpen]); return (
{isMobile || isTablet ? ( <> {taskObject.currentStage !== 'init' && taskObject.status !== 'COMPLETED' && ( )} ) : ( )}
{isDesktop ? (
setIsSmartChatBoxOpen(true)} setVideoPreview={(url, id) => { setPreviewVideoUrl(url); setPreviewVideoId(id); }} showGotoCutButton={showGotoCutButton} onGotoCut={generateEditPlan} isSmartChatBoxOpen={isSmartChatBoxOpen} onRetryVideo={(video_id) => handleRetryVideo(video_id)} aspectRatio={aspectRatio === 'VIDEO_ASPECT_RATIO_LANDSCAPE' ? '16:9' : '9:16'} placeholderWidth={aspectRatio === 'VIDEO_ASPECT_RATIO_LANDSCAPE' ? `calc((100vh - ${taskObject.final.url ? '8rem' : '12rem'}) / 9 * 16)` : `calc((100vh - ${taskObject.final.url ? '8rem' : '12rem'}) / 16 * 9)`} /> {['scene', 'character', 'video', 'final_video'].includes(taskObject.currentStage) && (
{ if (index === -1 && taskObject.final.url) { // 点击最终视频 setSelectedView('final'); setCurrentSketchIndex(0); } else { // 点击普通视频 taskObject.final.url && setSelectedView('video'); setCurrentSketchIndex(index); } }} onRetryVideo={handleRetryVideo} className={isDesktop ? 'auto-cols-[20%]' : (isTablet ? 'auto-cols-[25%]' : 'auto-cols-[33%]')} cols={isDesktop ? '20%' : isTablet ? '25%' : '33%'} selectedView={selectedView} aspectRatio={aspectRatio} isMobile={isMobile} />
)}
) : (
setIsSmartChatBoxOpen(true)} setVideoPreview={(url, id) => { setPreviewVideoUrl(url); setPreviewVideoId(id); }} isSmartChatBoxOpen={isSmartChatBoxOpen} onRetryVideo={(video_id) => handleRetryVideo(video_id)} onSelectView={(view) => setSelectedView(view)} enableVideoEdit={true} onVideoEditDescriptionSubmit={handleVideoEditDescriptionSubmit} projectId={episodeId} aspectRatio={aspectRatio === 'VIDEO_ASPECT_RATIO_LANDSCAPE' ? '16:9' : '9:16'} showProgress={taskObject.currentStage !== 'init' && taskObject.status !== 'COMPLETED'} /> {['scene', 'character', 'video', 'final_video'].includes(taskObject.currentStage) && (
{ if (index === -1) { // 点击最终视频 setSelectedView('final'); setCurrentSketchIndex(0); } else { // 点击普通视频 setSelectedView('video'); setCurrentSketchIndex(index); } }} onRetryVideo={handleRetryVideo} className={isDesktop ? 'auto-cols-[20%]' : (isTablet ? 'auto-cols-[25%]' : 'auto-cols-[33%]')} cols={isDesktop ? '20%' : isTablet ? '25%' : '33%'} selectedView={selectedView} aspectRatio={aspectRatio} isMobile={isMobile} />
)}
)}
{/* 智能对话按钮 */}
{isMobile ? (
{(!isSmartChatBoxOpen && chatTip) && (
{chatTip}
)} {/* 红点徽标 */} {(!isSmartChatBoxOpen && hasUnread) && ( )} { setIsSmartChatBoxOpen(true); setChatTip(null); setHasUnread(false); }} className="backdrop-blur-lg bg-custom-purple/80 border-transparent hover:bg-custom-purple/80" />
) : ( setIsSmartChatBoxOpen(true)} className="backdrop-blur-lg" /> )}
{/* 智能对话弹窗 */} setIsSmartChatBoxOpen(false)} > { if (!isSmartChatBoxOpen && snippet) { setChatTip(snippet); setHasUnread(true); // 5秒后自动消失 setTimeout(() => setChatTip(null), 5000); } }} onClearPreview={() => { setPreviewVideoUrl(null); setPreviewVideoId(null); }} aiEditingResult={aiEditingResult} /> { SaveEditUseCase.clearData(); setIsEditModalOpen(false) }} taskObject={taskObject} currentSketchIndex={currentSketchIndex} roles={taskObject.roles.data} setIsPauseWorkFlow={setIsPauseWorkFlow} isPauseWorkFlow={isPauseWorkFlow} fallbackToStep={fallbackToStep} originalText={originalText} />
) }); export default WorkFlow; // 桥接组件:监听全局事件并驱动 H5ProgressToast const H5ToastBridge: React.FC = () => { const { show, update, hide } = useH5ProgressToast(); useEffect(() => { const onShow = (e: Event) => { const detail = (e as CustomEvent).detail || {}; show(detail); }; const onUpdate = (e: Event) => { const detail = (e as CustomEvent).detail || {}; update(detail); }; const onHide = () => hide(); window.addEventListener('h5Toast:show', onShow as EventListener); window.addEventListener('h5Toast:update', onUpdate as EventListener); window.addEventListener('h5Toast:hide', onHide as EventListener); return () => { window.removeEventListener('h5Toast:show', onShow as EventListener); window.removeEventListener('h5Toast:update', onUpdate as EventListener); window.removeEventListener('h5Toast:hide', onHide as EventListener); }; }, [show, update, hide]); return null; };