feat: render workflow receipt evidence rows

This commit is contained in:
732642856
2026-07-21 15:28:46 +08:00
parent d6726e849d
commit 9db00c6d33
5 changed files with 54 additions and 4 deletions
+10 -1
View File
@@ -2098,6 +2098,15 @@ export default function Home() {
}
if (!response.body) throw new Error("浏览器未收到可读取的回答流");
const techniqueTruth = response.headers.get("x-jyotish-technique-truth") ?? "unknown";
const workflowReceipt = {
route: response.headers.get("x-jyotish-workflow-route") ?? "unknown",
status: response.headers.get("x-jyotish-workflow-status") ?? "unknown",
preciseTiming: response.headers.get("x-jyotish-precise-timing") ?? "unknown",
missingLayers: (response.headers.get("x-jyotish-missing-layers") ?? "none")
.split(",")
.map((item) => item.trim())
.filter((item) => item && item !== "none"),
};
const reader = response.body.getReader();
const decoder = new TextDecoder();
@@ -2126,7 +2135,7 @@ export default function Home() {
const completedSession: ChatSession = {
...userSession,
title: currentSession.messages.length === 0 && reply.title ? reply.title : userSession.title,
messages: [...userSession.messages, { role: "assistant", text: reply.text, suggestions: reply.suggestions, techniqueTruth }],
messages: [...userSession.messages, { role: "assistant", text: reply.text, suggestions: reply.suggestions, techniqueTruth, workflowReceipt }],
updatedAt: timestamp(),
};
updateSession(sessionId, () => completedSession);
+1 -1
View File
@@ -25,7 +25,7 @@ export function ChatMessageRow({ message }: { readonly message: ChatMessageView
{message.role === "assistant" ? (
message.state === "thinking"
? <div className="thinking"><i /><i /><i /></div>
: <><ClaimBoundaryBadge status={message.techniqueTruth} /><EvidenceAuditPanel claimStatus={message.techniqueTruth} /><ChatMessageContent text={message.text} /></>
: <><ClaimBoundaryBadge status={message.techniqueTruth} /><EvidenceAuditPanel claimStatus={message.techniqueTruth} workflowReceipt={message.workflowReceipt} /><ChatMessageContent text={message.text} /></>
) : <p>{message.text}</p>}
</div>
</div>
@@ -4,6 +4,13 @@ type AuditRow = {
boundary: string;
};
type WorkflowReceipt = {
route: string;
status: string;
preciseTiming: string;
missingLayers: readonly string[];
};
const defaultAuditRows: AuditRow[] = [
{ label: "D1 / Natal", status: "required", boundary: "基础命盘层" },
{ label: "Varga: D9 / D10 / D2 / D11", status: "required", boundary: "按问题域调用,不混用" },
@@ -15,12 +22,27 @@ const defaultAuditRows: AuditRow[] = [
{ label: "MEVG / Real Case Calibration", status: "blocked", boundary: "外部校准不足时降级" },
];
export function EvidenceAuditPanel({ claimStatus }: { readonly claimStatus?: string }) {
function workflowRows(receipt?: WorkflowReceipt): AuditRow[] {
if (!receipt) return defaultAuditRows;
return [
{ label: `Workflow route: ${receipt.route}`, status: receipt.status === "blocked" ? "blocked" : "required", boundary: "后端实际路由" },
{ label: `Precise timing: ${receipt.preciseTiming}`, status: receipt.preciseTiming === "allowed" ? "partial" : "blocked", boundary: "精确应期 claim gate" },
{
label: "Missing route layers",
status: receipt.missingLayers.length ? "blocked" : "required",
boundary: receipt.missingLayers.length ? receipt.missingLayers.join(" / ") : "none",
},
...defaultAuditRows,
];
}
export function EvidenceAuditPanel({ claimStatus, workflowReceipt }: { readonly claimStatus?: string; readonly workflowReceipt?: WorkflowReceipt }) {
const rows = workflowRows(workflowReceipt);
return (
<details className="evidence-audit-panel">
<summary> · {claimStatus || "unknown"}</summary>
<div className="evidence-audit-table" role="table" aria-label="Technique Audit Table">
{defaultAuditRows.map((row) => (
{rows.map((row) => (
<div className="evidence-audit-row" role="row" key={row.label}>
<span role="cell">{row.label}</span>
<b role="cell" data-status={row.status}>{row.status}</b>
+6
View File
@@ -3,6 +3,12 @@ export type ChatMessage = {
readonly text: string;
readonly suggestions?: readonly string[];
readonly techniqueTruth?: string;
readonly workflowReceipt?: {
readonly route: string;
readonly status: string;
readonly preciseTiming: string;
readonly missingLayers: readonly string[];
};
};
export type ChatMessageView = ChatMessage & {
@@ -4,12 +4,17 @@ import test from "node:test";
const rowSource = readFileSync(new URL("../src/components/chat-message-row.tsx", import.meta.url), "utf8");
const panelSource = readFileSync(new URL("../src/components/evidence-audit-panel.tsx", import.meta.url), "utf8");
const pageSource = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8");
const globalStyles = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
test("assistant messages include a collapsible Technique Audit Table shell", () => {
assert.match(rowSource, /EvidenceAuditPanel/);
assert.match(rowSource, /claimStatus=\{message\.techniqueTruth\}/);
assert.match(rowSource, /workflowReceipt=\{message\.workflowReceipt\}/);
assert.match(panelSource, /Technique Audit Table/);
assert.match(panelSource, /Workflow route:/);
assert.match(panelSource, /Precise timing:/);
assert.match(panelSource, /Missing route layers/);
assert.match(panelSource, /D1 \/ Natal/);
assert.match(panelSource, /Narayana Dasha/);
assert.match(panelSource, /Functional Benefic\/Malefic/);
@@ -17,3 +22,11 @@ test("assistant messages include a collapsible Technique Audit Table shell", ()
assert.match(panelSource, /组件 parity 未全闭环/);
assert.match(globalStyles, /\.evidence-audit-panel/);
});
test("consult responses persist workflow receipt headers for evidence rendering", () => {
assert.match(pageSource, /x-jyotish-workflow-route/);
assert.match(pageSource, /x-jyotish-workflow-status/);
assert.match(pageSource, /x-jyotish-precise-timing/);
assert.match(pageSource, /x-jyotish-missing-layers/);
assert.match(pageSource, /workflowReceipt/);
});