merge: synchronize remote main
This commit is contained in:
@@ -145,20 +145,10 @@ function TerminalAction({ controller, error, path }: {
|
||||
readonly error: string;
|
||||
readonly path: NonNullable<ReturnType<typeof guidedTerminalPath>>;
|
||||
}) {
|
||||
if (path.kind === "complete_with_candidate") {
|
||||
return (
|
||||
<div className="birth-time-next-step">
|
||||
<b>评估已完成,下一步</b>
|
||||
<p>点击后将使用 {path.time} 作为<span className="phrase-nowrap">当前排盘时间</span>并进入对话;<span className="phrase-nowrap">原始填报</span>和本次<span className="phrase-nowrap">候选结果</span><span className="phrase-nowrap">仍会保留</span>。</p>
|
||||
<button className="button-primary birth-time-guided-action" disabled={controller.pending} onClick={() => controller.completeCandidate(path.time)} type="button">
|
||||
{controller.pending ? `正在采用 ${path.time}…` : `采用 ${path.time} 并进入对话`}
|
||||
</button>
|
||||
{error ? <p className="form-error" role="alert">{error}</p> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="birth-time-new-assessment">
|
||||
<b>尚未达到采用条件</b>
|
||||
<p>候选范围已保留,但当前证据不足以将具体分钟写入当前排盘时间。补充经历后可重新评估。</p>
|
||||
<button className="button-secondary birth-time-guided-action" disabled={controller.pending} onClick={controller.editBirthTimeDetails} type="button">开始新的评估</button>
|
||||
{error ? <p className="form-error" role="alert">{error}</p> : null}
|
||||
<small>会建立新的记录,当前结果仍会保留。</small>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { ChatMessageContent } from "@/components/chat-message-content";
|
||||
import { ClaimBoundaryBadge } from "@/components/claim-boundary-badge";
|
||||
import { EvidenceAuditPanel } from "@/components/evidence-audit-panel";
|
||||
import type { ChatMessageView } from "@/lib/chat-message-view";
|
||||
import { consultationReportMarkdown, downloadMarkdownReport } from "@/lib/consultation-report-export";
|
||||
|
||||
export function AgentAvatar() {
|
||||
return <span className="agent-avatar" aria-hidden="true" />;
|
||||
@@ -23,7 +26,7 @@ export function ChatMessageRow({ message }: { readonly message: ChatMessageView
|
||||
{message.role === "assistant" ? (
|
||||
message.state === "thinking"
|
||||
? <div className="thinking"><i /><i /><i /></div>
|
||||
: <ChatMessageContent text={message.text} />
|
||||
: <><ClaimBoundaryBadge status={message.techniqueTruth} /><EvidenceAuditPanel claimStatus={message.techniqueTruth} workflowReceipt={message.workflowReceipt} /><ChatMessageContent text={message.text} /><button className="report-download-button" type="button" onClick={() => downloadMarkdownReport("Jyotisha 咨询报告", consultationReportMarkdown({ title: "Jyotisha 咨询报告", messages: [message] }))}>下载本次报告</button></>
|
||||
) : <p>{message.text}</p>}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
const boundaryCopy: Record<string, string> = {
|
||||
unknown: "证据边界未知",
|
||||
partial: "部分证据闭环",
|
||||
observation_only: "仅观察",
|
||||
blocked: "证据阻塞",
|
||||
reference_only: "仅参考",
|
||||
};
|
||||
|
||||
export function ClaimBoundaryBadge({ status }: { readonly status?: string }) {
|
||||
const normalized = status?.trim() || "unknown";
|
||||
const label = boundaryCopy[normalized] ?? `证据状态:${normalized}`;
|
||||
return <p className="claim-boundary-badge">{label} · 不把未闭环内容包装成确定预测。</p>;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
type AuditRow = {
|
||||
label: string;
|
||||
status: "required" | "partial" | "blocked";
|
||||
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: "按问题域调用,不混用" },
|
||||
{ label: "Vimshottari Dasha", status: "required", boundary: "阶段证据之一" },
|
||||
{ label: "Narayana Dasha", status: "required", boundary: "应期/校时必须交叉" },
|
||||
{ label: "Transit / Gochara", status: "partial", boundary: "精确日/月仍需 holdout" },
|
||||
{ label: "Shadbala / Ashtakavarga", status: "partial", boundary: "组件 parity 未全闭环" },
|
||||
{ label: "Functional Benefic/Malefic", status: "required", boundary: "不能只看自然吉凶" },
|
||||
{ label: "MEVG / Real Case Calibration", status: "blocked", boundary: "外部校准不足时降级" },
|
||||
];
|
||||
|
||||
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);
|
||||
const rawReceipt = {
|
||||
techniqueTruth: claimStatus || "unknown",
|
||||
workflowReceipt: workflowReceipt ?? null,
|
||||
privacyBoundary: "no_birth_data_or_private_case_raw",
|
||||
};
|
||||
return (
|
||||
<details className="evidence-audit-panel">
|
||||
<summary>证据链摘要 · {claimStatus || "unknown"}</summary>
|
||||
<div className="evidence-audit-table" role="table" aria-label="Technique Audit Table">
|
||||
{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>
|
||||
<small role="cell">{row.boundary}</small>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<details className="raw-evidence-receipt">
|
||||
<summary>查看非敏感 raw receipt</summary>
|
||||
<pre>{JSON.stringify(rawReceipt, null, 2)}</pre>
|
||||
</details>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
export type ParameterFreezeRow = {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export function ParameterFreezePanel({ rows }: { readonly rows: readonly ParameterFreezeRow[] }) {
|
||||
return (
|
||||
<section className="parameter-freeze-panel" aria-label="当前排盘参数冻结">
|
||||
<div>
|
||||
<b>当前排盘参数</b>
|
||||
<span>这些参数会随咨询一起送入证据链。</span>
|
||||
</div>
|
||||
<dl>
|
||||
{rows.map((row) => (
|
||||
<div key={row.label}>
|
||||
<dt>{row.label}</dt>
|
||||
<dd>{row.value}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user