fix(chat): make cloud the only truth for charts, synastry, and pin/archive
Local fallbacks were creating fake saves and resurrecting deleted rows. Pin and archive now live on chat_sessions so they follow the account. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -14,7 +14,7 @@ import { consumeUserRequestRateLimit } from "@/lib/request-rate-limit";
|
||||
|
||||
type RouteContext = { params: Promise<{ id: string }> };
|
||||
|
||||
const sessionSelect = "id,title,theme,model_id,messages,session_type,rectification_case_id,chart_profile_id,chart_profile_name,chart_profile_role,updated_at";
|
||||
const sessionSelect = "id,title,theme,model_id,messages,session_type,rectification_case_id,chart_profile_id,chart_profile_name,chart_profile_role,updated_at,pinned,archived_at";
|
||||
const sessionIdPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||
|
||||
function payloadHasMessages(payload: unknown): payload is { messages: unknown } {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { consumeUserRequestRateLimit } from "@/lib/request-rate-limit";
|
||||
import { isSupabaseConfigurationError } from "@/lib/supabase/config";
|
||||
import { createServerSupabaseClient } from "@/lib/supabase/server";
|
||||
|
||||
const SESSION_LIST_COLUMNS = "id,title,theme,model_id,session_type,rectification_case_id,chart_profile_id,chart_profile_name,chart_profile_role,updated_at";
|
||||
const SESSION_LIST_COLUMNS = "id,title,theme,model_id,session_type,rectification_case_id,chart_profile_id,chart_profile_name,chart_profile_role,updated_at,pinned,archived_at";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
|
||||
+159
-123
@@ -106,7 +106,11 @@ import {
|
||||
membershipHref,
|
||||
} from "@/lib/membership";
|
||||
import { nextActivityView, activityCompletedTrail, type AgentActivityView, type ChatMessage } from "@/lib/chat-message-view";
|
||||
import { chartLibrarySessionBranch } from "@/lib/chart-library-session";
|
||||
import {
|
||||
chartLibraryFromCloudOthers,
|
||||
chartLibraryOnCloudFailure,
|
||||
chartLibrarySessionBranch,
|
||||
} from "@/lib/chart-library-session";
|
||||
import {
|
||||
emptyConsultationTimeline,
|
||||
reduceConsultationTimeline,
|
||||
@@ -249,6 +253,8 @@ type ChatSession = {
|
||||
chartProfileId: string | null;
|
||||
chartProfileName: string | null;
|
||||
chartProfileRole: "self" | "other" | null;
|
||||
pinned: boolean;
|
||||
archivedAt: string | null;
|
||||
messagesHydrated: boolean;
|
||||
};
|
||||
|
||||
@@ -448,6 +454,8 @@ function createSession(
|
||||
updatedAt: timestamp(),
|
||||
sessionType,
|
||||
rectificationCaseId: null,
|
||||
pinned: false,
|
||||
archivedAt: null,
|
||||
messagesHydrated: true,
|
||||
...chartBinding,
|
||||
};
|
||||
@@ -497,19 +505,62 @@ function selectedBirthPlace(profile: Profile): BirthPlace | null {
|
||||
};
|
||||
}
|
||||
|
||||
function chartLibraryStorageKey(accountId: string) {
|
||||
return `jyotisha_chart_library:${accountId}`;
|
||||
}
|
||||
function activeChartStorageKey(accountId: string) {
|
||||
return `jyotisha_active_chart:${accountId}`;
|
||||
}
|
||||
function synastryHistoryStorageKey(accountId: string) {
|
||||
return `jyotisha_synastry_history:${accountId}`;
|
||||
}
|
||||
function dailyStarlanguageStorageKey(accountId: string) {
|
||||
return `jyotisha_daily_starlanguage:${accountId}`;
|
||||
}
|
||||
|
||||
function sessionControlsStorageKey(accountId: string, kind: "pinned" | "archived") {
|
||||
return `jyotisha-session-controls:${accountId}:${kind}`;
|
||||
}
|
||||
|
||||
function discardLegacyCloudMirrorKeys(accountId: string) {
|
||||
localStorage.removeItem(`jyotisha_chart_library:${accountId}`);
|
||||
localStorage.removeItem(`jyotisha_synastry_history:${accountId}`);
|
||||
}
|
||||
|
||||
function readLegacySessionControlIds(accountId: string, kind: "pinned" | "archived"): string[] {
|
||||
try {
|
||||
const parsed = JSON.parse(localStorage.getItem(sessionControlsStorageKey(accountId, kind)) || "null") as unknown;
|
||||
return Array.isArray(parsed) ? parsed.filter((id): id is string => typeof id === "string") : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function clearLegacySessionControlKeys(accountId: string) {
|
||||
localStorage.removeItem(sessionControlsStorageKey(accountId, "pinned"));
|
||||
localStorage.removeItem(sessionControlsStorageKey(accountId, "archived"));
|
||||
}
|
||||
|
||||
function applyLegacySessionControls(accountId: string, sessions: ChatSession[]): ChatSession[] {
|
||||
const pinnedKey = sessionControlsStorageKey(accountId, "pinned");
|
||||
const archivedKey = sessionControlsStorageKey(accountId, "archived");
|
||||
if (localStorage.getItem(pinnedKey) === null && localStorage.getItem(archivedKey) === null) {
|
||||
return sessions;
|
||||
}
|
||||
const pinnedIds = new Set(readLegacySessionControlIds(accountId, "pinned"));
|
||||
const archivedIds = new Set(readLegacySessionControlIds(accountId, "archived"));
|
||||
const next = sessions.map((session) => ({
|
||||
...session,
|
||||
pinned: session.pinned || pinnedIds.has(session.id),
|
||||
archivedAt: session.archivedAt || (archivedIds.has(session.id) ? new Date().toISOString() : null),
|
||||
}));
|
||||
void Promise.allSettled(next.flatMap((session, index) => {
|
||||
const previous = sessions[index];
|
||||
if (!previous) return [];
|
||||
const patch: { pinned?: boolean; archived_at?: string | null } = {};
|
||||
if (session.pinned !== previous.pinned) patch.pinned = session.pinned;
|
||||
if (session.archivedAt !== previous.archivedAt) patch.archived_at = session.archivedAt;
|
||||
if (patch.pinned === undefined && patch.archived_at === undefined) return [];
|
||||
return [writeChatSession(session.id, patch, "update")];
|
||||
}));
|
||||
clearLegacySessionControlKeys(accountId);
|
||||
return next;
|
||||
}
|
||||
|
||||
type StoredDailyStarlanguage = {
|
||||
readonly day: string;
|
||||
readonly fingerprint: string;
|
||||
@@ -574,34 +625,6 @@ function upsertSelfChart(library: ChartLibraryRecord[], profile: Profile) {
|
||||
return [buildSelfChartRecord(profile), ...others];
|
||||
}
|
||||
|
||||
function readChartLibrary(accountId: string): ChartLibraryRecord[] {
|
||||
try {
|
||||
const parsed = JSON.parse(localStorage.getItem(chartLibraryStorageKey(accountId)) || "[]") as ChartLibraryRecord[];
|
||||
return Array.isArray(parsed)
|
||||
? parsed
|
||||
.filter((record) => record?.id && record?.profile)
|
||||
.map((record) => {
|
||||
const relationship = record.role === "self" ? "self" : record.relationship || record.profile.chartRelationship || "other";
|
||||
return { ...record, profile: { ...record.profile, chartRelationship: relationship }, relationship };
|
||||
})
|
||||
: [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
function readSynastryHistory(accountId: string): SynastryReportCard[] {
|
||||
try {
|
||||
const parsed = JSON.parse(localStorage.getItem(synastryHistoryStorageKey(accountId)) || "[]") as SynastryReportCard[];
|
||||
return Array.isArray(parsed) ? parsed.filter((record) => record?.id && record?.partnerName).slice(0, 10) : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function writeSynastryHistory(accountId: string, history: SynastryReportCard[]) {
|
||||
localStorage.setItem(synastryHistoryStorageKey(accountId), JSON.stringify(history.slice(0, 10)));
|
||||
}
|
||||
|
||||
function normalizeSynastryReportApiRecord(record: SynastryReportApiRecord): SynastryReportCard | null {
|
||||
if (!record.report || typeof record.report !== "object") return null;
|
||||
return {
|
||||
@@ -903,6 +926,7 @@ function readSessions(value: unknown, catalog: PublicLanguageModelCatalog | null
|
||||
chart_profile_role?: unknown;
|
||||
session_type?: unknown;
|
||||
updated_at?: unknown;
|
||||
archived_at?: unknown;
|
||||
};
|
||||
const messagesPresent = Object.prototype.hasOwnProperty.call(session, "messages");
|
||||
const messages: Message[] = Array.isArray(session.messages)
|
||||
@@ -951,6 +975,12 @@ function readSessions(value: unknown, catalog: PublicLanguageModelCatalog | null
|
||||
chartProfileRole: session.chart_profile_role === "self" || session.chart_profile_role === "other"
|
||||
? session.chart_profile_role
|
||||
: null,
|
||||
pinned: session.pinned === true,
|
||||
archivedAt: typeof session.archived_at === "string" && session.archived_at
|
||||
? session.archived_at
|
||||
: typeof session.archivedAt === "string" && session.archivedAt
|
||||
? session.archivedAt
|
||||
: null,
|
||||
updatedAt: typeof session.updatedAt === "number"
|
||||
? session.updatedAt
|
||||
: typeof session.updated_at === "string"
|
||||
@@ -1286,8 +1316,6 @@ export default function Home() {
|
||||
const [accountError, setAccountError] = useState("");
|
||||
const [signingOut, setSigningOut] = useState(false);
|
||||
const [sessions, setSessions] = useState<ChatSession[]>([]);
|
||||
const [pinnedSessionIds, setPinnedSessionIds] = useState<string[]>([]);
|
||||
const [archivedSessionIds, setArchivedSessionIds] = useState<string[]>([]);
|
||||
const [showArchivedSessions, setShowArchivedSessions] = useState(false);
|
||||
const [sessionMenuId, setSessionMenuId] = useState<string | null>(null);
|
||||
const [pendingSessionDeletion, setPendingSessionDeletion] = useState<ChatSession | null>(null);
|
||||
@@ -1391,12 +1419,12 @@ export default function Home() {
|
||||
const rectificationSurfaceOpen = activeRectificationSession
|
||||
&& activeSession.id === rectificationSessionId;
|
||||
const visibleSessions = sessions
|
||||
.filter((session) => showArchivedSessions ? archivedSessionIds.includes(session.id) : !archivedSessionIds.includes(session.id))
|
||||
.filter((session) => showArchivedSessions ? Boolean(session.archivedAt) : !session.archivedAt)
|
||||
.filter((session) => session.sessionType === "birth_time_rectification"
|
||||
|| session.messages.length > 0
|
||||
|| !session.messagesHydrated
|
||||
|| session.id === activeSessionId)
|
||||
.sort((left, right) => Number(pinnedSessionIds.includes(right.id)) - Number(pinnedSessionIds.includes(left.id)));
|
||||
.sort((left, right) => Number(right.pinned) - Number(left.pinned));
|
||||
const activeError = requestError && requestError.sessionId === activeSession?.id ? requestError.message : "";
|
||||
const isLoading = pendingSessionId === activeSession?.id;
|
||||
const productEntrypointsDisabled = !hydrated
|
||||
@@ -1478,13 +1506,6 @@ export default function Home() {
|
||||
rectificationSessionId,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hydrated || !accountId) return;
|
||||
const prefix = `jyotisha-session-controls:${accountId}:`;
|
||||
setPinnedSessionIds(JSON.parse(localStorage.getItem(`${prefix}pinned`) || "[]"));
|
||||
setArchivedSessionIds(JSON.parse(localStorage.getItem(`${prefix}archived`) || "[]"));
|
||||
}, [accountId, hydrated]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hydrated || !accountId) return;
|
||||
void (async () => {
|
||||
@@ -1499,13 +1520,6 @@ export default function Home() {
|
||||
})();
|
||||
}, [accountId, hydrated]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hydrated || !accountId) return;
|
||||
const prefix = `jyotisha-session-controls:${accountId}:`;
|
||||
localStorage.setItem(`${prefix}pinned`, JSON.stringify(pinnedSessionIds));
|
||||
localStorage.setItem(`${prefix}archived`, JSON.stringify(archivedSessionIds));
|
||||
}, [accountId, archivedSessionIds, hydrated, pinnedSessionIds]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hydrated || !accountId) return;
|
||||
setActiveChartId(localStorage.getItem(activeChartStorageKey(accountId)) || "self");
|
||||
@@ -1523,37 +1537,47 @@ export default function Home() {
|
||||
const profileForLibrary: Profile = activeChartId === "self" ? profile : account ? readProfile(account.profile) : profile;
|
||||
if (branch === "hydrate-then-persist") {
|
||||
chartLibraryLoadedAccount.current = accountId;
|
||||
setChartLibrary(upsertSelfChart(readChartLibrary(accountId), profileForLibrary));
|
||||
setSynastryHistory(readSynastryHistory(accountId));
|
||||
discardLegacyCloudMirrorKeys(accountId);
|
||||
setChartLibrary(chartLibraryOnCloudFailure(profileForLibrary, upsertSelfChart));
|
||||
setSynastryHistory([]);
|
||||
void fetchCloudChartLibrary()
|
||||
.then((cloudLibrary) => {
|
||||
setChartLibrary(() => {
|
||||
const next = upsertSelfChart(cloudLibrary.filter((record) => record.role !== "self"), profileForLibrary);
|
||||
localStorage.setItem(chartLibraryStorageKey(accountId), JSON.stringify(next));
|
||||
return next;
|
||||
});
|
||||
setChartLibrary(chartLibraryFromCloudOthers(cloudLibrary, profileForLibrary, upsertSelfChart));
|
||||
})
|
||||
.catch(() => {
|
||||
// Cloud chart library is best-effort; local library remains usable.
|
||||
setComposerNotice("星盘库暂时无法读取,请重试。", {
|
||||
label: "重试",
|
||||
onClick: () => {
|
||||
void fetchCloudChartLibrary()
|
||||
.then((cloudLibrary) => {
|
||||
setChartLibrary(chartLibraryFromCloudOthers(cloudLibrary, profileForLibrary, upsertSelfChart));
|
||||
})
|
||||
.catch(() => {
|
||||
setComposerNotice("星盘库暂时无法读取,请重试。");
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
void fetchCloudSynastryHistory()
|
||||
.then((cloudHistory) => {
|
||||
setSynastryHistory((current) => {
|
||||
const byId = new Map([...current, ...cloudHistory].map((record) => [record.id, record] as const));
|
||||
const next = [...byId.values()].sort((a, b) => b.createdAt - a.createdAt).slice(0, 10);
|
||||
writeSynastryHistory(accountId, next);
|
||||
return next;
|
||||
});
|
||||
setSynastryHistory([...cloudHistory].sort((a, b) => b.createdAt - a.createdAt).slice(0, 10));
|
||||
})
|
||||
.catch(() => {
|
||||
// Cloud synastry history is best-effort; local history remains usable.
|
||||
setComposerNotice("合盘历史暂时无法读取,请重试。", {
|
||||
label: "重试",
|
||||
onClick: () => {
|
||||
void fetchCloudSynastryHistory()
|
||||
.then((cloudHistory) => {
|
||||
setSynastryHistory([...cloudHistory].sort((a, b) => b.createdAt - a.createdAt).slice(0, 10));
|
||||
})
|
||||
.catch(() => {
|
||||
setComposerNotice("合盘历史暂时无法读取,请重试。");
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
setChartLibrary((current) => {
|
||||
const next = upsertSelfChart(current, profileForLibrary);
|
||||
localStorage.setItem(chartLibraryStorageKey(accountId), JSON.stringify(next));
|
||||
return next;
|
||||
});
|
||||
setChartLibrary((current) => upsertSelfChart(current, profileForLibrary));
|
||||
}, [account, accountId, activeChartId, profile]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -1786,6 +1810,8 @@ export default function Home() {
|
||||
chartProfileId: "self",
|
||||
chartProfileName: previewProfile.name.trim() || "我",
|
||||
chartProfileRole: "self",
|
||||
pinned: false,
|
||||
archivedAt: null,
|
||||
messagesHydrated: true,
|
||||
};
|
||||
setAccount({
|
||||
@@ -1857,6 +1883,7 @@ export default function Home() {
|
||||
}
|
||||
nextSessions = [initialSession];
|
||||
}
|
||||
nextSessions = applyLegacySessionControls(nextAccount.user.id, nextSessions);
|
||||
|
||||
let reservedConsultation: ConsultationStatus | null = null;
|
||||
const storedPending: StoredPendingConsultation | null = readStoredPendingConsultation(
|
||||
@@ -2377,8 +2404,6 @@ export default function Home() {
|
||||
const nextSessions = sessions.filter((item) => item.id !== session.id);
|
||||
setSessions(nextSessions);
|
||||
setBirthTimeConsultationConsent((current) => clearBirthTimeConsultationConsent(current, session.id));
|
||||
setPinnedSessionIds((current) => current.filter((id) => id !== session.id));
|
||||
setArchivedSessionIds((current) => current.filter((id) => id !== session.id));
|
||||
if (activeSessionId === session.id) {
|
||||
const fallbackId = nextSessions[0]?.id ?? "";
|
||||
setActiveSessionId(fallbackId);
|
||||
@@ -2395,18 +2420,37 @@ export default function Home() {
|
||||
}
|
||||
|
||||
function togglePinnedSession(sessionId: string) {
|
||||
setPinnedSessionIds((current) => current.includes(sessionId) ? current.filter((id) => id !== sessionId) : [sessionId, ...current]);
|
||||
const session = sessions.find((item) => item.id === sessionId);
|
||||
if (!session) return;
|
||||
const nextPinned = !session.pinned;
|
||||
updateSession(sessionId, (current) => ({ ...current, pinned: nextPinned }));
|
||||
void writeChatSession(sessionId, { pinned: nextPinned }, "update").catch((caught) => {
|
||||
updateSession(sessionId, (current) => ({ ...current, pinned: session.pinned }));
|
||||
setComposerNotice(caught instanceof Error ? caught.message : "置顶同步失败");
|
||||
});
|
||||
}
|
||||
|
||||
function toggleArchivedSession(sessionId: string) {
|
||||
const restoring = archivedSessionIds.includes(sessionId);
|
||||
setArchivedSessionIds((current) => restoring ? current.filter((id) => id !== sessionId) : [sessionId, ...current]);
|
||||
const session = sessions.find((item) => item.id === sessionId);
|
||||
if (!session) return;
|
||||
const restoring = Boolean(session.archivedAt);
|
||||
const previousActiveId = activeSessionId;
|
||||
const nextArchivedAt = restoring ? null : new Date().toISOString();
|
||||
updateSession(sessionId, (current) => ({ ...current, archivedAt: nextArchivedAt }));
|
||||
if (!restoring && activeSessionId === sessionId) {
|
||||
const fallbackId = visibleSessions.find((session) => session.id !== sessionId)?.id ?? "";
|
||||
const fallbackId = visibleSessions.find((item) => item.id !== sessionId)?.id ?? "";
|
||||
setActiveSessionId(fallbackId);
|
||||
if (!uiPreview.current) writeSessionUrl(fallbackId || null, "replace");
|
||||
}
|
||||
setComposerNotice(restoring ? "已恢复到聊天记录。" : "已归档,可在左侧归档中恢复。");
|
||||
void writeChatSession(sessionId, { archived_at: nextArchivedAt }, "update").catch((caught) => {
|
||||
updateSession(sessionId, (current) => ({ ...current, archivedAt: session.archivedAt }));
|
||||
if (!restoring && previousActiveId === sessionId) {
|
||||
setActiveSessionId(previousActiveId);
|
||||
if (!uiPreview.current) writeSessionUrl(previousActiveId || null, "replace");
|
||||
}
|
||||
setComposerNotice(caught instanceof Error ? caught.message : "归档同步失败");
|
||||
});
|
||||
}
|
||||
|
||||
async function shareSession(session: ChatSession) {
|
||||
@@ -2680,43 +2724,32 @@ export default function Home() {
|
||||
return;
|
||||
}
|
||||
if (!accountId) return;
|
||||
let record: ChartLibraryRecord = {
|
||||
const record: ChartLibraryRecord = {
|
||||
id: editingChartId || globalThis.crypto.randomUUID(),
|
||||
role: "other",
|
||||
profile: nextProfile,
|
||||
relationship: otherChartRelationship,
|
||||
updatedAt: timestamp(),
|
||||
};
|
||||
let cloudSaved = false;
|
||||
try {
|
||||
if (editingChartId) {
|
||||
record = await updateCloudChartProfile(record);
|
||||
} else {
|
||||
record = await saveCloudChartProfile(record);
|
||||
cloudSaved = true;
|
||||
}
|
||||
cloudSaved = true;
|
||||
} catch {
|
||||
setProfileNotice("已保存到本地星盘库;云端同步失败,稍后会继续使用本地记录。");
|
||||
const saved = editingChartId
|
||||
? await updateCloudChartProfile(record)
|
||||
: await saveCloudChartProfile(record);
|
||||
const selfProfile = account ? readProfile(account.profile) : profile;
|
||||
setChartLibrary((current) => {
|
||||
const others = editingChartId
|
||||
? current.map((item) => item.id === saved.id ? saved : item)
|
||||
: [...current, saved];
|
||||
return upsertSelfChart(others, selfProfile);
|
||||
});
|
||||
setOtherProfileDraft(emptyProfile);
|
||||
setOtherChartRelationship("other");
|
||||
setEditingChartId(null);
|
||||
setAccountError("");
|
||||
}
|
||||
const selfProfile = account ? readProfile(account.profile) : profile;
|
||||
setChartLibrary((current) => {
|
||||
const others = editingChartId
|
||||
? current.map((item) => item.id === record.id ? record : item)
|
||||
: [...current, record];
|
||||
const next = upsertSelfChart(others, selfProfile);
|
||||
localStorage.setItem(chartLibraryStorageKey(accountId), JSON.stringify(next));
|
||||
return next;
|
||||
});
|
||||
setOtherProfileDraft(emptyProfile);
|
||||
setOtherChartRelationship("other");
|
||||
setEditingChartId(null);
|
||||
setAccountError("");
|
||||
if (cloudSaved) {
|
||||
setProfileNotice(editingChartId ? "已更新其他人的星盘资料。" : "已保存到云端星盘库。请选择关系类型后点击“用于合盘”。");
|
||||
} else {
|
||||
setProfileNotice("已保存到本地星盘库。");
|
||||
} catch {
|
||||
setProfileNotice("保存失败,请重试");
|
||||
setAccountError("");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2731,12 +2764,12 @@ export default function Home() {
|
||||
|
||||
async function deleteOtherChart(recordId: string) {
|
||||
if (!accountId || !window.confirm("确定删除这份其他人的星盘资料吗?删除后无法恢复。")) return;
|
||||
let cloudDeleted = false;
|
||||
try {
|
||||
await deleteCloudChartProfile(recordId);
|
||||
cloudDeleted = true;
|
||||
} catch {
|
||||
setProfileNotice("删除失败,请重试");
|
||||
setAccountError("");
|
||||
return;
|
||||
}
|
||||
setChartLibrary((current) => {
|
||||
const next = current.filter((record) => record.id !== recordId || record.role === "self");
|
||||
@@ -2744,13 +2777,10 @@ export default function Home() {
|
||||
setActiveChartId("self");
|
||||
localStorage.setItem(activeChartStorageKey(accountId), "self");
|
||||
}
|
||||
localStorage.setItem(chartLibraryStorageKey(accountId), JSON.stringify(next));
|
||||
return next;
|
||||
});
|
||||
setAccountError("");
|
||||
setProfileNotice(cloudDeleted
|
||||
? "已从云端星盘库删除。"
|
||||
: "已从本地星盘库删除;云端同步失败,稍后云端可能仍显示旧记录。");
|
||||
setProfileNotice("已从云端星盘库删除。");
|
||||
}
|
||||
|
||||
function makeDefaultChart(record: ChartLibraryRecord) {
|
||||
@@ -3073,6 +3103,7 @@ export default function Home() {
|
||||
|
||||
// Merge the server-created session into the local list. The browser
|
||||
// never generates a Case id; it only mirrors the returned binding.
|
||||
const existing = sessions.find((session) => session.id === opened.sessionId);
|
||||
const merged: ChatSession = {
|
||||
id: opened.sessionId,
|
||||
title: resolveSessionTitle("生时校正", undefined, {
|
||||
@@ -3085,6 +3116,8 @@ export default function Home() {
|
||||
updatedAt: timestamp(),
|
||||
sessionType: "birth_time_rectification",
|
||||
rectificationCaseId: opened.caseId,
|
||||
pinned: existing?.pinned ?? false,
|
||||
archivedAt: existing?.archivedAt ?? null,
|
||||
messagesHydrated: true,
|
||||
...chartSnapshotForSession(activeChartId, chartLibrary, profile),
|
||||
};
|
||||
@@ -3197,20 +3230,20 @@ export default function Home() {
|
||||
createdAt: Date.now(),
|
||||
};
|
||||
let savedReportCard = reportCard;
|
||||
let historyPersisted = !accountId;
|
||||
if (accountId) {
|
||||
try {
|
||||
savedReportCard = await saveCloudSynastryReport(reportCard);
|
||||
historyPersisted = true;
|
||||
} catch {
|
||||
// Local history remains the fallback when cloud persistence is unavailable.
|
||||
historyPersisted = false;
|
||||
}
|
||||
}
|
||||
setSynastryReportCard(savedReportCard);
|
||||
if (accountId) {
|
||||
setSynastryHistory((current) => {
|
||||
const next = [savedReportCard, ...current.filter((item) => item.id !== savedReportCard.id)].slice(0, 10);
|
||||
writeSynastryHistory(accountId, next);
|
||||
return next;
|
||||
});
|
||||
if (accountId && historyPersisted) {
|
||||
setSynastryHistory((current) => (
|
||||
[savedReportCard, ...current.filter((item) => item.id !== savedReportCard.id)].slice(0, 10)
|
||||
));
|
||||
}
|
||||
chooseSuggestedQuestion([
|
||||
baseQuestion,
|
||||
@@ -3218,6 +3251,9 @@ export default function Home() {
|
||||
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
|
||||
@@ -4108,8 +4144,8 @@ export default function Home() {
|
||||
const sidebarSessions = visibleSessions.map((session) => ({
|
||||
id: session.id,
|
||||
title: sessionSidebarTitle(session, chartLibrary),
|
||||
pinned: pinnedSessionIds.includes(session.id),
|
||||
archived: archivedSessionIds.includes(session.id),
|
||||
pinned: session.pinned,
|
||||
archived: Boolean(session.archivedAt),
|
||||
}));
|
||||
const sidebarCharts = (chartLibrary.length > 0
|
||||
? chartLibrary
|
||||
@@ -4354,7 +4390,7 @@ export default function Home() {
|
||||
newChatDisabled={!hydrated || !modelCatalog || creatingSession || Boolean(pendingSessionId) || cancellationPending}
|
||||
creatingSession={creatingSession}
|
||||
sessionControls={{
|
||||
archivedCount: archivedSessionIds.length,
|
||||
archivedCount: sessions.filter((session) => session.archivedAt).length,
|
||||
showingArchived: showArchivedSessions,
|
||||
menuSessionId: sessionMenuId,
|
||||
disabled: Boolean(pendingSessionId) || cancellationPending,
|
||||
|
||||
Reference in New Issue
Block a user