角色和场景一起展示

This commit is contained in:
北枳 2025-08-24 12:45:15 +08:00
parent 93a205135c
commit 628ff70a0c
6 changed files with 72 additions and 27 deletions

View File

@ -625,21 +625,25 @@ export interface Role {
name: string;
url: string;
status: number;
type: string;
}
interface Scene {
url: string;
script: string;
status: number;
type: string;
}
interface ShotSketch {
url: string;
script: string;
status: number;
type: string;
}
export interface ShotVideo {
video_id: string;
urls: string[];
video_status: number;
type: string;
}
// 执行loading文字映射

View File

@ -153,7 +153,7 @@
.media-Ocdu1O {
flex-direction: column;
gap: 12px;
gap: 8px;
height: 100%;
flex: 1;
display: flex;

View File

@ -77,7 +77,7 @@ const WorkFlow = React.memo(function WorkFlow() {
return (
<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-5rem)] absolute top-[4rem] left-0 right-0 px-[1rem]">
<div className="w-full h-full">
<div className="splashContainer-otuV_A">
<div className="content-vPGYx8">
@ -131,7 +131,7 @@ const WorkFlow = React.memo(function WorkFlow() {
) : isLoading ? (
<Skeleton className="w-full aspect-video rounded-lg" />
) : (
<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 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={taskObject.currentStage+'_'+currentSketchIndex}>
<ErrorBoundary>
<MediaViewer
taskObject={taskObject}
@ -151,7 +151,7 @@ const WorkFlow = React.memo(function WorkFlow() {
)}
</div>
{taskObject.currentStage !== 'final_video' && taskObject.currentStage !== 'script' && (
<div className="h-[112px] w-[calc((100vh-6rem-200px)/9*16)]">
<div className="h-[123px] w-[calc((100vh-6rem-200px)/9*16)]">
<ThumbnailGrid
isDisabledFocus={isEditModalOpen || isPauseWorkFlow || isSmartChatBoxOpen}
taskObject={taskObject}

View File

@ -546,14 +546,29 @@ export const MediaViewer = React.memo(function MediaViewer({
)}
{/* 只在生成过程中或没有分镜图片时使用ProgressiveReveal */}
{currentSketch.status === 1 && (
<img
key={currentSketchIndex}
src={currentSketch.url}
alt={`NG-Sketch ${currentSketchIndex + 1}`}
className="w-full h-full rounded-lg object-cover"
/>
<AnimatePresence mode="wait">
<motion.img
key={currentSketch.url}
src={currentSketch.url}
className="w-full h-full rounded-lg object-cover"
// 用 circle clip-path 实现“扩散”
initial={{ clipPath: "circle(0% at 50% 50%)" }}
animate={{ clipPath: "circle(150% at 50% 50%)" }}
exit={{ opacity: 0 }}
transition={{ duration: 1, ease: "easeInOut" }}
/>
</AnimatePresence>
)}
<div className="absolute top-0 left-0 right-0 p-2">
{/* 角色类型 */}
{currentSketch.type === 'role' && (
<div className="inline-flex items-center px-2 py-1 rounded-full bg-purple-500/20 backdrop-blur-sm">
<span className="text-xs text-purple-400">Role: {currentSketch.name}</span>
</div>
)}
</div>
{/* 操作按钮组 */}
<AnimatePresence>
<motion.div
@ -636,13 +651,13 @@ export const MediaViewer = React.memo(function MediaViewer({
return renderScriptContent();
}
if (taskObject.currentStage === 'scene') {
return renderSketchContent(taskObject.scenes.data[currentSketchIndex]);
if (taskObject.currentStage === 'scene' || taskObject.currentStage === 'character') {
return renderSketchContent([...taskObject.roles.data, ...taskObject.scenes.data][currentSketchIndex]);
}
if (taskObject.currentStage === 'shot_sketch') {
return renderSketchContent(taskObject.shot_sketch.data[currentSketchIndex]);
}
return renderSketchContent(taskObject.scenes.data[currentSketchIndex]);
return null;
});

View File

@ -4,7 +4,7 @@ import React, { useRef, useEffect, useState, useCallback } from 'react';
import { motion } from 'framer-motion';
import { Skeleton } from '@/components/ui/skeleton';
import { ProgressiveReveal, presets } from '@/components/ui/progressive-reveal';
import { Loader2, X } from 'lucide-react';
import { Loader2, X, SquareUserRound, MapPinHouse, Clapperboard } from 'lucide-react';
import { TaskObject } from '@/api/DTO/movieEdit';
interface ThumbnailGridProps {
@ -45,7 +45,7 @@ export function ThumbnailGrid({
if (taskObject.currentStage === 'video') {
return taskObject.videos.data;
} else if (taskObject.currentStage === 'scene' || taskObject.currentStage === 'character') {
return taskObject.scenes.data;
return [...taskObject.roles.data, ...taskObject.scenes.data];
} else if (taskObject.currentStage === 'shot_sketch') {
return taskObject.shot_sketch.data;
}
@ -251,13 +251,32 @@ export function ThumbnailGrid({
<img
className="w-full h-full object-cover select-none"
src={sketch.url}
alt={`NG ${index + 1}`}
draggable="false"
/>
</div>
)}
<div className="absolute bottom-0 left-0 right-0 p-2 bg-gradient-to-t from-black/60 to-transparent">
<span className="text-xs text-white/90">Scene {index + 1}</span>
<div className='absolute top-0 left-0 right-0 p-2'>
{/* 角色类型 */}
{sketch.type === 'role' && (
<div className="inline-flex items-center px-2 py-1 rounded-full bg-purple-500/20 backdrop-blur-sm">
<SquareUserRound className="w-3 h-3 text-purple-400 mr-1" />
<span className="text-xs text-purple-400">Role</span>
</div>
)}
{/* 场景类型 */}
{sketch.type === 'scene' && (
<div className="inline-flex items-center px-2 py-1 rounded-full bg-blue-500/20 backdrop-blur-sm">
<MapPinHouse className="w-3 h-3 text-blue-400 mr-1" />
<span className="text-xs text-blue-400">Scene</span>
</div>
)}
{/* 分镜类型 */}
{(!sketch.type || sketch.type === 'shot_sketch') && (
<div className="inline-flex items-center px-2 py-1 rounded-full bg-amber-500/20 backdrop-blur-sm">
<Clapperboard className="w-3 h-3 text-amber-400 mr-1" />
<span className="text-xs text-amber-400">Shot {index + 1}</span>
</div>
)}
</div>
</div>
);
@ -269,7 +288,7 @@ export function ThumbnailGrid({
<div
ref={thumbnailsRef}
tabIndex={0}
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"
className="w-full h-full grid grid-flow-col auto-cols-[20%] gap-2 overflow-x-auto hide-scrollbar px-1 py-1 cursor-grab active:cursor-grabbing focus:outline-none select-none"
autoFocus
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
@ -279,9 +298,8 @@ export function ThumbnailGrid({
onBlur={() => setIsFocused(false)}
>
{taskObject.currentStage === 'video' && renderVideoThumbnails()}
{taskObject.currentStage === 'scene' && renderSketchThumbnails(taskObject.scenes.data)}
{(taskObject.currentStage === 'scene' || taskObject.currentStage === 'character') && renderSketchThumbnails(getCurrentData())}
{taskObject.currentStage === 'shot_sketch' && renderSketchThumbnails(taskObject.shot_sketch.data)}
{taskObject.currentStage === 'character' && renderSketchThumbnails(taskObject.scenes.data)}
</div>
);
}

View File

@ -215,7 +215,8 @@ export function useWorkflowData() {
characterList.push({
name: character.character_name,
url: character.image_path,
status: character.image_path ? 1 : (task.task_status === 'COMPLETED' ? 2 : 0)
status: character.image_path ? 1 : (task.task_status === 'COMPLETED' ? 2 : 0),
type: 'role'
});
}
taskCurrent.roles.data = characterList;
@ -242,7 +243,8 @@ export function useWorkflowData() {
sketchList.push({
url: sketch.image_path,
script: sketch.sketch_name,
status: sketch.image_path ? 1 : (task.task_status === 'COMPLETED' ? 2 : 0)
status: sketch.image_path ? 1 : (task.task_status === 'COMPLETED' ? 2 : 0),
type: 'scene'
});
}
taskCurrent.scenes.data = sketchList;
@ -269,7 +271,8 @@ export function useWorkflowData() {
sketchList.push({
url: sketch.url,
script: sketch.description,
status: sketch.url ? 1 : (task.task_status === 'COMPLETED' ? 2 : 0)
status: sketch.url ? 1 : (task.task_status === 'COMPLETED' ? 2 : 0),
type: 'shot_sketch'
});
}
taskCurrent.shot_sketch.data = sketchList;
@ -302,6 +305,7 @@ export function useWorkflowData() {
urls: video.urls,
video_id: video.video_id,
video_status: video_status, // 0 生成中 1 生成完成 2 生成失败
type: 'video'
});
}
taskCurrent.videos.data = videoList;
@ -427,7 +431,8 @@ export function useWorkflowData() {
characterList.push({
name: character.character_name,
url: character.image_path,
status: character.image_path ? 1 : (data.character.task_status === 'COMPLETED' ? 2 : 0)
status: character.image_path ? 1 : (data.character.task_status === 'COMPLETED' ? 2 : 0),
type: 'role'
});
}
taskCurrent.roles.data = characterList;
@ -446,7 +451,8 @@ export function useWorkflowData() {
sketchList.push({
url: sketch.image_path,
script: sketch.sketch_name,
status: sketch.image_path ? 1 : (data.sketch.task_status === 'COMPLETED' ? 2 : 0)
status: sketch.image_path ? 1 : (data.sketch.task_status === 'COMPLETED' ? 2 : 0),
type: 'scene'
});
}
taskCurrent.scenes.data = sketchList;
@ -466,7 +472,8 @@ export function useWorkflowData() {
sketchList.push({
url: sketch.url,
script: sketch.description,
status: sketch.url ? 1 : (data.shot_sketch.task_status === 'COMPLETED' ? 2 : 0)
status: sketch.url ? 1 : (data.shot_sketch.task_status === 'COMPLETED' ? 2 : 0),
type: 'shot_sketch'
});
}
taskCurrent.shot_sketch.data = sketchList;
@ -492,6 +499,7 @@ export function useWorkflowData() {
urls: video.urls,
video_id: video.video_id,
video_status: video_status, // 0 生成中 1 生成完成 2 生成失败
type: 'video'
});
}
taskCurrent.videos.data = videoList;