forked from 77media/video-flow
253 lines
11 KiB
TypeScript
253 lines
11 KiB
TypeScript
import React from 'react';
|
|
import { Camera } from 'lucide-react';
|
|
import { TypewriterText } from './common/TypewriterText';
|
|
import { ContentCard } from './common/ContentCard';
|
|
import { SkeletonCard } from './common/SkeletonCard';
|
|
import { IconLoading } from './common/IconLoading';
|
|
|
|
interface StoryboardArtistProps {
|
|
currentContent: any;
|
|
isPlaying: boolean;
|
|
sketchType: string;
|
|
}
|
|
|
|
const SceneStoryboard = (currentContent: any, isPlaying: boolean) => {
|
|
return (
|
|
<div className="space-y-4 h-full overflow-y-auto">
|
|
<div className='grid grid-cols-2 gap-4'>
|
|
{/* 场景定位选择 */}
|
|
<div className="bg-black/30 rounded-lg p-4">
|
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
|
<span>Scene location selection</span>
|
|
<IconLoading icon={Camera} isActive={isPlaying} color="#06b6d4" />
|
|
</h3>
|
|
<div className="space-y-3">
|
|
{currentContent.location ? (
|
|
<div className="text-gray-300 text-xs">
|
|
<TypewriterText text={currentContent.location} stableId={currentContent.location} />
|
|
</div>
|
|
) : (
|
|
<SkeletonCard className="bg-cyan-500/20 rounded-lg p-3 border border-cyan-500/30" />
|
|
)}
|
|
</div>
|
|
</div>
|
|
{/* 场景氛围选择 */}
|
|
<div className="bg-black/30 rounded-lg p-4">
|
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
|
<span>Scene atmosphere selection</span>
|
|
<IconLoading icon={Camera} isActive={isPlaying} color="#06b6d4" />
|
|
</h3>
|
|
<div className="space-y-3">
|
|
{currentContent.core_atmosphere ? (
|
|
<div className="text-gray-300 text-xs">
|
|
<TypewriterText text={currentContent.core_atmosphere} stableId={currentContent.core_atmosphere} />
|
|
</div>
|
|
) : (
|
|
<SkeletonCard className="bg-cyan-500/20 rounded-lg p-3 border border-cyan-500/30" />
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/* 场景描述 */}
|
|
<div className='bg-black/30 rounded-lg p-4'>
|
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
|
<span>Scene description</span>
|
|
<IconLoading icon={Camera} isActive={isPlaying} color="#06b6d4" />
|
|
</h3>
|
|
<div className='space-y-3'>
|
|
{currentContent.description ? (
|
|
<div className="text-gray-300 text-xs">
|
|
<TypewriterText text={currentContent.description} stableId={currentContent.description} />
|
|
</div>
|
|
) : (
|
|
<SkeletonCard className="bg-cyan-500/20 rounded-lg p-3 border border-cyan-500/30" />
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const ShotSketchStoryboard = (currentContent: any, isPlaying: boolean) => {
|
|
return (
|
|
<div className="grid grid-cols-2 gap-4 h-full">
|
|
{/* 左侧:镜头语言和构图美学 */}
|
|
<div className="space-y-4 overflow-y-auto">
|
|
{/* 镜头语言选择 */}
|
|
<div className="bg-black/30 rounded-lg p-4">
|
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
|
<span>Shot language selection</span>
|
|
<IconLoading icon={Camera} isActive={isPlaying} color="#06b6d4" />
|
|
</h3>
|
|
<div className="space-y-3">
|
|
{currentContent.shotLanguage && currentContent.shotLanguage.length > 0 ? (
|
|
currentContent.shotLanguage.map((shot: any, index: number) => (
|
|
<ContentCard
|
|
key={index}
|
|
className="bg-cyan-500/20 rounded-lg p-3 border border-cyan-500/30"
|
|
>
|
|
<div className="flex justify-between items-center mb-2">
|
|
<span className="text-cyan-300 font-medium text-sm">{shot}</span>
|
|
</div>
|
|
<div className="text-gray-300 text-xs leading-relaxed">
|
|
<TypewriterText text={currentContent.frame_description} stableId={`shot${index}`} />
|
|
</div>
|
|
</ContentCard>
|
|
))
|
|
) : (
|
|
Array.from({length: 4}, (_, i) => (
|
|
<SkeletonCard key={i} className="bg-cyan-500/20 rounded-lg p-3 border border-cyan-500/30" />
|
|
))
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 构图美学运用 */}
|
|
<div className="bg-black/30 rounded-lg p-4">
|
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
|
<span>Composition aesthetics</span>
|
|
<IconLoading icon={Camera} isActive={isPlaying} color="#06b6d4" />
|
|
</h3>
|
|
{currentContent.atmosphere && currentContent.atmosphere.length > 0 ? (
|
|
<ContentCard className="space-y-3">
|
|
{currentContent.atmosphere.map((atmosphere: any, index: number) => (
|
|
<div key={index}>
|
|
<div className="text-cyan-300 text-sm font-medium">
|
|
<TypewriterText text={atmosphere} stableId={`${index}-atmosphere`} />
|
|
</div>
|
|
</div>
|
|
))}
|
|
</ContentCard>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{Array.from({length: 3}, (_, i) => (
|
|
<SkeletonCard key={i} className="bg-cyan-500/20 rounded-lg p-3 border border-cyan-500/30" />
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 右侧:摄影机运动和叙事逻辑 */}
|
|
<div className="space-y-4 overflow-y-auto">
|
|
{/* 摄影机运动设计 */}
|
|
<div className="bg-black/30 rounded-lg p-4">
|
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
|
<span>Camera movement design</span>
|
|
<IconLoading icon={Camera} isActive={isPlaying} color="#06b6d4" />
|
|
</h3>
|
|
<div className="space-y-3">
|
|
{currentContent.camera_motion && currentContent.camera_motion.length > 0 ? (
|
|
currentContent.camera_motion.map((move: any, index: number) => (
|
|
<ContentCard
|
|
key={index}
|
|
className="bg-cyan-400/20 rounded-lg p-3"
|
|
>
|
|
<div className="text-cyan-200 font-medium text-sm mb-1">{move}</div>
|
|
<div className="text-cyan-300 text-xs">
|
|
<TypewriterText text={currentContent.composition} stableId={`${index}-composition`} />
|
|
</div>
|
|
</ContentCard>
|
|
))
|
|
) : (
|
|
Array.from({length: 3}, (_, i) => (
|
|
<SkeletonCard key={i} className="bg-cyan-500/20 rounded-lg p-3 border border-cyan-500/30" />
|
|
))
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 视觉叙事逻辑 */}
|
|
<div className="bg-black/30 rounded-lg p-4">
|
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
|
<span>Visual storytelling logic</span>
|
|
<IconLoading icon={Camera} isActive={isPlaying} color="#06b6d4" />
|
|
</h3>
|
|
{currentContent.key_action ? (
|
|
<ContentCard className="space-y-3">
|
|
<div>
|
|
<div className="text-cyan-300 text-sm font-medium mb-1">Storytelling logic</div>
|
|
<div className="text-gray-300 text-xs">
|
|
<TypewriterText text={currentContent.frame_description} stableId={`frame-description-logic`} />
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div className="text-cyan-300 text-sm font-medium mb-1">Key emphasis</div>
|
|
<div className="text-gray-300 text-xs">
|
|
<TypewriterText text={currentContent.key_action} stableId={`key-action-emphasis`} />
|
|
</div>
|
|
</div>
|
|
</ContentCard>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{Array.from({length: 3}, (_, i) => (
|
|
<SkeletonCard key={i} className="bg-cyan-500/20 rounded-lg p-3 border border-cyan-500/30" />
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* 对话表演 */}
|
|
<div className="bg-black/30 rounded-lg p-4">
|
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
|
<span>Dialogue performance</span>
|
|
<IconLoading icon={Camera} isActive={isPlaying} color="#06b6d4" />
|
|
</h3>
|
|
<div className="space-y-2">
|
|
{currentContent.dialogue_performance ? (
|
|
<>
|
|
<ContentCard
|
|
className="bg-cyan-300/20 rounded p-2"
|
|
>
|
|
<div className="text-cyan-200 text-sm font-medium">Speaker</div>
|
|
<div className="text-gray-300 text-xs">
|
|
<TypewriterText text={currentContent.dialogue_performance.speaker} stableId={`speaker`} />
|
|
</div>
|
|
</ContentCard>
|
|
<ContentCard
|
|
className="bg-cyan-300/20 rounded p-2"
|
|
>
|
|
<div className="text-cyan-200 text-sm font-medium">Language</div>
|
|
<div className="text-gray-300 text-xs">
|
|
<TypewriterText text={currentContent.dialogue_performance.language} stableId={`language`} />
|
|
</div>
|
|
</ContentCard>
|
|
<ContentCard
|
|
className="bg-cyan-300/20 rounded p-2"
|
|
>
|
|
<div className="text-cyan-200 text-sm font-medium">Delivery</div>
|
|
<div className="text-gray-300 text-xs">
|
|
<TypewriterText text={currentContent.dialogue_performance.delivery} stableId={`delivery`} />
|
|
</div>
|
|
</ContentCard>
|
|
<ContentCard
|
|
className="bg-cyan-300/20 rounded p-2"
|
|
>
|
|
<div className="text-cyan-200 text-sm font-medium">Line</div>
|
|
<div className="text-gray-300 text-xs">
|
|
<TypewriterText text={currentContent.dialogue_performance.line} stableId={`line`} />
|
|
</div>
|
|
</ContentCard>
|
|
</>
|
|
) : (
|
|
Array.from({length: 4}, (_, i) => (
|
|
<SkeletonCard key={i} className="bg-cyan-500/20 rounded-lg p-3 border border-cyan-500/30" />
|
|
))
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const StoryboardArtist: React.FC<StoryboardArtistProps> = ({ currentContent, isPlaying, sketchType }) => {
|
|
return (
|
|
<>
|
|
{sketchType === 'scene' ? <SceneStoryboard currentContent={currentContent} isPlaying={isPlaying} /> : <ShotSketchStoryboard currentContent={currentContent} isPlaying={isPlaying} />}
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default StoryboardArtist;
|