From 647e3c4ff0b684b7d4dbd817ede0237a9934a49b Mon Sep 17 00:00:00 2001 From: ciregenz Date: Tue, 21 Jul 2026 18:40:19 -0700 Subject: [PATCH] [eric] chat: calm the in-between (no glow loops, quiet thinking line, duration-only summary w/ metrics in tooltip) --- .../src/app/pages/AgentChat/AgentChat.tsx | 18 +-- .../pages/AgentChat/bubbles/MessageBubble.tsx | 127 +++++++++--------- .../tool-bubbles/CreateAgentBubble.tsx | 1 - .../tool-bubbles/InvokeAgentBubble.tsx | 1 - 4 files changed, 64 insertions(+), 83 deletions(-) diff --git a/frontend/src/app/pages/AgentChat/AgentChat.tsx b/frontend/src/app/pages/AgentChat/AgentChat.tsx index 278817db..a557efbf 100644 --- a/frontend/src/app/pages/AgentChat/AgentChat.tsx +++ b/frontend/src/app/pages/AgentChat/AgentChat.tsx @@ -169,26 +169,16 @@ const ThinkingBubble: React.FC<{ label?: string | null }> = ({ label }) => { // Aux-LLM label wins; otherwise the pill stays plain "Thinking". The whimsical verbs read as personality // in the per-message thinking bubble (MessageBubble), but as a vague, confusing status on a working card. const display = label ? `${label}…` : `${THINKING_LABELS[0].live}…`; + // A quiet shimmer LINE, not a bordered card: status shares one visual language with the + // per-message thinking row, so only real content gets bubbles (the ChatGPT/Claude pattern). return ( - + 0 ? `${dynamicLabel}… ~${liveTokenEstimate} tokens` : `${dynamicLabel}…`) - : (liveTokenEstimate > 0 ? `${turnLabel.live}… (~${liveTokenEstimate} tokens)` : `${turnLabel.live}…`); + // No token counter while streaming: the label carries WHAT it's doing, the shimmer carries "live". + void liveTokenEstimate; + const activeLabel = dynamicLabel ? `${dynamicLabel}…` : `${turnLabel.live}…`; const fmtTokens = (n: number) => { if (n >= 1000) { @@ -637,70 +637,61 @@ const ThinkingBubble: React.FC<{ return { total: combinedTotalTokens, output: finalTokens, input: inputSide }; })(); + // Resting line reads like ChatGPT's "Thought for 12s": duration only. The dev metrics (token + // breakdown, tool count) live in a hover tooltip so the curious still get them without the + // transcript reading as telemetry. const renderPostStreamLabel = () => { - const segments: React.ReactNode[] = []; - segments.push( - - {finalSeconds != null - ? `${turnLabel.past} for ${fmtThoughtDuration(finalSeconds)}` - : turnLabel.past} - - ); - if (tokenBreakdown) { - const { total, input, output } = tokenBreakdown; - const tooltipBody = input != null && output != null ? ( - - - Input{input.toLocaleString()} - - - Output{output.toLocaleString()} - - - Total{total.toLocaleString()} + const durationText = finalSeconds != null + ? `${turnLabel.past} for ${fmtThoughtDuration(finalSeconds)}` + : turnLabel.past; + if (!tokenBreakdown && !(persistedToolCount != null && persistedToolCount > 0)) { + return {durationText}; + } + const { total, input, output } = tokenBreakdown ?? { total: 0, input: null, output: null }; + const tooltipBody = ( + + {input != null && output != null ? ( + <> + + Input{input.toLocaleString()} + + + Output{output.toLocaleString()} + + + Total{total.toLocaleString()} + + + ) : tokenBreakdown ? ( + {total.toLocaleString()} tokens (input + output + children) + ) : null} + {persistedToolCount != null && persistedToolCount > 0 && ( + + Tools used{persistedToolCount} + )} + {input != null && output != null && ( Input shown is your message, history, and tool outputs. The fixed framework preamble (system prompt, tool defs, MCP descriptions) is excluded, since it's constant overhead from the agent runtime, not anything you can shrink. - - ) : ( - - {total.toLocaleString()} tokens (input + output + children) - - ); - segments.push(, ); - segments.push( - - { e.stopPropagation(); }} - sx={{ - cursor: 'help', - borderBottom: `1px dotted ${c.border.medium}`, - '&:hover': { color: c.text.secondary }, - }} - > - {fmtTokens(total)} tokens - - - ); - } - if (persistedToolCount != null && persistedToolCount > 0) { - segments.push(, ); - segments.push( - {persistedToolCount} tool{persistedToolCount === 1 ? '' : 's'} used - ); - } - return segments; + )} + + ); + return ( + + {durationText} + + ); }; // Shimmer needs a flat string; post-stream label needs nodes for the token tooltip. @@ -1053,16 +1044,18 @@ const MessageBubble: React.FC = React.memo(({ message, editing = false, o > = ({ }`, borderRadius: 2, overflow: 'hidden', - animation: isPending ? 'border-glow 2s ease-in-out infinite' : 'none', transition: 'border-color 0.3s, box-shadow 0.3s', } as any} > diff --git a/frontend/src/app/pages/AgentChat/tool-bubbles/InvokeAgentBubble.tsx b/frontend/src/app/pages/AgentChat/tool-bubbles/InvokeAgentBubble.tsx index 396a3490..7961b851 100644 --- a/frontend/src/app/pages/AgentChat/tool-bubbles/InvokeAgentBubble.tsx +++ b/frontend/src/app/pages/AgentChat/tool-bubbles/InvokeAgentBubble.tsx @@ -53,7 +53,6 @@ export const InvokeAgentBubble: React.FC = ({ }`, borderRadius: 2, overflow: 'hidden', - animation: isPending ? 'border-glow 2s ease-in-out infinite' : 'none', transition: 'border-color 0.3s, box-shadow 0.3s', } as any} >