fix(web): keep thinking off the spoken consult and rectification answer

Enumerate evidence kinds so education cannot be proposed as a kind, and stream Chinese thinking on a separate channel that collapses when the reply arrives.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-21 21:30:55 +08:00
parent 56577504d8
commit 4e247c112e
27 changed files with 465 additions and 73 deletions
+31 -1
View File
@@ -1,6 +1,6 @@
"use client";
import { useEffect, useLayoutEffect, useRef } from "react";
import { useEffect, useLayoutEffect, useRef, useState } from "react";
import { AgentActivityStatus } from "@/components/agent-activity-status";
import { prefetchOnIdle } from "@/components/chat-chunk-prefetch";
@@ -31,6 +31,30 @@ function motionPreferred() {
if (motionPreferred()) prefetchOnIdle(loadGsap);
function MessageThinkingTrace({
text,
hasAnswer,
}: Readonly<{
text: string;
hasAnswer: boolean;
}>) {
const [userOpen, setUserOpen] = useState<boolean | null>(null);
const open = userOpen ?? !hasAnswer;
if (!text.trim()) return null;
return (
<details
className="message-thinking"
open={open}
onToggle={(event) => {
setUserOpen((event.currentTarget as HTMLDetailsElement).open);
}}
>
<summary></summary>
<div className="message-thinking-body">{text}</div>
</details>
);
}
export function AgentAvatar() {
return <span className="agent-avatar" aria-hidden="true" />;
}
@@ -104,6 +128,12 @@ export function ChatMessageRow({
completedTrail={message.activity?.completedTrail}
/>
)}
{message.thinkingText && (
<MessageThinkingTrace
text={message.thinkingText}
hasAnswer={Boolean(message.text.trim())}
/>
)}
{message.text && (
<ChatMessageContent
text={message.text}
@@ -382,6 +382,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
setDraft("");
let raw = "";
let thinkingRaw = "";
let activityReceiptState = createRectificationActivityReceiptState();
let completedReceipt = receiptFromRectificationActivityState(activityReceiptState);
let completedTurnId: string | undefined;
@@ -465,8 +466,18 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
}),
}
: message));
} else if (event.type === "thinking.delta" && typeof event.text === "string") {
thinkingRaw += event.text;
setMessages((current) => current.map((message) => message.renderKey === assistantRenderKey
? {
...message,
thinkingText: thinkingRaw,
state: raw ? "streaming" : "thinking",
}
: message));
} else if (event.type === "attempt.reset") {
raw = "";
thinkingRaw = "";
activityReceiptState = createRectificationActivityReceiptState();
completedReceipt = receiptFromRectificationActivityState(activityReceiptState);
completedTurnId = undefined;
@@ -474,6 +485,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
? {
...message,
text: "",
thinkingText: undefined,
state: "thinking",
completedReceipt: undefined,
failed: false,
@@ -806,6 +818,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
? {
...message,
text: "",
thinkingText: undefined,
state: "thinking" as const,
activity: nextActivityView(undefined, {
phase: "answer-composition",