'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() { const [messages, setMessages] = useState([]); 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'); 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 deploy! Generate some code first.'); return; } // Determine if we're updating an existing space or creating a new one let existingRepoId = currentRepoId; let actionMessage = 'Deploy'; // If we have a current repo, check if user owns it if (currentRepoId && username) { const ownsSpace = currentRepoId.startsWith(`${username}/`); if (ownsSpace) { actionMessage = 'Update'; const confirmUpdate = confirm(`Update existing space: ${currentRepoId}?`); if (!confirmUpdate) { existingRepoId = null; // Create new space instead actionMessage = 'Deploy'; } } else { // User doesn't own the imported space, create a new one existingRepoId = null; actionMessage = 'Deploy'; } } // Only prompt for space name if creating new space let spaceName = undefined; if (!existingRepoId) { const input = prompt('Enter HuggingFace Space name (or leave empty for auto-generated):'); if (input === null) return; // User cancelled spaceName = input || undefined; } try { console.log('[Deploy] Deploying with params:', { language: selectedLanguage, space_name: spaceName, existing_repo_id: existingRepoId, currentRepoId: currentRepoId, username: username, code_length: generatedCode.length }); const response = await apiClient.deploy({ code: generatedCode, space_name: spaceName, language: selectedLanguage, existing_repo_id: existingRepoId || undefined, commit_message: existingRepoId ? 'Update via AnyCoder' : undefined, }); 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 const deployMessage: Message = { role: 'assistant', content: existingRepoId ? `✅ Updated space: ${response.space_url}` : `✅ Deployed to: ${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.' : existingRepoId ? `✅ Updated successfully!\n\nOpening: ${response.space_url}` : `✅ Deployed 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(''); } }; const handleImport = (code: string, language: Language, importUrl?: string) => { console.log('[Import] Importing project:', { language, importUrl, username }); 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]; console.log('[Import] Extracted repo_id:', importedRepoId, 'Username:', username); // Only set as current repo if user owns it if (username && importedRepoId.startsWith(`${username}/`)) { setCurrentRepoId(importedRepoId); console.log('[Import] ✅ Set current repo to:', importedRepoId); } else { // User doesn't own the imported space, clear current repo setCurrentRepoId(null); 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'); } // 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• Deploy 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) */}
); }