forked from 77media/video-flow
修复工作流阶段状态问题
This commit is contained in:
parent
2c4bb10a36
commit
a5304f8077
@ -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;
|
||||
}; // 剪辑视频
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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]);
|
||||
});
|
||||
|
||||
@ -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 () => {
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@ -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>;
|
||||
}
|
||||
};
|
||||
|
||||
@ -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':
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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]);
|
||||
};
|
||||
|
||||
// 如果没有数据,显示空状态
|
||||
|
||||
@ -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'}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user