video-flow-b/components/workflow/generate-chapters-step.tsx
2025-06-19 17:15:03 +08:00

230 lines
9.3 KiB
TypeScript

"use client";
import { useState } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Textarea } from '@/components/ui/textarea';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Checkbox } from '@/components/ui/checkbox';
import { ArrowLeft, ArrowRight, Edit, RefreshCw, Users, Play, Plus, X } from 'lucide-react';
interface GenerateChaptersStepProps {
onNext: () => void;
onPrevious: () => void;
}
const mockChapters = [
{
id: 1,
title: '夜空烟花秀:璀璨瞬间',
content: '有没有想过,为什么烟花总能让我们突得那么开心?(停顿)当五颜六色的烟花在空中绽放时,人们会聚集在一起,眼睛睁得大大的。(停顿)每一次闪耀,每一次炸呼——都充满了魔力!(停顿)和朋友们一起在星空下看烟花,这许愿就是无与伦比的。',
duration: '45s',
selectedActors: [1],
},
{
id: 2,
title: 'Core Concepts',
content: 'Let\'s dive into the core concepts of artificial intelligence. We\'ll break down complex topics into digestible pieces that anyone can understand.',
duration: '1m 20s',
selectedActors: [2],
},
{
id: 3,
title: 'Practical Applications',
content: 'Now we\'ll examine real-world applications of AI technology across various industries and how they\'re transforming our daily lives.',
duration: '1m 15s',
selectedActors: [1, 3],
},
{
id: 4,
title: 'Future Outlook',
content: 'Finally, let\'s look ahead to the future of AI and what exciting developments we can expect in the coming years.',
duration: '50s',
selectedActors: [3],
},
];
const availableActors = [
{
id: 1,
name: 'Liu Wei 1.10x',
voice: 'Professional Female',
avatar: 'https://images.pexels.com/photos/774909/pexels-photo-774909.jpeg?auto=compress&cs=tinysrgb&w=100',
},
{
id: 2,
name: 'Dr. Marcus Webb',
voice: 'Expert Male',
avatar: 'https://images.pexels.com/photos/1222271/pexels-photo-1222271.jpeg?auto=compress&cs=tinysrgb&w=100',
},
{
id: 3,
name: 'Alex Rivera',
voice: 'Enthusiastic Neutral',
avatar: 'https://images.pexels.com/photos/1239291/pexels-photo-1239291.jpeg?auto=compress&cs=tinysrgb&w=100',
},
{
id: 4,
name: 'Lisa Park',
voice: 'Friendly Female',
avatar: 'https://images.pexels.com/photos/1181686/pexels-photo-1181686.jpeg?auto=compress&cs=tinysrgb&w=100',
},
];
export function GenerateChaptersStep({ onNext, onPrevious }: GenerateChaptersStepProps) {
const [chapters, setChapters] = useState(mockChapters);
const [editingChapter, setEditingChapter] = useState<number | null>(null);
const handleChapterEdit = (chapterId: number, content: string) => {
setChapters(chapters.map(ch =>
ch.id === chapterId ? { ...ch, content } : ch
));
setEditingChapter(null);
};
const handleActorToggle = (chapterId: number, actorId: number) => {
setChapters(chapters.map(ch => {
if (ch.id === chapterId) {
const isSelected = ch.selectedActors.includes(actorId);
const newSelectedActors = isSelected
? ch.selectedActors.filter(id => id !== actorId)
: [...ch.selectedActors, actorId];
return { ...ch, selectedActors: newSelectedActors };
}
return ch;
}));
};
const getSelectedActors = (chapterId: number) => {
const chapter = chapters.find(ch => ch.id === chapterId);
if (!chapter) return [];
return availableActors.filter(actor => chapter.selectedActors.includes(actor.id));
};
return (
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<Users className="h-5 w-5" />
<span>Generated Chapters & Actor Assignment</span>
</div>
<Badge variant="secondary">
{chapters.length} Chapters
</Badge>
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground mb-6">
AI has automatically split your script into chapters and suggested actors for each section.
You can edit the content and assign multiple actors as needed.
</p>
<div className="space-y-6">
{chapters.map((chapter, index) => (
<Card key={chapter.id} className="border-l-4 border-l-primary/20">
<CardHeader>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<h3 className="font-semibold text-lg">Chapter {index + 1}: {chapter.title}</h3>
</div>
</div>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Left side - Chapter Content */}
<div className="space-y-4">
<div className="space-y-2">
<h4 className="text-sm font-medium text-muted-foreground">Chapter Content</h4>
{editingChapter === chapter.id ? (
<div className="space-y-3">
<Textarea
value={chapter.content}
onChange={(e) => {
const updatedContent = e.target.value;
setChapters(chapters.map(ch =>
ch.id === chapter.id ? { ...ch, content: updatedContent } : ch
));
}}
onBlur={() => handleChapterEdit(chapter.id, chapter.content)}
className="min-h-[120px] text-sm"
/>
</div>
) : (
<div
className="text-sm leading-relaxed bg-muted/50 p-4 rounded-lg border cursor-pointer hover:bg-muted/70 transition-colors"
onClick={() => setEditingChapter(chapter.id)}
>
{chapter.content}
</div>
)}
</div>
</div>
{/* Right side - Actor Selection */}
<div className="space-y-4">
<div className="flex items-center justify-between">
<h4 className="text-sm font-medium text-muted-foreground">Select Actors</h4>
<Badge variant="outline" className="text-xs">
{getSelectedActors(chapter.id).length} selected
</Badge>
</div>
{/* Selected Actors Display */}
{getSelectedActors(chapter.id).length > 0 && (
<div className="space-y-2">
<div className="text-xs font-medium text-muted-foreground">Selected:</div>
<div className="flex flex-wrap gap-2">
{getSelectedActors(chapter.id).map((actor) => (
<div
key={actor.id}
className="flex items-center space-x-2 bg-primary/10 text-primary px-3 py-1 rounded-full text-xs"
>
<Avatar className="h-5 w-5">
<AvatarImage src={actor.avatar} />
<AvatarFallback className="text-xs">
{actor.name.split(' ').map(n => n[0]).join('')}
</AvatarFallback>
</Avatar>
<span>{actor.name}</span>
<Button
variant="ghost"
size="sm"
className="h-4 w-4 p-0 hover:bg-destructive/20"
onClick={() => handleActorToggle(chapter.id, actor.id)}
>
<X className="h-3 w-3" />
</Button>
</div>
))}
<div className="w-8 h-8 rounded-full bg-muted flex items-center justify-center">
<Plus className="h-4 w-4" />
</div>
</div>
</div>
)}
</div>
</div>
</CardContent>
</Card>
))}
</div>
</CardContent>
</Card>
{/* Action Buttons */}
<div className="flex justify-between">
<Button variant="outline" onClick={onPrevious}>
<ArrowLeft className="mr-2 h-4 w-4" />
Back to Script
</Button>
<Button onClick={onNext}>
Generate Scenes
<ArrowRight className="ml-2 h-4 w-4" />
</Button>
</div>
</div>
);
}