refactor(ai-chat): drop thinking-token text from typing indicator

The live typing placeholder now shows only the bouncing dots; the
"Thinking… · N tokens" line is removed. Clean up the dead plumbing:

- typing-indicator: remove thinkingTokens prop, thinkingLine and the
  <Text> line; keep the animated dots and the dimmed name label
- message-list: remove tailThinkingTokens helper, the thinkingTokens
  prop pass-through, and the now-unused liveTurnTokens import
- delete tail-thinking-tokens.test.ts (tested the removed helper)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
claude_code
2026-06-25 00:02:44 +03:00
parent d1fbcc1bfa
commit 91e7335d54
3 changed files with 3 additions and 83 deletions

View File

@@ -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 TypingIndicator from "@/features/ai-chat/components/typing-indicator.tsx";
import { isToolPart, toolRunState, ToolUiPart } from "@/features/ai-chat/utils/tool-parts.tsx"; import { isToolPart, toolRunState, ToolUiPart } from "@/features/ai-chat/utils/tool-parts.tsx";
import { assistantMessageHasVisibleContent } from "@/features/ai-chat/utils/message-content.ts"; 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"; import classes from "@/features/ai-chat/components/ai-chat.module.css";
interface MessageListProps { interface MessageListProps {
@@ -95,19 +94,6 @@ export function typingIndicatorShowsName(messages: UIMessage[]): boolean {
return !assistantMessageHasVisibleContent(last); 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, * 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 * but only while the user is pinned to the bottom — if they scrolled up to read
@@ -208,7 +194,6 @@ export default function MessageList({
<TypingIndicator <TypingIndicator
assistantName={assistantName} assistantName={assistantName}
showName={typingIndicatorShowsName(messages)} showName={typingIndicatorShowsName(messages)}
thinkingTokens={tailThinkingTokens(messages)}
/> />
)} )}
</Stack> </Stack>

View File

@@ -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);
});
});

View File

@@ -16,12 +16,6 @@ interface TypingIndicatorProps {
* assistant row above already shows the same name, to avoid a duplicate label. * assistant row above already shows the same name, to avoid a duplicate label.
*/ */
showName?: boolean; 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 * 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 * as the assistant's bubble taking shape. The dimmed label uses the configured
* identity name when provided (otherwise the generic "AI agent"), while the * identity name when provided (otherwise the generic "AI agent"); below it the
* typing line is always the generic "Thinking…" (it never includes the * animated dots stand in for the nascent bubble until content arrives.
* role/identity name).
*/ */
export default function TypingIndicator({ assistantName, showName = true, thinkingTokens }: TypingIndicatorProps) { export default function TypingIndicator({ assistantName, showName = true }: TypingIndicatorProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const name = resolveAssistantName(assistantName); 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 ( return (
<Box className={classes.messageRow}> <Box className={classes.messageRow}>
@@ -58,9 +46,6 @@ export default function TypingIndicator({ assistantName, showName = true, thinki
<span /> <span />
<span /> <span />
</span> </span>
<Text size="sm" c="dimmed">
{thinkingLine}
</Text>
</Group> </Group>
</Box> </Box>
); );