feat(web): refine run stage metric column

Render sub-second tool/command durations in milliseconds (e.g. 321ms)
instead of 0.3s. Round token counts to whole k/M. Wrap assistant token
metric in a tooltip showing the input/output breakdown. Allow Tooltip
labels to be arbitrary React nodes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-05-07 22:40:55 -07:00
parent be908cd0d5
commit 1182025c4c
No known key found for this signature in database
2 changed files with 41 additions and 14 deletions

View file

@ -68,7 +68,7 @@ export function Tooltip({
label,
children,
}: {
label: string;
label: React.ReactNode;
children: React.ReactNode;
}) {
const [open, setOpen] = useState(false);
@ -92,7 +92,7 @@ export function Tooltip({
</span>
{rect && portalTarget
? createPortal(
<span
<div
role="tooltip"
id={id}
style={{
@ -102,7 +102,7 @@ export function Tooltip({
className="pointer-events-none fixed z-50 -translate-x-1/2 -translate-y-full whitespace-nowrap rounded-md bg-panel px-2 py-1 text-xs text-fg-2 shadow-lg outline-1 -outline-offset-1 outline-line-strong"
>
{label}
</span>,
</div>,
portalTarget,
)
: null}

View file

@ -433,8 +433,8 @@ function formatDurationMs(ms: number): string {
function formatTokenCount(n: number): string {
if (n < 1000) return `${n}`;
if (n < 1_000_000) return `${(n / 1000).toFixed(1)}k`;
return `${(n / 1_000_000).toFixed(1)}M`;
if (n < 1_000_000) return `${Math.round(n / 1000)}k`;
return `${Math.round(n / 1_000_000)}M`;
}
export function turnMetric(turn: TurnType): string | null {
@ -488,6 +488,17 @@ function EventRow({
}) {
const metric = turnMetric(turn);
const MetricIcon = metric == null ? null : turn.kind === "assistant" ? CircleStackIcon : ClockIcon;
const metricSpan = (
<span className="inline-flex items-center justify-end gap-1.5 font-mono text-xs tabular-nums text-fg-muted">
{turn.kind === "tool" && turn.isError && (
<span className="rounded bg-coral/15 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wider text-coral">
Error
</span>
)}
{MetricIcon && <MetricIcon className="size-3" aria-hidden="true" />}
{metric ?? ""}
</span>
);
return (
<button
type="button"
@ -505,15 +516,31 @@ function EventRow({
<span className="min-w-0 truncate text-sm text-fg-3">
{turnSummary(turn)}
</span>
<span className="inline-flex items-center justify-end gap-1.5 font-mono text-xs tabular-nums text-fg-muted">
{turn.kind === "tool" && turn.isError && (
<span className="rounded bg-coral/15 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wider text-coral">
Error
</span>
)}
{MetricIcon && <MetricIcon className="size-3" aria-hidden="true" />}
{metric ?? ""}
</span>
{turn.kind === "assistant" && metric != null ? (
<Tooltip
label={
<div className="px-1 py-1">
<div className="mb-2 text-[10px] font-semibold uppercase tracking-wider text-fg-muted">
Tokens in / out
</div>
<div className="grid grid-cols-[auto_auto] items-baseline gap-x-3 gap-y-1 tabular-nums">
<span className="text-right font-medium text-fg">
{formatTokenCount(turn.inputTokens)}
</span>
<span className="text-fg-3">input</span>
<span className="text-right font-medium text-fg">
{formatTokenCount(turn.outputTokens)}
</span>
<span className="text-fg-3">output</span>
</div>
</div>
}
>
{metricSpan}
</Tooltip>
) : (
metricSpan
)}
<Tooltip label={formatAbsoluteTs(turn.ts)}>
<span className="pl-3 font-mono text-xs tabular-nums text-fg-muted">
{formatElapsed(turn.ts, runStart)}