Routing Assignment Panel

Right-dock panel listing the agent slots an active route dispatches to. Install Routing Assignment Panel from the VLLNT UI registry with the shadcn CLI.

Report a bug

Right-dock panel listing the agent slots an active route dispatches to. Part of the content family in VLLNT UI, it ships as a machine-readable registry entry — copy the source directly into your app with the shadcn CLI and own it, no runtime dependency on a component library.

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

Add Routing Assignment Panel to your project with the shadcn CLI. The source lands in your codebase, ready to adapt:

pnpm dlx shadcn@latest add https://ui.vllnt.com/r/routing-assignment-panel.json

Source

"use client"; import type { ComponentPropsWithoutRef, ReactNode } from "react"; import { cn } from "../../lib/utils"; /** * Role of an agent in the routing graph. * * @public */ export type RoutingRole = "fallback" | "primary" | "shadow"; const ROLE_LABEL: Record<RoutingRole, string> = { fallback: "Fallback", primary: "Primary", shadow: "Shadow", }; const ROLE_TONE: Record<RoutingRole, string> = { fallback: "border-amber-500/40 bg-amber-500/10 text-amber-700 dark:text-amber-300", primary: "border-emerald-500/40 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300", shadow: "border-border bg-muted/40 text-muted-foreground", }; /** * One row in the routing assignment list. * * @public */ export type RoutingAssignment = { /** Display name (e.g. `"researcher"`, `"ranker"`). */ agent: ReactNode; /** Stable identifier — used as the React key. */ id: string; /** Optional load fraction `0..1` rendered as a thin progress bar. */ load?: number; /** Optional click handler — when provided, the row becomes a button. */ onActivate?: () => void; /** Role of the agent for this slot. */ role: RoutingRole; }; /** * Localizable strings. * * @public */ export type RoutingAssignmentPanelLabels = { /** Empty-state copy. Defaults to `"No assignments"`. */ empty?: string; /** Aria-label for the panel. Defaults to `"Routing assignments"`. */ region?: string; }; const DEFAULT_LABELS = { empty: "No assignments", region: "Routing assignments", } as const satisfies Required<RoutingAssignmentPanelLabels>; /** * Props for {@link RoutingAssignmentPanel}. * * @public */ export type RoutingAssignmentPanelProps = { /** Assignments in render order. */ assignments: RoutingAssignment[]; /** Localizable strings. */ labels?: RoutingAssignmentPanelLabels; /** Panel title. Defaults to `"Routing"`. */ title?: ReactNode; } & ComponentPropsWithoutRef<"section">; const clampLoad = (value: number): number => { if (value < 0) { return 0; } if (value > 1) { return 1; } return value; }; const RowBody = (props: { assignment: RoutingAssignment; }): React.ReactElement => { const { assignment } = props; const load = assignment.load === undefined ? null : clampLoad(assignment.load); return ( <span className="flex flex-1 flex-col gap-1"> <span className="flex items-center justify-between gap-2"> <span className="truncate text-xs text-foreground"> {assignment.agent} </span> <span className={cn( "rounded-full border px-1.5 py-0.5 text-[10px] uppercase tracking-wide", ROLE_TONE[assignment.role], )} > {ROLE_LABEL[assignment.role]} </span> </span> {load === null ? null : ( <span aria-hidden="true" className="h-1 w-full overflow-hidden rounded-full bg-muted/40" > <span className="block h-full rounded-full bg-foreground/50" style={{ width: `${load * 100}%` }} /> </span> )} </span> ); }; const Row = (props: { assignment: RoutingAssignment }): React.ReactElement => { const { assignment } = props; if (assignment.onActivate) { const handleActivateAssignment = (): void => { assignment.onActivate?.(); }; return ( <button className="flex w-full items-center gap-2 rounded-md border border-transparent px-2 py-1.5 text-left transition-colors hover:border-border hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" data-routing-assignment={assignment.id} data-routing-role={assignment.role} onClick={handleActivateAssignment} type="button" > <RowBody assignment={assignment} /> </button> ); } return ( <div className="flex w-full items-center gap-2 rounded-md px-2 py-1.5" data-routing-assignment={assignment.id} data-routing-role={assignment.role} > <RowBody assignment={assignment} /> </div> ); }; /** * Right-dock panel listing the agent slots that the active route * dispatches to: primary handler + fallbacks + shadow probes. Each row * shows the agent name, role chip, and optional load bar. Pure * presentation; the host computes the assignments from the routing * config + observed traffic. * * @example * ```tsx * <RoutingAssignmentPanel * assignments={[ * { id: "1", agent: "researcher", role: "primary", load: 0.82 }, * { id: "2", agent: "researcher-mini", role: "fallback", load: 0.04 }, * { id: "3", agent: "shadow-eval", role: "shadow" }, * ]} * /> * ``` * * @public */ export const RoutingAssignmentPanel = ({ ref, ...props }: RoutingAssignmentPanelProps & { ref?: React.Ref<HTMLElement> }) => { const { assignments, className, labels, title = "Routing", ...rest } = props; const resolvedLabels = { ...DEFAULT_LABELS, ...labels }; return ( <section aria-label={resolvedLabels.region} className={cn( "flex w-full flex-col gap-2 rounded-lg border bg-background p-3 text-foreground", className, )} data-routing-assignment-panel ref={ref} {...rest} > <header> <h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground"> {title} </h3> </header> {assignments.length === 0 ? ( <p className="px-2 py-3 text-center text-xs text-muted-foreground" data-routing-state="empty" > {resolvedLabels.empty} </p> ) : ( <ul className="space-y-0.5"> {assignments.map((assignment) => ( <li key={assignment.id}> <Row assignment={assignment} /> </li> ))} </ul> )} </section> ); }; RoutingAssignmentPanel.displayName = "RoutingAssignmentPanel";

Stories

Explore every variant and state in the interactive Storybook:

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Dependencies

  • @vllnt/ui@^0.3.0