290 lines
11 KiB
TypeScript

import React 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">{`情感强度: ${payload[0].value}`}</p>
</div>
);
}
return null;
};
const Scriptwriter: React.FC<ScriptwriterProps> = ({ currentContent, isPlaying }) => {
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></span>
<IconLoading icon={Heart} isActive={!currentContent.acts && isPlaying} color="#8b5cf6" />
</h3>
<div className="space-y-3">
{currentContent.acts && currentContent.acts.length > 0 ? (
currentContent.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>
{/* 角色弧光设计 */}
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span></span>
<IconLoading icon={Heart} isActive={!currentContent.characters && isPlaying} color="#8b5cf6" />
</h3>
<div className="space-y-3">
{currentContent.characters && currentContent.characters.length > 0 ? (
currentContent.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>
</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></span>
<IconLoading icon={Heart} isActive={!currentContent.dialogue && isPlaying} color="#8b5cf6" />
</h3>
{currentContent.dialogue ? (
<div className="space-y-3">
<div>
<div className="text-purple-300 text-sm font-medium mb-1"></div>
<div className="text-gray-300 text-xs">
<TypewriterText text={currentContent.dialogue.rhythm} stableId={`${currentContent.dialogue.stableId}-rhythm`} />
</div>
</div>
<div>
<div className="text-purple-300 text-sm font-medium mb-1"></div>
<div className="text-gray-300 text-xs">
<TypewriterText text={currentContent.dialogue.style} stableId={`${currentContent.dialogue.stableId}-style`} />
</div>
</div>
</div>
) : (
<SkeletonCard className="bg-purple-500/20 rounded-lg p-3 border border-purple-500/30" />
)}
</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></span>
<IconLoading icon={Heart} isActive={!currentContent.themes && isPlaying} color="#8b5cf6" />
</h3>
<div className="space-y-3">
{currentContent.themes ? (
<>
{currentContent.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>
{/* 剧情起伏戏演线 */}
<div className="bg-black/30 rounded-lg p-4">
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
<span>线</span>
<IconLoading icon={Heart} isActive={!currentContent.dramaticLine && isPlaying} color="#8b5cf6" />
</h3>
{currentContent.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={currentContent.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: '情感强度',
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">
{currentContent.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>
</div>
</div>
);
};
export default Scriptwriter;