M3: seat configurator UI

A "AI seats" page (shadcn, on the design language): manage BYOK model connections (add +
test; the key is write-only), create seats on a team, and configure an agent per seat — name,
the color-graded autonomy dial (draft slate / gated indigo / auto teal), a model connection,
skill toggles from the registry, and docs. Navigable AppShell sidebar (Board / AI seats).

Verified: client `npm run build` clean (1890 modules, tsc + vite).
This commit is contained in:
soroush.asadi
2026-06-10 00:02:59 +03:30
parent e202246a1c
commit b61bbbcc52
3 changed files with 408 additions and 14 deletions
+29 -14
View File
@@ -1,5 +1,6 @@
import type { ReactNode } from 'react'
import { Inbox, type LucideIcon, LayoutDashboard, LogOut, Network } from 'lucide-react'
import { Link, useLocation } from 'react-router'
import { Bot, Inbox, type LucideIcon, LayoutDashboard, LogOut, Network } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Separator } from '@/components/ui/separator'
import { cn } from '@/lib/utils'
@@ -25,8 +26,9 @@ export function AppShell({ children }: { children: ReactNode }) {
<Separator className="bg-sidebar-border" />
<nav className="flex flex-1 flex-col gap-1 p-3">
<NavItem icon={LayoutDashboard} label="Board" active />
<NavItem icon={Inbox} label="Cartable" />
<NavItem icon={LayoutDashboard} label="Board" to="/" />
<NavItem icon={Bot} label="AI seats" to="/seats" />
<NavItem icon={Inbox} label="Cartable" muted />
<NavItem icon={Network} label="Org chart" muted />
</nav>
@@ -54,25 +56,38 @@ export function AppShell({ children }: { children: ReactNode }) {
function NavItem({
icon: Icon,
label,
active,
to,
muted,
}: {
icon: LucideIcon
label: string
active?: boolean
to?: string
muted?: boolean
}) {
return (
<span
className={cn(
'flex items-center gap-3 rounded-md px-3 py-2 text-sm',
active ? 'bg-sidebar-accent font-medium text-sidebar-accent-foreground' : 'text-sidebar-foreground/80',
muted && 'opacity-50',
)}
>
const location = useLocation()
const active = to ? location.pathname === to : false
const className = cn(
'flex items-center gap-3 rounded-md px-3 py-2 text-sm',
active ? 'bg-sidebar-accent font-medium text-sidebar-accent-foreground' : 'text-sidebar-foreground/80',
muted ? 'opacity-50' : 'hover:bg-sidebar-accent/60',
)
const content = (
<>
<Icon className="size-4" />
{label}
{muted && <span className="ml-auto text-[10px] uppercase tracking-wide opacity-70">soon</span>}
</span>
</>
)
if (!to || muted) {
return <span className={className}>{content}</span>
}
return (
<Link to={to} className={className}>
{content}
</Link>
)
}