"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 GeneratePageModalProps { isOpen: boolean onClose: () => void onGenerate: (data: { prompt: string style: string characterFiles?: File[] characterUrls?: string[] // For reusing existing characters isContinuation?: boolean }) => void pageNumber: number previousCharacters?: File[] previousPagePrompt?: string previousPageStyle?: string existingCharacterImages?: string[] // Character images from previous pages } export function GeneratePageModal({ isOpen, onClose, onGenerate, pageNumber, previousCharacters, previousPagePrompt, previousPageStyle, existingCharacterImages = [], }: GeneratePageModalProps) { const [prompt, setPrompt] = useState("") const [uploadedFiles, setUploadedFiles] = useState(previousCharacters || []) const [selectedExistingCharacters, setSelectedExistingCharacters] = useState([]) const [previews, setPreviews] = useState([]) const [showPreview, setShowPreview] = useState(null) const [isGenerating, setIsGenerating] = useState(false) const [isContinuing, setIsContinuing] = useState(false) const fileInputRef = useRef(null) const { toast } = useToast() const selectedStyleId = previousPageStyle || "noir" const selectedStyle = useMemo( () => COMIC_STYLES.find((s) => s.id === selectedStyleId)?.name || "Noir", [selectedStyleId] ) useEffect(() => { if (previousCharacters && previousCharacters.length > 0) { const newPreviews: string[] = [] previousCharacters.forEach((file, index) => { const reader = new FileReader() reader.onload = (e) => { newPreviews[index] = e.target?.result as string if (newPreviews.filter(Boolean).length === previousCharacters.length) { setPreviews([...newPreviews]) } } reader.readAsDataURL(file) }) } }, [previousCharacters]) const handleFiles = async (newFiles: FileList | null) => { if (!newFiles) return const filesArray = Array.from(newFiles) // Validate files (including WebP rejection) const validationResults = filesArray.map(file => ({ file, validation: validateFileForUpload(file, true) })) // Show errors for invalid files 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) // Max 2 files setUploadedFiles(totalFiles) // Generate previews for all files 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 handleGenerate = () => { if (!prompt.trim()) return setIsGenerating(true) onGenerate({ prompt, style: selectedStyle, characterFiles: uploadedFiles.length > 0 ? uploadedFiles : undefined, characterUrls: selectedExistingCharacters.length > 0 ? selectedExistingCharacters : undefined, isContinuation: false, }) } const handleContinue = () => { setIsContinuing(true) onGenerate({ prompt: prompt.trim() || `Continue the story from where it left off. Previous context: ${previousPagePrompt}`, style: selectedStyle, characterFiles: uploadedFiles.length > 0 ? uploadedFiles : undefined, characterUrls: selectedExistingCharacters.length > 0 ? selectedExistingCharacters : undefined, isContinuation: true, }) } useEffect(() => { if (!isOpen) { setIsGenerating(false) setIsContinuing(false) setPrompt("") } }, [isOpen]) return ( <> Generate Page {pageNumber}
{selectedStyle}