forked from 77media/video-flow
修复工作流阶段状态问题
This commit is contained in:
parent
2c4bb10a36
commit
a5304f8077
@ -119,6 +119,8 @@ export interface SketchResponse {
|
|||||||
total_count: number;
|
total_count: number;
|
||||||
/** 草图数据列表 */
|
/** 草图数据列表 */
|
||||||
data: SketchData[];
|
data: SketchData[];
|
||||||
|
/** 任务状态 */
|
||||||
|
task_status: string;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 角色响应数据接口
|
* 角色响应数据接口
|
||||||
@ -141,6 +143,8 @@ export interface CharacterData {
|
|||||||
data: CharacterResponse[];
|
data: CharacterResponse[];
|
||||||
/** 总数 */
|
/** 总数 */
|
||||||
total_count: number;
|
total_count: number;
|
||||||
|
/** 任务状态 */
|
||||||
|
task_status: string;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 镜头草图提示JSON结构接口
|
* 镜头草图提示JSON结构接口
|
||||||
@ -191,6 +195,8 @@ export interface ShotSketchResponse {
|
|||||||
data: ShotSketchData[];
|
data: ShotSketchData[];
|
||||||
/** 总数 */
|
/** 总数 */
|
||||||
total_count: number;
|
total_count: number;
|
||||||
|
/** 任务状态 */
|
||||||
|
task_status: string;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 主要角色列表项
|
* 主要角色列表项
|
||||||
@ -261,6 +267,8 @@ export interface VideoData {
|
|||||||
video_name_prefix: string;
|
video_name_prefix: string;
|
||||||
/** 视频URL列表 */
|
/** 视频URL列表 */
|
||||||
urls: string[];
|
urls: string[];
|
||||||
|
/** 视频状态 */
|
||||||
|
video_status: number;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 视频响应数据接口
|
* 视频响应数据接口
|
||||||
@ -275,6 +283,8 @@ export interface VideoResponse {
|
|||||||
guid: string;
|
guid: string;
|
||||||
/** 项目ID */
|
/** 项目ID */
|
||||||
project_id: string;
|
project_id: string;
|
||||||
|
/** 任务状态 */
|
||||||
|
task_status: string;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 音乐数据接口
|
* 音乐数据接口
|
||||||
@ -313,6 +323,8 @@ export interface ProjectContentData {
|
|||||||
video: VideoResponse;
|
video: VideoResponse;
|
||||||
/** 音乐数据 */
|
/** 音乐数据 */
|
||||||
music: MusicData;
|
music: MusicData;
|
||||||
|
/** 粗剪视频 */
|
||||||
|
final_simple_video: FinalVideo;
|
||||||
/** 最终视频 */
|
/** 最终视频 */
|
||||||
final_video: FinalVideo;
|
final_video: FinalVideo;
|
||||||
/** 多语言视频 */
|
/** 多语言视频 */
|
||||||
@ -608,3 +620,54 @@ export interface RoleResponse {
|
|||||||
character_draft: string;
|
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;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
.videoContainer-qteKNi {
|
.videoContainer-qteKNi {
|
||||||
/* flex: 1; */
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -224,7 +224,7 @@
|
|||||||
|
|
||||||
@media (height >= 880px) {
|
@media (height >= 880px) {
|
||||||
.imageGrid-ymZV9z {
|
.imageGrid-ymZV9z {
|
||||||
flex: 1;
|
/* flex: 1; */
|
||||||
height: auto;
|
height: auto;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
@ -54,8 +54,7 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
setAnyAttribute,
|
setAnyAttribute,
|
||||||
applyScript,
|
applyScript,
|
||||||
fallbackToStep,
|
fallbackToStep,
|
||||||
originalText,
|
originalText
|
||||||
currentStage
|
|
||||||
} = useWorkflowData();
|
} = useWorkflowData();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -69,36 +68,9 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
playTimerRef,
|
playTimerRef,
|
||||||
} = usePlaybackControls(taskSketch, taskVideos, currentStep);
|
} = usePlaybackControls(taskSketch, taskVideos, currentStep);
|
||||||
|
|
||||||
// 跟踪是否已经自动开始播放过,避免重复触发
|
|
||||||
const hasAutoStartedRef = useRef(false);
|
|
||||||
|
|
||||||
// 跟踪循环播放的起始索引,用于判断是否完成一轮循环
|
|
||||||
const loopStartIndexRef = useRef<number | null>(null);
|
|
||||||
|
|
||||||
// 调试:监控关键状态变化
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('工作流状态:', {
|
console.log('changedIndex_work-flow', currentSketchIndex, taskObject);
|
||||||
currentStep,
|
}, [currentSketchIndex]);
|
||||||
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]);
|
|
||||||
|
|
||||||
// 模拟 AI 建议 英文
|
// 模拟 AI 建议 英文
|
||||||
const mockSuggestions = [
|
const mockSuggestions = [
|
||||||
@ -145,7 +117,6 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
<div className="media-Ocdu1O rounded-lg">
|
<div className="media-Ocdu1O rounded-lg">
|
||||||
<div
|
<div
|
||||||
className="videoContainer-qteKNi"
|
className="videoContainer-qteKNi"
|
||||||
style={(currentStep !== '6' && currentStep !== '0') ? { flex: 3 } : {}}
|
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
>
|
>
|
||||||
{dataLoadError ? (
|
{dataLoadError ? (
|
||||||
@ -182,17 +153,15 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
) : isLoading ? (
|
) : isLoading ? (
|
||||||
<Skeleton className="w-full aspect-video rounded-lg" />
|
<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>
|
<ErrorBoundary>
|
||||||
<MediaViewer
|
<MediaViewer
|
||||||
currentStage={currentStage}
|
taskObject={taskObject}
|
||||||
scriptData={scriptData}
|
scriptData={scriptData}
|
||||||
currentStep={currentStep}
|
|
||||||
currentSketchIndex={currentSketchIndex}
|
currentSketchIndex={currentSketchIndex}
|
||||||
taskSketch={taskSketch}
|
taskSketch={taskSketch}
|
||||||
taskVideos={taskVideos}
|
taskVideos={taskVideos}
|
||||||
isVideoPlaying={isVideoPlaying}
|
isVideoPlaying={isVideoPlaying}
|
||||||
isPlaying={isPlaying}
|
|
||||||
showControls={showControls}
|
showControls={showControls}
|
||||||
isGeneratingSketch={isGeneratingSketch}
|
isGeneratingSketch={isGeneratingSketch}
|
||||||
isGeneratingVideo={isGeneratingVideo}
|
isGeneratingVideo={isGeneratingVideo}
|
||||||
@ -211,14 +180,12 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{currentStage !== 'final_video' && currentStage !== 'script' && (
|
{taskObject.currentStage !== 'final_video' && taskObject.currentStage !== 'script' && (
|
||||||
<div className="imageGrid-ymZV9z hide-scrollbar">
|
<div className="imageGrid-ymZV9z hide-scrollbar">
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<ThumbnailGrid
|
<ThumbnailGrid
|
||||||
currentStage={currentStage}
|
taskObject={taskObject}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
isPlaying={isPlaying}
|
|
||||||
currentStep={currentStep}
|
|
||||||
currentSketchIndex={currentSketchIndex}
|
currentSketchIndex={currentSketchIndex}
|
||||||
taskSketch={taskSketch}
|
taskSketch={taskSketch}
|
||||||
taskVideos={taskVideos}
|
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">
|
<div className="absolute right-12 bottom-16 z-[49] flex gap-4">
|
||||||
<GlassIconButton
|
<GlassIconButton
|
||||||
icon={isPauseWorkFlow ? Play : Pause}
|
icon={isPauseWorkFlow ? Play : Pause}
|
||||||
@ -278,14 +245,8 @@ const WorkFlow = React.memo(function WorkFlow() {
|
|||||||
SaveEditUseCase.clearData();
|
SaveEditUseCase.clearData();
|
||||||
setIsEditModalOpen(false)
|
setIsEditModalOpen(false)
|
||||||
}}
|
}}
|
||||||
taskStatus={taskObject?.taskStatus || '1'}
|
|
||||||
taskSketch={taskSketch}
|
|
||||||
sketchVideo={taskVideos}
|
|
||||||
taskScenes={taskScenes}
|
|
||||||
currentSketchIndex={currentSketchIndex}
|
currentSketchIndex={currentSketchIndex}
|
||||||
onSketchSelect={setCurrentSketchIndex}
|
roles={taskObject.roles.data}
|
||||||
roles={roles}
|
|
||||||
music={music}
|
|
||||||
setIsPauseWorkFlow={setIsPauseWorkFlow}
|
setIsPauseWorkFlow={setIsPauseWorkFlow}
|
||||||
isPauseWorkFlow={isPauseWorkFlow}
|
isPauseWorkFlow={isPauseWorkFlow}
|
||||||
fallbackToStep={fallbackToStep}
|
fallbackToStep={fallbackToStep}
|
||||||
|
|||||||
@ -8,15 +8,15 @@ import { GlassIconButton } from '@/components/ui/glass-icon-button';
|
|||||||
import { ScriptRenderer } from '@/components/script-renderer/ScriptRenderer';
|
import { ScriptRenderer } from '@/components/script-renderer/ScriptRenderer';
|
||||||
import { mockScriptData } from '@/components/script-renderer/mock';
|
import { mockScriptData } from '@/components/script-renderer/mock';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
import { TaskObject } from '@/api/DTO/movieEdit';
|
||||||
|
|
||||||
interface MediaViewerProps {
|
interface MediaViewerProps {
|
||||||
|
taskObject: TaskObject;
|
||||||
scriptData: any;
|
scriptData: any;
|
||||||
currentStep: string;
|
|
||||||
currentSketchIndex: number;
|
currentSketchIndex: number;
|
||||||
taskSketch: any[];
|
taskSketch: any[];
|
||||||
taskVideos: any[];
|
taskVideos: any[];
|
||||||
isVideoPlaying: boolean;
|
isVideoPlaying: boolean;
|
||||||
isPlaying: boolean;
|
|
||||||
showControls: boolean;
|
showControls: boolean;
|
||||||
isGeneratingSketch: boolean;
|
isGeneratingSketch: boolean;
|
||||||
isGeneratingVideo: boolean;
|
isGeneratingVideo: boolean;
|
||||||
@ -30,17 +30,15 @@ interface MediaViewerProps {
|
|||||||
isPauseWorkFlow: boolean;
|
isPauseWorkFlow: boolean;
|
||||||
applyScript: any;
|
applyScript: any;
|
||||||
mode: string;
|
mode: string;
|
||||||
currentStage: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MediaViewer = React.memo(function MediaViewer({
|
export const MediaViewer = React.memo(function MediaViewer({
|
||||||
|
taskObject,
|
||||||
scriptData,
|
scriptData,
|
||||||
currentStep,
|
|
||||||
currentSketchIndex,
|
currentSketchIndex,
|
||||||
taskSketch,
|
taskSketch,
|
||||||
taskVideos,
|
taskVideos,
|
||||||
isVideoPlaying,
|
isVideoPlaying,
|
||||||
isPlaying,
|
|
||||||
showControls,
|
showControls,
|
||||||
isGeneratingSketch,
|
isGeneratingSketch,
|
||||||
isGeneratingVideo,
|
isGeneratingVideo,
|
||||||
@ -53,8 +51,7 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
setAnyAttribute,
|
setAnyAttribute,
|
||||||
isPauseWorkFlow,
|
isPauseWorkFlow,
|
||||||
applyScript,
|
applyScript,
|
||||||
mode,
|
mode
|
||||||
currentStage
|
|
||||||
}: MediaViewerProps) {
|
}: MediaViewerProps) {
|
||||||
const mainVideoRef = useRef<HTMLVideoElement>(null);
|
const mainVideoRef = useRef<HTMLVideoElement>(null);
|
||||||
const finalVideoRef = useRef<HTMLVideoElement>(null);
|
const finalVideoRef = useRef<HTMLVideoElement>(null);
|
||||||
@ -150,26 +147,26 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
|
|
||||||
// 使用 useMemo 缓存最终视频元素,避免重复创建和请求
|
// 使用 useMemo 缓存最终视频元素,避免重复创建和请求
|
||||||
const memoizedFinalVideoElement = useMemo(() => {
|
const memoizedFinalVideoElement = useMemo(() => {
|
||||||
console.log('final', final);
|
console.log('final', taskObject.final);
|
||||||
if (!final?.url) return null;
|
if (!taskObject.final?.url) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<video
|
<video
|
||||||
ref={finalVideoRef}
|
ref={finalVideoRef}
|
||||||
className="w-full h-full object-cover rounded-lg"
|
className="w-full h-full object-cover rounded-lg"
|
||||||
src={final.url}
|
src={taskObject.final.url}
|
||||||
autoPlay={isFinalVideoPlaying}
|
autoPlay={isFinalVideoPlaying}
|
||||||
loop
|
loop
|
||||||
playsInline
|
playsInline
|
||||||
preload="metadata"
|
preload="metadata"
|
||||||
poster={`${final.url}?vframe/jpg/offset/1`}
|
poster={`${taskObject.final.url}?vframe/jpg/offset/1`}
|
||||||
onLoadedData={handleFinalVideoLoaded}
|
onLoadedData={handleFinalVideoLoaded}
|
||||||
onPlay={() => setIsFinalVideoPlaying(true)}
|
onPlay={() => setIsFinalVideoPlaying(true)}
|
||||||
onPause={() => setIsFinalVideoPlaying(false)}
|
onPause={() => setIsFinalVideoPlaying(false)}
|
||||||
onClick={handleVideoClick}
|
onClick={handleVideoClick}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}, [final?.url, isFinalVideoPlaying, handleFinalVideoLoaded, handleVideoClick]);
|
}, [taskObject.final?.url, isFinalVideoPlaying, handleFinalVideoLoaded, handleVideoClick]);
|
||||||
|
|
||||||
// 包装编辑按钮点击事件
|
// 包装编辑按钮点击事件
|
||||||
const handleEditClick = (tab: string, from?: string) => {
|
const handleEditClick = (tab: string, from?: string) => {
|
||||||
@ -308,16 +305,13 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 渲染最终成片
|
// 渲染最终成片
|
||||||
const renderFinalVideo = (currentStep: string) => {
|
const renderFinalVideo = () => {
|
||||||
// 使用真实的final数据,如果没有则使用默认值
|
// 使用真实的final数据,如果没有则使用默认值
|
||||||
const finalVideo = final || {
|
|
||||||
url: 'https://cdn.qikongjian.com/videos/1750389908_37d4fffa-8516-43a3-a423-fc0274f40e8a_text_to_video_0.mp4'
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="relative w-full h-full rounded-lg overflow-hidden"
|
className="relative w-full h-full rounded-lg overflow-hidden"
|
||||||
key={`render-video-${currentStep}`}
|
key={`render-video-${taskObject.final.note}`}
|
||||||
onMouseEnter={() => onControlsChange(true)}
|
onMouseEnter={() => onControlsChange(true)}
|
||||||
onMouseLeave={() => onControlsChange(false)}
|
onMouseLeave={() => onControlsChange(false)}
|
||||||
>
|
>
|
||||||
@ -331,7 +325,7 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
>
|
>
|
||||||
<video
|
<video
|
||||||
className="w-full h-full rounded-lg object-cover object-center"
|
className="w-full h-full rounded-lg object-cover object-center"
|
||||||
src={taskVideos[currentSketchIndex]?.url}
|
src={taskObject.final.url}
|
||||||
loop
|
loop
|
||||||
playsInline
|
playsInline
|
||||||
muted
|
muted
|
||||||
@ -391,7 +385,7 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
ease: "easeInOut"
|
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>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -409,21 +403,6 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
whileTap={{ scale: 0.9 }}
|
whileTap={{ scale: 0.9 }}
|
||||||
className="relative"
|
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
|
<GlassIconButton
|
||||||
icon={isFinalVideoPlaying ? Pause : Play}
|
icon={isFinalVideoPlaying ? Pause : Play}
|
||||||
tooltip={isFinalVideoPlaying ? "Pause video" : "Play video"}
|
tooltip={isFinalVideoPlaying ? "Pause video" : "Play video"}
|
||||||
@ -461,10 +440,6 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
|
|
||||||
// 渲染视频内容
|
// 渲染视频内容
|
||||||
const renderVideoContent = () => {
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className="relative w-full h-full rounded-lg"
|
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`}}>
|
<div className="absolute inset-0 overflow-hidden" style={{background: `url(${taskSketch[currentSketchIndex]?.url}) no-repeat center center`}}>
|
||||||
{/* 生成中 */}
|
{/* 生成中 */}
|
||||||
{taskVideos[currentSketchIndex].video_status === 0 && (
|
{taskObject.videos.data[currentSketchIndex].video_status === 0 && (
|
||||||
<div className="absolute inset-0 bg-black/50 flex items-center justify-center">
|
<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">
|
<div className="text-blue-500 text-2xl font-bold flex items-center gap-2">
|
||||||
<Loader2 className="w-10 h-10 animate-spin" />
|
<Loader2 className="w-10 h-10 animate-spin" />
|
||||||
<span>Generating...</span>
|
<span>Generating...</span>
|
||||||
@ -485,8 +460,8 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* 生成失败 */}
|
{/* 生成失败 */}
|
||||||
{taskVideos[currentSketchIndex].video_status === 2 && (
|
{taskObject.videos.data[currentSketchIndex].video_status === 2 && (
|
||||||
<div className="absolute inset-0 bg-red-500 flex items-center justify-center">
|
<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">
|
<div className="text-red-500 text-2xl font-bold flex items-center gap-2">
|
||||||
<X className="w-10 h-10" />
|
<X className="w-10 h-10" />
|
||||||
<span>Failed</span>
|
<span>Failed</span>
|
||||||
@ -496,7 +471,7 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 视频 多个 取第一个 */}
|
{/* 视频 多个 取第一个 */}
|
||||||
{ taskVideos[currentSketchIndex].url && (
|
{ taskObject.videos.data[currentSketchIndex].urls && (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ clipPath: "inset(0 100% 0 0)" }}
|
initial={{ clipPath: "inset(0 100% 0 0)" }}
|
||||||
animate={{ clipPath: "inset(0 0% 0 0)" }}
|
animate={{ clipPath: "inset(0 0% 0 0)" }}
|
||||||
@ -505,9 +480,9 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
>
|
>
|
||||||
<video
|
<video
|
||||||
ref={mainVideoRef}
|
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"
|
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}
|
autoPlay={isVideoPlaying}
|
||||||
loop={true}
|
loop={true}
|
||||||
playsInline
|
playsInline
|
||||||
@ -543,7 +518,7 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|
||||||
{/* 底部控制区域 */}
|
{/* 底部控制区域 */}
|
||||||
{ taskVideos[currentSketchIndex] && (
|
{ taskObject.videos.data[currentSketchIndex] && (
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
<motion.div
|
<motion.div
|
||||||
className="absolute bottom-4 left-4 z-[11] flex items-center gap-3"
|
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 renderSketchContent = (currentSketch: any) => {
|
||||||
const currentSketch = taskSketch[currentSketchIndex];
|
|
||||||
const defaultBgColors = ['RGB(45, 50, 70)', 'RGB(75, 80, 100)', 'RGB(105, 110, 130)'];
|
const defaultBgColors = ['RGB(45, 50, 70)', 'RGB(75, 80, 100)', 'RGB(105, 110, 130)'];
|
||||||
const bgColors = currentSketch?.bg_rgb || defaultBgColors;
|
const bgColors = defaultBgColors;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -587,107 +561,25 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
onMouseEnter={() => onControlsChange(true)}
|
onMouseEnter={() => onControlsChange(true)}
|
||||||
onMouseLeave={() => onControlsChange(false)}
|
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 */}
|
{/* 只在生成过程中或没有分镜图片时使用ProgressiveReveal */}
|
||||||
{(isGeneratingSketch || !currentSketch) ? (
|
{currentSketch.status === 1 && (
|
||||||
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 */
|
|
||||||
<img
|
<img
|
||||||
key={currentSketchIndex}
|
key={currentSketchIndex}
|
||||||
src={currentSketch.url}
|
src={currentSketch.url}
|
||||||
@ -729,28 +621,7 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
whileTap={{ scale: 0.9 }}
|
whileTap={{ scale: 0.9 }}
|
||||||
className="relative"
|
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>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
@ -790,17 +661,25 @@ export const MediaViewer = React.memo(function MediaViewer({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 根据当前步骤渲染对应内容
|
// 根据当前步骤渲染对应内容
|
||||||
if (currentStage === 'final_video') {
|
if (taskObject.currentStage === 'final_video') {
|
||||||
return renderFinalVideo(currentStep);
|
return renderFinalVideo();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentStage === 'video') {
|
if (taskObject.currentStage === 'video') {
|
||||||
return renderVideoContent();
|
return renderVideoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentStage === 'script') {
|
if (taskObject.currentStage === 'script') {
|
||||||
return renderScriptContent();
|
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);
|
setCurrentStage(3);
|
||||||
console.log('isScriptModalOpen-Post-production', currentLoadingText, isScriptModalOpen);
|
console.log('isScriptModalOpen-Post-production', currentLoadingText, isScriptModalOpen);
|
||||||
timerRef.current = setTimeout(() => {
|
timerRef.current = setTimeout(() => {
|
||||||
setIsScriptModalOpen(true);
|
// setIsScriptModalOpen(true);
|
||||||
}, 8000);
|
}, 8000);
|
||||||
}
|
}
|
||||||
if (currentLoadingText.includes('video') && !currentLoadingText.includes('Post-production')) {
|
if (currentLoadingText.includes('video') && !currentLoadingText.includes('Post-production')) {
|
||||||
@ -214,7 +214,7 @@ export function TaskInfo({
|
|||||||
}
|
}
|
||||||
if (currentLoadingText.includes('initializing')) {
|
if (currentLoadingText.includes('initializing')) {
|
||||||
console.log('isScriptModalOpen-initializing', currentLoadingText, isScriptModalOpen);
|
console.log('isScriptModalOpen-initializing', currentLoadingText, isScriptModalOpen);
|
||||||
setIsScriptModalOpen(true);
|
// setIsScriptModalOpen(true);
|
||||||
setCurrentStage(0);
|
setCurrentStage(0);
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useRef, useEffect, useState } from 'react';
|
import React, { useRef, useEffect, useState, useCallback } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
import { ProgressiveReveal, presets } from '@/components/ui/progressive-reveal';
|
import { ProgressiveReveal, presets } from '@/components/ui/progressive-reveal';
|
||||||
import { Loader2, X } from 'lucide-react';
|
import { Loader2, X } from 'lucide-react';
|
||||||
|
import { TaskObject } from '@/api/DTO/movieEdit';
|
||||||
|
|
||||||
interface ThumbnailGridProps {
|
interface ThumbnailGridProps {
|
||||||
|
taskObject: TaskObject;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
isPlaying: boolean;
|
|
||||||
currentStep: string;
|
|
||||||
currentSketchIndex: number;
|
currentSketchIndex: number;
|
||||||
taskSketch: any[];
|
taskSketch: any[];
|
||||||
taskVideos: any[];
|
taskVideos: any[];
|
||||||
@ -18,13 +18,11 @@ interface ThumbnailGridProps {
|
|||||||
sketchCount: number;
|
sketchCount: number;
|
||||||
totalSketchCount: number;
|
totalSketchCount: number;
|
||||||
onSketchSelect: (index: number) => void;
|
onSketchSelect: (index: number) => void;
|
||||||
currentStage: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ThumbnailGrid({
|
export function ThumbnailGrid({
|
||||||
|
taskObject,
|
||||||
isLoading,
|
isLoading,
|
||||||
isPlaying,
|
|
||||||
currentStep,
|
|
||||||
currentSketchIndex,
|
currentSketchIndex,
|
||||||
taskSketch,
|
taskSketch,
|
||||||
taskVideos,
|
taskVideos,
|
||||||
@ -32,8 +30,7 @@ export function ThumbnailGrid({
|
|||||||
isGeneratingVideo,
|
isGeneratingVideo,
|
||||||
sketchCount,
|
sketchCount,
|
||||||
totalSketchCount,
|
totalSketchCount,
|
||||||
onSketchSelect,
|
onSketchSelect
|
||||||
currentStage
|
|
||||||
}: ThumbnailGridProps) {
|
}: ThumbnailGridProps) {
|
||||||
const thumbnailsRef = useRef<HTMLDivElement>(null);
|
const thumbnailsRef = useRef<HTMLDivElement>(null);
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
@ -43,7 +40,7 @@ export function ThumbnailGrid({
|
|||||||
|
|
||||||
// 监听当前选中索引变化,自动滚动到对应位置
|
// 监听当前选中索引变化,自动滚动到对应位置
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (thumbnailsRef.current && taskSketch.length > 0) {
|
if (thumbnailsRef.current) {
|
||||||
const container = thumbnailsRef.current;
|
const container = thumbnailsRef.current;
|
||||||
const thumbnailWidth = container.offsetWidth / 4; // 每个缩略图宽度(包含间距)
|
const thumbnailWidth = container.offsetWidth / 4; // 每个缩略图宽度(包含间距)
|
||||||
const scrollPosition = currentSketchIndex * thumbnailWidth;
|
const scrollPosition = currentSketchIndex * thumbnailWidth;
|
||||||
@ -53,39 +50,97 @@ export function ThumbnailGrid({
|
|||||||
behavior: 'smooth'
|
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(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const currentData = getCurrentData();
|
||||||
// 只在元素被聚焦时处理键盘事件
|
if (currentData && currentData.length > 0) {
|
||||||
if (!isFocused) return;
|
const currentDataStr = JSON.stringify(currentData);
|
||||||
|
const prevDataStr = JSON.stringify(prevDataRef.current);
|
||||||
const isVideoPhase = Number(currentStep) > 2 && taskVideos.length > 0 && Number(currentStep) < 6;
|
|
||||||
const maxIndex = isVideoPhase ? taskVideos.length - 1 : taskSketch.length - 1;
|
|
||||||
|
|
||||||
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
|
// 只有当数据真正发生变化时才进行处理
|
||||||
e.preventDefault();
|
if (currentDataStr !== prevDataStr) {
|
||||||
let newIndex = currentSketchIndex;
|
// 找到最新更新的数据项的索引
|
||||||
|
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') {
|
console.log('changedIndex_thumbnail-grid', changedIndex, 'currentData:', currentData, 'prevData:', prevDataRef.current);
|
||||||
// 向左循环
|
|
||||||
newIndex = currentSketchIndex === 0 ? maxIndex : currentSketchIndex - 1;
|
// 如果找到变化的项,自动选择该项
|
||||||
} else {
|
if (changedIndex !== -1) {
|
||||||
// 向右循环
|
onSketchSelect(changedIndex);
|
||||||
newIndex = currentSketchIndex === maxIndex ? 0 : currentSketchIndex + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
return () => window.removeEventListener('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>) => {
|
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
// 阻止默认的拖拽行为
|
||||||
|
e.preventDefault();
|
||||||
setIsDragging(true);
|
setIsDragging(true);
|
||||||
setStartX(e.pageX - thumbnailsRef.current!.offsetLeft);
|
setStartX(e.pageX - thumbnailsRef.current!.offsetLeft);
|
||||||
setScrollLeft(thumbnailsRef.current!.scrollLeft);
|
setScrollLeft(thumbnailsRef.current!.scrollLeft);
|
||||||
@ -102,22 +157,13 @@ export function ThumbnailGrid({
|
|||||||
const handleMouseUp = (e: React.MouseEvent<HTMLDivElement>) => {
|
const handleMouseUp = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
setIsDragging(false);
|
setIsDragging(false);
|
||||||
if (!isDragging) return;
|
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) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
@ -131,7 +177,7 @@ export function ThumbnailGrid({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 粗剪/精剪最终成片阶段不显示缩略图
|
// 粗剪/精剪最终成片阶段不显示缩略图
|
||||||
if (Number(currentStep) === 5.5 || Number(currentStep) === 6) {
|
if (taskObject.currentStage === 'final_video') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,9 +249,7 @@ export function ThumbnailGrid({
|
|||||||
|
|
||||||
// 渲染视频阶段的缩略图
|
// 渲染视频阶段的缩略图
|
||||||
const renderVideoThumbnails = () => (
|
const renderVideoThumbnails = () => (
|
||||||
taskVideos.map((video, index) => {
|
taskObject.videos.data.map((video, index) => {
|
||||||
const defaultBgColors = ['RGB(45, 50, 70)', 'RGB(75, 80, 100)', 'RGB(105, 110, 130)'];
|
|
||||||
const bgColors = defaultBgColors;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -217,16 +261,16 @@ export function ThumbnailGrid({
|
|||||||
|
|
||||||
{/* 视频层 */}
|
{/* 视频层 */}
|
||||||
<div className="relative w-full h-full transform hover:scale-105 transition-transform duration-500">
|
<div className="relative w-full h-full transform hover:scale-105 transition-transform duration-500">
|
||||||
{taskVideos[index].video_status === 0 && (
|
{taskObject.videos.data[index].video_status === 0 && (
|
||||||
<div className="absolute inset-0 bg-black/50 flex items-center justify-center z-20">
|
<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">
|
<div className="text-blue-500 text-xl font-bold flex items-center gap-2">
|
||||||
<Loader2 className="w-10 h-10 animate-spin" />
|
<Loader2 className="w-10 h-10 animate-spin" />
|
||||||
<span>Generating...</span>
|
<span>Generating...</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{taskVideos[index].video_status === 2 && (
|
{taskObject.videos.data[index].video_status === 2 && (
|
||||||
<div className="absolute inset-0 bg-red-500 flex items-center justify-center z-20">
|
<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">
|
<div className="text-red-500 text-xl font-bold flex items-center gap-2">
|
||||||
<X className="w-10 h-10" />
|
<X className="w-10 h-10" />
|
||||||
<span>Failed</span>
|
<span>Failed</span>
|
||||||
@ -234,10 +278,10 @@ export function ThumbnailGrid({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{taskVideos[index].url ? (
|
{taskObject.videos.data[index].urls ? (
|
||||||
<video
|
<video
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
src={taskVideos[index].url[0]}
|
src={taskObject.videos.data[index].urls[0]}
|
||||||
playsInline
|
playsInline
|
||||||
loop
|
loop
|
||||||
muted
|
muted
|
||||||
@ -245,11 +289,12 @@ export function ThumbnailGrid({
|
|||||||
) : (
|
) : (
|
||||||
<div className="w-full h-full transform hover:scale-105 transition-transform duration-500">
|
<div className="w-full h-full transform hover:scale-105 transition-transform duration-500">
|
||||||
<img
|
<img
|
||||||
className={`w-full h-full object-cover transition-all duration-300 ${
|
className={`w-full h-full object-cover transition-all duration-300 select-none ${
|
||||||
(!taskSketch[index] && !isPlaying) ? 'filter blur-sm opacity-60' : ''
|
(!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}`}
|
alt={`Thumbnail ${index + 1}`}
|
||||||
|
draggable="false"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -265,11 +310,9 @@ export function ThumbnailGrid({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 渲染分镜草图阶段的缩略图
|
// 渲染分镜草图阶段的缩略图
|
||||||
const renderSketchThumbnails = () => (
|
const renderSketchThumbnails = (sketchData: any[]) => (
|
||||||
<>
|
<>
|
||||||
{taskSketch.map((sketch, index) => {
|
{sketchData.map((sketch, index) => {
|
||||||
const defaultBgColors = ['RGB(45, 50, 70)', 'RGB(75, 80, 100)', 'RGB(105, 110, 130)'];
|
|
||||||
const bgColors = sketch?.bg_rgb || defaultBgColors;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<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'}`}
|
${currentSketchIndex === index ? 'ring-2 ring-blue-500 z-10' : 'hover:ring-2 hover:ring-blue-500/50'}`}
|
||||||
onClick={() => !isDragging && onSketchSelect(index)}
|
onClick={() => !isDragging && onSketchSelect(index)}
|
||||||
>
|
>
|
||||||
{/* 只在生成过程中或没有分镜图片时使用ProgressiveReveal */}
|
|
||||||
{(isGeneratingSketch || !sketch) ? (
|
{/* 状态 */}
|
||||||
<ProgressiveReveal
|
{sketch.status === 0 && (
|
||||||
key={`sketch-thumbnail-generating-${index}`}
|
<div className="absolute inset-0 bg-black/10 flex items-center justify-center">
|
||||||
revealDuration={0.8}
|
<div className="text-blue-500 text-xl font-bold flex items-center gap-2">
|
||||||
blurDuration={0.3}
|
<Loader2 className="w-10 h-10 animate-spin" />
|
||||||
initialBlur={10}
|
<span>Generating...</span>
|
||||||
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}`}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</ProgressiveReveal>
|
</div>
|
||||||
) : (
|
)}
|
||||||
/* 生成完成后直接显示,不使用ProgressiveReveal */
|
{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">
|
<div className="w-full h-full transform hover:scale-105 transition-transform duration-500">
|
||||||
<img
|
<img
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover select-none"
|
||||||
src={sketch.url}
|
src={sketch.url}
|
||||||
alt={`NG ${index + 1}`}
|
alt={`NG ${index + 1}`}
|
||||||
|
draggable="false"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -345,7 +364,8 @@ export function ThumbnailGrid({
|
|||||||
<div
|
<div
|
||||||
ref={thumbnailsRef}
|
ref={thumbnailsRef}
|
||||||
tabIndex={0}
|
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}
|
onMouseDown={handleMouseDown}
|
||||||
onMouseMove={handleMouseMove}
|
onMouseMove={handleMouseMove}
|
||||||
onMouseUp={handleMouseUp}
|
onMouseUp={handleMouseUp}
|
||||||
@ -353,10 +373,10 @@ export function ThumbnailGrid({
|
|||||||
onFocus={() => setIsFocused(true)}
|
onFocus={() => setIsFocused(true)}
|
||||||
onBlur={() => setIsFocused(false)}
|
onBlur={() => setIsFocused(false)}
|
||||||
>
|
>
|
||||||
{currentStage === 'video'
|
{taskObject.currentStage === 'video' && renderVideoThumbnails()}
|
||||||
? renderVideoThumbnails()
|
{taskObject.currentStage === 'scene' && renderSketchThumbnails(taskObject.scenes.data)}
|
||||||
: renderSketchThumbnails()
|
{taskObject.currentStage === 'shot_sketch' && renderSketchThumbnails(taskObject.shot_sketch.data)}
|
||||||
}
|
{taskObject.currentStage === 'character' && renderSketchThumbnails(taskObject.scenes.data)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1,12 +1,13 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
import { detailScriptEpisodeNew, getScriptTitle, getRunningStreamData, pauseMovieProjectPlan, resumeMovieProjectPlan } from '@/api/video_flow';
|
import { detailScriptEpisodeNew, getScriptTitle, getRunningStreamData, pauseMovieProjectPlan, resumeMovieProjectPlan } from '@/api/video_flow';
|
||||||
import { useAppDispatch, useAppSelector } from '@/lib/store/hooks';
|
import { useAppDispatch, useAppSelector } from '@/lib/store/hooks';
|
||||||
import { setSketchCount, setVideoCount } from '@/lib/store/workflowSlice';
|
import { setSketchCount, setVideoCount } from '@/lib/store/workflowSlice';
|
||||||
import { useScriptService } from "@/app/service/Interaction/ScriptService";
|
import { useScriptService } from "@/app/service/Interaction/ScriptService";
|
||||||
import { useUpdateEffect } from '@/app/hooks/useUpdateEffect';
|
import { useUpdateEffect } from '@/app/hooks/useUpdateEffect';
|
||||||
|
import { TaskObject, Status, Stage } from '@/api/DTO/movieEdit';
|
||||||
|
|
||||||
// 步骤映射
|
// 步骤映射
|
||||||
const STEP_MAP = {
|
const STEP_MAP = {
|
||||||
@ -22,14 +23,14 @@ const LOADING_TEXT_MAP = {
|
|||||||
initializing: 'initializing...',
|
initializing: 'initializing...',
|
||||||
script: 'Generating script...',
|
script: 'Generating script...',
|
||||||
getSketchStatus: 'Getting sketch status...',
|
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',
|
sketchComplete: 'Sketch generation complete',
|
||||||
character: 'Drawing characters...',
|
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...',
|
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...',
|
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',
|
videoComplete: 'Video generation complete',
|
||||||
audio: 'Generating background audio...',
|
audio: 'Generating background audio...',
|
||||||
postProduction: (step: string) => `Post-production: ${step}...`,
|
postProduction: (step: string) => `Post-production: ${step}...`,
|
||||||
@ -39,20 +40,6 @@ const LOADING_TEXT_MAP = {
|
|||||||
|
|
||||||
type ApiStep = keyof typeof STEP_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() {
|
export function useWorkflowData() {
|
||||||
console.log('98877766777777888888990')
|
console.log('98877766777777888888990')
|
||||||
@ -60,8 +47,37 @@ export function useWorkflowData() {
|
|||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const episodeId = searchParams.get('episodeId') || '';
|
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 的类型
|
// 更新 taskObject 的类型
|
||||||
const [taskObject, setTaskObject] = useState<TaskObject | null>(null);
|
const [taskObject, setTaskObject] = useState<TaskObject>(tempTaskObject.current);
|
||||||
const [originalText, setOriginalText] = useState<string>('');
|
const [originalText, setOriginalText] = useState<string>('');
|
||||||
const [scriptData, setScriptData] = useState<any>(null);
|
const [scriptData, setScriptData] = useState<any>(null);
|
||||||
const [taskSketch, setTaskSketch] = useState<any[]>([]);
|
const [taskSketch, setTaskSketch] = useState<any[]>([]);
|
||||||
@ -82,25 +98,11 @@ export function useWorkflowData() {
|
|||||||
const [needStreamData, setNeedStreamData] = useState(false);
|
const [needStreamData, setNeedStreamData] = useState(false);
|
||||||
const [isPauseWorkFlow, setIsPauseWorkFlow] = useState(false);
|
const [isPauseWorkFlow, setIsPauseWorkFlow] = useState(false);
|
||||||
const [mode, setMode] = useState<'automatic' | 'manual' | 'auto'>('automatic');
|
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 dispatch = useAppDispatch();
|
||||||
const { sketchCount, videoCount } = useAppSelector((state) => state.workflow);
|
const { sketchCount, videoCount } = useAppSelector((state) => state.workflow);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log('---------currentStage', currentStage);
|
|
||||||
}, [currentStage]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
scriptBlocksMemo, // 渲染剧本数据
|
scriptBlocksMemo, // 渲染剧本数据
|
||||||
initializeFromProject,
|
initializeFromProject,
|
||||||
@ -109,7 +111,7 @@ export function useWorkflowData() {
|
|||||||
} = useScriptService();
|
} = useScriptService();
|
||||||
// 初始化剧本
|
// 初始化剧本
|
||||||
useUpdateEffect(() => {
|
useUpdateEffect(() => {
|
||||||
if (currentStep === '0') {
|
if (taskObject.currentStage === 'script') {
|
||||||
console.log('开始初始化剧本', originalText,episodeId);
|
console.log('开始初始化剧本', originalText,episodeId);
|
||||||
// TODO 为什么一开始没项目id
|
// TODO 为什么一开始没项目id
|
||||||
originalText && initializeFromProject(episodeId, originalText).then(() => {
|
originalText && initializeFromProject(episodeId, originalText).then(() => {
|
||||||
@ -124,12 +126,12 @@ export function useWorkflowData() {
|
|||||||
if (scriptBlocksMemo.length > 0) {
|
if (scriptBlocksMemo.length > 0) {
|
||||||
console.log('scriptBlocksMemo 更新:', scriptBlocksMemo);
|
console.log('scriptBlocksMemo 更新:', scriptBlocksMemo);
|
||||||
setScriptData(scriptBlocksMemo);
|
setScriptData(scriptBlocksMemo);
|
||||||
setCurrentLoadingText(LOADING_TEXT_MAP.script);
|
// setCurrentLoadingText(LOADING_TEXT_MAP.script);
|
||||||
}
|
}
|
||||||
}, [scriptBlocksMemo]);
|
}, [scriptBlocksMemo]);
|
||||||
// 监听继续 请求更新数据
|
// 监听继续 请求更新数据
|
||||||
useUpdateEffect(() => {
|
useUpdateEffect(() => {
|
||||||
if (currentStep === '6') {
|
if (taskObject.status !== 'IN_PROGRESS') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isPauseWorkFlow) {
|
if (isPauseWorkFlow) {
|
||||||
@ -140,19 +142,21 @@ export function useWorkflowData() {
|
|||||||
}, [isPauseWorkFlow], { mode: "debounce", delay: 1000 });
|
}, [isPauseWorkFlow], { mode: "debounce", delay: 1000 });
|
||||||
|
|
||||||
// 自动开始播放一轮
|
// 自动开始播放一轮
|
||||||
const autoPlaySketch = useCallback(() => {
|
const autoPlaySketch = useCallback((length: number) => {
|
||||||
return new Promise<void>((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
let currentIndex = 0;
|
let currentIndex = 0;
|
||||||
const interval = 2000; // 每个草图显示2秒
|
const interval = 2000; // 每个草图显示2秒
|
||||||
|
|
||||||
const playNext = () => {
|
const playNext = () => {
|
||||||
if (currentIndex < taskSketch.length) {
|
if (currentIndex < length) {
|
||||||
|
console.log('自动播放设置索引:', currentIndex);
|
||||||
setCurrentSketchIndex(currentIndex);
|
setCurrentSketchIndex(currentIndex);
|
||||||
currentIndex++;
|
currentIndex++;
|
||||||
setTimeout(playNext, interval);
|
setTimeout(playNext, interval);
|
||||||
} else {
|
} else {
|
||||||
// 播放完成后重置到第一个
|
// 播放完成后重置到第一个
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
console.log('自动播放完成,重置索引到0');
|
||||||
setCurrentSketchIndex(0);
|
setCurrentSketchIndex(0);
|
||||||
resolve();
|
resolve();
|
||||||
}, 500); // 短暂延迟后重置
|
}, 500); // 短暂延迟后重置
|
||||||
@ -162,18 +166,64 @@ export function useWorkflowData() {
|
|||||||
// 开始播放
|
// 开始播放
|
||||||
playNext();
|
playNext();
|
||||||
});
|
});
|
||||||
}, [taskSketch.length]);
|
}, []);
|
||||||
|
|
||||||
// 草图生成完毕后自动播放一轮
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleAutoPlay = async () => {
|
if (['video', 'shot_sketch', 'sketch'].includes(taskObject.currentStage)) {
|
||||||
if (!isGeneratingSketch && taskSketch.length > 0 && sketchCount === totalSketchCount && currentStep === '3' && !taskVideos.length) {
|
setCurrentSketchIndex(0);
|
||||||
await autoPlaySketch();
|
}
|
||||||
}
|
}, [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
|
// 更新 setSketchCount
|
||||||
const updateSketchCount = useCallback((count: number) => {
|
const updateSketchCount = useCallback((count: number) => {
|
||||||
@ -185,25 +235,17 @@ export function useWorkflowData() {
|
|||||||
dispatch(setVideoCount(count));
|
dispatch(setVideoCount(count));
|
||||||
}, [dispatch]);
|
}, [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 中 每一次变化也要更新
|
// 将 sketchCount 和 videoCount 放到 redux 中 每一次变化也要更新
|
||||||
|
|
||||||
// 添加手动播放控制
|
// 添加手动播放控制
|
||||||
const handleManualPlay = useCallback(async () => {
|
const handleManualPlay = useCallback(async () => {
|
||||||
if (!isGeneratingSketch && taskSketch.length > 0) {
|
if (taskObject.currentStage === 'scene' && taskObject.scenes.data.length > 0) {
|
||||||
await autoPlaySketch();
|
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 () => {
|
const fetchStreamData = useCallback(async () => {
|
||||||
@ -222,228 +264,166 @@ export function useWorkflowData() {
|
|||||||
all_task_data[0] = all_task_data[1];
|
all_task_data[0] = all_task_data[1];
|
||||||
all_task_data[1] = temp;
|
all_task_data[1] = temp;
|
||||||
|
|
||||||
|
const { current: taskCurrent } = tempTaskObject;
|
||||||
|
|
||||||
console.log('---look-all_task_data', all_task_data);
|
console.log('---look-all_task_data', all_task_data);
|
||||||
console.log('---look-taskData', taskData);
|
console.log('---look-tempTaskObject', taskCurrent);
|
||||||
|
|
||||||
// 收集所有需要更新的状态
|
// 收集所有需要更新的状态
|
||||||
let stateUpdates: {
|
let stateUpdates = JSON.stringify(taskCurrent);
|
||||||
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;
|
|
||||||
} = {};
|
|
||||||
|
|
||||||
for (const task of all_task_data) {
|
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)) {
|
if (task.task_name === 'generate_sketch' && task.task_result && task.task_result.data) {
|
||||||
taskData.status = '1';
|
let realSketchResultData = task.task_result.data.filter((item: any) => item.image_path);
|
||||||
const realSketchResultData = task.task_result.data.filter((item: any) => item.image_path);
|
if (task.task_status === 'COMPLETED') {
|
||||||
if (realSketchResultData.length >= 0) {
|
realSketchResultData = taskCurrent.scenes.data.filter((item: any) => item.status !== 0);
|
||||||
taskData.currentStage = 'sketch';
|
}
|
||||||
|
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 数据
|
// 正在生成草图中 替换 sketch 数据
|
||||||
const sketchList = [];
|
const sketchList = [];
|
||||||
for (const sketch of realSketchResultData) {
|
for (const sketch of task.task_result.data) {
|
||||||
sketchList.push({
|
sketchList.push({
|
||||||
url: sketch.image_path,
|
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;
|
taskCurrent.scenes.data = sketchList;
|
||||||
stateUpdates.taskSketch = sketchList;
|
if (task.task_status === 'COMPLETED') {
|
||||||
stateUpdates.taskScenes = sketchList;
|
// 草图生成完成
|
||||||
stateUpdates.isGeneratingSketch = true;
|
}
|
||||||
stateUpdates.totalSketchCount = task.task_result.total_count;
|
break;
|
||||||
loadingText = LOADING_TEXT_MAP.sketch(sketchList.length, task.task_result.total_count);
|
|
||||||
}
|
}
|
||||||
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_name === 'generate_character' && task.task_result && task.task_result.data) {
|
||||||
if (task.task_result.data.length >= 0 && roles.length !== task.task_result.data.length) {
|
let realCharacterResultData = task.task_result.data.filter((item: any) => item.image_path);
|
||||||
taskData.currentStage = 'character';
|
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 = [];
|
const characterList = [];
|
||||||
for (const character of task.task_result.data) {
|
for (const character of task.task_result.data) {
|
||||||
characterList.push({
|
characterList.push({
|
||||||
name: character.character_name,
|
name: character.character_name,
|
||||||
url: character.image_path,
|
url: character.image_path,
|
||||||
sound: null,
|
status: character.image_path ? 1 : (task.task_status === 'COMPLETED' ? 2 : 0)
|
||||||
soundDescription: '',
|
|
||||||
roleDescription: character.character_description
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
taskData.character.data = characterList;
|
taskCurrent.roles.data = characterList;
|
||||||
stateUpdates.roles = characterList;
|
if (task.task_status === 'COMPLETED') {
|
||||||
loadingText = LOADING_TEXT_MAP.newCharacter(characterList.length, task.task_result.total_count);
|
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)) {
|
// debugger;
|
||||||
const realShotResultData = task.task_result.data.filter((item: any) => item.url);
|
if (task.task_name === 'generate_shot_sketch' && task.task_result && task.task_result.data) {
|
||||||
if (realShotResultData.length >= 0) {
|
let realShotResultData = task.task_result.data.filter((item: any) => item.url);
|
||||||
taskData.currentStage = 'shot_sketch';
|
if (task.task_status === 'COMPLETED') {
|
||||||
taskData.status = '1';
|
realShotResultData = taskCurrent.shot_sketch.data.filter((item: any) => item.status !== 0);
|
||||||
console.log('----------正在生成草图中 替换 sketch 数据', taskShotSketch.length, realShotResultData.length);
|
}
|
||||||
|
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 数据
|
// 正在生成草图中 替换 sketch 数据
|
||||||
const sketchList = [];
|
const sketchList = [];
|
||||||
for (const sketch of realShotResultData) {
|
for (const sketch of task.task_result.data) {
|
||||||
sketchList.push({
|
sketchList.push({
|
||||||
url: sketch.url,
|
url: sketch.url,
|
||||||
script: sketch.description
|
script: sketch.description,
|
||||||
|
status: sketch.url ? 1 : (task.task_status === 'COMPLETED' ? 2 : 0)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
taskData.shot_sketch.data = sketchList;
|
taskCurrent.shot_sketch.data = sketchList;
|
||||||
stateUpdates.taskSketch = sketchList;
|
if (task.task_status === 'COMPLETED') {
|
||||||
stateUpdates.taskShotSketch = sketchList;
|
// 草图生成完成
|
||||||
stateUpdates.isGeneratingSketch = true;
|
console.log('----------草图生成完成', sketchCount);
|
||||||
stateUpdates.totalSketchCount = task.task_result.total_count;
|
}
|
||||||
loadingText = LOADING_TEXT_MAP.shotSketch(sketchList.length, task.task_result.total_count);
|
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_name === 'generate_videos' && task.task_result && task.task_result.data) {
|
||||||
if (task.task_result.data) {
|
let realTaskResultData = task.task_result.data.filter((item: any) => (item.urls || (item.video_status !== 0 && item.video_status !== undefined)));
|
||||||
const 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 = [];
|
const videoList = [];
|
||||||
let video_status = 0;
|
let video_status = 0;
|
||||||
for (const video of task.task_result.data) {
|
for (const video of task.task_result.data) {
|
||||||
// 适配旧数据
|
// 适配旧数据
|
||||||
video_status = video.video_status === undefined ? (video.urls ? 1 : 0) : video.video_status;
|
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_status = task.task_status === 'COMPLETED' && video_status === 0 ? 2 : video_status;
|
||||||
// 每一项 video 有多个视频 先默认取第一个
|
// 每一项 video 有多个视频 先默认取第一个
|
||||||
videoList.push({
|
videoList.push({
|
||||||
url: video.urls,
|
urls: video.urls,
|
||||||
script: video.description,
|
|
||||||
audio: null,
|
|
||||||
video_id: video.video_id,
|
video_id: video.video_id,
|
||||||
video_status: video_status, // 0 生成中 1 生成完成 2 生成失败
|
video_status: video_status, // 0 生成中 1 生成完成 2 生成失败
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (realTaskResultData.length > 0) {
|
taskCurrent.videos.data = videoList;
|
||||||
taskData.currentStage = 'video';
|
|
||||||
}
|
|
||||||
console.log('----------正在生成视频中', realTaskResultData.length);
|
console.log('----------正在生成视频中', realTaskResultData.length);
|
||||||
taskData.video.data = videoList;
|
if (task.task_status === 'COMPLETED') {
|
||||||
stateUpdates.taskVideos = videoList;
|
console.log('----------视频生成完成');
|
||||||
stateUpdates.isGeneratingVideo = true;
|
// 视频生成完成
|
||||||
loadingText = LOADING_TEXT_MAP.video(realTaskResultData.length, task.task_result.total_count);
|
// 暂时没有音频生成 直接跳过
|
||||||
|
}
|
||||||
|
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_name === 'generate_final_simple_video') {
|
||||||
if (task.task_result && task.task_result.video) {
|
if (task.task_result && task.task_result.video) {
|
||||||
taskData.currentStage = 'final_video';
|
taskCurrent.currentStage = 'final_video';
|
||||||
stateUpdates.final = {
|
taskCurrent.final.url = task.task_result.video;
|
||||||
url: task.task_result.video,
|
taskCurrent.final.note = 'simple';
|
||||||
};
|
|
||||||
taskData.status = '5.5';
|
|
||||||
loadingText = LOADING_TEXT_MAP.postProduction('generating fine-grained video clips...');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 最终剪辑
|
// 最终剪辑
|
||||||
if (task.task_name === 'generate_final_video') {
|
if (task.task_name === 'generate_final_video') {
|
||||||
if (task.task_result && task.task_result.video) {
|
if (task.task_result && task.task_result.video) {
|
||||||
taskData.currentStage = 'final_video';
|
taskCurrent.currentStage = 'final_video';
|
||||||
stateUpdates.final = {
|
taskCurrent.final.url = task.task_result.video;
|
||||||
url: task.task_result.video,
|
taskCurrent.final.note = 'final';
|
||||||
};
|
taskCurrent.status = 'COMPLETED';
|
||||||
taskData.status = '6';
|
|
||||||
loadingText = LOADING_TEXT_MAP.complete;
|
|
||||||
|
|
||||||
// 停止轮询
|
// 停止轮询
|
||||||
stateUpdates.needStreamData = false;
|
setNeedStreamData(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('-----look-finalStep-----', taskData.status);
|
console.log('-----look-tempTaskObject-----', loadingText.current);
|
||||||
|
|
||||||
// 设置最终的状态更新
|
// 设置最终的状态更新
|
||||||
stateUpdates.currentStep = taskData.status;
|
setCurrentLoadingText(loadingText.current);
|
||||||
stateUpdates.currentLoadingText = loadingText;
|
|
||||||
|
|
||||||
// 批量更新所有状态 因为上面的循环 会执行很多次更新 影响性能
|
if (JSON.stringify(taskCurrent) !== stateUpdates) {
|
||||||
if (stateUpdates.taskSketch) setTaskSketch(stateUpdates.taskSketch);
|
console.log('-----look-tempTaskObject-changed-----', taskCurrent);
|
||||||
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) {
|
|
||||||
setTaskObject(prev => {
|
setTaskObject(prev => {
|
||||||
if (!prev) return null;
|
const newState = JSON.parse(JSON.stringify({...prev, ...taskCurrent}));
|
||||||
return {
|
return newState;
|
||||||
...prev,
|
|
||||||
taskStatus: stateUpdates.currentStep!
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -453,7 +433,7 @@ export function useWorkflowData() {
|
|||||||
}, [episodeId, needStreamData, roles.length, taskShotSketch.length]);
|
}, [episodeId, needStreamData, roles.length, taskShotSketch.length]);
|
||||||
|
|
||||||
// 轮询获取流式数据
|
// 轮询获取流式数据
|
||||||
useEffect(() => {
|
useUpdateEffect(() => {
|
||||||
let interval: NodeJS.Timeout;
|
let interval: NodeJS.Timeout;
|
||||||
|
|
||||||
if (needStreamData) {
|
if (needStreamData) {
|
||||||
@ -466,7 +446,7 @@ export function useWorkflowData() {
|
|||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [needStreamData, fetchStreamData]);
|
}, [needStreamData, fetchStreamData], { mode: "debounce", delay: 1000 });
|
||||||
|
|
||||||
// 初始化数据
|
// 初始化数据
|
||||||
const initializeWorkflow = async () => {
|
const initializeWorkflow = async () => {
|
||||||
@ -491,13 +471,11 @@ export function useWorkflowData() {
|
|||||||
setOriginalText(original_text);
|
setOriginalText(original_text);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|
||||||
// 设置初始数据
|
const { current: taskCurrent } = tempTaskObject;
|
||||||
setTaskObject({
|
|
||||||
taskStatus: '0',
|
taskCurrent.title = name || 'generating...';
|
||||||
title: name || 'generating...',
|
taskCurrent.tags = tags || [];
|
||||||
currentLoadingText: status === 'COMPLETED' ? LOADING_TEXT_MAP.complete : LOADING_TEXT_MAP.initializing,
|
taskCurrent.status = status as Status;
|
||||||
tags: tags || []
|
|
||||||
});
|
|
||||||
|
|
||||||
// 设置标题
|
// 设置标题
|
||||||
if (!name) {
|
if (!name) {
|
||||||
@ -505,174 +483,127 @@ export function useWorkflowData() {
|
|||||||
const titleResponse = await getScriptTitle({ project_id: episodeId });
|
const titleResponse = await getScriptTitle({ project_id: episodeId });
|
||||||
console.log('titleResponse', titleResponse);
|
console.log('titleResponse', titleResponse);
|
||||||
if (titleResponse.successful) {
|
if (titleResponse.successful) {
|
||||||
setTaskObject((prev: TaskObject | null) => ({
|
taskCurrent.title = titleResponse.data.title;
|
||||||
...(prev || {}),
|
taskCurrent.tags = titleResponse.data.tags || [];
|
||||||
title: titleResponse.data.title,
|
|
||||||
tags: titleResponse.data.tags || []
|
|
||||||
} as TaskObject));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === 'COMPLETED') {
|
if (status === 'COMPLETED') {
|
||||||
loadingText = LOADING_TEXT_MAP.complete;
|
loadingText = LOADING_TEXT_MAP.complete;
|
||||||
taskData.status = '6';
|
taskCurrent.currentStage = 'final_video';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果有已完成的数据,同步到状态
|
// 如果有已完成的数据,同步到状态
|
||||||
if (data) {
|
if (data) {
|
||||||
if (data.sketch && data.sketch.data) {
|
if (data.sketch && data.sketch.data) {
|
||||||
taskData.currentStage = 'sketch';
|
taskCurrent.currentStage = 'scene';
|
||||||
taskData.status = '1';
|
|
||||||
const realSketchResultData = data.sketch.data.filter((item: any) => item.image_path);
|
const realSketchResultData = data.sketch.data.filter((item: any) => item.image_path);
|
||||||
const sketchList = [];
|
const sketchList = [];
|
||||||
for (const sketch of realSketchResultData) {
|
for (const sketch of data.sketch.data) {
|
||||||
sketchList.push({
|
sketchList.push({
|
||||||
url: sketch.image_path,
|
url: sketch.image_path,
|
||||||
script: sketch.sketch_name,
|
script: sketch.sketch_name,
|
||||||
|
status: sketch.image_path ? 1 : (data.sketch.task_status === 'COMPLETED' ? 2 : 0)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
taskData.sketch.data = sketchList;
|
taskCurrent.scenes.data = sketchList;
|
||||||
taskData.sketch.total_count = data.sketch.total_count;
|
taskCurrent.scenes.total_count = data.sketch.total_count;
|
||||||
setTaskSketch(sketchList);
|
|
||||||
setTaskScenes(sketchList);
|
|
||||||
updateSketchCount(sketchList.length);
|
|
||||||
// 设置为最后一个草图
|
// 设置为最后一个草图
|
||||||
if (data.sketch.total_count > realSketchResultData.length) {
|
if (data.sketch.total_count > realSketchResultData.length) {
|
||||||
|
// 场景生成中
|
||||||
setIsGeneratingSketch(true);
|
setIsGeneratingSketch(true);
|
||||||
loadingText = LOADING_TEXT_MAP.sketch(realSketchResultData.length, data.sketch.total_count);
|
|
||||||
} else {
|
} 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) {
|
if (data.character && data.character.data && data.character.data.length > 0) {
|
||||||
taskData.currentStage = 'character';
|
taskCurrent.currentStage = 'character';
|
||||||
const characterList = [];
|
const characterList = [];
|
||||||
for (const character of data.character.data) {
|
for (const character of data.character.data) {
|
||||||
characterList.push({
|
characterList.push({
|
||||||
name: character.character_name,
|
name: character.character_name,
|
||||||
url: character.image_path,
|
url: character.image_path,
|
||||||
sound: null,
|
status: character.image_path ? 1 : (data.character.task_status === 'COMPLETED' ? 2 : 0)
|
||||||
soundDescription: '',
|
|
||||||
roleDescription: character.character_description
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
taskData.character.data = characterList;
|
taskCurrent.roles.data = characterList;
|
||||||
taskData.character.total_count = data.character.total_count;
|
taskCurrent.roles.total_count = data.character.total_count;
|
||||||
setRoles(characterList);
|
|
||||||
if (data.character.total_count > data.character.data.length) {
|
if (data.character.total_count > data.character.data.length) {
|
||||||
loadingText = LOADING_TEXT_MAP.newCharacter(data.character.data.length, data.character.total_count);
|
// 角色生成中
|
||||||
} else {
|
} 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) {
|
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 realShotResultData = data.shot_sketch.data.filter((item: any) => item.url);
|
||||||
const sketchList = [];
|
const sketchList = [];
|
||||||
for (const sketch of realShotResultData) {
|
for (const sketch of data.shot_sketch.data) {
|
||||||
sketchList.push({
|
sketchList.push({
|
||||||
url: sketch.url,
|
url: sketch.url,
|
||||||
script: sketch.description,
|
script: sketch.description,
|
||||||
|
status: sketch.url ? 1 : (data.shot_sketch.task_status === 'COMPLETED' ? 2 : 0)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
taskData.shot_sketch.data = sketchList;
|
taskCurrent.shot_sketch.data = sketchList;
|
||||||
taskData.shot_sketch.total_count = data.shot_sketch.total_count;
|
taskCurrent.shot_sketch.total_count = data.shot_sketch.total_count;
|
||||||
setTaskSketch(sketchList);
|
|
||||||
setTaskShotSketch(sketchList);
|
|
||||||
updateSketchCount(sketchList.length);
|
|
||||||
// 设置为最后一个草图
|
// 设置为最后一个草图
|
||||||
if (data.shot_sketch.total_count > realShotResultData.length) {
|
if (data.shot_sketch.total_count > realShotResultData.length) {
|
||||||
setIsGeneratingSketch(true);
|
// 草图生成中
|
||||||
loadingText = LOADING_TEXT_MAP.shotSketch(realShotResultData.length, data.shot_sketch.total_count);
|
|
||||||
} else {
|
} 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) {
|
if (data.video.data) {
|
||||||
const realDataVideoData = data.video.data.filter((item: any) => (item.urls || (item.video_status !== 0 && item.video_status !== undefined)));
|
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') {
|
taskCurrent.currentStage = 'video';
|
||||||
loadingText = LOADING_TEXT_MAP.video(0, data.video.total_count);
|
taskCurrent.videos.total_count = data.video.total_count;
|
||||||
}
|
|
||||||
if (realDataVideoData.length > 0) {
|
|
||||||
taskData.currentStage = 'video';
|
|
||||||
}
|
|
||||||
const videoList = [];
|
const videoList = [];
|
||||||
console.log('----------data.video.data', data.video.data);
|
console.log('----------data.video.data', data.video.data);
|
||||||
for (const video of 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 有多个视频 默认取存在的项
|
// 每一项 video 有多个视频 默认取存在的项
|
||||||
videoList.push({
|
videoList.push({
|
||||||
url: video.urls,
|
urls: video.urls,
|
||||||
script: video.description,
|
|
||||||
audio: null,
|
|
||||||
video_id: video.video_id,
|
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;
|
taskCurrent.videos.data = videoList;
|
||||||
taskData.video.total_count = data.video.total_count;
|
|
||||||
setTaskVideos(videoList);
|
|
||||||
updateVideoCount(videoList.length);
|
|
||||||
// 如果在视频步骤,设置为最后一个视频
|
// 如果在视频步骤,设置为最后一个视频
|
||||||
if (data.video.total_count > realDataVideoData.length) {
|
if (data.video.total_count > realDataVideoData.length) {
|
||||||
setIsGeneratingVideo(true);
|
// 视频生成中
|
||||||
loadingText = LOADING_TEXT_MAP.video(realDataVideoData.length, data.video.total_count);
|
|
||||||
} else {
|
} 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) {
|
if (data.final_simple_video && data.final_simple_video.video) {
|
||||||
taskData.currentStage = 'final_video';
|
taskCurrent.currentStage = 'final_video';
|
||||||
setFinal({
|
taskCurrent.final.url = data.final_simple_video.video;
|
||||||
url: (data as any).final_simple_video.video
|
taskCurrent.final.note = 'simple';
|
||||||
});
|
|
||||||
taskData.status = '5.5';
|
|
||||||
loadingText = LOADING_TEXT_MAP.postProduction('generating fine-grained video clips...');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.final_video && data.final_video.video) {
|
if (data.final_video && data.final_video.video) {
|
||||||
taskData.currentStage = 'final_video';
|
taskCurrent.currentStage = 'final_video';
|
||||||
setFinal({
|
taskCurrent.final.url = data.final_video.video;
|
||||||
url: data.final_video.video
|
taskCurrent.final.note = 'final';
|
||||||
});
|
|
||||||
taskData.status = '6';
|
|
||||||
loadingText = LOADING_TEXT_MAP.complete;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('---look-taskData', taskData);
|
console.log('---look-taskData', taskCurrent);
|
||||||
|
|
||||||
// 设置步骤
|
// 设置步骤
|
||||||
setCurrentStage(taskData.currentStage);
|
|
||||||
setCurrentStep(taskData.status);
|
|
||||||
setTaskObject(prev => {
|
setTaskObject(prev => {
|
||||||
if (!prev) return null;
|
const newState = JSON.parse(JSON.stringify({...prev, ...taskCurrent}));
|
||||||
return {
|
return newState;
|
||||||
...prev,
|
|
||||||
taskStatus: taskData.status
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
console.log('---------loadingText', loadingText);
|
|
||||||
setCurrentLoadingText(loadingText);
|
|
||||||
|
|
||||||
// 设置是否需要获取流式数据
|
// 设置是否需要获取流式数据
|
||||||
setNeedStreamData(status !== 'COMPLETED' && taskData.status !== '6');
|
setNeedStreamData(status !== 'COMPLETED');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('初始化失败:', error);
|
console.error('初始化失败:', error);
|
||||||
@ -684,15 +615,8 @@ export function useWorkflowData() {
|
|||||||
// 回退到 指定状态 重新获取数据
|
// 回退到 指定状态 重新获取数据
|
||||||
const fallbackToStep = (step: string) => {
|
const fallbackToStep = (step: string) => {
|
||||||
console.log('fallbackToStep', step);
|
console.log('fallbackToStep', step);
|
||||||
setCurrentStep(step);
|
|
||||||
setNeedStreamData(true);
|
setNeedStreamData(true);
|
||||||
taskData = {
|
tempTaskObject.current.currentStage = step as Stage;
|
||||||
sketch: { data: [], total_count: -1 },
|
|
||||||
character: { data: [], total_count: -1 },
|
|
||||||
shot_sketch: { data: [], total_count: -1 },
|
|
||||||
video: { data: [], total_count: -1 },
|
|
||||||
status: step
|
|
||||||
};
|
|
||||||
// loadingText = LOADING_TEXT_MAP.initializing;
|
// loadingText = LOADING_TEXT_MAP.initializing;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -747,7 +671,6 @@ export function useWorkflowData() {
|
|||||||
setAnyAttribute,
|
setAnyAttribute,
|
||||||
applyScript,
|
applyScript,
|
||||||
fallbackToStep,
|
fallbackToStep,
|
||||||
originalText,
|
originalText
|
||||||
currentStage
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -73,13 +73,7 @@ export const ScriptRenderer: React.FC<ScriptRendererProps> = ({ data, setIsPause
|
|||||||
return <em className="italic">{content.text}</em>;
|
return <em className="italic">{content.text}</em>;
|
||||||
default:
|
default:
|
||||||
return <p className="mb-2">
|
return <p className="mb-2">
|
||||||
{
|
<span>{content.text}</span>
|
||||||
(isInit && from !== 'tab') ? (
|
|
||||||
<TypewriterText text={content.text || ''} stableId={content.type} />
|
|
||||||
) : (
|
|
||||||
<span>{content.text}</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</p>;
|
</p>;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -17,14 +17,8 @@ interface EditModalProps {
|
|||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
activeEditTab: string;
|
activeEditTab: string;
|
||||||
taskStatus: string;
|
|
||||||
taskSketch: any[];
|
|
||||||
sketchVideo: any[];
|
|
||||||
taskScenes: any[];
|
|
||||||
currentSketchIndex: number;
|
currentSketchIndex: number;
|
||||||
onSketchSelect: (index: number) => void;
|
|
||||||
roles?: any[];
|
roles?: any[];
|
||||||
music?: any;
|
|
||||||
setIsPauseWorkFlow: (isPauseWorkFlow: boolean) => void;
|
setIsPauseWorkFlow: (isPauseWorkFlow: boolean) => void;
|
||||||
isPauseWorkFlow: boolean;
|
isPauseWorkFlow: boolean;
|
||||||
fallbackToStep: any;
|
fallbackToStep: any;
|
||||||
@ -45,14 +39,8 @@ export function EditModal({
|
|||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
activeEditTab,
|
activeEditTab,
|
||||||
taskStatus,
|
|
||||||
taskSketch,
|
|
||||||
sketchVideo,
|
|
||||||
taskScenes,
|
|
||||||
currentSketchIndex,
|
currentSketchIndex,
|
||||||
onSketchSelect,
|
|
||||||
roles = [],
|
roles = [],
|
||||||
music,
|
|
||||||
setIsPauseWorkFlow,
|
setIsPauseWorkFlow,
|
||||||
isPauseWorkFlow,
|
isPauseWorkFlow,
|
||||||
fallbackToStep,
|
fallbackToStep,
|
||||||
@ -83,8 +71,8 @@ const [pendingSwitchTabId, setPendingSwitchTabId] = useState<string | null>(null
|
|||||||
if (tabId === 'settings') return false;
|
if (tabId === 'settings') return false;
|
||||||
// 换成 如果对应标签下 数据存在 就不禁用
|
// 换成 如果对应标签下 数据存在 就不禁用
|
||||||
// if (tabId === '1') return roles.length === 0;
|
// if (tabId === '1') return roles.length === 0;
|
||||||
if (tabId === '2') return taskScenes.length === 0;
|
// if (tabId === '2') return taskScenes.length === 0;
|
||||||
if (tabId === '3') return sketchVideo.length === 0;
|
// if (tabId === '3') return sketchVideo.length === 0;
|
||||||
if (tabId === '4') return false;
|
if (tabId === '4') return false;
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
@ -144,10 +132,10 @@ const [pendingSwitchTabId, setPendingSwitchTabId] = useState<string | null>(null
|
|||||||
console.log('handleConfirmGotoFallback');
|
console.log('handleConfirmGotoFallback');
|
||||||
SaveEditUseCase.saveData();
|
SaveEditUseCase.saveData();
|
||||||
if (activeTab === '0') {
|
if (activeTab === '0') {
|
||||||
fallbackToStep('0');
|
fallbackToStep('script');
|
||||||
// 应用剧本
|
// 应用剧本
|
||||||
} else {
|
} else {
|
||||||
fallbackToStep('1');
|
fallbackToStep('video');
|
||||||
}
|
}
|
||||||
setIsRemindFallbackOpen(false);
|
setIsRemindFallbackOpen(false);
|
||||||
// 关闭弹窗
|
// 关闭弹窗
|
||||||
@ -200,9 +188,7 @@ const [pendingSwitchTabId, setPendingSwitchTabId] = useState<string | null>(null
|
|||||||
case '2':
|
case '2':
|
||||||
return (
|
return (
|
||||||
<SceneTabContent
|
<SceneTabContent
|
||||||
taskSketch={taskScenes}
|
|
||||||
currentSketchIndex={currentIndex}
|
currentSketchIndex={currentIndex}
|
||||||
onSketchSelect={hanldeChangeSelect}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case '3':
|
case '3':
|
||||||
@ -215,10 +201,7 @@ const [pendingSwitchTabId, setPendingSwitchTabId] = useState<string | null>(null
|
|||||||
case '4':
|
case '4':
|
||||||
return (
|
return (
|
||||||
<MusicTabContent
|
<MusicTabContent
|
||||||
taskSketch={taskSketch}
|
|
||||||
currentSketchIndex={currentIndex}
|
currentSketchIndex={currentIndex}
|
||||||
onSketchSelect={onSketchSelect}
|
|
||||||
music={music}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case 'settings':
|
case 'settings':
|
||||||
|
|||||||
@ -17,18 +17,20 @@ interface Music {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface MusicTabContentProps {
|
interface MusicTabContentProps {
|
||||||
taskSketch: any[];
|
|
||||||
currentSketchIndex: number;
|
currentSketchIndex: number;
|
||||||
onSketchSelect: (index: number) => void;
|
|
||||||
music?: Music;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MusicTabContent({
|
export function MusicTabContent({
|
||||||
taskSketch,
|
|
||||||
currentSketchIndex,
|
currentSketchIndex,
|
||||||
onSketchSelect,
|
|
||||||
music
|
|
||||||
}: MusicTabContentProps) {
|
}: MusicTabContentProps) {
|
||||||
|
const music = {
|
||||||
|
url: '',
|
||||||
|
script: '',
|
||||||
|
name: '',
|
||||||
|
duration: '',
|
||||||
|
totalDuration: '',
|
||||||
|
isLooped: true,
|
||||||
|
}
|
||||||
const [isReplaceModalOpen, setIsReplaceModalOpen] = useState(false);
|
const [isReplaceModalOpen, setIsReplaceModalOpen] = useState(false);
|
||||||
const [activeMethod, setActiveMethod] = useState('upload');
|
const [activeMethod, setActiveMethod] = useState('upload');
|
||||||
const [isPlaying, setIsPlaying] = useState(false);
|
const [isPlaying, setIsPlaying] = useState(false);
|
||||||
|
|||||||
@ -163,7 +163,7 @@ export function ReplacePanel({
|
|||||||
ref={el => {
|
ref={el => {
|
||||||
if (el) videoRefs.current[shot.id] = 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"
|
className="w-full h-full object-cover"
|
||||||
loop
|
loop
|
||||||
muted
|
muted
|
||||||
|
|||||||
@ -29,9 +29,7 @@ interface SceneEnvironment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface SceneTabContentProps {
|
interface SceneTabContentProps {
|
||||||
taskSketch: any[];
|
|
||||||
currentSketchIndex: number;
|
currentSketchIndex: number;
|
||||||
onSketchSelect: (index: number) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SceneSketch {
|
interface SceneSketch {
|
||||||
@ -65,22 +63,23 @@ const mockSketch: SceneSketch = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function SceneTabContent({
|
export function SceneTabContent({
|
||||||
taskSketch = [],
|
currentSketchIndex = 0
|
||||||
currentSketchIndex = 0,
|
|
||||||
onSketchSelect
|
|
||||||
}: SceneTabContentProps) {
|
}: SceneTabContentProps) {
|
||||||
const thumbnailsRef = useRef<HTMLDivElement>(null);
|
const thumbnailsRef = useRef<HTMLDivElement>(null);
|
||||||
const scriptsRef = useRef<HTMLDivElement>(null);
|
const scriptsRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// 确保 taskSketch 是数组
|
// 确保 taskSketch 是数组
|
||||||
const sketches = Array.isArray(taskSketch) ? taskSketch : [];
|
const sketches: any[] = [];
|
||||||
|
|
||||||
const [localSketch, setLocalSketch] = useState(mockSketch);
|
const [localSketch, setLocalSketch] = useState(mockSketch);
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const [isReplacePanelOpen, setIsReplacePanelOpen] = useState(false);
|
const [isReplacePanelOpen, setIsReplacePanelOpen] = useState(false);
|
||||||
const [replacePanelKey, setReplacePanelKey] = useState(0);
|
const [replacePanelKey, setReplacePanelKey] = useState(0);
|
||||||
const [ignoreReplace, setIgnoreReplace] = useState(false);
|
const [ignoreReplace, setIgnoreReplace] = useState(false);
|
||||||
const [currentScene, setCurrentScene] = useState(taskSketch[currentSketchIndex]);
|
const [currentScene, setCurrentScene] = useState({
|
||||||
|
url: '',
|
||||||
|
script: ''
|
||||||
|
});
|
||||||
|
|
||||||
// 天气图标映射
|
// 天气图标映射
|
||||||
const weatherIcons = {
|
const weatherIcons = {
|
||||||
@ -144,12 +143,12 @@ export function SceneTabContent({
|
|||||||
}, [currentSketchIndex]);
|
}, [currentSketchIndex]);
|
||||||
|
|
||||||
const handleReplaceScene = (url: string) => {
|
const handleReplaceScene = (url: string) => {
|
||||||
setCurrentScene({
|
// setCurrentScene({
|
||||||
...currentScene,
|
// ...currentScene,
|
||||||
url: url
|
// url: url
|
||||||
});
|
// });
|
||||||
|
|
||||||
setIsReplacePanelOpen(true);
|
// setIsReplacePanelOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleConfirmReplace = (selectedShots: string[], addToLibrary: boolean) => {
|
const handleConfirmReplace = (selectedShots: string[], addToLibrary: boolean) => {
|
||||||
@ -165,17 +164,16 @@ export function SceneTabContent({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleChangeScene = (index: number) => {
|
const handleChangeScene = (index: number) => {
|
||||||
if (currentScene?.url !== taskSketch[currentSketchIndex]?.url && !ignoreReplace) {
|
// if (currentScene?.url !== sketches[currentSketchIndex]?.url && !ignoreReplace) {
|
||||||
// 提示 场景已修改,弹出替换场景面板
|
// // 提示 场景已修改,弹出替换场景面板
|
||||||
if (isReplacePanelOpen) {
|
// if (isReplacePanelOpen) {
|
||||||
setReplacePanelKey(replacePanelKey + 1);
|
// setReplacePanelKey(replacePanelKey + 1);
|
||||||
} else {
|
// } else {
|
||||||
setIsReplacePanelOpen(true);
|
// setIsReplacePanelOpen(true);
|
||||||
}
|
// }
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
onSketchSelect(index);
|
// setCurrentScene(sketches[index]);
|
||||||
setCurrentScene(taskSketch[index]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 如果没有数据,显示空状态
|
// 如果没有数据,显示空状态
|
||||||
|
|||||||
@ -238,7 +238,7 @@ export function ShotTabContent({
|
|||||||
)}
|
)}
|
||||||
{shot.status === 1 && (
|
{shot.status === 1 && (
|
||||||
<video
|
<video
|
||||||
src={shot.videoUrl[0]}
|
src={shot.videoUrl[0].video_url}
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
muted
|
muted
|
||||||
loop
|
loop
|
||||||
@ -342,7 +342,7 @@ export function ShotTabContent({
|
|||||||
layoutId={`video-preview-${selectedIndex}`}
|
layoutId={`video-preview-${selectedIndex}`}
|
||||||
>
|
>
|
||||||
<PersonDetectionScene
|
<PersonDetectionScene
|
||||||
videoSrc={shotData[selectedIndex]?.videoUrl[0]}
|
videoSrc={shotData[selectedIndex]?.videoUrl[0].video_url}
|
||||||
detections={detections}
|
detections={detections}
|
||||||
scanState={scanState}
|
scanState={scanState}
|
||||||
triggerScan={scanState === 'scanning'}
|
triggerScan={scanState === 'scanning'}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user