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
This commit is contained in:
co-authored by
Claude Opus 5
parent
6df40c322c
commit
99425fe61f
@@ -0,0 +1,143 @@
|
||||
"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 };
|
||||
}
|
||||
Reference in New Issue
Block a user