video-flow-b/components/pages/work-flow.tsx
2025-07-14 03:56:30 +08:00

305 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import React, { useRef, useEffect } 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 } from "lucide-react";
import { motion } from "framer-motion";
export default function WorkFlow() {
const containerRef = useRef<HTMLDivElement>(null);
const [isEditModalOpen, setIsEditModalOpen] = React.useState(false);
const [activeEditTab, setActiveEditTab] = React.useState('1');
// 使用自定义 hooks 管理状态
const {
taskObject,
taskSketch,
taskVideos,
sketchCount,
isLoading,
currentStep,
currentSketchIndex,
isGeneratingSketch,
isGeneratingVideo,
currentLoadingText,
totalSketchCount,
roles,
music,
final,
dataLoadError,
setCurrentSketchIndex,
retryLoadData,
} = useWorkflowData();
const {
isPlaying,
isVideoPlaying,
showControls,
setShowControls,
setIsPlaying,
togglePlay,
toggleVideoPlay,
playTimerRef,
} = usePlaybackControls(taskSketch, taskVideos, currentStep);
// 跟踪是否已经自动开始播放过,避免重复触发
const hasAutoStartedRef = useRef(false);
// 跟踪循环播放的起始索引,用于判断是否完成一轮循环
const loopStartIndexRef = useRef<number | null>(null);
// 调试:监控关键状态变化
useEffect(() => {
console.log('工作流状态:', {
currentStep,
isGeneratingSketch,
isGeneratingVideo,
isPlaying,
taskSketchLength: taskSketch.length,
sketchCount,
totalSketchCount
});
}, [isGeneratingSketch, taskSketch.length, sketchCount, totalSketchCount, currentStep, isPlaying]);
// 专门监控isPlaying状态变化
useEffect(() => {
console.log('播放状态变化:', isPlaying ? '开始播放' : '停止播放');
}, [isPlaying]);
// 检查分镜数据
useEffect(() => {
if (taskSketch.length > 0) {
console.log('分镜数据:', `${taskSketch.length}个分镜,当前索引:${currentSketchIndex}`);
}
}, [taskSketch.length, currentSketchIndex]);
// 第一个分镜视频生成完成时停止循环播放并切换到第一个
useEffect(() => {
if (taskVideos.length === 1 && isPlaying) {
console.log('第一个分镜视频生成完成,停止循环播放并切换到第一个分镜');
setIsPlaying(false); // 停止循环播放
setCurrentSketchIndex(0); // 切换到第一个分镜
}
}, [taskVideos.length, isPlaying, setIsPlaying, setCurrentSketchIndex]);
// 分镜草图生成完毕后自动开始播放
useEffect(() => {
if (
!isGeneratingSketch && // 分镜草图生成完毕
taskSketch.length > 0 && // 有分镜草图数据
sketchCount === totalSketchCount && // 确保所有分镜草图都生成完毕
(currentStep === '1' || currentStep === '2') && // 允许在步骤1或步骤2初期触发
!hasAutoStartedRef.current && // 还没有自动开始过播放
!isPlaying // 当前没有播放
) {
console.log('所有分镜草图生成完毕,自动开始播放');
// 添加小延迟确保状态完全更新
setTimeout(() => {
hasAutoStartedRef.current = true;
setIsPlaying(true); // 自动开始播放
}, 500);
}
// 当切换到步骤3及以后时重置标记
if (Number(currentStep) >= 3) {
hasAutoStartedRef.current = false;
}
}, [isGeneratingSketch, taskSketch.length, sketchCount, totalSketchCount, currentStep, isPlaying, setIsPlaying]);
// 处理自动播放的分镜切换逻辑 仅循环一轮
useEffect(() => {
if (isPlaying && taskSketch.length > 0) {
console.log('开始自动切换分镜,总数:', taskSketch.length);
// 记录循环开始时的索引
if (loopStartIndexRef.current === null) {
loopStartIndexRef.current = currentSketchIndex;
console.log('记录循环起始索引:', currentSketchIndex);
}
const interval = setInterval(() => {
setCurrentSketchIndex((prev: number) => {
const nextIndex = (prev + 1) % taskSketch.length;
// 检查是否完成了一轮循环(回到起始索引)
if (nextIndex === loopStartIndexRef.current && prev !== loopStartIndexRef.current) {
console.log('完成一轮循环,停止自动播放');
setTimeout(() => {
setIsPlaying(false);
loopStartIndexRef.current = null; // 重置循环起始索引
}, 1000); // 延迟1秒停止让最后一个分镜显示完整
}
return nextIndex;
});
}, 1000);
return () => {
clearInterval(interval);
};
} else {
// 当停止播放时重置循环起始索引
loopStartIndexRef.current = null;
}
}, [isPlaying, taskSketch.length, setCurrentSketchIndex, currentSketchIndex]);
// 模拟 AI 建议 英文
const mockSuggestions = [
"Refine scene transitions",
"Adjust scene composition",
"Improve character action design",
"Add environmental atmosphere",
"Adjust lens language"
];
const handleEditModalOpen = (tab: string) => {
setActiveEditTab(tab);
setIsEditModalOpen(true);
};
const handleSuggestionClick = (suggestion: string) => {
console.log('Selected suggestion:', suggestion);
};
const handleSubmit = (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 p-6 justify-center items-center pt-0">
<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}
/>
</ErrorBoundary>
</div>
</div>
<div className="media-Ocdu1O">
<div
className="videoContainer-qteKNi"
style={currentStep !== '6' ? { flex: 3 } : {}}
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" }}>
<ErrorBoundary>
<MediaViewer
currentStep={currentStep}
currentSketchIndex={currentSketchIndex}
taskSketch={taskSketch}
taskVideos={taskVideos}
isVideoPlaying={isVideoPlaying}
isPlaying={isPlaying}
showControls={showControls}
isGeneratingSketch={isGeneratingSketch}
isGeneratingVideo={isGeneratingVideo}
onControlsChange={setShowControls}
onEditModalOpen={handleEditModalOpen}
onToggleVideoPlay={toggleVideoPlay}
onTogglePlay={togglePlay}
final={final}
/>
</ErrorBoundary>
</div>
)}
</div>
<div className="imageGrid-ymZV9z hide-scrollbar" style={{ display: currentStep === '6' ? 'none' : 'block' }}>
<ErrorBoundary>
<ThumbnailGrid
isLoading={isLoading}
isPlaying={isPlaying}
currentStep={currentStep}
currentSketchIndex={currentSketchIndex}
taskSketch={taskSketch}
taskVideos={taskVideos}
isGeneratingSketch={isGeneratingSketch}
isGeneratingVideo={isGeneratingVideo}
sketchCount={sketchCount}
totalSketchCount={totalSketchCount}
onSketchSelect={setCurrentSketchIndex}
/>
</ErrorBoundary>
</div>
</div>
</div>
</div>
</div>
{/* AI 建议栏 */}
<ErrorBoundary>
<AISuggestionBar
suggestions={mockSuggestions}
onSuggestionClick={handleSuggestionClick}
onSubmit={handleSubmit}
placeholder="Please input your ideas, or click the predefined tags to receive AI advice..."
/>
</ErrorBoundary>
<ErrorBoundary>
<EditModal
isOpen={isEditModalOpen}
activeEditTab={activeEditTab}
onClose={() => setIsEditModalOpen(false)}
taskStatus={taskObject?.taskStatus || '1'}
taskSketch={taskSketch}
sketchVideo={taskVideos}
currentSketchIndex={currentSketchIndex}
onSketchSelect={setCurrentSketchIndex}
roles={roles}
music={music}
/>
</ErrorBoundary>
</div>
</ErrorBoundary>
)
}