'use client'; import { useState, useEffect, useRef } from 'react'; import { flushSync } from 'react-dom'; import Header from '@/components/Header'; import LandingPage from '@/components/LandingPage'; 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() { // Initialize messages as empty array (will load from localStorage in useEffect) const [messages, setMessages] = useState([]); const [generatedCode, setGeneratedCode] = useState(''); const [selectedLanguage, setSelectedLanguage] = useState('html'); const [selectedModel, setSelectedModel] = useState('deepseek-ai/DeepSeek-V3.2-Exp'); 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 const [pendingPR, setPendingPR] = useState<{ repoId: string; language: Language } | null>(null); // Track pending PR after redesign const pendingPRRef = useRef<{ repoId: string; language: Language } | null>(null); // Ref for immediate access // Landing page state - show landing page if no messages exist const [showLandingPage, setShowLandingPage] = useState(true); // Mobile view state: 'chat', 'editor', or 'settings' - start on chat for mobile const [mobileView, setMobileView] = useState<'chat' | 'editor' | 'settings'>('chat'); // Resizable sidebar widths (in pixels) const [chatSidebarWidth, setChatSidebarWidth] = useState(320); const [settingsSidebarWidth, setSettingsSidebarWidth] = useState(288); const [isResizingChat, setIsResizingChat] = useState(false); const [isResizingSettings, setIsResizingSettings] = useState(false); const [isDesktop, setIsDesktop] = useState(false); // Debug: Log currentRepoId changes useEffect(() => { console.log('[App] šŸ”µ currentRepoId changed to:', currentRepoId); }, [currentRepoId]); // Clear cache on app startup to ensure fresh data useEffect(() => { if (typeof window !== 'undefined') { console.log('[Cache] Clearing models and languages cache on app startup'); localStorage.removeItem('anycoder_models'); localStorage.removeItem('anycoder_languages'); } }, []); // Run once on mount // Load messages from localStorage on mount (client-side only to avoid hydration issues) useEffect(() => { 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'); setMessages(parsed); // If there are existing messages, show the full UI if (parsed.length > 0) { setShowLandingPage(false); } } catch (e) { console.error('[localStorage] Failed to parse saved messages:', e); } } // Load sidebar widths from localStorage const savedChatWidth = localStorage.getItem('anycoder_chat_sidebar_width'); const savedSettingsWidth = localStorage.getItem('anycoder_settings_sidebar_width'); if (savedChatWidth) { setChatSidebarWidth(parseInt(savedChatWidth, 10)); } if (savedSettingsWidth) { setSettingsSidebarWidth(parseInt(savedSettingsWidth, 10)); } // Check if desktop on mount const checkDesktop = () => { setIsDesktop(window.innerWidth >= 768); }; checkDesktop(); // Listen for window resize to update desktop status window.addEventListener('resize', checkDesktop); return () => window.removeEventListener('resize', checkDesktop); } }, []); // Empty deps = run once on mount // Save messages to localStorage whenever they change (CRITICAL FOR PERSISTENCE!) useEffect(() => { if (typeof window !== 'undefined' && messages.length > 0) { localStorage.setItem('anycoder_messages', JSON.stringify(messages)); console.log('[localStorage] Saved', messages.length, 'messages to localStorage'); } }, [messages]); // Track if we've attempted to fetch username to avoid repeated failures const usernameFetchAttemptedRef = useRef(false); // Track if backend appears to be unavailable (to avoid repeated failed requests) const backendUnavailableRef = useRef(false); // Check auth on mount and handle OAuth callback useEffect(() => { checkAuth(); // Check for OAuth callback in URL (handles ?session=token) // initializeOAuth already handles this, but we call checkAuth to sync state const urlParams = new URLSearchParams(window.location.search); if (urlParams.get('session')) { // OAuth callback - reset both flags and check auth after a brief delay usernameFetchAttemptedRef.current = false; backendUnavailableRef.current = false; // Reset backend status on OAuth callback setTimeout(() => checkAuth(), 200); } }, []); // Only run once on mount // Listen for storage changes (e.g., logout from another tab) // Note: storage events only fire in OTHER tabs, not the current one useEffect(() => { const handleStorageChange = (e: StorageEvent) => { if (e.key === 'hf_oauth_token' || e.key === 'hf_user_info') { // Only reset username fetch if we have a token (might be logging in) if (e.newValue) { usernameFetchAttemptedRef.current = false; backendUnavailableRef.current = false; // Reset backend status on login } checkAuth(); } }; window.addEventListener('storage', handleStorageChange); return () => window.removeEventListener('storage', handleStorageChange); }, []); // Listen for authentication expiration events useEffect(() => { const handleAuthExpired = (e: CustomEvent) => { console.log('[Auth] Session expired:', e.detail?.message); // Clear authentication state setIsAuthenticated(false); setUsername(null); apiClient.setToken(null); // Show alert to user if (typeof window !== 'undefined') { alert(e.detail?.message || 'Your session has expired. Please sign in again.'); } }; window.addEventListener('auth-expired', handleAuthExpired as EventListener); return () => window.removeEventListener('auth-expired', handleAuthExpired as EventListener); }, []); // Listen for window focus (user returns to tab after OAuth redirect) // Only check if backend was available before or if we're authenticated with token useEffect(() => { const handleFocus = () => { // Only reset and check if we're authenticated (might have logged in elsewhere) // Don't reset if backend is known to be unavailable and we're not authenticated const authenticated = checkIsAuthenticated(); if (authenticated) { usernameFetchAttemptedRef.current = false; backendUnavailableRef.current = false; // Reset backend status - might be back up } checkAuth(); }; window.addEventListener('focus', handleFocus); return () => window.removeEventListener('focus', handleFocus); }, []); const checkAuth = async () => { const authenticated = checkIsAuthenticated(); setIsAuthenticated(authenticated); // Make sure API client has the token or clears it if (authenticated) { const token = getStoredToken(); if (token) { apiClient.setToken(token); // Get username from auth status (only if we don't have it yet and backend is available) // Skip if backend is known to be unavailable to avoid repeated failed requests if (!username && !usernameFetchAttemptedRef.current && !backendUnavailableRef.current) { usernameFetchAttemptedRef.current = true; try { const authStatus = await apiClient.getAuthStatus(); if (authStatus.username) { setUsername(authStatus.username); backendUnavailableRef.current = false; // Backend is working } } catch (error: any) { // Check if this is a connection error const isConnectionError = error.code === 'ECONNABORTED' || error.code === 'ECONNRESET' || error.code === 'ECONNREFUSED' || error.message?.includes('socket hang up') || error.message?.includes('timeout') || error.message?.includes('Network Error') || error.response?.status === 503 || error.response?.status === 502; if (isConnectionError) { // Mark backend as unavailable to avoid repeated requests backendUnavailableRef.current = true; // Don't reset attempt flag - keep it true so we don't retry until explicitly reset // This prevents repeated failed requests when backend is down } else { // Non-connection error - log it and reset attempt flag console.error('Failed to get username:', error); usernameFetchAttemptedRef.current = false; } } } } else { // Token missing but authenticated flag is true - clear state setIsAuthenticated(false); if (username) { setUsername(null); } usernameFetchAttemptedRef.current = false; backendUnavailableRef.current = false; } } else { // Not authenticated - clear username and reset flags apiClient.setToken(null); if (username) { setUsername(null); } usernameFetchAttemptedRef.current = false; // Keep backendUnavailableRef as is - it's useful information even when not authenticated } }; const handleSendMessage = async (message: string, overrideLanguage?: Language, overrideModel?: string, overrideRepoId?: string, shouldCreatePR?: boolean) => { if (!isAuthenticated) { alert('Please sign in with HuggingFace first! Click the "Sign in with Hugging Face" button in the header.'); return; } // Hide landing page and show full UI when first message is sent if (showLandingPage) { setShowLandingPage(false); } // Use override values if provided, otherwise use state const language = overrideLanguage || selectedLanguage; const model = overrideModel || selectedModel; // Update state if override values provided if (overrideLanguage) { setSelectedLanguage(overrideLanguage); } if (overrideModel) { setSelectedModel(overrideModel); } // 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\`\`\`${language}\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 // Use overrideRepoId if provided (from import/duplicate), otherwise use currentRepoId from state const effectiveRepoId = overrideRepoId || currentRepoId || undefined; console.log('[SendMessage] ========== GENERATION REQUEST =========='); console.log('[SendMessage] overrideRepoId:', overrideRepoId); console.log('[SendMessage] currentRepoId:', currentRepoId); console.log('[SendMessage] effectiveRepoId (will use):', effectiveRepoId); console.log('[SendMessage] =========================================='); const request: CodeGenerationRequest = { query: enhancedMessage, language: language, model_id: model, provider: 'auto', history: messages.map((m) => [m.role, m.content]), agent_mode: false, existing_repo_id: effectiveRepoId, // Pass duplicated/imported space ID for auto-deploy skip_auto_deploy: !!shouldCreatePR, // Skip auto-deploy if creating PR }; 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; }); // Check if we need to create a PR (redesign with PR option) console.log('[PR] onComplete - Checking pendingPR ref:', pendingPRRef.current); console.log('[PR] onComplete - Checking pendingPR state:', pendingPR); const prInfo = pendingPRRef.current; if (prInfo) { console.log('[PR] Creating pull request for:', prInfo.repoId); createPullRequestAfterGeneration(prInfo.repoId, code, prInfo.language); setPendingPR(null); // Clear state pendingPRRef.current = null; // Clear ref } else { console.log('[PR] No pending PR - skipping PR creation'); } }, // onError (error: string) => { setIsGenerating(false); setMessages((prev) => { const newMessages = [...prev]; newMessages[newMessages.length - 1] = { ...assistantMessage, content: `āŒ Error: ${error}`, }; return newMessages; }); }, // onDeploying (message: string) => { console.log('[Deploy] Deployment started:', message); // Update message to show deployment in progress setMessages((prev) => { const newMessages = [...prev]; newMessages[newMessages.length - 1] = { ...assistantMessage, content: `āœ… Code generated successfully!\n\n${message}`, }; return newMessages; }); }, // onDeployed (message: string, spaceUrl: string) => { console.log('[Deploy] Deployment successful:', spaceUrl); // Extract repo_id from space URL const match = spaceUrl.match(/huggingface\.co\/spaces\/([^\/\s\)]+\/[^\/\s\)]+)/); if (match) { setCurrentRepoId(match[1]); } // Update message with deployment success - use backend message format for history tracking setMessages((prev) => { const newMessages = [...prev]; newMessages[newMessages.length - 1] = { ...assistantMessage, content: `āœ… Code generated successfully!\n\n${message}`, }; return newMessages; }); // Open the space URL in a new tab window.open(spaceUrl, '_blank'); }, // onDeployError (message: string) => { console.log('[Deploy] Deployment error:', message); // Update message to show deployment failed (but code generation succeeded) setMessages((prev) => { const newMessages = [...prev]; newMessages[newMessages.length - 1] = { ...assistantMessage, content: `āœ… Code generated successfully!\n\n${message}\n\nYou can still use the "Publish" button to deploy manually.`, }; 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 createPullRequestAfterGeneration = async (repoId: string, code: string, language: Language) => { try { console.log('[PR] Creating PR on:', repoId); // Update message to show PR creation in progress setMessages((prev) => { const newMessages = [...prev]; newMessages[newMessages.length - 1] = { ...newMessages[newMessages.length - 1], content: 'āœ… Code generated successfully!\n\nšŸ”„ Creating Pull Request...', }; return newMessages; }); const prResult = await apiClient.createPullRequest( repoId, code, language, 'šŸŽØ Redesign from AnyCoder', undefined ); if (prResult.success && prResult.pr_url) { console.log('[PR] Pull Request created:', prResult.pr_url); // Update message with PR link setMessages((prev) => { const newMessages = [...prev]; newMessages[newMessages.length - 1] = { ...newMessages[newMessages.length - 1], content: `āœ… Code generated successfully!\n\nāœ… Pull Request created! [View PR](${prResult.pr_url})`, }; return newMessages; }); // Open PR in new tab window.open(prResult.pr_url, '_blank'); } else { throw new Error(prResult.message || 'Failed to create Pull Request'); } } catch (error: any) { console.error('[PR] Failed to create Pull Request:', error); // Update message with error setMessages((prev) => { const newMessages = [...prev]; newMessages[newMessages.length - 1] = { ...newMessages[newMessages.length - 1], content: `āœ… Code generated successfully!\n\nāŒ Failed to create Pull Request: ${error.message || 'Unknown error'}`, }; return newMessages; }); } }; const handleDeploy = async () => { console.log('[Deploy] šŸŽ¬ handleDeploy called'); console.log('[Deploy] generatedCode exists?', !!generatedCode); console.log('[Deploy] generatedCode length:', generatedCode?.length); console.log('[Deploy] generatedCode preview:', generatedCode?.substring(0, 200)); 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] Messages count:', messages.length); console.log('[Deploy] Messages (first 3):', messages.slice(0, 3).map(m => ({ role: m.role, content: m.content.substring(0, 100) }))); // CRITICAL DEBUG: Check what we're actually sending const historyToSend = messages.map(msg => ({ role: msg.role, content: msg.content })); console.log('[Deploy] History to send (length):', historyToSend.length); console.log('[Deploy] History to send (first 2):', historyToSend.slice(0, 2)); console.log('[Deploy] ================================================================='); // Build deploy request, omitting undefined fields const deployRequest: any = { code: generatedCode, language: selectedLanguage, history: historyToSend // Use the variable we just logged }; // Only include optional fields if they have values if (spaceName) { deployRequest.space_name = spaceName; } if (existingSpace) { deployRequest.existing_repo_id = existingSpace; deployRequest.commit_message = 'Update via AnyCoder'; } 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, code_length: deployRequest.code?.length, history_length: deployRequest.history?.length }); console.log('[Deploy] Full request object:', JSON.stringify(deployRequest, null, 2).substring(0, 500)); const response = await apiClient.deploy(deployRequest); console.log('[Deploy] āœ… Response received:', response); 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 format backend expects) const deployMessage: Message = { role: 'assistant', content: existingSpace ? `āœ… Updated! View your space at: ${response.space_url}` : `āœ… Deployed! View your space at: ${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: any) { console.error('[Deploy] Full error object:', error); console.error('[Deploy] Error response:', error.response); console.error('[Deploy] Error data:', error.response?.data); const errorMessage = error.response?.data?.detail || error.response?.data?.message || error.message || 'Unknown error'; alert(`Deployment error: ${errorMessage}\n\nCheck console for details.`); } }; const handleClear = () => { if (confirm('Clear all messages and code?')) { setMessages([]); setGeneratedCode(''); setShowLandingPage(true); // 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); // Hide landing page when importing if (showLandingPage) { setShowLandingPage(false); } 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}/`)) { console.log('[Import] āœ…āœ…āœ… BEFORE setCurrentRepoId - currentRepoId was:', currentRepoId); setCurrentRepoId(importedRepoId); console.log('[Import] āœ…āœ…āœ… CALLED setCurrentRepoId with:', importedRepoId); console.log('[Import] āœ…āœ…āœ… Note: State update is async, currentRepoId will update later'); } 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'); }; // Handle landing page prompt submission const handleLandingPageStart = async (prompt: string, language: Language, modelId: string, repoId?: string, shouldCreatePR?: boolean) => { // Hide landing page immediately for smooth transition setShowLandingPage(false); // If shouldCreatePR is true, set pending PR state and ref if (shouldCreatePR && repoId) { console.log('[PR] Setting pending PR for:', repoId); const prInfo = { repoId, language }; setPendingPR(prInfo); pendingPRRef.current = prInfo; // Set ref immediately for synchronous access } // Send the message with the selected language and model // Don't pass repoId to handleSendMessage when creating PR (we want to generate code first, then create PR) await handleSendMessage(prompt, language, modelId, shouldCreatePR ? undefined : repoId, shouldCreatePR); }; // Resize handlers for chat sidebar (desktop only) const startResizingChat = () => { if (isDesktop) { setIsResizingChat(true); } }; const startResizingSettings = () => { if (isDesktop) { setIsResizingSettings(true); } }; // Handle mouse move for resizing (desktop only) useEffect(() => { const handleMouseMove = (e: MouseEvent) => { if (!isDesktop) return; // Skip on mobile if (isResizingChat) { const newWidth = Math.min(Math.max(e.clientX, 250), 600); // Min 250px, max 600px setChatSidebarWidth(newWidth); } if (isResizingSettings) { const newWidth = Math.min(Math.max(window.innerWidth - e.clientX, 220), 500); // Min 220px, max 500px setSettingsSidebarWidth(newWidth); } }; const handleMouseUp = () => { if (isResizingChat) { setIsResizingChat(false); // Save to localStorage localStorage.setItem('anycoder_chat_sidebar_width', chatSidebarWidth.toString()); document.body.classList.remove('resizing'); } if (isResizingSettings) { setIsResizingSettings(false); // Save to localStorage localStorage.setItem('anycoder_settings_sidebar_width', settingsSidebarWidth.toString()); document.body.classList.remove('resizing'); } }; if (isResizingChat || isResizingSettings) { document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); // Add resizing class to body for cursor and selection styles document.body.classList.add('resizing'); } return () => { document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); document.body.classList.remove('resizing'); }; }, [isResizingChat, isResizingSettings, chatSidebarWidth, settingsSidebarWidth, isDesktop]); // Show landing page if no messages and showLandingPage is true if (showLandingPage && messages.length === 0) { return (
); } return (
{/* Apple-style layout - Responsive */}
{/* Left Sidebar - Chat Panel (Hidden on mobile, shown when mobileView='chat') */}
{/* Panel Header */}
Chat
{/* Chat Panel */}
{/* Resize Handle for Chat Sidebar (Desktop only) */}
{/* Center - Editor Group (Always visible on mobile when mobileView='editor', always visible on desktop) */}
{/* Tab Bar */}
{selectedLanguage === 'html' ? 'app.html' : selectedLanguage === 'gradio' || selectedLanguage === 'streamlit' ? 'app.py' : selectedLanguage === 'transformers.js' ? 'app.js' : selectedLanguage === 'comfyui' ? 'app.json' : selectedLanguage === 'react' ? 'app.jsx' : `${selectedLanguage}.txt`}
{isGenerating && (
Generating...
)} {selectedLanguage.toUpperCase()}
{/* Editor */}
{/* Resize Handle for Settings Sidebar (Desktop only) */}
{/* 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) */}
); }