Add reliable execution reporting and coordinated dashboard views

This commit is contained in:
aiagent
2026-10-19 04:29:33 -07:00
parent 547f62fa53
commit c2dfe311f7
41 changed files with 3867 additions and 107 deletions
+22 -4
View File
@@ -1,6 +1,7 @@
import { useCallback, useEffect, useState } from 'react'
import { HashRouter, Navigate, Route, Routes, useLocation } from 'react-router-dom'
import { Blocks, Circle } from 'lucide-react'
import { HashRouter, Link, Navigate, Route, Routes, useLocation } from 'react-router-dom'
import { Blocks, Circle, RefreshCw } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { AppSidebar, navigation } from '@/components/app-sidebar'
import { LoginForm } from '@/components/login-form'
import { SidebarInset, SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar'
@@ -10,13 +11,23 @@ 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 type { State, AttentionItem } 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'
import Logs from '@/pages/logs'
function attentionTarget(item: AttentionItem) {
const target = item.href.startsWith('#/') ? item.href.slice(1) : item.href
return /^\/(overview|tasks|cluster|communication|decisions|logs)(?:[?#]|$)/.test(target) ? target : `/${item.page}`
}
function AttentionNotice({ state, pathname }: { state: State; pathname: string }) {
const items = (state.attention?.items ?? []).filter(item => item.required && `/${item.page}` === pathname)
if (!items.length) return null
return <nav aria-label="当前页面需要关注的事项" className="max-h-32 shrink-0 overflow-y-auto rounded-md border bg-background px-4 py-2 text-sm"><ul className="space-y-1">{items.map(item => <li key={item.id}><Link to={attentionTarget(item)} className="flex items-start gap-2 rounded-sm underline underline-offset-4 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"><span aria-hidden="true" className="mt-1.5 size-2 shrink-0 rounded-full bg-destructive" /><span className="break-words">{item.reason}</span></Link></li>)}</ul></nav>
}
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()
@@ -28,7 +39,14 @@ function Shell({ token, initial, logout }: { token: string; initial: State; logo
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>
const isChat = location.pathname === '/communication'
return <Workbench.Provider value={{ state, token, refresh }}><SidebarProvider><AppSidebar title={title} onLogout={logout} /><SidebarInset className={isChat ? 'h-svh min-h-0 overflow-hidden' : undefined}>
<header className="flex h-16 shrink-0 items-center justify-between gap-3 border-b px-4 md:px-6"><div className="flex min-w-0 items-center gap-3"><SidebarTrigger aria-label="切换导航" /><Separator orientation="vertical" className="h-4" />{isChat ? <div className="min-w-0"><h1 className="font-semibold">项目沟通</h1><p className="truncate text-xs text-muted-foreground">询问进度、讨论方案,或告诉我你的新想法。</p></div> : <Breadcrumb><BreadcrumbList><BreadcrumbItem><BreadcrumbPage>{navigation.find(n => n.url === location.pathname)?.title || '总览'}</BreadcrumbPage></BreadcrumbItem></BreadcrumbList></Breadcrumb>}</div>
{isChat ? <Button variant="ghost" size="sm" onClick={() => window.dispatchEvent(new Event('workbench-refresh-conversation'))}><RefreshCw />刷新</Button> : <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={isChat ? 'flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden bg-background' : '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>}<AttentionNotice state={state} pathname={location.pathname} /><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="/logs" element={<Logs />} /><Route path="*" element={<Navigate to="/overview" replace />} /></Routes></main>
{!isChat && <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)