"use client"; import { useEffect, useRef, useState } from "react"; import { Drawer, Popconfirm, Tooltip, Upload, Dropdown } from "antd"; import { ImagePlay, Sparkles, Trash2 } from "lucide-react"; import { useRouter } from "next/navigation"; import GlobalLoad from "../common/GlobalLoad"; import { ActionButton } from "../common/ActionButton"; import { HighlightEditor } from "../common/HighlightEditor"; import { useImageStoryServiceHook } from "@/app/service/Interaction/ImageStoryService"; import { useLoadScriptText } from "@/app/service/domain/service"; import { AspectRatioSelector, AspectRatioValue } from "./AspectRatioSelector"; type ConfigOptions = { mode: "auto" | "manual"; resolution: "720p" | "1080p" | "4k"; language: string; videoDuration: string; }; /** * 移动端/平板端全屏抽屉版本:基于 PcPhotoStoryModal 的功能与交互 * - 顶部固定标题栏 * - 中间可滚动内容 * - 底部固定操作条 * - 人物头像采用网格布局,避免横向滚动误触返回手势 * * @param {boolean} isOpen - 是否打开抽屉 * @param {() => void} onClose - 关闭回调 * @param {boolean} isCreating - 是否正在创建视频 * @param {(v: boolean) => void} setIsCreating - 设置创建状态 * @param {boolean} isPhotoCreating - 是否正在分析图片 * @param {(v: boolean) => void} setIsPhotoCreating - 设置分析状态 * @param {ConfigOptions} configOptions - 配置项,默认与 PC 版一致 */ export const H5PhotoStoryDrawer = ({ isMobile, isOpen, onClose, isCreating, setIsCreating, isPhotoCreating, setIsPhotoCreating, configOptions = { mode: "auto", resolution: "720p", language: "english", videoDuration: "1min", }, }: { isMobile: boolean; isOpen: boolean; onClose: () => void; isCreating: boolean; setIsCreating: (value: boolean) => void; isPhotoCreating: boolean; setIsPhotoCreating: (value: boolean) => void; configOptions?: ConfigOptions; }) => { const { activeImageUrl, storyContent, potentialGenres, selectedCategory, isLoading, hasAnalyzed, taskProgress, updateStoryType, updateStoryContent, updateCharacterName, resetImageStory, triggerFileSelection, avatarComputed, uploadAndAnalyzeImage, setCharactersAnalysis, originalUserDescription, actionMovie, uploadCharacterAvatarAndAnalyzeFeatures, } = useImageStoryServiceHook(); const { loadingText } = useLoadScriptText(isLoading); const [localLoading, setLocalLoading] = useState(0); const [aspectUI, setAspectUI] = useState("VIDEO_ASPECT_RATIO_PORTRAIT"); const router = useRouter(); const taskProgressRef = useRef(taskProgress); const [cursorPosition, setCursorPosition] = useState(0); const handleCursorPositionChange = (position: number) => { setCursorPosition(position); }; useEffect(() => { taskProgressRef.current = taskProgress; }, [taskProgress]); const handleImageUpload = async (e: any) => { const target = e.target as HTMLImageElement; if (!(target.tagName == "IMG" || e.target.dataset.alt == "image-upload-area")) { return; } e.preventDefault(); e.stopPropagation(); try { await triggerFileSelection(); } catch (error) { // 保持静默失败,避免打断用户 } }; const handleConfirm = async () => { try { setIsCreating(true); const User = JSON.parse(localStorage.getItem("currentUser") || "{}"); if (!User.id) { setIsCreating(false); return; } const episodeResponse = await actionMovie( String(User.id), configOptions.mode as "auto" | "manual", configOptions.resolution as "720p" | "1080p" | "4k", configOptions.language, aspectUI as AspectRatioValue ); if (!episodeResponse) return; const episodeId = episodeResponse.project_id; router.push(`/movies/work-flow?episodeId=${episodeId}`); onClose(); } catch (error) { setIsCreating(false); } }; const handleAnalyzeImage = async () => { if (isPhotoCreating || isLoading) return; setIsPhotoCreating(true); let timeout = 100; let timer: NodeJS.Timeout; timer = setInterval(() => { const currentProgress = taskProgressRef.current; setLocalLoading((prev) => { if (prev >= currentProgress && currentProgress != 0) { return currentProgress; } return prev + 0.1; }); }, timeout); try { await uploadAndAnalyzeImage(); } catch (error) { setIsPhotoCreating(false); } finally { clearInterval(timer); setLocalLoading(0); } }; return ( 0} progress={localLoading}>

Movie Generation from Image

{/* 上传卡片 */}
{activeImageUrl ? (
Story inspiration { resetImageStory(); }} okText="Yes" cancelText="No" showCancel={false} okType="default" placement="top" classNames={{ root: "text-white event-pointer", body: "text-white border rounded-lg bg-white/[0.04] [&_.ant-popconfirm-description]:!text-white [&_.ant-popconfirm-title]:!text-white [&_.ant-btn]:!text-white", }} >
) : (
Upload
)}
{/* 人物头像网格 */} {hasAnalyzed && avatarComputed.length > 0 && (
{avatarComputed.map((avatar, index) => (
{avatar.name} { const target = e.target as HTMLImageElement; target.src = activeImageUrl; }} />
{ const newName = e.target.value.trim(); if (newName && newName !== avatar.name) { updateCharacterName(avatar.name, newName); } }} className="w-full max-w-[72px] md:max-w-[80px] text-center text-xs md:text-sm text-white/80 bg-transparent border-none outline-none focus:ring-1 focus:ring-blue-400/50 rounded px-1 py-0.5 mobile-input" />
))}
)} {/* 题材标签(自动换行,避免横向滚动) */} {hasAnalyzed && potentialGenres.length > 0 && (
{[...potentialGenres].map((genre) => ( ))}
)} {/* 用户原始文本信息 */} {originalUserDescription && (
Your Provided Text: {originalUserDescription}
)} {/* 文本编辑器 */}
{/* 横/竖屏选择 */} {!hasAnalyzed ? (
} disabled={isLoading || isPhotoCreating} width="w-10" height="h-10" />
) : (
} disabled={isCreating} width="w-10" height="h-10" />
)}
); }; export default H5PhotoStoryDrawer;