"use client"; import { useState, useRef } from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Slider } from '@/components/ui/slider'; import { ArrowLeft, ArrowRight, Play, Trash2, Replace, Scissors, Volume2, Edit, Upload, Image, Sparkles, ChevronLeft, ChevronRight, Layers, Pause, File, Ruler, UnfoldHorizontal, RefreshCcw, RotateCcw } from 'lucide-react'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Checkbox } from '@/components/ui/checkbox'; import { Label } from '@/components/ui/label'; import { Input } from '@/components/ui/input'; interface GenerateShotsStepProps { onNext: () => void; onPrevious: () => void; } interface Shot { id: string; type: string; duration: number; shotVideo: string; generatedVideos: string[]; description: string; transition: string; volume: number; mediaNumber: number; } interface Chapter { id: number; title: string; shots: Shot[]; } // 时间轴组件 const TimelineView = ({ chapters, selectedShot, onShotSelect, onVideoCheck }: { chapters: Chapter[]; selectedShot: string; onShotSelect: (shotId: string) => void; onVideoCheck?: () => void; }) => (
{chapters.map((chapter) => (
Chapter {chapter.id}
{chapter.shots.map((shot) => (
onShotSelect(shot.id)} >
))}
))}
); // 媒体信息项组件 const MediaInfoItem = ({ icon, text, popoverContent }: { icon: React.ReactNode; text: string; popoverContent?: React.ReactNode; }) => (
{icon} {text} {popoverContent && ( {popoverContent} )}
); // 查看视频弹窗 const CheckVideoDialog = ({ isOpen, onClose, currentShot }: { isOpen: boolean; onClose: (open: boolean) => void; currentShot?: Shot; }) => ( Media history
{currentShot?.generatedVideos.map((video, index) => (
))}
); // 替换媒体弹窗 const ReplaceMediaDialog = ({ isOpen, onClose, chapters, selectedShot, onShotSelect, activeTab, setActiveTab }: { isOpen: boolean; onClose: (open: boolean) => void; chapters: Chapter[]; selectedShot: string; onShotSelect: (shotId: string) => void; activeTab: string; setActiveTab: (tab: string) => void; }) => { const replaceMediaTabs = [ { value: 'uploaded', label: 'Uploaded media' }, { value: 'stock', label: 'Stock media' }, { value: 'generative', label: 'Generative media' }, ]; return ( Replace media
{replaceMediaTabs.map((tab) => ( {tab.label} ))}

No media uploaded

); }; // 媒体属性弹窗 const MediaPropertyDialog = ({ isOpen, onClose, chapters, selectedShot, onShotSelect, currentShot, activeTab, setActiveTab }: { isOpen: boolean; onClose: (open: boolean) => void; chapters: Chapter[]; selectedShot: string; onShotSelect: (shotId: string) => void; currentShot?: Shot; activeTab: string; setActiveTab: (tab: string) => void; }) => { const mediaPropertyTabs = [ { value: 'media', label: 'Media' }, { value: 'audio', label: 'Audio & SFX' }, ]; return ( Media properties
{mediaPropertyTabs.map((tab) => ( {tab.label} ))}
00m : 10s : 500ms / 00m : 17s : 320ms
This part of the script is 21.00 seconds long.
There are 2 media attached to this part of the script:
Airplane Rocket Fire Close
{currentShot && (
Chapter 1 / media 1 / People gathered in a city square to watch a fireworks display
0:00
0:12
{Array.from({ length: 40 }).map((_, i) => (
))}
Chapter 1 / Audio & SFX / Airplane Rocket Fire Close
)}
); }; const mockChapters: Chapter[] = [ { id: 1, title: 'Chapter 1', shots: [ { id: '1-1', type: 'talking-head', duration: 8, shotVideo: 'https://cdn.qikongjian.com/videos/1750162598_db834807-3f1c-4c52-8f50-b25396bd73ef_text_to_video_0.mp4', generatedVideos:[ 'https://cdn.qikongjian.com/videos/1750162598_db834807-3f1c-4c52-8f50-b25396bd73ef_text_to_video_0.mp4', 'https://cdn.qikongjian.com/videos/1750162598_db834807-3f1c-4c52-8f50-b25396bd73ef_text_to_video_0.mp4' ], description: 'Opening welcome shot with character', transition: 'Selected Automatically by Preset', volume: 55, mediaNumber: 1, }, { id: '1-2', type: 'b-roll', duration: 9, shotVideo: 'https://cdn.qikongjian.com/videos/1750162598_db834807-3f1c-4c52-8f50-b25396bd73ef_text_to_video_0.mp4', generatedVideos:[ 'https://cdn.qikongjian.com/videos/1750162598_db834807-3f1c-4c52-8f50-b25396bd73ef_text_to_video_0.mp4', 'https://cdn.qikongjian.com/videos/1750162598_db834807-3f1c-4c52-8f50-b25396bd73ef_text_to_video_0.mp4', 'https://cdn.qikongjian.com/videos/1750162598_db834807-3f1c-4c52-8f50-b25396bd73ef_text_to_video_0.mp4' ], description: 'Technology overview montage', transition: 'Selected Automatically by Preset', volume: 55, mediaNumber: 2, } ], }, { id: 2, title: 'Chapter 2', shots: [ { id: '2-1', type: 'talking-head', duration: 8, shotVideo: 'https://cdn.qikongjian.com/videos/1750162598_db834807-3f1c-4c52-8f50-b25396bd73ef_text_to_video_0.mp4', generatedVideos:[ 'https://cdn.qikongjian.com/videos/1750162598_db834807-3f1c-4c52-8f50-b25396bd73ef_text_to_video_0.mp4' ], description: 'Opening welcome shot with character', transition: 'Selected Automatically by Preset', volume: 55, mediaNumber: 1, }, { id: '2-2', type: 'b-roll', duration: 9, shotVideo: 'https://cdn.qikongjian.com/videos/1750162598_db834807-3f1c-4c52-8f50-b25396bd73ef_text_to_video_0.mp4', generatedVideos:[ 'https://cdn.qikongjian.com/videos/1750162598_db834807-3f1c-4c52-8f50-b25396bd73ef_text_to_video_0.mp4', 'https://cdn.qikongjian.com/videos/1750162598_db834807-3f1c-4c52-8f50-b25396bd73ef_text_to_video_0.mp4', 'https://cdn.qikongjian.com/videos/1750162598_db834807-3f1c-4c52-8f50-b25396bd73ef_text_to_video_0.mp4' ], description: 'Technology overview montage', transition: 'Selected Automatically by Preset', volume: 55, mediaNumber: 2, }, { id: '2-3', type: 'animation', duration: 10, shotVideo: 'https://cdn.qikongjian.com/videos/1750162598_db834807-3f1c-4c52-8f50-b25396bd73ef_text_to_video_0.mp4', generatedVideos:[ 'https://cdn.qikongjian.com/videos/1750162598_db834807-3f1c-4c52-8f50-b25396bd73ef_text_to_video_0.mp4', 'https://cdn.qikongjian.com/videos/1750162598_db834807-3f1c-4c52-8f50-b25396bd73ef_text_to_video_0.mp4' ], description: 'Animation sequence', transition: 'Selected Automatically by Preset', volume: 55, mediaNumber: 3, }, { id: '2-4', type: 'talking-head', duration: 8, shotVideo: 'https://cdn.qikongjian.com/videos/1750162598_db834807-3f1c-4c52-8f50-b25396bd73ef_text_to_video_0.mp4', generatedVideos:[ 'https://cdn.qikongjian.com/videos/1750162598_db834807-3f1c-4c52-8f50-b25396bd73ef_text_to_video_0.mp4', 'https://cdn.qikongjian.com/videos/1750162598_db834807-3f1c-4c52-8f50-b25396bd73ef_text_to_video_0.mp4', 'https://cdn.qikongjian.com/videos/1750162598_db834807-3f1c-4c52-8f50-b25396bd73ef_text_to_video_0.mp4' ], description: 'Character dialogue', transition: 'Selected Automatically by Preset', volume: 55, mediaNumber: 4, } ] } ]; export function GenerateShotsStep({ onNext, onPrevious }: GenerateShotsStepProps) { const [selectedChapter, setSelectedChapter] = useState(1); const [selectedShot, setSelectedShot] = useState('1-1'); const [chapters, setChapters] = useState(mockChapters); const videoRef = useRef(null); const [isPlaying, setIsPlaying] = useState(false); const [isCheckVideoOpen, setIsCheckVideoOpen] = useState(false); const [isReplaceMediaOpen, setIsReplaceMediaOpen] = useState(false); const [isMediaPropertyOpen, setIsMediaPropertyOpen] = useState(false); const [activeTabReplaceMedia, setActiveTabReplaceMedia] = useState('uploaded'); const [activeTabMediaProperty, setActiveTabMediaProperty] = useState('media'); const currentChapter = chapters.find(ch => ch.id === selectedChapter); const currentShot = currentChapter?.shots.find(shot => shot.id === selectedShot); const handlePlayPause = () => { if (videoRef.current) { if (videoRef.current.paused) { videoRef.current.play(); setIsPlaying(true); } else { videoRef.current.pause(); setIsPlaying(false); } } }; const handleTransitionChange = (shotId: string, transition: string) => { setChapters(chapters.map(ch => ch.id === selectedChapter ? { ...ch, shots: ch.shots.map(shot => shot.id === shotId ? { ...shot, transition } : shot ) } : ch )); }; const handleVolumeChange = (shotId: string, volume: number[]) => { setChapters(chapters.map(ch => ch.id === selectedChapter ? { ...ch, shots: ch.shots.map(shot => shot.id === shotId ? { ...shot, volume: volume[0] } : shot ) } : ch )); }; const handleDeleteShot = (shotId: string) => { setChapters(chapters.map(ch => ch.id === selectedChapter ? { ...ch, shots: ch.shots.filter(shot => shot.id !== shotId) } : ch )); }; const handleOpenReplaceMedia = (tab: string) => { setActiveTabReplaceMedia(tab); setIsReplaceMediaOpen(true); }; const handleOpenMediaProperty = (tab: string) => { setActiveTabMediaProperty(tab); setIsMediaPropertyOpen(true); }; return (
{/* 弹窗组件 */} {/* Timeline Header */}
setIsCheckVideoOpen(true)} />
但我决心要改变它。我的翅膀展开,在千星的光芒中翱翔,降临人生。和星光铸就,我是凤青楗,这就是我重生的故事。不......?
{/* Main Content */}
{/* Replace Media Section */} {currentShot && (

Replace media {currentShot.mediaNumber} with:

)} {/* Media Info */} {currentShot && (

Media info:

} text={`Chapter 1 / media ${currentShot.mediaNumber} / Generated media`} popoverContent={
Delete media
Delete and add blank media
} /> } text="00m : 08s : 070ms / 00m : 08s : 070ms" popoverContent={
Trim
} /> } text={`Transition: ${currentShot.transition}`} popoverContent={
Transition
} />
Audio volume: {currentShot.volume}% volume
{/* Media Properties */}

handleOpenMediaProperty('media')}>Media properties

{currentShot && (
)}
)}
{/* Action Buttons */}
); }