Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 31 additions & 8 deletions console/src/components/layout/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import {
MessagesSquare,
Network,
PanelLeftClose,
PanelLeftOpen,
Settings,
} from "lucide-react"
import { cn } from "@/lib/utils"
import { useSidebarStore } from "@/stores/sidebar"
import { Logo } from "@/components/ui/logo"

/** Toolbar-level param keys that should be preserved across page navigation */
const TOOLBAR_KEYS = ["preset", "start", "end", "wire_api", "model", "server_ip", "refresh"]
Expand Down Expand Up @@ -54,13 +54,36 @@ export function Sidebar() {
expanded ? "w-[200px]" : "w-[44px]",
)}
>
<div className="flex h-12 items-center justify-center border-b border-border px-2">
<button
onClick={toggle}
className="flex size-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
>
{expanded ? <PanelLeftClose className="size-4" /> : <PanelLeftOpen className="size-4" />}
</button>
<div
className={cn(
"flex h-12 items-center border-b border-border",
expanded ? "justify-between pl-3 pr-2" : "justify-center px-2",
)}
>
{expanded ? (
<>
<Logo variant="wordmark" className="h-5 text-foreground" />
<button
onClick={toggle}
aria-label="Collapse sidebar"
className="flex size-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
>
<PanelLeftClose className="size-4" />
</button>
</>
) : (
// Collapsed: the icon doubles as the expand affordance — saves a
// row and is the most discoverable place to put the toggle when
// there's no room for a second button.
<button
onClick={toggle}
aria-label="Expand sidebar"
title="TokenScope — click to expand"
className="flex size-8 items-center justify-center rounded-md text-foreground transition-colors hover:bg-muted"
>
<Logo variant="icon" className="size-5" />
</button>
)}
</div>

<nav className="flex flex-1 flex-col gap-1 p-1.5">
Expand Down
71 changes: 71 additions & 0 deletions console/src/components/ui/logo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* TokenScope brand mark. Two variants share the same icon glyph (rounded
* square "scope frame" containing three decreasing horizontal lines —
* abstracted tokens viewed through the lens), so the icon-only and the
* wordmark line up visually when the sidebar collapses.
*
* Stroke + fills use `currentColor` so the mark inherits the surrounding
* text colour and respects light/dark themes without extra CSS.
*/

import { cn } from "@/lib/utils"

interface LogoProps {
variant: "icon" | "wordmark"
className?: string
}

export function Logo({ variant, className }: LogoProps) {
if (variant === "icon") {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={1.75}
strokeLinecap="round"
strokeLinejoin="round"
className={cn("shrink-0", className)}
aria-label="TokenScope"
role="img"
>
<rect x={2.5} y={2.5} width={19} height={19} rx={4.5} />
<line x1={6.5} y1={9} x2={17.5} y2={9} />
<line x1={6.5} y1={13} x2={14} y2={13} />
<line x1={6.5} y1={17} x2={10.5} y2={17} />
</svg>
)
}

// Wordmark: same icon glyph at the left + "TokenScope" set in a
// system-stack semi-bold. SVG <text> renders crisply at any DPI and
// tints with currentColor; we accept the (tiny) variance across OS
// font choices in exchange for not shipping a webfont.
return (
<svg
viewBox="0 0 156 24"
fill="none"
className={cn("shrink-0", className)}
aria-label="TokenScope"
role="img"
>
<g stroke="currentColor" strokeWidth={1.75} strokeLinecap="round" strokeLinejoin="round">
<rect x={2.5} y={2.5} width={19} height={19} rx={4.5} />
<line x1={6.5} y1={9} x2={17.5} y2={9} />
<line x1={6.5} y1={13} x2={14} y2={13} />
<line x1={6.5} y1={17} x2={10.5} y2={17} />
</g>
<text
x={30}
y={17}
fontFamily='ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif'
fontWeight={600}
fontSize={15}
letterSpacing={-0.2}
fill="currentColor"
>
TokenScope
</text>
</svg>
)
}
13 changes: 9 additions & 4 deletions console/src/pages/agent-turns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,22 @@ const STATUS_OPTIONS = ["in_progress", "complete", "incomplete"]

const PAGE_SIZES = [20, 50, 100] as const

// Identity columns (Agent / Client) sit immediately after Time; coarse
// shape (Calls / Status) and per-turn token counters (In / Out) follow
// — that's the order operators reach for first when triaging a turn.
// Less-frequently-scanned dimensions (Model / Wire API / Server) and
// the long preview column trail.
const columns = [
{ key: "start_time", label: "Time", width: "w-[210px]", sortable: true, align: "left" as const },
{ key: "wire_api", label: "Wire API", width: "w-[120px]", sortable: false, align: "left" as const },
{ key: "primary_model", label: "Model", width: "w-[180px]", sortable: false, align: "left" as const },
{ key: "agent_kind", label: "Agent", width: "w-[100px]", sortable: false, align: "left" as const },
{ key: "client_ip", label: "Client", width: "w-[130px]", sortable: false, align: "left" as const },
{ key: "server_ip", label: "Server", width: "w-[130px]", sortable: false, align: "left" as const },
{ key: "status", label: "Status", width: "w-[100px]", sortable: false, align: "left" as const },
{ key: "call_count", label: "Calls", width: "w-[60px]", sortable: true, align: "right" as const },
{ key: "status", label: "Status", width: "w-[100px]", sortable: false, align: "left" as const },
{ key: "total_input_tokens", label: "In", width: "w-[70px]", sortable: true, align: "right" as const },
{ key: "total_output_tokens", label: "Out", width: "w-[70px]", sortable: true, align: "right" as const },
{ key: "primary_model", label: "Model", width: "w-[180px]", sortable: false, align: "left" as const },
{ key: "wire_api", label: "Wire API", width: "w-[120px]", sortable: false, align: "left" as const },
{ key: "server_ip", label: "Server", width: "w-[130px]", sortable: false, align: "left" as const },
{ key: "duration_ms", label: "Duration", width: "w-[90px]", sortable: true, align: "right" as const },
{ key: "preview", label: "User Input", width: "", sortable: false, align: "left" as const },
] as const
Expand Down
13 changes: 11 additions & 2 deletions console/src/pages/models.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ function getErrorRate(m: MetricsModelRow): number {
function getSortValue(m: MetricsModelRow, key: SortKey): number | string {
if (key === "error_rate") return getErrorRate(m)
if (key === "model" || key === "wire_api") return m[key]
// Underlying field is tpot_avg (ms/token, lower = faster) but the column
// surfaces it as TPS = 1000/tpot_avg. Invert the sort value so "desc"
// gives fastest first — matches what a user clicking "Generation TPS"
// expects to see.
if (key === "tpot_avg") {
return m.tpot_avg != null && m.tpot_avg > 0 ? 1000 / m.tpot_avg : 0
}
return (m[key] as number) ?? 0
}

Expand Down Expand Up @@ -146,7 +153,7 @@ export function ModelsPage() {
<th className="px-3 py-3 text-right"><SortHeader label="TTFT p95" field="ttft_p95" align="right" /></th>
<th className="px-3 py-3 text-right"><SortHeader label="E2E avg" field="e2e_avg" align="right" /></th>
<th className="px-3 py-3 text-right"><SortHeader label="E2E p95" field="e2e_p95" align="right" /></th>
<th className="px-3 py-3 text-right"><SortHeader label="TPOT" field="tpot_avg" align="right" /></th>
<th className="px-3 py-3 text-right"><SortHeader label="Generation TPS" field="tpot_avg" align="right" /></th>
<th className="px-3 py-3 text-right"><SortHeader label="In Tokens" field="total_input_tokens" align="right" /></th>
<th className="px-3 py-3 text-right"><SortHeader label="Out Tokens" field="total_output_tokens" align="right" /></th>
<th className="w-8 px-2 py-3" />
Expand Down Expand Up @@ -197,7 +204,9 @@ export function ModelsPage() {
<td className="px-3 py-2.5 text-right tabular-nums">{formatMs(m.e2e_avg)}</td>
<td className="px-3 py-2.5 text-right tabular-nums">{formatMs(m.e2e_p95)}</td>
<td className="px-3 py-2.5 text-right tabular-nums">
{m.tpot_avg != null ? `${m.tpot_avg.toFixed(1)} ms/tok` : "—"}
{m.tpot_avg != null && m.tpot_avg > 0
? `${(1000 / m.tpot_avg).toFixed(1)} tok/s`
: "—"}
</td>
<td className="px-3 py-2.5 text-right tabular-nums">{formatNumber(m.total_input_tokens)}</td>
<td className="px-3 py-2.5 text-right tabular-nums">{formatNumber(m.total_output_tokens)}</td>
Expand Down
10 changes: 7 additions & 3 deletions console/src/pages/overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,13 @@ export function OverviewPage() {
subtext={`${formatNumber(summary?.total_input_tokens)} in / ${formatNumber(summary?.total_output_tokens)} out`}
/>
<KpiCard
title="Avg TPOT"
value={summary?.tpot_avg != null ? `${summary.tpot_avg.toFixed(1)} ms/tok` : "—"}
subtext="streaming only"
title="Avg TPS"
value={
summary?.tpot_avg != null && summary.tpot_avg > 0
? `${(1000 / summary.tpot_avg).toFixed(1)} tok/s`
: "—"
}
subtext="streaming only · generation speed"
/>
</div>

Expand Down
Loading