Files
Jyotisha/frontend/src/hooks/use-synastry.ts
T
Jesse_ChenandClaude Opus 5 99425fe61f refactor(home): stabilize shell setters and lower the synastry cluster
5.1: memoize createRectificationShellSetters so the four shell setters keep a
stable identity across renders, then list the two the entry-summary effect uses
in its dependency array. Lint warnings 120 -> 119 with no new warning.

5.2: move the four synastry useStates and draftSynastryQuestionFromChart into
the new useSynastry hook. Home keeps only two stable callbacks the chart-library
effect calls; the chart-library dialog receives one spread synastryPanel object.

Zero behavior change, zero copy change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
2026-09-16 02:02:22 +00:00

144 lines
6.7 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;
};
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;
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 };
}