import React, { useState, useRef, useCallback } from 'react'; import { Button } from './components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from './components/ui/card'; import { Textarea } from './components/ui/textarea'; import { Badge } from './components/ui/badge'; import { Separator } from './components/ui/separator'; import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from './components/ui/sheet'; import { ScrollArea } from './components/ui/scroll-area'; import { Alert, AlertDescription } from './components/ui/alert'; import { Tabs, TabsContent, TabsList, TabsTrigger } from './components/ui/tabs'; import { Play, Download, Code2, FileText, Loader2, AlertCircle, Eye, Sparkles, Terminal } from 'lucide-react'; import { toast, Toaster } from 'sonner'; import './App.css'; const BACKEND_URL = process.env.REACT_APP_BACKEND_URL || 'http://localhost:8001'; export default function App() { const [prompt, setPrompt] = useState(''); const [output, setOutput] = useState(''); const [isGenerating, setIsGenerating] = useState(false); const [parsedFiles, setParsedFiles] = useState({}); const [error, setError] = useState(''); const [previewUrl, setPreviewUrl] = useState(''); const eventSourceRef = useRef(null); const createPreviewUrl = useCallback((files) => { console.log('🔍 Creating preview for files:', Object.keys(files)); if (!files || Object.keys(files).length === 0) { console.log('❌ No files provided for preview'); return ''; } try { // Check if this is a React project const hasReactFiles = Object.keys(files).some(name => name.endsWith('.jsx') || name.endsWith('.tsx') ); console.log('📱 Has React files:', hasReactFiles); if (hasReactFiles) { const previewUrl = createReactPreview(files); console.log('⚛️ React preview URL created:', !!previewUrl); return previewUrl; } // Handle HTML files const htmlFile = files['index.html'] || Object.values(files).find(content => typeof content === 'string' && content.includes('') ); if (htmlFile) { console.log('🌐 Processing HTML file'); let htmlContent = htmlFile; // Inject all CSS files const cssFiles = Object.entries(files).filter(([name]) => name.endsWith('.css')); if (cssFiles.length > 0) { console.log('🎨 Injecting CSS files:', cssFiles.map(([name]) => name)); const allCSS = cssFiles.map(([, content]) => content).join('\n\n'); htmlContent = htmlContent.replace( '', `\n` ); } const blob = new Blob([htmlContent], { type: 'text/html' }); const url = URL.createObjectURL(blob); console.log('✅ HTML preview URL created successfully'); return url; } console.log('❌ No valid HTML or React files found for preview'); return ''; } catch (error) { console.error('❌ Error creating preview:', error); toast.error(`Preview generation failed: ${error.message}`); return ''; } }, []); const createReactPreview = (files) => { console.log('⚛️ Creating React preview...'); try { // Collect all CSS files const cssFiles = Object.entries(files).filter(([name]) => name.endsWith('.css')); const allCSS = cssFiles.map(([, content]) => content).join('\n\n'); console.log('🎨 Combined CSS length:', allCSS.length); // Collect all JSX files const jsxFiles = Object.entries(files).filter(([name]) => name.endsWith('.jsx') || name.endsWith('.tsx') || name.endsWith('.js') ); console.log('📄 JSX files found:', jsxFiles.map(([name]) => name)); if (jsxFiles.length === 0) { console.log('❌ No JSX files found'); return ''; } // Get all component code const allComponents = jsxFiles.map(([filename, content]) => { const componentName = filename.split('/').pop().replace(/\.(jsx|tsx|js)$/, '') || 'Component'; // Simple cleanup - just remove imports and exports let cleanContent = content .replace(/^import\s+.*$/gm, '') // Remove imports .replace(/^export\s+default\s+/gm, '') // Remove export default .replace(/^export\s*\{[^}]*\}/gm, '') // Remove named exports .trim(); return cleanContent; }).join('\n\n'); // Create a simplified React preview with better error handling const htmlWrapper = ` React Preview
`; console.log('📦 Created HTML wrapper, length:', htmlWrapper.length); const blob = new Blob([htmlWrapper], { type: 'text/html' }); const url = URL.createObjectURL(blob); console.log('✅ React preview URL created successfully'); return url; } catch (error) { console.error('❌ Error in createReactPreview:', error); toast.error(`React preview failed: ${error.message}`); return ''; } }; const handleGenerate = async () => { if (!prompt.trim()) { toast.error('Please enter a prompt'); return; } setIsGenerating(true); setOutput(''); setParsedFiles({}); setError(''); setPreviewUrl(''); try { const response = await fetch(`${BACKEND_URL}/api/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ prompt }), }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { try { const data = JSON.parse(line.slice(6)); if (data.error) { setError(`${data.error}: ${data.details || 'Unknown error'}`); toast.error(data.error); continue; } if (data.type === 'content') { setOutput(data.full_content || ''); } else if (data.type === 'complete') { setParsedFiles(data.files || {}); if (data.files && Object.keys(data.files).length > 0) { const url = createPreviewUrl(data.files); setPreviewUrl(url); toast.success(`Generated ${Object.keys(data.files).length} file(s)!`); } } } catch (e) { console.error('Error parsing SSE data:', e); } } } } } catch (error) { console.error('Generation error:', error); setError(`Connection error: ${error.message}`); toast.error('Failed to generate code. Please try again.'); } finally { setIsGenerating(false); } }; const handleDownload = async () => { if (Object.keys(parsedFiles).length === 0) { toast.error('No files to download'); return; } try { const response = await fetch(`${BACKEND_URL}/api/download`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(parsedFiles), }); if (!response.ok) { throw new Error('Download failed'); } const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'generated-code.zip'; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); toast.success('Files downloaded successfully!'); } catch (error) { console.error('Download error:', error); toast.error('Failed to download files'); } }; const getFileIcon = (filename) => { if (filename.endsWith('.html')) return ; if (filename.endsWith('.jsx') || filename.endsWith('.tsx')) return ; if (filename.endsWith('.js')) return ; if (filename.endsWith('.css')) return ; return ; }; const getLanguageFromFilename = (filename) => { if (filename.endsWith('.html')) return 'html'; if (filename.endsWith('.jsx')) return 'jsx'; if (filename.endsWith('.tsx')) return 'tsx'; if (filename.endsWith('.css')) return 'css'; if (filename.endsWith('.js')) return 'javascript'; return 'text'; }; const copyToClipboard = async (content, filename) => { try { await navigator.clipboard.writeText(content); toast.success(`${filename} copied to clipboard!`); } catch (error) { console.error('Failed to copy to clipboard:', error); toast.error('Failed to copy to clipboard'); } }; return (
{/* Header */}

AI Coding Playground

Generate clean, production-ready code with live preview and instant download. Powered by Qwen3 Coder via OpenRouter.

{/* Input Section */}
Code Generation