import React, { useState, useCallback, useEffect } from 'react'; import { AppStatus, Provider, AppType } from './types'; import { generateCode } from './services/geminiService'; import ImageUploader from './components/ImageUploader'; import CodeDisplay from './components/CodeDisplay'; import Loader from './components/Loader'; import PythonIcon from './components/icons/PythonIcon'; import CppIcon from './components/icons/CppIcon'; import SparklesIcon from './components/icons/SparklesIcon'; import ApiConfig from './components/ApiConfig'; import AppTypeSelector from './components/AppTypeSelector'; const App: React.FC = () => { const [status, setStatus] = useState(AppStatus.IDLE); const [appType, setAppType] = useState(null); const [generatedCode, setGeneratedCode] = useState(''); const [generatedFiles, setGeneratedFiles] = useState | null>(null); const [errorMessage, setErrorMessage] = useState(''); const [imageFiles, setImageFiles] = useState([]); const [imagePreviewUrls, setImagePreviewUrls] = useState([]); const [instructions, setInstructions] = useState(''); const [loadingMessage, setLoadingMessage] = useState(''); const [provider, setProvider] = useState('gemini'); const [geminiApiKey, setGeminiApiKey] = useState(''); const [openaiApiKey, setOpenaiApiKey] = useState(''); const [anthropicApiKey, setAnthropicApiKey] = useState(''); useEffect(() => { // Load keys and provider from localStorage on initial render. const savedGeminiKey = localStorage.getItem('geminiApiKey'); const savedOpenaiKey = localStorage.getItem('openaiApiKey'); const savedAnthropicKey = localStorage.getItem('anthropicApiKey'); const savedProvider = localStorage.getItem('provider') as Provider; if (savedGeminiKey) setGeminiApiKey(savedGeminiKey); if (savedOpenaiKey) setOpenaiApiKey(savedOpenaiKey); if (savedAnthropicKey) setAnthropicApiKey(savedAnthropicKey); if (savedProvider) setProvider(savedProvider); // Effect to clean up object URLs return () => { imagePreviewUrls.forEach(url => URL.revokeObjectURL(url)); }; }, []); // Run only once on mount const resetStateForNewRun = () => { setGeneratedCode(''); setGeneratedFiles(null); setErrorMessage(''); setStatus(AppStatus.IDLE); } const handleProviderChange = (newProvider: Provider) => { setProvider(newProvider); localStorage.setItem('provider', newProvider); }; const handleApiKeyChange = (keyType: Provider, key: string) => { switch (keyType) { case 'gemini': setGeminiApiKey(key); localStorage.setItem('geminiApiKey', key); break; case 'openai': setOpenaiApiKey(key); localStorage.setItem('openaiApiKey', key); break; case 'anthropic': setAnthropicApiKey(key); localStorage.setItem('anthropicApiKey', key); break; } }; const fileToBase64 = (file: File): Promise<{mimeType: string, data: string}> => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onload = () => { const result = reader.result as string; const [header, data] = result.split(','); const mimeType = header.match(/:(.*?);/)?.[1] || 'application/octet-stream'; resolve({ mimeType, data }); }; reader.onerror = error => reject(error); }); }; const handleImageUpload = (newFiles: File[]) => { const filesToUpload = newFiles.slice(0, 10); setImageFiles(filesToUpload); // Revoke old URLs before creating new ones to prevent memory leaks imagePreviewUrls.forEach(url => URL.revokeObjectURL(url)); setImagePreviewUrls(filesToUpload.map(file => URL.createObjectURL(file))); resetStateForNewRun(); }; const handleRemoveImage = (indexToRemove: number) => { const updatedFiles = imageFiles.filter((_, index) => index !== indexToRemove); URL.revokeObjectURL(imagePreviewUrls[indexToRemove]); const updatedUrls = imagePreviewUrls.filter((_, index) => index !== indexToRemove); setImageFiles(updatedFiles); setImagePreviewUrls(updatedUrls); } const handleGenerateClick = useCallback(async () => { if (!appType) return; const apiKeys = { gemini: geminiApiKey, openai: openaiApiKey, anthropic: anthropicApiKey }; const apiKey = apiKeys[provider]; if (!apiKey) { setErrorMessage(`Please provide an API key for ${provider.charAt(0).toUpperCase() + provider.slice(1)}.`); setStatus(AppStatus.ERROR); return; } if (imageFiles.length === 0) { setErrorMessage('Please upload at least one image first.'); setStatus(AppStatus.ERROR); return; } setStatus(AppStatus.PROCESSING); setErrorMessage(''); setGeneratedCode(''); setGeneratedFiles(null); setLoadingMessage('Preparing to generate...'); try { const imageParts = await Promise.all(imageFiles.map(file => fileToBase64(file))); const result = await generateCode( appType, provider, apiKey, imageParts, instructions, (statusMsg) => setLoadingMessage(statusMsg), (chunk) => { if (appType === 'python') { setGeneratedCode(prev => prev + chunk); } else { // For C++, we accumulate the JSON string representation setGeneratedCode(prev => prev + chunk); } } ); if(appType === 'c++') { try { const finalJsonString = result as string; // Clean potential markdown fences const fenceRegex = /^```(json)?\s*\n?(.*?)\n?\s*```$/s; const match = finalJsonString.match(fenceRegex); const cleanedJson = match ? match[2] : finalJsonString; const files = JSON.parse(cleanedJson); setGeneratedFiles(files); setGeneratedCode(''); // Clear the raw string } catch(e) { console.error("Failed to parse C++ file JSON:", e); throw new Error("AI returned an invalid format for C++ files. Please try again."); } } setStatus(AppStatus.SUCCESS); } catch (error) { console.error(error); setErrorMessage(error instanceof Error ? error.message : 'An unknown error occurred.'); setStatus(AppStatus.ERROR); } }, [appType, imageFiles, instructions, provider, geminiApiKey, openaiApiKey, anthropicApiKey]); if (!appType) { return ; } const OutputArea: React.FC = () => { switch (status) { case AppStatus.PROCESSING: return (

{loadingMessage || 'Initializing...'}

Using {provider} to analyze UI and generate code.

{ appType === 'python' && generatedCode &&
                
                  {generatedCode}
                
              
}
); case AppStatus.SUCCESS: return ; case AppStatus.ERROR: return (

Generation Failed

{errorMessage}

); case AppStatus.IDLE: default: return (
{appType === 'python' ? : }

Functional Code Output

Configure your API, upload UI images, and provide instructions to generate a functional {appType === 'python' ? 'Python' : 'C++'} script.

); } }; const isApiKeyMissing = !( (provider === 'gemini' && geminiApiKey) || (provider === 'openai' && openaiApiKey) || (provider === 'anthropic' && anthropicApiKey) ); return (

Functional {appType === 'python' ? 'PyQt' : 'C++ Qt'} App Generator

Turn UI Images into Functional {appType === 'python' ? 'Python' : 'C++'} Apps with AI

1. Upload UI Images

{imagePreviewUrls.length > 0 && (

Image Previews ({imagePreviewUrls.length}/10)

{imagePreviewUrls.map((url, index) => (
{`UI
))}
)}

2. Add Instructions (Optional)