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
+15
-98
@@ -5,7 +5,7 @@ import Link from "next/link";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Sparkles } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import type { FormEvent, KeyboardEvent } from "react";
|
||||
import { AccountDialogOverlay, type AccountOverlayModel } from "@/components/account-dialog-overlay";
|
||||
import { ProfilePanel } from "@/components/profile-panel";
|
||||
@@ -75,6 +75,7 @@ import {
|
||||
EMPTY_RECTIFICATION_SHELL,
|
||||
useRectificationSurface,
|
||||
} from "@/hooks/use-rectification-surface";
|
||||
import { useSynastry } from "@/hooks/use-synastry";
|
||||
import { useSessionManagement } from "@/hooks/use-session-management";
|
||||
import { sortSessions } from "@/lib/session-groups";
|
||||
import { showChatNotice as setComposerNotice } from "@/lib/chat-notice";
|
||||
@@ -170,13 +171,10 @@ import {
|
||||
type RequestError,
|
||||
type StoredPendingConsultation,
|
||||
type StreamingReply,
|
||||
type SynastryRelationshipType,
|
||||
type SynastryReportCard,
|
||||
type Theme,
|
||||
} from "@/lib/home-types";
|
||||
import {
|
||||
birthQuestion,
|
||||
buildSynastryQuestion,
|
||||
chartSnapshotForSession,
|
||||
completedOnboardingMessage,
|
||||
completedOnboardingTranscript,
|
||||
@@ -216,7 +214,6 @@ import {
|
||||
readStoredDailyStarlanguage,
|
||||
readStoredPendingConsultation,
|
||||
redirectToLogin,
|
||||
saveCloudSynastryReport,
|
||||
waitForUndoWindow,
|
||||
writeStoredDailyStarlanguage,
|
||||
} from "@/lib/home-cloud-sync";
|
||||
@@ -251,10 +248,6 @@ export default function Home() {
|
||||
const [activeAccountDialog, setActiveAccountDialog] = useState<AccountDialog | null>(null);
|
||||
const [chartLibrary, setChartLibrary] = useState<ChartLibraryRecord[]>([]);
|
||||
const [activeChartId, setActiveChartId] = useState("self");
|
||||
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[]>([]);
|
||||
const [billingPane, setBillingPane] = useState<OpenAccountDialogOptions>({});
|
||||
const [dailyStarlanguage, setDailyStarlanguage] = useState<DailyStarlanguageState>({ kind: "pending" });
|
||||
const [profileNotice, setProfileNotice] = useState("");
|
||||
@@ -301,7 +294,7 @@ export default function Home() {
|
||||
setRectificationErrorSessionId,
|
||||
setRectificationEntrySummary,
|
||||
setRectificationEntrySummarySettled,
|
||||
} = createRectificationShellSetters(setRectification);
|
||||
} = useMemo(() => createRectificationShellSetters(setRectification), [setRectification]);
|
||||
const [hydrated, setHydrated] = useState(false);
|
||||
const [bootstrapPhase, setBootstrapPhase] = useState<BootstrapPhase>("account");
|
||||
const prepareStartedAt = useRef<number | null>(null);
|
||||
@@ -467,6 +460,10 @@ export default function Home() {
|
||||
guidedBirthTimeReadyRef, editDeclaredBirthTimeDetailsRef,
|
||||
});
|
||||
|
||||
const { synastryPanel, clearSynastryHistory, applyCloudSynastryHistory } = useSynastry({
|
||||
accountId, profile, chooseSuggestedQuestion, closeAccountDialog,
|
||||
});
|
||||
|
||||
const {
|
||||
setRectificationHeaderSlot,
|
||||
rectificationPanel,
|
||||
@@ -554,7 +551,7 @@ export default function Home() {
|
||||
setRectificationEntrySummarySettled(true);
|
||||
}
|
||||
})();
|
||||
}, [accountId, bootstrapPhase]);
|
||||
}, [accountId, bootstrapPhase, setRectificationEntrySummary, setRectificationEntrySummarySettled]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hydrated || !accountId) return;
|
||||
@@ -568,7 +565,7 @@ export default function Home() {
|
||||
chartLibraryLoadedAccount.current = "";
|
||||
queueMicrotask(() => {
|
||||
setChartLibrary([]);
|
||||
setSynastryHistory([]);
|
||||
clearSynastryHistory();
|
||||
setActiveChartId("self");
|
||||
});
|
||||
return;
|
||||
@@ -578,7 +575,7 @@ export default function Home() {
|
||||
chartLibraryLoadedAccount.current = accountId;
|
||||
discardLegacyCloudMirrorKeys(accountId);
|
||||
setChartLibrary(chartLibraryOnCloudFailure(profileForLibrary, upsertSelfChart));
|
||||
setSynastryHistory([]);
|
||||
clearSynastryHistory();
|
||||
void fetchCloudChartLibrary()
|
||||
.then((cloudLibrary) => {
|
||||
setChartLibrary(chartLibraryFromCloudOthers(cloudLibrary, profileForLibrary, upsertSelfChart));
|
||||
@@ -599,7 +596,7 @@ export default function Home() {
|
||||
});
|
||||
void fetchCloudSynastryHistory()
|
||||
.then((cloudHistory) => {
|
||||
setSynastryHistory([...cloudHistory].sort((a, b) => b.createdAt - a.createdAt).slice(0, 10));
|
||||
applyCloudSynastryHistory(cloudHistory);
|
||||
})
|
||||
.catch(() => {
|
||||
setComposerNotice("合盘历史暂时无法读取,请重试。", {
|
||||
@@ -607,7 +604,7 @@ export default function Home() {
|
||||
onClick: () => {
|
||||
void fetchCloudSynastryHistory()
|
||||
.then((cloudHistory) => {
|
||||
setSynastryHistory([...cloudHistory].sort((a, b) => b.createdAt - a.createdAt).slice(0, 10));
|
||||
applyCloudSynastryHistory(cloudHistory);
|
||||
})
|
||||
.catch(() => {
|
||||
setComposerNotice("合盘历史暂时无法读取,请重试。");
|
||||
@@ -617,7 +614,7 @@ export default function Home() {
|
||||
});
|
||||
}
|
||||
setChartLibrary((current) => upsertSelfChart(current, profileForLibrary));
|
||||
}, [account, accountId, activeChartId, profile]);
|
||||
}, [account, accountId, activeChartId, applyCloudSynastryHistory, clearSynastryHistory, profile]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hydrated || !accountId || chartLibrary.length === 0) return;
|
||||
@@ -1408,84 +1405,6 @@ export default function Home() {
|
||||
);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
async function copyAssistantMessage(renderKey: string, text: string) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
@@ -1609,10 +1528,8 @@ export default function Home() {
|
||||
account={account} accountId={accountId} chartLibrary={chartLibrary} setChartLibrary={setChartLibrary}
|
||||
profile={profile} profileDraft={profileDraft} setProfileDraft={setProfileDraft} setProfile={setProfile}
|
||||
profileSaving={profileSaving} profileNotice={profileNotice} setProfileNotice={setProfileNotice} setAccountError={setAccountError}
|
||||
synastryRelationshipType={synastryRelationshipType} setSynastryRelationshipType={setSynastryRelationshipType}
|
||||
synastryPendingId={synastryPendingId} synastryReportCard={synastryReportCard} setSynastryReportCard={setSynastryReportCard}
|
||||
synastryHistory={synastryHistory} activeChartId={activeChartId} setActiveChartId={setActiveChartId}
|
||||
saveProfile={saveProfile} draftSynastryQuestionFromChart={draftSynastryQuestionFromChart}
|
||||
{...synastryPanel} activeChartId={activeChartId} setActiveChartId={setActiveChartId}
|
||||
saveProfile={saveProfile}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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