Files
Jyotisha/frontend/src/lib/rectification-timeline-adapter.ts
T
Jesse_Chen 6356d7b549 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
2026-09-02 04:29:01 +00:00

86 lines
3.1 KiB
TypeScript

import type { AgentActivityTraceItem } from "./agent-activity-trace.ts";
import type { AgentActivityView } from "./chat-message-view.ts";
import type { ConsultationTimelineRow } from "./consultation-run-timeline.ts";
import type { CompletedActivityReceiptView } from "./rectification-activity-receipt.ts";
import { PUBLIC_RECTIFICATION_METHOD_LABELS } from "./rectification-varga-sentence.ts";
export const RECTIFICATION_TIMELINE_SOURCE_LIMIT = 8;
export const RECTIFICATION_TIMELINE_LIVE_ID = "activity-live";
const FAILED_SUFFIX = "未完成";
export type RectificationTimelineInput = Readonly<{
trace: readonly AgentActivityTraceItem[] | undefined;
receipt: CompletedActivityReceiptView | undefined;
activity: AgentActivityView | undefined;
settled: boolean;
}>;
function methodChips(receipt: CompletedActivityReceiptView | undefined): string[] {
if (!receipt) return [];
const seen = new Set<string>();
const chips: string[] = [];
for (const method of receipt.methods) {
const label = PUBLIC_RECTIFICATION_METHOD_LABELS[method];
if (!label || seen.has(label)) continue;
seen.add(label);
chips.push(label);
if (chips.length >= RECTIFICATION_TIMELINE_SOURCE_LIMIT) break;
}
return chips;
}
/** Progress labels are the only ones that name an action still under way. */
function isProgressLabel(label: string): boolean {
return /^正在/.test(label.trim());
}
/**
* Project the rectification agent's tool trace and receipt onto the same
* timeline rows the consultation surface renders, so both sessions share one
* step list, one live marker and one settled summary. Thinking text never
* reaches this surface: the public stream drops it on the server.
*/
export function rectificationTimelineRows(input: RectificationTimelineInput): ConsultationTimelineRow[] {
const failedTool = input.receipt?.failedTool;
const rows: ConsultationTimelineRow[] = (input.trace ?? []).map((item) => {
if (item.kind === "think") {
return {
id: item.id,
kind: "think",
status: item.status,
label: item.label,
...(item.text?.trim() ? { thinkingText: item.text } : {}),
};
}
const failed = item.status === "done" && failedTool !== undefined && item.tool === failedTool;
return {
id: item.id,
kind: "calculate",
status: item.status,
label: failed ? `${item.label}${FAILED_SUFFIX}` : item.label,
};
});
const chips = methodChips(input.receipt);
if (chips.length > 0) {
for (let index = rows.length - 1; index >= 0; index -= 1) {
const row = rows[index];
if (!row || row.kind !== "calculate" || row.status !== "done" || row.label.endsWith(FAILED_SUFFIX)) continue;
rows[index] = { ...row, sources: chips };
break;
}
}
const hasLiveRow = rows.some((row) => row.status === "live");
const label = input.activity?.label ?? "";
if (!input.settled && !hasLiveRow && isProgressLabel(label)) {
rows.push({
id: RECTIFICATION_TIMELINE_LIVE_ID,
kind: input.activity?.phase === "answer-composition" ? "write" : "calculate",
status: "live",
label,
});
}
return rows;
}