"use client"; import { useState, useEffect, useRef, useCallback } from 'react'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { ArrowLeft, ChevronDown, ChevronUp, Video, ListOrdered, Play, Loader2, Pause, MoreHorizontal, Edit2, Check, X, RefreshCw, Lightbulb, Package, Crown, ArrowUp, Search, Filter, Grid, Grid3X3, Calendar, Clock, Eye, Heart, Share2 } from 'lucide-react'; import { useRouter, useSearchParams } from 'next/navigation'; import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger, SheetClose } from "@/components/ui/sheet"; import { Input } from "@/components/ui/input"; import './style/create-to-video2.css'; import { Dropdown, Menu } from 'antd'; import type { MenuProps } from 'antd'; import dynamic from 'next/dynamic'; import { ModeEnum, ResolutionEnum } from "@/app/model/enums"; import { createScriptEpisodeNew, getScriptEpisodeListNew, CreateScriptEpisodeRequest } from "@/api/script_episode"; import { getUploadToken, uploadToQiniu } from "@/api/common"; import { EmptyStateAnimation } from '@/components/common/EmptyStateAnimation2'; import { ScriptEditDialog } from '@/components/script-edit-dialog'; const ideaText = 'a cute capybara with an orange on its head, staring into the distance and walking forward'; export function CreateToVideo2() { const router = useRouter(); const searchParams = useSearchParams(); const projectId = searchParams.get('projectId') ? parseInt(searchParams.get('projectId')!) : 0; const [isClient, setIsClient] = useState(false); const [isExpanded, setIsExpanded] = useState(false); const [videoUrl, setVideoUrl] = useState(''); const [isUploading, setIsUploading] = useState(false); const containerRef = useRef(null); const [activeTab, setActiveTab] = useState('script'); const [isFocus, setIsFocus] = useState(false); const [selectedMode, setSelectedMode] = useState(ModeEnum.AUTOMATIC); const [selectedResolution, setSelectedResolution] = useState(ResolutionEnum.HD_720P); const [script, setInputText] = useState(''); const editorRef = useRef(null); const [runTour, setRunTour] = useState(true); const [episodeId, setEpisodeId] = useState(0); const [isCreating, setIsCreating] = useState(false); const [generatedVideoList, setGeneratedVideoList] = useState([]); const [projectName, setProjectName] = useState('默认名称'); const [episodeList, setEpisodeList] = useState([]); const [currentPage, setCurrentPage] = useState(1); const [totalPage, setTotalPage] = useState(1); const [limit, setLimit] = useState(12); const [isLoading, setIsLoading] = useState(false); const [hasMore, setHasMore] = useState(true); const [searchQuery, setSearchQuery] = useState(''); const [filterStatus, setFilterStatus] = useState('all'); const [sortBy, setSortBy] = useState('created_at'); const [isLoadingMore, setIsLoadingMore] = useState(false); const scrollContainerRef = useRef(null); const [isSmartAssistantExpanded, setIsSmartAssistantExpanded] = useState(false); const [userId, setUserId] = useState(0); const [isComposing, setIsComposing] = useState(false); const [isScriptEditDialogOpen, setIsScriptEditDialogOpen] = useState(false); // 在客户端挂载后读取localStorage useEffect(() => { if (typeof window !== 'undefined') { const currentUser = JSON.parse(localStorage.getItem('currentUser') || '{}'); console.log('currentUser', currentUser); setUserId(currentUser.id); const savedProjectName = localStorage.getItem('projectName'); if (savedProjectName) { setProjectName(savedProjectName); } getEpisodeList(currentUser.id); } }, []); // 获取剧集列表 const getEpisodeList = async (userId: number) => { if (isLoading || isLoadingMore) return; console.log('getEpisodeList', userId); setIsLoading(true); try { const params = { user_id: String(userId), }; const episodeListResponse = await getScriptEpisodeListNew(params); console.log('episodeListResponse', episodeListResponse); if (episodeListResponse.code === 0) { setEpisodeList(episodeListResponse.data.movie_projects); // 每一项 有 // final_video_url: "", // 生成的视频地址 // last_message: "", // name: "After the Flood", // 剧集名称 // project_id: "9c34fcc4-c8d8-44fc-879e-9bd56f608c76", // 剧集ID // status: "INIT", // 剧集状态 INIT 初始化 // step: "INIT" // 剧集步骤 INIT 初始化 } } catch (error) { console.error('Failed to fetch episode list:', error); } finally { setIsLoading(false); setIsLoadingMore(false); } }; const handleUploadVideo = async () => { console.log('upload video'); // 打开文件选择器 const input = document.createElement('input'); input.type = 'file'; input.accept = 'video/*'; input.onchange = async (e) => { const file = (e.target as HTMLInputElement).files?.[0]; if (file) { try { setIsUploading(true); // 获取上传token const { token } = await getUploadToken(); // 上传到七牛云 const videoUrl = await uploadToQiniu(file, token); // 上传成功,设置视频URL setVideoUrl(videoUrl); console.log('视频上传成功:', videoUrl); } catch (error) { console.error('上传错误:', error); alert('上传失败,请稍后重试'); } finally { setIsUploading(false); } } } input.click(); } const handleCreateVideo = async () => { setIsScriptEditDialogOpen(true); return; setIsCreating(true); // 创建剧集数据 let episodeData: any = { user_id: String(userId), script: script, mode: selectedMode, resolution: selectedResolution }; // 调用创建剧集API const episodeResponse = await createScriptEpisodeNew(episodeData); console.log('episodeResponse', episodeResponse); if (episodeResponse.code !== 0) { console.error(`创建剧集失败: ${episodeResponse.message}`); alert(`创建剧集失败: ${episodeResponse.message}`); return; } let episodeId = episodeResponse.data.project_id; // let episodeId = '9c34fcc4-c8d8-44fc-879e-9bd56f608c76'; router.push(`/create/work-flow?episodeId=${episodeId}`); setIsCreating(false); } // 下拉菜单项配置 const modeItems: MenuProps['items'] = [ { type: 'group', label: (
Mode
), children: [ { key: ModeEnum.AUTOMATIC, label: (
Auto
Automatically execute the workflow, you can't edit the workflow before it's finished.
), }, { key: ModeEnum.MANUAL, label: (
Manual
Manually control the workflow, you can control the workflow everywhere.
), }, ], }, ]; // 分辨率选项配置 const resolutionItems: MenuProps['items'] = [ { type: 'group', label: (
Resolution
), children: [ { key: ResolutionEnum.HD_720P, label: (
720P
), }, { key: ResolutionEnum.FULL_HD_1080P, label: (
1080P
), }, { key: ResolutionEnum.UHD_2K, label: (
2K
), }, { key: ResolutionEnum.UHD_4K, label: (
4K
), }, ], }, ]; // 处理模式选择 const handleModeSelect: MenuProps['onClick'] = ({ key }) => { setSelectedMode(key as ModeEnum); }; // 处理分辨率选择 const handleResolutionSelect: MenuProps['onClick'] = ({ key }) => { setSelectedResolution(key as ResolutionEnum); }; const handleStartCreating = () => { setActiveTab('script'); setInputText(ideaText); } // 处理编辑器聚焦 const handleEditorFocus = () => { setIsFocus(true); if (editorRef.current) { const range = document.createRange(); const selection = window.getSelection(); const textNode = Array.from(editorRef.current.childNodes).find( node => node.nodeType === Node.TEXT_NODE ); if (!textNode) { const newTextNode = document.createTextNode(script || ''); editorRef.current.appendChild(newTextNode); range.setStart(newTextNode, (script || '').length); range.setEnd(newTextNode, (script || '').length); } else { range.setStart(textNode, textNode.textContent?.length || 0); range.setEnd(textNode, textNode.textContent?.length || 0); } selection?.removeAllRanges(); selection?.addRange(range); } }; const handleEditorChange = (e: React.FormEvent) => { const newText = e.currentTarget.textContent || ''; // 如果正在输入中文,只更新内部文本,不更新状态 if (isComposing) { return; } // 更新状态 setInputText(newText); // 保存当前选区位置 const selection = window.getSelection(); if (selection && selection.rangeCount > 0) { const range = selection.getRangeAt(0); const currentPosition = range.startOffset; // 使用 requestAnimationFrame 确保在下一帧恢复光标位置 requestAnimationFrame(() => { if (editorRef.current) { // 找到或创建文本节点 let textNode = Array.from(editorRef.current.childNodes).find( node => node.nodeType === Node.TEXT_NODE ) as Text; if (!textNode) { textNode = document.createTextNode(newText); editorRef.current.appendChild(textNode); } // 计算正确的光标位置 const finalPosition = Math.min(currentPosition, textNode.length); // 设置新的选区 const newRange = document.createRange(); newRange.setStart(textNode, finalPosition); newRange.setEnd(textNode, finalPosition); selection.removeAllRanges(); selection.addRange(newRange); } }); } }; // 处理中文输入开始 const handleCompositionStart = () => { setIsComposing(true); }; // 处理中文输入结束 const handleCompositionEnd = (e: React.CompositionEvent) => { setIsComposing(false); // 在输入完成后更新内容 const newText = e.currentTarget.textContent || ''; setInputText(newText); // 保存并恢复光标位置 const selection = window.getSelection(); if (selection && selection.rangeCount > 0) { const range = selection.getRangeAt(0); const currentPosition = range.startOffset; requestAnimationFrame(() => { if (editorRef.current) { let textNode = Array.from(editorRef.current.childNodes).find( node => node.nodeType === Node.TEXT_NODE ) as Text; if (!textNode) { textNode = document.createTextNode(newText); editorRef.current.appendChild(textNode); } // 计算正确的光标位置 const finalPosition = Math.min(currentPosition, textNode.length); // 设置新的选区 const newRange = document.createRange(); newRange.setStart(textNode, finalPosition); newRange.setEnd(textNode, finalPosition); selection.removeAllRanges(); selection.addRange(newRange); } }); } }; // 检查是否需要显示引导 useEffect(() => { if (typeof window !== 'undefined') { const hasCompletedTour = localStorage.getItem('hasCompletedTour'); if (hasCompletedTour) { setRunTour(false); } } }, []); useEffect(() => { setIsClient(true); }, []); // 渲染剧集卡片 const renderEpisodeCard = (episode: any) => { return (
router.push(`/create/work-flow?episodeId=${episode.project_id}`)} > {/* 视频缩略图 */}
{episode.final_video_url ? (
{/* 内容区域 */}

{episode.name || episode.title || 'Unnamed episode'}

{/* 元数据 */}
{new Date(episode.created_at).toLocaleDateString()}
{new Date(episode.updated_at).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}
{/* 操作按钮 */}
); }; return ( <>
{/* 优化后的主要内容区域 */}
{isLoading && episodeList.length === 0 ? ( /* 优化的加载状态 */
{[...Array(10)].map((_, index) => (
))}
) : episodeList.length > 0 ? ( /* 优化的剧集网格 */
{episodeList.map(renderEpisodeCard)}
{/* 加载更多指示器 */} {isLoadingMore && (
Loading more episodes...
)} {/* 到底提示 */} {!hasMore && episodeList.length > 0 && (

All episodes loaded

)}
) : ( <> )}
{/* 创建工具栏 */}
{isExpanded ? (
setIsExpanded(false)}> Click to action
) : (
setIsExpanded(true)}>
)}
setActiveTab('script')}> script
setActiveTab('clone')}> clone
{activeTab === 'clone' && (
{isUploading ? ( ) : (
{isUploading ? '上传中...' : 'Add Video'}
{videoUrl && (
)}
)} {activeTab === 'script' && (
setIsFocus(false)} onInput={handleEditorChange} onCompositionStart={handleCompositionStart} onCompositionEnd={handleCompositionEnd} suppressContentEditableWarning > {script}
Describe the content you want to action. Get an setInputText(ideaText)} > idea
)}
{selectedMode === ModeEnum.AUTOMATIC ? 'Auto' : 'Manual'}
{isCreating ? ( <> Actioning... ) : ( <> Action )}
{episodeList.length === 0 && !isLoading && (
)} {isScriptEditDialogOpen && ( setIsScriptEditDialogOpen(false)} /> )} ); }