diff --git a/apps/fabro-web/app/routes/run-stages.test.ts b/apps/fabro-web/app/routes/run-stages.test.ts index 3fa5bf3ee..804017679 100644 --- a/apps/fabro-web/app/routes/run-stages.test.ts +++ b/apps/fabro-web/app/routes/run-stages.test.ts @@ -8,7 +8,6 @@ import { formatStageModelUsageLabel, groupConsecutiveTools, selectStageRenderer, - stageModelUsageTitle, } from "./run-stages"; function envelope(seq: number, partial: Partial): EventEnvelope { @@ -391,18 +390,7 @@ describe("eventsToActivity", () => { reasoning_effort: "high", speed: "fast", }), - ).toBe("gpt-5.5 · high"); - }); - - test("stageModelUsageTitle includes provider and speed details", () => { - expect( - stageModelUsageTitle({ - mode: "prompt", - provider: "anthropic", - model: "claude-sonnet-4-6", - speed: "standard", - }), - ).toBe("Provider: anthropic\nModel: claude-sonnet-4-6\nSpeed: standard"); + ).toBe("gpt-5.5[high]"); }); test("formatStageModelUsageLabel returns null when the projection has no model", () => { diff --git a/apps/fabro-web/app/routes/run-stages.tsx b/apps/fabro-web/app/routes/run-stages.tsx index 2c4245ed1..d10756a24 100644 --- a/apps/fabro-web/app/routes/run-stages.tsx +++ b/apps/fabro-web/app/routes/run-stages.tsx @@ -29,7 +29,13 @@ import { StageInsightsSidebar } from "../components/stage-insights-sidebar"; import { StageSidebar } from "../components/stage-sidebar"; import type { Stage } from "../components/stage-sidebar"; import { EmptyState } from "../components/state"; -import { Tooltip } from "../components/ui"; +import { + HoverCard, + PopoverHeader, + PopoverRow, + PopoverRows, + Tooltip, +} from "../components/ui"; import { ConditionalDecision } from "../components/stage-renderers/conditional-decision"; import { FanInResults } from "../components/stage-renderers/fan-in-results"; import { @@ -548,21 +554,31 @@ export function formatStageModelUsageLabel( const model = providerUsed?.model; if (!model) return null; const effort = providerUsed.reasoning_effort; - return effort ? `${model} · ${effort}` : model; + return effort ? `${model}[${effort}]` : model; } -export function stageModelUsageTitle( - providerUsed: StageModelUsage | null | undefined, -): string { - if (!providerUsed) return "LLM model used for this stage"; - const parts: string[] = []; - if (providerUsed.provider) parts.push(`Provider: ${providerUsed.provider}`); - if (providerUsed.model) parts.push(`Model: ${providerUsed.model}`); - if (providerUsed.reasoning_effort) { - parts.push(`Reasoning effort: ${providerUsed.reasoning_effort}`); - } - if (providerUsed.speed) parts.push(`Speed: ${providerUsed.speed}`); - return parts.length ? parts.join("\n") : "LLM model used for this stage"; +function ModelUsagePopover({ providerUsed }: { providerUsed: StageModelUsage }) { + return ( + <> + Model + + {providerUsed.provider && ( + {providerUsed.provider} + )} + {providerUsed.model && ( + + {providerUsed.model} + + )} + {providerUsed.reasoning_effort && ( + {providerUsed.reasoning_effort} + )} + {providerUsed.speed && ( + {providerUsed.speed} + )} + + + ); } function turnLabel(turn: TurnType): string { @@ -1271,10 +1287,10 @@ function EventsToolbar({ onSearchChange(""); } - const modelUsage = useMemo(() => { - const label = formatStageModelUsageLabel(providerUsed); - return label ? { label, title: stageModelUsageTitle(providerUsed) } : null; - }, [providerUsed]); + const modelUsageLabel = useMemo( + () => formatStageModelUsageLabel(providerUsed), + [providerUsed], + ); return (
@@ -1321,16 +1337,16 @@ function EventsToolbar({ : `${totalCount.toLocaleString()} events`} )} - {modelUsage && ( - } > + {modelUsageLabel} + )} {tab === "primary" && renderer === "command" && commandTurn && (