fix(web): show live agent work progress and fail truncated rectification answers
Rectification dropped tool.activity started events and treated length finishes as completed. Share generation settings with consultation, keep the activity line through streaming, and name multi-domain chart calculation. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import type { OrbState } from "thinking-orbs";
|
||||
|
||||
import { prefetchOnIdle } from "@/components/chat-chunk-prefetch";
|
||||
import { activityElapsedLabel } from "@/lib/chat-message-view";
|
||||
|
||||
const labels = {
|
||||
working: "正在处理任务…",
|
||||
@@ -27,17 +29,40 @@ prefetchOnIdle(importThinkingOrb);
|
||||
|
||||
export type AgentActivityState = OrbState;
|
||||
|
||||
function ActivityElapsed({ startedAt }: Readonly<{ startedAt: number }>) {
|
||||
const [now, setNow] = useState(() => Date.now());
|
||||
useEffect(() => {
|
||||
const timer = window.setInterval(() => setNow(Date.now()), 1000);
|
||||
return () => window.clearInterval(timer);
|
||||
}, []);
|
||||
const label = activityElapsedLabel(startedAt, now);
|
||||
if (!label) return null;
|
||||
return <span className="agent-activity-status__elapsed" aria-hidden="true">{label}</span>;
|
||||
}
|
||||
|
||||
export function AgentActivityStatus({
|
||||
state,
|
||||
label = labels[state],
|
||||
startedAt,
|
||||
completedTrail,
|
||||
}: Readonly<{
|
||||
state: AgentActivityState;
|
||||
label?: string;
|
||||
startedAt?: number;
|
||||
completedTrail?: string;
|
||||
}>) {
|
||||
return (
|
||||
<div className="agent-activity-status" role="status">
|
||||
<ThinkingOrb aria-hidden="true" state={state} size={20} />
|
||||
<span key={label} className="agent-activity-status__text">{label}</span>
|
||||
<div className="agent-activity-status">
|
||||
<div className="agent-activity-status__row">
|
||||
<div className="agent-activity-status__live" role="status">
|
||||
<ThinkingOrb aria-hidden="true" state={state} size={20} />
|
||||
<span key={label} className="agent-activity-status__text">{label}</span>
|
||||
</div>
|
||||
{startedAt ? <ActivityElapsed key={startedAt} startedAt={startedAt} /> : null}
|
||||
</div>
|
||||
{completedTrail ? (
|
||||
<p className="agent-activity-status__trail" aria-hidden="true">{completedTrail}</p>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -97,7 +97,12 @@ export function ChatMessageRow({
|
||||
{message.role === "assistant" ? (
|
||||
<>
|
||||
{showActivity && (
|
||||
<AgentActivityStatus state={activityState} label={activityLabel} />
|
||||
<AgentActivityStatus
|
||||
state={activityState}
|
||||
label={activityLabel}
|
||||
startedAt={message.activity?.startedAt}
|
||||
completedTrail={message.activity?.completedTrail}
|
||||
/>
|
||||
)}
|
||||
{message.text && (
|
||||
<ChatMessageContent
|
||||
|
||||
@@ -2,27 +2,14 @@
|
||||
|
||||
import { Check, ChevronDown } from "lucide-react";
|
||||
import type { CompletedActivityReceiptView } from "@/lib/rectification-activity-receipt";
|
||||
import { RECTIFICATION_TOOL_DONE_LABELS } from "@/lib/rectification-activity-labels";
|
||||
import type {
|
||||
PublicRectificationMethod,
|
||||
PublicRectificationTool,
|
||||
} from "@/lib/rectification-agentic/v9/public-receipt";
|
||||
import { PUBLIC_RECTIFICATION_METHOD_LABELS } from "@/lib/rectification-varga-sentence";
|
||||
|
||||
const TOOL_LABELS: Readonly<Record<PublicRectificationTool, string>> = {
|
||||
"rectification-read-case": "读取校正记录",
|
||||
"rectification-set-focus": "设置对话焦点",
|
||||
"rectification-resolve-focus": "处理当前焦点",
|
||||
"rectification-record-evidence-batch": "整理多条事件证据",
|
||||
"rectification-propose-evidence": "整理事件证据",
|
||||
"rectification-confirm-evidence": "确认事件证据",
|
||||
"rectification-revise-evidence": "修订事件证据",
|
||||
"rectification-compare-candidates": "比较候选时间",
|
||||
"rectification-read-diagnostics": "检查候选稳健性",
|
||||
"rectification-offer-candidates": "生成候选建议",
|
||||
"rectification-accept-candidate": "采用候选时间",
|
||||
"rectification-confirm-birth-time": "确认校正时间",
|
||||
"rectification-close-case": "完成校正记录",
|
||||
};
|
||||
const TOOL_LABELS = RECTIFICATION_TOOL_DONE_LABELS;
|
||||
|
||||
const METHOD_LABELS = PUBLIC_RECTIFICATION_METHOD_LABELS;
|
||||
|
||||
|
||||
@@ -4,7 +4,12 @@ import { ArrowUp, Square } from "lucide-react";
|
||||
import { useCallback, useEffect, useId, useLayoutEffect, useRef, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { parseAgentReply } from "@/lib/agent-reply";
|
||||
import type { ChatMessage, ChatMessageView } from "@/lib/chat-message-view";
|
||||
import { nextActivityView, type ChatMessage, type ChatMessageView } from "@/lib/chat-message-view";
|
||||
import {
|
||||
RECTIFICATION_TOOL_PROGRESS_LABELS,
|
||||
rectificationCompletedTrail,
|
||||
rectificationToolActivityPhase,
|
||||
} from "@/lib/rectification-activity-labels";
|
||||
import {
|
||||
createRectificationActivityReceiptState,
|
||||
receiptFromRectificationActivityState,
|
||||
@@ -348,7 +353,11 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
...(action === "message"
|
||||
? [{ role: "user", text: trimmed, renderKey: userRenderKey, state: "settled" } satisfies RenderMessage]
|
||||
: []),
|
||||
{ role: "assistant", text: "", renderKey: assistantRenderKey, state: "thinking" },
|
||||
{ role: "assistant", text: "", renderKey: assistantRenderKey, state: "thinking", activity: {
|
||||
phase: "evidence-validation",
|
||||
label: "正在处理…",
|
||||
startedAt: Date.now(),
|
||||
} },
|
||||
]);
|
||||
setDraft("");
|
||||
|
||||
@@ -426,7 +435,15 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
raw += event.text;
|
||||
const parsed = parseAgentReply(raw);
|
||||
setMessages((current) => current.map((message) => message.renderKey === assistantRenderKey
|
||||
? { ...message, text: parsed.text, state: "streaming" }
|
||||
? {
|
||||
...message,
|
||||
text: parsed.text,
|
||||
state: "streaming",
|
||||
activity: nextActivityView(message.activity, {
|
||||
phase: "answer-composition",
|
||||
label: "正在组织回答…",
|
||||
}),
|
||||
}
|
||||
: message));
|
||||
} else if (event.type === "attempt.reset") {
|
||||
raw = "";
|
||||
@@ -434,7 +451,18 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
completedReceipt = receiptFromRectificationActivityState(activityReceiptState);
|
||||
completedTurnId = undefined;
|
||||
setMessages((current) => current.map((message) => message.renderKey === assistantRenderKey
|
||||
? { ...message, text: "", state: "thinking", completedReceipt: undefined, failed: false, turnId: undefined }
|
||||
? {
|
||||
...message,
|
||||
text: "",
|
||||
state: "thinking",
|
||||
completedReceipt: undefined,
|
||||
failed: false,
|
||||
turnId: undefined,
|
||||
activity: nextActivityView(undefined, {
|
||||
phase: "evidence-validation",
|
||||
label: "正在处理…",
|
||||
}),
|
||||
}
|
||||
: message));
|
||||
} else if (event.type === "run.failed") {
|
||||
streamFailed = true;
|
||||
@@ -447,7 +475,19 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
} else if (event.type === "tool.activity") {
|
||||
const tool = isPublicRectificationTool(event.tool) ? event.tool : null;
|
||||
if (!tool) continue;
|
||||
if (event.status === "started") continue;
|
||||
if (event.status === "started") {
|
||||
setMessages((current) => current.map((message) => message.renderKey === assistantRenderKey
|
||||
? {
|
||||
...message,
|
||||
activity: nextActivityView(message.activity, {
|
||||
phase: rectificationToolActivityPhase(tool),
|
||||
label: RECTIFICATION_TOOL_PROGRESS_LABELS[tool],
|
||||
completedTrail: rectificationCompletedTrail(activityReceiptState.completedSteps),
|
||||
}),
|
||||
}
|
||||
: message));
|
||||
continue;
|
||||
}
|
||||
if (event.status !== "completed" && event.status !== "failed") continue;
|
||||
activityReceiptState = reduceRectificationActivityReceipt(activityReceiptState, {
|
||||
tool,
|
||||
@@ -473,20 +513,24 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
completedReceipt,
|
||||
failed: false,
|
||||
turnId: completedTurnId,
|
||||
activity: undefined,
|
||||
}];
|
||||
}
|
||||
if (streamFailed || hasActivityReceipt(completedReceipt)) {
|
||||
if (streamFailed || hasActivityReceipt(completedReceipt) || parsed.text) {
|
||||
return [{
|
||||
...message,
|
||||
text: "",
|
||||
text: parsed.text,
|
||||
state: "settled",
|
||||
completedReceipt,
|
||||
failed: true,
|
||||
activity: undefined,
|
||||
}];
|
||||
}
|
||||
return [];
|
||||
}));
|
||||
if (!succeeded && completedReceipt.failedTool) {
|
||||
if (!succeeded && parsed.text) {
|
||||
setError((current) => current || "回答未完成,已保留现有内容;本次不会扣点。");
|
||||
} else if (!succeeded && completedReceipt.failedTool) {
|
||||
setError(completedReceipt.failedTool === "rectification-compare-candidates"
|
||||
? "候选比较未完成,当前进度已保留。"
|
||||
: "本轮处理未完成,当前进度已保留。请稍后再试。");
|
||||
@@ -719,7 +763,15 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
&& !readonly
|
||||
&& regeneratingMessageKey === null;
|
||||
const displayedMessage = regenerating
|
||||
? { ...message, text: "", state: "thinking" as const }
|
||||
? {
|
||||
...message,
|
||||
text: "",
|
||||
state: "thinking" as const,
|
||||
activity: nextActivityView(undefined, {
|
||||
phase: "answer-composition",
|
||||
label: "正在组织回答…",
|
||||
}),
|
||||
}
|
||||
: message;
|
||||
const vargaSentence = message.state === "settled" && !message.failed
|
||||
? vargaSentenceFromMethods(message.completedReceipt?.methods)
|
||||
@@ -728,13 +780,15 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
<div key={message.renderKey} className="rectification-message-wrap rectification-message-entry">
|
||||
{message.state === "settled" && message.failed && (
|
||||
<p className="rectification-activity-failure" role="status">
|
||||
本轮处理未完成,已保留服务端记录的执行进度。
|
||||
{message.text
|
||||
? "回答未完成,已保留现有内容;本次不会扣点。"
|
||||
: "本轮处理未完成,已保留服务端记录的执行进度。"}
|
||||
</p>
|
||||
)}
|
||||
{(!message.failed || Boolean(displayedMessage.text) || regenerating) && (
|
||||
<ChatMessageRow
|
||||
message={displayedMessage}
|
||||
showActivity={displayedMessage.state === "thinking"}
|
||||
showActivity={displayedMessage.state !== "settled"}
|
||||
vargaSentence={vargaSentence}
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user