import { useState, useEffect, useCallback } from 'react'; import { Loader2 } from 'lucide-react'; import ModelSelector from './ModelSelector'; import PromptInput from './PromptInput'; import AspectRatioSelector from './AspectRatioSelector'; import ImageCountSlider from './ImageCountSlider'; import { Button } from '../ui/button'; import { v4 as uuidv4 } from 'uuid'; interface Image { url: string; } interface Batch { id: number | string; // Allow string for tempId prompt: string; width: number; height: number; model: string; images: Image[]; status?: string; tempId?: string; } export default function GeneratorForm({ onGenerate, remixBatch }: { onGenerate: (batch: Batch, isPlaceholder: boolean) => void, remixBatch: Batch | null }) { const [prompt, setPrompt] = useState(''); const [model, setModel] = useState('runware:100@1'); // FLUX SCHNELL as default const [aspectRatio, setAspectRatio] = useState('square'); const [imageCount, setImageCount] = useState(1); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); useEffect(() => { if (remixBatch) { setPrompt(remixBatch.prompt); setModel(remixBatch.model); setAspectRatio(getAspectRatioFromDimensions(remixBatch.width, remixBatch.height)); setImageCount(remixBatch.images.length); } }, [remixBatch]); const getAspectRatioFromDimensions = (width: number, height: number) => { if (width === height) return 'square'; if (width === 832 && height === 1216) return 'portrait'; if (width === 1216 && height === 832) return 'landscape'; return 'square'; // Default to square if dimensions don't match known ratios }; const aspectRatios: { [key: string]: { width: number; height: number } } = { square: { width: 1024, height: 1024 }, landscape: { width: 1216, height: 832 }, portrait: { width: 832, height: 1216 } }; const handleGenerate = useCallback(async () => { setError(null); const placeholderId = uuidv4(); const placeholderBatch: Batch = { id: placeholderId, prompt, width: aspectRatios[aspectRatio as keyof typeof aspectRatios].width, height: aspectRatios[aspectRatio as keyof typeof aspectRatios].height, model, images: Array(imageCount).fill({ url: '/placeholder-image.png' }), status: 'pending', tempId: placeholderId }; onGenerate(placeholderBatch, true); try { const response = await fetch('/api/generate-image', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ prompt, width: placeholderBatch.width, height: placeholderBatch.height, model, number_results: imageCount, placeholderId, }), }); if (!response.ok) { throw new Error('Failed to generate image'); } const reader = response.body?.getReader(); if (!reader) { throw new Error('Failed to read response'); } while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = new TextDecoder().decode(value); const data = JSON.parse(chunk); if (data.batch) { onGenerate({ ...data.batch, tempId: placeholderId }, false); } } } catch (error) { console.error('Error generating image:', error); setError(error instanceof Error ? error.message : 'An unknown error occurred'); onGenerate({ ...placeholderBatch, status: 'error' }, false); } }, [aspectRatio, prompt, model, imageCount, onGenerate, aspectRatios]); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') { event.preventDefault(); handleGenerate(); } }; document.addEventListener('keydown', handleKeyDown); return () => { document.removeEventListener('keydown', handleKeyDown); }; }, [handleGenerate]); return (
{error && (
{error}
)}
); }