This commit is contained in:
海龙 2025-08-18 19:02:38 +08:00
commit 4ef38e0c73
14 changed files with 166 additions and 128 deletions

View File

@ -738,6 +738,8 @@ export const createMovieProjectV1 = async (request: {
resolution: "720p" | "1080p" | "4k"; resolution: "720p" | "1080p" | "4k";
/** 语言 */ /** 语言 */
language: string; language: string;
/** 视频时长 */
video_duration: string;
}) => { }) => {
return post<ApiResponse<{ return post<ApiResponse<{
/** 项目ID */ /** 项目ID */

View File

@ -85,7 +85,8 @@ export interface UseScriptService {
userId: string, userId: string,
mode: "automatic" | "manual", mode: "automatic" | "manual",
resolution: string, resolution: string,
language: string language: string,
video_duration: string
) => Promise<void>; ) => Promise<void>;
/** 设置任何属性 */ /** 设置任何属性 */
setAnyAttribute: any; setAnyAttribute: any;
@ -158,7 +159,8 @@ export const useScriptService = (): UseScriptService => {
userId: string, userId: string,
mode: "automatic" | "manual", mode: "automatic" | "manual",
resolution: string, resolution: string,
language: string language: string,
video_duration: string
): Promise<void> => { ): Promise<void> => {
try { try {
setLoading(true); setLoading(true);
@ -169,7 +171,8 @@ export const useScriptService = (): UseScriptService => {
userId, userId,
mode as "automatic" | "manual", mode as "automatic" | "manual",
resolution as "720p" | "1080p" | "4k", resolution as "720p" | "1080p" | "4k",
language language,
video_duration
); );
setProjectId(projectData.project_id); setProjectId(projectData.project_id);

View File

@ -60,10 +60,10 @@ export class TextToShotAdapter {
let currentText = text; let currentText = text;
// 按角色名称长度降序排序,避免短名称匹配到长名称的一部分 // 按角色名称长度降序排序,避免短名称匹配到长名称的一部分
// 既要兼容 首字母大写 其余小写、还要兼容 全部大写 // 既要兼容 每个单词 首字母大写 其余小写、还要兼容 全部大写
const sortedRoles = [...roles].sort((a, b) => b.name.length - a.name.length).map(role => ({ const sortedRoles = [...roles].sort((a, b) => b.name.length - a.name.length).map(role => ({
...role, ...role,
name: role.name.charAt(0).toUpperCase() + role.name.slice(1).toLowerCase() name: role.name.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(' ')
})).concat([...roles].map(role => ({ })).concat([...roles].map(role => ({
...role, ...role,
name: role.name.toUpperCase() name: role.name.toUpperCase()
@ -257,14 +257,16 @@ export class TextToShotAdapter {
if (shotData.shotDescContent.length > 0) { if (shotData.shotDescContent.length > 0) {
// 合并所有描述段落的文本内容 // 合并所有描述段落的文本内容
shotData.shotDescContent.forEach(paragraph => { shotData.shotDescContent.forEach(paragraph => {
paragraph.content.forEach(node => { if (paragraph.content) {
if (node.type === 'text') { paragraph.content.forEach(node => {
currentScript += node.text; if (node.type === 'text') {
} currentScript += node.text;
if (node.type === 'characterToken') { }
currentScript += node.attrs.name; if (node.type === 'characterToken') {
} currentScript += node.attrs.name;
}); }
});
}
}); });
} }

View File

@ -40,7 +40,8 @@ describe("ScriptService 业务逻辑测试", () => {
"user123", "user123",
"automatic", "automatic",
"720p", "720p",
"en" "en",
"10"
); );
expect(createRes.project_id).toBeDefined(); expect(createRes.project_id).toBeDefined();
projectId = createRes.project_id; projectId = createRes.project_id;

View File

@ -126,7 +126,8 @@ export class ScriptEditUseCase {
userId: string , userId: string ,
mode: "automatic" | "manual" = "automatic", mode: "automatic" | "manual" = "automatic",
resolution: "720p" | "1080p" | "4k" = "720p", resolution: "720p" | "1080p" | "4k" = "720p",
language: string language: string,
video_duration: string
) { ) {
try { try {
// 调用创建项目API // 调用创建项目API
@ -136,6 +137,7 @@ export class ScriptEditUseCase {
mode, mode,
resolution, resolution,
language, language,
video_duration,
}); });
if (!response.successful) { if (!response.successful) {

View File

@ -7,7 +7,7 @@ interface AISuggestionBarProps {
suggestions: string[]; suggestions: string[];
onSuggestionClick: (suggestion: string) => void; onSuggestionClick: (suggestion: string) => void;
onSubmit: (text: string) => void; onSubmit: (text: string) => void;
onFocus: () => void; onClick: () => void;
placeholder?: string; placeholder?: string;
} }
@ -15,7 +15,7 @@ export function AISuggestionBar({
suggestions, suggestions,
onSuggestionClick, onSuggestionClick,
onSubmit, onSubmit,
onFocus, onClick,
placeholder = "输入你的想法,或点击预设词条获取 AI 建议..." placeholder = "输入你的想法,或点击预设词条获取 AI 建议..."
}: AISuggestionBarProps) { }: AISuggestionBarProps) {
const [inputText, setInputText] = useState(''); const [inputText, setInputText] = useState('');
@ -89,7 +89,7 @@ export function AISuggestionBar({
</motion.div> </motion.div>
</div> </div>
<div className="max-w-5xl mx-auto px-6"> <div className="max-w-5xl mx-auto px-6" onClick={onClick}>
{/* 智能预设词条 英文 */} {/* 智能预设词条 英文 */}
<AnimatePresence> <AnimatePresence>
{showSuggestions && !isCollapsed && ( {showSuggestions && !isCollapsed && (
@ -179,7 +179,6 @@ export function AISuggestionBar({
if (isCollapsed) { if (isCollapsed) {
toggleCollapse(); toggleCollapse();
} }
onFocus();
}} }}
onBlur={() => setIsFocused(false)} onBlur={() => setIsFocused(false)}
placeholder={isCollapsed ? "点击展开..." : placeholder} placeholder={isCollapsed ? "点击展开..." : placeholder}

View File

@ -30,10 +30,15 @@ import { AudioRecorder } from "./AudioRecorder";
import { useTemplateStoryServiceHook } from "@/app/service/Interaction/templateStoryService"; import { useTemplateStoryServiceHook } from "@/app/service/Interaction/templateStoryService";
import { createScriptEpisodeNew } from "@/api/script_episode"; import { createScriptEpisodeNew } from "@/api/script_episode";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
<<<<<<< HEAD
import { EditorContent, useEditor } from "@tiptap/react"; import { EditorContent, useEditor } from "@tiptap/react";
import StarterKit from "@tiptap/starter-kit"; import StarterKit from "@tiptap/starter-kit";
import { HighlightTextExtension } from "@/components/ui/main-editor/HighlightText"; import { HighlightTextExtension } from "@/components/ui/main-editor/HighlightText";
import Placeholder from "@tiptap/extension-placeholder"; import Placeholder from "@tiptap/extension-placeholder";
import { createMovieProjectV1 } from "@/api/video_flow";
=======
import { createMovieProjectV1 } from "@/api/video_flow";
>>>>>>> 3a713f988f2a1b9f322258e14c192470d879eee5
// 自定义音频播放器样式 // 自定义音频播放器样式
const customAudioPlayerStyles = ` const customAudioPlayerStyles = `
@ -548,8 +553,8 @@ export function ChatInputBox() {
}; };
// 调用创建剧集API // 调用创建剧集API
const episodeResponse = await createScriptEpisodeNew(episodeData); const episodeResponse = await createMovieProjectV1(episodeData);
console.log("episodeResponse", episodeResponse); console.log('episodeResponse', episodeResponse);
if (episodeResponse.code !== 0) { if (episodeResponse.code !== 0) {
console.error(`创建剧集失败: ${episodeResponse.message}`); console.error(`创建剧集失败: ${episodeResponse.message}`);
alert(`创建剧集失败: ${episodeResponse.message}`); alert(`创建剧集失败: ${episodeResponse.message}`);

View File

@ -159,6 +159,10 @@
display: flex; display: flex;
overflow: hidden; overflow: hidden;
min-height: 0; min-height: 0;
display: grid;
grid-auto-rows: auto;
justify-content: center;
} }
.videoContainer-qteKNi { .videoContainer-qteKNi {
flex: 1; flex: 1;
@ -166,13 +170,14 @@
display: flex; display: flex;
position: relative; position: relative;
justify-content: center; justify-content: center;
width: max-content;
} }
.heroVideo-FIzuK1 { .heroVideo-FIzuK1 {
object-fit: cover; object-fit: cover;
object-position: center; object-position: center;
background-color: #0003; background-color: #0003;
border-radius: 8px; border-radius: 8px;
width: 100%;
height: 100%; height: 100%;
} }
.container-kIPoeH { .container-kIPoeH {

View File

@ -21,7 +21,6 @@ const WorkFlow = React.memo(function WorkFlow() {
console.log("init-WorkFlow"); console.log("init-WorkFlow");
return () => console.log("unmount-WorkFlow"); return () => console.log("unmount-WorkFlow");
}, []); }, []);
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const [isEditModalOpen, setIsEditModalOpen] = React.useState(false); const [isEditModalOpen, setIsEditModalOpen] = React.useState(false);
const [activeEditTab, setActiveEditTab] = React.useState('1'); const [activeEditTab, setActiveEditTab] = React.useState('1');
@ -92,102 +91,98 @@ const WorkFlow = React.memo(function WorkFlow() {
return ( return (
<ErrorBoundary> <ErrorBoundary>
<div className="w-full overflow-hidden h-[calc(100vh-6rem)] absolute top-[4rem] left-0 right-0 px-[1rem]"> <div className="w-full overflow-hidden h-[calc(100vh-6rem)] absolute top-[4rem] left-0 right-0 px-[1rem]">
<div className="flex h-full flex-col justify-start items-center"> <div className="w-full h-full">
<div className="container-H2sRZG"> <div className="splashContainer-otuV_A">
<div className="splashContainer-otuV_A"> <div className="content-vPGYx8">
<div className="content-vPGYx8"> <div className="info-UUGkPJ">
<div className="info-UUGkPJ"> <ErrorBoundary>
<ErrorBoundary> <TaskInfo
<TaskInfo isLoading={isLoading}
isLoading={isLoading} taskObject={taskObject}
taskObject={taskObject} currentLoadingText={currentLoadingText}
currentLoadingText={currentLoadingText} dataLoadError={dataLoadError}
dataLoadError={dataLoadError} roles={taskObject.roles.data}
roles={taskObject.roles.data} isPauseWorkFlow={isPauseWorkFlow}
isPauseWorkFlow={isPauseWorkFlow} />
/> </ErrorBoundary>
</ErrorBoundary>
</div>
</div> </div>
<div className="media-Ocdu1O rounded-lg"> </div>
<div <div className="media-Ocdu1O rounded-lg">
className="videoContainer-qteKNi" <div
ref={containerRef} className="videoContainer-qteKNi"
> ref={containerRef}
{dataLoadError ? ( >
{dataLoadError ? (
<motion.div
className="flex flex-col items-center justify-center w-full aspect-video rounded-lg bg-red-50 border-2 border-red-200"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<motion.div <motion.div
className="flex flex-col items-center justify-center w-full aspect-video rounded-lg bg-red-50 border-2 border-red-200" className="flex items-center gap-3 mb-4"
initial={{ opacity: 0, y: 20 }} initial={{ scale: 0.8 }}
animate={{ opacity: 1, y: 0 }} animate={{ scale: 1 }}
transition={{ duration: 0.5 }} transition={{ duration: 0.3, delay: 0.2 }}
> >
<motion.div <AlertCircle className="w-8 h-8 text-red-500" />
className="flex items-center gap-3 mb-4" <h3 className="text-lg font-medium text-red-800"></h3>
initial={{ scale: 0.8 }}
animate={{ scale: 1 }}
transition={{ duration: 0.3, delay: 0.2 }}
>
<AlertCircle className="w-8 h-8 text-red-500" />
<h3 className="text-lg font-medium text-red-800"></h3>
</motion.div>
<p className="text-red-600 text-center mb-6 max-w-md px-4">
{dataLoadError}
</p>
<motion.button
className="flex items-center gap-2 px-6 py-3 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors"
onClick={() => retryLoadData?.()}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<RefreshCw className="w-4 h-4" />
</motion.button>
</motion.div> </motion.div>
) : isLoading ? (
<Skeleton className="w-full aspect-video rounded-lg" /> <p className="text-red-600 text-center mb-6 max-w-md px-4">
) : ( {dataLoadError}
<div className="heroVideo-FIzuK1" style={{ aspectRatio: "16 / 9" }} key={currentSketchIndex}> </p>
<ErrorBoundary>
<MediaViewer <motion.button
taskObject={taskObject} className="flex items-center gap-2 px-6 py-3 bg-red-500 text-white rounded-lg hover:bg-red-600 transition-colors"
scriptData={scriptData} onClick={() => retryLoadData?.()}
currentSketchIndex={currentSketchIndex} whileHover={{ scale: 1.05 }}
isVideoPlaying={isVideoPlaying} whileTap={{ scale: 0.95 }}
onEditModalOpen={handleEditModalOpen} >
onToggleVideoPlay={toggleVideoPlay} <RefreshCw className="w-4 h-4" />
setIsPauseWorkFlow={setIsPauseWorkFlow}
setAnyAttribute={setAnyAttribute} </motion.button>
isPauseWorkFlow={isPauseWorkFlow} </motion.div>
applyScript={applyScript} ) : isLoading ? (
mode={mode} <Skeleton className="w-full aspect-video rounded-lg" />
/> ) : (
</ErrorBoundary> <div className={`heroVideo-FIzuK1 ${['final_video', 'script'].includes(taskObject.currentStage) ? 'h-[calc(100vh-6rem)] w-[calc((100vh-6rem)/9*16)]' : 'h-[calc(100vh-6rem-200px)] w-[calc((100vh-6rem-200px)/9*16)]'}`} style={{ aspectRatio: "16 / 9" }} key={currentSketchIndex}>
</div>
)}
</div>
{taskObject.currentStage !== 'final_video' && taskObject.currentStage !== 'script' && (
<div className="imageGrid-ymZV9z hide-scrollbar">
<ErrorBoundary> <ErrorBoundary>
<ThumbnailGrid <MediaViewer
isEditModalOpen={isEditModalOpen}
taskObject={taskObject} taskObject={taskObject}
isLoading={isLoading} scriptData={scriptData}
currentSketchIndex={currentSketchIndex} currentSketchIndex={currentSketchIndex}
taskSketch={taskSketch} isVideoPlaying={isVideoPlaying}
taskVideos={taskVideos} onEditModalOpen={handleEditModalOpen}
isGeneratingSketch={isGeneratingSketch} onToggleVideoPlay={toggleVideoPlay}
isGeneratingVideo={isGeneratingVideo} setIsPauseWorkFlow={setIsPauseWorkFlow}
sketchCount={sketchCount} setAnyAttribute={setAnyAttribute}
totalSketchCount={totalSketchCount} isPauseWorkFlow={isPauseWorkFlow}
onSketchSelect={setCurrentSketchIndex} applyScript={applyScript}
mode={mode}
/> />
</ErrorBoundary> </ErrorBoundary>
</div> </div>
)} )}
</div> </div>
{taskObject.currentStage !== 'final_video' && taskObject.currentStage !== 'script' && (
<div className="h-[112px] w-[calc((100vh-6rem-200px)/9*16)]">
<ThumbnailGrid
isDisabledFocus={isEditModalOpen || isPauseWorkFlow}
taskObject={taskObject}
isLoading={isLoading}
currentSketchIndex={currentSketchIndex}
taskSketch={taskSketch}
taskVideos={taskVideos}
isGeneratingSketch={isGeneratingSketch}
isGeneratingVideo={isGeneratingVideo}
sketchCount={sketchCount}
totalSketchCount={totalSketchCount}
onSketchSelect={setCurrentSketchIndex}
/>
</div>
)}
</div> </div>
</div> </div>
</div> </div>
@ -202,7 +197,7 @@ const WorkFlow = React.memo(function WorkFlow() {
tooltip={isPauseWorkFlow ? "Play" : "Pause"} tooltip={isPauseWorkFlow ? "Play" : "Pause"}
onClick={() => setIsPauseWorkFlow(!isPauseWorkFlow)} onClick={() => setIsPauseWorkFlow(!isPauseWorkFlow)}
/> />
{ mode !== 'automatic' && ( { !mode.includes('auto') && (
<GlassIconButton <GlassIconButton
icon={ChevronLast} icon={ChevronLast}
size='lg' size='lg'
@ -220,7 +215,7 @@ const WorkFlow = React.memo(function WorkFlow() {
suggestions={mockSuggestions} suggestions={mockSuggestions}
onSuggestionClick={handleSuggestionClick} onSuggestionClick={handleSuggestionClick}
onSubmit={handleSubmit} onSubmit={handleSubmit}
onFocus={() => setIsPauseWorkFlow(true)} onClick={() => setIsPauseWorkFlow(true)}
placeholder="Please input your ideas, or click the predefined tags to receive AI advice..." placeholder="Please input your ideas, or click the predefined tags to receive AI advice..."
/> />
</ErrorBoundary> </ErrorBoundary>

View File

@ -137,7 +137,7 @@ export const MediaViewer = React.memo(function MediaViewer({
return ( return (
<video <video
ref={finalVideoRef} ref={finalVideoRef}
className="w-[auto] h-full object-cover rounded-lg" className="w-full h-full object-cover rounded-lg"
src={taskObject.final.url} src={taskObject.final.url}
autoPlay={isFinalVideoPlaying} autoPlay={isFinalVideoPlaying}
loop loop
@ -305,7 +305,7 @@ export const MediaViewer = React.memo(function MediaViewer({
transition={{ duration: 0.8, ease: "easeInOut" }} transition={{ duration: 0.8, ease: "easeInOut" }}
> >
<video <video
className="w-[auto] h-full rounded-lg object-cover object-center" className="w-full h-full rounded-lg object-cover object-center"
src={taskObject.final.url} src={taskObject.final.url}
loop loop
playsInline playsInline

View File

@ -8,7 +8,7 @@ import { Loader2, X } from 'lucide-react';
import { TaskObject } from '@/api/DTO/movieEdit'; import { TaskObject } from '@/api/DTO/movieEdit';
interface ThumbnailGridProps { interface ThumbnailGridProps {
isEditModalOpen: boolean; isDisabledFocus: boolean;
taskObject: TaskObject; taskObject: TaskObject;
isLoading: boolean; isLoading: boolean;
currentSketchIndex: number; currentSketchIndex: number;
@ -22,7 +22,7 @@ interface ThumbnailGridProps {
} }
export function ThumbnailGrid({ export function ThumbnailGrid({
isEditModalOpen, isDisabledFocus,
taskObject, taskObject,
isLoading, isLoading,
currentSketchIndex, currentSketchIndex,
@ -124,7 +124,7 @@ export function ThumbnailGrid({
// 监听键盘事件 // 监听键盘事件
useEffect(() => { useEffect(() => {
// 组件挂载时自动聚焦 // 组件挂载时自动聚焦
if (thumbnailsRef.current && !isEditModalOpen) { if (thumbnailsRef.current && !isDisabledFocus) {
thumbnailsRef.current.focus(); thumbnailsRef.current.focus();
} }
@ -134,7 +134,7 @@ export function ThumbnailGrid({
// 确保在数据变化时保持焦点 // 确保在数据变化时保持焦点
useEffect(() => { useEffect(() => {
if (thumbnailsRef.current && !isFocused && !isEditModalOpen) { if (thumbnailsRef.current && !isFocused && !isDisabledFocus) {
thumbnailsRef.current.focus(); thumbnailsRef.current.focus();
} }
}, [taskObject.currentStage, isFocused]); }, [taskObject.currentStage, isFocused]);
@ -267,7 +267,6 @@ export function ThumbnailGrid({
<div className="absolute inset-0 bg-black/10 flex items-center justify-center z-20"> <div className="absolute inset-0 bg-black/10 flex items-center justify-center z-20">
<div className="text-blue-500 text-xl font-bold flex items-center gap-2"> <div className="text-blue-500 text-xl font-bold flex items-center gap-2">
<Loader2 className="w-10 h-10 animate-spin" /> <Loader2 className="w-10 h-10 animate-spin" />
<span>Generating...</span>
</div> </div>
</div> </div>
)} )}
@ -275,7 +274,6 @@ export function ThumbnailGrid({
<div className="absolute inset-0 bg-red-500/10 flex items-center justify-center z-20"> <div className="absolute inset-0 bg-red-500/10 flex items-center justify-center z-20">
<div className="text-red-500 text-xl font-bold flex items-center gap-2"> <div className="text-red-500 text-xl font-bold flex items-center gap-2">
<X className="w-10 h-10" /> <X className="w-10 h-10" />
<span>Failed</span>
</div> </div>
</div> </div>
)} )}
@ -329,7 +327,6 @@ export function ThumbnailGrid({
<div className="absolute inset-0 bg-black/10 flex items-center justify-center"> <div className="absolute inset-0 bg-black/10 flex items-center justify-center">
<div className="text-blue-500 text-xl font-bold flex items-center gap-2"> <div className="text-blue-500 text-xl font-bold flex items-center gap-2">
<Loader2 className="w-10 h-10 animate-spin" /> <Loader2 className="w-10 h-10 animate-spin" />
<span>Generating...</span>
</div> </div>
</div> </div>
)} )}
@ -337,7 +334,6 @@ export function ThumbnailGrid({
<div className="absolute inset-0 bg-red-500/10 flex items-center justify-center"> <div className="absolute inset-0 bg-red-500/10 flex items-center justify-center">
<div className="text-red-500 text-xl font-bold flex items-center gap-2"> <div className="text-red-500 text-xl font-bold flex items-center gap-2">
<X className="w-10 h-10" /> <X className="w-10 h-10" />
<span>Failed</span>
</div> </div>
</div> </div>
)} )}
@ -366,7 +362,7 @@ export function ThumbnailGrid({
<div <div
ref={thumbnailsRef} ref={thumbnailsRef}
tabIndex={0} tabIndex={0}
className="w-full grid grid-flow-col auto-cols-[20%] gap-4 overflow-x-auto hide-scrollbar px-1 py-1 cursor-grab active:cursor-grabbing focus:outline-none select-none" className="w-full h-full grid grid-flow-col auto-cols-[20%] gap-4 overflow-x-auto hide-scrollbar px-1 py-1 cursor-grab active:cursor-grabbing focus:outline-none select-none"
autoFocus autoFocus
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove} onMouseMove={handleMouseMove}

View File

@ -1,4 +1,5 @@
import React, { forwardRef, useEffect, useRef, useState } from "react"; import React, { forwardRef, useRef, useState } from "react";
import { useDeepCompareEffect } from "@/hooks/useDeepCompareEffect";
import { Plus, X, UserRoundPlus, MessageCirclePlus, MessageCircleMore, ClipboardType } from "lucide-react"; import { Plus, X, UserRoundPlus, MessageCirclePlus, MessageCircleMore, ClipboardType } from "lucide-react";
import ShotEditor from "./ShotEditor"; import ShotEditor from "./ShotEditor";
import { toast } from "sonner"; import { toast } from "sonner";
@ -51,16 +52,16 @@ export const ShotsEditor = forwardRef<any, ShotsEditorProps>(function ShotsEdito
const descEditorRef = useRef<any>(null); const descEditorRef = useRef<any>(null);
const dialogEditorRef = useRef<any>(null); const dialogEditorRef = useRef<any>(null);
useEffect(() => { useDeepCompareEffect(() => {
console.log('-==========shotInfo===========-', shotInfo);
if (shotInfo) { if (shotInfo) {
console.log('-==========shotInfo===========-', shotInfo);
const shots = shotInfo.map((shot) => { const shots = shotInfo.map((shot) => {
return TextToShotAdapter.fromLensType(shot, roles); return TextToShotAdapter.fromLensType(shot, roles);
}); });
console.log('-==========shots===========-', shots); console.log('-==========shots===========-', shots);
setShots(shots as Shot[]); setShots(shots as Shot[]);
} }
}, [shotInfo]); }, [shotInfo, roles]);
const handleDescContentChange = (content: any) => { const handleDescContentChange = (content: any) => {
const shot = shots[currentShotIndex]; const shot = shots[currentShotIndex];

View File

@ -47,11 +47,19 @@ export const ShotTabContent = (props: ShotTabContentProps) => {
const [isLoadingShots, setIsLoadingShots] = useState(false); const [isLoadingShots, setIsLoadingShots] = useState(false);
const shotsEditorRef = useRef<any>(null); const shotsEditorRef = useRef<any>(null);
const [isRegenerate, setIsRegenerate] = useState(false); const [isRegenerate, setIsRegenerate] = useState(false);
const [pendingRegeneration, setPendingRegeneration] = useState(false);
useEffect(() => { useEffect(() => {
console.log('shotTabContent-----roles', roles); console.log('shotTabContent-----roles', roles);
}, [roles]); }, [roles]);
useEffect(() => {
if (pendingRegeneration) {
regenerateVideoSegment();
setPendingRegeneration(false);
}
}, [shotData[selectedIndex]?.lens]);
// 监听当前选中index变化 // 监听当前选中index变化
useEffect(() => { useEffect(() => {
console.log('shotTabContent-----shotData', shotData); console.log('shotTabContent-----shotData', shotData);
@ -165,13 +173,12 @@ export const ShotTabContent = (props: ShotTabContentProps) => {
console.log('regenerate'); console.log('regenerate');
setIsRegenerate(true); setIsRegenerate(true);
const shotInfo = shotsEditorRef.current.getShotInfo(); const shotInfo = shotsEditorRef.current.getShotInfo();
console.log('shotTabContent-----shotInfo', shotInfo); console.log('shotInfo', shotInfo);
setSelectedSegment({ setSelectedSegment({
...shotData[selectedIndex], ...shotData[selectedIndex],
lens: shotInfo lens: shotInfo
}); });
await regenerateVideoSegment(); setPendingRegeneration(true);
setIsRegenerate(false);
}; };
// 新增分镜 // 新增分镜
@ -408,7 +415,7 @@ export const ShotTabContent = (props: ShotTabContentProps) => {
<motion.button <motion.button
onClick={() => handleRegenerate()} onClick={() => handleRegenerate()}
className="flex items-center justify-center gap-2 px-4 py-3 bg-blue-500/10 hover:bg-blue-500/20 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" text-blue-500 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
whileHover={{ scale: 1.02 }} whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }} whileTap={{ scale: 0.98 }}
disabled={isRegenerate} disabled={isRegenerate}

View File

@ -0,0 +1,20 @@
import { useEffect, useRef } from 'react';
import isEqual from 'lodash/isEqual';
function useDeepCompareMemoize<T>(value: T) {
const ref = useRef<T>();
if (!isEqual(value, ref.current)) {
ref.current = value;
}
return ref.current;
}
export function useDeepCompareEffect(
callback: () => void | (() => void),
dependencies: any[]
) {
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(callback, dependencies.map(useDeepCompareMemoize));
}