From c30fb57f6baac20a13f692437320a17553c039e8 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Mon, 8 Sep 2025 15:57:52 -0600 Subject: [PATCH] =?UTF-8?q?refactor(chat):=20reasoning=20UI=20tidy=20?= =?UTF-8?q?=E2=80=94=20utility=20classes,=20mb-2.5,=20safer=20effect=20gua?= =?UTF-8?q?rd;=20add=20ReasoningMeta=20typing;=20reduce=20any=20casts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/webview/webviewMessageHandler.ts | 4 +-- src/shared/WebviewMessage.ts | 10 ++++--- .../src/components/chat/ReasoningBlock.tsx | 30 +++++++++++-------- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 5597180e1c..e12beadb0a 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -2876,8 +2876,8 @@ export const webviewMessageHandler = async ( if (messageIndex === -1) { break } - const msg = currentCline.clineMessages[messageIndex] as any - const existingMeta = (msg.metadata as any) || {} + const msg = currentCline.clineMessages[messageIndex] as { metadata?: { reasoning?: { startedAt?: number; elapsedMs?: number } } } + const existingMeta = msg.metadata || {} const existingReasoning = existingMeta.reasoning || {} msg.metadata = { ...existingMeta, diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 55d1ada638..e71d327bf1 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -23,6 +23,11 @@ export interface UpdateTodoListPayload { todos: any[] } +export interface ReasoningMeta { + startedAt?: number + elapsedMs?: number +} + export type EditQueuedMessagePayload = Pick export interface WebviewMessage { @@ -257,10 +262,7 @@ export interface WebviewMessage { terminalOperation?: "continue" | "abort" messageTs?: number restoreCheckpoint?: boolean - reasoningMeta?: { - startedAt?: number - elapsedMs?: number - } + reasoningMeta?: ReasoningMeta historyPreviewCollapsed?: boolean filters?: { type?: string; search?: string; tags?: string[] } settings?: any diff --git a/webview-ui/src/components/chat/ReasoningBlock.tsx b/webview-ui/src/components/chat/ReasoningBlock.tsx index fbb34344b3..1c5d823e59 100644 --- a/webview-ui/src/components/chat/ReasoningBlock.tsx +++ b/webview-ui/src/components/chat/ReasoningBlock.tsx @@ -4,37 +4,43 @@ import { useTranslation } from "react-i18next" import MarkdownBlock from "../common/MarkdownBlock" import { vscode } from "@src/utils/vscode" +interface ReasoningMeta { + startedAt?: number + elapsedMs?: number +} + interface ReasoningBlockProps { content: string ts: number isStreaming: boolean isLast: boolean - metadata?: Record + metadata?: { reasoning?: ReasoningMeta } | Record } /** * Render reasoning with a heading and a persistent timer. * - Heading uses i18n key chat:reasoning.thinking - * - Timer shown as "(⟲ 24s)" beside the heading and persists via message.metadata.reasoning { startedAt, elapsedMs } + * - Timer shown beside the heading and persists via message.metadata.reasoning { startedAt, elapsedMs } */ export const ReasoningBlock = ({ content, ts, isStreaming, isLast, metadata }: ReasoningBlockProps) => { const { t } = useTranslation() - const persisted = (metadata?.reasoning as { startedAt?: number; elapsedMs?: number } | undefined) || {} + const persisted: ReasoningMeta = (metadata?.reasoning as ReasoningMeta) || {} const startedAtRef = useRef(persisted.startedAt ?? Date.now()) const [elapsed, setElapsed] = useState(persisted.elapsedMs ?? 0) + const postedRef = useRef(false) - // Initialize startedAt on first mount if missing (persist to task) + // Initialize startedAt on first mount if missing (persist to task) - guard with postedRef useEffect(() => { - if (!persisted.startedAt && isLast) { + if (!persisted.startedAt && isLast && !postedRef.current) { + postedRef.current = true vscode.postMessage({ type: "updateMessageReasoningMeta", messageTs: ts, reasoningMeta: { startedAt: startedAtRef.current }, - } as any) + }) } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ts]) + }, [ts, isLast, persisted.startedAt]) // Tick while active (last row and streaming) useEffect(() => { @@ -58,7 +64,7 @@ export const ReasoningBlock = ({ content, ts, isStreaming, isLast, metadata }: R type: "updateMessageReasoningMeta", messageTs: ts, reasoningMeta: { startedAt: startedAtRef.current, elapsedMs: finalMs }, - } as any) + }) } wasActiveRef.current = active }, [isLast, isStreaming, ts]) @@ -73,13 +79,13 @@ export const ReasoningBlock = ({ content, ts, isStreaming, isLast, metadata }: R return (
-
+
- + {t("chat:reasoning.thinking")}
- + {secondsLabel}