Add reusable multi-session skill and five-page project workbench
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { HashRouter, Navigate, Route, Routes, useLocation } from 'react-router-dom'
|
||||
import { Blocks, Circle } from 'lucide-react'
|
||||
import { AppSidebar, navigation } from '@/components/app-sidebar'
|
||||
import { LoginForm } from '@/components/login-form'
|
||||
import { SidebarInset, SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { TooltipProvider } from '@/components/ui/tooltip'
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||
import { Breadcrumb, BreadcrumbItem, BreadcrumbList, BreadcrumbPage } from '@/components/ui/breadcrumb'
|
||||
import { Workbench } from '@/lib/context'
|
||||
import { request, errorText } from '@/lib/api'
|
||||
import type { State } from '@/lib/types'
|
||||
import { projectTitle, date } from '@/lib/types'
|
||||
import Overview from '@/pages/overview'
|
||||
import Tasks from '@/pages/tasks'
|
||||
import Cluster from '@/pages/cluster'
|
||||
import Communication from '@/pages/communication'
|
||||
import Decisions from '@/pages/decisions'
|
||||
function Shell({ token, initial, logout }: { token: string; initial: State; logout: () => void }) {
|
||||
const [state, setState] = useState(initial), [error, setError] = useState('')
|
||||
const title = projectTitle(state), location = useLocation()
|
||||
const refresh = useCallback(async () => { const latest = await request<State>(token, '/api/state'); setState(latest); setError('') }, [token])
|
||||
useEffect(() => {
|
||||
const controller = new AbortController(); let busy = false
|
||||
const poll = async () => { if (busy) return; busy = true; try { const latest = await request<State>(token, '/api/state', undefined, controller.signal); if (!controller.signal.aborted) { setState(latest); setError('') } } catch (e) { if (!controller.signal.aborted) setError(errorText(e)) } finally { busy = false } }
|
||||
const timer = setInterval(() => void poll(), 3000)
|
||||
return () => { clearInterval(timer); controller.abort() }
|
||||
}, [token])
|
||||
useEffect(() => { document.title = `${navigation.find(n => n.url === location.pathname)?.title || '总览'} · ${title}` }, [title, location.pathname])
|
||||
return <Workbench.Provider value={{ state, token, refresh }}><SidebarProvider><AppSidebar title={title} onLogout={logout} /><SidebarInset><header className="flex h-16 shrink-0 items-center justify-between gap-3 border-b px-4 md:px-6"><div className="flex items-center gap-3"><SidebarTrigger aria-label="切换导航" /><Separator orientation="vertical" className="h-4" /><Breadcrumb><BreadcrumbList><BreadcrumbItem><BreadcrumbPage>{navigation.find(n => n.url === location.pathname)?.title || '总览'}</BreadcrumbPage></BreadcrumbItem></BreadcrumbList></Breadcrumb></div><div className="flex items-center gap-2 text-xs text-muted-foreground"><Circle className={`size-2 fill-current ${error ? 'text-destructive' : 'text-primary'}`} /><span>{error ? '连接中断' : '实时同步'}</span><span className="hidden lg:inline">{date(state.updated)}</span></div></header><main className="min-w-0 flex-1 space-y-5 bg-muted/25 p-4 md:p-6 lg:p-8">{error && <Alert variant="destructive"><AlertDescription>{error} 当前显示上次同步的数据。</AlertDescription></Alert>}<Routes><Route path="/overview" element={<Overview />} /><Route path="/tasks" element={<Tasks />} /><Route path="/cluster" element={<Cluster />} /><Route path="/communication" element={<Communication />} /><Route path="/decisions" element={<Decisions />} /><Route path="*" element={<Navigate to="/overview" replace />} /></Routes></main><footer className="border-t px-6 py-4 text-xs text-muted-foreground">Agent Workbench · 每 3 秒同步 · AI 接入状态以会话显示为准</footer></SidebarInset></SidebarProvider></Workbench.Provider>
|
||||
}
|
||||
export default function App() {
|
||||
const [auth, setAuth] = useState<{ token: string; state: State } | null>(null)
|
||||
return <HashRouter><TooltipProvider>{auth ? <Shell token={auth.token} initial={auth.state} logout={() => setAuth(null)} /> : <main className="flex min-h-svh flex-col items-center justify-center gap-6 bg-muted/40 p-5"><div className="flex items-center gap-3 text-lg font-semibold"><span className="rounded-lg bg-primary p-2 text-primary-foreground"><Blocks className="size-5" /></span>Agent Workbench</div><div className="w-full max-w-md"><LoginForm onLogin={(token, state) => setAuth({ token, state })} /></div></main>}</TooltipProvider></HashRouter>
|
||||
}
|
||||
Reference in New Issue
Block a user