2025-07-25 11:41:12 +08:00

314 lines
11 KiB
TypeScript

import React, { useMemo } from 'react';
import { Heart } from 'lucide-react';
import { motion } from 'framer-motion';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
import { TypewriterText } from './common/TypewriterText';
import { ContentCard } from './common/ContentCard';
import { SkeletonCard } from './common/SkeletonCard';
import { IconLoading } from './common/IconLoading';
interface ScriptContent {
acts?: Array<{
id: string;
stableId: string;
title: string;
desc: string;
beats: string[];
}>;
characters?: Array<{
id: string;
stableId: string;
name: string;
role: string;
arc: string;
desc: string;
color: string;
}>;
dialogue?: {
stableId: string;
rhythm: string;
style: string;
};
themes?: Array<{
id: string;
stableId: string;
theme: string;
desc: string;
depth: string;
}>;
dramaticLine?: {
stableId: string;
points: Array<{
id: string;
stableId: string;
title: string;
desc: string;
intensity: number; // 0-100 情感强度
}>;
};
}
interface ScriptwriterProps {
currentContent: ScriptContent;
isPlaying: boolean;
}
const CustomTooltip = ({ active, payload, label }: any) => {
if (active && payload && payload.length) {
return (
<div className="bg-black/80 backdrop-blur-sm p-2 rounded-lg border border-purple-500/30">
<p className="text-purple-300 text-xs font-medium">{payload[0].payload.title}</p>
<p className="text-white text-xs">{`Emotional intensity: ${payload[0].value}`}</p>
</div>
);
}
return null;
};
// Three-act Structure Component
const ThreeActStructure = React.memo(({ acts, isPlaying }: { acts?: ScriptContent['acts']; isPlaying: boolean }) => {
return (
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span>Three-act structure</span>
<IconLoading icon={Heart} isActive={isPlaying} color="#8b5cf6" />
</h3>
<div className="space-y-3">
{acts && acts.length > 0 ? (
acts.map((act) => (
<ContentCard
key={act.stableId}
className="bg-purple-500/20 rounded-lg p-3 border border-purple-500/30"
>
<div className="text-purple-300 font-medium text-sm mb-2">
{act.title}
</div>
<div className="text-gray-300 text-xs leading-relaxed mb-2">
<TypewriterText text={act.desc} stableId={act.stableId} />
</div>
<div className="flex flex-wrap gap-1">
{act.beats?.map((beat, index) => (
<span key={index} className="text-xs bg-purple-500/30 px-2 py-1 rounded">
{beat}
</span>
))}
</div>
</ContentCard>
))
) : (
Array.from({length: 3}, (_, i) => (
<SkeletonCard key={i} className="bg-purple-500/20 rounded-lg p-3 border border-purple-500/30" />
))
)}
</div>
</div>
);
});
// Character Arc Design Component
const CharacterArcDesign = React.memo(({ characters, isPlaying }: { characters?: ScriptContent['characters']; isPlaying: boolean }) => {
return (
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span>Character arc design</span>
<IconLoading icon={Heart} isActive={isPlaying} color="#8b5cf6" />
</h3>
<div className="space-y-3">
{characters && characters.length > 0 ? (
characters.map((char) => (
<ContentCard
key={char.stableId}
className="bg-slate-700/50 rounded-lg p-3 border border-slate-600/50"
>
<div className="flex items-center space-x-2 mb-2">
<div
className="w-3 h-3 rounded-full"
style={{ backgroundColor: char.color }}
/>
<span className="text-white font-medium text-sm">{char.name}</span>
<span className="text-gray-400 text-xs">({char.role})</span>
</div>
<div className="text-xs mb-2" style={{ color: char.color }}>
{char.arc}
</div>
<div className="text-gray-300 text-xs leading-relaxed">
<TypewriterText text={char.desc} stableId={char.stableId} />
</div>
</ContentCard>
))
) : (
Array.from({length: 3}, (_, i) => (
<SkeletonCard key={i} className="bg-purple-500/20 rounded-lg p-3 border border-purple-500/30" />
))
)}
</div>
</div>
);
});
// Dialogue Rhythm Component
const DialogueRhythm = React.memo(({ dialogue, isPlaying }: { dialogue?: ScriptContent['dialogue']; isPlaying: boolean }) => {
return (
<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 rhythm</span>
<IconLoading icon={Heart} isActive={isPlaying} color="#8b5cf6" />
</h3>
{dialogue ? (
<div className="space-y-3">
<div>
<div className="text-purple-300 text-sm font-medium mb-1">Rhythm control</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={dialogue.rhythm} stableId={`${dialogue.stableId}-rhythm`} />
</div>
</div>
<div>
<div className="text-purple-300 text-sm font-medium mb-1">Expression style</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={dialogue.style} stableId={`${dialogue.stableId}-style`} />
</div>
</div>
</div>
) : (
<SkeletonCard className="bg-purple-500/20 rounded-lg p-3 border border-purple-500/30" />
)}
</div>
);
});
// Theme Development Component
const ThemeDevelopment = React.memo(({ themes, isPlaying }: { themes?: ScriptContent['themes']; isPlaying: boolean }) => {
return (
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span>Theme deepening process</span>
<IconLoading icon={Heart} isActive={isPlaying} color="#8b5cf6" />
</h3>
<div className="space-y-3">
{themes ? (
themes.map((theme) => (
<ContentCard
key={theme.stableId}
className="bg-purple-400/10 rounded p-3"
>
<div className="text-purple-200 text-sm font-medium mb-1">{theme.theme}</div>
<div className="text-gray-300 text-xs mb-2">
<TypewriterText text={theme.desc} stableId={`${theme.stableId}-desc`} />
</div>
<div className="text-gray-400 text-xs">
<TypewriterText text={theme.depth} stableId={`${theme.stableId}-depth`} />
</div>
</ContentCard>
))
) : (
Array.from({length: 2}, (_, i) => (
<SkeletonCard key={i} className="bg-purple-500/20 rounded-lg p-3 border border-purple-500/30" />
))
)}
</div>
</div>
);
});
// Dramatic Line Component
const DramaticLine = React.memo(({ dramaticLine, isPlaying }: { dramaticLine?: ScriptContent['dramaticLine']; isPlaying: boolean }) => {
return (
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span>Dramatic line</span>
<IconLoading icon={Heart} isActive={isPlaying} color="#8b5cf6" />
</h3>
{dramaticLine ? (
<div className="space-y-4">
<div className="h-48 bg-purple-500/10 rounded-lg p-2">
<ResponsiveContainer width="100%" height="100%">
<LineChart
data={dramaticLine.points || []}
margin={{ top: 10, right: 10, left: 0, bottom: 10 }}
>
<CartesianGrid strokeDasharray="3 3" stroke="#374151" />
<XAxis
dataKey="title"
tick={{ fill: '#9CA3AF', fontSize: 10 }}
stroke="#4B5563"
/>
<YAxis
domain={[0, 100]}
tick={{ fill: '#9CA3AF', fontSize: 10 }}
stroke="#4B5563"
label={{
value: 'Emotional intensity',
angle: -90,
position: 'insideLeft',
fill: '#9CA3AF',
fontSize: 12
}}
/>
<Tooltip content={<CustomTooltip />} />
<Line
type="monotone"
dataKey="intensity"
stroke="#8B5CF6"
strokeWidth={2}
dot={{
fill: '#8B5CF6',
stroke: '#C4B5FD',
strokeWidth: 2,
r: 4
}}
activeDot={{
fill: '#8B5CF6',
stroke: '#C4B5FD',
strokeWidth: 2,
r: 6
}}
/>
</LineChart>
</ResponsiveContainer>
</div>
<div className="space-y-2">
{dramaticLine.points?.map((point) => (
<ContentCard
key={point.stableId}
className="bg-purple-400/10 rounded p-2"
>
<div className="text-purple-200 text-sm font-medium mb-1">{point.title}</div>
<div className="text-gray-300 text-xs">
<TypewriterText text={point.desc} stableId={point.stableId} />
</div>
</ContentCard>
))}
</div>
</div>
) : (
<div className="space-y-4">
<SkeletonCard className="h-48 bg-purple-500/20 rounded-lg border border-purple-500/30" />
</div>
)}
</div>
);
});
const Scriptwriter: React.FC<ScriptwriterProps> = React.memo(({ currentContent, isPlaying }) => {
return (
<div className="grid grid-cols-2 gap-4 h-full">
{/* 左侧:三幕结构和角色弧光 */}
<div className="space-y-4 overflow-y-auto">
<ThreeActStructure acts={currentContent.acts} isPlaying={isPlaying} />
<CharacterArcDesign characters={currentContent.characters} isPlaying={isPlaying} />
</div>
{/* 右侧:对白节奏和主题深化 */}
<div className="space-y-4 overflow-y-auto">
<DialogueRhythm dialogue={currentContent.dialogue} isPlaying={isPlaying} />
<ThemeDevelopment themes={currentContent.themes} isPlaying={isPlaying} />
<DramaticLine dramaticLine={currentContent.dramaticLine} isPlaying={isPlaying} />
</div>
</div>
);
});
Scriptwriter.displayName = 'Scriptwriter';
export default Scriptwriter;