diff --git a/api/DTO/movieEdit.ts b/api/DTO/movieEdit.ts
index 62b67ea..ded289a 100644
--- a/api/DTO/movieEdit.ts
+++ b/api/DTO/movieEdit.ts
@@ -621,7 +621,7 @@ export interface RoleResponse {
}
-interface Role {
+export interface Role {
name: string;
url: string;
status: number;
@@ -636,12 +636,30 @@ interface ShotSketch {
script: string;
status: number;
}
-interface Video {
+export interface ShotVideo {
video_id: string;
urls: string[];
video_status: number;
}
+// 执行loading文字映射
+export const LOADING_TEXT_MAP = {
+ initializing: 'initializing...',
+ script: 'Generating script...',
+ getSketchStatus: 'Getting sketch status...',
+ sketch: (count: number, total: number) => `Generating sketch ${count}/${total}...`,
+ character: 'Getting character status...',
+ newCharacter: (count: number, total: number) => `Drawing character ${count}/${total}...`,
+ getShotSketchStatus: 'Getting shot sketch status...',
+ shotSketch: (count: number, total: number) => `Generating shot sketch ${count}/${total}...`,
+ getVideoStatus: 'Getting video status...',
+ video: (count: number, total: number) => `Generating video ${count}/${total}...`,
+ audio: 'Generating background audio...',
+ postProduction: (step: string) => `Post-production: ${step}...`,
+ final: 'Generating final product...',
+ complete: 'Task completed'
+} as const;
+
export type Status = 'IN_PROGRESS' | 'COMPLETED' | 'FAILED';
export type Stage = 'script' | 'character' | 'scene' | 'shot_sketch' | 'video' | 'final_video';
// 添加 TaskObject 接口
@@ -663,7 +681,7 @@ export interface TaskObject {
total_count: number;
}; // 分镜草图
videos: {
- data: Video[];
+ data: ShotVideo[];
total_count: number;
}; // 视频
final: {
diff --git a/app/service/Interaction/RoleShotService.ts b/app/service/Interaction/RoleShotService.ts
index 4c75ed1..de33edf 100644
--- a/app/service/Interaction/RoleShotService.ts
+++ b/app/service/Interaction/RoleShotService.ts
@@ -179,6 +179,7 @@ export const useRoleShotServiceHook = (projectId: string,selectRole?:RoleEntity,
return role.fromDraft
});
console.log('newDraftRoleList', newDraftRoleList)
+ console.log('应用角色到分镜', shotSelectionList)
// 循环调用接口,为每个选中的分镜单独调用
const res = await Promise.all( shotSelectionList.map(async (shot) => {
// 调用应用角色到分镜接口(不等待完成)
@@ -191,6 +192,8 @@ export const useRoleShotServiceHook = (projectId: string,selectRole?:RoleEntity,
})
}))
+ console.log('应用角色到分镜', res);
+
SaveEditUseCase.setVideoTasks([
...SaveEditUseCase.videoTasks,
...res.map(item=>{
diff --git a/components/pages/work-flow.tsx b/components/pages/work-flow.tsx
index 991adbf..c2de686 100644
--- a/components/pages/work-flow.tsx
+++ b/components/pages/work-flow.tsx
@@ -33,17 +33,9 @@ const WorkFlow = React.memo(function WorkFlow() {
const {
taskObject,
scriptData,
- taskSketch,
- taskVideos,
- sketchCount,
isLoading,
- currentStep,
currentSketchIndex,
- isGeneratingSketch,
- isGeneratingVideo,
currentLoadingText,
- totalSketchCount,
- final,
dataLoadError,
setCurrentSketchIndex,
retryLoadData,
@@ -58,9 +50,8 @@ const WorkFlow = React.memo(function WorkFlow() {
const {
isVideoPlaying,
- togglePlay,
toggleVideoPlay,
- } = usePlaybackControls(taskSketch, taskVideos, currentStep);
+ } = usePlaybackControls(taskObject.videos.data, taskObject.currentStage);
useEffect(() => {
console.log('changedIndex_work-flow', currentSketchIndex, taskObject);
@@ -97,10 +88,8 @@ const WorkFlow = React.memo(function WorkFlow() {
@@ -170,14 +159,7 @@ const WorkFlow = React.memo(function WorkFlow() {
@@ -228,6 +210,7 @@ const WorkFlow = React.memo(function WorkFlow() {
SaveEditUseCase.clearData();
setIsEditModalOpen(false)
}}
+ taskObject={taskObject}
currentSketchIndex={currentSketchIndex}
roles={taskObject.roles.data}
setIsPauseWorkFlow={setIsPauseWorkFlow}
diff --git a/components/pages/work-flow/task-info.tsx b/components/pages/work-flow/task-info.tsx
index b972cb0..d7d2a1d 100644
--- a/components/pages/work-flow/task-info.tsx
+++ b/components/pages/work-flow/task-info.tsx
@@ -10,12 +10,11 @@ import {
Film,
Scissors
} from 'lucide-react';
+import { TaskObject } from '@/api/DTO/movieEdit';
interface TaskInfoProps {
- isLoading: boolean;
- taskObject: any;
+ taskObject: TaskObject;
currentLoadingText: string;
- dataLoadError?: string | null;
roles: any[];
isPauseWorkFlow: boolean;
}
@@ -120,17 +119,14 @@ const StageIcons = ({ currentStage, isExpanded, isPauseWorkFlow }: { currentStag
);
};
-export function TaskInfo({
- isLoading,
- taskObject,
+export function TaskInfo({
+ taskObject,
currentLoadingText,
- dataLoadError,
roles,
isPauseWorkFlow
}: TaskInfoProps) {
const [isScriptModalOpen, setIsScriptModalOpen] = useState(false);
const [currentStage, setCurrentStage] = useState(0);
- const [isShowScriptIcon, setIsShowScriptIcon] = useState(true);
const [isStageIconsExpanded, setIsStageIconsExpanded] = useState(false);
const timerRef = useRef(null);
@@ -146,72 +142,39 @@ export function TaskInfo({
timerRef.current = null;
}
+ // 统一更新currentStage
+ if (currentLoadingText.includes('initializing') || currentLoadingText.includes('script') || currentLoadingText.includes('character')) {
+ setCurrentStage(0);
+ } else if (currentLoadingText.includes('sketch') && !currentLoadingText.includes('shot sketch')) {
+ setCurrentStage(1);
+ } else if (!currentLoadingText.includes('Post-production') && (currentLoadingText.includes('shot sketch') || currentLoadingText.includes('video'))) {
+ setCurrentStage(2);
+ } else if (currentLoadingText.includes('Post-production')) {
+ setCurrentStage(3);
+ }
+
if (currentLoadingText.includes('Task completed')) {
console.log('Closing modal at completion');
setIsScriptModalOpen(false);
- setIsShowScriptIcon(false);
}
- if (currentLoadingText.includes('Post-production')) {
- if (isScriptModalOpen) {
- setIsScriptModalOpen(false);
- }
- setCurrentStage(3);
+ if (currentLoadingText.includes('Post-production') || currentLoadingText.includes('status')) {
console.log('isScriptModalOpen-Post-production', currentLoadingText, isScriptModalOpen);
- timerRef.current = setTimeout(() => {
- setIsScriptModalOpen(true);
- }, 8000);
- }
- if (currentLoadingText.includes('Generating video')) {
- console.log('isScriptModalOpen-video', currentLoadingText, isScriptModalOpen);
if (isScriptModalOpen) {
setIsScriptModalOpen(false);
- setCurrentStage(2);
-
- // 延迟8s 再次打开
- timerRef.current = setTimeout(() => {
- setIsScriptModalOpen(true);
- }, 8000);
} else {
setIsScriptModalOpen(true);
- setCurrentStage(2);
- }
- }
- if (currentLoadingText.includes('video status')) {
- if (isScriptModalOpen) {
- setIsScriptModalOpen(false);
- }
- setCurrentStage(2);
- }
- if (currentLoadingText.includes('Generating sketch') || currentLoadingText.includes('Generating shot sketch')) {
- console.log('isScriptModalOpen-sketch', currentLoadingText, isScriptModalOpen);
- if (isScriptModalOpen) {
- setIsScriptModalOpen(false);
- setCurrentStage(1);
-
- // 延迟8s 再次打开
timerRef.current = setTimeout(() => {
- setIsScriptModalOpen(true);
+ setIsScriptModalOpen(false);
}, 8000);
- } else {
- setIsScriptModalOpen(true);
- setCurrentStage(1);
}
}
- if (currentLoadingText.includes('sketch status')) {
- if (isScriptModalOpen) {
- setIsScriptModalOpen(false);
- }
- setCurrentStage(1);
- }
if (currentLoadingText.includes('script')) {
console.log('isScriptModalOpen-script', currentLoadingText, isScriptModalOpen);
setIsScriptModalOpen(true);
- setCurrentStage(0);
}
if (currentLoadingText.includes('initializing')) {
console.log('isScriptModalOpen-initializing', currentLoadingText, isScriptModalOpen);
setIsScriptModalOpen(true);
- setCurrentStage(0);
}
return () => {
if (timerRef.current) {
diff --git a/components/pages/work-flow/thumbnail-grid.tsx b/components/pages/work-flow/thumbnail-grid.tsx
index fdd86f6..3a82032 100644
--- a/components/pages/work-flow/thumbnail-grid.tsx
+++ b/components/pages/work-flow/thumbnail-grid.tsx
@@ -10,28 +10,14 @@ import { TaskObject } from '@/api/DTO/movieEdit';
interface ThumbnailGridProps {
isDisabledFocus: boolean;
taskObject: TaskObject;
- isLoading: boolean;
currentSketchIndex: number;
- taskSketch: any[];
- taskVideos: any[];
- isGeneratingSketch: boolean;
- isGeneratingVideo: boolean;
- sketchCount: number;
- totalSketchCount: number;
onSketchSelect: (index: number) => void;
}
export function ThumbnailGrid({
isDisabledFocus,
taskObject,
- isLoading,
currentSketchIndex,
- taskSketch,
- taskVideos,
- isGeneratingSketch,
- isGeneratingVideo,
- sketchCount,
- totalSketchCount,
onSketchSelect
}: ThumbnailGridProps) {
const thumbnailsRef = useRef(null);
@@ -166,89 +152,11 @@ export function ThumbnailGrid({
console.log('taskObject.currentStage_thumbnail-grid', taskObject.currentStage);
}, [taskObject.currentStage]);
- // 渲染加载状态
- if (isLoading) {
- return (
- <>
-
-
-
-
- >
- );
- }
-
// 粗剪/精剪最终成片阶段不显示缩略图
if (taskObject.currentStage === 'final_video') {
return null;
}
- // 渲染生成中的缩略图
- const renderGeneratingThumbnail = () => {
- 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 (
-
- {/* 动态渐变背景 */}
-
- {/* 动态光效 */}
-
-
-
- Scene {sketchCount + 1}
-
-
- );
- };
-
// 渲染视频阶段的缩略图
const renderVideoThumbnails = () => (
taskObject.videos.data.map((video, index) => {
@@ -354,7 +262,6 @@ export function ThumbnailGrid({
);
})}
- {isGeneratingSketch && sketchCount < totalSketchCount && renderGeneratingThumbnail()}
>
);
diff --git a/components/pages/work-flow/use-edit-data.tsx b/components/pages/work-flow/use-edit-data.tsx
index 5217cf5..33a81c4 100644
--- a/components/pages/work-flow/use-edit-data.tsx
+++ b/components/pages/work-flow/use-edit-data.tsx
@@ -5,13 +5,14 @@ import { useSearchParams } from 'next/navigation';
import { useRoleServiceHook } from "@/app/service/Interaction/RoleService";
import { useRoleShotServiceHook } from "@/app/service/Interaction/RoleShotService";
import { useScriptService } from "@/app/service/Interaction/ScriptService";
+import { VideoSegmentEntity } from "@/app/service/domain/Entities";
export const useEditData = (tabType: string, originalText?: string) => {
const searchParams = useSearchParams();
const projectId = searchParams.get('episodeId') || '';
const [loading, setLoading] = useState(true);
const [scriptData, setScriptData] = useState([]);
- const [shotData, setShotData] = useState([]);
+ const [shotData, setShotData] = useState([]);
const [roleData, setRoleData] = useState([]);
@@ -24,6 +25,7 @@ export const useEditData = (tabType: string, originalText?: string) => {
const {
videoSegments,
+ selectedSegment,
scriptRoles,
getVideoSegmentList,
setSelectedSegment,
@@ -123,6 +125,7 @@ export const useEditData = (tabType: string, originalText?: string) => {
applyScript,
// shot
shotData,
+ selectedSegment,
scriptRoles,
setSelectedSegment,
regenerateVideoSegment,
diff --git a/components/pages/work-flow/use-playback-controls.tsx b/components/pages/work-flow/use-playback-controls.tsx
index 166a403..740377e 100644
--- a/components/pages/work-flow/use-playback-controls.tsx
+++ b/components/pages/work-flow/use-playback-controls.tsx
@@ -2,7 +2,7 @@
import { useState, useRef, useEffect, useCallback } from 'react';
-export function usePlaybackControls(taskSketch: any[], taskVideos: any[], currentStep: string) {
+export function usePlaybackControls(taskVideos: any[], currentStage: string) {
const [isPlaying, setIsPlaying] = useState(false);
const [isVideoPlaying, setIsVideoPlaying] = useState(true);
const [showControls, setShowControls] = useState(false);
@@ -19,24 +19,6 @@ export function usePlaybackControls(taskSketch: any[], taskVideos: any[], curren
setIsVideoPlaying(prev => !prev);
}, []);
- // 自动播放逻辑 - 分镜草图(移除重复的定时器逻辑,由主组件处理)
- // useEffect(() => {
- // if (isPlaying && taskSketch.length > 0) {
- // playTimerRef.current = setInterval(() => {
- // // 这里的切换逻辑需要在父组件中处理
- // // 因为需要访问 setCurrentSketchIndex
- // }, 1000);
- // } else if (playTimerRef.current) {
- // clearInterval(playTimerRef.current);
- // }
-
- // return () => {
- // if (playTimerRef.current) {
- // clearInterval(playTimerRef.current);
- // }
- // };
- // }, [isPlaying, taskSketch.length]);
-
// 视频自动播放逻辑
useEffect(() => {
if (isVideoPlaying && taskVideos.length > 0) {
@@ -55,20 +37,12 @@ export function usePlaybackControls(taskSketch: any[], taskVideos: any[], curren
};
}, [isVideoPlaying, taskVideos.length]);
- // 当切换到视频模式时,停止分镜草图播放(注释掉,让用户手动控制)
- // useEffect(() => {
- // if (Number(currentStep) >= 3) {
- // console.log('切换到步骤3+,停止分镜草图播放');
- // setIsPlaying(false);
- // }
- // }, [currentStep]);
-
// 当切换到分镜草图模式时,停止视频播放
useEffect(() => {
- if (currentStep !== '3') {
+ if (currentStage !== 'video') {
setIsVideoPlaying(false);
}
- }, [currentStep]);
+ }, [currentStage]);
return {
isPlaying,
diff --git a/components/pages/work-flow/use-workflow-data.tsx b/components/pages/work-flow/use-workflow-data.tsx
index a6939fa..df92ee4 100644
--- a/components/pages/work-flow/use-workflow-data.tsx
+++ b/components/pages/work-flow/use-workflow-data.tsx
@@ -3,43 +3,9 @@
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
import { useSearchParams } from 'next/navigation';
import { detailScriptEpisodeNew, getScriptTitle, getRunningStreamData, pauseMovieProjectPlan, resumeMovieProjectPlan } from '@/api/video_flow';
-import { useAppDispatch, useAppSelector } from '@/lib/store/hooks';
-import { setSketchCount, setVideoCount } from '@/lib/store/workflowSlice';
import { useScriptService } from "@/app/service/Interaction/ScriptService";
import { useUpdateEffect } from '@/app/hooks/useUpdateEffect';
-import { TaskObject, Status, Stage } from '@/api/DTO/movieEdit';
-
-// 步骤映射
-const STEP_MAP = {
- 'initializing': '0',
- 'sketch': '1',
- 'character': '2',
- 'video': '3',
- 'music': '4',
- 'final_video': '6'
-} as const;
-// 执行loading文字映射
-const LOADING_TEXT_MAP = {
- initializing: 'initializing...',
- script: 'Generating script...',
- getSketchStatus: 'Getting sketch status...',
- sketch: (count: number, total: number) => `Generating sketch ${count}/${total}...`,
- sketchComplete: 'Sketch generation complete',
- character: 'Drawing characters...',
- newCharacter: (count: number, total: number) => `Drawing character ${count}/${total}...`,
- getShotSketchStatus: 'Getting shot sketch status...',
- shotSketch: (count: number, total: number) => `Generating shot sketch ${count}/${total}...`,
- getVideoStatus: 'Getting video status...',
- video: (count: number, total: number) => `Generating video ${count}/${total}...`,
- videoComplete: 'Video generation complete',
- audio: 'Generating background audio...',
- postProduction: (step: string) => `Post-production: ${step}...`,
- final: 'Generating final product...',
- complete: 'Task completed'
-} as const;
-
-type ApiStep = keyof typeof STEP_MAP;
-
+import { LOADING_TEXT_MAP, TaskObject, Status, Stage } from '@/api/DTO/movieEdit';
export function useWorkflowData() {
useEffect(() => {
@@ -53,7 +19,7 @@ export function useWorkflowData() {
let tempTaskObject = useRef({
title: '',
tags: [],
- currentStage: 'script',
+ currentStage: 'script' as Stage,
status: 'IN_PROGRESS' as Status,
roles: {
data: [],
@@ -81,19 +47,8 @@ export function useWorkflowData() {
// 更新 taskObject 的类型
const [taskObject, setTaskObject] = useState(tempTaskObject.current);
- const [taskSketch, setTaskSketch] = useState([]);
- const [taskScenes, setTaskScenes] = useState([]);
- const [taskShotSketch, setTaskShotSketch] = useState([]);
- const [taskVideos, setTaskVideos] = useState([]);
- const [currentStep, setCurrentStep] = useState('0');
const [currentSketchIndex, setCurrentSketchIndex] = useState(0);
- const [isGeneratingSketch, setIsGeneratingSketch] = useState(false);
- const [isGeneratingVideo, setIsGeneratingVideo] = useState(false);
const [currentLoadingText, setCurrentLoadingText] = useState('loading project info...');
- const [totalSketchCount, setTotalSketchCount] = useState(0);
- const [roles, setRoles] = useState([]);
- const [music, setMusic] = useState([]);
- const [final, setFinal] = useState(null);
const [dataLoadError, setDataLoadError] = useState(null);
const [needStreamData, setNeedStreamData] = useState(false);
const [isPauseWorkFlow, setIsPauseWorkFlow] = useState(false);
@@ -103,10 +58,6 @@ export function useWorkflowData() {
isLoading: true
});
-
- const dispatch = useAppDispatch();
- const { sketchCount, videoCount } = useAppSelector((state) => state.workflow);
-
const {
scriptBlocksMemo, // 渲染剧本数据
initializeFromProject,
@@ -170,24 +121,24 @@ export function useWorkflowData() {
console.log('应用剧本');
// 自动模式下 应用剧本;手动模式 需要点击 下一步 触发
// 确保仅自动触发一次
- state.mode.includes('auto') && loadingText.current !== LOADING_TEXT_MAP.getSketchStatus && applyScript();
- loadingText.current = LOADING_TEXT_MAP.getSketchStatus;
+ state.mode.includes('auto') && loadingText.current !== LOADING_TEXT_MAP.character && applyScript();
+ loadingText.current = LOADING_TEXT_MAP.character;
} 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.getSketchStatus;
+ }
+ }
+ 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.getShotSketchStatus;
}
@@ -217,16 +168,6 @@ export function useWorkflowData() {
setCurrentLoadingText(loadingText.current);
}, [scriptBlocksMemo, taskObject.currentStage, taskObject.scenes.data, taskObject.roles.data, taskObject.shot_sketch.data, taskObject.videos.data, taskObject.status], {mode: 'none'});
- // 更新 setSketchCount
- const updateSketchCount = useCallback((count: number) => {
- dispatch(setSketchCount(count));
- }, [dispatch]);
-
- // 更新 setVideoCount
- const updateVideoCount = useCallback((count: number) => {
- dispatch(setVideoCount(count));
- }, [dispatch]);
-
// 将 sketchCount 和 videoCount 放到 redux 中 每一次变化也要更新
// 添加手动播放控制
@@ -249,13 +190,7 @@ export function useWorkflowData() {
throw new Error(response.message);
}
- let sketchCount = 0;
const all_task_data = response.data;
- // all_task_data 下标0 和 下标1 换位置
- const temp = all_task_data[0];
- all_task_data[0] = all_task_data[1];
- all_task_data[1] = temp;
-
const { current: taskCurrent } = tempTaskObject;
console.log('---look-all_task_data', all_task_data);
@@ -266,32 +201,6 @@ export function useWorkflowData() {
for (const task of all_task_data) {
// 如果有已完成的数据,同步到状态
- if (task.task_name === 'generate_sketch' && task.task_result && task.task_result.data) {
- let realSketchResultData = task.task_result.data.filter((item: any) => item.image_path);
- if (task.task_status === 'COMPLETED') {
- realSketchResultData = taskCurrent.scenes.data.filter((item: any) => item.status !== 0);
- }
- console.log('---look-realSketchResultData', realSketchResultData);
- taskCurrent.scenes.total_count = task.task_result.total_count;
- if (task.task_status !== 'COMPLETED' || taskCurrent.scenes.total_count !== realSketchResultData.length) {
- taskCurrent.currentStage = 'scene';
- // 正在生成草图中 替换 sketch 数据
- const sketchList = [];
- for (const sketch of task.task_result.data) {
- sketchList.push({
- url: sketch.image_path,
- script: sketch.sketch_name,
- status: sketch.image_path ? 1 : (task.task_status === 'COMPLETED' ? 2 : 0)
- });
- }
- taskCurrent.scenes.data = sketchList;
- if (task.task_status === 'COMPLETED') {
- // 草图生成完成
- }
- break;
- }
- }
-
if (task.task_name === 'generate_character' && task.task_result && task.task_result.data) {
let realCharacterResultData = task.task_result.data.filter((item: any) => item.image_path);
if (task.task_status === 'COMPLETED') {
@@ -317,6 +226,32 @@ export function useWorkflowData() {
}
}
+
+ if (task.task_name === 'generate_sketch' && task.task_result && task.task_result.data) {
+ let realSketchResultData = task.task_result.data.filter((item: any) => item.image_path);
+ if (task.task_status === 'COMPLETED') {
+ realSketchResultData = taskCurrent.scenes.data.filter((item: any) => item.status !== 0);
+ }
+ console.log('---look-realSketchResultData', realSketchResultData);
+ taskCurrent.scenes.total_count = task.task_result.total_count;
+ if (task.task_status !== 'COMPLETED' || taskCurrent.scenes.total_count !== realSketchResultData.length) {
+ taskCurrent.currentStage = 'scene';
+ // 正在生成草图中 替换 sketch 数据
+ const sketchList = [];
+ for (const sketch of task.task_result.data) {
+ sketchList.push({
+ url: sketch.image_path,
+ script: sketch.sketch_name,
+ status: sketch.image_path ? 1 : (task.task_status === 'COMPLETED' ? 2 : 0)
+ });
+ }
+ taskCurrent.scenes.data = sketchList;
+ if (task.task_status === 'COMPLETED') {
+ // 草图生成完成
+ }
+ break;
+ }
+ }
// debugger;
if (task.task_name === 'generate_shot_sketch' && task.task_result && task.task_result.data) {
@@ -485,27 +420,6 @@ export function useWorkflowData() {
// 如果有已完成的数据,同步到状态
if (data) {
- if (data.sketch && data.sketch.data) {
- taskCurrent.currentStage = 'scene';
- const realSketchResultData = data.sketch.data.filter((item: any) => item.image_path);
- const sketchList = [];
- for (const sketch of data.sketch.data) {
- sketchList.push({
- url: sketch.image_path,
- script: sketch.sketch_name,
- status: sketch.image_path ? 1 : (data.sketch.task_status === 'COMPLETED' ? 2 : 0)
- });
- }
- taskCurrent.scenes.data = sketchList;
- taskCurrent.scenes.total_count = data.sketch.total_count;
- // 设置为最后一个草图
- if (data.sketch.total_count > realSketchResultData.length) {
- // 场景生成中
- setIsGeneratingSketch(true);
- } else {
- // 场景生成完成
- }
- }
if (data.character && data.character.data && data.character.data.length > 0) {
taskCurrent.currentStage = 'character';
const characterList = [];
@@ -524,6 +438,26 @@ export function useWorkflowData() {
// 角色生成完成
}
}
+ if (data.sketch && data.sketch.data) {
+ taskCurrent.currentStage = 'scene';
+ const realSketchResultData = data.sketch.data.filter((item: any) => item.image_path);
+ const sketchList = [];
+ for (const sketch of data.sketch.data) {
+ sketchList.push({
+ url: sketch.image_path,
+ script: sketch.sketch_name,
+ status: sketch.image_path ? 1 : (data.sketch.task_status === 'COMPLETED' ? 2 : 0)
+ });
+ }
+ taskCurrent.scenes.data = sketchList;
+ taskCurrent.scenes.total_count = data.sketch.total_count;
+ // 设置为最后一个草图
+ if (data.sketch.total_count > realSketchResultData.length) {
+ // 场景生成中
+ } else {
+ // 场景生成完成
+ }
+ }
if (data.shot_sketch && data.shot_sketch.data) {
taskCurrent.currentStage = 'shot_sketch';
const realShotResultData = data.shot_sketch.data.filter((item: any) => item.url);
@@ -629,17 +563,7 @@ export function useWorkflowData() {
// 重试加载数据
const retryLoadData = () => {
setDataLoadError(null);
- // 重置所有状态
- setTaskSketch([]);
- setTaskScenes([]);
- setTaskVideos([]);
- updateSketchCount(0);
- updateVideoCount(0);
- setRoles([]);
- setMusic([]);
- setFinal(null);
setCurrentSketchIndex(0);
- setCurrentStep('0');
// 重新初始化
initializeWorkflow();
};
@@ -652,21 +576,9 @@ export function useWorkflowData() {
return {
taskObject,
scriptData,
- taskSketch,
- taskScenes,
- taskShotSketch,
- taskVideos,
- sketchCount,
isLoading: state.isLoading,
- currentStep,
currentSketchIndex,
- isGeneratingSketch,
- isGeneratingVideo,
currentLoadingText,
- totalSketchCount,
- roles,
- music,
- final,
dataLoadError,
setCurrentSketchIndex,
retryLoadData,
diff --git a/components/ui/character-tab-content.tsx b/components/ui/character-tab-content.tsx
index db18732..38aa4c0 100644
--- a/components/ui/character-tab-content.tsx
+++ b/components/ui/character-tab-content.tsx
@@ -11,30 +11,10 @@ import HorizontalScroller from './HorizontalScroller';
import { useEditData } from '@/components/pages/work-flow/use-edit-data';
import { useSearchParams } from 'next/navigation';
import { RoleEntity } from '@/app/service/domain/Entities';
-
-interface Appearance {
- hairStyle: string;
- skinTone: string;
- facialFeatures: string;
- bodyType: string;
-}
-
-interface Role {
- name: string;
- url: string;
- sound: string;
- soundDescription: string;
- roleDescription: string;
- age: number;
- gender: 'male' | 'female' | 'other';
- ethnicity: string;
- appearance: Appearance;
- // 新增标签数组
- tags: string[];
-}
-
+import { Role } from '@/api/DTO/movieEdit';
interface CharacterTabContentProps {
+ originalRoles: Role[];
onClose: () => void;
onApply: () => void;
setActiveTab: (tabId: string) => void;
@@ -42,10 +22,10 @@ interface CharacterTabContentProps {
export const CharacterTabContent = forwardRef<
- { switchBefore: (tabId: string) => boolean, saveBefore: () => void },
+ { switchBefore: (tabId: string) => boolean, saveOrCloseBefore: () => void },
CharacterTabContentProps
>((props, ref) => {
- const { onClose, onApply, setActiveTab } = props;
+ const { onClose, onApply, setActiveTab, originalRoles } = props;
const [isReplacePanelOpen, setIsReplacePanelOpen] = useState(false);
const [replacePanelKey, setReplacePanelKey] = useState(0);
const [ignoreReplace, setIgnoreReplace] = useState(false);
@@ -76,7 +56,6 @@ CharacterTabContentProps
regenerateRole,
fetchUserRoleLibrary,
uploadImageAndUpdateRole,
- changeTabCallback,
// role shot
shotSelectionList,
fetchRoleShots,
@@ -91,7 +70,8 @@ CharacterTabContentProps
switchBefore: (tabId: string) => {
setNextToTabId(tabId);
// 判断 角色是否修改
- const isChange = selectedRole!.isChangeRole
+ const currentIndex = getCurrentIndex();
+ const isChange = currentIndex !== -1 && isRoleChange(originalRoles[currentIndex]);
console.log('switchBefore', isChange);
if (isChange) {
setTriggerType('tab');
@@ -99,15 +79,16 @@ CharacterTabContentProps
}
return isChange;
},
- saveBefore: () => {
- console.log('saveBefore');
+ saveOrCloseBefore: () => {
+ console.log('saveOrCloseBefore');
// 判断 角色是否修改
- changeTabCallback((isChange: Boolean) => {
- if (isChange) {
- setTriggerType('apply');
- handleStartReplaceCharacter();
- }
- });
+ const currentIndex = getCurrentIndex();
+ if (currentIndex !== -1 && isRoleChange(originalRoles[currentIndex])) {
+ setTriggerType('apply');
+ handleStartReplaceCharacter();
+ } else {
+ onClose();
+ }
}
}));
@@ -179,24 +160,33 @@ CharacterTabContentProps
setIsReplacePanelOpen(false);
};
+ // 对比角色是否修改
+ const isRoleChange = (role: Role) => {
+ console.log('对比角色是否修改', role, selectedRole);
+ return role.name !== selectedRole?.name || role.url !== selectedRole?.imageUrl;
+ };
+ // 获取当前选中下标
+ const getCurrentIndex = () => {
+ return originalRoles.findIndex(role => role.name === selectedRole?.name);
+ };
+
const handleChangeRole = (index: number) => {
- const oldRole = roleData.find(role => role.id === selectedRole?.id);
console.log('切换角色前对比');
- changeTabCallback((isChange: Boolean) => {
- if (isChange) {
- setTriggerType('user');
- setIsRemindReplacePanelOpen(true);
- setNextToUserIndex(index);
- return;
- }
+ const currentIndex = getCurrentIndex();
+ if (currentIndex === index) return;
+ if (currentIndex !== -1 && isRoleChange(originalRoles[currentIndex])) {
+ setTriggerType('user');
+ setIsRemindReplacePanelOpen(true);
+ setNextToUserIndex(index);
+ return;
+ }
- // 重置替换规则
- setEnableAnimation(false);
- setIgnoreReplace(false);
- setIsRegenerate(false);
+ // 重置替换规则
+ setEnableAnimation(false);
+ setIgnoreReplace(false);
+ setIsRegenerate(false);
- selectRole(roleData[index]);
- });
+ selectRole(roleData[index]);
};
// 从角色库中选择角色
@@ -266,18 +256,8 @@ CharacterTabContentProps
});
};
- // 如果loading 显示loading状态
- if (loading) {
- return (
-
- );
- }
-
// 如果没有角色数据,显示占位内容
- if (roleData.length === 0) {
+ if (originalRoles.length === 0) {
return (
@@ -306,22 +286,22 @@ CharacterTabContentProps
role.id === selectedRole?.id)}
+ selectedIndex={originalRoles?.findIndex(role => role.name === selectedRole?.name)}
onItemClick={(i: number) => handleChangeRole(i)}
>
- {roleData.map((role, index) => (
+ {originalRoles.map((role, index) => (
@@ -335,78 +315,83 @@ CharacterTabContentProps
{/* 下部分:角色详情 */}
-
+ { loading ? (
+
+ ) : (
+
- {/* 左列:角色预览 */}
-
- {/* 角色预览图 */}
-
-
+ {/* 角色预览图 */}
+
+
+ {/* 应用角色按钮 */}
+
+
+ {isUploading ? : }
+
+ handleOpenReplaceLibrary()}
+ >
+
+
+
+
+
+
+ {/* 右列:角色信息 */}
+
+
updateRoleText(text)}
/>
- {/* 应用角色按钮 */}
-
+ {/* 重新生成按钮、替换形象按钮 */}
+
handleRegenerate()}
+ className="flex items-center justify-center gap-2 px-4 py-3 bg-blue-500/10 hover:bg-blue-500/20
+ text-blue-500 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
+ whileHover={{ scale: 1.02 }}
+ whileTap={{ scale: 0.98 }}
+ disabled={isRegenerate}
>
- {isUploading ? : }
-
- handleOpenReplaceLibrary()}
- >
-
+
+ {isRegenerate ? 'Regenerating...' : 'Regenerate'}
-
-
- {/* 右列:角色信息 */}
-
-
updateRoleText(text)}
- />
- {/* 重新生成按钮、替换形象按钮 */}
-
- handleRegenerate()}
- className="flex items-center justify-center gap-2 px-4 py-3 bg-blue-500/10 hover:bg-blue-500/20
- text-blue-500 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
- whileHover={{ scale: 1.02 }}
- whileTap={{ scale: 0.98 }}
- disabled={isRegenerate}
- >
-
- {isRegenerate ? 'Regenerating...' : 'Regenerate'}
-
-
-
-
-
-
-
+
+ )}
+
(null);
const characterTabContentRef = useRef(null);
+ const shotTabContentRef = useRef(null);
// 添加一个状态来标记是否是从切换tab触发的提醒
const [pendingSwitchTabId, setPendingSwitchTabId] = useState(null);
const [disabledBtn, setDisabledBtn] = useState(false);
@@ -72,9 +76,9 @@ export function EditModal({
const isTabDisabled = (tabId: string) => {
if (tabId === 'settings') return false;
// 换成 如果对应标签下 数据存在 就不禁用
- // if (tabId === '1') return roles.length === 0;
+ if (tabId === '1') return taskObject?.roles.data.length === 0;
// if (tabId === '2') return taskScenes.length === 0;
- // if (tabId === '3') return sketchVideo.length === 0;
+ if (tabId === '3') return taskObject?.videos.data.length === 0;
if (tabId === '4') return false;
return false;
};
@@ -98,6 +102,11 @@ export function EditModal({
if (characterTabContent) {
return characterTabContent.switchBefore(tabId);
}
+ } else if (activeTab === '3') {
+ const shotTabContent = shotTabContentRef.current;
+ if (shotTabContent) {
+ return shotTabContent.switchBefore(tabId);
+ }
}
return false;
}
@@ -117,11 +126,11 @@ export function EditModal({
console.log('handleSave');
// setIsRemindFallbackOpen(true);
if (activeTab === '0') {
- scriptTabContentRef.current.saveBefore();
+ scriptTabContentRef.current.saveOrCloseBefore();
} else if (activeTab === '1') {
- characterTabContentRef.current.saveBefore();
+ characterTabContentRef.current.saveOrCloseBefore();
} else if (activeTab === '3') {
- handleConfirmGotoFallback();
+ shotTabContentRef.current.saveOrCloseBefore('apply');
}
}
@@ -170,7 +179,13 @@ export function EditModal({
const handleClickClose = () => {
// TODO 关闭前 检查 当前tab 下是否有更新 如果有更新 则提醒用户 是否确认应用
// 暂时 默认弹出提醒
- setIsRemindCloseOpen(true);
+ if (activeTab === '0') {
+ scriptTabContentRef.current.saveOrCloseBefore();
+ } else if (activeTab === '1') {
+ characterTabContentRef.current.saveOrCloseBefore();
+ } else if (activeTab === '3') {
+ shotTabContentRef.current.saveOrCloseBefore('close');
+ }
}
const handleConfirmApply = () => {
@@ -195,6 +210,7 @@ export function EditModal({
originalText={originalText}
onApply={handleApply}
setActiveTab={setActiveTab}
+ onClose={onClose}
/>
);
case '1':
@@ -204,6 +220,7 @@ export function EditModal({
onClose={onClose}
onApply={handleApply}
setActiveTab={setActiveTab}
+ originalRoles={taskObject?.roles.data || []}
/>
);
case '2':
@@ -215,9 +232,12 @@ export function EditModal({
case '3':
return (
);
case '4':
diff --git a/components/ui/script-tab-content.tsx b/components/ui/script-tab-content.tsx
index c347006..6efd486 100644
--- a/components/ui/script-tab-content.tsx
+++ b/components/ui/script-tab-content.tsx
@@ -12,13 +12,14 @@ interface ScriptTabContentProps {
originalText?: string;
onApply: () => void;
setActiveTab: (tabId: string) => void;
+ onClose: () => void;
}
export const ScriptTabContent = forwardRef<
- { switchBefore: (tabId: string) => boolean, saveBefore: () => void },
+ { switchBefore: (tabId: string) => boolean, saveOrCloseBefore: () => void },
ScriptTabContentProps
>((props, ref) => {
- const { setIsPauseWorkFlow, isPauseWorkFlow, originalText, onApply, setActiveTab } = props;
+ const { setIsPauseWorkFlow, isPauseWorkFlow, originalText, onApply, setActiveTab, onClose } = props;
const { loading, scriptData, setAnyAttribute, applyScript } = useEditData('script', originalText);
const [isUpdate, setIsUpdate] = useState(false);
@@ -39,10 +40,12 @@ export const ScriptTabContent = forwardRef<
}
return isUpdate;
},
- saveBefore: () => {
- console.log('saveBefore');
+ saveOrCloseBefore: () => {
+ console.log('saveOrCloseBefore');
if (isUpdate) {
onApply();
+ } else {
+ onClose();
}
}
}));
diff --git a/components/ui/shot-tab-content.tsx b/components/ui/shot-tab-content.tsx
index e15c2d2..b97da86 100644
--- a/components/ui/shot-tab-content.tsx
+++ b/components/ui/shot-tab-content.tsx
@@ -1,6 +1,6 @@
'use client';
-import React, { useRef, useEffect, useState } from 'react';
+import React, { useRef, useEffect, useState, forwardRef } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { RefreshCw, User, Loader2, X, Plus, Video, CircleX } from 'lucide-react';
import { cn } from '@/public/lib/utils';
@@ -11,19 +11,26 @@ import FloatingGlassPanel from './FloatingGlassPanel';
import { ReplaceCharacterPanel } from './replace-character-panel';
import HorizontalScroller from './HorizontalScroller';
import { useEditData } from '@/components/pages/work-flow/use-edit-data';
-import { RoleEntity } from '@/app/service/domain/Entities';
+import { RoleEntity, VideoSegmentEntity } from '@/app/service/domain/Entities';
+import { ShotVideo } from '@/api/DTO/movieEdit';
interface ShotTabContentProps {
currentSketchIndex: number;
- roles?: any[];
+ originalVideos: ShotVideo[];
onApply: () => void;
+ onClose: () => void;
+ setActiveTab: (tabId: string) => void;
}
-export const ShotTabContent = (props: ShotTabContentProps) => {
- const { currentSketchIndex = 0, roles = [], onApply } = props;
+export const ShotTabContent = forwardRef<
+ { switchBefore: (tabId: string) => boolean, saveOrCloseBefore: (type: 'apply' | 'close') => void },
+ ShotTabContentProps
+>((props, ref) => {
+ const { currentSketchIndex = 0, onApply, onClose, originalVideos, setActiveTab } = props;
const {
loading,
shotData,
+ selectedSegment,
scriptRoles,
setSelectedSegment,
regenerateVideoSegment,
@@ -36,8 +43,6 @@ export const ShotTabContent = (props: ShotTabContentProps) => {
calculateRecognitionBoxes,
setSelectedRole
} = useEditData('shot');
- const [selectedIndex, setSelectedIndex] = useState(currentSketchIndex);
-
const [detections, setDetections] = useState([]);
const [scanState, setScanState] = useState<'idle' | 'scanning' | 'detected' | 'failed' | 'timeout'>('idle');
const [isScanFailed, setIsScanFailed] = useState(false);
@@ -51,13 +56,54 @@ export const ShotTabContent = (props: ShotTabContentProps) => {
const [isRegenerate, setIsRegenerate] = useState(false);
const [pendingRegeneration, setPendingRegeneration] = useState(false);
+ const [isInitialized, setIsInitialized] = useState(true);
+ const [triggerType, setTriggerType] = useState<'tab' | 'apply' | 'close'>('tab');
+ const [nextToTabId, setNextToTabId] = useState('');
+ const [isRemindApplyUpdate, setIsRemindApplyUpdate] = useState(false);
+ const [updateData, setUpdateData] = useState([]);
+
useEffect(() => {
console.log('shotTabContent-----shotData', shotData);
}, [shotData]);
+ useEffect(() => {
+ console.log('-==========shotData===========-', shotData);
+ // 只在初始化且有角色数据时执行
+ if (isInitialized && shotData.length > 0) {
+ setIsInitialized(false);
+ setSelectedSegment(shotData[0]);
+ }
+ }, [shotData, isInitialized]);
+
+ // 暴露方法给父组件
+ React.useImperativeHandle(ref, () => ({
+ switchBefore: (tabId: string) => {
+ setNextToTabId(tabId);
+ // 判断 是否修改数据
+ const isChange = handleGetUpdateData().length > 0;
+ console.log('switchBefore', isChange);
+ if (isChange) {
+ setTriggerType('tab');
+ setIsRemindApplyUpdate(true);
+ }
+ return isChange;
+ },
+ saveOrCloseBefore: (type: 'apply' | 'close') => {
+ console.log('saveOrCloseBefore');
+ // 判断 是否修改数据
+ const isChange = handleGetUpdateData().length > 0;
+ if (isChange) {
+ setTriggerType(type);
+ setIsRemindApplyUpdate(true);
+ } else {
+ onClose();
+ }
+ }
+ }));
+
useEffect(() => {
if (pendingRegeneration) {
- console.log('pendingRegeneration', pendingRegeneration, shotData[selectedIndex]?.lens);
+ console.log('pendingRegeneration', pendingRegeneration, selectedSegment?.lens);
regenerateVideoSegment().then(() => {
setPendingRegeneration(false);
setIsRegenerate(false);
@@ -65,16 +111,35 @@ export const ShotTabContent = (props: ShotTabContentProps) => {
}
}, [pendingRegeneration]);
- // 监听当前选中index变化
+ // 监听当前选中segment变化
useEffect(() => {
console.log('shotTabContent-----shotData', shotData);
- if (shotData.length > 0) {
+ if (shotData.length > 0 && !selectedSegment) {
// 清空检测状态 和 检测结果
setScanState('idle');
setDetections([]);
- setSelectedSegment(shotData[selectedIndex]);
+ setSelectedSegment(shotData[0]);
}
- }, [selectedIndex, shotData]);
+ }, [shotData, selectedSegment]);
+
+ // 获取修改的数据
+ const handleGetUpdateData = () => {
+ console.log('handleGetUpdateData', shotData, originalVideos);
+ const updateData: VideoSegmentEntity[] = [];
+ shotData.forEach((shot, index) => {
+ const a = shot.videoUrl.map((url) => url.video_url).join(',');
+ const b = originalVideos[index].urls.join(',');
+ if (a !== b) {
+ updateData.push({
+ ...shot,
+ name: 'Segment ' + (index + 1)
+ });
+ }
+ });
+ console.log('updateData', updateData);
+ setUpdateData(updateData);
+ return updateData;
+ }
// 处理扫描开始
const handleScan = async () => {
@@ -174,16 +239,34 @@ export const ShotTabContent = (props: ShotTabContentProps) => {
onApply();
};
+ // 应用修改
+ const handleApplyUpdate = () => {
+ console.log('apply update');
+ onApply();
+ }
+
+ // 忽略修改
+ const handleIgnoreUpdate = () => {
+ console.log('ignore update');
+ if (triggerType === 'apply') {
+ onClose();
+ } else if (triggerType === 'tab') {
+ setActiveTab(nextToTabId);
+ }
+ }
+
// 点击按钮重新生成
const handleRegenerate = async () => {
console.log('regenerate');
setIsRegenerate(true);
const shotInfo = shotsEditorRef.current.getShotInfo();
console.log('shotInfo', shotInfo);
- setSelectedSegment({
- ...shotData[selectedIndex],
- lens: shotInfo
- });
+ if (selectedSegment) {
+ setSelectedSegment({
+ ...selectedSegment,
+ lens: shotInfo
+ });
+ }
setTimeout(() => {
setPendingRegeneration(true);
}, 1000);
@@ -197,22 +280,20 @@ export const ShotTabContent = (props: ShotTabContentProps) => {
// 切换选择分镜
const handleSelectShot = (index: number) => {
- // 切换前 判断数据是否发生变化
- setSelectedIndex(index);
+ // 通过 video_id 找到对应的分镜
+ const selectedVideo = originalVideos[index];
+ const targetSegment = shotData.find(shot =>
+ shot.videoUrl.some(url => url.video_id === selectedVideo.video_id)
+ );
+ if (targetSegment) {
+ setSelectedSegment(targetSegment);
+ }
};
- // 如果loading 显示loading状态
- if (loading) {
- return (
-
- );
- }
+
// 如果没有数据,显示空状态
- if (shotData.length === 0) {
+ if (originalVideos.length === 0) {
return (
@@ -233,28 +314,23 @@ export const ShotTabContent = (props: ShotTabContentProps) => {
selectedSegment?.videoUrl.some(url => url.video_id === shot.video_id))}
onItemClick={(i: number) => handleSelectShot(i)}
>
- {shotData.map((shot, index) => (
+ {originalVideos.map((shot, index) => (
url.video_id === shot.video_id) ? 'ring-2 ring-blue-500' : 'hover:ring-2 hover:ring-blue-500/50'
)}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
- {(shot.status === 0 || shot.videoUrl.length === 0) && (
-
-
-
- )}
- {shot.status === 1 && shot.videoUrl[0] && (
+ {shot.urls.length > 0 && (