修复工作流阶段状态问题

This commit is contained in:
北枳 2025-08-16 13:31:37 +08:00
parent 2c4bb10a36
commit a5304f8077
13 changed files with 543 additions and 720 deletions

View File

@ -119,6 +119,8 @@ export interface SketchResponse {
total_count: number;
/** 草图数据列表 */
data: SketchData[];
/** 任务状态 */
task_status: string;
}
/**
*
@ -141,6 +143,8 @@ export interface CharacterData {
data: CharacterResponse[];
/** 总数 */
total_count: number;
/** 任务状态 */
task_status: string;
}
/**
* JSON结构接口
@ -191,6 +195,8 @@ export interface ShotSketchResponse {
data: ShotSketchData[];
/** 总数 */
total_count: number;
/** 任务状态 */
task_status: string;
}
/**
*
@ -261,6 +267,8 @@ export interface VideoData {
video_name_prefix: string;
/** 视频URL列表 */
urls: string[];
/** 视频状态 */
video_status: number;
}
/**
*
@ -275,6 +283,8 @@ export interface VideoResponse {
guid: string;
/** 项目ID */
project_id: string;
/** 任务状态 */
task_status: string;
}
/**
*
@ -313,6 +323,8 @@ export interface ProjectContentData {
video: VideoResponse;
/** 音乐数据 */
music: MusicData;
/** 粗剪视频 */
final_simple_video: FinalVideo;
/** 最终视频 */
final_video: FinalVideo;
/** 多语言视频 */
@ -608,3 +620,54 @@ export interface RoleResponse {
character_draft: string;
}
interface Role {
name: string;
url: string;
status: number;
}
interface Scene {
url: string;
script: string;
status: number;
}
interface ShotSketch {
url: string;
script: string;
status: number;
}
interface Video {
video_id: string;
urls: string[];
video_status: number;
}
export type Status = 'IN_PROGRESS' | 'COMPLETED' | 'FAILED';
export type Stage = 'script' | 'character' | 'scene' | 'shot_sketch' | 'video' | 'final_video';
// 添加 TaskObject 接口
export interface TaskObject {
title: string; // 标题
tags?: any[]; // 主题
currentStage: Stage; // 当前阶段
status: Status; // 状态
roles: {
data: Role[];
total_count: number;
}; // 角色
scenes: {
data: Scene[];
total_count: number;
}; // 场景
shot_sketch: {
data: ShotSketch[];
total_count: number;
}; // 分镜草图
videos: {
data: Video[];
total_count: number;
}; // 视频
final: {
url: string;
note: string;
}; // 剪辑视频
}

View File

@ -161,7 +161,7 @@
min-height: 0;
}
.videoContainer-qteKNi {
/* flex: 1; */
flex: 1;
min-height: 0;
display: flex;
position: relative;
@ -224,7 +224,7 @@
@media (height >= 880px) {
.imageGrid-ymZV9z {
flex: 1;
/* flex: 1; */
height: auto;
min-height: 0;
display: grid;

View File

@ -54,8 +54,7 @@ const WorkFlow = React.memo(function WorkFlow() {
setAnyAttribute,
applyScript,
fallbackToStep,
originalText,
currentStage
originalText
} = useWorkflowData();
const {
@ -69,36 +68,9 @@ const WorkFlow = React.memo(function WorkFlow() {
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
});
}, [currentStep, isGeneratingSketch, isGeneratingVideo, isPlaying, taskSketch.length, sketchCount, totalSketchCount]);
// 专门监控isPlaying状态变化
useEffect(() => {
console.log('播放状态变化:', isPlaying ? '开始播放' : '停止播放');
}, [isPlaying]);
// 检查分镜数据
useEffect(() => {
if (taskSketch.length > 0) {
console.log('分镜数据:', `${taskSketch.length}个分镜,当前索引:${currentSketchIndex}`);
}
}, [taskSketch.length, currentSketchIndex]);
console.log('changedIndex_work-flow', currentSketchIndex, taskObject);
}, [currentSketchIndex]);
// 模拟 AI 建议 英文
const mockSuggestions = [
@ -145,7 +117,6 @@ const WorkFlow = React.memo(function WorkFlow() {
<div className="media-Ocdu1O rounded-lg">
<div
className="videoContainer-qteKNi"
style={(currentStep !== '6' && currentStep !== '0') ? { flex: 3 } : {}}
ref={containerRef}
>
{dataLoadError ? (
@ -182,17 +153,15 @@ const WorkFlow = React.memo(function WorkFlow() {
) : isLoading ? (
<Skeleton className="w-full aspect-video rounded-lg" />
) : (
<div className="heroVideo-FIzuK1" style={{ aspectRatio: "16 / 9" }}>
<div className="heroVideo-FIzuK1" style={{ aspectRatio: "16 / 9" }} key={currentSketchIndex}>
<ErrorBoundary>
<MediaViewer
currentStage={currentStage}
taskObject={taskObject}
scriptData={scriptData}
currentStep={currentStep}
currentSketchIndex={currentSketchIndex}
taskSketch={taskSketch}
taskVideos={taskVideos}
isVideoPlaying={isVideoPlaying}
isPlaying={isPlaying}
showControls={showControls}
isGeneratingSketch={isGeneratingSketch}
isGeneratingVideo={isGeneratingVideo}
@ -211,14 +180,12 @@ const WorkFlow = React.memo(function WorkFlow() {
</div>
)}
</div>
{currentStage !== 'final_video' && currentStage !== 'script' && (
{taskObject.currentStage !== 'final_video' && taskObject.currentStage !== 'script' && (
<div className="imageGrid-ymZV9z hide-scrollbar">
<ErrorBoundary>
<ThumbnailGrid
currentStage={currentStage}
taskObject={taskObject}
isLoading={isLoading}
isPlaying={isPlaying}
currentStep={currentStep}
currentSketchIndex={currentSketchIndex}
taskSketch={taskSketch}
taskVideos={taskVideos}
@ -239,7 +206,7 @@ const WorkFlow = React.memo(function WorkFlow() {
{/* 暂停/播放按钮 */}
{
(currentStage !== 'final_video' && currentStage !== 'script') && (
(taskObject.currentStage !== 'final_video') && (
<div className="absolute right-12 bottom-16 z-[49] flex gap-4">
<GlassIconButton
icon={isPauseWorkFlow ? Play : Pause}
@ -278,14 +245,8 @@ const WorkFlow = React.memo(function WorkFlow() {
SaveEditUseCase.clearData();
setIsEditModalOpen(false)
}}
taskStatus={taskObject?.taskStatus || '1'}
taskSketch={taskSketch}
sketchVideo={taskVideos}
taskScenes={taskScenes}
currentSketchIndex={currentSketchIndex}
onSketchSelect={setCurrentSketchIndex}
roles={roles}
music={music}
roles={taskObject.roles.data}
setIsPauseWorkFlow={setIsPauseWorkFlow}
isPauseWorkFlow={isPauseWorkFlow}
fallbackToStep={fallbackToStep}

View File

@ -8,15 +8,15 @@ import { GlassIconButton } from '@/components/ui/glass-icon-button';
import { ScriptRenderer } from '@/components/script-renderer/ScriptRenderer';
import { mockScriptData } from '@/components/script-renderer/mock';
import { Skeleton } from '@/components/ui/skeleton';
import { TaskObject } from '@/api/DTO/movieEdit';
interface MediaViewerProps {
taskObject: TaskObject;
scriptData: any;
currentStep: string;
currentSketchIndex: number;
taskSketch: any[];
taskVideos: any[];
isVideoPlaying: boolean;
isPlaying: boolean;
showControls: boolean;
isGeneratingSketch: boolean;
isGeneratingVideo: boolean;
@ -30,17 +30,15 @@ interface MediaViewerProps {
isPauseWorkFlow: boolean;
applyScript: any;
mode: string;
currentStage: string;
}
export const MediaViewer = React.memo(function MediaViewer({
taskObject,
scriptData,
currentStep,
currentSketchIndex,
taskSketch,
taskVideos,
isVideoPlaying,
isPlaying,
showControls,
isGeneratingSketch,
isGeneratingVideo,
@ -53,8 +51,7 @@ export const MediaViewer = React.memo(function MediaViewer({
setAnyAttribute,
isPauseWorkFlow,
applyScript,
mode,
currentStage
mode
}: MediaViewerProps) {
const mainVideoRef = useRef<HTMLVideoElement>(null);
const finalVideoRef = useRef<HTMLVideoElement>(null);
@ -150,26 +147,26 @@ export const MediaViewer = React.memo(function MediaViewer({
// 使用 useMemo 缓存最终视频元素,避免重复创建和请求
const memoizedFinalVideoElement = useMemo(() => {
console.log('final', final);
if (!final?.url) return null;
console.log('final', taskObject.final);
if (!taskObject.final?.url) return null;
return (
<video
ref={finalVideoRef}
className="w-full h-full object-cover rounded-lg"
src={final.url}
src={taskObject.final.url}
autoPlay={isFinalVideoPlaying}
loop
playsInline
preload="metadata"
poster={`${final.url}?vframe/jpg/offset/1`}
poster={`${taskObject.final.url}?vframe/jpg/offset/1`}
onLoadedData={handleFinalVideoLoaded}
onPlay={() => setIsFinalVideoPlaying(true)}
onPause={() => setIsFinalVideoPlaying(false)}
onClick={handleVideoClick}
/>
);
}, [final?.url, isFinalVideoPlaying, handleFinalVideoLoaded, handleVideoClick]);
}, [taskObject.final?.url, isFinalVideoPlaying, handleFinalVideoLoaded, handleVideoClick]);
// 包装编辑按钮点击事件
const handleEditClick = (tab: string, from?: string) => {
@ -308,16 +305,13 @@ export const MediaViewer = React.memo(function MediaViewer({
);
// 渲染最终成片
const renderFinalVideo = (currentStep: string) => {
const renderFinalVideo = () => {
// 使用真实的final数据如果没有则使用默认值
const finalVideo = final || {
url: 'https://cdn.qikongjian.com/videos/1750389908_37d4fffa-8516-43a3-a423-fc0274f40e8a_text_to_video_0.mp4'
};
return (
<div
className="relative w-full h-full rounded-lg overflow-hidden"
key={`render-video-${currentStep}`}
key={`render-video-${taskObject.final.note}`}
onMouseEnter={() => onControlsChange(true)}
onMouseLeave={() => onControlsChange(false)}
>
@ -331,7 +325,7 @@ export const MediaViewer = React.memo(function MediaViewer({
>
<video
className="w-full h-full rounded-lg object-cover object-center"
src={taskVideos[currentSketchIndex]?.url}
src={taskObject.final.url}
loop
playsInline
muted
@ -391,7 +385,7 @@ export const MediaViewer = React.memo(function MediaViewer({
ease: "easeInOut"
}}
/>
<span className="text-sm font-medium text-white/90">{currentStep === '6' ? 'Final product' : 'Trailer Video'}</span>
<span className="text-sm font-medium text-white/90">{taskObject.final.note === 'final' ? 'Final product' : 'Trailer Video'}</span>
</div>
</div>
</motion.div>
@ -409,21 +403,6 @@ export const MediaViewer = React.memo(function MediaViewer({
whileTap={{ scale: 0.9 }}
className="relative"
>
{/* 播放时的发光效果 */}
{isFinalVideoPlaying && (
<motion.div
className="absolute inset-0 rounded-full blur-md"
animate={{
scale: [1, 1.2, 1],
opacity: [0.5, 0.8, 0.5]
}}
transition={{
duration: 2,
repeat: Infinity,
ease: "easeInOut"
}}
/>
)}
<GlassIconButton
icon={isFinalVideoPlaying ? Pause : Play}
tooltip={isFinalVideoPlaying ? "Pause video" : "Play video"}
@ -461,10 +440,6 @@ export const MediaViewer = React.memo(function MediaViewer({
// 渲染视频内容
const renderVideoContent = () => {
const currentSketch = taskSketch[currentSketchIndex];
const defaultBgColors = ['RGB(45, 50, 70)', 'RGB(75, 80, 100)', 'RGB(105, 110, 130)'];
const bgColors = currentSketch?.bg_rgb || defaultBgColors;
return (
<div
className="relative w-full h-full rounded-lg"
@ -476,8 +451,8 @@ export const MediaViewer = React.memo(function MediaViewer({
{/* 背景模糊的图片 */}
<div className="absolute inset-0 overflow-hidden" style={{background: `url(${taskSketch[currentSketchIndex]?.url}) no-repeat center center`}}>
{/* 生成中 */}
{taskVideos[currentSketchIndex].video_status === 0 && (
<div className="absolute inset-0 bg-black/50 flex items-center justify-center">
{taskObject.videos.data[currentSketchIndex].video_status === 0 && (
<div className="absolute inset-0 bg-black/10 flex items-center justify-center">
<div className="text-blue-500 text-2xl font-bold flex items-center gap-2">
<Loader2 className="w-10 h-10 animate-spin" />
<span>Generating...</span>
@ -485,8 +460,8 @@ export const MediaViewer = React.memo(function MediaViewer({
</div>
)}
{/* 生成失败 */}
{taskVideos[currentSketchIndex].video_status === 2 && (
<div className="absolute inset-0 bg-red-500 flex items-center justify-center">
{taskObject.videos.data[currentSketchIndex].video_status === 2 && (
<div className="absolute inset-0 bg-red-500/10 flex items-center justify-center">
<div className="text-red-500 text-2xl font-bold flex items-center gap-2">
<X className="w-10 h-10" />
<span>Failed</span>
@ -496,7 +471,7 @@ export const MediaViewer = React.memo(function MediaViewer({
</div>
{/* 视频 多个 取第一个 */}
{ taskVideos[currentSketchIndex].url && (
{ taskObject.videos.data[currentSketchIndex].urls && (
<motion.div
initial={{ clipPath: "inset(0 100% 0 0)" }}
animate={{ clipPath: "inset(0 0% 0 0)" }}
@ -505,9 +480,9 @@ export const MediaViewer = React.memo(function MediaViewer({
>
<video
ref={mainVideoRef}
key={taskVideos[currentSketchIndex].url[0]}
key={taskObject.videos.data[currentSketchIndex].urls[0]}
className="w-full h-full rounded-lg object-cover object-center relative z-10"
src={taskVideos[currentSketchIndex].url[0]}
src={taskObject.videos.data[currentSketchIndex].urls[0]}
autoPlay={isVideoPlaying}
loop={true}
playsInline
@ -543,7 +518,7 @@ export const MediaViewer = React.memo(function MediaViewer({
</AnimatePresence>
{/* 底部控制区域 */}
{ taskVideos[currentSketchIndex] && (
{ taskObject.videos.data[currentSketchIndex] && (
<AnimatePresence>
<motion.div
className="absolute bottom-4 left-4 z-[11] flex items-center gap-3"
@ -576,10 +551,9 @@ export const MediaViewer = React.memo(function MediaViewer({
};
// 渲染分镜草图
const renderSketchContent = () => {
const currentSketch = taskSketch[currentSketchIndex];
const renderSketchContent = (currentSketch: any) => {
const defaultBgColors = ['RGB(45, 50, 70)', 'RGB(75, 80, 100)', 'RGB(105, 110, 130)'];
const bgColors = currentSketch?.bg_rgb || defaultBgColors;
const bgColors = defaultBgColors;
return (
<div
@ -587,107 +561,25 @@ export const MediaViewer = React.memo(function MediaViewer({
onMouseEnter={() => onControlsChange(true)}
onMouseLeave={() => onControlsChange(false)}
>
{/* 状态 */}
{currentSketch.status === 0 && (
<div className="absolute inset-0 bg-black/10 flex items-center justify-center">
<div className="text-blue-500 text-2xl font-bold flex items-center gap-2">
<Loader2 className="w-10 h-10 animate-spin" />
<span>Generating...</span>
</div>
</div>
)}
{currentSketch.status === 2 && (
<div className="absolute inset-0 bg-red-500/10 flex items-center justify-center">
<div className="text-red-500 text-2xl font-bold flex items-center gap-2">
<X className="w-10 h-10" />
<span>Failed</span>
</div>
</div>
)}
{/* 只在生成过程中或没有分镜图片时使用ProgressiveReveal */}
{(isGeneratingSketch || !currentSketch) ? (
currentSketch ? (
<ProgressiveReveal
key={`sketch-generating-${currentSketchIndex}`}
className="w-full h-full rounded-lg"
revealDuration={0.8}
blurDuration={0.3}
initialBlur={10}
customVariants={{
hidden: {
opacity: 0,
scale: 0.9,
filter: "blur(30px)"
},
visible: {
opacity: 1,
scale: 1,
filter: "blur(0px)",
transition: {
duration: 1.5,
ease: [0.23, 1, 0.32, 1],
opacity: { duration: 0.8, ease: "easeOut" },
scale: { duration: 1.2, ease: "easeOut" },
filter: { duration: 0.8, delay: 0.4, ease: "easeOut" }
}
}
}}
loadingBgConfig={{
fromColor: `from-[${bgColors[0]}]`,
viaColor: `via-[${bgColors[1]}]`,
toColor: `to-[${bgColors[2]}]`,
glowOpacity: 0.8,
duration: 4,
}}
>
<img
key={currentSketchIndex}
src={currentSketch.url}
alt={`Sketch ${currentSketchIndex + 1}`}
className="w-full h-full rounded-lg object-cover"
/>
</ProgressiveReveal>
) : (
<div className="w-full h-full flex items-center justify-center rounded-lg overflow-hidden relative">
{/* 动态渐变背景 */}
<motion.div
className={`absolute inset-0 bg-gradient-to-r from-[${bgColors[0]}] via-[${bgColors[1]}] to-[${bgColors[2]}]`}
animate={{
backgroundPosition: ["0% 50%", "100% 50%", "0% 50%"],
}}
transition={{
duration: 5,
repeat: Infinity,
ease: "linear"
}}
style={{
backgroundSize: "200% 200%",
}}
/>
{/* 动态光效 */}
<motion.div
className="absolute inset-0 opacity-50"
style={{
background: "radial-gradient(circle at center, rgba(255,255,255,0.8) 0%, transparent 50%)",
}}
animate={{
scale: [1, 1.2, 1],
}}
transition={{
duration: 2,
repeat: Infinity,
ease: "easeInOut"
}}
/>
<motion.div
className="flex flex-col items-center gap-4 relative z-10"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<div className="relative">
<motion.div
className="absolute -inset-4 bg-gradient-to-r from-white via-sky-200 to-cyan-200 rounded-full opacity-60 blur-xl"
animate={{
scale: [1, 1.2, 1],
rotate: [0, 180, 360],
}}
transition={{
duration: 4,
repeat: Infinity,
ease: "linear"
}}
/>
</div>
</motion.div>
</div>
)
) : (
/* 生成完成后直接显示图片不使用ProgressiveReveal */
{currentSketch.status === 1 && (
<img
key={currentSketchIndex}
src={currentSketch.url}
@ -729,28 +621,7 @@ export const MediaViewer = React.memo(function MediaViewer({
whileTap={{ scale: 0.9 }}
className="relative"
>
{/* 播放时的发光效果 */}
{isPlaying && (
<motion.div
className="absolute inset-0 rounded-full bg-blue-500/30 blur-md"
animate={{
scale: [1, 1.2, 1],
opacity: [0.5, 0.8, 0.5]
}}
transition={{
duration: 2,
repeat: Infinity,
ease: "easeInOut"
}}
/>
)}
<GlassIconButton
icon={isPlaying ? Pause : Play}
tooltip={isPlaying ? "Pause auto play" : "Start auto play"}
onClick={onTogglePlay}
size="sm"
className={isPlaying ? "border-blue-500/50 bg-blue-500/10" : ""}
/>
</motion.div>
</motion.div>
</AnimatePresence>
@ -790,17 +661,25 @@ export const MediaViewer = React.memo(function MediaViewer({
};
// 根据当前步骤渲染对应内容
if (currentStage === 'final_video') {
return renderFinalVideo(currentStep);
if (taskObject.currentStage === 'final_video') {
return renderFinalVideo();
}
if (currentStage === 'video') {
if (taskObject.currentStage === 'video') {
return renderVideoContent();
}
if (currentStage === 'script') {
if (taskObject.currentStage === 'script') {
return renderScriptContent();
}
return renderSketchContent();
if (taskObject.currentStage === 'scene') {
return renderSketchContent(taskObject.scenes.data[currentSketchIndex]);
}
if (taskObject.currentStage === 'shot_sketch') {
return renderSketchContent(taskObject.shot_sketch.data[currentSketchIndex]);
}
return renderSketchContent(taskObject.scenes.data[currentSketchIndex]);
});

View File

@ -158,7 +158,7 @@ export function TaskInfo({
setCurrentStage(3);
console.log('isScriptModalOpen-Post-production', currentLoadingText, isScriptModalOpen);
timerRef.current = setTimeout(() => {
setIsScriptModalOpen(true);
// setIsScriptModalOpen(true);
}, 8000);
}
if (currentLoadingText.includes('video') && !currentLoadingText.includes('Post-production')) {
@ -214,7 +214,7 @@ export function TaskInfo({
}
if (currentLoadingText.includes('initializing')) {
console.log('isScriptModalOpen-initializing', currentLoadingText, isScriptModalOpen);
setIsScriptModalOpen(true);
// setIsScriptModalOpen(true);
setCurrentStage(0);
}
return () => {

View File

@ -1,15 +1,15 @@
'use client';
import React, { useRef, useEffect, useState } from 'react';
import React, { useRef, useEffect, useState, useCallback } from 'react';
import { motion } from 'framer-motion';
import { Skeleton } from '@/components/ui/skeleton';
import { ProgressiveReveal, presets } from '@/components/ui/progressive-reveal';
import { Loader2, X } from 'lucide-react';
import { TaskObject } from '@/api/DTO/movieEdit';
interface ThumbnailGridProps {
taskObject: TaskObject;
isLoading: boolean;
isPlaying: boolean;
currentStep: string;
currentSketchIndex: number;
taskSketch: any[];
taskVideos: any[];
@ -18,13 +18,11 @@ interface ThumbnailGridProps {
sketchCount: number;
totalSketchCount: number;
onSketchSelect: (index: number) => void;
currentStage: string;
}
export function ThumbnailGrid({
taskObject,
isLoading,
isPlaying,
currentStep,
currentSketchIndex,
taskSketch,
taskVideos,
@ -32,8 +30,7 @@ export function ThumbnailGrid({
isGeneratingVideo,
sketchCount,
totalSketchCount,
onSketchSelect,
currentStage
onSketchSelect
}: ThumbnailGridProps) {
const thumbnailsRef = useRef<HTMLDivElement>(null);
const [isDragging, setIsDragging] = useState(false);
@ -43,7 +40,7 @@ export function ThumbnailGrid({
// 监听当前选中索引变化,自动滚动到对应位置
useEffect(() => {
if (thumbnailsRef.current && taskSketch.length > 0) {
if (thumbnailsRef.current) {
const container = thumbnailsRef.current;
const thumbnailWidth = container.offsetWidth / 4; // 每个缩略图宽度(包含间距)
const scrollPosition = currentSketchIndex * thumbnailWidth;
@ -53,39 +50,97 @@ export function ThumbnailGrid({
behavior: 'smooth'
});
}
}, [currentSketchIndex, taskSketch.length]);
}, [currentSketchIndex]);
// 处理键盘左右键事件
// 获取当前阶段的数据数组
const getCurrentData = useCallback(() => {
if (taskObject.currentStage === 'video') {
return taskObject.videos.data;
} else if (taskObject.currentStage === 'scene' || taskObject.currentStage === 'character') {
return taskObject.scenes.data;
} else if (taskObject.currentStage === 'shot_sketch') {
return taskObject.shot_sketch.data;
}
return [];
}, [taskObject.currentStage, taskObject.videos.data, taskObject.scenes.data, taskObject.shot_sketch.data]);
// 使用 useRef 存储前一次的数据,避免触发重渲染
const prevDataRef = useRef<any[]>([]);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
// 只在元素被聚焦时处理键盘事件
if (!isFocused) return;
const isVideoPhase = Number(currentStep) > 2 && taskVideos.length > 0 && Number(currentStep) < 6;
const maxIndex = isVideoPhase ? taskVideos.length - 1 : taskSketch.length - 1;
const currentData = getCurrentData();
if (currentData && currentData.length > 0) {
const currentDataStr = JSON.stringify(currentData);
const prevDataStr = JSON.stringify(prevDataRef.current);
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
e.preventDefault();
let newIndex = currentSketchIndex;
// 只有当数据真正发生变化时才进行处理
if (currentDataStr !== prevDataStr) {
// 找到最新更新的数据项的索引
const changedIndex = currentData.findIndex((item, index) => {
// 检查是否是新增的数据
if (index >= prevDataRef.current.length) return true;
// 检查数据是否发生变化(包括状态变化)
return JSON.stringify(item) !== JSON.stringify(prevDataRef.current[index]);
});
if (e.key === 'ArrowLeft') {
// 向左循环
newIndex = currentSketchIndex === 0 ? maxIndex : currentSketchIndex - 1;
} else {
// 向右循环
newIndex = currentSketchIndex === maxIndex ? 0 : currentSketchIndex + 1;
console.log('changedIndex_thumbnail-grid', changedIndex, 'currentData:', currentData, 'prevData:', prevDataRef.current);
// 如果找到变化的项,自动选择该项
if (changedIndex !== -1) {
onSketchSelect(changedIndex);
}
onSketchSelect(newIndex);
// 更新前一次的数据快照
prevDataRef.current = JSON.parse(JSON.stringify(currentData));
}
};
}
}, [taskObject, getCurrentData, onSketchSelect]);
// 处理键盘左右键事件
const handleKeyDown = useCallback((e: KeyboardEvent) => {
const currentData = getCurrentData();
const maxIndex = currentData.length - 1;
console.log('handleKeyDown', maxIndex, 'isFocused:', isFocused);
if ((e.key === 'ArrowLeft' || e.key === 'ArrowRight') && maxIndex >= 0) {
e.preventDefault();
let newIndex = currentSketchIndex;
if (e.key === 'ArrowLeft') {
// 向左循环
newIndex = currentSketchIndex === 0 ? maxIndex : currentSketchIndex - 1;
} else {
// 向右循环
newIndex = currentSketchIndex === maxIndex ? 0 : currentSketchIndex + 1;
}
console.log('切换索引:', currentSketchIndex, '->', newIndex, '最大索引:', maxIndex);
onSketchSelect(newIndex);
}
}, [isFocused, currentSketchIndex, onSketchSelect, getCurrentData]);
// 监听键盘事件
useEffect(() => {
// 组件挂载时自动聚焦
if (thumbnailsRef.current) {
thumbnailsRef.current.focus();
}
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [currentStep, currentSketchIndex, taskSketch.length, taskVideos.length, onSketchSelect, isFocused]);
}, [handleKeyDown]);
// 确保在数据变化时保持焦点
useEffect(() => {
if (thumbnailsRef.current && !isFocused) {
thumbnailsRef.current.focus();
}
}, [taskObject.currentStage, isFocused]);
// 处理鼠标/触摸拖动事件
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
// 阻止默认的拖拽行为
e.preventDefault();
setIsDragging(true);
setStartX(e.pageX - thumbnailsRef.current!.offsetLeft);
setScrollLeft(thumbnailsRef.current!.scrollLeft);
@ -102,22 +157,13 @@ export function ThumbnailGrid({
const handleMouseUp = (e: React.MouseEvent<HTMLDivElement>) => {
setIsDragging(false);
if (!isDragging) return;
const container = thumbnailsRef.current!;
const thumbnailWidth = container.offsetWidth / 4;
const currentScroll = container.scrollLeft;
const nearestIndex = Math.round(currentScroll / thumbnailWidth);
// 只有在拖动距离较小时才触发选中
const x = e.pageX - container.offsetLeft;
const walk = Math.abs(x - startX);
if (walk < 10) {
return; // 如果拖动距离太小,保持原有的点击选中逻辑
}
onSketchSelect(Math.min(Math.max(0, nearestIndex), taskSketch.length - 1));
};
// 监听阶段变化
useEffect(() => {
console.log('taskObject.currentStage_thumbnail-grid', taskObject.currentStage);
}, [taskObject.currentStage]);
// 渲染加载状态
if (isLoading) {
return (
@ -131,7 +177,7 @@ export function ThumbnailGrid({
}
// 粗剪/精剪最终成片阶段不显示缩略图
if (Number(currentStep) === 5.5 || Number(currentStep) === 6) {
if (taskObject.currentStage === 'final_video') {
return null;
}
@ -203,9 +249,7 @@ export function ThumbnailGrid({
// 渲染视频阶段的缩略图
const renderVideoThumbnails = () => (
taskVideos.map((video, index) => {
const defaultBgColors = ['RGB(45, 50, 70)', 'RGB(75, 80, 100)', 'RGB(105, 110, 130)'];
const bgColors = defaultBgColors;
taskObject.videos.data.map((video, index) => {
return (
<div
@ -217,16 +261,16 @@ export function ThumbnailGrid({
{/* 视频层 */}
<div className="relative w-full h-full transform hover:scale-105 transition-transform duration-500">
{taskVideos[index].video_status === 0 && (
<div className="absolute inset-0 bg-black/50 flex items-center justify-center z-20">
{taskObject.videos.data[index].video_status === 0 && (
<div className="absolute inset-0 bg-black/10 flex items-center justify-center z-20">
<div className="text-blue-500 text-xl font-bold flex items-center gap-2">
<Loader2 className="w-10 h-10 animate-spin" />
<span>Generating...</span>
</div>
</div>
)}
{taskVideos[index].video_status === 2 && (
<div className="absolute inset-0 bg-red-500 flex items-center justify-center z-20">
{taskObject.videos.data[index].video_status === 2 && (
<div className="absolute inset-0 bg-red-500/10 flex items-center justify-center z-20">
<div className="text-red-500 text-xl font-bold flex items-center gap-2">
<X className="w-10 h-10" />
<span>Failed</span>
@ -234,10 +278,10 @@ export function ThumbnailGrid({
</div>
)}
{taskVideos[index].url ? (
{taskObject.videos.data[index].urls ? (
<video
className="w-full h-full object-cover"
src={taskVideos[index].url[0]}
src={taskObject.videos.data[index].urls[0]}
playsInline
loop
muted
@ -245,11 +289,12 @@ export function ThumbnailGrid({
) : (
<div className="w-full h-full transform hover:scale-105 transition-transform duration-500">
<img
className={`w-full h-full object-cover transition-all duration-300 ${
(!taskSketch[index] && !isPlaying) ? 'filter blur-sm opacity-60' : ''
className={`w-full h-full object-cover transition-all duration-300 select-none ${
(!taskObject.shot_sketch.data[index]) ? 'filter blur-sm opacity-60' : ''
}`}
src={taskSketch[index] ? taskSketch[index].url : video.url}
src={taskObject.shot_sketch.data[index] ? taskObject.shot_sketch.data[index].url : video.urls[0]}
alt={`Thumbnail ${index + 1}`}
draggable="false"
/>
</div>
)}
@ -265,11 +310,9 @@ export function ThumbnailGrid({
);
// 渲染分镜草图阶段的缩略图
const renderSketchThumbnails = () => (
const renderSketchThumbnails = (sketchData: any[]) => (
<>
{taskSketch.map((sketch, index) => {
const defaultBgColors = ['RGB(45, 50, 70)', 'RGB(75, 80, 100)', 'RGB(105, 110, 130)'];
const bgColors = sketch?.bg_rgb || defaultBgColors;
{sketchData.map((sketch, index) => {
return (
<div
@ -278,56 +321,32 @@ export function ThumbnailGrid({
${currentSketchIndex === index ? 'ring-2 ring-blue-500 z-10' : 'hover:ring-2 hover:ring-blue-500/50'}`}
onClick={() => !isDragging && onSketchSelect(index)}
>
{/* 只在生成过程中或没有分镜图片时使用ProgressiveReveal */}
{(isGeneratingSketch || !sketch) ? (
<ProgressiveReveal
key={`sketch-thumbnail-generating-${index}`}
revealDuration={0.8}
blurDuration={0.3}
initialBlur={10}
delay={index === currentSketchIndex ? 0 : index * 0.1}
customVariants={{
hidden: {
opacity: 0,
scale: 0.95,
filter: "blur(10px)"
},
visible: {
opacity: 1,
scale: 1,
filter: "blur(0px)",
transition: {
duration: 0.8,
ease: [0.23, 1, 0.32, 1],
opacity: { duration: 0.6, ease: "easeInOut" },
scale: { duration: 1, ease: "easeOut" },
filter: { duration: 0.8, ease: "easeOut", delay: 0.2 }
}
}
}}
loadingBgConfig={{
fromColor: `from-[${bgColors[0]}]`,
viaColor: `via-[${bgColors[1]}]`,
toColor: `to-[${bgColors[2]}]`,
glowOpacity: 0.4,
duration: 4
}}
>
<div className="w-full h-full transform hover:scale-105 transition-transform duration-500">
<img
className="w-full h-full object-cover"
src={sketch.url}
alt={`NG ${index + 1}`}
/>
{/* 状态 */}
{sketch.status === 0 && (
<div className="absolute inset-0 bg-black/10 flex items-center justify-center">
<div className="text-blue-500 text-xl font-bold flex items-center gap-2">
<Loader2 className="w-10 h-10 animate-spin" />
<span>Generating...</span>
</div>
</ProgressiveReveal>
) : (
/* 生成完成后直接显示不使用ProgressiveReveal */
</div>
)}
{sketch.status === 2 && (
<div className="absolute inset-0 bg-red-500/10 flex items-center justify-center">
<div className="text-red-500 text-xl font-bold flex items-center gap-2">
<X className="w-10 h-10" />
<span>Failed</span>
</div>
</div>
)}
{/* 只在生成过程中或没有分镜图片时使用ProgressiveReveal */}
{(sketch.status === 1) && (
<div className="w-full h-full transform hover:scale-105 transition-transform duration-500">
<img
className="w-full h-full object-cover"
className="w-full h-full object-cover select-none"
src={sketch.url}
alt={`NG ${index + 1}`}
draggable="false"
/>
</div>
)}
@ -345,7 +364,8 @@ export function ThumbnailGrid({
<div
ref={thumbnailsRef}
tabIndex={0}
className="w-full grid grid-flow-col auto-cols-[20%] gap-4 overflow-x-auto hide-scrollbar px-1 py-1 cursor-grab active:cursor-grabbing focus:outline-none"
className="w-full grid grid-flow-col auto-cols-[20%] gap-4 overflow-x-auto hide-scrollbar px-1 py-1 cursor-grab active:cursor-grabbing focus:outline-none select-none"
autoFocus
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
@ -353,10 +373,10 @@ export function ThumbnailGrid({
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
>
{currentStage === 'video'
? renderVideoThumbnails()
: renderSketchThumbnails()
}
{taskObject.currentStage === 'video' && renderVideoThumbnails()}
{taskObject.currentStage === 'scene' && renderSketchThumbnails(taskObject.scenes.data)}
{taskObject.currentStage === 'shot_sketch' && renderSketchThumbnails(taskObject.shot_sketch.data)}
{taskObject.currentStage === 'character' && renderSketchThumbnails(taskObject.scenes.data)}
</div>
);
}

View File

@ -1,12 +1,13 @@
'use client';
import { useState, useEffect, useCallback } from 'react';
import { useState, useEffect, useCallback, useRef } from 'react';
import { useSearchParams } from 'next/navigation';
import { detailScriptEpisodeNew, getScriptTitle, getRunningStreamData, pauseMovieProjectPlan, resumeMovieProjectPlan } from '@/api/video_flow';
import { useAppDispatch, useAppSelector } from '@/lib/store/hooks';
import { setSketchCount, setVideoCount } from '@/lib/store/workflowSlice';
import { useScriptService } from "@/app/service/Interaction/ScriptService";
import { useUpdateEffect } from '@/app/hooks/useUpdateEffect';
import { TaskObject, Status, Stage } from '@/api/DTO/movieEdit';
// 步骤映射
const STEP_MAP = {
@ -22,14 +23,14 @@ const LOADING_TEXT_MAP = {
initializing: 'initializing...',
script: 'Generating script...',
getSketchStatus: 'Getting sketch status...',
sketch: (count: number, total: number) => `Generating sketch ${count + 1 > total ? total : count + 1}/${total}...`,
sketch: (count: number, total: number) => `Generating sketch ${count}/${total}...`,
sketchComplete: 'Sketch generation complete',
character: 'Drawing characters...',
newCharacter: (count: number, total: number) => `Drawing character ${count + 1 > total ? total : count + 1}/${total}...`,
newCharacter: (count: number, total: number) => `Drawing character ${count}/${total}...`,
getShotSketchStatus: 'Getting shot sketch status...',
shotSketch: (count: number, total: number) => `Generating shot sketch ${count + 1 > total ? total : count + 1}/${total}...`,
shotSketch: (count: number, total: number) => `Generating shot sketch ${count}/${total}...`,
getVideoStatus: 'Getting video status...',
video: (count: number, total: number) => `Generating video ${count + 1 > total ? total : count + 1}/${total}...`,
video: (count: number, total: number) => `Generating video ${count}/${total}...`,
videoComplete: 'Video generation complete',
audio: 'Generating background audio...',
postProduction: (step: string) => `Post-production: ${step}...`,
@ -39,20 +40,6 @@ const LOADING_TEXT_MAP = {
type ApiStep = keyof typeof STEP_MAP;
// 添加 TaskObject 接口
interface TaskObject {
taskStatus: string;
title: string;
currentLoadingText: string;
sketchCount?: number;
totalSketchCount?: number;
isGeneratingSketch?: boolean;
isGeneratingVideo?: boolean;
roles?: any[];
music?: any[];
final?: any;
tags?: any[];
}
export function useWorkflowData() {
console.log('98877766777777888888990')
@ -60,8 +47,37 @@ export function useWorkflowData() {
const searchParams = useSearchParams();
const episodeId = searchParams.get('episodeId') || '';
let tempTaskObject = useRef<TaskObject>({
title: '',
tags: [],
currentStage: 'script',
status: 'IN_PROGRESS' as Status,
roles: {
data: [],
total_count: -1
},
scenes: {
data: [],
total_count: -1
},
shot_sketch: {
data: [],
total_count: -1
},
videos: {
data: [],
total_count: -1
},
final: {
url: '',
note: ''
}
});
let loadingText: any = useRef(LOADING_TEXT_MAP.initializing);
// 更新 taskObject 的类型
const [taskObject, setTaskObject] = useState<TaskObject | null>(null);
const [taskObject, setTaskObject] = useState<TaskObject>(tempTaskObject.current);
const [originalText, setOriginalText] = useState<string>('');
const [scriptData, setScriptData] = useState<any>(null);
const [taskSketch, setTaskSketch] = useState<any[]>([]);
@ -82,25 +98,11 @@ export function useWorkflowData() {
const [needStreamData, setNeedStreamData] = useState(false);
const [isPauseWorkFlow, setIsPauseWorkFlow] = useState(false);
const [mode, setMode] = useState<'automatic' | 'manual' | 'auto'>('automatic');
const [currentStage, setCurrentStage] = useState<'script' | 'character' | 'sketch' | 'shot_sketch' | 'video' | 'final_video'>('script');
let taskData: any = {
sketch: { data: [], total_count: -1 },
character: { data: [], total_count: -1 },
shot_sketch: { data: [], total_count: -1 },
video: { data: [], total_count: -1 },
status: '0',
currentStage: 'script'
};
let loadingText: any = LOADING_TEXT_MAP.initializing;
const dispatch = useAppDispatch();
const { sketchCount, videoCount } = useAppSelector((state) => state.workflow);
useEffect(() => {
console.log('---------currentStage', currentStage);
}, [currentStage]);
const {
scriptBlocksMemo, // 渲染剧本数据
initializeFromProject,
@ -109,7 +111,7 @@ export function useWorkflowData() {
} = useScriptService();
// 初始化剧本
useUpdateEffect(() => {
if (currentStep === '0') {
if (taskObject.currentStage === 'script') {
console.log('开始初始化剧本', originalText,episodeId);
// TODO 为什么一开始没项目id
originalText && initializeFromProject(episodeId, originalText).then(() => {
@ -124,12 +126,12 @@ export function useWorkflowData() {
if (scriptBlocksMemo.length > 0) {
console.log('scriptBlocksMemo 更新:', scriptBlocksMemo);
setScriptData(scriptBlocksMemo);
setCurrentLoadingText(LOADING_TEXT_MAP.script);
// setCurrentLoadingText(LOADING_TEXT_MAP.script);
}
}, [scriptBlocksMemo]);
// 监听继续 请求更新数据
useUpdateEffect(() => {
if (currentStep === '6') {
if (taskObject.status !== 'IN_PROGRESS') {
return;
}
if (isPauseWorkFlow) {
@ -140,19 +142,21 @@ export function useWorkflowData() {
}, [isPauseWorkFlow], { mode: "debounce", delay: 1000 });
// 自动开始播放一轮
const autoPlaySketch = useCallback(() => {
const autoPlaySketch = useCallback((length: number) => {
return new Promise<void>((resolve) => {
let currentIndex = 0;
const interval = 2000; // 每个草图显示2秒
const playNext = () => {
if (currentIndex < taskSketch.length) {
if (currentIndex < length) {
console.log('自动播放设置索引:', currentIndex);
setCurrentSketchIndex(currentIndex);
currentIndex++;
setTimeout(playNext, interval);
} else {
// 播放完成后重置到第一个
setTimeout(() => {
console.log('自动播放完成重置索引到0');
setCurrentSketchIndex(0);
resolve();
}, 500); // 短暂延迟后重置
@ -162,18 +166,64 @@ export function useWorkflowData() {
// 开始播放
playNext();
});
}, [taskSketch.length]);
}, []);
// 草图生成完毕后自动播放一轮
useEffect(() => {
const handleAutoPlay = async () => {
if (!isGeneratingSketch && taskSketch.length > 0 && sketchCount === totalSketchCount && currentStep === '3' && !taskVideos.length) {
await autoPlaySketch();
}
};
if (['video', 'shot_sketch', 'sketch'].includes(taskObject.currentStage)) {
setCurrentSketchIndex(0);
}
}, [taskObject.currentStage]);
handleAutoPlay();
}, [sketchCount, totalSketchCount, isGeneratingSketch, autoPlaySketch]);
useUpdateEffect(() => {
console.log('-----look-taskObject_find_changed-----', taskObject);
if (taskObject.currentStage === 'script') {
if (scriptBlocksMemo.length > 0) {
loadingText.current = LOADING_TEXT_MAP.getSketchStatus;
} else {
loadingText.current = LOADING_TEXT_MAP.script;
}
}
if (taskObject.currentStage === 'scene') {
const realSketchResultData = taskObject.scenes.data.filter((item: any) => item.status !== 0);
if (taskObject.scenes.total_count > realSketchResultData.length) {
loadingText.current = LOADING_TEXT_MAP.sketch(realSketchResultData.length, taskObject.scenes.total_count);
} else {
loadingText.current = LOADING_TEXT_MAP.character;
}
}
if (taskObject.currentStage === 'character') {
const realCharacterResultData = taskObject.roles.data.filter((item: any) => item.status !== 0);
if (taskObject.roles.total_count > realCharacterResultData.length) {
loadingText.current = LOADING_TEXT_MAP.newCharacter(realCharacterResultData.length, taskObject.roles.total_count);
} else {
loadingText.current = LOADING_TEXT_MAP.getShotSketchStatus;
}
}
if (taskObject.currentStage === 'shot_sketch') {
const realShotResultData = taskObject.shot_sketch.data.filter((item: any) => item.status !== 0);
if (taskObject.shot_sketch.total_count > realShotResultData.length) {
loadingText.current = LOADING_TEXT_MAP.shotSketch(realShotResultData.length, taskObject.shot_sketch.total_count);
} else {
loadingText.current = LOADING_TEXT_MAP.getVideoStatus;
}
}
if (taskObject.currentStage === 'video') {
const realTaskResultData = taskObject.videos.data.filter((item: any) => item.video_status !== 0);
if (taskObject.videos.total_count > realTaskResultData.length) {
loadingText.current = LOADING_TEXT_MAP.video(realTaskResultData.length, taskObject.videos.total_count);
} else {
loadingText.current = LOADING_TEXT_MAP.postProduction('generating rough cut video...');
}
}
if (taskObject.currentStage === 'final_video') {
loadingText.current = LOADING_TEXT_MAP.postProduction('generating fine-grained video clips...');
}
if (taskObject.status === 'COMPLETED') {
loadingText.current = LOADING_TEXT_MAP.complete;
}
setCurrentLoadingText(loadingText.current);
}, [scriptBlocksMemo, taskObject.currentStage, taskObject.scenes.data, taskObject.roles.data, taskObject.shot_sketch.data, taskObject.videos.data], {mode: 'none'});
// 更新 setSketchCount
const updateSketchCount = useCallback((count: number) => {
@ -185,25 +235,17 @@ export function useWorkflowData() {
dispatch(setVideoCount(count));
}, [dispatch]);
// 替换原有的 setSketchCount 和 setVideoCount 调用
useEffect(() => {
console.log('sketchCount 已更新:', sketchCount);
currentStep !== '3' && setCurrentSketchIndex(sketchCount - 1);
}, [sketchCount]);
useEffect(() => {
console.log('videoCount 已更新:', videoCount);
setCurrentSketchIndex(videoCount - 1);
}, [videoCount]);
// 将 sketchCount 和 videoCount 放到 redux 中 每一次变化也要更新
// 添加手动播放控制
const handleManualPlay = useCallback(async () => {
if (!isGeneratingSketch && taskSketch.length > 0) {
await autoPlaySketch();
if (taskObject.currentStage === 'scene' && taskObject.scenes.data.length > 0) {
await autoPlaySketch(taskObject.scenes.data.length);
}
}, [isGeneratingSketch, taskSketch.length, autoPlaySketch]);
if (taskObject.currentStage === 'shot_sketch' && taskObject.shot_sketch.data.length > 0) {
await autoPlaySketch(taskObject.shot_sketch.data.length);
}
}, [taskObject.currentStage, taskObject.scenes.data, taskObject.shot_sketch.data, autoPlaySketch]);
// 获取流式数据
const fetchStreamData = useCallback(async () => {
@ -222,228 +264,166 @@ export function useWorkflowData() {
all_task_data[0] = all_task_data[1];
all_task_data[1] = temp;
const { current: taskCurrent } = tempTaskObject;
console.log('---look-all_task_data', all_task_data);
console.log('---look-taskData', taskData);
console.log('---look-tempTaskObject', taskCurrent);
// 收集所有需要更新的状态
let stateUpdates: {
taskSketch?: any[];
taskScenes?: any[];
taskShotSketch?: any[];
roles?: any[];
taskVideos?: any[];
isGeneratingSketch?: boolean;
isGeneratingVideo?: boolean;
currentStep?: string;
currentLoadingText?: string;
final?: any;
needStreamData?: boolean;
totalSketchCount?: number;
currentStage?: string;
} = {};
let stateUpdates = JSON.stringify(taskCurrent);
for (const task of all_task_data) {
// 如果有已完成的数据,同步到状态
if (task.task_name === 'generate_sketch' && (task.task_status !== 'COMPLETED' || taskData.sketch.total_count !== taskData.sketch.data.length)) {
taskData.status = '1';
const realSketchResultData = task.task_result.data.filter((item: any) => item.image_path);
if (realSketchResultData.length >= 0) {
taskData.currentStage = 'sketch';
if (task.task_name === 'generate_sketch' && task.task_result && task.task_result.data) {
let realSketchResultData = task.task_result.data.filter((item: any) => item.image_path);
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';
// 正在生成草图中 替换 sketch 数据
const sketchList = [];
for (const sketch of realSketchResultData) {
for (const sketch of task.task_result.data) {
sketchList.push({
url: sketch.image_path,
script: sketch.sketch_name
script: sketch.sketch_name,
status: sketch.image_path ? 1 : (task.task_status === 'COMPLETED' ? 2 : 0)
});
}
taskData.sketch.data = sketchList;
stateUpdates.taskSketch = sketchList;
stateUpdates.taskScenes = sketchList;
stateUpdates.isGeneratingSketch = true;
stateUpdates.totalSketchCount = task.task_result.total_count;
loadingText = LOADING_TEXT_MAP.sketch(sketchList.length, task.task_result.total_count);
taskCurrent.scenes.data = sketchList;
if (task.task_status === 'COMPLETED') {
// 草图生成完成
}
break;
}
if (task.task_status === 'COMPLETED') {
// 草图生成完成
taskData.sketch.total_count = taskData.sketch.data.length;
stateUpdates.isGeneratingSketch = false;
sketchCount = task.task_result.total_count;
loadingText = LOADING_TEXT_MAP.sketchComplete;
taskData.status = '2';
}
stateUpdates.currentStep = taskData.status;
break;
}
if (task.task_name === 'generate_character' && (task.task_status !== 'COMPLETED' || taskData.character.total_count !== taskData.character.data.length)) {
if (task.task_result.data.length >= 0 && roles.length !== task.task_result.data.length) {
taskData.currentStage = 'character';
if (task.task_name === 'generate_character' && task.task_result && task.task_result.data) {
let realCharacterResultData = task.task_result.data.filter((item: any) => item.image_path);
if (task.task_status === 'COMPLETED') {
realCharacterResultData = taskCurrent.roles.data.filter((item: any) => item.status !== 0);
}
taskCurrent.roles.total_count = task.task_result.total_count;
if (task.task_status !== 'COMPLETED' || taskCurrent.roles.total_count !== realCharacterResultData.length) {
taskCurrent.currentStage = 'character';
// 正在生成角色中 替换角色数据
const characterList = [];
for (const character of task.task_result.data) {
characterList.push({
name: character.character_name,
url: character.image_path,
sound: null,
soundDescription: '',
roleDescription: character.character_description
status: character.image_path ? 1 : (task.task_status === 'COMPLETED' ? 2 : 0)
});
}
taskData.character.data = characterList;
stateUpdates.roles = characterList;
loadingText = LOADING_TEXT_MAP.newCharacter(characterList.length, task.task_result.total_count);
taskCurrent.roles.data = characterList;
if (task.task_status === 'COMPLETED') {
console.log('----------角色生成完成,有几个分镜', sketchCount);
// 角色生成完成
}
break;
}
if (task.task_status === 'COMPLETED') {
console.log('----------角色生成完成,有几个分镜', sketchCount);
// 角色生成完成
taskData.character.total_count = taskData.character.data.length;
taskData.status = '3';
loadingText = LOADING_TEXT_MAP.getShotSketchStatus;
}
stateUpdates.currentStep = taskData.status;
break;
}
if (task.task_name === 'generate_shot_sketch' && (task.task_status !== 'COMPLETED' || taskData.shot_sketch.total_count !== taskData.shot_sketch.data.length)) {
const realShotResultData = task.task_result.data.filter((item: any) => item.url);
if (realShotResultData.length >= 0) {
taskData.currentStage = 'shot_sketch';
taskData.status = '1';
console.log('----------正在生成草图中 替换 sketch 数据', taskShotSketch.length, realShotResultData.length);
// debugger;
if (task.task_name === 'generate_shot_sketch' && task.task_result && task.task_result.data) {
let realShotResultData = task.task_result.data.filter((item: any) => item.url);
if (task.task_status === 'COMPLETED') {
realShotResultData = taskCurrent.shot_sketch.data.filter((item: any) => item.status !== 0);
}
taskCurrent.shot_sketch.total_count = task.task_result.total_count;
if (task.task_status !== 'COMPLETED' || taskCurrent.shot_sketch.total_count !== realShotResultData.length) {
taskCurrent.currentStage = 'shot_sketch';
console.log('----------正在生成草图中 替换 sketch 数据', realShotResultData.length);
// 正在生成草图中 替换 sketch 数据
const sketchList = [];
for (const sketch of realShotResultData) {
for (const sketch of task.task_result.data) {
sketchList.push({
url: sketch.url,
script: sketch.description
script: sketch.description,
status: sketch.url ? 1 : (task.task_status === 'COMPLETED' ? 2 : 0)
});
}
taskData.shot_sketch.data = sketchList;
stateUpdates.taskSketch = sketchList;
stateUpdates.taskShotSketch = sketchList;
stateUpdates.isGeneratingSketch = true;
stateUpdates.totalSketchCount = task.task_result.total_count;
loadingText = LOADING_TEXT_MAP.shotSketch(sketchList.length, task.task_result.total_count);
taskCurrent.shot_sketch.data = sketchList;
if (task.task_status === 'COMPLETED') {
// 草图生成完成
console.log('----------草图生成完成', sketchCount);
}
break;
}
if (task.task_status === 'COMPLETED') {
// 草图生成完成
taskData.shot_sketch.total_count = taskData.shot_sketch.data.length;
stateUpdates.isGeneratingSketch = false;
stateUpdates.isGeneratingVideo = true;
sketchCount = task.task_result.total_count;
console.log('----------草图生成完成', sketchCount);
loadingText = LOADING_TEXT_MAP.getVideoStatus;
taskData.status = '3';
}
stateUpdates.currentStep = taskData.status;
break;
}
if (task.task_name === 'generate_videos' && (task.task_status !== 'COMPLETED' || taskData.video.total_count !== taskData.video.data.length)) {
if (task.task_result.data) {
const realTaskResultData = task.task_result.data.filter((item: any) => (item.urls || (item.video_status !== 0 && item.video_status !== undefined)));
if (task.task_name === 'generate_videos' && task.task_result && task.task_result.data) {
let realTaskResultData = task.task_result.data.filter((item: any) => (item.urls || (item.video_status !== 0 && item.video_status !== undefined)));
taskCurrent.videos.total_count = task.task_result.total_count;
if (task.task_status === 'COMPLETED') {
realTaskResultData = taskCurrent.videos.data.filter((item: any) => item.video_status !== 0);
}
if (task.task_status !== 'COMPLETED' || taskCurrent.videos.total_count !== realTaskResultData.length) {
taskCurrent.currentStage = 'video';
// 正在生成视频中 替换视频数据
const videoList = [];
let video_status = 0;
for (const video of task.task_result.data) {
// 适配旧数据
video_status = video.video_status === undefined ? (video.urls ? 1 : 0) : video.video_status;
// 完成 还是 0 就是 生成失败
video_status = task.task_status === 'COMPLETED' && video_status === 0 ? 2 : video_status;
// 每一项 video 有多个视频 先默认取第一个
videoList.push({
url: video.urls,
script: video.description,
audio: null,
urls: video.urls,
video_id: video.video_id,
video_status: video_status, // 0 生成中 1 生成完成 2 生成失败
});
}
if (realTaskResultData.length > 0) {
taskData.currentStage = 'video';
}
taskCurrent.videos.data = videoList;
console.log('----------正在生成视频中', realTaskResultData.length);
taskData.video.data = videoList;
stateUpdates.taskVideos = videoList;
stateUpdates.isGeneratingVideo = true;
loadingText = LOADING_TEXT_MAP.video(realTaskResultData.length, task.task_result.total_count);
if (task.task_status === 'COMPLETED') {
console.log('----------视频生成完成');
// 视频生成完成
// 暂时没有音频生成 直接跳过
}
break;
}
if (task.task_status === 'COMPLETED') {
console.log('----------视频生成完成');
// 视频生成完成
taskData.video.total_count = taskData.video.data.length;
stateUpdates.isGeneratingVideo = false;
taskData.status = '4';
// 暂时没有音频生成 直接跳过
taskData.status = '5';
loadingText = LOADING_TEXT_MAP.postProduction('generating rough cut video...');
}
stateUpdates.currentStep = taskData.status;
break;
}
// 粗剪
if (task.task_name === 'generate_final_simple_video') {
if (task.task_result && task.task_result.video) {
taskData.currentStage = 'final_video';
stateUpdates.final = {
url: task.task_result.video,
};
taskData.status = '5.5';
loadingText = LOADING_TEXT_MAP.postProduction('generating fine-grained video clips...');
taskCurrent.currentStage = 'final_video';
taskCurrent.final.url = task.task_result.video;
taskCurrent.final.note = 'simple';
}
}
// 最终剪辑
if (task.task_name === 'generate_final_video') {
if (task.task_result && task.task_result.video) {
taskData.currentStage = 'final_video';
stateUpdates.final = {
url: task.task_result.video,
};
taskData.status = '6';
loadingText = LOADING_TEXT_MAP.complete;
taskCurrent.currentStage = 'final_video';
taskCurrent.final.url = task.task_result.video;
taskCurrent.final.note = 'final';
taskCurrent.status = 'COMPLETED';
// 停止轮询
stateUpdates.needStreamData = false;
setNeedStreamData(false);
}
}
}
console.log('-----look-finalStep-----', taskData.status);
console.log('-----look-tempTaskObject-----', loadingText.current);
// 设置最终的状态更新
stateUpdates.currentStep = taskData.status;
stateUpdates.currentLoadingText = loadingText;
setCurrentLoadingText(loadingText.current);
// 批量更新所有状态 因为上面的循环 会执行很多次更新 影响性能
if (stateUpdates.taskSketch) setTaskSketch(stateUpdates.taskSketch);
if (stateUpdates.taskScenes) setTaskScenes(stateUpdates.taskScenes);
if (stateUpdates.taskShotSketch) setTaskShotSketch(stateUpdates.taskShotSketch);
if (stateUpdates.roles) setRoles(stateUpdates.roles);
if (stateUpdates.taskVideos) setTaskVideos(stateUpdates.taskVideos);
if (stateUpdates.isGeneratingSketch !== undefined) setIsGeneratingSketch(stateUpdates.isGeneratingSketch);
if (stateUpdates.isGeneratingVideo !== undefined) setIsGeneratingVideo(stateUpdates.isGeneratingVideo);
if (stateUpdates.currentStep) setCurrentStep(stateUpdates.currentStep);
if (stateUpdates.currentLoadingText) setCurrentLoadingText(stateUpdates.currentLoadingText);
if (stateUpdates.final) setFinal(stateUpdates.final);
if (stateUpdates.needStreamData !== undefined) setNeedStreamData(stateUpdates.needStreamData);
if (stateUpdates.totalSketchCount) setTotalSketchCount(stateUpdates.totalSketchCount);
// Redux 更新放在最后,避免触发额外的 useEffect
if (stateUpdates.taskSketch) updateSketchCount(stateUpdates.taskSketch.length);
if (stateUpdates.taskVideos) updateVideoCount(stateUpdates.taskVideos.length);
if (stateUpdates.currentStage) taskData.currentStage = stateUpdates.currentStage;
// 更新 taskObject
if (stateUpdates.currentStep) {
if (JSON.stringify(taskCurrent) !== stateUpdates) {
console.log('-----look-tempTaskObject-changed-----', taskCurrent);
// 强制更新,使用新的对象引用确保触发更新
setTaskObject(prev => {
if (!prev) return null;
return {
...prev,
taskStatus: stateUpdates.currentStep!
};
const newState = JSON.parse(JSON.stringify({...prev, ...taskCurrent}));
return newState;
});
}
@ -453,7 +433,7 @@ export function useWorkflowData() {
}, [episodeId, needStreamData, roles.length, taskShotSketch.length]);
// 轮询获取流式数据
useEffect(() => {
useUpdateEffect(() => {
let interval: NodeJS.Timeout;
if (needStreamData) {
@ -466,7 +446,7 @@ export function useWorkflowData() {
clearInterval(interval);
}
};
}, [needStreamData, fetchStreamData]);
}, [needStreamData, fetchStreamData], { mode: "debounce", delay: 1000 });
// 初始化数据
const initializeWorkflow = async () => {
@ -491,13 +471,11 @@ export function useWorkflowData() {
setOriginalText(original_text);
setIsLoading(false);
// 设置初始数据
setTaskObject({
taskStatus: '0',
title: name || 'generating...',
currentLoadingText: status === 'COMPLETED' ? LOADING_TEXT_MAP.complete : LOADING_TEXT_MAP.initializing,
tags: tags || []
});
const { current: taskCurrent } = tempTaskObject;
taskCurrent.title = name || 'generating...';
taskCurrent.tags = tags || [];
taskCurrent.status = status as Status;
// 设置标题
if (!name) {
@ -505,174 +483,127 @@ export function useWorkflowData() {
const titleResponse = await getScriptTitle({ project_id: episodeId });
console.log('titleResponse', titleResponse);
if (titleResponse.successful) {
setTaskObject((prev: TaskObject | null) => ({
...(prev || {}),
title: titleResponse.data.title,
tags: titleResponse.data.tags || []
} as TaskObject));
taskCurrent.title = titleResponse.data.title;
taskCurrent.tags = titleResponse.data.tags || [];
}
}
if (status === 'COMPLETED') {
loadingText = LOADING_TEXT_MAP.complete;
taskData.status = '6';
taskCurrent.currentStage = 'final_video';
}
// 如果有已完成的数据,同步到状态
if (data) {
if (data.sketch && data.sketch.data) {
taskData.currentStage = 'sketch';
taskData.status = '1';
taskCurrent.currentStage = 'scene';
const realSketchResultData = data.sketch.data.filter((item: any) => item.image_path);
const sketchList = [];
for (const sketch of realSketchResultData) {
for (const sketch of data.sketch.data) {
sketchList.push({
url: sketch.image_path,
script: sketch.sketch_name,
status: sketch.image_path ? 1 : (data.sketch.task_status === 'COMPLETED' ? 2 : 0)
});
}
taskData.sketch.data = sketchList;
taskData.sketch.total_count = data.sketch.total_count;
setTaskSketch(sketchList);
setTaskScenes(sketchList);
updateSketchCount(sketchList.length);
taskCurrent.scenes.data = sketchList;
taskCurrent.scenes.total_count = data.sketch.total_count;
// 设置为最后一个草图
if (data.sketch.total_count > realSketchResultData.length) {
// 场景生成中
setIsGeneratingSketch(true);
loadingText = LOADING_TEXT_MAP.sketch(realSketchResultData.length, data.sketch.total_count);
} else {
taskData.status = '2';
if (!data.character || !data.character.data || !data.character.data.length) {
loadingText = LOADING_TEXT_MAP.newCharacter(0, data.character.total_count);
}
// 场景生成完成
}
}
if (data.character && data.character.data && data.character.data.length > 0) {
taskData.currentStage = 'character';
taskCurrent.currentStage = 'character';
const characterList = [];
for (const character of data.character.data) {
characterList.push({
name: character.character_name,
url: character.image_path,
sound: null,
soundDescription: '',
roleDescription: character.character_description
status: character.image_path ? 1 : (data.character.task_status === 'COMPLETED' ? 2 : 0)
});
}
taskData.character.data = characterList;
taskData.character.total_count = data.character.total_count;
setRoles(characterList);
taskCurrent.roles.data = characterList;
taskCurrent.roles.total_count = data.character.total_count;
if (data.character.total_count > data.character.data.length) {
loadingText = LOADING_TEXT_MAP.newCharacter(data.character.data.length, data.character.total_count);
// 角色生成中
} else {
taskData.status = '3';
if (!data.video || !data.video.data || !data.video.data.length) {
loadingText = LOADING_TEXT_MAP.getShotSketchStatus;
}
// 角色生成完成
}
}
if (data.shot_sketch && data.shot_sketch.data) {
taskData.currentStage = 'shot_sketch';
taskCurrent.currentStage = 'shot_sketch';
const realShotResultData = data.shot_sketch.data.filter((item: any) => item.url);
const sketchList = [];
for (const sketch of realShotResultData) {
for (const sketch of data.shot_sketch.data) {
sketchList.push({
url: sketch.url,
script: sketch.description,
status: sketch.url ? 1 : (data.shot_sketch.task_status === 'COMPLETED' ? 2 : 0)
});
}
taskData.shot_sketch.data = sketchList;
taskData.shot_sketch.total_count = data.shot_sketch.total_count;
setTaskSketch(sketchList);
setTaskShotSketch(sketchList);
updateSketchCount(sketchList.length);
taskCurrent.shot_sketch.data = sketchList;
taskCurrent.shot_sketch.total_count = data.shot_sketch.total_count;
// 设置为最后一个草图
if (data.shot_sketch.total_count > realShotResultData.length) {
setIsGeneratingSketch(true);
loadingText = LOADING_TEXT_MAP.shotSketch(realShotResultData.length, data.shot_sketch.total_count);
// 草图生成中
} else {
taskData.status = '3';
setIsGeneratingVideo(true);
if (!data.character || !data.character.data || !data.character.data.length) {
loadingText = LOADING_TEXT_MAP.getVideoStatus;
}
// 草图生成完成
}
}
if (data.video.data) {
const realDataVideoData = data.video.data.filter((item: any) => (item.urls || (item.video_status !== 0 && item.video_status !== undefined)));
if (realDataVideoData.length === 0 && taskData.status === '3') {
loadingText = LOADING_TEXT_MAP.video(0, data.video.total_count);
}
if (realDataVideoData.length > 0) {
taskData.currentStage = 'video';
}
taskCurrent.currentStage = 'video';
taskCurrent.videos.total_count = data.video.total_count;
const videoList = [];
console.log('----------data.video.data', data.video.data);
for (const video of data.video.data) {
let video_status = video.video_status === undefined ? (video.urls ? 1 : 0) : video.video_status;
video_status = data.video.task_status === 'COMPLETED' && video_status === 0 ? 2 : video_status;
// 每一项 video 有多个视频 默认取存在的项
videoList.push({
url: video.urls,
script: video.description,
audio: null,
urls: video.urls,
video_id: video.video_id,
video_status: video.video_status === undefined ? (video.urls ? 1 : 0) : video.video_status, // 0 生成中 1 生成完成 2 生成失败
video_status: video_status, // 0 生成中 1 生成完成 2 生成失败
});
}
taskData.video.data = videoList;
taskData.video.total_count = data.video.total_count;
setTaskVideos(videoList);
updateVideoCount(videoList.length);
taskCurrent.videos.data = videoList;
// 如果在视频步骤,设置为最后一个视频
if (data.video.total_count > realDataVideoData.length) {
setIsGeneratingVideo(true);
loadingText = LOADING_TEXT_MAP.video(realDataVideoData.length, data.video.total_count);
// 视频生成中
} else {
taskData.status = '4';
loadingText = LOADING_TEXT_MAP.audio;
// 视频生成完成
// 暂时没有音频生成 直接跳过
taskData.status = '5';
loadingText = LOADING_TEXT_MAP.postProduction('generating rough cut video...');
}
}
// 粗剪
if ((data as any).final_simple_video && (data as any).final_simple_video.video) {
taskData.currentStage = 'final_video';
setFinal({
url: (data as any).final_simple_video.video
});
taskData.status = '5.5';
loadingText = LOADING_TEXT_MAP.postProduction('generating fine-grained video clips...');
if (data.final_simple_video && data.final_simple_video.video) {
taskCurrent.currentStage = 'final_video';
taskCurrent.final.url = data.final_simple_video.video;
taskCurrent.final.note = 'simple';
}
if (data.final_video && data.final_video.video) {
taskData.currentStage = 'final_video';
setFinal({
url: data.final_video.video
});
taskData.status = '6';
loadingText = LOADING_TEXT_MAP.complete;
taskCurrent.currentStage = 'final_video';
taskCurrent.final.url = data.final_video.video;
taskCurrent.final.note = 'final';
}
}
console.log('---look-taskData', taskData);
console.log('---look-taskData', taskCurrent);
// 设置步骤
setCurrentStage(taskData.currentStage);
setCurrentStep(taskData.status);
setTaskObject(prev => {
if (!prev) return null;
return {
...prev,
taskStatus: taskData.status
};
const newState = JSON.parse(JSON.stringify({...prev, ...taskCurrent}));
return newState;
});
console.log('---------loadingText', loadingText);
setCurrentLoadingText(loadingText);
// 设置是否需要获取流式数据
setNeedStreamData(status !== 'COMPLETED' && taskData.status !== '6');
setNeedStreamData(status !== 'COMPLETED');
} catch (error) {
console.error('初始化失败:', error);
@ -684,15 +615,8 @@ export function useWorkflowData() {
// 回退到 指定状态 重新获取数据
const fallbackToStep = (step: string) => {
console.log('fallbackToStep', step);
setCurrentStep(step);
setNeedStreamData(true);
taskData = {
sketch: { data: [], total_count: -1 },
character: { data: [], total_count: -1 },
shot_sketch: { data: [], total_count: -1 },
video: { data: [], total_count: -1 },
status: step
};
tempTaskObject.current.currentStage = step as Stage;
// loadingText = LOADING_TEXT_MAP.initializing;
}
@ -747,7 +671,6 @@ export function useWorkflowData() {
setAnyAttribute,
applyScript,
fallbackToStep,
originalText,
currentStage
originalText
};
}

View File

@ -73,13 +73,7 @@ export const ScriptRenderer: React.FC<ScriptRendererProps> = ({ data, setIsPause
return <em className="italic">{content.text}</em>;
default:
return <p className="mb-2">
{
(isInit && from !== 'tab') ? (
<TypewriterText text={content.text || ''} stableId={content.type} />
) : (
<span>{content.text}</span>
)
}
<span>{content.text}</span>
</p>;
}
};

View File

@ -17,14 +17,8 @@ interface EditModalProps {
isOpen: boolean;
onClose: () => void;
activeEditTab: string;
taskStatus: string;
taskSketch: any[];
sketchVideo: any[];
taskScenes: any[];
currentSketchIndex: number;
onSketchSelect: (index: number) => void;
roles?: any[];
music?: any;
setIsPauseWorkFlow: (isPauseWorkFlow: boolean) => void;
isPauseWorkFlow: boolean;
fallbackToStep: any;
@ -45,14 +39,8 @@ export function EditModal({
isOpen,
onClose,
activeEditTab,
taskStatus,
taskSketch,
sketchVideo,
taskScenes,
currentSketchIndex,
onSketchSelect,
roles = [],
music,
setIsPauseWorkFlow,
isPauseWorkFlow,
fallbackToStep,
@ -83,8 +71,8 @@ const [pendingSwitchTabId, setPendingSwitchTabId] = useState<string | null>(null
if (tabId === 'settings') return false;
// 换成 如果对应标签下 数据存在 就不禁用
// if (tabId === '1') return roles.length === 0;
if (tabId === '2') return taskScenes.length === 0;
if (tabId === '3') return sketchVideo.length === 0;
// if (tabId === '2') return taskScenes.length === 0;
// if (tabId === '3') return sketchVideo.length === 0;
if (tabId === '4') return false;
return false;
};
@ -144,10 +132,10 @@ const [pendingSwitchTabId, setPendingSwitchTabId] = useState<string | null>(null
console.log('handleConfirmGotoFallback');
SaveEditUseCase.saveData();
if (activeTab === '0') {
fallbackToStep('0');
fallbackToStep('script');
// 应用剧本
} else {
fallbackToStep('1');
fallbackToStep('video');
}
setIsRemindFallbackOpen(false);
// 关闭弹窗
@ -200,9 +188,7 @@ const [pendingSwitchTabId, setPendingSwitchTabId] = useState<string | null>(null
case '2':
return (
<SceneTabContent
taskSketch={taskScenes}
currentSketchIndex={currentIndex}
onSketchSelect={hanldeChangeSelect}
/>
);
case '3':
@ -215,10 +201,7 @@ const [pendingSwitchTabId, setPendingSwitchTabId] = useState<string | null>(null
case '4':
return (
<MusicTabContent
taskSketch={taskSketch}
currentSketchIndex={currentIndex}
onSketchSelect={onSketchSelect}
music={music}
/>
);
case 'settings':

View File

@ -17,18 +17,20 @@ interface Music {
}
interface MusicTabContentProps {
taskSketch: any[];
currentSketchIndex: number;
onSketchSelect: (index: number) => void;
music?: Music;
}
export function MusicTabContent({
taskSketch,
currentSketchIndex,
onSketchSelect,
music
}: MusicTabContentProps) {
const music = {
url: '',
script: '',
name: '',
duration: '',
totalDuration: '',
isLooped: true,
}
const [isReplaceModalOpen, setIsReplaceModalOpen] = useState(false);
const [activeMethod, setActiveMethod] = useState('upload');
const [isPlaying, setIsPlaying] = useState(false);

View File

@ -163,7 +163,7 @@ export function ReplacePanel({
ref={el => {
if (el) videoRefs.current[shot.id] = el;
}}
src={shot.videoUrl[0]}
src={shot.videoUrl[0].video_url}
className="w-full h-full object-cover"
loop
muted

View File

@ -29,9 +29,7 @@ interface SceneEnvironment {
}
interface SceneTabContentProps {
taskSketch: any[];
currentSketchIndex: number;
onSketchSelect: (index: number) => void;
}
interface SceneSketch {
@ -65,22 +63,23 @@ const mockSketch: SceneSketch = {
};
export function SceneTabContent({
taskSketch = [],
currentSketchIndex = 0,
onSketchSelect
currentSketchIndex = 0
}: SceneTabContentProps) {
const thumbnailsRef = useRef<HTMLDivElement>(null);
const scriptsRef = useRef<HTMLDivElement>(null);
// 确保 taskSketch 是数组
const sketches = Array.isArray(taskSketch) ? taskSketch : [];
const sketches: any[] = [];
const [localSketch, setLocalSketch] = useState(mockSketch);
const textareaRef = useRef<HTMLTextAreaElement>(null);
const [isReplacePanelOpen, setIsReplacePanelOpen] = useState(false);
const [replacePanelKey, setReplacePanelKey] = useState(0);
const [ignoreReplace, setIgnoreReplace] = useState(false);
const [currentScene, setCurrentScene] = useState(taskSketch[currentSketchIndex]);
const [currentScene, setCurrentScene] = useState({
url: '',
script: ''
});
// 天气图标映射
const weatherIcons = {
@ -144,12 +143,12 @@ export function SceneTabContent({
}, [currentSketchIndex]);
const handleReplaceScene = (url: string) => {
setCurrentScene({
...currentScene,
url: url
});
// setCurrentScene({
// ...currentScene,
// url: url
// });
setIsReplacePanelOpen(true);
// setIsReplacePanelOpen(true);
};
const handleConfirmReplace = (selectedShots: string[], addToLibrary: boolean) => {
@ -165,17 +164,16 @@ export function SceneTabContent({
};
const handleChangeScene = (index: number) => {
if (currentScene?.url !== taskSketch[currentSketchIndex]?.url && !ignoreReplace) {
// 提示 场景已修改,弹出替换场景面板
if (isReplacePanelOpen) {
setReplacePanelKey(replacePanelKey + 1);
} else {
setIsReplacePanelOpen(true);
}
return;
}
onSketchSelect(index);
setCurrentScene(taskSketch[index]);
// if (currentScene?.url !== sketches[currentSketchIndex]?.url && !ignoreReplace) {
// // 提示 场景已修改,弹出替换场景面板
// if (isReplacePanelOpen) {
// setReplacePanelKey(replacePanelKey + 1);
// } else {
// setIsReplacePanelOpen(true);
// }
// return;
// }
// setCurrentScene(sketches[index]);
};
// 如果没有数据,显示空状态

View File

@ -238,7 +238,7 @@ export function ShotTabContent({
)}
{shot.status === 1 && (
<video
src={shot.videoUrl[0]}
src={shot.videoUrl[0].video_url}
className="w-full h-full object-cover"
muted
loop
@ -342,7 +342,7 @@ export function ShotTabContent({
layoutId={`video-preview-${selectedIndex}`}
>
<PersonDetectionScene
videoSrc={shotData[selectedIndex]?.videoUrl[0]}
videoSrc={shotData[selectedIndex]?.videoUrl[0].video_url}
detections={detections}
scanState={scanState}
triggerScan={scanState === 'scanning'}