import { useState, useEffect } from 'react' import { motion, AnimatePresence } from 'framer-motion' import { MessageSquare, Target, GitFork, X } from 'lucide-react' import { Header } from './components/Header' import { LeftSidebar } from './components/LeftSidebar' import { ChatPanel } from './components/ChatPanel' import { BenchmarkPanel } from './components/BenchmarkPanel' import { ERDiagram } from './components/ERDiagram' import { RightSidebar } from './components/RightSidebar' import { DemoMode } from './components/DemoMode' import { ConnectDB } from './components/ConnectDB' import { useStore } from './store/useStore' import { fetchInit, fetchBenchmarkQuestions } from './lib/api' type Tab = 'chat' | 'benchmark' | 'er' const TABS: { id: Tab; label: string; icon: React.ReactNode }[] = [ { id: 'chat', label: 'Chat', icon: }, { id: 'benchmark', label: 'Benchmark', icon: }, { id: 'er', label: 'ER Diagram', icon: }, ] export default function App() { const [activeTab, setActiveTab] = useState('chat') const [leftOpen, setLeftOpen] = useState(false) const [rightOpen, setRightOpen] = useState(false) const [demoOpen, setDemoOpen] = useState(false) const [connectDbOpen, setConnectDbOpen] = useState(false) const { theme, setDbSeeded, setTables, setSchemaGraph, setDbLabel, taskDifficulty, isCustomDb } = useStore() // Apply theme on mount / change useEffect(() => { document.documentElement.setAttribute('data-theme', theme) }, [theme]) // Restore theme from storage on mount useEffect(() => { try { const saved = localStorage.getItem('theme') as 'dark' | 'light' | null if (saved) { document.documentElement.setAttribute('data-theme', saved) useStore.setState({ theme: saved }) } } catch { /* noop */ } }, []) // Fetch init data useEffect(() => { fetchInit() .then((d) => { setDbSeeded(true) setTables(d.tables) if (d.dbLabel) setDbLabel(d.dbLabel) // Lazy-load schema graph fetch('/api/schema-graph') .then((r) => r.json()) .then((g) => setSchemaGraph(g)) .catch(() => { /* noop */ }) }) .catch(() => { /* noop */ }) }, [setDbSeeded, setTables, setSchemaGraph]) // Load benchmark questions from API on mount useEffect(() => { const { setBenchmarkResults } = useStore.getState() fetchBenchmarkQuestions(taskDifficulty) .then(({ questions }) => { setBenchmarkResults( questions.map((q) => ({ id: q.id, question: q.question, difficulty: q.difficulty as 'easy' | 'medium' | 'hard', status: 'pending' as const, score: null, sql: null, reason: null, attempts: null, refRowCount: null, agentRowCount: null, })) ) }) .catch(() => { /* noop */ }) // eslint-disable-next-line react-hooks/exhaustive-deps }, []) // Close mobile sidebars on tab change useEffect(() => { setLeftOpen(false) setRightOpen(false) }, [activeTab]) // If custom DB is enabled while benchmark tab is active, switch to chat useEffect(() => { if (isCustomDb && activeTab === 'benchmark') setActiveTab('chat') }, [isCustomDb, activeTab]) return (
{ setLeftOpen((v) => !v); setRightOpen(false) }} onToggleRight={() => { setRightOpen((v) => !v); setLeftOpen(false) }} onDemo={() => setDemoOpen(true)} onConnectDb={() => setConnectDbOpen(true)} /> {demoOpen && setDemoOpen(false)} />} {connectDbOpen && setConnectDbOpen(false)} />}
{/* Overlay backdrop (mobile) */} {(leftOpen || rightOpen) && (
{ setLeftOpen(false); setRightOpen(false) }} /> )} {/* LEFT SIDEBAR */} {/* CENTER: Tabbed panel */}
{/* Tab bar */}
{TABS.filter((tab) => !(isCustomDb && tab.id === 'benchmark')).map((tab) => ( ))}
{/* Tab content */}
{activeTab === 'chat' && } {activeTab === 'benchmark' && } {activeTab === 'er' && }
{/* RIGHT SIDEBAR */}
) }