'use client'; import { useState, useRef, useEffect, KeyboardEvent } from 'react'; import { MinimizedApiKey } from '@/components/demo/MinimizedApiKey'; import { GenerationTimer } from '@/components/demo/GenerationTimer'; import { ResultCard } from '@/components/demo/ResultCard'; import { AdvancedOptionsModal, AdvancedOptionsData } from '@/components/demo/AdvancedOptionsModal'; const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000'; const API_KEY_STORAGE_KEY = 'banatie_demo_api_key'; // Generate random 6-character uppercase ID for pairing images function generatePairId(): string { return Math.random().toString(36).substring(2, 8).toUpperCase(); } interface GenerationResult { id: string; timestamp: Date; originalPrompt: string; enhancedPrompt?: string; leftImage: { url: string; width: number; height: number; error?: string; } | null; rightImage: { url: string; width: number; height: number; error?: string; } | null; durationMs?: number; leftData?: { request: object; response: object; geminiParams: object; }; rightData?: { request: object; response: object; geminiParams: object; }; enhancementOptions?: { template?: string; meta?: { tags?: string[]; }; } & AdvancedOptionsData; } interface ApiKeyInfo { organizationSlug?: string; projectSlug?: string; } export default function DemoTTIPage() { // API Key State const [apiKey, setApiKey] = useState(''); const [apiKeyVisible, setApiKeyVisible] = useState(false); const [apiKeyValidated, setApiKeyValidated] = useState(false); const [apiKeyInfo, setApiKeyInfo] = useState(null); const [apiKeyError, setApiKeyError] = useState(''); const [validatingKey, setValidatingKey] = useState(false); // Prompt State const [prompt, setPrompt] = useState(''); const [generating, setGenerating] = useState(false); const [generationStartTime, setGenerationStartTime] = useState(); const [generationError, setGenerationError] = useState(''); // Enhancement Options State const [aspectRatio, setAspectRatio] = useState('1:1'); const [template, setTemplate] = useState('photorealistic'); const [advancedOptions, setAdvancedOptions] = useState({}); const [showAdvancedModal, setShowAdvancedModal] = useState(false); // Results State const [results, setResults] = useState([]); // Modal State const [zoomedImage, setZoomedImage] = useState(null); const textareaRef = useRef(null); // Load API key from localStorage on mount useEffect(() => { const storedApiKey = localStorage.getItem(API_KEY_STORAGE_KEY); if (storedApiKey) { setApiKey(storedApiKey); // Auto-validate the stored key validateStoredApiKey(storedApiKey); } }, []); // Validate stored API key (without user interaction) const validateStoredApiKey = async (keyToValidate: string) => { setValidatingKey(true); setApiKeyError(''); try { const response = await fetch(`${API_BASE_URL}/api/info`, { headers: { 'X-API-Key': keyToValidate, }, }); if (response.ok) { const data = await response.json(); setApiKeyValidated(true); if (data.keyInfo) { setApiKeyInfo({ organizationSlug: data.keyInfo.organizationSlug || data.keyInfo.organizationId, projectSlug: data.keyInfo.projectSlug || data.keyInfo.projectId, }); } else { setApiKeyInfo({ organizationSlug: 'Unknown', projectSlug: 'Unknown', }); } } else { // Stored key is invalid, clear it localStorage.removeItem(API_KEY_STORAGE_KEY); setApiKeyError('Stored API key is invalid or expired'); setApiKeyValidated(false); } } catch (error) { setApiKeyError('Failed to validate stored API key'); setApiKeyValidated(false); } finally { setValidatingKey(false); } }; // Validate API Key const validateApiKey = async () => { if (!apiKey.trim()) { setApiKeyError('Please enter an API key'); return; } setValidatingKey(true); setApiKeyError(''); try { // Test API key with a minimal request to /api/info or similar const response = await fetch(`${API_BASE_URL}/api/info`, { headers: { 'X-API-Key': apiKey, }, }); if (response.ok) { const data = await response.json(); setApiKeyValidated(true); // Save to localStorage on successful validation localStorage.setItem(API_KEY_STORAGE_KEY, apiKey); // Extract org/project info from API response if (data.keyInfo) { setApiKeyInfo({ organizationSlug: data.keyInfo.organizationSlug || data.keyInfo.organizationId, projectSlug: data.keyInfo.projectSlug || data.keyInfo.projectId, }); } else { setApiKeyInfo({ organizationSlug: 'Unknown', projectSlug: 'Unknown', }); } } else { const error = await response.json(); setApiKeyError(error.message || 'Invalid API key'); setApiKeyValidated(false); } } catch (error) { setApiKeyError('Failed to validate API key. Please check your connection.'); setApiKeyValidated(false); } finally { setValidatingKey(false); } }; // Revoke API Key const revokeApiKey = () => { // Clear localStorage localStorage.removeItem(API_KEY_STORAGE_KEY); // Clear state setApiKey(''); setApiKeyValidated(false); setApiKeyInfo(null); setApiKeyError(''); }; // Generate Images const generateImages = async () => { if (!prompt.trim()) { setGenerationError('Please enter a prompt'); return; } setGenerating(true); setGenerationError(''); const startTime = Date.now(); setGenerationStartTime(startTime); const resultId = Date.now().toString(); const pairId = generatePairId(); // NEW: Generate unique pair ID const timestamp = new Date(); try { // Call API twice in parallel // Left: original prompt WITHOUT enhancement (autoEnhance: false) // Right: original prompt WITH enhancement (autoEnhance: true + template) const [leftResult, rightResult] = await Promise.all([ fetch(`${API_BASE_URL}/api/text-to-image`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': apiKey, }, body: JSON.stringify({ prompt: prompt.trim(), filename: `demo_${resultId}_left`, aspectRatio, autoEnhance: false, // Explicitly disable enhancement for left image meta: { tags: [pairId, 'simple'], // NEW: Pair ID + "simple" tag }, }), }), fetch(`${API_BASE_URL}/api/text-to-image`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': apiKey, }, body: JSON.stringify({ prompt: prompt.trim(), filename: `demo_${resultId}_right`, aspectRatio, autoEnhance: true, // Enable enhancement for right image enhancementOptions: { template: template || 'photorealistic', // Only template parameter }, meta: { tags: [pairId, 'enhanced'], // NEW: Pair ID + "enhanced" tag }, }), }), ]); const leftData = await leftResult.json(); const rightData = await rightResult.json(); const endTime = Date.now(); const durationMs = endTime - startTime; // Create result object const newResult: GenerationResult = { id: resultId, timestamp, originalPrompt: prompt.trim(), enhancedPrompt: rightData.data?.promptEnhancement?.enhancedPrompt, leftImage: leftData.success ? { url: leftData.data.url || leftData.data.filepath, width: 1024, // Default, would come from API height: 1024, } : null, rightImage: rightData.success ? { url: rightData.data.url || rightData.data.filepath, width: 1024, height: 1024, } : null, durationMs, // Store full request/response data for inspect mode leftData: { request: { prompt: prompt.trim(), filename: `demo_${resultId}_left`, aspectRatio, autoEnhance: false, meta: { tags: [pairId, 'simple'], }, }, response: leftData, geminiParams: leftData.data?.geminiParams || {}, }, rightData: { request: { prompt: prompt.trim(), filename: `demo_${resultId}_right`, aspectRatio, autoEnhance: true, enhancementOptions: { template: template || 'photorealistic', }, meta: { tags: [pairId, 'enhanced'], }, }, response: rightData, geminiParams: rightData.data?.geminiParams || {}, }, // Store enhancement options for display in inspect mode enhancementOptions: { template, meta: { tags: [pairId, 'enhanced'], }, }, }; if (!leftData.success) { newResult.leftImage = { url: '', width: 0, height: 0, error: leftData.error }; } if (!rightData.success) { newResult.rightImage = { url: '', width: 0, height: 0, error: rightData.error }; } // Add to results at the top setResults((prev) => [newResult, ...prev]); // Clear prompt setPrompt(''); } catch (error) { setGenerationError(error instanceof Error ? error.message : 'Failed to generate images'); } finally { setGenerating(false); setGenerationStartTime(undefined); } }; // Handle Ctrl+Enter const handleKeyDown = (e: KeyboardEvent) => { if (e.ctrlKey && e.key === 'Enter') { e.preventDefault(); generateImages(); } }; // Copy to clipboard const copyToClipboard = (text: string) => { navigator.clipboard.writeText(text); }; // Download image const downloadImage = async (url: string, filename: string) => { try { const response = await fetch(url); const blob = await response.blob(); const blobUrl = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = blobUrl; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(blobUrl); } catch (error) { console.error('Download failed:', error); } }; // Reuse prompt const reusePrompt = (promptText: string) => { setPrompt(promptText); textareaRef.current?.focus(); }; return (
{/* Minimized API Key Badge */} {apiKeyValidated && apiKeyInfo && ( )} {/* Page Header */}

Text-to-Image Workbench

Developer tool for API testing and prompt engineering

{/* API Key Section - Only show when not validated */} {!apiKeyValidated && (

API Key

setApiKey(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); validateApiKey(); } }} placeholder="Enter your API key" className="w-full px-4 py-3 bg-slate-800 border border-slate-700 rounded-lg text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:border-transparent pr-12" aria-label="API key input" />
{apiKeyError && (

{apiKeyError}

)}
)} {/* Unified Prompt & Generation Card */}
{/* Prompt Textarea */}