"use client"; import { useEffect, useState } from "react"; import { Drawer, Tooltip, Upload, Image } from "antd"; import { UploadOutlined } from "@ant-design/icons"; import { Clapperboard, Sparkles, LayoutTemplate, ChevronDown, ChevronUp, CheckCircle2 } from "lucide-react"; import { useRouter } from "next/navigation"; import { StoryTemplateEntity } from "@/app/service/domain/Entities"; import { useTemplateStoryServiceHook } from "@/app/service/Interaction/templateStoryService"; import { useUploadFile } from "@/app/service/domain/service"; import { ActionButton } from "../common/ActionButton"; import GlobalLoad from "../common/GlobalLoad"; import { motion, AnimatePresence } from "framer-motion"; interface H5TemplateDrawerProps { isMobile: boolean; isTemplateCreating: boolean; setIsTemplateCreating: (value: boolean) => void; isRoleGenerating: { [key: string]: boolean }; setIsRoleGenerating: ( value: | { [key: string]: boolean } | ((prev: { [key: string]: boolean }) => { [key: string]: boolean }) ) => void; isItemGenerating: { [key: string]: boolean }; setIsItemGenerating: ( value: | { [key: string]: boolean } | ((prev: { [key: string]: boolean }) => { [key: string]: boolean }) ) => void; isOpen: boolean; onClose: () => void; configOptions: { mode: "auto" | "manual"; resolution: "720p" | "1080p" | "4k"; language: string; videoDuration: string; }; } export const H5TemplateDrawer = ({ isMobile, isTemplateCreating, setIsTemplateCreating, isRoleGenerating, setIsRoleGenerating, isItemGenerating, setIsItemGenerating, isOpen, onClose, configOptions, }: H5TemplateDrawerProps) => { const router = useRouter(); const { templateStoryList, selectedTemplate, isLoading, getTemplateStoryList, actionStory, setSelectedTemplate, AvatarAndAnalyzeFeatures, updateItemImage, handleRoleFieldBlur, handleItemFieldBlur, clearData, } = useTemplateStoryServiceHook(); const { uploadFile } = useUploadFile(); const [localLoading, setLocalLoading] = useState(0); const [inputVisible, setInputVisible] = useState<{ [key: string]: boolean }>({}); const [isBottomExpanded, setIsBottomExpanded] = useState(true); const [isDescExpanded, setIsDescExpanded] = useState(false); 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 handleConfirm = async () => { if (!selectedTemplate || isTemplateCreating) return; setIsTemplateCreating(true); let timer: NodeJS.Timeout | null = null; try { const User = JSON.parse(localStorage.getItem("currentUser") || "{}"); if (!User.id) return; timer = setInterval(() => { setLocalLoading((prev) => (prev >= 95 ? 95 : prev + 0.1)); }, 100); setLocalLoading(1); const projectId = await actionStory( String(User.id), configOptions.mode, configOptions.resolution, configOptions.language ); if (projectId) { router.push(`/movies/work-flow?episodeId=${projectId}`); onClose(); setSelectedTemplate(null); } } catch (error) { setIsTemplateCreating(false); setLocalLoading(0); setSelectedTemplate(null); } finally { setLocalLoading(0); if (timer) clearInterval(timer); } }; const renderTopTemplateList = () => { return (
{templateStoryList.map((template, index) => { const isSelected = selectedTemplate?.id === template.id; return ( ); })}
); }; const renderRoles = () => { if (!selectedTemplate?.storyRole || selectedTemplate.storyRole.length === 0) return null; return (

Character Configuration

{selectedTemplate.storyRole.map((role, index) => (
{ const updatedTemplate: StoryTemplateEntity = { ...selectedTemplate!, storyRole: selectedTemplate!.storyRole.map((r) => r.role_name === role.role_name ? { ...r, role_description: e.target.value } : r ), }; setSelectedTemplate(updatedTemplate); }} placeholder={role.user_tips} className="w-[20rem] px-3 py-2 pr-12 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" />
{ if (role.role_description && role.role_description.trim()) { setIsRoleGenerating((prev) => ({ ...prev, [role.role_name]: true })); try { await handleRoleFieldBlur(role.role_name, role.role_description.trim()); } finally { setIsRoleGenerating((prev) => ({ ...prev, [role.role_name]: false })); } } setInputVisible((prev) => ({ ...prev, [role.role_name]: false })); }} icon={} width="w-8" height="h-8" disabled={isRoleGenerating[role.role_name] || false} />
} placement="top" classNames={{ root: "max-w-none" }} open={inputVisible[role.role_name]} onOpenChange={(visible) => setInputVisible((prev) => ({ ...prev, [role.role_name]: visible })) } trigger="click" styles={{ root: { zIndex: 1000 } }} >
{role.role_name}
{ const isImage = file.type.startsWith("image/"); if (!isImage) return false; const isLt5M = file.size / 1024 / 1024 < 5; if (!isLt5M) return false; return true; }} customRequest={async ({ file, onSuccess, onError }) => { try { const fileObj = file as File; const uploadedUrl = await uploadFile(fileObj, () => {}); await AvatarAndAnalyzeFeatures(uploadedUrl, role.role_name); onSuccess?.(uploadedUrl as any); } catch (error) { onError?.(error as Error); } }} >
{role.role_name}
))}
); }; const renderItems = () => { if (!selectedTemplate?.storyItem || selectedTemplate.storyItem.length === 0) return null; return (

props Configuration

{selectedTemplate.storyItem.map((item, index) => (
{ const updatedTemplate: StoryTemplateEntity = { ...selectedTemplate!, storyItem: selectedTemplate!.storyItem.map((i) => i.item_name === item.item_name ? { ...i, item_description: e.target.value } : i ), }; setSelectedTemplate(updatedTemplate); }} placeholder="Enter description for AI image generation..." className="w-[20rem] px-3 py-2 pr-12 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" />
{ if (item.item_description && item.item_description.trim()) { setIsItemGenerating((prev) => ({ ...prev, [item.item_name]: true })); try { await handleItemFieldBlur(item.item_name, item.item_description.trim()); } finally { setIsItemGenerating((prev) => ({ ...prev, [item.item_name]: false })); } } setInputVisible((prev) => ({ ...prev, [item.item_name]: false })); }} icon={} width="w-8" height="h-8" disabled={isItemGenerating[item.item_name] || false} />
} placement="top" classNames={{ root: "max-w-none" }} open={inputVisible[item.item_name]} onOpenChange={(visible) => setInputVisible((prev) => ({ ...prev, [item.item_name]: visible })) } trigger="click" styles={{ root: { zIndex: 1000 } }} >
{item.item_name}
{ const isImage = file.type.startsWith("image/"); if (!isImage) return false; const isLt5M = file.size / 1024 / 1024 < 5; if (!isLt5M) return false; return true; }} customRequest={async ({ file, onSuccess, onError }) => { try { const fileObj = file as File; const uploadedUrl = await uploadFile(fileObj, () => {}); updateItemImage(item.item_name, uploadedUrl); onSuccess?.(uploadedUrl as any); } catch (error) { onError?.(error as Error); } }} >
{item.item_name}
))}
); }; const renderBottomDetail = () => { if (!selectedTemplate) { return (

No templates available

Please try again later

); } return (
{selectedTemplate.name}

{selectedTemplate.name}

{selectedTemplate.generateText}

{!isDescExpanded && (
)}
{renderRoles()} {renderItems()}
{selectedTemplate?.freeInputItem && selectedTemplate.freeInputItem.length > 0 && (
{ const updatedTemplate = { ...selectedTemplate!, freeInputItem: selectedTemplate!.freeInputItem.map((item) => ({ ...item, free_input_text: e.target.value, })), } as StoryTemplateEntity; setSelectedTemplate(updatedTemplate); }} />
)} 0} handleCreateVideo={handleConfirm} icon={} disabled={isTemplateCreating || localLoading > 0} width="w-10" height="h-10" />
); }; return ( { clearData(); onClose(); }} className="h5-template-drawer [&_.ant-drawer-body]:!p-0 bg-white/[0.02]" styles={{ body: { height: `calc(100vh - 3rem)`, overflow: "hidden", display: "flex", flexDirection: "column", position: "relative", }, }} >

Template Story

{renderTopTemplateList()}
setIsBottomExpanded((v) => !v)} className="w-10 h-10 rounded-full bg-white/10 hover:bg-white/20 border border-white/20 text-white flex items-center justify-center shadow-xl" whileTap={{ scale: 0.96 }} >
{isBottomExpanded && ( {renderBottomDetail()} )}
); }; export default H5TemplateDrawer;