fix(web): split rectification process talk from the spoken reply
Keep provider thinking off, classify CoT as 思考, and stop remounted sessions from firing a second opening. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -59,7 +59,7 @@ function MessageThinkingTrace({
|
||||
setUserOpen((event.currentTarget as HTMLDetailsElement).open);
|
||||
}}
|
||||
>
|
||||
<summary>思考过程</summary>
|
||||
<summary>思考</summary>
|
||||
<div className="message-thinking-body">{text}</div>
|
||||
</details>
|
||||
);
|
||||
|
||||
@@ -64,6 +64,8 @@ export function ChatMessageRow({
|
||||
const thinkingSections = message.thinkingSections ?? [];
|
||||
const showReport = thinkingSections.length > 0;
|
||||
const showThinkingPanel = !showReport && (showActivity || Boolean(message.thinkingText?.trim()));
|
||||
const showSpokenAnswer = !showReport && Boolean(message.text);
|
||||
const stackedThinkingAndAnswer = showThinkingPanel && showSpokenAnswer;
|
||||
|
||||
useEntryEffect(() => {
|
||||
const row = messageRow.current;
|
||||
@@ -89,6 +91,29 @@ export function ChatMessageRow({
|
||||
return () => motion.revert();
|
||||
}, [message.role]);
|
||||
|
||||
const thinkingPanel = showThinkingPanel
|
||||
? (
|
||||
<AgentActivityStatus
|
||||
state={activityState}
|
||||
label={activityLabel}
|
||||
startedAt={message.activity?.startedAt}
|
||||
completedTrail={message.activity?.completedTrail}
|
||||
thinkingText={message.thinkingText}
|
||||
hasAnswer={hasAnswer}
|
||||
showLive={showActivity}
|
||||
/>
|
||||
)
|
||||
: null;
|
||||
const spokenAnswer = showSpokenAnswer
|
||||
? (
|
||||
<ChatMessageContent
|
||||
text={message.text}
|
||||
auditRows={message.agentExecutionReceipt?.techniqueAuditTable}
|
||||
vargaSentence={vargaSentence}
|
||||
/>
|
||||
)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<article
|
||||
ref={messageRow}
|
||||
@@ -112,23 +137,18 @@ export function ChatMessageRow({
|
||||
vargaSentence={vargaSentence}
|
||||
/>
|
||||
)}
|
||||
{showThinkingPanel && (
|
||||
<AgentActivityStatus
|
||||
state={activityState}
|
||||
label={activityLabel}
|
||||
startedAt={message.activity?.startedAt}
|
||||
completedTrail={message.activity?.completedTrail}
|
||||
thinkingText={message.thinkingText}
|
||||
hasAnswer={hasAnswer}
|
||||
showLive={showActivity}
|
||||
/>
|
||||
)}
|
||||
{!showReport && message.text && (
|
||||
<ChatMessageContent
|
||||
text={message.text}
|
||||
auditRows={message.agentExecutionReceipt?.techniqueAuditTable}
|
||||
vargaSentence={vargaSentence}
|
||||
/>
|
||||
{stackedThinkingAndAnswer ? (
|
||||
<div className="consultation-thinking-report">
|
||||
{thinkingPanel}
|
||||
<section className="consultation-report-analysis" aria-label="回复">
|
||||
{spokenAnswer}
|
||||
</section>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{thinkingPanel}
|
||||
{spokenAnswer}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : <p>{message.text}</p>}
|
||||
|
||||
@@ -34,6 +34,7 @@ export type ConversationalBirthTimeRectificationProps = Readonly<{
|
||||
onProfileIncomplete?: () => void;
|
||||
onSaved?: (time: string, status: "accepted" | "confirmed") => void;
|
||||
onStartConsultation?: () => void;
|
||||
onOpeningConsumed?: () => void;
|
||||
pendingConsultationQuestion?: string | null;
|
||||
onRestart?: () => void;
|
||||
headerSlot: HTMLElement | null;
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
isPublicRectificationMethod,
|
||||
isPublicRectificationTool,
|
||||
} from "@/lib/rectification-agentic/v9/public-receipt";
|
||||
import { finalizeRectificationSpokenAndThinking } from "@/lib/rectification-agentic/v9/spoken-answer";
|
||||
import {
|
||||
CHOICE_STOP_MESSAGE,
|
||||
choiceCardUserMessage,
|
||||
@@ -136,6 +137,7 @@ type RectificationAgenticChatProps = Readonly<{
|
||||
onProfileIncomplete?: () => void;
|
||||
onSaved?: (time: string, status: "accepted" | "confirmed") => void;
|
||||
onStartConsultation?: () => void;
|
||||
onOpeningConsumed?: () => void;
|
||||
pendingConsultationQuestion?: string | null;
|
||||
onRestart?: () => void;
|
||||
headerSlot: HTMLElement | null;
|
||||
@@ -200,9 +202,13 @@ function messagesFromTurns(initialTurns: readonly PersistedTurn[]): RenderMessag
|
||||
const key = `persisted-${turn.id}-${index}`;
|
||||
if (turn.role === "assistant") {
|
||||
const failed = persistedTurnFailed(turn);
|
||||
const raw = failed ? "" : turn.text ?? "";
|
||||
const split = raw ? finalizeRectificationSpokenAndThinking(raw) : { thinking: "", spoken: raw };
|
||||
const thinkingText = split.thinking.trim() || undefined;
|
||||
return [{
|
||||
role: "assistant",
|
||||
text: failed ? "" : turn.text ?? "",
|
||||
text: split.spoken || raw,
|
||||
...(thinkingText ? { thinkingText } : {}),
|
||||
renderKey: key,
|
||||
state: turn.status === "completed" || failed ? "settled" : "thinking",
|
||||
completedReceipt: completedReceiptFromPersisted(turn.receipt),
|
||||
@@ -235,6 +241,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
onProfileIncomplete,
|
||||
onSaved,
|
||||
onStartConsultation,
|
||||
onOpeningConsumed,
|
||||
pendingConsultationQuestion,
|
||||
onRestart,
|
||||
headerSlot,
|
||||
@@ -617,10 +624,15 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
}, [busy, caseId, loadCaseSnapshot, onCompleted, onMessagesChange, onProfileIncomplete, readonly, selectedModelId, sessionId, setPending]);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialTurns.length > 0) {
|
||||
if (shouldStartOpening) onOpeningConsumed?.();
|
||||
return;
|
||||
}
|
||||
if (readonly || openingStarted.current || !shouldStartOpening) return;
|
||||
openingStarted.current = true;
|
||||
onOpeningConsumed?.();
|
||||
void send("opening", "");
|
||||
}, [readonly, send, shouldStartOpening]);
|
||||
}, [initialTurns.length, onOpeningConsumed, readonly, send, shouldStartOpening]);
|
||||
|
||||
const acceptCandidate = useCallback(async (candidateId: string) => {
|
||||
if (!candidateResult || acceptingCandidateId || readonly) return;
|
||||
|
||||
Reference in New Issue
Block a user