feat: render workflow receipt evidence rows
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user