"use client"; import { useState, useEffect, useRef } from "react"; import { ChevronDown, ChevronUp, Video, Loader2, Lightbulb, Package, Crown, Clapperboard, Globe, Clock, Trash2, LayoutTemplate, ImagePlay, Sparkles, Settings, } from "lucide-react"; import { Dropdown, Modal, Tooltip, Upload, Popconfirm, Image, Popover, } from "antd"; import { UploadOutlined } from "@ant-design/icons"; import { StoryTemplateEntity } from "@/app/service/domain/Entities"; import { useImageStoryServiceHook } from "@/app/service/Interaction/ImageStoryService"; import TemplateCard from "./templateCard"; import { AudioRecorder } from "./AudioRecorder"; import { useTemplateStoryServiceHook } from "@/app/service/Interaction/templateStoryService"; import { useRouter } from "next/navigation"; import { createMovieProjectV1 } from "@/api/video_flow"; import { MovieProjectService, MovieProjectMode } from "@/app/service/Interaction/MovieProjectService"; import { useLoadScriptText, useUploadFile } from "@/app/service/domain/service"; import { ActionButton } from "../common/ActionButton"; import { HighlightEditor } from "../common/HighlightEditor"; import GlobalLoad from "../common/GlobalLoad"; /**模板故事模式弹窗组件 */ const RenderTemplateStoryMode = ({ isOpen, onClose, configOptions = { mode: "auto" as "auto" | "manual", resolution: "720p" as "720p" | "1080p" | "4k", language: "english", videoDuration: "1min", }, }: { isOpen: boolean; onClose: () => void; configOptions: { mode: "auto" | "manual"; resolution: "720p" | "1080p" | "4k"; language: string; videoDuration: string; }; }) => { // 使用 hook 管理状态 const { templateStoryList, selectedTemplate, activeRoleIndex, activeRole, isLoading, getTemplateStoryList, actionStory, setSelectedTemplate, setActiveRoleIndex, AvatarAndAnalyzeFeatures, setActiveRoleAudio, updateFillableContentField, handleFieldBlur, clearData, } = useTemplateStoryServiceHook(); // 使用上传文件hook const { uploadFile, isUploading } = useUploadFile(); // 本地加载状态,用于 UI 反馈 const [localLoading, setLocalLoading] = useState(0); // 控制输入框显示状态 const [inputVisible, setInputVisible] = useState<{ [key: string]: boolean }>( {} ); const router = useRouter(); // 组件挂载时获取模板列表 useEffect(() => { if (isOpen) { getTemplateStoryList(); } }, [isOpen, getTemplateStoryList]); // 监听点击外部区域关闭输入框 useEffect(() => { const handleClickOutside = (event: MouseEvent) => { const target = event.target as Element; // 检查是否点击了输入框相关的元素 if ( !target.closest(".ant-tooltip") && !target.closest('[data-alt*="field-ai-button"]') ) { // 关闭所有打开的输入框 setInputVisible({}); } }; document.addEventListener("mousedown", handleClickOutside); return () => { document.removeEventListener("mousedown", handleClickOutside); }; }, []); // 处理模板选择 const handleTemplateSelect = (template: StoryTemplateEntity) => { setSelectedTemplate(template); setActiveRoleIndex(0); // 重置角色选择 }; // 处理确认操作 const handleConfirm = async () => { if (!selectedTemplate) return; let timer = setInterval(() => { setLocalLoading((prev) => { if (prev >= 95) { clearInterval(timer); return 95; } return prev + 0.1; }); }, 100); try { setLocalLoading(1); // 获取当前用户信息 const User = JSON.parse(localStorage.getItem("currentUser") || "{}"); if (!User.id) { console.error("用户未登录"); return; } const projectId = await actionStory( String(User.id), configOptions.mode, configOptions.resolution, configOptions.language ); if (projectId) { // 跳转到电影详情页 router.push(`/create/work-flow?episodeId=${projectId}`); onClose(); // 重置状态 setSelectedTemplate(null); setActiveRoleIndex(0); } console.log("Story action created:", projectId); } catch (error) { console.error("Failed to create story action:", error); // 这里可以添加 toast 提示 onClose(); // 重置状态 setSelectedTemplate(null); setActiveRoleIndex(0); } finally { setLocalLoading(0); clearInterval(timer); } }; // 模板列表渲染 const templateListRender = () => { return (
{templateStoryList.map((template, index) => (
handleTemplateSelect(template)} >
))}
); }; // 故事编辑器渲染 const storyEditorRender = () => { return selectedTemplate ? (
{/* 模板信息头部 - 增加顶部空间 */}
{/* 左侧图片 */}
{selectedTemplate.name}
{/* 右侧信息 - 增加文本渲染空间 */}

{selectedTemplate.name}

{selectedTemplate.generateText}

{/* 变量字段填写区域 */} {selectedTemplate?.fillable_content && selectedTemplate.fillable_content.length > 0 && (

Template Configuration

{selectedTemplate.fillable_content.map((field, index) => (
{/* 图片容器 */}
updateFillableContentField( field.field_name, e.target.value ) } placeholder={`${field.field_value}`} className="w-[30rem] px-3 py-2 pr-16 bg-white/0 border border-white/10 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500/30 transition-all duration-200 text-sm" />
{/* AI生成按钮 */} { handleFieldBlur( field.field_name, field.value || "" ); setInputVisible((prev) => ({ ...prev, [field.field_name]: false, })); }} icon={} width="w-8" height="h-8" />
} placement="top" classNames={{ root: "max-w-none", }} open={inputVisible[field.field_name]} onOpenChange={(visible) => setInputVisible((prev) => ({ ...prev, [field.field_name]: visible, })) } trigger="contextMenu" styles={{ root: { zIndex: 1000 } }} > {/* 图片 */}
role.role_name === field.field_name )?.photo_url || "/assets/empty_video.png" } alt={field.field_name} className="w-full h-full object-cover" preview={{ mask: null, maskClassName: "hidden", }} fallback="/assets/empty_video.png" />
{/* 角色名称 - 图片下方 */}
{field.field_name}
{/* 按钮组 - 右上角 */}
{/* AI生成按钮 */} {/* 上传按钮 */} { const isImage = file.type.startsWith("image/"); if (!isImage) { console.error("只能上传图片文件"); return false; } const isLt5M = file.size / 1024 / 1024 < 5; if (!isLt5M) { console.error("图片大小不能超过5MB"); return false; } return true; }} customRequest={async ({ file, onSuccess, onError, }) => { try { const fileObj = file as File; const uploadedUrl = await uploadFile( fileObj, (progress) => { console.log(`上传进度: ${progress}%`); } ); await AvatarAndAnalyzeFeatures( uploadedUrl, field.field_name ); onSuccess?.(uploadedUrl); } catch (error) { console.error("字段图片上传失败:", error); onError?.(error as Error); } }} >
))}
)} {/* 角色自定义部分 - 精简布局 */} {/*

Character Customization

{/* 紧凑布局 */} {/*
{/* 左侧:音频部分 */} {/*
{/* 音频操作区域 - 使用新的 AudioRecorder 组件 */} {/*
{ setActiveRoleAudio(audioUrl); }} onAudioDeleted={() => { setActiveRoleAudio(""); }} />
{/* 右侧:角色图片缩略图列表 - 精简 */} {/*
{selectedTemplate.storyRole.map((role, index: number) => (
{/* 上传按钮 - 右上角 */} {/* { // 验证文件类型 const isImage = file.type.startsWith("image/"); if (!isImage) { console.error("只能上传图片文件"); return false; } // 验证文件大小 (10MB) const isLt10M = file.size / 1024 / 1024 < 10; if (!isLt10M) { console.error("图片大小不能超过10MB"); return false; } return true; }} customRequest={async ({ file, onSuccess, onError }) => { try { const fileObj = file as File; console.log( "开始上传图片文件:", fileObj.name, fileObj.type, fileObj.size ); // 使用 hook 上传文件到七牛云 const uploadedUrl = await uploadFile( fileObj, (progress) => { console.log(`上传进度: ${progress}%`); } ); console.log("图片上传成功,URL:", uploadedUrl); // 上传成功后,更新角色图片 await AvatarAndAnalyzeFeatures(uploadedUrl); onSuccess?.(uploadedUrl); } catch (error) { console.error("图片上传失败:", error); onError?.(error as Error); } }} >
))}
*/}
0} handleCreateVideo={handleConfirm} icon={} />
) : (

No templates available

Please try again later

); }; return ( <> { // 清空所有选中的内容数据 clearData(); onClose(); }} footer={null} width="60%" closable={false} style={{ maxWidth: "800px", marginTop: "0vh" }} className="photo-story-modal !pb-0 rounded-lg bg-white/[0.08] backdrop-blur-[20px] [&_.ant-modal-content]:bg-white/[0.00]" >
{/* 弹窗头部 */}

Template Story Selection

{templateListRender()}
{storyEditorRender()}
); }; /** * 视频工具面板组件 * 提供脚本输入和视频克隆两种模式,支持展开/收起功能 */ export function ChatInputBox({ noData }: { noData: boolean }) { // 控制面板展开/收起状态 const [isExpanded, setIsExpanded] = useState(false); // 模板故事弹窗状态 const [isTemplateModalOpen, setIsTemplateModalOpen] = useState(false); // 图片故事弹窗状态 const [isPhotoStoryModalOpen, setIsPhotoStoryModalOpen] = useState(false); // 共享状态 - 需要在不同渲染函数间共享 const [script, setScript] = useState(""); // 用户输入的脚本内容 const router = useRouter(); const [loadingIdea, setLoadingIdea] = useState(false); // 获取创意建议时的加载状态 const [isCreating, setIsCreating] = useState(false); // 视频创建过程中的加载状态 // 配置选项状态 - 整合所有配置项到一个对象 const [configOptions, setConfigOptions] = useState<{ mode: "auto" | "manual"; resolution: "720p" | "1080p" | "4k"; language: string; videoDuration: string; }>({ mode: "auto", resolution: "720p", language: "english", videoDuration: "1min", }); // 配置项显示控制状态 const [showConfigOptions, setShowConfigOptions] = useState(false); const handleGetIdea = () => { if (loadingIdea) return; setLoadingIdea(true); const ideaText = "a cute capybara with an orange on its head, staring into the distance and walking forward"; setTimeout(() => { setScript(ideaText); setLoadingIdea(false); }, 3000); }; // Handle creating video const handleCreateVideo = async () => { setIsCreating(true); if (!script) { setIsCreating(false); return; } const User = JSON.parse(localStorage.getItem("currentUser") || "{}"); // 创建剧集数据 let episodeData: any = { user_id: String(User.id), script: script, mode: configOptions.mode, resolution: configOptions.resolution, language: configOptions.language, video_duration: configOptions.videoDuration, }; // 调用创建剧集API try { const result = await MovieProjectService.createProject( MovieProjectMode.NORMAL, episodeData ); const episodeId = result.project_id; router.push(`/create/work-flow?episodeId=${episodeId}`); } catch (error) { console.error("创建剧集失败:", error); } finally { setIsCreating(false); } }; return (
{/* 视频故事板工具面板 - 毛玻璃效果背景 */}
{/* 展开/收起控制区域 */} { !noData && ( <> {isExpanded ? ( // 展开状态:显示收起按钮和提示
setIsExpanded(false)} > Click to action
) : ( // 收起状态:显示展开按钮
setIsExpanded(true)} >
)} ) } {/* 主要内容区域 - 简化层级,垂直居中 */}
{/* 右上角齿轮图标和配置项 */} {!isExpanded && (
{/* 使用 Dropdown 替代手动控制显示/隐藏 */} (
setConfigOptions((prev) => ({ ...prev, [key]: value })) } />
)} placement={"left" as any} trigger={["click"]} > {/* 配置项显示控制按钮 - 齿轮图标 */}
)} {/* 输入框和Action按钮 - 只在展开状态显示 */} {!isExpanded && (
{/* 第一行:输入框 */}
{/* 文本输入框 - 改为textarea */}