import { ReactNode, useState, useEffect } from 'react'; import Button from '../ui/button/Button'; import TextArea from '../form/input/TextArea'; import Select from '../form/Select'; import Label from '../form/Label'; import { useToast } from '../ui/toast/ToastContainer'; import { fetchAPI } from '../../services/api'; interface ImageGenerationCardProps { title: string; description?: string; integrationId: string; icon?: ReactNode; } interface GeneratedImage { url: string; revised_prompt?: string; model?: string; provider?: string; size?: string; format?: string; cost?: string; } interface ImageSettings { service?: string; model?: string; runwareModel?: string; } /** * Image Generation Testing Card Component * Full implementation with form fields and image display */ export default function ImageGenerationCard({ title, description, integrationId, icon, }: ImageGenerationCardProps) { const toast = useToast(); const [isGenerating, setIsGenerating] = useState(false); const [prompt, setPrompt] = useState(''); const [negativePrompt, setNegativePrompt] = useState('text, watermark, logo, overlay, title, caption, writing on walls, writing on objects, UI, infographic elements, post title'); const [imageType, setImageType] = useState('realistic'); const [imageSize, setImageSize] = useState('1024x1024'); const [imageFormat, setImageFormat] = useState('webp'); const [imageSettings, setImageSettings] = useState({}); // Valid image sizes per model (from OpenAI official documentation) const VALID_SIZES_BY_MODEL: Record = { 'dall-e-3': ['1024x1024', '1024x1792', '1792x1024'], 'dall-e-2': ['256x256', '512x512', '1024x1024'], }; // Get valid sizes for current model const getValidSizes = (): string[] => { const service = imageSettings.service || 'openai'; const model = service === 'openai' ? (imageSettings.model || 'dall-e-3') : null; if (model && VALID_SIZES_BY_MODEL[model]) { return VALID_SIZES_BY_MODEL[model]; } // Default to DALL-E 3 sizes if unknown return VALID_SIZES_BY_MODEL['dall-e-3']; }; // Update size if current size is invalid for the selected model useEffect(() => { const validSizes = getValidSizes(); if (validSizes.length > 0 && !validSizes.includes(imageSize)) { // Reset to first valid size (usually the default) setImageSize(validSizes[0]); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [imageSettings.model, imageSettings.service]); // imageSize intentionally omitted to avoid infinite loop const [generatedImage, setGeneratedImage] = useState(null); const [error, setError] = useState(null); const API_BASE_URL = import.meta.env.VITE_BACKEND_URL || 'https://api.igny8.com/api'; // Load default image generation settings on mount useEffect(() => { const loadImageSettings = async () => { try { const response = await fetch( `${API_BASE_URL}/v1/system/settings/integrations/image_generation/`, { credentials: 'include' } ); if (response.ok) { const data = await response.json(); if (data.success && data.data) { setImageSettings(data.data); } } } catch (error) { console.error('Error loading image settings:', error); } }; loadImageSettings(); }, [API_BASE_URL]); const handleGenerate = async () => { if (!prompt.trim()) { toast.error('Please enter a prompt description'); return; } setIsGenerating(true); setError(null); setGeneratedImage(null); try { // Get the default service and model from settings const service = imageSettings.service || 'openai'; const model = service === 'openai' ? (imageSettings.model || 'dall-e-3') : (imageSettings.runwareModel || 'runware:97@1'); // Build prompt with template (similar to reference plugin) const fullPrompt = `Create a high-quality ${imageType} image. ${prompt}`; const requestBody = { prompt: fullPrompt, negative_prompt: negativePrompt, image_type: imageType, image_size: imageSize, image_format: imageFormat, provider: service, model: model, }; // fetchAPI extracts data from unified format {success: true, data: {...}} // So data is the extracted response payload const data = await fetchAPI('/v1/system/settings/integrations/image_generation/generate/', { method: 'POST', body: JSON.stringify(requestBody), }); // fetchAPI extracts data from unified format, so data is the response payload // If fetchAPI didn't throw, the request was successful if (!data || typeof data !== 'object') { throw new Error('Invalid response format'); } const imageData = { url: data.image_url, revised_prompt: data.revised_prompt, model: data.model || model, provider: data.provider || service, size: imageSize, format: imageFormat.toUpperCase(), cost: data.cost, }; setGeneratedImage(imageData); // Emit custom event for ImageResultCard to listen to window.dispatchEvent( new CustomEvent('imageGenerated', { detail: imageData, }) ); toast.success('Image generated successfully!'); } catch (err: any) { console.error('[ImageGenerationCard] Error in handleGenerate:', { error: err, message: err.message, stack: err.stack, }); const errorMessage = err.message || 'Failed to generate image'; setError(errorMessage); // Emit error event for ImageResultCard window.dispatchEvent( new CustomEvent('imageGenerationError', { detail: errorMessage, }) ); toast.error(errorMessage); } finally { console.log('[ImageGenerationCard] handleGenerate completed'); setIsGenerating(false); } }; // Get display name for provider and model const getProviderDisplay = () => { const service = imageSettings.service || 'openai'; if (service === 'openai') { const model = imageSettings.model || 'dall-e-3'; const modelNames: Record = { 'dall-e-3': 'DALL·E 3', 'dall-e-2': 'DALL·E 2', 'gpt-image-1': 'GPT Image 1 (Full)', 'gpt-image-1-mini': 'GPT Image 1 Mini', }; return `OpenAI ${modelNames[model] || model}`; } else { return 'Runware'; } }; // Image size options - dynamically generated based on selected model const sizeLabels: Record = { '1024x1024': 'Square - 1024 x 1024', '1024x1792': 'Portrait - 1024 x 1792', '1792x1024': 'Landscape - 1792 x 1024', '256x256': 'Small - 256 x 256', '512x512': 'Medium - 512 x 512', }; const sizeOptions = getValidSizes().map(size => ({ value: size, label: sizeLabels[size] || size, })); // Image type options const typeOptions = [ { value: 'realistic', label: 'Realistic' }, { value: 'illustration', label: 'Illustration' }, { value: '3D render', label: '3D Render' }, { value: 'minimalist', label: 'Minimalist' }, { value: 'cartoon', label: 'Cartoon' }, ]; // Format options const formatOptions = [ { value: 'webp', label: 'WEBP' }, { value: 'jpg', label: 'JPG' }, { value: 'png', label: 'PNG' }, ]; return (
{icon && (
{icon}
)}

{title}

{description && (

{description}

)}
{/* API Provider and Model Display */}

Provider & Model

{getProviderDisplay()}

{/* Prompt Description - Full Width */}