diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 353ccc8e..aeba5cb7 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -45,6 +45,7 @@ import { import { defaultGuidedJyotishTopics } from "@/lib/guided-jyotish-topics"; import { keepFocusWithin } from "@/lib/focus-trap"; import { chatMessageViews, type ChatMessage } from "@/lib/chat-message-view"; +import { consultationReportMarkdown } from "@/lib/consultation-report-export"; import { OnboardingAuthenticationError, type OnboardingContent, @@ -1292,11 +1293,15 @@ export default function Home() { message_count: session.messages.length, messages: session.messages.map((message) => ({ role: message.role, text: message.text })), }; + const reportMarkdown = consultationReportMarkdown({ title: session.title, messages: session.messages }); const transcript = [ `Jyotisha 对话:${session.title}`, "", ...session.messages.map((message) => `${message.role === "user" ? "我" : "Jyotisha"}:${message.text}`), "", + "---- Markdown 报告 ----", + reportMarkdown, + "", "---- JSON 分享包 ----", JSON.stringify(sharePayload, null, 2), ].join("\n"); diff --git a/frontend/src/lib/consultation-report-export.ts b/frontend/src/lib/consultation-report-export.ts new file mode 100644 index 00000000..88aee559 --- /dev/null +++ b/frontend/src/lib/consultation-report-export.ts @@ -0,0 +1,25 @@ +import type { ChatMessage } from "./chat-message-view"; + +export function consultationReportMarkdown(input: { + title: string; + messages: readonly ChatMessage[]; +}) { + const latestAssistant = [...input.messages].reverse().find((message) => message.role === "assistant"); + const evidence = latestAssistant?.workflowReceipt; + return [ + `# ${input.title}`, + "", + "## 最新回答", + latestAssistant?.text || "暂无回答。", + "", + "## Claim boundary", + `technique_truth: ${latestAssistant?.techniqueTruth || "unknown"}`, + evidence ? `workflow_route: ${evidence.route}` : "workflow_route: unknown", + evidence ? `workflow_status: ${evidence.status}` : "workflow_status: unknown", + evidence ? `precise_timing: ${evidence.preciseTiming}` : "precise_timing: unknown", + evidence ? `missing_layers: ${evidence.missingLayers.join(" / ") || "none"}` : "missing_layers: unknown", + "", + "## Boundary", + "本报告保留证据边界;未闭环内容不得包装成确定预测。", + ].join("\n"); +} diff --git a/frontend/tests/consultation-report-export.test.ts b/frontend/tests/consultation-report-export.test.ts new file mode 100644 index 00000000..90cb8ee6 --- /dev/null +++ b/frontend/tests/consultation-report-export.test.ts @@ -0,0 +1,30 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { consultationReportMarkdown } from "../src/lib/consultation-report-export.ts"; + +test("exports latest consultation answer with workflow receipt and claim boundary", () => { + const report = consultationReportMarkdown({ + title: "事业咨询", + messages: [ + { role: "user", text: "未来一年事业如何?" }, + { + role: "assistant", + text: "先看阶段,不承诺具体日期。", + techniqueTruth: "partial", + workflowReceipt: { + route: "career", + status: "ready", + preciseTiming: "blocked", + missingLayers: ["MEVG"], + }, + }, + ], + }); + assert.match(report, /# 事业咨询/); + assert.match(report, /先看阶段/); + assert.match(report, /technique_truth: partial/); + assert.match(report, /workflow_route: career/); + assert.match(report, /precise_timing: blocked/); + assert.match(report, /missing_layers: MEVG/); + assert.match(report, /未闭环内容不得包装成确定预测/); +});