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 ( -
-
-

Loading...

-
- ); - } - // 如果没有角色数据,显示占位内容 - 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) => ( {role.name} @@ -335,78 +315,83 @@ CharacterTabContentProps {/* 下部分:角色详情 */} - + { loading ? ( +
+ +

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 ( -
-
-

Loading...

-
- ); - } + // 如果没有数据,显示空状态 - if (shotData.length === 0) { + if (originalVideos.length === 0) { return (