Files
Jyotisha/frontend/src/hooks/use-synastry.ts
T
jesse-uxandClaude Code 4d801e53c3
Independent Staging Quality Gate / validate (push) Successful in 16m28s
Independent Staging Quality Gate / publish (push) Successful in 3m33s
fix: integrate people archive, report reader and western chart corrections
Validate on Linux Node 22 and PostgreSQL 17: 3894 frontend tests and 64 database tests pass, with no removed test names or new failures. Preserve static Home, bounded gzip, assertion-change records and manual acceptance gaps.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-25 20:41:16 +08:00

146 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useCallback, useState, type Dispatch, type SetStateAction } from "react";
import { showChatNotice as setComposerNotice } from "@/lib/chat-notice";
import { saveCloudSynastryReport } from "@/lib/home-cloud-sync";
import { buildSynastryQuestion } from "@/lib/home-profile";
import {
timestamp,
type ChartLibraryRecord,
type Profile,
type SynastryRelationshipType,
type SynastryReportCard,
type Theme,
} from "@/lib/home-types";
export type SynastryParams = {
accountId: string | undefined;
profile: Profile;
chooseSuggestedQuestion: (question: string, theme: Theme) => void;
closeAccountDialog: () => void;
startNewChat: (options: { subjectId: "self" }) => Promise<unknown>;
};
export type SynastryPanel = {
synastryRelationshipType: SynastryRelationshipType;
setSynastryRelationshipType: Dispatch<SetStateAction<SynastryRelationshipType>>;
synastryPendingId: string | null;
synastryReportCard: SynastryReportCard | null;
setSynastryReportCard: Dispatch<SetStateAction<SynastryReportCard | null>>;
synastryHistory: SynastryReportCard[];
draftSynastryQuestionFromChart: (record: ChartLibraryRecord, relationshipType: SynastryRelationshipType) => Promise<void>;
};
/**
* Owns the four synastry states that used to sit in Home(). Home keeps only the
* two stable history hooks the chart-library effect calls; everything else the
* chart-library dialog needs travels as one `synastryPanel` object.
*/
export function useSynastry(params: SynastryParams) {
const { accountId, profile, chooseSuggestedQuestion, closeAccountDialog } = params;
const [synastryRelationshipType, setSynastryRelationshipType] = useState<SynastryRelationshipType>("romance");
const [synastryPendingId, setSynastryPendingId] = useState<string | null>(null);
const [synastryReportCard, setSynastryReportCard] = useState<SynastryReportCard | null>(null);
const [synastryHistory, setSynastryHistory] = useState<SynastryReportCard[]>([]);
// Stable across renders so Home's chart-library effect can list them in its
// dependency array without gaining a re-run.
const clearSynastryHistory = useCallback(() => {
setSynastryHistory([]);
}, []);
const applyCloudSynastryHistory = useCallback((cloudHistory: SynastryReportCard[]) => {
setSynastryHistory([...cloudHistory].sort((a, b) => b.createdAt - a.createdAt).slice(0, 10));
}, []);
async function draftSynastryQuestionFromChart(record: ChartLibraryRecord, relationshipType: SynastryRelationshipType) {
if (record.role !== "other") return;
if (synastryPendingId) return;
if (!await params.startNewChat({ subjectId: "self" })) return;
const baseQuestion = buildSynastryQuestion(profile, record.profile, relationshipType);
setSynastryPendingId(record.id);
setComposerNotice("正在计算基础合盘证据,请稍候。");
try {
const response = await fetch("/api/synastry", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ partnerChartProfileId: record.id, relationshipType }),
});
const payload = await response.json().catch(() => null) as { status?: string; claimStatus?: string; blockedLayers?: string[]; evidenceLayers?: string[]; synastry?: { total_score?: number; max_score?: number; assessment?: string }; relationshipReport?: { headline?: string; scoreBand?: string; strengths?: string[]; risks?: string[]; nextEvidence?: string[] } } | null;
if (response.ok && payload?.status === "ok") {
const score = payload.synastry?.total_score;
const max = payload.synastry?.max_score;
const assessment = payload.synastry?.assessment;
const layers = (payload.evidenceLayers || []).join(" / ") || "Ashtakoot / Moon / D9";
const evidenceSummary = relationshipType === "business"
? `已完成基础商业合作证据筛查:${layers};声明状态:${payload.claimStatus || "partial"};未用层:${(payload.blockedLayers || []).join(" / ") || "A10 / 双方 Dasha-Narayana / 功能吉凶"}。请勿将其表述为合作保证或精确时点。`
: `已计算基础合盘证据:${layers};Ashtakoot ${score ?? "?"}/${max ?? "?"},初步评级:${assessment || "待解释"}。请基于这个证据包继续分析。`;
const reportCard: SynastryReportCard = {
id: `${record.id}-${timestamp()}`,
partnerName: record.profile.name || "对方",
partnerChartId: record.id,
score,
maxScore: max,
assessment,
headline: payload.relationshipReport?.headline,
scoreBand: payload.relationshipReport?.scoreBand,
strengths: payload.relationshipReport?.strengths,
risks: payload.relationshipReport?.risks,
nextEvidence: payload.relationshipReport?.nextEvidence,
createdAt: timestamp(),
};
let savedReportCard = reportCard;
let historyPersisted = !accountId;
if (accountId) {
try {
savedReportCard = await saveCloudSynastryReport(reportCard);
historyPersisted = true;
} catch {
historyPersisted = false;
}
}
setSynastryReportCard(savedReportCard);
if (accountId && historyPersisted) {
setSynastryHistory((current) => (
[savedReportCard, ...current.filter((item) => item.id !== savedReportCard.id)].slice(0, 10)
));
}
chooseSuggestedQuestion([
baseQuestion,
"",
evidenceSummary,
payload.relationshipReport?.headline ? `结构化摘要:${payload.relationshipReport.headline}` : "",
].join("\n"), relationshipType === "business" ? "career" : "marriage");
if (accountId && !historyPersisted) {
setComposerNotice("未能存入历史");
}
} else {
chooseSuggestedQuestion(baseQuestion, relationshipType === "business" ? "career" : "marriage");
setComposerNotice(response.status === 404
? "请先把对方星盘保存到云端,再用于合盘。"
: response.status === 429
? "合盘请求过于频繁,请稍后再试。"
: payload?.status === "blocked" ? "合盘计算暂时不可用,已先生成问题草稿。" : "已生成合盘问题草稿。");
}
} catch {
chooseSuggestedQuestion(baseQuestion, relationshipType === "business" ? "career" : "marriage");
setComposerNotice("合盘计算暂时不可用,已先生成问题草稿。");
} finally {
setSynastryPendingId(null);
}
closeAccountDialog();
}
const synastryPanel: SynastryPanel = {
synastryRelationshipType,
setSynastryRelationshipType,
synastryPendingId,
synastryReportCard,
setSynastryReportCard,
synastryHistory,
draftSynastryQuestionFromChart,
};
return { synastryPanel, clearSynastryHistory, applyCloudSynastryHistory };
}