import { readCurrentSubjectId } from "@/lib/current-subject"; import { isConsultationSubjectProfileComplete } from "@/lib/consultation-subject-resolver"; import { sessionChartLabel } from "@/lib/home-profile"; import type { ChartLibraryRecord, ChatSession } from "@/lib/home-types"; export type ChatProfileCatalogStatus = "idle" | "loading" | "ready" | "failed" | "degraded"; /** New ordinary chats follow the current person, not a fixed self id. */ export function NEW_CHAT_SUBJECT_ID(): string { return readCurrentSubjectId(); } export type ChatProfileChoice = | { readonly action: "bind"; readonly chartProfileId: string } | { readonly action: "open-new"; readonly chartProfileId: string } | { readonly action: "ignore" }; export type ChatProfilePickerRow = { readonly id: string; readonly role: "self" | "other"; readonly name: string; readonly detail: string; readonly complete: boolean; readonly selected: boolean; }; type SubjectSession = Pick; export function chatSessionSubjectLocked(session: { sessionType: string; messagesHydrated: boolean; messages: readonly unknown[]; }): boolean { if (session.sessionType !== "consultation") return true; if (!session.messagesHydrated) return true; return session.messages.length > 0; } export function sessionMatchesSubject( session: Pick, subjectId: string, ): boolean { const id = session.chartProfileId?.trim() || null; if (!subjectId || subjectId === "self") { return id == null || id === "self" || session.chartProfileRole === "self"; } return id === subjectId; } export function chatProfileSelectedId(session: Pick): string { if (session.chartProfileRole === "other" || (session.chartProfileId && session.chartProfileId !== "self")) { return session.chartProfileId ?? ""; } return "self"; } export function chatProfileDisplayName(input: { session: Pick; status: ChatProfileCatalogStatus; library: readonly ChartLibraryRecord[]; }): string { if (input.status !== "ready") { const snapshot = input.session.chartProfileName?.trim(); if (snapshot) return snapshot; if (!input.session.chartProfileId || input.session.chartProfileId === "self" || input.session.chartProfileRole !== "other") { return "我"; } return "未命名资料"; } return sessionChartLabel(input.session, input.library); } export function chatSubjectSendBlocked(input: { session: Pick | undefined; status: ChatProfileCatalogStatus; library: readonly ChartLibraryRecord[]; }): boolean { if (!input.session || input.session.sessionType !== "consultation") return false; if (input.status !== "ready") return false; const id = input.session.chartProfileId; if (!id || id === "self" || input.session.chartProfileRole === "self") return false; return !input.library.some((record) => record.id === id && record.role === "other"); } function rowName(record: ChartLibraryRecord): string { return record.profile.name.trim() || (record.role === "self" ? "我" : "未命名资料"); } export function chatProfilePickerRows(input: { status: ChatProfileCatalogStatus; library: readonly ChartLibraryRecord[]; selectedId: string; }): ChatProfilePickerRow[] { if (input.status !== "ready" && input.status !== "degraded") return []; const self = input.library.find((record) => record.role === "self" || record.id === "self"); const others = input.library.filter((record) => record.role === "other" && record.id !== "self"); const rows: ChatProfilePickerRow[] = []; if (self) rows.push(toRow(self, input.selectedId)); else { rows.push({ id: "self", role: "self", name: "我", detail: "这份资料还不完整", complete: false, selected: input.selectedId === "self", }); } if (input.status === "degraded") return rows; for (const record of others) rows.push(toRow(record, input.selectedId)); return rows; } function toRow(record: ChartLibraryRecord, selectedId: string): ChatProfilePickerRow { const complete = isConsultationSubjectProfileComplete(record.profile); const id = record.role === "self" ? "self" : record.id; return { id, role: record.role, name: rowName(record), detail: complete ? (record.role === "self" ? "本人" : (record.profile.date || "其他人")) : "这份资料还不完整", complete, selected: id === selectedId, }; } export function chatProfileChoice(input: { session: Pick; chartProfileId: string; requestInFlight: boolean; libraryReady: boolean; selectable: boolean; }): ChatProfileChoice { if (!input.libraryReady || !input.selectable || !input.chartProfileId) return { action: "ignore" }; if (input.session.sessionType !== "consultation") return { action: "ignore" }; if (chatSessionSubjectLocked(input.session)) { if (input.chartProfileId === (input.session.chartProfileId || "self")) return { action: "ignore" }; if (input.requestInFlight) return { action: "ignore" }; return { action: "open-new", chartProfileId: input.chartProfileId }; } if (input.chartProfileId === input.session.chartProfileId) return { action: "ignore" }; return { action: "bind", chartProfileId: input.chartProfileId }; } /** Empty-session PATCH body. The server fills the name; the client does not send one. */ export function emptySessionBindingWrite(chartProfileId: string, role: "self" | "other"): { chart_profile_id: string; chart_profile_role: "self" | "other"; } { return { chart_profile_id: role === "self" ? "self" : chartProfileId, chart_profile_role: role, }; }