更新工作流组件,新增originalText参数以支持脚本数据的初始化,同时优化编辑模态中的重置逻辑,提升用户体验和代码可读性。

This commit is contained in:
北枳 2025-08-13 02:23:26 +08:00
parent c08983ce12
commit 26006bde15
5 changed files with 115 additions and 53 deletions

View File

@ -47,7 +47,8 @@ export default function WorkFlow() {
setIsPauseWorkFlow, setIsPauseWorkFlow,
setAnyAttribute, setAnyAttribute,
applyScript, applyScript,
fallbackToStep fallbackToStep,
originalText
} = useWorkflowData(); } = useWorkflowData();
const { const {
@ -270,12 +271,10 @@ export default function WorkFlow() {
onSketchSelect={setCurrentSketchIndex} onSketchSelect={setCurrentSketchIndex}
roles={roles} roles={roles}
music={music} music={music}
scriptData={scriptData}
setIsPauseWorkFlow={setIsPauseWorkFlow} setIsPauseWorkFlow={setIsPauseWorkFlow}
setAnyAttribute={setAnyAttribute}
isPauseWorkFlow={isPauseWorkFlow} isPauseWorkFlow={isPauseWorkFlow}
applyScript={applyScript}
fallbackToStep={fallbackToStep} fallbackToStep={fallbackToStep}
originalText={originalText}
/> />
</ErrorBoundary> </ErrorBoundary>
</div> </div>

View File

@ -4,29 +4,24 @@ import { useShotService } from "@/app/service/Interaction/ShotService";
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from 'next/navigation';
import { useRoleServiceHook } from "@/app/service/Interaction/RoleService"; import { useRoleServiceHook } from "@/app/service/Interaction/RoleService";
import { useRoleShotServiceHook } from "@/app/service/Interaction/RoleShotService"; import { useRoleShotServiceHook } from "@/app/service/Interaction/RoleShotService";
import { useScriptService } from "@/app/service/Interaction/ScriptService";
const mockRoleData = [{ export const useEditData = (tabType: string, originalText?: string) => {
id: '1',
name: 'KAPI',
imageUrl: 'https://c.huiying.video/images/420bfb4f-b5d4-475c-a2fb-5e40af770b29.jpg',
generateText: 'A 3 to 5-year-old boy with a light to medium olive skin tone, full cheeks, and warm brown eyes. He has short, straight, dark brown hair, neatly styled with a part on his left side. His facial structure includes a small, slightly upturned nose. His lips are typically held in a slight, gentle, closed-mouth smile, which can part to show his small, white teeth.',
tags: [
{ id: '1', content: 'boy', color: 'red' },
{ id: '2', content: '3 to 5-year-old', color: 'yellow' },
{ id: '3', content: 'light to medium olive skin tone', color: 'green' },
{ id: '4', content: 'full cheeks', color: 'blue' },
{ id: '5', content: 'warm brown eyes', color: 'purple' },
]
}]
export const useEditData = (tabType: string) => {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const projectId = searchParams.get('episodeId') || ''; const projectId = searchParams.get('episodeId') || '';
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [scriptData, setScriptData] = useState<any[]>([]);
const [shotData, setShotData] = useState<any[]>([]); const [shotData, setShotData] = useState<any[]>([]);
const [roleData, setRoleData] = useState<any[]>([]); const [roleData, setRoleData] = useState<any[]>([]);
const {
scriptBlocksMemo, // 渲染剧本数据
initializeFromProject,
setAnyAttribute,
applyScript
} = useScriptService();
const { const {
videoSegments, videoSegments,
getVideoSegmentList, getVideoSegmentList,
@ -72,7 +67,15 @@ export const useEditData = (tabType: string) => {
}, [selectedRole]); }, [selectedRole]);
useEffect(() => { useEffect(() => {
if (tabType === 'shot') { if (tabType === 'script') {
initializeFromProject(projectId, originalText || '').then(() => {
setLoading(false);
}).catch((err) => {
console.log('useEditData-----err', err);
setScriptData([]);
setLoading(false);
});
} else if (tabType === 'shot') {
getVideoSegmentList(projectId).then(() => { getVideoSegmentList(projectId).then(() => {
setLoading(false); setLoading(false);
}).catch((err) => { }).catch((err) => {
@ -91,6 +94,12 @@ export const useEditData = (tabType: string) => {
} }
}, [tabType]); }, [tabType]);
useEffect(() => {
if (scriptBlocksMemo.length > 0) {
setScriptData(scriptBlocksMemo);
}
}, [scriptBlocksMemo]);
useEffect(() => { useEffect(() => {
console.log('useEditData-----videoSegments', videoSegments); console.log('useEditData-----videoSegments', videoSegments);
setShotData(videoSegments); setShotData(videoSegments);
@ -103,6 +112,10 @@ export const useEditData = (tabType: string) => {
return { return {
loading, loading,
// script
scriptData,
setAnyAttribute,
applyScript,
// shot // shot
shotData, shotData,
setSelectedSegment, setSelectedSegment,

View File

@ -449,10 +449,9 @@ export function useWorkflowData() {
} }
// 如果有已完成的数据,同步到状态 // 如果有已完成的数据,同步到状态
let finalStep = '0';
if (data) { if (data) {
if (data.sketch && data.sketch.data) { if (data.sketch && data.sketch.data) {
finalStep = '1'; 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 realSketchResultData) {
@ -461,6 +460,8 @@ export function useWorkflowData() {
script: sketch.sketch_name, script: sketch.sketch_name,
}); });
} }
taskData.sketch.data = sketchList;
taskData.sketch.total_count = data.sketch.total_count;
setTaskSketch(sketchList); setTaskSketch(sketchList);
setTaskScenes(sketchList); setTaskScenes(sketchList);
updateSketchCount(sketchList.length); updateSketchCount(sketchList.length);
@ -469,7 +470,7 @@ export function useWorkflowData() {
setIsGeneratingSketch(true); setIsGeneratingSketch(true);
loadingText = LOADING_TEXT_MAP.sketch(realSketchResultData.length, data.sketch.total_count); loadingText = LOADING_TEXT_MAP.sketch(realSketchResultData.length, data.sketch.total_count);
} else { } else {
finalStep = '2'; taskData.status = '2';
if (!data.character || !data.character.data || !data.character.data.length) { if (!data.character || !data.character.data || !data.character.data.length) {
loadingText = LOADING_TEXT_MAP.newCharacter(0, data.character.total_count); loadingText = LOADING_TEXT_MAP.newCharacter(0, data.character.total_count);
} }
@ -486,11 +487,13 @@ export function useWorkflowData() {
roleDescription: character.character_description roleDescription: character.character_description
}); });
} }
taskData.character.data = characterList;
taskData.character.total_count = data.character.total_count;
setRoles(characterList); 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); loadingText = LOADING_TEXT_MAP.newCharacter(data.character.data.length, data.character.total_count);
} else { } else {
finalStep = '3'; taskData.status = '3';
if (!data.video || !data.video.data || !data.video.data.length) { if (!data.video || !data.video.data || !data.video.data.length) {
loadingText = LOADING_TEXT_MAP.getShotSketchStatus; loadingText = LOADING_TEXT_MAP.getShotSketchStatus;
} }
@ -505,6 +508,8 @@ export function useWorkflowData() {
script: sketch.description, script: sketch.description,
}); });
} }
taskData.shot_sketch.data = sketchList;
taskData.shot_sketch.total_count = data.shot_sketch.total_count;
setTaskSketch(sketchList); setTaskSketch(sketchList);
setTaskShotSketch(sketchList); setTaskShotSketch(sketchList);
updateSketchCount(sketchList.length); updateSketchCount(sketchList.length);
@ -513,7 +518,7 @@ export function useWorkflowData() {
setIsGeneratingSketch(true); setIsGeneratingSketch(true);
loadingText = LOADING_TEXT_MAP.shotSketch(realShotResultData.length, data.shot_sketch.total_count); loadingText = LOADING_TEXT_MAP.shotSketch(realShotResultData.length, data.shot_sketch.total_count);
} else { } else {
finalStep = '3'; taskData.status = '3';
setIsGeneratingVideo(true); setIsGeneratingVideo(true);
if (!data.character || !data.character.data || !data.character.data.length) { if (!data.character || !data.character.data || !data.character.data.length) {
loadingText = LOADING_TEXT_MAP.getVideoStatus; loadingText = LOADING_TEXT_MAP.getVideoStatus;
@ -522,7 +527,7 @@ export function useWorkflowData() {
} }
if (data.video.data) { if (data.video.data) {
const realDataVideoData = data.video.data.filter((item: any) => item.urls && item.urls.length > 0); const realDataVideoData = data.video.data.filter((item: any) => item.urls && item.urls.length > 0);
if (realDataVideoData.length === 0 && finalStep === '3') { if (realDataVideoData.length === 0 && taskData.status === '3') {
loadingText = LOADING_TEXT_MAP.video(0, data.video.total_count); loadingText = LOADING_TEXT_MAP.video(0, data.video.total_count);
} }
if (realDataVideoData.length > 0) { if (realDataVideoData.length > 0) {
@ -537,6 +542,8 @@ export function useWorkflowData() {
video_id: video.video_id, video_id: video.video_id,
}); });
} }
taskData.video.data = videoList;
taskData.video.total_count = data.video.total_count;
setTaskVideos(videoList); setTaskVideos(videoList);
updateVideoCount(videoList.length); updateVideoCount(videoList.length);
// 如果在视频步骤,设置为最后一个视频 // 如果在视频步骤,设置为最后一个视频
@ -544,11 +551,11 @@ export function useWorkflowData() {
setIsGeneratingVideo(true); setIsGeneratingVideo(true);
loadingText = LOADING_TEXT_MAP.video(realDataVideoData.length, data.video.total_count); loadingText = LOADING_TEXT_MAP.video(realDataVideoData.length, data.video.total_count);
} else { } else {
finalStep = '4'; taskData.status = '4';
loadingText = LOADING_TEXT_MAP.audio; loadingText = LOADING_TEXT_MAP.audio;
// 暂时没有音频生成 直接跳过 // 暂时没有音频生成 直接跳过
finalStep = '5'; taskData.status = '5';
loadingText = LOADING_TEXT_MAP.postProduction('generating rough cut video...'); loadingText = LOADING_TEXT_MAP.postProduction('generating rough cut video...');
} }
} }
@ -559,7 +566,7 @@ export function useWorkflowData() {
setFinal({ setFinal({
url: data.final_simple_video.video url: data.final_simple_video.video
}); });
finalStep = '5.5'; taskData.status = '5.5';
loadingText = LOADING_TEXT_MAP.postProduction('generating fine-grained video clips...'); loadingText = LOADING_TEXT_MAP.postProduction('generating fine-grained video clips...');
} }
@ -567,25 +574,25 @@ export function useWorkflowData() {
setFinal({ setFinal({
url: data.final_video.video url: data.final_video.video
}); });
finalStep = '6'; taskData.status = '6';
loadingText = LOADING_TEXT_MAP.complete; loadingText = LOADING_TEXT_MAP.complete;
} }
} }
// 设置步骤 // 设置步骤
setCurrentStep(finalStep); setCurrentStep(taskData.status);
setTaskObject(prev => { setTaskObject(prev => {
if (!prev) return null; if (!prev) return null;
return { return {
...prev, ...prev,
taskStatus: finalStep taskStatus: taskData.status
}; };
}); });
console.log('---------loadingText', loadingText); console.log('---------loadingText', loadingText);
setCurrentLoadingText(loadingText); setCurrentLoadingText(loadingText);
// 设置是否需要获取流式数据 // 设置是否需要获取流式数据
setNeedStreamData(status !== 'COMPLETED' && finalStep !== '6'); setNeedStreamData(status !== 'COMPLETED' && taskData.status !== '6');
} catch (error) { } catch (error) {
console.error('初始化失败:', error); console.error('初始化失败:', error);
@ -650,6 +657,7 @@ export function useWorkflowData() {
setIsPauseWorkFlow, setIsPauseWorkFlow,
setAnyAttribute, setAnyAttribute,
applyScript, applyScript,
fallbackToStep fallbackToStep,
originalText
}; };
} }

View File

@ -25,11 +25,9 @@ interface EditModalProps {
roles?: any[]; roles?: any[];
music?: any; music?: any;
setIsPauseWorkFlow: (isPauseWorkFlow: boolean) => void; setIsPauseWorkFlow: (isPauseWorkFlow: boolean) => void;
setAnyAttribute: any;
isPauseWorkFlow: boolean; isPauseWorkFlow: boolean;
scriptData: any[] | null;
applyScript: any;
fallbackToStep: any; fallbackToStep: any;
originalText?: string;
} }
const tabs = [ const tabs = [
@ -55,16 +53,16 @@ export function EditModal({
roles = [], roles = [],
music, music,
setIsPauseWorkFlow, setIsPauseWorkFlow,
setAnyAttribute,
isPauseWorkFlow, isPauseWorkFlow,
scriptData, fallbackToStep,
applyScript, originalText
fallbackToStep
}: EditModalProps) { }: EditModalProps) {
const [activeTab, setActiveTab] = useState(activeEditTab); const [activeTab, setActiveTab] = useState(activeEditTab);
const [currentIndex, setCurrentIndex] = useState(currentSketchIndex); const [currentIndex, setCurrentIndex] = useState(currentSketchIndex);
const [currentRoleIndex, setCurrentRoleIndex] = useState(0); const [currentRoleIndex, setCurrentRoleIndex] = useState(0);
const [isRemindFallbackOpen, setIsRemindFallbackOpen] = useState(false); const [isRemindFallbackOpen, setIsRemindFallbackOpen] = useState(false);
const [isRemindResetOpen, setIsRemindResetOpen] = useState(false);
const [resetKey, setResetKey] = useState(0);
useEffect(() => { useEffect(() => {
setCurrentIndex(currentSketchIndex); setCurrentIndex(currentSketchIndex);
@ -109,7 +107,6 @@ export function EditModal({
if (activeTab === '0') { if (activeTab === '0') {
fallbackToStep('0'); fallbackToStep('0');
// 应用剧本 // 应用剧本
applyScript();
} else { } else {
fallbackToStep('1'); fallbackToStep('1');
} }
@ -121,7 +118,13 @@ export function EditModal({
const handleReset = () => { const handleReset = () => {
console.log('handleReset'); console.log('handleReset');
// 重置当前tab修改的数据 // 重置当前tab修改的数据
setIsRemindResetOpen(true);
}
const handleConfirmReset = () => {
console.log('handleConfirmReset');
setIsRemindResetOpen(false);
setResetKey(resetKey + 1);
} }
const renderTabContent = () => { const renderTabContent = () => {
@ -129,11 +132,9 @@ export function EditModal({
case '0': case '0':
return ( return (
<ScriptTabContent <ScriptTabContent
scriptData={scriptData}
setIsPauseWorkFlow={setIsPauseWorkFlow} setIsPauseWorkFlow={setIsPauseWorkFlow}
setAnyAttribute={setAnyAttribute}
isPauseWorkFlow={isPauseWorkFlow} isPauseWorkFlow={isPauseWorkFlow}
applyScript={applyScript} originalText={originalText}
/> />
); );
case '1': case '1':
@ -265,7 +266,7 @@ export function EditModal({
<div className="overflow-y-auto p-4" style={{ height: 'calc(100% - 8rem)' }}> <div className="overflow-y-auto p-4" style={{ height: 'calc(100% - 8rem)' }}>
<AnimatePresence mode="wait"> <AnimatePresence mode="wait">
<motion.div <motion.div
key={activeTab} key={`${activeTab}-${resetKey}`}
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }} exit={{ opacity: 0, y: -20 }}
@ -334,6 +335,39 @@ export function EditModal({
</div> </div>
</div> </div>
</FloatingGlassPanel> </FloatingGlassPanel>
{/* 提醒用户 重置当前tab修改的数据 */}
<FloatingGlassPanel
open={isRemindResetOpen}
width='500px'
clickMaskClose={false}
>
<div className="flex flex-col items-center gap-4 text-white py-4">
<div className="flex items-center gap-3">
<TriangleAlert className="w-6 h-6 text-yellow-400" />
<p className="text-lg font-medium">Are you sure you want to reset the current tab?</p>
</div>
<div className="flex gap-3 mt-2">
<button
onClick={() => handleConfirmReset()}
data-alt="confirm-replace-button"
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-md transition-colors duration-200 flex items-center gap-2"
>
<Undo2 className="w-4 h-4" />
Reset
</button>
<button
onClick={() => setIsRemindResetOpen(false)}
data-alt="ignore-button"
className="px-4 py-2 bg-gray-600 hover:bg-gray-700 rounded-md transition-colors duration-200 flex items-center gap-2"
>
<X className="w-4 h-4" />
Cancel
</button>
</div>
</div>
</FloatingGlassPanel>
</> </>
)} )}
</AnimatePresence> </AnimatePresence>

View File

@ -2,23 +2,31 @@ import React, { useState, useCallback, useEffect, SetStateAction } from 'react';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import { FileText } from 'lucide-react'; import { FileText } from 'lucide-react';
import { ScriptRenderer } from '@/components/script-renderer/ScriptRenderer'; import { ScriptRenderer } from '@/components/script-renderer/ScriptRenderer';
import { useEditData } from '@/components/pages/work-flow/use-edit-data';
interface ScriptTabContentProps { interface ScriptTabContentProps {
scriptData: any[] | null;
setIsPauseWorkFlow: (isPauseWorkFlow: boolean) => void; setIsPauseWorkFlow: (isPauseWorkFlow: boolean) => void;
setAnyAttribute: any;
isPauseWorkFlow: boolean; isPauseWorkFlow: boolean;
applyScript: any; originalText?: string;
} }
export function ScriptTabContent({ export function ScriptTabContent({
scriptData = [],
setIsPauseWorkFlow, setIsPauseWorkFlow,
setAnyAttribute,
isPauseWorkFlow, isPauseWorkFlow,
applyScript originalText,
}: ScriptTabContentProps) { }: ScriptTabContentProps) {
const { loading, scriptData, setAnyAttribute, applyScript } = useEditData('script', originalText);
// 如果loading 显示loading状态
if (loading) {
return (
<div className="flex flex-col items-center justify-center min-h-[400px] text-white/50">
<div className="w-12 h-12 mb-4 animate-spin rounded-full border-b-2 border-blue-600" />
<p>Loading...</p>
</div>
);
}
// 如果没有数据,显示空状态 // 如果没有数据,显示空状态
if (!scriptData || scriptData.length === 0) { if (!scriptData || scriptData.length === 0) {