"use client"; import { useState, useRef, useEffect } from "react"; import { ChevronDown, ChevronUp, Video, Loader2, Lightbulb, Package, Crown, ArrowUp, Globe, AudioLines, Info, Clock, Trash2 } from "lucide-react"; import { Dropdown, Modal, Tooltip, Upload } from "antd"; import { PlusOutlined, UploadOutlined } from "@ant-design/icons"; import type { MenuProps } from "antd"; import { ModeEnum, ResolutionEnum, VideoDurationEnum } from "@/app/model/enums"; import { StoryTemplateEntity, ImageStoryEntity, } from "@/app/service/domain/Entities"; import AudioPlayer from "react-h5-audio-player"; import "react-h5-audio-player/lib/styles.css"; // 自定义音频播放器样式 const customAudioPlayerStyles = ` .custom-audio-player { background: rgba(255, 255, 255, 0.05) !important; border-radius: 8px !important; border: 1px solid rgba(255, 255, 255, 0.1) !important; } .custom-audio-player .rhap_main-controls-button { color: white !important; } .custom-audio-player .rhap_progress-filled { background-color: #3b82f6 !important; } .custom-audio-player .rhap_progress-indicator { background-color: #3b82f6 !important; } .custom-audio-player .rhap_time { color: rgba(255, 255, 255, 0.7) !important; } .custom-audio-player .rhap_volume-controls { color: white !important; } `; /**模板故事模式 */ const RenderTemplateStoryMode = () => { const [templates, setTemplates] = useState([]); const [selectedTemplate, setSelectedTemplate] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); const [loading, setLoading] = useState(false); // 角色资源状态管理 const [roleImages, setRoleImages] = useState<{ [key: number]: string }>({}); const [roleAudios, setRoleAudios] = useState<{ [key: number]: string }>({}); const [selectedRoleIndex, setSelectedRoleIndex] = useState(0); // 模拟API请求获取模板数据 const fetchTemplates = async () => { setLoading(true); try { // 模拟API调用,实际项目中替换为真实API const mockTemplates: StoryTemplateEntity[] = [ { id: "1", name: "Three Little Dwarves", imageUrl: "https://picsum.photos/200/200?random=1", generateText: "A story about courage and friendship, where the protagonist faces numerous challenges...", storyRole: ["Brave Warrior", "Wise Mage", "Loyal Companion"], userResources: [], }, { id: "2", name: "Seven Old Women", imageUrl: "https://picsum.photos/200/200?random=2", generateText: "In a futuristic world where technology and humanity intertwine, exploring the unknown mysteries of the universe...", storyRole: ["Space Explorer", "AI Assistant", "Alien Creature"], userResources: [], }, { id: "3", name: "Nine Wooden Men", imageUrl: "https://picsum.photos/200/200?random=3", generateText: "A magical fairy tale world where good battles evil, leading to a beautiful ending...", storyRole: ["Princess", "Prince", "Magician"], userResources: [], }, ]; setTemplates(mockTemplates); } catch (error) { console.error("Failed to fetch templates:", error); } finally { setLoading(false); } }; useEffect(() => { fetchTemplates(); }, []); // 处理模板选择 const handleTemplateSelect = (template: StoryTemplateEntity) => { setSelectedTemplate(template); setIsModalOpen(true); }; // 处理确认操作 const handleConfirm = () => { // 这里可以添加实际的API调用逻辑 console.log("Template confirmed:", selectedTemplate); setIsModalOpen(false); setSelectedTemplate(null); }; // 处理角色图片上传 const handleRoleImageUpload = (roleIndex: number, file: any) => { if (file && selectedTemplate) { // 这里可以添加实际的上传逻辑 console.log("Character image uploaded:", file.name); // 模拟上传成功,设置图片URL const imageUrl = URL.createObjectURL(file); setRoleImages((prev) => ({ ...prev, [roleIndex]: imageUrl })); // 保存到模板资源中 const roleName = selectedTemplate.storyRole[roleIndex]; // 查找是否已存在该角色的资源记录 const existingResourceIndex = selectedTemplate.userResources.findIndex( (resource) => resource.role_name === roleName ); let updatedTemplate; if (existingResourceIndex >= 0) { // 如果已存在,更新现有记录 const updatedResources = [...selectedTemplate.userResources]; updatedResources[existingResourceIndex] = { ...updatedResources[existingResourceIndex], photo_url: imageUrl, }; updatedTemplate = { ...selectedTemplate, userResources: updatedResources, }; } else { // 如果不存在,创建新记录 updatedTemplate = { ...selectedTemplate, userResources: [ ...selectedTemplate.userResources, { role_name: roleName, photo_url: imageUrl, voice_url: "", }, ], }; } setSelectedTemplate(updatedTemplate); } }; // 删除角色图片 const handleDeleteRoleImage = (roleIndex: number) => { setRoleImages((prev) => { const newState = { ...prev }; delete newState[roleIndex]; return newState; }); // 同时从模板资源中删除图片 if (selectedTemplate) { const roleName = selectedTemplate.storyRole[roleIndex]; const existingResourceIndex = selectedTemplate.userResources.findIndex( (resource) => resource.role_name === roleName ); if (existingResourceIndex >= 0) { const updatedResources = [...selectedTemplate.userResources]; updatedResources[existingResourceIndex] = { ...updatedResources[existingResourceIndex], photo_url: "", }; const updatedTemplate = { ...selectedTemplate, userResources: updatedResources, }; setSelectedTemplate(updatedTemplate); } } }; // 处理音频录制 const handleAudioRecord = async (roleIndex: number) => { try { // 请求麦克风权限 const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); // 创建 MediaRecorder const mediaRecorder = new MediaRecorder(stream); const audioChunks: Blob[] = []; mediaRecorder.ondataavailable = (event) => { audioChunks.push(event.data); }; mediaRecorder.onstop = () => { // 停止所有音轨 stream.getTracks().forEach((track) => track.stop()); // 创建音频 Blob const audioBlob = new Blob(audioChunks, { type: "audio/wav" }); const audioUrl = URL.createObjectURL(audioBlob); // 保存到状态 setRoleAudios((prev) => ({ ...prev, [roleIndex]: audioUrl })); // 保存到模板资源中 if (selectedTemplate) { const roleName = selectedTemplate.storyRole[roleIndex]; // 查找是否已存在该角色的资源记录 const existingResourceIndex = selectedTemplate.userResources.findIndex( (resource) => resource.role_name === roleName ); let updatedTemplate; if (existingResourceIndex >= 0) { // 如果已存在,更新现有记录 const updatedResources = [...selectedTemplate.userResources]; updatedResources[existingResourceIndex] = { ...updatedResources[existingResourceIndex], voice_url: audioUrl, }; updatedTemplate = { ...selectedTemplate, userResources: updatedResources, }; } else { // 如果不存在,创建新记录 updatedTemplate = { ...selectedTemplate, userResources: [ ...selectedTemplate.userResources, { role_name: roleName, photo_url: "", voice_url: audioUrl, }, ], }; } setSelectedTemplate(updatedTemplate); } }; // 开始录制 mediaRecorder.start(); // 显示录制状态(可以添加一个录制指示器) console.log("Started recording audio for role:", roleIndex); // 5秒后自动停止录制(或者可以添加手动停止按钮) setTimeout(() => { if (mediaRecorder.state === "recording") { mediaRecorder.stop(); } }, 5000); } catch (error) { console.error("Failed to start recording:", error); alert("Failed to access microphone. Please check permissions."); } }; // 处理音频上传 const handleAudioUpload = (roleIndex: number) => { const input = document.createElement("input"); input.type = "file"; input.accept = "audio/*"; input.onchange = (e) => { const file = (e.target as HTMLInputElement).files?.[0]; if (file) { // 这里可以添加实际的上传逻辑 console.log("Audio file uploaded:", file.name); // 模拟上传成功,设置音频URL const audioUrl = URL.createObjectURL(file); setRoleAudios((prev) => ({ ...prev, [roleIndex]: audioUrl })); // 保存到模板资源中 if (selectedTemplate) { const roleName = selectedTemplate.storyRole[roleIndex]; // 查找是否已存在该角色的资源记录 const existingResourceIndex = selectedTemplate.userResources.findIndex( (resource) => resource.role_name === roleName ); let updatedTemplate; if (existingResourceIndex >= 0) { // 如果已存在,更新现有记录 const updatedResources = [...selectedTemplate.userResources]; updatedResources[existingResourceIndex] = { ...updatedResources[existingResourceIndex], voice_url: audioUrl, }; updatedTemplate = { ...selectedTemplate, userResources: updatedResources, }; } else { // 如果不存在,创建新记录 updatedTemplate = { ...selectedTemplate, userResources: [ ...selectedTemplate.userResources, { role_name: roleName, photo_url: "", voice_url: audioUrl, }, ], }; } setSelectedTemplate(updatedTemplate); } } }; input.click(); }; // 删除角色音频 const handleDeleteRoleAudio = (roleIndex: number) => { setRoleAudios((prev) => { const newState = { ...prev }; delete newState[roleIndex]; return newState; }); // 同时从模板资源中删除音频 if (selectedTemplate) { const roleName = selectedTemplate.storyRole[roleIndex]; const existingResourceIndex = selectedTemplate.userResources.findIndex( (resource) => resource.role_name === roleName ); if (existingResourceIndex >= 0) { const updatedResources = [...selectedTemplate.userResources]; updatedResources[existingResourceIndex] = { ...updatedResources[existingResourceIndex], voice_url: "", }; const updatedTemplate = { ...selectedTemplate, userResources: updatedResources, }; setSelectedTemplate(updatedTemplate); } } }; return ( <>
{/* 使用 Ant Design Tooltip */}
?
{/* 模板列表 - 横向滚动 */}
{loading ? ( // Loading state
) : ( // Template icon list templates.map((template) => (
handleTemplateSelect(template)} > {/* 模板卡片容器 */}
{/* 图片区域 - 占满整个容器 */}
{template.name}
{/* 鼠标悬停信息覆盖层 */}
{/* 故事名称 */}

{template.name}

{/* 角色数量 */}
{template.storyRole.length} Characters
)) )}
{/* 模板详情弹窗 */} { // 清空所有选中的内容数据 setRoleImages({}); setRoleAudios({}); setSelectedTemplate(null); setSelectedRoleIndex(0); setIsModalOpen(false); }} footer={null} width="50%" style={{ maxWidth: "600px" }} className="template-modal" closeIcon={
×
} > {selectedTemplate && (
{/* 弹窗头部 */}
{/* 左侧图片 - 减小尺寸 */}
{selectedTemplate.name}
{/* 右侧信息 - 垂直从上排列,支持滚动 */}

{selectedTemplate.name}

{selectedTemplate.generateText}

{/* 角色自定义部分 */}

Character Customization

{/* 角色Tab切换 */}
{selectedTemplate.storyRole.map((role: string, index: number) => ( ))}
{/* 当前选中角色的自定义内容 */} {selectedRoleIndex !== null && (
{/* 图片上传部分 */}
{/* 图片上传提示 */}

Character Photo

Upload a portrait photo to replace this character's appearance in the movie.

{/* 图片上传区域 - 放大尺寸 */}
{roleImages[selectedRoleIndex] ? (
Character
) : ( false} onChange={(info) => { if (info.file.status === "done") { handleRoleImageUpload( selectedRoleIndex, info.file.originFileObj ); } }} >
Upload Photo
)}
{/* 音频部分 */}
{/* 音频录制提示 */}

Character Voice

Record your voice to replace this character's voice in the movie. Aim for 15 seconds of clear, natural speech.

"The sun sets slowly behind the mountain, casting a warm glow over the calm valley."

{/* 音频操作区域 */}
{/* 音频播放器或占位符 */} {roleAudios[selectedRoleIndex] ? (
{/* 删除按钮集成到播放器中 - 更小且位于右上角 */}
) : (
No Audio Recorded
)} {/* 音频操作按钮 - 只在没有音频时显示 */} {!roleAudios[selectedRoleIndex] && (
{/* 录制音频按钮 */} {/* 上传音频按钮 */}
)}
)}
{/* 弹窗底部操作 - 只保留 Action 按钮 */}
{loading ? ( <> Actioning... ) : ( <> Action )}
)}
); }; /**照片故事模式 */ const RenderPhotoStoryMode = ({ photoStory, setPhotoStory, hasGenerated, setHasGenerated, }: { photoStory: Partial; setPhotoStory: React.Dispatch< React.SetStateAction> >; hasGenerated: boolean; setHasGenerated: React.Dispatch>; }) => { const [isGenerating, setIsGenerating] = useState(false); const [isUploading, setIsUploading] = useState(false); // 故事分类选项 const storyTypes = [ { key: "auto", label: "Auto" }, { key: "adventure", label: "Adventure" }, { key: "romance", label: "Romance" }, { key: "mystery", label: "Mystery" }, { key: "fantasy", label: "Fantasy" }, { key: "comedy", label: "Comedy" }, ]; // 处理图片上传 const handleImageUpload = async () => { const input = document.createElement("input"); input.type = "file"; input.accept = "image/*"; input.onchange = async (e) => { const file = (e.target as HTMLInputElement).files?.[0]; if (file) { try { setIsUploading(true); // 模拟图片上传,实际项目中替换为真实API const imageUrl = URL.createObjectURL(file); setPhotoStory((prev) => ({ ...prev, imageUrl })); console.log("Image uploaded successfully:", file.name); } catch (error) { console.error("Upload error:", error); alert("Upload failed, please try again"); } finally { setIsUploading(false); } } }; input.click(); }; // 处理AI生成故事 const handleGenerateStory = async () => { if (!photoStory.imageUrl || !photoStory.imageStory) { alert("Please upload an image and enter story inspiration first"); return; } setIsGenerating(true); try { // 模拟AI生成故事,实际项目中替换为真实API await new Promise((resolve) => setTimeout(resolve, 2000)); const generatedStory = `Based on your image and inspiration "${photoStory.imageStory}", I've created a captivating story that captures the essence of your vision. The narrative weaves together elements of mystery and wonder, creating an engaging tale that will captivate your audience.`; setPhotoStory((prev) => ({ ...prev, imageStory: generatedStory })); setHasGenerated(true); } catch (error) { console.error("Story generation failed:", error); alert("Story generation failed, please try again"); } finally { setIsGenerating(false); } }; // 处理重置 const handleReset = () => { setPhotoStory({ imageUrl: "", imageStory: "", storyType: "auto", }); setHasGenerated(false); }; // 处理故事文本变化 const handleStoryChange = (e: React.ChangeEvent) => { setPhotoStory((prev) => ({ ...prev, imageStory: e.target.value })); }; // 处理分类选择 const handleTypeChange = (value: string) => { setPhotoStory((prev) => ({ ...prev, storyType: value })); }; return (
{/* 图片上传区域 */}
{/* 图片上传按钮 */}
{isUploading ? ( ) : photoStory.imageUrl ? ( Story Image ) : ( )}
{isUploading ? "Uploading..." : photoStory.imageUrl ? "Image" : "Add Image"}
{/* 文本输入和分类选择区域 */}
{/* 文本输入框 */}