Files
ai-safety-console-web/src/app/AppShell.tsx
T
baozaotumao2025 1b90e552a5 first commit
2026-07-18 21:10:39 +08:00

52 lines
1.9 KiB
TypeScript

import { NavLink, Outlet, useNavigate } from 'react-router-dom'
import { Badge, Button } from 'antd'
import { useQuery } from '@tanstack/react-query'
import { useAuth } from '../auth/AuthProvider'
import { ThemeToggle } from '../components/ThemeToggle'
import { services } from '../services'
const links = [
['/overview', '概览'],
['/runs/new', '新建测试'],
['/runs', '测试运行'],
['/reports', '汇总报告'],
['/providers', '模型配置'],
] as const
export function AppShell() {
const auth = useAuth()
const navigate = useNavigate()
const activeRuns = useQuery({
queryKey: ['runs', 'active-count'],
queryFn: () => services.runs.list(50),
refetchInterval: 5000,
select: (runs) => runs.filter((run) => !run.terminal).length,
})
return (
<div className="app-shell">
<aside className="sidebar">
<div className="brand"><span className="brand-mark">AS</span><div><strong>Safety Console</strong><small>AI </small></div></div>
<nav aria-label="主导航">
{links.map(([to, label]) => <NavLink key={to} to={to}>{label}</NavLink>)}
{auth.user?.is_admin && <NavLink to="/users"></NavLink>}
<NavLink to="/account"></NavLink>
</nav>
<div className="sidebar-footer">
<div><strong>{auth.user?.username}</strong><small>{auth.user?.is_admin ? '管理员' : '操作员'}</small></div>
<Button type="text" onClick={() => void auth.logout()}>退</Button>
</div>
</aside>
<div className="workspace">
<header className="topbar">
<button className="running-indicator" onClick={() => navigate('/runs')}>
<Badge status={activeRuns.data ? 'processing' : 'default'} />
{activeRuns.data || 0}
</button>
<ThemeToggle />
</header>
<main className="content"><Outlet /></main>
</div>
</div>
)
}