feat: add guided Jyotish topic evidence cards

This commit is contained in:
732642856
2026-07-21 14:40:07 +08:00
parent 2ce83ec7e3
commit 878ff96dc5
3 changed files with 68 additions and 6 deletions
+3 -6
View File
@@ -41,6 +41,7 @@ import {
isGuidedBirthTimePreview,
previewRectificationJourney,
} from "@/lib/birth-time-guided-preview";
import { defaultGuidedJyotishTopics } from "@/lib/guided-jyotish-topics";
import { keepFocusWithin } from "@/lib/focus-trap";
import { chatMessageViews, type ChatMessage } from "@/lib/chat-message-view";
import {
@@ -155,11 +156,7 @@ type PendingConsultation = {
const undoWindowMs = 2_500;
const china = chinaLocations.country;
const themes: Array<{ id: Exclude<Theme, "general">; label: string; prompt: string }> = [
{ id: "career", label: "事业", prompt: "未来一年,事业和收入该关注什么?" },
{ id: "marriage", label: "关系", prompt: "我的关系模式是什么?" },
{ id: "timing", label: "时运", prompt: "未来哪些阶段值得把握?" },
];
const themes = defaultGuidedJyotishTopics;
const accountDialogTitles = {
profile: "个人资料",
@@ -2482,7 +2479,7 @@ export default function Home() {
const theme = themes.find((candidate) => candidate.id === item.theme);
return (
<button key={`${item.theme}-${item.text}`} type="button" disabled={!hydrated || Boolean(pendingSessionId) || cancellationPending || !account || !modelCatalog} onClick={() => chooseSuggestedQuestion(item.text, item.theme)}>
<span className="starter-content"><b>{theme?.label || "开始"}</b><span>{item.text}</span></span>
<span className="starter-content"><b>{theme?.label || "开始"}</b><span>{item.text}</span>{theme && <small>{theme.evidencePreview.join(" / ")} · {theme.claimBoundary}</small>}</span>
<ArrowUpRight className="starter-arrow" aria-hidden="true" />
</button>
);
+50
View File
@@ -0,0 +1,50 @@
import type { ConsultationTheme } from "./consultation-workflow-request";
export type GuidedJyotishTopic = {
id: ConsultationTheme;
label: string;
prompt: string;
strictWorkflowRoute: string;
evidencePreview: string[];
confidenceCap: "low" | "medium";
claimBoundary: string;
};
export const defaultGuidedJyotishTopics: GuidedJyotishTopic[] = [
{
id: "career",
label: "事业",
prompt: "未来一年,事业和收入该关注什么?",
strictWorkflowRoute: "career",
evidencePreview: ["D1", "D10", "A10", "Vimshottari", "Narayana", "Transit"],
confidenceCap: "medium",
claimBoundary: "输出职业结构与阶段判断;具体日/月只作为候选窗口。",
},
{
id: "marriage",
label: "关系",
prompt: "我的关系模式是什么?",
strictWorkflowRoute: "marriage",
evidencePreview: ["D1", "D9", "7宫", "DK", "UL", "Vimshottari", "Narayana"],
confidenceCap: "medium",
claimBoundary: "输出关系模式与宽窗口;不承诺某日必然发生事件。",
},
{
id: "wealth",
label: "财富",
prompt: "我的财富增长方式和风险点是什么?",
strictWorkflowRoute: "wealth",
evidencePreview: ["D1", "D2", "D11", "2/11宫", "财富 Yoga", "Ashtakavarga"],
confidenceCap: "medium",
claimBoundary: "输出财富结构和风险类型;不替代投资建议。",
},
{
id: "timing",
label: "时运",
prompt: "未来哪些阶段值得把握?",
strictWorkflowRoute: "timing",
evidencePreview: ["Dasha", "Narayana", "Transit", "Varga"],
confidenceCap: "low",
claimBoundary: "精确月/日仍是探索性候选,未通过独立 holdout 前不升级。",
},
];
+15
View File
@@ -5,6 +5,7 @@ import test from "node:test";
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");
const appSidebarSource = readFileSync(new URL("../src/components/app-sidebar.tsx", import.meta.url), "utf8");
const guidedTopicsSource = readFileSync(new URL("../src/lib/guided-jyotish-topics.ts", import.meta.url), "utf8");
function sourceBetween(source: string, startMarker: string, endMarker: string) {
const start = source.indexOf(startMarker);
@@ -28,6 +29,20 @@ test("keeps starter questions visible while the user edits a draft", () => {
assert.doesNotMatch(starterVisibilityGuard, /\bdraft\b/);
});
test("default starter questions are guided Jyotish topics with evidence and claim boundaries", () => {
assert.match(pageSource, /defaultGuidedJyotishTopics/);
assert.match(pageSource, /theme\.evidencePreview\.join/);
assert.match(pageSource, /theme\.claimBoundary/);
assert.match(guidedTopicsSource, /strictWorkflowRoute/);
assert.match(guidedTopicsSource, /evidencePreview/);
assert.match(guidedTopicsSource, /confidenceCap/);
assert.match(guidedTopicsSource, /claimBoundary/);
assert.match(guidedTopicsSource, /D10/);
assert.match(guidedTopicsSource, /D9/);
assert.match(guidedTopicsSource, /Ashtakavarga/);
assert.match(guidedTopicsSource, /独立 holdout/);
});
test("keeps follow-up suggestions visible while the user edits a draft", () => {
// Given: the follow-up suggestion block and its render guard.
const suggestionGuard = sourceBetween(