From ccdae76dd32a59e52b071b1eff75bd038eb4981b Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Tue, 1 Sep 2026 23:10:28 +0000 Subject: [PATCH] refactor(chat): render rectification activity through the shared step timeline The rectification surface had its own activity pipeline: a trace panel with a 20px canvas orb clipped inside a 14px marker, a list that never collapsed, a second receipt disclosure under every reply, an inline failure banner and a staged label while regenerating. Its trace and receipt are now projected onto ConsultationTimelineRow so both surfaces render one ConsultationRunTimeline with one live marker and one settled summary; receipt methods become source chips on the last completed row. The unreachable sections-report/step-tree path, the trace panel, the receipt component and the thinking-orbs dependency are removed. The server-side drop of rectification thinking deltas is untouched. BUG-476 Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45 --- docs/BUG_HISTORY.md | 16 ++ frontend/package-lock.json | 107 --------- frontend/package.json | 1 - frontend/src/app/globals.css | 152 ------------- .../src/components/agent-activity-status.tsx | 96 ++------ frontend/src/components/chat-message-row.tsx | 28 +-- .../components/completed-activity-receipt.tsx | 113 ---------- .../consultation-thinking-report.tsx | 69 ------ .../components/rectification-agentic-chat.tsx | 29 ++- .../src/components/thinking-step-tree.tsx | 213 ------------------ .../src/lib/rectification-timeline-adapter.ts | 85 +++++++ .../chat-bundle-splitting-contract.test.ts | 20 +- frontend/tests/chat-stream-layout.test.ts | 37 ++- .../class-name-definition-contract.test.ts | 1 - .../rectification-activity-receipt.test.ts | 18 +- .../tests/rectification-agentic-entry.test.ts | 16 +- .../rectification-timeline-adapter.test.ts | 131 +++++++++++ 17 files changed, 313 insertions(+), 819 deletions(-) delete mode 100644 frontend/src/components/completed-activity-receipt.tsx delete mode 100644 frontend/src/components/consultation-thinking-report.tsx delete mode 100644 frontend/src/components/thinking-step-tree.tsx create mode 100644 frontend/src/lib/rectification-timeline-adapter.ts create mode 100644 frontend/tests/rectification-timeline-adapter.test.ts diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index 8709b6a9..8db2208c 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -7314,3 +7314,19 @@ - 相关记录:BUG-474 - 复发自:无 - 修复版本:待发布 + +## BUG-476 | 生时校正会话的 Agent 活动 UI 与普通会话是两套,live 标记被 14px 格子裁切 + +- 状态:resolved +- 首次发现:2026-09-01 +- 最近更新:2026-09-01 +- 影响面:`rectification-agentic-chat.tsx`、`agent-activity-status.tsx`、`chat-message-row.tsx`、`globals.css`、`package.json`(`thinking-orbs`) +- 用户现象:校正会话里步骤标记是一个 20px 的 canvas 小球,被压在 14px 的格子里边缘裁切;结算后步骤列表永远全展开,下面还多挂一个「本轮完成 · N 个步骤」折叠块;失败时消息上方多一行红字;重新生成时先出现一个假的「正在组织回答…」;普通会话则是行内 spinner、结算收成「已完成 N 步」一行。两边看起来不像一个产品。 +- 触发条件:任何一次校正回合与任何一次咨询回合并排对比。 +- 根因:校正走 `activityTrace` + `AgentActivityStatus` 的 trace 分支,普通会话走 `ConsultationRunTimeline`;`chat-message-row.tsx` 里并存三条思考渲染路径(timeline、`ConsultationThinkingReport`+`ThinkingStepTree`、trace panel),其中 sections report 已无任何调用方可达;`.conversation.is-rectification .agent-thinking-marker { 14px }` 覆写与 `ThinkingOrb size={20}` 冲突。 +- 修复:新增 `lib/rectification-timeline-adapter.ts`,把 trace/receipt/activity 映射成 `ConsultationTimelineRow`(tool → calculate 行;失败 tool 标签加「未完成」;回执方法作为最后一条完成行的 sources chips,去重 ≤8;无 live 行时把「正在…」类活动文案作为 live 行,answer-composition 归 write)。校正消息由此走同一个 `ConsultationRunTimeline`。删除 `consultation-thinking-report.tsx`、`thinking-step-tree.tsx`、`completed-activity-receipt.tsx` 及其 CSS;`AgentActivityStatus` 只保留无 timeline 的兜底,live 标记统一 `InlineSpinner` 12px,移除 `thinking-orbs` 依赖;删除 14px 覆写;失败态改走既有 error 通知;重新生成显示 queued 行由真实事件填充。校正 `thinking.delta` 仍在服务端公开边界丢弃,本轮未动。 +- 验证:`tests/rectification-timeline-adapter.test.ts`;`chat-stream-layout`、`chat-bundle-splitting-contract`、`rectification-agentic-entry`、`rectification-activity-receipt` 中锁死路径的断言按「注明原值与错因」改为锁新路径。 +- 防复发:任何会话面的 Agent 活动都必须投影成 `ConsultationTimelineRow` 交给 `ConsultationRunTimeline`;live 标记只允许 `InlineSpinner`;不得再引入第二种等待词汇。 +- 相关记录:BUG-474、BUG-475 +- 复发自:无 +- 修复版本:待发布 diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 542f3768..13b48200 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -39,7 +39,6 @@ "sonner": "^2.0.7", "tailwind-merge": "^3.6.0", "tailwindcss": "^4.3.2", - "thinking-orbs": "^0.1.1", "tsx": "^4.23.1", "tw-animate-css": "^1.4.0", "zod": "^3.25.76" @@ -1745,9 +1744,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1764,9 +1760,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1783,9 +1776,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1802,9 +1792,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1821,9 +1808,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1840,9 +1824,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1859,9 +1840,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1878,9 +1856,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -1897,9 +1872,6 @@ "cpu": [ "arm" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1922,9 +1894,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1947,9 +1916,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1972,9 +1938,6 @@ "cpu": [ "riscv64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1997,9 +1960,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -2022,9 +1982,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -2047,9 +2004,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -2072,9 +2026,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -2508,9 +2459,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2527,9 +2475,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2546,9 +2491,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2565,9 +2507,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3886,9 +3825,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3903,9 +3839,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -3920,9 +3853,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -3937,9 +3867,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -4205,9 +4132,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4224,9 +4148,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -4243,9 +4164,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -4262,9 +4180,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -9371,9 +9286,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -9394,9 +9306,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -9417,9 +9326,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -9440,9 +9346,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -13217,16 +13120,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/thinking-orbs": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/thinking-orbs/-/thinking-orbs-0.1.1.tgz", - "integrity": "sha512-nLvLTGJtk74K13MmP7XiRdzFiQx7UsoZAIyBZNgXyl7Q3h2mdz0r3eKn9LCsuzb3DGOVjwDvMhtnAkIqJJRVQA==", - "license": "MIT", - "peerDependencies": { - "react": ">=18.0.0", - "react-dom": ">=18.0.0" - } - }, "node_modules/throttle-debounce": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index ebec2b02..980a68e4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -48,7 +48,6 @@ "sonner": "^2.0.7", "tailwind-merge": "^3.6.0", "tailwindcss": "^4.3.2", - "thinking-orbs": "^0.1.1", "tsx": "^4.23.1", "tw-animate-css": "^1.4.0", "zod": "^3.25.76" diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 6655e9d4..4d629c32 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -426,13 +426,6 @@ button:disabled { cursor: default; opacity: .45; } gap: var(--space-2); min-height: 24px; } -.agent-thinking-step.is-think:has(.message-thinking) { - display: block; - min-height: 0; -} -.agent-thinking-step.is-think .message-thinking { - margin-bottom: 0; -} .agent-thinking-marker { width: 20px; height: 20px; @@ -449,8 +442,6 @@ button:disabled { cursor: default; opacity: .45; } } .agent-activity-status__row { min-height: 24px; display: flex; align-items: center; gap: var(--space-2); min-width: 0; width: 100%; } .agent-activity-status__live { min-height: 24px; display: flex; align-items: center; gap: var(--space-2); min-width: 0; } -.agent-activity-status canvas, -.agent-thinking-marker canvas { flex: 0 0 auto; } .agent-activity-status__elapsed, .agent-thinking-elapsed { flex: 0 0 auto; @@ -1054,67 +1045,10 @@ button:disabled { cursor: default; opacity: .45; } line-height: 1.55; white-space: pre-wrap; } -.consultation-step-tree__group { - display: grid; - gap: var(--space-2); -} -.consultation-step-tree__stage { - display: grid; - grid-template-columns: 20px minmax(0, 1fr); - align-items: center; - gap: var(--space-2); - min-height: 24px; - margin: 0; - color: var(--color-ink-strong); - font-family: inherit; - font-size: var(--type-caption); - font-weight: 600; - line-height: 1.35; - text-wrap: balance; -} -.consultation-step-tree__stage-index, -.consultation-step-tree__stage-mark { - width: 20px; - height: 20px; - display: grid; - place-items: center; - border-radius: var(--radius-xs); - background: var(--color-canvas-muted); - box-shadow: inset 0 0 0 1px var(--color-border); - color: var(--color-ink-secondary); - font-size: 10px; - font-weight: 600; - font-variant-numeric: tabular-nums; - letter-spacing: 0.02em; -} -.consultation-step-tree__stage-mark { - background: transparent; - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-border) 80%, transparent); -} -.consultation-step-tree__stage-title { - min-width: 0; -} -.agent-thinking-marker.is-pending { - background: var(--color-canvas-muted); - box-shadow: inset 0 0 0 1px var(--color-border); -} -.agent-thinking-step.is-more { - color: var(--color-ink-tertiary); -} .consultation-thinking-report { display: grid; gap: var(--space-6); } -.consultation-step-tree__group + .consultation-step-tree__group { - margin-top: var(--space-6); -} -.consultation-step-tree__reasoning { - margin: 0 0 var(--space-4); - color: var(--color-ink-secondary); - font-size: var(--type-caption); - line-height: 1.55; - white-space: pre-wrap; -} .consultation-report-analysis { min-width: 0; } @@ -1124,11 +1058,6 @@ button:disabled { cursor: default; opacity: .45; } .consultation-report-analysis .message-markdown { color: var(--color-ink-strong); } -.consultation-thinking-report .message-thinking { - margin-bottom: 0; - padding-bottom: var(--space-3); - border-bottom: 1px solid color-mix(in srgb, var(--color-border) 72%, transparent); -} .consultation-run-timeline { min-width: 0; } @@ -2682,14 +2611,6 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class max-width: 720px; padding-bottom: var(--rectification-jump-clearance); } -.conversation.is-rectification .message-assistant .agent-thinking-step { - grid-template-columns: 14px minmax(0, 1fr); - gap: 6px; -} -.conversation.is-rectification .message-assistant .agent-thinking-marker { - width: 14px; - height: 14px; -} .conversation.is-rectification .message-actions { margin-inline-start: var(--assistant-content-inset); } @@ -3545,78 +3466,6 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class } .rectification-message-wrap { display: grid; gap: var(--space-1); } -.rectification-activity-receipt { - width: min(720px, 100%); - margin: 0 0 var(--space-2) 36px; - color: var(--color-ink-tertiary); - font-size: var(--type-caption); -} -.rectification-activity-receipt summary { - display: flex; - min-height: 32px; - align-items: center; - justify-content: space-between; - gap: var(--space-3); - padding: 0; - color: var(--color-ink-tertiary); - cursor: pointer; - font-weight: 500; - list-style: none; -} -.rectification-activity-receipt summary::-webkit-details-marker { display: none; } -.rectification-activity-receipt summary:focus-visible { - border-radius: var(--radius-sm); - outline: 2px solid var(--color-focus); - outline-offset: 3px; -} -.rectification-activity-receipt__summary-main, -.rectification-activity-receipt__toggle { - display: inline-flex; - align-items: center; - gap: var(--space-1); -} -.rectification-activity-receipt__summary-main svg { color: var(--color-success); } -.rectification-activity-receipt__toggle { flex: 0 0 auto; color: var(--color-ink-tertiary); } -.rectification-activity-receipt__toggle svg { transition: transform 160ms ease; } -.rectification-activity-receipt[open] .rectification-activity-receipt__toggle svg { transform: rotate(180deg); } -.rectification-activity-receipt__failure-mark { - display: inline-grid; - width: 15px; - height: 15px; - place-items: center; - border: 1px solid currentColor; - border-radius: 50%; - font-size: 10px; - font-weight: 700; - line-height: 1; -} -.rectification-activity-receipt.is-failed, -.rectification-activity-failure { color: var(--color-danger); } -.rectification-activity-failure { margin: 0 0 var(--space-2) 36px; font-size: var(--type-caption); } -.rectification-activity-receipt__details { - display: grid; - gap: var(--space-3); - margin-top: var(--space-1); - padding: var(--space-3) 0 var(--space-2) 19px; - border-left: 1px solid var(--color-border); -} -.rectification-activity-receipt__details section { display: grid; gap: var(--space-1); } -.rectification-activity-receipt__details h3 { - margin: 0; - color: var(--color-ink-tertiary); - font-size: var(--type-overline); - font-weight: 650; -} -.rectification-activity-receipt__details p { margin: 0; color: var(--color-ink-secondary); line-height: 1.65; } -.rectification-activity-receipt__method-groups { display: grid; gap: 2px; } -.rectification-activity-receipt__method-groups strong { color: var(--color-ink-secondary); font-weight: 600; } - -@media (max-width: 640px) { - .rectification-activity-receipt, - .rectification-activity-failure { margin-left: 32px; } - .rectification-activity-receipt summary { align-items: flex-start; } - .rectification-activity-receipt__toggle { padding-left: var(--space-1); } -} @media (prefers-reduced-motion: reduce) { .agent-activity-status__text { @@ -3624,6 +3473,5 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class background: none; -webkit-text-fill-color: currentColor; } - .rectification-activity-receipt__toggle svg { transition: none; } .technique-audit > summary::before { transition: none; } } diff --git a/frontend/src/components/agent-activity-status.tsx b/frontend/src/components/agent-activity-status.tsx index eba9b608..641895c8 100644 --- a/frontend/src/components/agent-activity-status.tsx +++ b/frontend/src/components/agent-activity-status.tsx @@ -1,13 +1,12 @@ "use client"; import { useEffect, useState } from "react"; -import dynamic from "next/dynamic"; import { Check } from "lucide-react"; -import type { OrbState } from "thinking-orbs"; -import { prefetchOnIdle } from "@/components/chat-chunk-prefetch"; +import { InlineSpinner } from "@/components/inline-spinner"; import { activityCompletedSteps, activityElapsedLabel } from "@/lib/chat-message-view"; -import type { AgentActivityTraceItem } from "@/lib/agent-activity-trace"; + +export type AgentActivityState = "working" | "searching" | "solving" | "listening" | "composing" | "shaping"; const labels = { working: "正在处理任务…", @@ -16,20 +15,7 @@ const labels = { listening: "正在聆听…", composing: "正在组织回答…", shaping: "正在生成结果…", -} as const satisfies Record; - -const importThinkingOrb = () => import("thinking-orbs"); - -const ThinkingOrb = dynamic(async () => (await importThinkingOrb()).ThinkingOrb, { - loading: () => ( -