diff --git a/apps/client/src/features/ai-chat/components/message-list.tsx b/apps/client/src/features/ai-chat/components/message-list.tsx index d9995cda..f04ca2ab 100644 --- a/apps/client/src/features/ai-chat/components/message-list.tsx +++ b/apps/client/src/features/ai-chat/components/message-list.tsx @@ -6,7 +6,6 @@ import MessageItem from "@/features/ai-chat/components/message-item.tsx"; import TypingIndicator from "@/features/ai-chat/components/typing-indicator.tsx"; import { isToolPart, toolRunState, ToolUiPart } from "@/features/ai-chat/utils/tool-parts.tsx"; import { assistantMessageHasVisibleContent } from "@/features/ai-chat/utils/message-content.ts"; -import { liveTurnTokens } from "@/features/ai-chat/utils/count-stream-tokens.ts"; import classes from "@/features/ai-chat/components/ai-chat.module.css"; interface MessageListProps { @@ -95,19 +94,6 @@ export function typingIndicatorShowsName(messages: UIMessage[]): boolean { return !assistantMessageHasVisibleContent(last); } -/** - * The live thinking-token count to show on the standalone typing indicator. It - * is the reasoning split of the tail assistant message (estimate while streaming, - * authoritative once the server attaches usage at a step/turn boundary). Returns - * 0 when the turn has produced no reasoning yet — the indicator then shows the - * plain "Thinking…" line. - */ -export function tailThinkingTokens(messages: UIMessage[]): number { - const last = messages[messages.length - 1]; - if (!last || last.role !== "assistant") return 0; - return liveTurnTokens(last).reasoning; -} - /** * Scrollable transcript. Auto-scrolls to the newest message as it streams in, * but only while the user is pinned to the bottom — if they scrolled up to read @@ -208,7 +194,6 @@ export default function MessageList({ )} diff --git a/apps/client/src/features/ai-chat/components/tail-thinking-tokens.test.ts b/apps/client/src/features/ai-chat/components/tail-thinking-tokens.test.ts deleted file mode 100644 index 5f421aec..00000000 --- a/apps/client/src/features/ai-chat/components/tail-thinking-tokens.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { describe, expect, it } from "vitest"; -import type { UIMessage } from "@ai-sdk/react"; -import { tailThinkingTokens } from "@/features/ai-chat/components/message-list.tsx"; - -/** - * Pure-helper tests for `tailThinkingTokens`: the live thinking-token count the - * standalone typing indicator shows. It is the reasoning split of the tail - * assistant message (estimate while streaming, authoritative once usage arrives). - */ -const msg = ( - role: "user" | "assistant", - parts: unknown[], - metadata?: unknown, -): UIMessage => - ({ id: Math.random().toString(), role, parts, metadata }) as UIMessage; - -describe("tailThinkingTokens", () => { - it("is 0 when there are no messages", () => { - expect(tailThinkingTokens([])).toBe(0); - }); - - it("is 0 when the tail message is the user's", () => { - expect(tailThinkingTokens([msg("user", [{ type: "text", text: "q" }])])).toBe(0); - }); - - it("is 0 when the assistant has produced no reasoning yet", () => { - expect( - tailThinkingTokens([msg("assistant", [{ type: "text", text: "answer" }])]), - ).toBe(0); - }); - - it("estimates reasoning tokens from streamed reasoning text", () => { - // 8 chars -> 2 tokens. - expect( - tailThinkingTokens([ - msg("assistant", [{ type: "reasoning", text: "12345678" }]), - ]), - ).toBe(2); - }); - - it("uses authoritative usage.reasoningTokens once the server attaches it", () => { - expect( - tailThinkingTokens([ - msg("assistant", [{ type: "reasoning", text: "x" }], { - usage: { outputTokens: 100, reasoningTokens: 42 }, - }), - ]), - ).toBe(42); - }); -}); diff --git a/apps/client/src/features/ai-chat/components/typing-indicator.tsx b/apps/client/src/features/ai-chat/components/typing-indicator.tsx index 72ac3179..a3e9f937 100644 --- a/apps/client/src/features/ai-chat/components/typing-indicator.tsx +++ b/apps/client/src/features/ai-chat/components/typing-indicator.tsx @@ -16,12 +16,6 @@ interface TypingIndicatorProps { * assistant row above already shows the same name, to avoid a duplicate label. */ showName?: boolean; - /** - * Live thinking/reasoning token count for the in-flight turn. When > 0 the - * typing line becomes `Thinking… · {count} tokens` (like Claude Code). Omitted - * / 0 keeps the plain `Thinking…` line. - */ - thinkingTokens?: number; } /** @@ -32,18 +26,12 @@ interface TypingIndicatorProps { * * Mirrors the assistant row layout in MessageItem (the dimmed label), so it reads * as the assistant's bubble taking shape. The dimmed label uses the configured - * identity name when provided (otherwise the generic "AI agent"), while the - * typing line is always the generic "Thinking…" (it never includes the - * role/identity name). + * identity name when provided (otherwise the generic "AI agent"); below it the + * animated dots stand in for the nascent bubble until content arrives. */ -export default function TypingIndicator({ assistantName, showName = true, thinkingTokens }: TypingIndicatorProps) { +export default function TypingIndicator({ assistantName, showName = true }: TypingIndicatorProps) { const { t } = useTranslation(); const name = resolveAssistantName(assistantName); - // Show the running thinking-token count only once there is something to count. - const thinkingLine = - thinkingTokens && thinkingTokens > 0 - ? t("Thinking… · {{count}} tokens", { count: thinkingTokens }) - : t("Thinking…"); return ( @@ -58,9 +46,6 @@ export default function TypingIndicator({ assistantName, showName = true, thinki - - {thinkingLine} - );