forked from 77media/video-flow
262 lines
9.5 KiB
TypeScript
262 lines
9.5 KiB
TypeScript
"use client"
|
|
import React, { useRef, useEffect, useCallback } from "react";
|
|
import "./style/work-flow.css";
|
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
import { AISuggestionBar } from "@/components/ai-suggestion-bar";
|
|
import { EditModal } from "@/components/ui/edit-modal";
|
|
import { ErrorBoundary } from "@/components/ui/error-boundary";
|
|
import { TaskInfo } from "./work-flow/task-info";
|
|
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 { AlertCircle, RefreshCw, Pause, Play, ChevronLast } from "lucide-react";
|
|
import { motion } from "framer-motion";
|
|
import { GlassIconButton } from '@/components/ui/glass-icon-button';
|
|
import { SaveEditUseCase } from "@/app/service/usecase/SaveEditUseCase";
|
|
import { useSearchParams } from "next/navigation";
|
|
|
|
const WorkFlow = React.memo(function WorkFlow() {
|
|
console.log('WorkFlow--0294877777777777')
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const [isEditModalOpen, setIsEditModalOpen] = React.useState(false);
|
|
const [activeEditTab, setActiveEditTab] = React.useState('1');
|
|
|
|
const searchParams = useSearchParams();
|
|
const episodeId = searchParams.get('episodeId') || '';
|
|
|
|
SaveEditUseCase.setProjectId(episodeId);
|
|
// 使用自定义 hooks 管理状态
|
|
const {
|
|
taskObject,
|
|
scriptData,
|
|
taskSketch,
|
|
taskScenes,
|
|
taskShotSketch,
|
|
taskVideos,
|
|
sketchCount,
|
|
isLoading,
|
|
currentStep,
|
|
currentSketchIndex,
|
|
isGeneratingSketch,
|
|
isGeneratingVideo,
|
|
currentLoadingText,
|
|
totalSketchCount,
|
|
roles,
|
|
music,
|
|
final,
|
|
dataLoadError,
|
|
setCurrentSketchIndex,
|
|
retryLoadData,
|
|
isPauseWorkFlow,
|
|
mode,
|
|
setIsPauseWorkFlow,
|
|
setAnyAttribute,
|
|
applyScript,
|
|
fallbackToStep,
|
|
originalText
|
|
} = useWorkflowData();
|
|
|
|
const {
|
|
isPlaying,
|
|
isVideoPlaying,
|
|
showControls,
|
|
setShowControls,
|
|
setIsPlaying,
|
|
togglePlay,
|
|
toggleVideoPlay,
|
|
playTimerRef,
|
|
} = usePlaybackControls(taskSketch, taskVideos, currentStep);
|
|
|
|
useEffect(() => {
|
|
console.log('changedIndex_work-flow', currentSketchIndex, taskObject);
|
|
}, [currentSketchIndex]);
|
|
|
|
// 模拟 AI 建议 英文
|
|
const mockSuggestions = [
|
|
"Refine scene transitions",
|
|
"Adjust scene composition",
|
|
"Improve character action design",
|
|
"Add environmental atmosphere",
|
|
"Adjust lens language"
|
|
];
|
|
|
|
const handleEditModalOpen = useCallback((tab: string) => {
|
|
setActiveEditTab(tab);
|
|
setIsEditModalOpen(true);
|
|
}, []);
|
|
|
|
const handleSuggestionClick = useCallback((suggestion: string) => {
|
|
console.log('Selected suggestion:', suggestion);
|
|
}, []);
|
|
|
|
const handleSubmit = useCallback((text: string) => {
|
|
console.log('Submitted text:', text);
|
|
}, []);
|
|
|
|
return (
|
|
<ErrorBoundary>
|
|
<div className="w-full overflow-hidden h-[calc(100vh-6rem)] absolute top-[4rem] left-0 right-0 px-[1rem]">
|
|
<div className="flex h-full flex-col justify-start items-center">
|
|
<div className="container-H2sRZG">
|
|
<div className="splashContainer-otuV_A">
|
|
<div className="content-vPGYx8">
|
|
<div className="info-UUGkPJ">
|
|
<ErrorBoundary>
|
|
<TaskInfo
|
|
isLoading={isLoading}
|
|
taskObject={taskObject}
|
|
currentLoadingText={currentLoadingText}
|
|
dataLoadError={dataLoadError}
|
|
roles={roles}
|
|
isPauseWorkFlow={isPauseWorkFlow}
|
|
/>
|
|
</ErrorBoundary>
|
|
</div>
|
|
</div>
|
|
<div className="media-Ocdu1O rounded-lg">
|
|
<div
|
|
className="videoContainer-qteKNi"
|
|
ref={containerRef}
|
|
>
|
|
{dataLoadError ? (
|
|
<motion.div
|
|
className="flex flex-col items-center justify-center w-full aspect-video rounded-lg bg-red-50 border-2 border-red-200"
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5 }}
|
|
>
|
|
<motion.div
|
|
className="flex items-center gap-3 mb-4"
|
|
initial={{ scale: 0.8 }}
|
|
animate={{ scale: 1 }}
|
|
transition={{ duration: 0.3, delay: 0.2 }}
|
|
>
|
|
<AlertCircle className="w-8 h-8 text-red-500" />
|
|
<h3 className="text-lg font-medium text-red-800">数据加载失败</h3>
|
|
</motion.div>
|
|
|
|
<p className="text-red-600 text-center mb-6 max-w-md px-4">
|
|
{dataLoadError}
|
|
</p>
|
|
|
|
<motion.button
|
|
className="flex items-center gap-2 px-6 py-3 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors"
|
|
onClick={() => retryLoadData?.()}
|
|
whileHover={{ scale: 1.05 }}
|
|
whileTap={{ scale: 0.95 }}
|
|
>
|
|
<RefreshCw className="w-4 h-4" />
|
|
重试加载
|
|
</motion.button>
|
|
</motion.div>
|
|
) : isLoading ? (
|
|
<Skeleton className="w-full aspect-video rounded-lg" />
|
|
) : (
|
|
<div className="heroVideo-FIzuK1" style={{ aspectRatio: "16 / 9" }} key={currentSketchIndex}>
|
|
<ErrorBoundary>
|
|
<MediaViewer
|
|
taskObject={taskObject}
|
|
scriptData={scriptData}
|
|
currentSketchIndex={currentSketchIndex}
|
|
taskSketch={taskSketch}
|
|
taskVideos={taskVideos}
|
|
isVideoPlaying={isVideoPlaying}
|
|
showControls={showControls}
|
|
isGeneratingSketch={isGeneratingSketch}
|
|
isGeneratingVideo={isGeneratingVideo}
|
|
onControlsChange={setShowControls}
|
|
onEditModalOpen={handleEditModalOpen}
|
|
onToggleVideoPlay={toggleVideoPlay}
|
|
onTogglePlay={togglePlay}
|
|
final={final}
|
|
setIsPauseWorkFlow={setIsPauseWorkFlow}
|
|
setAnyAttribute={setAnyAttribute}
|
|
isPauseWorkFlow={isPauseWorkFlow}
|
|
applyScript={applyScript}
|
|
mode={mode}
|
|
/>
|
|
</ErrorBoundary>
|
|
</div>
|
|
)}
|
|
</div>
|
|
{taskObject.currentStage !== 'final_video' && taskObject.currentStage !== 'script' && (
|
|
<div className="imageGrid-ymZV9z hide-scrollbar">
|
|
<ErrorBoundary>
|
|
<ThumbnailGrid
|
|
taskObject={taskObject}
|
|
isLoading={isLoading}
|
|
currentSketchIndex={currentSketchIndex}
|
|
taskSketch={taskSketch}
|
|
taskVideos={taskVideos}
|
|
isGeneratingSketch={isGeneratingSketch}
|
|
isGeneratingVideo={isGeneratingVideo}
|
|
sketchCount={sketchCount}
|
|
totalSketchCount={totalSketchCount}
|
|
onSketchSelect={setCurrentSketchIndex}
|
|
/>
|
|
</ErrorBoundary>
|
|
</div>
|
|
)}
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 暂停/播放按钮 */}
|
|
{
|
|
(taskObject.currentStage !== 'final_video') && (
|
|
<div className="absolute right-12 bottom-16 z-[49] flex gap-4">
|
|
<GlassIconButton
|
|
icon={isPauseWorkFlow ? Play : Pause}
|
|
size='lg'
|
|
tooltip={isPauseWorkFlow ? "Play" : "Pause"}
|
|
onClick={() => setIsPauseWorkFlow(!isPauseWorkFlow)}
|
|
/>
|
|
{ mode !== 'automatic' && (
|
|
<GlassIconButton
|
|
icon={ChevronLast}
|
|
size='lg'
|
|
tooltip="Next"
|
|
/>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
|
|
{/* AI 建议栏 */}
|
|
<ErrorBoundary>
|
|
<AISuggestionBar
|
|
suggestions={mockSuggestions}
|
|
onSuggestionClick={handleSuggestionClick}
|
|
onSubmit={handleSubmit}
|
|
onFocus={() => setIsPauseWorkFlow(true)}
|
|
placeholder="Please input your ideas, or click the predefined tags to receive AI advice..."
|
|
/>
|
|
</ErrorBoundary>
|
|
|
|
<ErrorBoundary>
|
|
<EditModal
|
|
isOpen={isEditModalOpen}
|
|
activeEditTab={activeEditTab}
|
|
onClose={() => {
|
|
SaveEditUseCase.clearData();
|
|
setIsEditModalOpen(false)
|
|
}}
|
|
currentSketchIndex={currentSketchIndex}
|
|
roles={taskObject.roles.data}
|
|
setIsPauseWorkFlow={setIsPauseWorkFlow}
|
|
isPauseWorkFlow={isPauseWorkFlow}
|
|
fallbackToStep={fallbackToStep}
|
|
originalText={originalText}
|
|
/>
|
|
</ErrorBoundary>
|
|
</div>
|
|
</ErrorBoundary>
|
|
)
|
|
});
|
|
|
|
export default WorkFlow;
|