'use client'; import { useState, useEffect } from 'react'; import { flushSync } from 'react-dom'; import Header from '@/components/Header'; import ChatInterface from '@/components/ChatInterface'; import CodeEditor from '@/components/CodeEditor'; import ControlPanel from '@/components/ControlPanel'; import { apiClient } from '@/lib/api'; import { isAuthenticated as checkIsAuthenticated, getStoredToken } from '@/lib/auth'; import type { Message, Language, CodeGenerationRequest } from '@/types'; export default function Home() { // Load messages from localStorage on mount (CRITICAL FOR IMPORT/DEPLOY TRACKING!) const [messages, setMessages] = useState(() => { if (typeof window !== 'undefined') { const saved = localStorage.getItem('anycoder_messages'); if (saved) { try { const parsed = JSON.parse(saved); console.log('[localStorage] Loaded messages from localStorage:', parsed.length, 'messages'); return parsed; } catch (e) { console.error('[localStorage] Failed to parse saved messages:', e); } } } return []; }); const [generatedCode, setGeneratedCode] = useState(''); const [selectedLanguage, setSelectedLanguage] = useState('html'); const [selectedModel, setSelectedModel] = useState('gemini-3.0-pro'); const [isGenerating, setIsGenerating] = useState(false); const [isAuthenticated, setIsAuthenticated] = useState(false); const [currentRepoId, setCurrentRepoId] = useState(null); // Track imported/deployed space const [username, setUsername] = useState(null); // Track current user // Mobile view state: 'chat', 'editor', or 'settings' const [mobileView, setMobileView] = useState<'chat' | 'editor' | 'settings'>('editor'); // Save messages to localStorage whenever they change (CRITICAL FOR PERSISTENCE!) useEffect(() => { if (typeof window !== 'undefined') { localStorage.setItem('anycoder_messages', JSON.stringify(messages)); console.log('[localStorage] Saved', messages.length, 'messages to localStorage'); } }, [messages]); useEffect(() => { checkAuth(); // Check auth status every second to catch OAuth redirects const interval = setInterval(checkAuth, 1000); return () => clearInterval(interval); }, []); const checkAuth = async () => { const authenticated = checkIsAuthenticated(); setIsAuthenticated(authenticated); // Make sure API client has the token if (authenticated) { const token = getStoredToken(); if (token) { apiClient.setToken(token); // Get username from auth status try { const authStatus = await apiClient.getAuthStatus(); if (authStatus.username) { setUsername(authStatus.username); } } catch (error) { console.error('Failed to get username:', error); } } } }; const handleSendMessage = async (message: string) => { if (!isAuthenticated) { alert('Please sign in with HuggingFace first! Click the "Sign in with Hugging Face" button in the header.'); return; } // If there's existing code, include it in the message context for modifications let enhancedMessage = message; const hasRealCode = generatedCode && generatedCode.length > 50 && !generatedCode.includes('Your generated code will appear here'); if (hasRealCode) { enhancedMessage = `I have existing code in the editor. Please modify it based on my request.\n\nCurrent code:\n\`\`\`${selectedLanguage}\n${generatedCode}\n\`\`\`\n\nMy request: ${message}`; } // Add user message (show original message to user, but send enhanced to API) const userMessage: Message = { role: 'user', content: message, timestamp: new Date().toISOString(), }; setMessages((prev) => [...prev, userMessage]); setIsGenerating(true); // Clear previous code to show streaming from start setGeneratedCode(''); // Prepare request with enhanced query that includes current code const request: CodeGenerationRequest = { query: enhancedMessage, language: selectedLanguage, model_id: selectedModel, provider: 'auto', history: messages.map((m) => [m.role, m.content]), agent_mode: false, }; const assistantMessage: Message = { role: 'assistant', content: '⏳ Generating code...', timestamp: new Date().toISOString(), }; // Add placeholder for assistant message setMessages((prev) => [...prev, assistantMessage]); // Stream the response try { apiClient.generateCodeStream( request, // onChunk - Update code editor in real-time with immediate flush (chunk: string) => { console.log('[Stream] Received chunk:', chunk.substring(0, 50), '... (length:', chunk.length, ')'); // Use flushSync to force immediate DOM update without React batching flushSync(() => { setGeneratedCode((prevCode) => { const newCode = prevCode + chunk; console.log('[Stream] Total code length:', newCode.length); return newCode; }); }); }, // onComplete (code: string) => { setGeneratedCode(code); setIsGenerating(false); // Update final message - just show success, not the code setMessages((prev) => { const newMessages = [...prev]; newMessages[newMessages.length - 1] = { ...assistantMessage, content: '✅ Code generated successfully! Check the editor →', }; return newMessages; }); }, // onError (error: string) => { setIsGenerating(false); setMessages((prev) => { const newMessages = [...prev]; newMessages[newMessages.length - 1] = { ...assistantMessage, content: `❌ Error: ${error}`, }; return newMessages; }); } ); } catch (error) { setIsGenerating(false); setMessages((prev) => { const newMessages = [...prev]; newMessages[newMessages.length - 1] = { ...assistantMessage, content: `❌ Error: ${error instanceof Error ? error.message : 'Unknown error'}`, }; return newMessages; }); } }; const handleDeploy = async () => { if (!generatedCode) { alert('No code to publish! Generate some code first.'); return; } // Get current username (fetch if not loaded) let currentUsername = username; if (!currentUsername) { console.log('[Deploy] Username not in state, fetching from auth...'); try { const authStatus = await apiClient.getAuthStatus(); if (authStatus.username) { currentUsername = authStatus.username; setUsername(authStatus.username); console.log('[Deploy] Fetched username:', currentUsername); } } catch (e) { console.error('[Deploy] Could not get username:', e); // Don't fail - let backend handle auth } } // SAME LOGIC AS GRADIO VERSION: Parse message history to find existing space let existingSpace: string | null = null; // Look for previous deployment or imported space in history console.log('[Deploy] ========== DEBUG START =========='); console.log('[Deploy] Total messages in history:', messages.length); console.log('[Deploy] Current username:', currentUsername); console.log('[Deploy] Auth status:', isAuthenticated ? 'authenticated' : 'not authenticated'); console.log('[Deploy] Messages:', JSON.stringify(messages, null, 2)); if (messages.length > 0 && currentUsername) { console.log('[Deploy] Scanning message history FORWARD (oldest first) - MATCHING GRADIO LOGIC...'); console.log('[Deploy] Total messages to scan:', messages.length); // EXACT GRADIO LOGIC: Scan forward (oldest first) and stop at first match // Gradio: for user_msg, assistant_msg in history: for (let i = 0; i < messages.length; i++) { const msg = messages[i]; console.log(`[Deploy] Checking message ${i}:`, { role: msg.role, contentPreview: msg.content.substring(0, 100) }); // Check assistant messages for deployment confirmations if (msg.role === 'assistant') { // Check for "✅ Deployed!" message if (msg.content.includes('✅ Deployed!')) { const match = msg.content.match(/huggingface\.co\/spaces\/([^\/\s\)]+\/[^\/\s\)]+)/); if (match) { existingSpace = match[1]; console.log('[Deploy] ✅ Found "✅ Deployed!" - existing_space:', existingSpace); break; } } // Check for "✅ Updated!" message else if (msg.content.includes('✅ Updated!')) { const match = msg.content.match(/huggingface\.co\/spaces\/([^\/\s\)]+\/[^\/\s\)]+)/); if (match) { existingSpace = match[1]; console.log('[Deploy] ✅ Found "✅ Updated!" - existing_space:', existingSpace); break; } } } // Check user messages for imports else if (msg.role === 'user' && msg.content.startsWith('Imported Space from')) { console.log('[Deploy] 🎯 Found "Imported Space from" message'); const match = msg.content.match(/huggingface\.co\/spaces\/([^\/\s\)]+\/[^\/\s\)]+)/); if (match) { const importedSpace = match[1]; console.log('[Deploy] Extracted imported space:', importedSpace); console.log('[Deploy] Checking ownership - user:', currentUsername, 'space:', importedSpace); // Only use if user owns it (EXACT GRADIO LOGIC) if (importedSpace.startsWith(`${currentUsername}/`)) { existingSpace = importedSpace; console.log('[Deploy] ✅✅✅ USER OWNS - Will update:', existingSpace); break; } else { console.log('[Deploy] ⚠️ User does NOT own - will create new space'); // existing_space remains None (create new deployment) } } } } console.log('[Deploy] Final existingSpace value:', existingSpace); } else { console.log('[Deploy] Skipping scan - no messages or no username'); console.log('[Deploy] Messages length:', messages.length); console.log('[Deploy] Username:', currentUsername); } console.log('[Deploy] ========== DEBUG END =========='); // TEMPORARY DEBUG: Show what will be sent console.log('[Deploy] 🚀 ABOUT TO DEPLOY:'); console.log('[Deploy] - Language:', selectedLanguage); console.log('[Deploy] - existing_repo_id:', existingSpace || 'None (new deployment)'); console.log('[Deploy] - Username:', currentUsername); // Auto-generate space name (never prompt user) let spaceName = undefined; // undefined = backend will auto-generate try { console.log('[Deploy] ========== DEPLOY START (Gradio-style history parsing) =========='); console.log('[Deploy] Username:', currentUsername); console.log('[Deploy] Existing space from history:', existingSpace); console.log('[Deploy] Will create new space?', !existingSpace); console.log('[Deploy] ================================================================='); const deployRequest = { code: generatedCode, space_name: spaceName, language: selectedLanguage, existing_repo_id: existingSpace || undefined, commit_message: existingSpace ? 'Update via AnyCoder' : undefined, }; console.log('[Deploy] 🚀 Sending to backend:', { existing_repo_id: deployRequest.existing_repo_id, space_name: deployRequest.space_name, language: deployRequest.language, has_code: !!deployRequest.code }); const response = await apiClient.deploy(deployRequest); if (response.success) { // Update current repo ID if we got one back if (response.repo_id) { console.log('[Deploy] Setting currentRepoId to:', response.repo_id); setCurrentRepoId(response.repo_id); } else if (response.space_url) { // Extract repo_id from space_url as fallback const match = response.space_url.match(/huggingface\.co\/spaces\/([^\/\s\)]+\/[^\/\s\)]+)/); if (match) { console.log('[Deploy] Extracted repo_id from URL:', match[1]); setCurrentRepoId(match[1]); } } // Add deployment message to chat (EXACT Gradio format with markdown link) const deployMessage: Message = { role: 'assistant', content: existingSpace ? `✅ Updated! [Open your Space here](${response.space_url})` : `✅ Deployed! [Open your Space here](${response.space_url})`, timestamp: new Date().toISOString(), }; setMessages((prev) => [...prev, deployMessage]); // Open the space URL in a new tab window.open(response.space_url, '_blank'); // Show success message const isDev = response.dev_mode; const message = isDev ? '🚀 Opening HuggingFace Spaces creation page...\nPlease complete the space setup in the new tab.' : existingSpace ? `✅ Updated successfully!\n\nOpening: ${response.space_url}` : `✅ Published successfully!\n\nOpening: ${response.space_url}`; alert(message); } else { alert(`Deployment failed: ${response.message}`); } } catch (error) { alert(`Deployment error: ${error instanceof Error ? error.message : 'Unknown error'}`); } }; const handleClear = () => { if (confirm('Clear all messages and code?')) { setMessages([]); setGeneratedCode(''); // Clear localStorage to remove import history if (typeof window !== 'undefined') { localStorage.removeItem('anycoder_messages'); console.log('[localStorage] Cleared messages from localStorage'); } } }; const handleImport = (code: string, language: Language, importUrl?: string) => { console.log('[Import] ========== IMPORT START =========='); console.log('[Import] Language:', language); console.log('[Import] Import URL:', importUrl); console.log('[Import] Current username:', username); console.log('[Import] Current repo before import:', currentRepoId); setGeneratedCode(code); setSelectedLanguage(language); // Extract repo_id from import URL if provided if (importUrl) { const spaceMatch = importUrl.match(/huggingface\.co\/spaces\/([^\/\s\)]+\/[^\/\s\)]+)/); console.log('[Import] Regex match result:', spaceMatch); if (spaceMatch) { const importedRepoId = spaceMatch[1]; const importedUsername = importedRepoId.split('/')[0]; console.log('[Import] ========================================'); console.log('[Import] Extracted repo_id:', importedRepoId); console.log('[Import] Imported username:', importedUsername); console.log('[Import] Logged-in username:', username); console.log('[Import] Ownership check:', importedUsername === username); console.log('[Import] ========================================'); // Only set as current repo if user owns it if (username && importedRepoId.startsWith(`${username}/`)) { setCurrentRepoId(importedRepoId); console.log('[Import] ✅✅✅ SETTING currentRepoId to:', importedRepoId); } else { // User doesn't own the imported space, clear current repo setCurrentRepoId(null); if (!username) { console.log('[Import] ⚠️⚠️⚠️ USERNAME IS NULL - Cannot set repo ownership!'); } else { console.log('[Import] ⚠️ User does not own imported space:', importedRepoId, '(username:', username, ')'); } } } else { console.log('[Import] ⚠️ Could not extract repo_id from URL:', importUrl); } } else { console.log('[Import] No import URL provided'); } console.log('[Import] ========== IMPORT END =========='); // Add messages that include the imported code so LLM can see it const userMessage: Message = { role: 'user', content: importUrl ? `Imported Space from ${importUrl}` : `I imported a ${language} project. Here's the code that was imported.`, timestamp: new Date().toISOString(), }; const assistantMessage: Message = { role: 'assistant', content: `✅ I've loaded your ${language} project. The code is now in the editor. You can ask me to:\n\n• Modify existing features\n• Add new functionality\n• Fix bugs or improve code\n• Explain how it works\n• Publish it to HuggingFace Spaces\n\nWhat would you like me to help you with?`, timestamp: new Date().toISOString(), }; setMessages((prev) => [...prev, userMessage, assistantMessage]); // Switch to editor view on mobile setMobileView('editor'); }; return (
{/* VS Code layout with Apple styling - Responsive */}
{/* Left Sidebar - Chat Panel (Hidden on mobile, shown when mobileView='chat') */}
{/* Panel Header */}
Chat
{/* Chat Panel */}
{/* Center - Editor Group (Always visible on mobile when mobileView='editor', always visible on desktop) */}
{/* Tab Bar */}
{selectedLanguage}.{ selectedLanguage === 'html' ? 'html' : selectedLanguage === 'gradio' || selectedLanguage === 'streamlit' ? 'py' : selectedLanguage === 'transformers.js' ? 'js' : selectedLanguage === 'comfyui' ? 'json' : 'jsx' }
{isGenerating && (
Generating...
)} {selectedLanguage.toUpperCase()}
{/* Editor */}
{/* Right Sidebar - Configuration Panel (Hidden on mobile, shown when mobileView='settings') */}
{/* Mobile Bottom Navigation (visible only on mobile) */} {/* Status Bar - Apple style (hidden on mobile) */}
); }