fix(rectification): surface real activity context
This commit is contained in:
@@ -5,7 +5,12 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { parseAgentReply } from "@/lib/agent-reply";
|
||||
import type { ChatMessage, ChatMessageView } from "@/lib/chat-message-view";
|
||||
import { membershipHref } from "@/lib/membership";
|
||||
import { PUBLIC_RECTIFICATION_PHASES, type PublicRectificationPhase } from "@/lib/rectification-agentic/v9/public-receipt";
|
||||
import {
|
||||
isPublicRectificationMethod,
|
||||
isPublicRectificationTool,
|
||||
type PublicRectificationMethod,
|
||||
type PublicRectificationTool,
|
||||
} from "@/lib/rectification-agentic/v9/public-receipt";
|
||||
import type { PublicLanguageModel } from "@/lib/public-models";
|
||||
import { ChatMessageRow } from "./chat-message-row";
|
||||
import { ModelSelector } from "./model-selector";
|
||||
@@ -21,6 +26,7 @@ type PersistedTurn = Readonly<{
|
||||
status: string;
|
||||
phases: readonly string[];
|
||||
tools: readonly string[];
|
||||
methods?: readonly string[];
|
||||
skill_name?: string;
|
||||
skill_version?: string;
|
||||
}> | null;
|
||||
@@ -58,38 +64,52 @@ type RectificationAgenticChatProps = Readonly<{
|
||||
type RenderMessage = ChatMessageView & {
|
||||
renderKey: string;
|
||||
/** V9 execution-receipt steps; separate from shared consultation activity. */
|
||||
receiptActivity?: readonly string[];
|
||||
receiptActivity?: ReceiptActivity;
|
||||
receiptStatus?: string;
|
||||
};
|
||||
|
||||
const PHASE_LABELS: Readonly<Partial<Record<PublicRectificationPhase, string>>> = {
|
||||
"run.started": "开始本轮执行",
|
||||
"skill.started": "正在加载专用方法",
|
||||
"skill.loaded": "专用方法已加载",
|
||||
"case.loaded": "已读取校正记录",
|
||||
"evidence.proposed": "记录了一条事件草稿",
|
||||
"evidence.confirmed": "事件已确认",
|
||||
"candidates.comparing": "正在比较候选时间",
|
||||
"candidates.updated": "候选已更新",
|
||||
"diagnostics.completed": "稳健性诊断完成",
|
||||
"candidate.accepted": "已采用候选时间",
|
||||
"birth_time.confirmed": "校正时间已确认",
|
||||
"run.completed": "本轮完成",
|
||||
"run.failed": "本轮失败",
|
||||
type ReceiptActivity = Readonly<{
|
||||
steps: readonly string[];
|
||||
methods: readonly string[];
|
||||
}>;
|
||||
|
||||
const TOOL_LABELS: Readonly<Record<PublicRectificationTool, string>> = {
|
||||
"rectification-read-case": "读取校正记录",
|
||||
"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": "关闭校正记录",
|
||||
};
|
||||
|
||||
function isPublicPhase(value: unknown): value is PublicRectificationPhase {
|
||||
return typeof value === "string" && (PUBLIC_RECTIFICATION_PHASES as readonly string[]).includes(value);
|
||||
}
|
||||
const METHOD_LABELS: Readonly<Record<PublicRectificationMethod, string>> = {
|
||||
"d1-rashi": "D1 本命盘",
|
||||
"d2-hora": "D2 财帛分盘",
|
||||
"d4-chaturthamsha": "D4 地产迁移分盘",
|
||||
"d9-navamsa": "D9 婚姻分盘",
|
||||
"d10-dashamsa": "D10 事业分盘",
|
||||
"d11-labhamsha": "D11 收益分盘",
|
||||
"d24-chaturvimshamsha": "D24 教育分盘",
|
||||
"d30-trimshamsha": "D30 健康压力分盘",
|
||||
"vimshottari-dasha": "Vimshottari 大运",
|
||||
"narayana-dasha": "Narayana 大运",
|
||||
gochara: "Gochara 行运",
|
||||
ashtakavarga: "Ashtakavarga",
|
||||
shadbala: "Shadbala",
|
||||
"arudha-pada": "Arudha Pada",
|
||||
"functional-benefic-malefic": "功能吉凶星",
|
||||
};
|
||||
|
||||
function labelForPhase(phase: string): string {
|
||||
return isPublicPhase(phase) ? (PHASE_LABELS[phase] ?? phase) : phase;
|
||||
}
|
||||
|
||||
function activityFromReceipt(receipt: PersistedTurn["receipt"]): string[] {
|
||||
if (!receipt) return [];
|
||||
const phases = (receipt.phases ?? []).filter(isPublicPhase);
|
||||
return phases.map(labelForPhase).slice(0, 12);
|
||||
function activityFromReceipt(receipt: PersistedTurn["receipt"]): ReceiptActivity {
|
||||
if (!receipt) return { steps: [], methods: [] };
|
||||
return {
|
||||
steps: [...new Set((receipt.tools ?? []).filter(isPublicRectificationTool).map((tool) => TOOL_LABELS[tool]))],
|
||||
methods: [...new Set((receipt.methods ?? []).filter(isPublicRectificationMethod).map((method) => METHOD_LABELS[method]))],
|
||||
};
|
||||
}
|
||||
|
||||
export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
@@ -213,13 +233,14 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
...(action === "message"
|
||||
? [{ role: "user", text: trimmed, renderKey: userRenderKey, state: "settled" } satisfies RenderMessage]
|
||||
: []),
|
||||
{ role: "assistant", text: "", renderKey: assistantRenderKey, state: "thinking", receiptActivity: [] },
|
||||
{ role: "assistant", text: "", renderKey: assistantRenderKey, state: "thinking", receiptActivity: { steps: [], methods: [] } },
|
||||
]);
|
||||
setDraft("");
|
||||
|
||||
let raw = "";
|
||||
let liveActivity: string[] = [];
|
||||
const activitySet = new Set<string>();
|
||||
let liveActivity: ReceiptActivity = { steps: [], methods: [] };
|
||||
const activitySteps = new Set<string>();
|
||||
const activityMethods = new Set<string>();
|
||||
try {
|
||||
const response = await fetch("/api/rectification/agent", {
|
||||
method: "POST",
|
||||
@@ -268,9 +289,9 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
buffer = lines.pop() ?? "";
|
||||
for (const line of lines) {
|
||||
if (!line.trim()) continue;
|
||||
let event: { type?: unknown; text?: unknown; message?: unknown };
|
||||
let event: { type?: unknown; text?: unknown; message?: unknown; tool?: unknown; methods?: unknown };
|
||||
try {
|
||||
event = JSON.parse(line) as { type?: unknown; text?: unknown; message?: unknown };
|
||||
event = JSON.parse(line) as { type?: unknown; text?: unknown; message?: unknown; tool?: unknown; methods?: unknown };
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
@@ -289,10 +310,16 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
setError(typeof event.message === "string" ? event.message : "生时校正暂时不可用,请稍后再试。");
|
||||
} else if (event.type === "run.completed") {
|
||||
completed = true;
|
||||
} else if (isPublicPhase(event.type)) {
|
||||
if (!activitySet.has(event.type)) {
|
||||
activitySet.add(event.type);
|
||||
liveActivity = [...liveActivity, labelForPhase(event.type)];
|
||||
} else {
|
||||
const tool = isPublicRectificationTool(event.tool) ? event.tool : null;
|
||||
if (tool) activitySteps.add(TOOL_LABELS[tool]);
|
||||
if (Array.isArray(event.methods)) {
|
||||
for (const method of event.methods) {
|
||||
if (isPublicRectificationMethod(method)) activityMethods.add(METHOD_LABELS[method]);
|
||||
}
|
||||
}
|
||||
if (tool || activityMethods.size !== liveActivity.methods.length) {
|
||||
liveActivity = { steps: [...activitySteps], methods: [...activityMethods] };
|
||||
setMessages((current) => current.map((message) => message.renderKey === assistantRenderKey
|
||||
? { ...message, receiptActivity: liveActivity }
|
||||
: message));
|
||||
@@ -386,15 +413,23 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
|
||||
)}
|
||||
{messages.map((message) => (
|
||||
<div key={message.renderKey} className="rectification-message-wrap">
|
||||
<ChatMessageRow message={message} />
|
||||
{message.receiptActivity && message.receiptActivity.length > 0 && (
|
||||
{message.receiptActivity && (message.receiptActivity.steps.length > 0 || message.receiptActivity.methods.length > 0) && (
|
||||
<details className="rectification-activity">
|
||||
<summary>本轮做了什么</summary>
|
||||
<ol>
|
||||
{message.receiptActivity.map((step) => <li key={step}>{step}</li>)}
|
||||
</ol>
|
||||
{message.receiptActivity.steps.length > 0 && (
|
||||
<ol>
|
||||
{message.receiptActivity.steps.map((step) => <li key={step}>{step}</li>)}
|
||||
</ol>
|
||||
)}
|
||||
{message.receiptActivity.methods.length > 0 && (
|
||||
<div className="rectification-activity-methods">
|
||||
<span>使用技法</span>
|
||||
<ul>{message.receiptActivity.methods.map((method) => <li key={method}>{method}</li>)}</ul>
|
||||
</div>
|
||||
)}
|
||||
</details>
|
||||
)}
|
||||
<ChatMessageRow message={message} />
|
||||
</div>
|
||||
))}
|
||||
{candidateResult?.selectionAllowed && candidateResult.candidates.length > 0 && (
|
||||
|
||||
Reference in New Issue
Block a user