diff --git a/app/create/page.tsx b/app/create/page.tsx index ade8853..dfb0e15 100644 --- a/app/create/page.tsx +++ b/app/create/page.tsx @@ -1,5 +1,7 @@ -import { redirect } from 'next/navigation'; +// import { redirect } from 'next/navigation'; +import { CreateToVideo } from '@/components/pages/create-to-video'; export default function CreatePage() { - redirect('/create/video-to-video'); + // redirect('/create/video-to-video'); + return ; } \ No newline at end of file diff --git a/app/globals.css b/app/globals.css index 3fe7533..9bf31fc 100644 --- a/app/globals.css +++ b/app/globals.css @@ -76,15 +76,16 @@ body { height: 100vh; overflow: hidden; - background: radial-gradient(circle at 50% 0, - rgba(255, 0, 0, 0.3), + /* background: radial-gradient(circle at 50% 0, + rgba(255, 0, 0, 0.4), rgba(255, 0, 0, 0) 70.71%), radial-gradient(circle at 6.7% 75%, - rgba(0, 0, 255, 0.3), + rgba(0, 0, 255, 0.4), rgba(0, 0, 255, 0) 70.71%), radial-gradient(circle at 93.3% 75%, - rgba(0, 0, 255, 0.3), - rgba(0, 0, 255, 0) 70.71%) beige !important; + rgba(0, 0, 255, 0.4), + rgba(0, 0, 255, 0) 70.71%) beige !important; */ + background: #6370b0 !important; } .hide-scrollbar::-webkit-scrollbar { diff --git a/components/layout/dashboard-layout.tsx b/components/layout/dashboard-layout.tsx index ddb9e57..24edce1 100644 --- a/components/layout/dashboard-layout.tsx +++ b/components/layout/dashboard-layout.tsx @@ -13,13 +13,18 @@ export function DashboardLayout({ children }: DashboardLayoutProps) { return (
- -
+ {/* */} + {/*
*/} + {/*
{children}
-
+
*/} + +
+ {children} +
); } \ No newline at end of file diff --git a/components/layout/top-bar.tsx b/components/layout/top-bar.tsx index 022ef6d..4a39bf7 100644 --- a/components/layout/top-bar.tsx +++ b/components/layout/top-bar.tsx @@ -17,14 +17,17 @@ import { LogOut, Bell, } from 'lucide-react'; +import { useRouter } from 'next/navigation'; export function TopBar({ collapsed }: { collapsed: boolean }) { const { theme, setTheme } = useTheme(); + const router = useRouter(); return ( -
+ //
+
-
+
router.push('/')}>

Movie Flow

@@ -35,14 +38,14 @@ export function TopBar({ collapsed }: { collapsed: boolean }) { {/* Theme Toggle */} - + */} {/* User Menu */} diff --git a/components/pages/create-to-video.tsx b/components/pages/create-to-video.tsx new file mode 100644 index 0000000..e5dce68 --- /dev/null +++ b/components/pages/create-to-video.tsx @@ -0,0 +1,1031 @@ +"use client"; + +import { useState, useEffect, useRef } from 'react'; +import { Card } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { ArrowLeft, ChevronDown, ChevronUp, Video, ListOrdered, Play, Loader2, Pause, MoreHorizontal, Edit2, Check, X, RefreshCw } from 'lucide-react'; +import { useRouter } from 'next/navigation'; +import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger, SheetClose } from "@/components/ui/sheet"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import './style/create-to-video.css'; +import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/components/ui/resizable"; +import { Skeleton } from "@/components/ui/skeleton"; + +// 添加自定义滚动条样式 +const scrollbarStyles = ` + .custom-scrollbar::-webkit-scrollbar { + width: 4px; + } + .custom-scrollbar::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.05); + border-radius: 2px; + } + .custom-scrollbar::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.1); + border-radius: 2px; + } + .custom-scrollbar::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.2); + } +`; + +interface SceneVideo { + id: number; + video_url: string; + script: any; +} + +export function CreateToVideo() { + const router = useRouter(); + const [isUploading, setIsUploading] = useState(false); + const [isExpanded, setIsExpanded] = useState(false); + const [videoUrl, setVideoUrl] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [loadingText, setLoadingText] = useState('Generating...'); + const [isPaused, setIsPaused] = useState(false); + const [showMoreSettings, setShowMoreSettings] = useState(false); + const [generateObj, setGenerateObj] = useState({ + scripts: null, + frame_urls: null, + video_info: null, + scene_videos: null, + cut_video_url: null, + audio_video_url: null, + final_video_url: null + }); + const [showAllFrames, setShowAllFrames] = useState(false); + const containerRef = useRef(null); + const [showScrollNav, setShowScrollNav] = useState(false); + const [selectedVideoIndex, setSelectedVideoIndex] = useState(null); + const videosContainerRef = useRef(null); + const scriptsContainerRef = useRef(null); + const [activeTab, setActiveTab] = useState('clone'); + const [editingField, setEditingField] = useState<{ + type: 'shot' | 'frame' | 'atmosphere' | null; + value: string; + }>({ type: null, value: '' }); + const [alternativeVideos, setAlternativeVideos] = useState<{ [key: number]: string[] }>({ + 0: [ + 'https://cdn.qikongjian.com/videos/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp4', + 'https://cdn.qikongjian.com/videos/1750389908_37d4fffa-8516-43a3-a423-fc0274f40e8a_text_to_video_0.mp4', + ], + 1: [ + 'https://cdn.qikongjian.com/videos/1750384661_d8e30b79-828e-48cd-9025-ab62a996717c_text_to_video_0.mp4', + 'https://cdn.qikongjian.com/videos/1750320040_4b47996e-7c70-490e-8433-80c7df990fdd_text_to_video_0.mp4', + ] + }); + const [currentVideoIndex, setCurrentVideoIndex] = useState<{ [key: number]: number }>({}); + const [volume, setVolume] = useState<{ [key: number]: number }>({}); + const [transition, setTransition] = useState<{ [key: number]: string }>({}); + const [buttonPosition, setButtonPosition] = useState({ x: window.innerWidth - 100, y: window.innerHeight / 2 }); + const [isDragging, setIsDragging] = useState(false); + const [dragStart, setDragStart] = useState({ x: 0, y: 0 }); + const buttonRef = useRef(null); + + // 计算每行可以显示的图片数量(基于图片高度100px和容器宽度) + const imagesPerRow = Math.floor(1080 / (100 * 16/9 + 8)); // 假设图片宽高比16:9,间距8px + // 计算三行可以显示的最大图片数量 + const maxVisibleImages = imagesPerRow * 3; + + // 生成分镜视频后默认选中第一个 + useEffect(() => { + if (generateObj.scene_videos?.length > 0 && selectedVideoIndex === null) { + setSelectedVideoIndex(0); + } + }, [generateObj.scene_videos]); + + // 处理暂停/继续 + const handlePauseResume = () => { + setIsPaused(!isPaused); + // TODO: 实现具体的暂停/继续逻辑 + + }; + + const handleUploadVideo = () => { + console.log('upload video'); + // 打开文件选择器 + const input = document.createElement('input'); + input.type = 'file'; + input.accept = 'video/*'; + input.onchange = (e) => { + const file = (e.target as HTMLInputElement).files?.[0]; + if (file) { + setVideoUrl(URL.createObjectURL(file)); + } + } + input.click(); + } + + const generateSences = async () => { + try { + generateObj.scene_videos = []; + const videoUrls = [ + 'https://cdn.qikongjian.com/videos/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp4', + 'https://cdn.qikongjian.com/videos/1750389908_37d4fffa-8516-43a3-a423-fc0274f40e8a_text_to_video_0.mp4', + 'https://cdn.qikongjian.com/videos/1750384661_d8e30b79-828e-48cd-9025-ab62a996717c_text_to_video_0.mp4', + 'https://cdn.qikongjian.com/videos/1750320040_4b47996e-7c70-490e-8433-80c7df990fdd_text_to_video_0.mp4', + 'https://cdn.qikongjian.com/videos/1750303377_8c3c4ca6-c4ea-4376-8583-de3afa5681d8_text_to_video_0.mp4', + 'https://cdn.qikongjian.com/videos/1750162598_db834807-3f1c-4c52-8f50-b25396bd73ef_text_to_video_0.mp4' + ]; + setGenerateObj({...generateObj, scene_videos: []}); + + // 使用 Promise.all 和 Array.map 来处理异步操作 + const promises = generateObj.scripts.map((element: any, index: number) => { + return new Promise((resolveVideo) => { + setTimeout(() => { + generateObj.scene_videos.push({ + id: index, + video_url: videoUrls[index], + script: element + }); + setGenerateObj({...generateObj}); + setLoadingText(`生成第 ${index + 1} 个分镜视频...`); + resolveVideo(); + }, index * 3000); // 每个视频间隔3秒 + }); + }); + + // 等待所有视频生成完成 + await Promise.all(promises); + } catch (error) { + console.error('生成分镜视频失败:', error); + throw error; + } + } + + const handleCreateVideo = async () => { + try { + // 清空所有数据 + setGenerateObj({ + scripts: null, + frame_urls: null, + video_info: null, + scene_videos: null, + cut_video_url: null, + audio_video_url: null, + final_video_url: null + }); + console.log('create video'); + setIsLoading(true); + setIsExpanded(true); + + // 提取帧 + await new Promise(resolve => setTimeout(resolve, 3000)); + setLoadingText('提取帧...'); + + // 生成帧 + await new Promise(resolve => setTimeout(resolve, 3000)); + generateObj.frame_urls = [ + "https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000000.jpg/1750507510_tmphfb431oc_000000.jpg", + "https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000001.jpg/1750507511_tmphfb431oc_000001.jpg", + "https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000002.jpg/1750507511_tmphfb431oc_000002.jpg", + "https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000003.jpg/1750507511_tmphfb431oc_000003.jpg", + "https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000004.jpg/1750507512_tmphfb431oc_000004.jpg", + "https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000005.jpg/1750507512_tmphfb431oc_000005.jpg", + "https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000006.jpg/1750507513_tmphfb431oc_000006.jpg", + "https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000007.jpg/1750507514_tmphfb431oc_000007.jpg", + "https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000008.jpg/1750507515_tmphfb431oc_000008.jpg", + "https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000009.jpg/1750507515_tmphfb431oc_000009.jpg", + "https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000010.jpg/1750507516_tmphfb431oc_000010.jpg", + "https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000011.jpg/1750507516_tmphfb431oc_000011.jpg", + "https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000012.jpg/1750507516_tmphfb431oc_000012.jpg", + "https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000013.jpg/1750507517_tmphfb431oc_000013.jpg", + "https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000014.jpg/1750507517_tmphfb431oc_000014.jpg", + "https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000015.jpg/1750507517_tmphfb431oc_000015.jpg", + "https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000016.jpg/1750507517_tmphfb431oc_000016.jpg", + "https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000017.jpg/1750507517_tmphfb431oc_000017.jpg", + "https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000018.jpg/1750507518_tmphfb431oc_000018.jpg", + "https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000019.jpg/1750507520_tmphfb431oc_000019.jpg", + "https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000020.jpg/1750507521_tmphfb431oc_000020.jpg", + "https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000021.jpg/1750507523_tmphfb431oc_000021.jpg", + "https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000022.jpg/1750507523_tmphfb431oc_000022.jpg", + "https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000023.jpg/1750507524_tmphfb431oc_000023.jpg", + ]; + setGenerateObj({...generateObj}); + setLoadingText('分析视频...'); + + // 生成视频信息 + await new Promise(resolve => setTimeout(resolve, 6000)); + generateObj.video_info = { + roles: [ + { + name: '雪 (YUKI)', + core_identity: '一位接近二十岁或二十出头的年轻女性,东亚裔,拥有深色长发和刘海,五官柔和且富有表现力。', + avatar: 'https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000000.jpg/1750507510_tmphfb431oc_000000.jpg', + },{ + name: '春 (HARU)', + core_identity: '一位接近二十岁或二十出头的年轻男性,东亚裔,拥有深色、发型整洁的中长发和深思的气质。', + avatar: 'https://smart-video-ai.oss-cn-beijing.aliyuncs.com/frames/d877fa43-4856-4acb-9a3b-627c28275343/frame_000000.jpg/1750507510_tmphfb431oc_000000.jpg', + } + ], + sence: '叙事在两个不同的时间段展开。现在时空设定在一个安静的乡下小镇,冬季时被大雪覆盖。记忆则设定在春夏两季,一个日本高中的校园内外,天气温暖而晴朗。', + style: '电影感,照片般逼真,带有柔和、梦幻般的质感。其美学风格让人联想到日本的浪漫剧情片。现在时空的场景使用冷色、蓝色调的调色板,而记忆序列则沐浴在温暖的黄金时刻光晕中。大量使用浅景深、微妙的镜头光晕,以及平滑、富有情感的节奏。8K分辨率。' + }; + setGenerateObj({...generateObj}); + setLoadingText('提取分镜脚本...'); + + // 生成分镜脚本 + await new Promise(resolve => setTimeout(resolve, 6000)); + generateObj.scripts = [ + { + shot: '面部特写,拉远至广角镜头。', + frame: '序列以雪(YUKI)面部的特写开场,她闭着眼睛躺在一片纯净的雪地里。柔和的雪花轻轻飘落在她的黑发和苍白的皮肤上。摄像机缓慢拉远,形成一幅令人惊叹的广角画面,揭示出在黄昏时分,她是在一片广阔、寂静、白雪覆盖的景观中的一个渺小、孤独的身影。', + atmosphere: '忧郁、宁静、寂静且寒冷。' + }, { + shot: '切至室内中景,随后是一个透过窗户的主观视角镜头。', + frame: '我们切到雪(YUKI)在一个舒适、光线温暖的卧室里醒来。她穿着一件舒适的毛衣,从床上下来,走向一扇窗户。她的呼吸在冰冷的玻璃上凝成雾气。她的主观视角镜头揭示了外面的雪景,远处有一座红色的房子,以及一封信被放入邮箱的记忆,从而触发了一段闪回。', + atmosphere: '怀旧、温暖、内省。' + }, { + shot: '跟踪镜头,随后是一系列切出镜头和特写。', + frame: '记忆开始。一个跟踪镜头跟随着一群学生,包括雪(YUKI),他们在飘落的樱花花瓣构成的华盖下走路上学。场景切到一个阳光普照的教室。雪(YUKI)穿着校服,坐在她的课桌前,害羞地瞥了一眼坐在前几排的春(HARU)。他仿佛感觉到她的凝视,巧妙地转过头来。', + atmosphere: '充满青春气息、怀旧、温暖,带有一种萌芽的、未言明的浪漫感。' + }, { + shot: '静态中景,切到另一个中景,营造出共享空间的感觉。', + frame: '记忆转移到学校图书馆,充满了金色的光束。一个中景镜头显示春(HARU)靠在一个书架上,全神贯注地读一本书。然后摄像机切到雪(YUKI),她坐在附近的一张桌子旁,专注于画架上的一幅小画,当她感觉到他的存在时,嘴唇上泛起一丝淡淡的、秘密的微笑。', + atmosphere: '平和、亲密、书卷气、温暖。' + }, { + shot: '雪(YUKI)的中景,过渡到通过相机镜头的特写主观视角。', + frame: '在一个阳光明媚的运动日,雪(YUKI)站在运动场边缘,拿着一台老式相机。她举起相机,镜头推进到她透过取景器看的眼睛的特写。我们切到她的主观视角:除了站在运动场上、表情专注而坚定的春(HARU)之外,整个世界都是失焦的。', + atmosphere: '充满活力、专注,一种投入的观察和遥远的钦佩感。' + }, + ]; + setGenerateObj({...generateObj}); // 使用展开运算符创建新对象,确保触发更新 + setLoadingText('生成分镜视频...'); + + // 生成分镜视频 + await new Promise(resolve => setTimeout(resolve, 2000)); + await generateSences(); + setLoadingText('分镜剪辑...'); + + // 生成剪辑后的视频 + await new Promise(resolve => setTimeout(resolve, 6000)); + generateObj.cut_video_url = 'https://cdn.qikongjian.com/videos/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp4'; + setGenerateObj({...generateObj}); + setLoadingText('口型同步...'); + + // 口型同步后生成视频 + await new Promise(resolve => setTimeout(resolve, 6000)); + generateObj.audio_video_url = 'https://cdn.qikongjian.com/videos/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp4'; + setGenerateObj({...generateObj}); + setLoadingText('一致化处理...'); + + // 最终完成 + await new Promise(resolve => setTimeout(resolve, 6000)); + generateObj.final_video_url = 'https://cdn.qikongjian.com/videos/1750385931_99a8fb42-af89-4ae9-841a-a49869f026bd_text_to_video_0.mp4'; + setGenerateObj({...generateObj}); + setLoadingText('完成'); + setIsLoading(false); + } catch (error) { + console.error('视频生成过程出错:', error); + setLoadingText('生成失败,请重试'); + setIsLoading(false); + } + } + + // 处理视频选中 + const handleVideoSelect = (index: number) => { + setSelectedVideoIndex(index); + // 滚动脚本到对应位置 + const scriptElement = document.getElementById(`script-${index}`); + scriptElement?.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); + }; + + // 处理脚本选中 + const handleScriptSelect = (index: number) => { + setSelectedVideoIndex(index); + // 滚动视频到对应位置 + const videoElement = document.getElementById(`video-${index}`); + videoElement?.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); + }; + + // 处理脚本编辑 + const handleEditScript = (type: 'shot' | 'frame' | 'atmosphere', value: string) => { + setEditingField({ type, value }); + }; + + // 处理脚本点击 + const handleScriptClick = (type: 'shot' | 'frame' | 'atmosphere', value: string) => { + if (editingField.type !== type) { + handleEditScript(type, value); + } + }; + + // 自动保存脚本编辑 + useEffect(() => { + if (editingField.type && editingField.value && selectedVideoIndex !== null) { + const timeoutId = setTimeout(() => { + const updatedScripts = [...generateObj.scripts]; + const fieldType = editingField.type as keyof typeof updatedScripts[number]; + updatedScripts[selectedVideoIndex] = { + ...updatedScripts[selectedVideoIndex], + [fieldType]: editingField.value + }; + setGenerateObj({ + ...generateObj, + scripts: updatedScripts + }); + }, 500); // 500ms 防抖 + + return () => clearTimeout(timeoutId); + } + }, [editingField.value, editingField.type, selectedVideoIndex, generateObj]); + + // 刷新分镜脚本 + const handleRefreshScript = () => { + if (selectedVideoIndex === null) return; + // TODO: 实现刷新脚本的逻辑 + console.log('Refresh script for scene', selectedVideoIndex + 1); + }; + + // 处理删除分镜视频 + const handleDeleteScene = (index: number) => { + if (generateObj.scene_videos) { + const newSceneVideos = generateObj.scene_videos.filter((_: SceneVideo, i: number) => i !== index); + setGenerateObj({ + ...generateObj, + scene_videos: newSceneVideos + }); + setSelectedVideoIndex(null); + } + }; + + // 处理重新生成分镜视频 + const handleRegenerateScene = async (index: number) => { + // TODO: 实现重新生成逻辑 + console.log('Regenerate scene', index); + }; + + // 处理切换其他生成的视频 + const handleSwitchVideo = (sceneIndex: number, videoIndex: number) => { + if (alternativeVideos[sceneIndex]?.[videoIndex]) { + const newSceneVideos = [...generateObj.scene_videos]; + newSceneVideos[sceneIndex] = { + ...newSceneVideos[sceneIndex], + video_url: alternativeVideos[sceneIndex][videoIndex] + }; + setGenerateObj({ + ...generateObj, + scene_videos: newSceneVideos + }); + setCurrentVideoIndex({ + ...currentVideoIndex, + [sceneIndex]: videoIndex + }); + } + }; + + // 处理音量调节 + const handleVolumeChange = (index: number, value: number) => { + setVolume({ + ...volume, + [index]: value + }); + // TODO: 实现音量调节逻辑 + }; + + // 处理转场设置 + const handleTransitionChange = (index: number, value: string) => { + setTransition({ + ...transition, + [index]: value + }); + // TODO: 实现转场效果逻辑 + }; + + // 处理拖动开始 + const handleDragStart = (e: React.MouseEvent) => { + setIsDragging(true); + setDragStart({ + x: e.clientX - buttonPosition.x, + y: e.clientY - buttonPosition.y + }); + }; + + // 处理拖动 + const handleDrag = (e: MouseEvent) => { + if (isDragging && buttonRef.current) { + const newX = Math.min( + Math.max(0, e.clientX - dragStart.x), + window.innerWidth - buttonRef.current.offsetWidth + ); + const newY = Math.min( + Math.max(0, e.clientY - dragStart.y), + window.innerHeight - buttonRef.current.offsetHeight + ); + + setButtonPosition({ x: newX, y: newY }); + } + }; + + // 处理拖动结束 + const handleDragEnd = () => { + setIsDragging(false); + }; + + // 添加拖动事件监听 + useEffect(() => { + if (isDragging) { + window.addEventListener('mousemove', handleDrag); + window.addEventListener('mouseup', handleDragEnd); + } + + return () => { + window.removeEventListener('mousemove', handleDrag); + window.removeEventListener('mouseup', handleDragEnd); + }; + }, [isDragging, dragStart]); + + return ( +
+ {/* 展示创作详细过程 */} + {generateObj && ( +
+ + {/* 第一行 - 占2/3高度 */} + + + {/* 第一列 - 角色档案卡 */} + +
+
+

角色档案

+
+
+ {generateObj.video_info ? ( +
+ {generateObj.video_info.roles.map((role: any, index: number) => ( +
+
+
+ {role.name} +
+
+
+
{role.name}
+
{role.core_identity}
+
+
+ ))} +
+ ) : ( +
+ {[1, 2].map((i) => ( +
+
+ +
+ + + +
+
+
+ ))} +
+ )} +
+
+
+ + + {/* 第二列 - 视频预览和加载状态 */} + +
+
+

视频预览

+
+
+
+ {isLoading ? ( +
+
+
+
+
+
+
+
+ {loadingText} + + . + . + . + +
+
+ ) : generateObj.final_video_url ? ( +
+
+
+
+ + + {/* 第三列 - 概要信息 */} + +
+
+

概要信息

+
+
+ {generateObj.video_info ? ( +
+
+ 场景 +

{generateObj.video_info.sence}

+
+
+ 风格 +

{generateObj.video_info.style}

+
+
+ ) : ( +
+
+ + +
+
+ + +
+
+ )} +
+
+
+
+
+ + + {/* 第二行 - 占1/3高度 */} + + + {/* 第一列 - 分镜脚本 */} + +
+
+
+

分镜脚本

+ {selectedVideoIndex !== null && ( + + Scene {selectedVideoIndex + 1} + + )} +
+ +
+
+ {generateObj.scripts && selectedVideoIndex !== null ? ( +
+
+
+ 镜头 +
+ {editingField.type === 'shot' ? ( +