App.tsx
Language TypeScript2.9 KB88 lines
import { useEffect, useRef, useState } from 'react'import { ChatHeader } from './components/ChatHeader'import { ChatView } from './components/ChatView'import { Composer, type ComposerHandle } from './components/Composer'import { SearchPalette } from './components/SearchPalette'import { SettingsPanel } from './components/SettingsPanel'import { Sidebar } from './components/Sidebar'import { BudgetBanner, Toasts } from './components/Toasts'import { UsagePanel } from './components/UsagePanel'import { ipc } from './ipc'import { useTheme } from './lib/theme'import { useStore } from './state/store'export function App() {const [ready, setReady] = useState(false)const [error, setError] = useState<string | null>(null)const settings = useStore((s) => s.settings)const info = useStore((s) => s.info)const panel = useStore((s) => s.panel)const composer = useRef<ComposerHandle>(null)useTheme(settings, !!info?.vibrancy)useEffect(() => {ipc().then(async (client) => {await useStore.getState().init(client)setReady(true)// Show the window after the first paint. A hidden page gets no// animation frames, so a timer backs it up (or it would never show).let shown = falseconst show = () => {if (!shown) {shown = truevoid client.windowReady()}}requestAnimationFrame(show)setTimeout(show, 100)}).catch((e) => {setError(String(e))void ipc().then((c) => c.windowReady())})}, [])useEffect(() => {const onKey = (e: KeyboardEvent) => {const mod = e.metaKey || e.ctrlKeyconst s = useStore.getState()if (mod && e.key.toLowerCase() === 'k') {e.preventDefault()s.setPanel(s.panel === 'search' ? null : 'search')} else if (mod && e.key.toLowerCase() === 'n') {e.preventDefault()s.newChat()composer.current?.focus()} else if (mod && e.key === ',') {e.preventDefault()s.setPanel('settings')} else if (e.key === 'Escape' && s.panel) {s.setPanel(null)}}window.addEventListener('keydown', onKey)return () => window.removeEventListener('keydown', onKey)}, [])if (error) return <div className="fatal">Cortex could not start: {error}</div>if (!ready) return <div className="app loading" aria-busy />return (<div className="app"><Sidebar /><main className="main"><ChatHeader /><BudgetBanner /><ChatView onSuggest={(t) => composer.current?.setText(t)} /><div className="composer-wrap"><Composer ref={composer} /></div></main>{panel === 'search' && <SearchPalette />}{panel === 'settings' && <SettingsPanel />}{panel === 'usage' && <UsagePanel />}<Toasts /></div>)}