Spaces:
Running
Running
| 'use client'; | |
| import { useState, useEffect, useRef } from 'react'; | |
| import { CodeEditor } from '@/components/CodeEditor'; | |
| import { ChatPanel } from '@/components/ChatPanel'; | |
| import { Header } from '@/components/Header'; | |
| import { Sidebar } from '@/components/Sidebar'; | |
| import { api } from '@/lib/api'; | |
| import type { Message, Model } from '@/types'; | |
| export default function Home() { | |
| const [messages, setMessages] = useState<Message[]>([]); | |
| const [code, setCode] = useState(''); | |
| const [language, setLanguage] = useState('html'); | |
| const [selectedModel, setSelectedModel] = useState<Model | null>(null); | |
| const [isGenerating, setIsGenerating] = useState(false); | |
| const [isAuthenticated, setIsAuthenticated] = useState(false); | |
| const [username, setUsername] = useState(''); | |
| const [sidebarOpen, setSidebarOpen] = useState(false); | |
| const [leftPanelWidth, setLeftPanelWidth] = useState(40); // percentage | |
| const [isResizing, setIsResizing] = useState(false); | |
| // Check auth status on mount | |
| useEffect(() => { | |
| checkAuth(); | |
| loadModels(); | |
| }, []); | |
| const checkAuth = async () => { | |
| try { | |
| const status = await api.getAuthStatus(); | |
| setIsAuthenticated(status.authenticated); | |
| setUsername(status.username || ''); | |
| } catch (error) { | |
| console.error('Auth check failed:', error); | |
| } | |
| }; | |
| const loadModels = async () => { | |
| try { | |
| const models = await api.getModels(); | |
| if (models.length > 0) { | |
| setSelectedModel(models[0]); | |
| } | |
| } catch (error) { | |
| console.error('Failed to load models:', error); | |
| } | |
| }; | |
| const handleSendMessage = async (message: string, imageUrl?: string) => { | |
| if (!message.trim() || isGenerating) return; | |
| const userMessage: Message = { | |
| role: 'user', | |
| content: message, | |
| timestamp: new Date(), | |
| }; | |
| setMessages((prev) => [...prev, userMessage]); | |
| setIsGenerating(true); | |
| try { | |
| let generatedCode = ''; | |
| await api.generateCode( | |
| { | |
| query: message, | |
| language, | |
| model_id: selectedModel?.id || 'zai-org/GLM-4.6V:zai-org', | |
| provider: 'auto', | |
| history: messages.map((m) => [m.role, m.content]), | |
| image_url: imageUrl, | |
| }, | |
| (chunk) => { | |
| generatedCode += chunk; | |
| setCode(generatedCode); | |
| } | |
| ); | |
| const assistantMessage: Message = { | |
| role: 'assistant', | |
| content: 'β Code generated successfully!', | |
| timestamp: new Date(), | |
| }; | |
| setMessages((prev) => [...prev, assistantMessage]); | |
| } catch (error) { | |
| const errorMessage: Message = { | |
| role: 'assistant', | |
| content: `β Error: ${error instanceof Error ? error.message : 'Unknown error'}`, | |
| timestamp: new Date(), | |
| }; | |
| setMessages((prev) => [...prev, errorMessage]); | |
| } finally { | |
| setIsGenerating(false); | |
| } | |
| }; | |
| const handleDeploy = async () => { | |
| if (!code.trim()) { | |
| alert('No code to deploy'); | |
| return; | |
| } | |
| try { | |
| const result = await api.deployCode({ | |
| code, | |
| language, | |
| history: messages.map((m) => ({ role: m.role, content: m.content })), | |
| }); | |
| if (result.success && result.space_url) { | |
| alert(`π Deployed successfully!\n\nView at: ${result.space_url}`); | |
| } else { | |
| alert(`β Deployment failed: ${result.message}`); | |
| } | |
| } catch (error) { | |
| alert(`β Deployment error: ${error instanceof Error ? error.message : 'Unknown error'}`); | |
| } | |
| }; | |
| // Handle panel resizing | |
| const handleMouseDown = (e: React.MouseEvent) => { | |
| e.preventDefault(); | |
| setIsResizing(true); | |
| document.body.classList.add('resizing'); | |
| }; | |
| useEffect(() => { | |
| const handleMouseMove = (e: MouseEvent) => { | |
| if (!isResizing) return; | |
| const containerWidth = window.innerWidth; | |
| const newWidth = (e.clientX / containerWidth) * 100; | |
| // Clamp between 25% and 75% | |
| const clampedWidth = Math.min(Math.max(newWidth, 25), 75); | |
| setLeftPanelWidth(clampedWidth); | |
| }; | |
| const handleMouseUp = () => { | |
| setIsResizing(false); | |
| document.body.classList.remove('resizing'); | |
| }; | |
| if (isResizing) { | |
| document.addEventListener('mousemove', handleMouseMove); | |
| document.addEventListener('mouseup', handleMouseUp); | |
| } | |
| return () => { | |
| document.removeEventListener('mousemove', handleMouseMove); | |
| document.removeEventListener('mouseup', handleMouseUp); | |
| }; | |
| }, [isResizing]); | |
| return ( | |
| <div className="flex flex-col h-screen bg-[#1d1d1f] text-[#e5e5e7]"> | |
| <Header | |
| isAuthenticated={isAuthenticated} | |
| username={username} | |
| onMenuClick={() => setSidebarOpen(!sidebarOpen)} | |
| /> | |
| <div className="flex flex-1 overflow-hidden"> | |
| {/* Mobile Sidebar Overlay */} | |
| {sidebarOpen && ( | |
| <div | |
| className="fixed inset-0 bg-black/50 z-40 md:hidden" | |
| onClick={() => setSidebarOpen(false)} | |
| /> | |
| )} | |
| {/* Sidebar */} | |
| <Sidebar | |
| isOpen={sidebarOpen} | |
| onClose={() => setSidebarOpen(false)} | |
| language={language} | |
| onLanguageChange={setLanguage} | |
| selectedModel={selectedModel} | |
| onModelChange={setSelectedModel} | |
| onDeploy={handleDeploy} | |
| isAuthenticated={isAuthenticated} | |
| /> | |
| {/* Main Content Area - Split View */} | |
| <div className="flex flex-1 overflow-hidden"> | |
| {/* Left Panel - Chat */} | |
| <div | |
| className="flex flex-col bg-[#1d1d1f] border-r border-[#3e3e42]" | |
| style={{ width: `${leftPanelWidth}%` }} | |
| > | |
| <ChatPanel | |
| messages={messages} | |
| onSendMessage={handleSendMessage} | |
| isGenerating={isGenerating} | |
| isAuthenticated={isAuthenticated} | |
| /> | |
| </div> | |
| {/* Resize Handle */} | |
| <div | |
| className="resize-handle" | |
| onMouseDown={handleMouseDown} | |
| /> | |
| {/* Right Panel - Code Editor */} | |
| <div | |
| className="flex flex-col bg-[#1e1e1e]" | |
| style={{ width: `${100 - leftPanelWidth}%` }} | |
| > | |
| <CodeEditor | |
| code={code} | |
| language={language} | |
| onChange={setCode} | |
| /> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } |