diff --git a/packages/ui/src/components/message-stream-v2.tsx b/packages/ui/src/components/message-stream-v2.tsx index 37b6ed6d..6f44df17 100644 --- a/packages/ui/src/components/message-stream-v2.tsx +++ b/packages/ui/src/components/message-stream-v2.tsx @@ -339,13 +339,22 @@ export default function MessageStreamV2(props: MessageStreamV2Props) { return } - if (part.type === "step-start" || part.type === "step-finish") { + if (part.type === "step-start") { flushContent() const key = makeInstanceCacheKey(instanceId, `${record.id}:${part.id ?? partIndex}:${part.type}`) items.push({ type: part.type, key, part, messageInfo }) return } + if (part.type === "step-finish") { + flushContent() + if (showUsageMetrics) { + const key = makeInstanceCacheKey(instanceId, `${record.id}:${part.id ?? partIndex}:${part.type}`) + items.push({ type: part.type, key, part, messageInfo }) + } + return + } + if (part.type === "reasoning") { flushContent() if (showThinking && reasoningHasRenderableContent(part)) { @@ -741,17 +750,6 @@ interface StepCardProps { } function StepCard(props: StepCardProps) { - const snapshot = () => { - const value = (props.part as { snapshot?: string }).snapshot - return typeof value === "string" ? value : "" - } - - const reason = () => { - if (props.kind !== "finish") return "" - const value = (props.part as { reason?: string }).reason - return typeof value === "string" ? value : "" - } - const timestamp = () => { const value = props.messageInfo?.time?.created ?? (props.part as any)?.time?.start ?? Date.now() const date = new Date(value) @@ -800,60 +798,67 @@ function StepCard(props: StepCardProps) { } } + type UsageInfo = NonNullable> + + const renderUsageChips = (usage: UsageInfo) => ( +
+
+ Input + {formatTokenTotal(usage.input)} +
+
+ Output + {formatTokenTotal(usage.output)} +
+
+ Reasoning + {formatTokenTotal(usage.reasoning)} +
+
+ Cache Read + {formatTokenTotal(usage.cacheRead)} +
+
+ Cache Write + {formatTokenTotal(usage.cacheWrite)} +
+
+ Cost + {formatCostValue(usage.cost)} +
+
+ ) + + if (props.kind === "finish") { + const usage = usageStats() + if (!usage) { + return null + } + return ( +
+ {renderUsageChips(usage)} +
+ ) + } + return ( -
+
- + {(value) => Agent: {value()}} {(value) => Model: {value()}} - {props.kind === "start" ? "Step started" : "Step finished"}
{timestamp()}
- {(value) => {value()}}
- - {(usage) => ( -
-
- Input - {formatTokenTotal(usage().input)} -
-
- Output - {formatTokenTotal(usage().output)} -
-
- Reasoning - {formatTokenTotal(usage().reasoning)} -
-
- Cache Read - {formatTokenTotal(usage().cacheRead)} -
-
- Cache Write - {formatTokenTotal(usage().cacheWrite)} -
-
- Cost - {formatCostValue(usage().cost)} -
-
- )} -
- - ) } - function formatCostValue(value: number) { if (!value) return "$0.00" if (value < 0.01) return `$${value.toPrecision(2)}` diff --git a/packages/ui/src/styles/messaging/message-base.css b/packages/ui/src/styles/messaging/message-base.css index a973fea9..8450e2d6 100644 --- a/packages/ui/src/styles/messaging/message-base.css +++ b/packages/ui/src/styles/messaging/message-base.css @@ -18,15 +18,25 @@ .message-step-start { background-color: var(--message-assistant-bg); border-left: 4px solid var(--message-assistant-border); - margin-top: 0.25rem; + margin-top: 0.125rem; } .message-step-finish { background-color: var(--message-assistant-bg); border-left: 4px solid var(--message-assistant-border); - margin-bottom: 0.25rem; + margin-bottom: 0.125rem; } +.message-step-usage { + @apply flex flex-wrap items-center gap-1 text-[10px] text-[var(--text-muted)]; +} + +.message-step-heading { + @apply flex flex-wrap items-center gap-2 text-xs; + color: var(--text-muted); +} + + .message-queued-badge { @apply inline-block font-bold px-3 py-1 rounded mb-3 text-xs tracking-wide; diff --git a/packages/ui/src/styles/messaging/message-stream.css b/packages/ui/src/styles/messaging/message-stream.css index f7b40f71..4914873b 100644 --- a/packages/ui/src/styles/messaging/message-stream.css +++ b/packages/ui/src/styles/messaging/message-stream.css @@ -73,7 +73,7 @@ .message-stream-block { display: flex; flex-direction: column; - gap: 0.25rem; + gap: 0.125rem; } .message-scroll-button-wrapper {