diff --git a/app/components/Chat.tsx b/app/components/Chat.tsx new file mode 100644 index 00000000..1ec128c0 --- /dev/null +++ b/app/components/Chat.tsx @@ -0,0 +1,9 @@ +import { useChat } from '@/hooks/useChat'; + +export const Chat = () => { + const { chatMessages, addChatMessage, clearChatHistory } = useChat(); + + // ... render chat messages and input + + return
...
; +}; \ No newline at end of file diff --git a/app/components/CodeDisplay.tsx b/app/components/CodeDisplay.tsx new file mode 100644 index 00000000..381c9528 --- /dev/null +++ b/app/components/CodeDisplay.tsx @@ -0,0 +1,9 @@ +import { useCodeGeneration } from '@/hooks/useCodeGeneration'; + +export const CodeDisplay = () => { + const { generationProgress } = useCodeGeneration(); + + // ... render code display and file explorer + + return
...
; +}; \ No newline at end of file diff --git a/app/components/SandboxPreview.tsx b/app/components/SandboxPreview.tsx new file mode 100644 index 00000000..b3feb13c --- /dev/null +++ b/app/components/SandboxPreview.tsx @@ -0,0 +1,9 @@ +import { useSandbox } from '@/hooks/useSandbox'; + +export const SandboxPreview = () => { + const { sandboxData } = useSandbox(); + + // ... render sandbox preview iframe and screenshot + + return
...
; +}; \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx index dfe0d897..8ee817af 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,65 +1,46 @@ -'use client'; - import { useState, useEffect, useRef } from 'react'; import { useSearchParams, useRouter } from 'next/navigation'; import { appConfig } from '@/config/app.config'; import { Button } from '@/components/ui/button'; import { Textarea } from '@/components/ui/textarea'; -import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; -import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism'; -// Import icons from centralized module to avoid Turbopack chunk issues -import { - FiFile, - FiChevronRight, - FiChevronDown, - FiGithub, - BsFolderFill, - BsFolder2Open, - SiJavascript, - SiReact, - SiCss3, - SiJson -} from '@/lib/icons'; import { motion, AnimatePresence } from 'framer-motion'; -import CodeApplicationProgress, { type CodeApplicationState } from '@/components/CodeApplicationProgress'; +import CodeApplicationProgress from '@/components/CodeApplicationProgress'; -interface SandboxData { - sandboxId: string; - url: string; - [key: string]: any; -} +import { useSandbox } from '@/hooks/useSandbox'; +import { useChat } from '@/hooks/useChat'; +import { useCodeGeneration } from '@/hooks/useCodeGeneration'; -interface ChatMessage { - content: string; - type: 'user' | 'ai' | 'system' | 'file-update' | 'command' | 'error'; - timestamp: Date; - metadata?: { - scrapedUrl?: string; - scrapedContent?: any; - generatedCode?: string; - appliedFiles?: string[]; - commandType?: 'input' | 'output' | 'error' | 'success'; - }; -} +import { Chat } from '@/app/components/Chat'; +import { CodeDisplay } from '@/app/components/CodeDisplay'; +import { SandboxPreview } from '@/app/components/SandboxPreview'; export default function AISandboxPage() { - const [sandboxData, setSandboxData] = useState(null); - const [loading, setLoading] = useState(false); - const [status, setStatus] = useState({ text: 'Not connected', active: false }); - const [responseArea, setResponseArea] = useState([]); - const [structureContent, setStructureContent] = useState('No sandbox created yet'); + const searchParams = useSearchParams(); + const router = useRouter(); + + const { + sandboxData, + status, + structureContent, + updateStatus, + createSandbox, + checkSandboxStatus, + fetchSandboxFiles, + setSandboxData, + setStructureContent + } = useSandbox(); + const { chatMessages, conversationContext, addChatMessage, clearChatHistory, setConversationContext } = useChat(); + const { + generationProgress, + codeApplicationState, + setGenerationProgress, + setCodeApplicationState, + applyGeneratedCode + } = useCodeGeneration(); + const [promptInput, setPromptInput] = useState(''); - const [chatMessages, setChatMessages] = useState([ - { - content: 'Welcome! I can help you generate code with full context of your sandbox files and structure. Just start chatting - I\'ll automatically create a sandbox for you if needed!\n\nTip: If you see package errors like "react-router-dom not found", just type "npm install" or "check packages" to automatically install missing packages.', - type: 'system', - timestamp: new Date() - } - ]); const [aiChatInput, setAiChatInput] = useState(''); const [aiEnabled] = useState(true); - const searchParams = useSearchParams(); - const router = useRouter(); const [aiModel, setAiModel] = useState(() => { const modelParam = searchParams.get('model'); return appConfig.ai.availableModels.includes(modelParam || '') ? modelParam! : appConfig.ai.defaultModel; @@ -85,54 +66,9 @@ export default function AISandboxPage() { const [loadingStage, setLoadingStage] = useState<'gathering' | 'planning' | 'generating' | null>(null); const [sandboxFiles, setSandboxFiles] = useState>({}); const [fileStructure, setFileStructure] = useState(''); - - const [conversationContext, setConversationContext] = useState<{ - scrapedWebsites: Array<{ url: string; content: any; timestamp: Date }>; - generatedComponents: Array<{ name: string; path: string; content: string }>; - appliedCode: Array<{ files: string[]; timestamp: Date }>; - currentProject: string; - lastGeneratedCode?: string; - }>({ - scrapedWebsites: [], - generatedComponents: [], - appliedCode: [], - currentProject: '', - lastGeneratedCode: undefined - }); - - const iframeRef = useRef(null); + const chatMessagesRef = useRef(null); const codeDisplayRef = useRef(null); - - const [codeApplicationState, setCodeApplicationState] = useState({ - stage: null - }); - - const [generationProgress, setGenerationProgress] = useState<{ - isGenerating: boolean; - status: string; - components: Array<{ name: string; path: string; completed: boolean }>; - currentComponent: number; - streamedCode: string; - isStreaming: boolean; - isThinking: boolean; - thinkingText?: string; - thinkingDuration?: number; - currentFile?: { path: string; content: string; type: string }; - files: Array<{ path: string; content: string; type: string; completed: boolean }>; - lastProcessedPosition: number; - isEdit?: boolean; - }>({ - isGenerating: false, - status: '', - components: [], - currentComponent: 0, - streamedCode: '', - isStreaming: false, - isThinking: false, - files: [], - lastProcessedPosition: 0 - }); // Clear old conversation data on component mount and create/restore sandbox useEffect(() => { @@ -237,26 +173,10 @@ export default function AISandboxPage() { }, [chatMessages]); - const updateStatus = (text: string, active: boolean) => { - setStatus({ text, active }); - }; - const log = (message: string, type: 'info' | 'error' | 'command' = 'info') => { setResponseArea(prev => [...prev, `[${type}] ${message}`]); }; - const addChatMessage = (content: string, type: ChatMessage['type'], metadata?: ChatMessage['metadata']) => { - setChatMessages(prev => { - // Skip duplicate consecutive system messages - if (type === 'system' && prev.length > 0) { - const lastMessage = prev[prev.length - 1]; - if (lastMessage.type === 'system' && lastMessage.content === content) { - return prev; // Skip duplicate - } - } - return [...prev, { content, type, timestamp: new Date(), metadata }]; - }); - }; const checkAndInstallPackages = async () => { if (!sandboxData) { @@ -346,117 +266,6 @@ export default function AISandboxPage() { } }; - const checkSandboxStatus = async () => { - try { - const response = await fetch('/api/sandbox-status'); - const data = await response.json(); - - if (data.active && data.healthy && data.sandboxData) { - setSandboxData(data.sandboxData); - updateStatus('Sandbox active', true); - } else if (data.active && !data.healthy) { - // Sandbox exists but not responding - updateStatus('Sandbox not responding', false); - // Optionally try to create a new one - } else { - setSandboxData(null); - updateStatus('No sandbox', false); - } - } catch (error) { - console.error('Failed to check sandbox status:', error); - setSandboxData(null); - updateStatus('Error', false); - } - }; - - const createSandbox = async (fromHomeScreen = false) => { - console.log('[createSandbox] Starting sandbox creation...'); - setLoading(true); - setShowLoadingBackground(true); - updateStatus('Creating sandbox...', false); - setResponseArea([]); - setScreenshotError(null); - - try { - const response = await fetch('/api/create-ai-sandbox', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({}) - }); - - const data = await response.json(); - console.log('[createSandbox] Response data:', data); - - if (data.success) { - setSandboxData(data); - updateStatus('Sandbox active', true); - log('Sandbox created successfully!'); - log(`Sandbox ID: ${data.sandboxId}`); - log(`URL: ${data.url}`); - - // Update URL with sandbox ID - const newParams = new URLSearchParams(searchParams.toString()); - newParams.set('sandbox', data.sandboxId); - newParams.set('model', aiModel); - router.push(`/?${newParams.toString()}`, { scroll: false }); - - // Fade out loading background after sandbox loads - setTimeout(() => { - setShowLoadingBackground(false); - }, 3000); - - if (data.structure) { - displayStructure(data.structure); - } - - // Fetch sandbox files after creation - setTimeout(fetchSandboxFiles, 1000); - - // Restart Vite server to ensure it's running - setTimeout(async () => { - try { - console.log('[createSandbox] Ensuring Vite server is running...'); - const restartResponse = await fetch('/api/restart-vite', { - method: 'POST', - headers: { 'Content-Type': 'application/json' } - }); - - if (restartResponse.ok) { - const restartData = await restartResponse.json(); - if (restartData.success) { - console.log('[createSandbox] Vite server started successfully'); - } - } - } catch (error) { - console.error('[createSandbox] Error starting Vite server:', error); - } - }, 2000); - - // Only add welcome message if not coming from home screen - if (!fromHomeScreen) { - addChatMessage(`Sandbox created! ID: ${data.sandboxId}. I now have context of your sandbox and can help you build your app. Just ask me to create components and I'll automatically apply them! - -Tip: I automatically detect and install npm packages from your code imports (like react-router-dom, axios, etc.)`, 'system'); - } - - setTimeout(() => { - if (iframeRef.current) { - iframeRef.current.src = data.url; - } - }, 100); - } else { - throw new Error(data.error || 'Unknown error'); - } - } catch (error: any) { - console.error('[createSandbox] Error:', error); - updateStatus('Error', false); - log(`Failed to create sandbox: ${error.message}`, 'error'); - addChatMessage(`Failed to create sandbox: ${error.message}`, 'system'); - } finally { - setLoading(false); - } - }; - const displayStructure = (structure: any) => { if (typeof structure === 'object') { setStructureContent(JSON.stringify(structure, null, 2)); @@ -465,456 +274,6 @@ Tip: I automatically detect and install npm packages from your code imports (lik } }; - const applyGeneratedCode = async (code: string, isEdit: boolean = false) => { - setLoading(true); - log('Applying AI-generated code...'); - - try { - // Show progress component instead of individual messages - setCodeApplicationState({ stage: 'analyzing' }); - - // Get pending packages from tool calls - const pendingPackages = ((window as any).pendingPackages || []).filter((pkg: any) => pkg && typeof pkg === 'string'); - if (pendingPackages.length > 0) { - console.log('[applyGeneratedCode] Sending packages from tool calls:', pendingPackages); - // Clear pending packages after use - (window as any).pendingPackages = []; - } - - // Use streaming endpoint for real-time feedback - const response = await fetch('/api/apply-ai-code-stream', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - response: code, - isEdit: isEdit, - packages: pendingPackages, - sandboxId: sandboxData?.sandboxId // Pass the sandbox ID to ensure proper connection - }) - }); - - if (!response.ok) { - throw new Error(`Failed to apply code: ${response.statusText}`); - } - - // Handle streaming response - const reader = response.body?.getReader(); - const decoder = new TextDecoder(); - let finalData: any = null; - - while (reader) { - 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)); - - switch (data.type) { - case 'start': - // Don't add as chat message, just update state - setCodeApplicationState({ stage: 'analyzing' }); - break; - - case 'step': - // Update progress state based on step - if (data.message.includes('Installing') && data.packages) { - setCodeApplicationState({ - stage: 'installing', - packages: data.packages - }); - } else if (data.message.includes('Creating files') || data.message.includes('Applying')) { - setCodeApplicationState({ - stage: 'applying', - filesGenerated: results.filesCreated - }); - } - break; - - case 'package-progress': - // Handle package installation progress - if (data.installedPackages) { - setCodeApplicationState(prev => ({ - ...prev, - installedPackages: data.installedPackages - })); - } - break; - - case 'command': - // Don't show npm install commands - they're handled by info messages - if (data.command && !data.command.includes('npm install')) { - addChatMessage(data.command, 'command', { commandType: 'input' }); - } - break; - - case 'success': - if (data.installedPackages) { - setCodeApplicationState(prev => ({ - ...prev, - installedPackages: data.installedPackages - })); - } - break; - - case 'file-progress': - // Skip file progress messages, they're noisy - break; - - case 'file-complete': - // Could add individual file completion messages if desired - break; - - case 'command-progress': - addChatMessage(`${data.action} command: ${data.command}`, 'command', { commandType: 'input' }); - break; - - case 'command-output': - addChatMessage(data.output, 'command', { - commandType: data.stream === 'stderr' ? 'error' : 'output' - }); - break; - - case 'command-complete': - if (data.success) { - addChatMessage(`Command completed successfully`, 'system'); - } else { - addChatMessage(`Command failed with exit code ${data.exitCode}`, 'system'); - } - break; - - case 'complete': - finalData = data; - setCodeApplicationState({ stage: 'complete' }); - // Clear the state after a delay - setTimeout(() => { - setCodeApplicationState({ stage: null }); - }, 3000); - break; - - case 'error': - addChatMessage(`Error: ${data.message || data.error || 'Unknown error'}`, 'system'); - break; - - case 'warning': - addChatMessage(`${data.message}`, 'system'); - break; - - case 'info': - // Show info messages, especially for package installation - if (data.message) { - addChatMessage(data.message, 'system'); - } - break; - } - } catch (e) { - // Ignore parse errors - } - } - } - } - - // Process final data - if (finalData && finalData.type === 'complete') { - const data = { - success: true, - results: finalData.results, - explanation: finalData.explanation, - structure: finalData.structure, - message: finalData.message - }; - - if (data.success) { - const { results } = data; - - // Log package installation results without duplicate messages - if (results.packagesInstalled?.length > 0) { - log(`Packages installed: ${results.packagesInstalled.join(', ')}`); - } - - if (results.filesCreated?.length > 0) { - log('Files created:'); - results.filesCreated.forEach((file: string) => { - log(` ${file}`, 'command'); - }); - - // Verify files were actually created by refreshing the sandbox if needed - if (sandboxData?.sandboxId && results.filesCreated.length > 0) { - // Small delay to ensure files are written - setTimeout(() => { - // Force refresh the iframe to show new files - if (iframeRef.current) { - iframeRef.current.src = iframeRef.current.src; - } - }, 1000); - } - } - - if (results.filesUpdated?.length > 0) { - log('Files updated:'); - results.filesUpdated.forEach((file: string) => { - log(` ${file}`, 'command'); - }); - } - - // Update conversation context with applied code - setConversationContext(prev => ({ - ...prev, - appliedCode: [...prev.appliedCode, { - files: [...(results.filesCreated || []), ...(results.filesUpdated || [])], - timestamp: new Date() - }] - })); - - if (results.commandsExecuted?.length > 0) { - log('Commands executed:'); - results.commandsExecuted.forEach((cmd: string) => { - log(` $ ${cmd}`, 'command'); - }); - } - - if (results.errors?.length > 0) { - results.errors.forEach((err: string) => { - log(err, 'error'); - }); - } - - if (data.structure) { - displayStructure(data.structure); - } - - if (data.explanation) { - log(data.explanation); - } - - if (data.autoCompleted) { - log('Auto-generating missing components...', 'command'); - - if (data.autoCompletedComponents) { - setTimeout(() => { - log('Auto-generated missing components:', 'info'); - data.autoCompletedComponents.forEach((comp: string) => { - log(` ${comp}`, 'command'); - }); - }, 1000); - } - } else if (data.warning) { - log(data.warning, 'error'); - - if (data.missingImports && data.missingImports.length > 0) { - const missingList = data.missingImports.join(', '); - addChatMessage( - `Ask me to "create the missing components: ${missingList}" to fix these import errors.`, - 'system' - ); - } - } - - log('Code applied successfully!'); - console.log('[applyGeneratedCode] Response data:', data); - console.log('[applyGeneratedCode] Debug info:', data.debug); - console.log('[applyGeneratedCode] Current sandboxData:', sandboxData); - console.log('[applyGeneratedCode] Current iframe element:', iframeRef.current); - console.log('[applyGeneratedCode] Current iframe src:', iframeRef.current?.src); - - if (results.filesCreated?.length > 0) { - setConversationContext(prev => ({ - ...prev, - appliedCode: [...prev.appliedCode, { - files: results.filesCreated, - timestamp: new Date() - }] - })); - - // Update the chat message to show success - // Only show file list if not in edit mode - if (isEdit) { - addChatMessage(`Edit applied successfully!`, 'system'); - } else { - // Check if this is part of a generation flow (has recent AI recreation message) - const recentMessages = chatMessages.slice(-5); - const isPartOfGeneration = recentMessages.some(m => - m.content.includes('AI recreation generated') || - m.content.includes('Code generated') - ); - - // Don't show files if part of generation flow to avoid duplication - if (isPartOfGeneration) { - addChatMessage(`Applied ${results.filesCreated.length} files successfully!`, 'system'); - } else { - addChatMessage(`Applied ${results.filesCreated.length} files successfully!`, 'system', { - appliedFiles: results.filesCreated - }); - } - } - - // If there are failed packages, add a message about checking for errors - if (results.packagesFailed?.length > 0) { - addChatMessage(`⚠️ Some packages failed to install. Check the error banner above for details.`, 'system'); - } - - // Fetch updated file structure - await fetchSandboxFiles(); - - // Automatically check and install any missing packages - await checkAndInstallPackages(); - - // Test build to ensure everything compiles correctly - // Skip build test for now - it's causing errors with undefined activeSandbox - // The build test was trying to access global.activeSandbox from the frontend, - // but that's only available in the backend API routes - console.log('[build-test] Skipping build test - would need API endpoint'); - - // Force iframe refresh after applying code - const refreshDelay = appConfig.codeApplication.defaultRefreshDelay; // Allow Vite to process changes - - setTimeout(() => { - if (iframeRef.current && sandboxData?.url) { - console.log('[home] Refreshing iframe after code application...'); - - // Method 1: Change src with timestamp - const urlWithTimestamp = `${sandboxData.url}?t=${Date.now()}&applied=true`; - iframeRef.current.src = urlWithTimestamp; - - // Method 2: Force reload after a short delay - setTimeout(() => { - try { - if (iframeRef.current?.contentWindow) { - iframeRef.current.contentWindow.location.reload(); - console.log('[home] Force reloaded iframe content'); - } - } catch (e) { - console.log('[home] Could not reload iframe (cross-origin):', e); - } - }, 1000); - } - }, refreshDelay); - - // Vite error checking removed - handled by template setup - } - - // Give Vite HMR a moment to detect changes, then ensure refresh - if (iframeRef.current && sandboxData?.url) { - // Wait for Vite to process the file changes - // If packages were installed, wait longer for Vite to restart - const packagesInstalled = results?.packagesInstalled?.length > 0 || data.results?.packagesInstalled?.length > 0; - const refreshDelay = packagesInstalled ? appConfig.codeApplication.packageInstallRefreshDelay : appConfig.codeApplication.defaultRefreshDelay; - console.log(`[applyGeneratedCode] Packages installed: ${packagesInstalled}, refresh delay: ${refreshDelay}ms`); - - setTimeout(async () => { - if (iframeRef.current && sandboxData?.url) { - console.log('[applyGeneratedCode] Starting iframe refresh sequence...'); - console.log('[applyGeneratedCode] Current iframe src:', iframeRef.current.src); - console.log('[applyGeneratedCode] Sandbox URL:', sandboxData.url); - - // Method 1: Try direct navigation first - try { - const urlWithTimestamp = `${sandboxData.url}?t=${Date.now()}&force=true`; - console.log('[applyGeneratedCode] Attempting direct navigation to:', urlWithTimestamp); - - // Remove any existing onload handler - iframeRef.current.onload = null; - - // Navigate directly - iframeRef.current.src = urlWithTimestamp; - - // Wait a bit and check if it loaded - await new Promise(resolve => setTimeout(resolve, 2000)); - - // Try to access the iframe content to verify it loaded - try { - const iframeDoc = iframeRef.current.contentDocument || iframeRef.current.contentWindow?.document; - if (iframeDoc && iframeDoc.readyState === 'complete') { - console.log('[applyGeneratedCode] Iframe loaded successfully'); - return; - } - } catch (e) { - console.log('[applyGeneratedCode] Cannot access iframe content (CORS), assuming loaded'); - return; - } - } catch (e) { - console.error('[applyGeneratedCode] Direct navigation failed:', e); - } - - // Method 2: Force complete iframe recreation if direct navigation failed - console.log('[applyGeneratedCode] Falling back to iframe recreation...'); - const parent = iframeRef.current.parentElement; - const newIframe = document.createElement('iframe'); - - // Copy attributes - newIframe.className = iframeRef.current.className; - newIframe.title = iframeRef.current.title; - newIframe.allow = iframeRef.current.allow; - // Copy sandbox attributes - const sandboxValue = iframeRef.current.getAttribute('sandbox'); - if (sandboxValue) { - newIframe.setAttribute('sandbox', sandboxValue); - } - - // Remove old iframe - iframeRef.current.remove(); - - // Add new iframe - newIframe.src = `${sandboxData.url}?t=${Date.now()}&recreated=true`; - parent?.appendChild(newIframe); - - // Update ref - (iframeRef as any).current = newIframe; - - console.log('[applyGeneratedCode] Iframe recreated with new content'); - } else { - console.error('[applyGeneratedCode] No iframe or sandbox URL available for refresh'); - } - }, refreshDelay); // Dynamic delay based on whether packages were installed - } - - } else { - throw new Error(finalData?.error || 'Failed to apply code'); - } - } else { - // If no final data was received, still close loading - addChatMessage('Code application may have partially succeeded. Check the preview.', 'system'); - } - } catch (error: any) { - log(`Failed to apply code: ${error.message}`, 'error'); - } finally { - setLoading(false); - // Clear isEdit flag after applying code - setGenerationProgress(prev => ({ - ...prev, - isEdit: false - })); - } - }; - - const fetchSandboxFiles = async () => { - if (!sandboxData) return; - - try { - const response = await fetch('/api/get-sandbox-files', { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - } - }); - - if (response.ok) { - const data = await response.json(); - if (data.success) { - setSandboxFiles(data.files || {}); - setFileStructure(data.structure || ''); - console.log('[fetchSandboxFiles] Updated file list:', Object.keys(data.files || {}).length, 'files'); - } - } - } catch (error) { - console.error('[fetchSandboxFiles] Error fetching files:', error); - } - }; - const restartViteServer = async () => { try { addChatMessage('Restarting Vite dev server...', 'system'); @@ -969,499 +328,29 @@ Tip: I automatically detect and install npm packages from your code imports (lik const renderMainContent = () => { if (activeTab === 'generation' && (generationProgress.isGenerating || generationProgress.files.length > 0)) { return ( - /* Generation Tab Content */ -
- {/* File Explorer - Hide during edits */} - {!generationProgress.isEdit && ( -
-
-
- - Explorer -
-
- - {/* File Tree */} -
-
- {/* Root app folder */} -
toggleFolder('app')} - > - {expandedFolders.has('app') ? ( - - ) : ( - - )} - {expandedFolders.has('app') ? ( - - ) : ( - - )} - app -
- - {expandedFolders.has('app') && ( -
- {/* Group files by directory */} - {(() => { - const fileTree: { [key: string]: Array<{ name: string; edited?: boolean }> } = {}; - - // Create a map of edited files - const editedFiles = new Set( - generationProgress.files - .filter(f => f.edited) - .map(f => f.path) - ); - - // Process all files from generation progress - generationProgress.files.forEach(file => { - const parts = file.path.split('/'); - const dir = parts.length > 1 ? parts.slice(0, -1).join('/') : ''; - const fileName = parts[parts.length - 1]; - - if (!fileTree[dir]) fileTree[dir] = []; - fileTree[dir].push({ - name: fileName, - edited: file.edited || false - }); - }); - - return Object.entries(fileTree).map(([dir, files]) => ( -
- {dir && ( -
toggleFolder(dir)} - > - {expandedFolders.has(dir) ? ( - - ) : ( - - )} - {expandedFolders.has(dir) ? ( - - ) : ( - - )} - {dir.split('/').pop()} -
- )} - {(!dir || expandedFolders.has(dir)) && ( -
- {files.sort((a, b) => a.name.localeCompare(b.name)).map(fileInfo => { - const fullPath = dir ? `${dir}/${fileInfo.name}` : fileInfo.name; - const isSelected = selectedFile === fullPath; - - return ( -
handleFileClick(fullPath)} - > - {getFileIcon(fileInfo.name)} - - {fileInfo.name} - {fileInfo.edited && ( - - )} - -
- ); - })} -
- )} -
- )); - })()} -
- )} -
-
-
- )} - - {/* Code Content */} -
- {/* Thinking Mode Display - Only show during active generation */} - {generationProgress.isGenerating && (generationProgress.isThinking || generationProgress.thinkingText) && ( -
-
-
- {generationProgress.isThinking ? ( - <> -
- AI is thinking... - - ) : ( - <> - - Thought for {generationProgress.thinkingDuration || 0} seconds - - )} -
-
- {generationProgress.thinkingText && ( -
-
-                      {generationProgress.thinkingText}
-                    
-
- )} -
- )} - - {/* Live Code Display */} -
-
- {/* Show selected file if one is selected */} - {selectedFile ? ( -
-
-
-
- {getFileIcon(selectedFile)} - {selectedFile} -
- -
-
- { - const ext = selectedFile.split('.').pop()?.toLowerCase(); - if (ext === 'css') return 'css'; - if (ext === 'json') return 'json'; - if (ext === 'html') return 'html'; - return 'jsx'; - })()} - style={vscDarkPlus} - customStyle={{ - margin: 0, - padding: '1rem', - fontSize: '0.875rem', - background: 'transparent', - }} - showLineNumbers={true} - > - {(() => { - // Find the file content from generated files - const file = generationProgress.files.find(f => f.path === selectedFile); - return file?.content || '// File content will appear here'; - })()} - -
-
-
- ) : /* If no files parsed yet, show loading or raw stream */ - generationProgress.files.length === 0 && !generationProgress.currentFile ? ( - generationProgress.isThinking ? ( - // Beautiful loading state while thinking -
-
-
-
-
-
-
-
-

AI is analyzing your request

-

{generationProgress.status || 'Preparing to generate code...'}

-
-
- ) : ( -
-
-
-
- Streaming code... -
-
-
- - {generationProgress.streamedCode || 'Starting code generation...'} - - -
-
- ) - ) : ( -
- {/* Show current file being generated */} - {generationProgress.currentFile && ( -
-
-
-
- {generationProgress.currentFile.path} - - {generationProgress.currentFile.type === 'javascript' ? 'JSX' : generationProgress.currentFile.type.toUpperCase()} - -
-
-
- - {generationProgress.currentFile.content} - - -
-
- )} - - {/* Show completed files */} - {generationProgress.files.map((file, idx) => ( -
-
-
- - {file.path} -
- - {file.type === 'javascript' ? 'JSX' : file.type.toUpperCase()} - -
-
- - {file.content} - -
-
- ))} - - {/* Show remaining raw stream if there's content after the last file */} - {!generationProgress.currentFile && generationProgress.streamedCode.length > 0 && ( -
-
-
-
- Processing... -
-
-
- - {(() => { - // Show only the tail of the stream after the last file - const lastFileEnd = generationProgress.files.length > 0 - ? generationProgress.streamedCode.lastIndexOf('') + 7 - : 0; - let remainingContent = generationProgress.streamedCode.slice(lastFileEnd).trim(); - - // Remove explanation tags and content - remainingContent = remainingContent.replace(/[\s\S]*?<\/explanation>/g, '').trim(); - - // If only whitespace or nothing left, show waiting message - return remainingContent || 'Waiting for next file...'; - })()} - -
-
- )} -
- )} -
-
- - {/* Progress indicator */} - {generationProgress.components.length > 0 && ( -
-
-
-
-
- )} -
-
+ ); } else if (activeTab === 'preview') { - // Show screenshot when we have one and (loading OR generating OR no sandbox yet) - if (urlScreenshot && (loading || generationProgress.isGenerating || !sandboxData?.url || isPreparingDesign)) { - return ( -
- Website preview - {(generationProgress.isGenerating || isPreparingDesign) && ( -
-
-
-

- {generationProgress.isGenerating ? 'Generating code...' : `Preparing your design for ${targetUrl}...`} -

-
-
- )} -
- ); - } - - // Check loading stage FIRST to prevent showing old sandbox - // Don't show loading overlay for edits - if (loadingStage || (generationProgress.isGenerating && !generationProgress.isEdit)) { - return ( -
-
-
-
-
-

- {loadingStage === 'gathering' && 'Gathering website information...'} - {loadingStage === 'planning' && 'Planning your design...'} - {(loadingStage === 'generating' || generationProgress.isGenerating) && 'Generating your application...'} -

-

- {loadingStage === 'gathering' && 'Analyzing the website structure and content'} - {loadingStage === 'planning' && 'Creating the optimal React component architecture'} - {(loadingStage === 'generating' || generationProgress.isGenerating) && 'Writing clean, modern code for your app'} -

-
-
- ); - } - - // Show sandbox iframe only when not in any loading state - if (sandboxData?.url && !loading) { - return ( -
-