"use client"; import { useState, useRef, useEffect, useMemo } from "react"; import { Upload, X, Loader2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { useToast } from "@/hooks/use-toast"; import { validateFileForUpload, generateFilePreview } from "@/lib/file-utils"; import { COMIC_STYLES } from "@/lib/constants"; interface PageReference { pageNumber: number; characterImages: string[]; prompt: string; imageUrl?: string; style: string; } interface GeneratePageModalProps { isOpen: boolean; onClose: () => void; onGenerate: (data: { prompt: string; style: string; characterFiles?: File[]; characterUrls?: string[]; isContinuation?: boolean; }) => void; pageNumber: number; allPages: PageReference[]; } export function GeneratePageModal({ isOpen, onClose, onGenerate, pageNumber, allPages, }: GeneratePageModalProps) { const [prompt, setPrompt] = useState(""); const [uploadedFiles, setUploadedFiles] = useState([]); const [selectedExistingCharacters, setSelectedExistingCharacters] = useState< string[] >([]); const [referencePageNumber, setReferencePageNumber] = useState( allPages.length > 0 ? allPages.length : 1 ); const [pageImage, setPageImage] = useState(null); const [previews, setPreviews] = useState([]); const [showPreview, setShowPreview] = useState(null); const [isGenerating, setIsGenerating] = useState(false); const fileInputRef = useRef(null); const { toast } = useToast(); const referencePage = allPages.find((p) => p.pageNumber === referencePageNumber) || null; const referenceStyleId = referencePage?.style || "noir"; const selectedStyleName = COMIC_STYLES.find((s) => s.id === referenceStyleId)?.name || "Noir"; const selectedStyle = useMemo(() => selectedStyleName, [referenceStyleId]); useEffect(() => { if (isOpen && referencePage) { setSelectedExistingCharacters(referencePage.characterImages); setPageImage(referencePage.imageUrl || null); } else if (isOpen && !referencePage) { setPageImage(null); } }, [isOpen, referencePage]); const handleFiles = async (newFiles: FileList | null) => { if (!newFiles) return; const filesArray = Array.from(newFiles); const validationResults = filesArray.map((file) => ({ file, validation: validateFileForUpload(file, true), })); validationResults.forEach(({ validation }) => { if (!validation.valid && validation.error) { toast({ title: "Invalid file", description: validation.error, variant: "destructive", duration: 4000, }); } }); const validFiles = validationResults .filter(({ validation }) => validation.valid) .map(({ file }) => file); if (validFiles.length === 0) return; const totalFiles = [...uploadedFiles, ...validFiles].slice(0, 2); setUploadedFiles(totalFiles); const newPreviews = await Promise.all( totalFiles.map((file) => generateFilePreview(file)) ); setPreviews(newPreviews); }; const removeFile = (index: number) => { const newFiles = uploadedFiles.filter((_, i) => i !== index); const newPreviews = previews.filter((_, i) => i !== index); setUploadedFiles(newFiles); setPreviews(newPreviews); setShowPreview(null); if (fileInputRef.current) { fileInputRef.current.value = ""; } }; const toggleExistingCharacter = (characterUrl: string) => { setSelectedExistingCharacters((prev) => prev.includes(characterUrl) ? prev.filter((url) => url !== characterUrl) : [...prev, characterUrl] ); }; const togglePageImage = () => { setPageImage((prev) => prev === null && referencePage?.imageUrl ? referencePage.imageUrl : null ); }; const handleGenerate = async () => { if (!prompt.trim()) return; setIsGenerating(true); const fileDataUrls = await Promise.all( uploadedFiles.map((file, index) => { return new Promise((resolve) => { const reader = new FileReader(); reader.onload = (e) => { const base64 = e.target?.result as string; const [header, base64Data] = base64.split(","); // Send just the base64 data without the data URL prefix resolve(base64Data); }; reader.readAsDataURL(file); }); }) ); const allCharacterUrls: string[] = [ ...selectedExistingCharacters, ...fileDataUrls, ...(pageImage ? [pageImage] : []), ]; onGenerate({ prompt, style: selectedStyle, characterFiles: uploadedFiles.length > 0 ? uploadedFiles : undefined, characterUrls: allCharacterUrls, isContinuation: false, }); }; useEffect(() => { if (!isOpen) { setIsGenerating(false); setPrompt(""); setUploadedFiles([]); setSelectedExistingCharacters([]); setPageImage(null); if (allPages.length > 0) { setReferencePageNumber(allPages.length); } } }, [isOpen]); return ( <> Generate Page {pageNumber}
{/* Reference Page Selection */} {allPages.length > 0 && (
)}
{selectedStyleName}