fix(chat): choose the chart person before the first message
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
import type { ChatProfileCatalogStatus } from "@/lib/chat-profile-picker-model";
|
||||
|
||||
/**
|
||||
* Load state for the ordinary-chat person list.
|
||||
* The page effect still owns the chart-library fetch; this hook only records
|
||||
* whether that read is in flight, ready, or failed. A failure stays failed
|
||||
* until an explicit retry, so a self-only fallback cannot look like deletion.
|
||||
*/
|
||||
export function useChatProfileCatalog(accountId: string | undefined) {
|
||||
const accountRef = useRef(accountId);
|
||||
useEffect(() => {
|
||||
accountRef.current = accountId;
|
||||
}, [accountId]);
|
||||
const [trackedAccount, setTrackedAccount] = useState(accountId);
|
||||
const [status, setStatus] = useState<ChatProfileCatalogStatus>(accountId ? "loading" : "idle");
|
||||
if (trackedAccount !== accountId) {
|
||||
setTrackedAccount(accountId);
|
||||
setStatus(accountId ? "loading" : "idle");
|
||||
}
|
||||
|
||||
const markReady = useCallback((forAccountId: string) => {
|
||||
if (accountRef.current !== forAccountId) return;
|
||||
setStatus("ready");
|
||||
}, []);
|
||||
|
||||
const markFailed = useCallback((forAccountId: string) => {
|
||||
if (accountRef.current !== forAccountId) return;
|
||||
setStatus("failed");
|
||||
}, []);
|
||||
|
||||
const beginReload = useCallback(() => {
|
||||
if (!accountRef.current) return;
|
||||
setStatus("loading");
|
||||
}, []);
|
||||
|
||||
return { status, markReady, markFailed, beginReload };
|
||||
}
|
||||
@@ -117,6 +117,7 @@ export type ConsultationRunParams = {
|
||||
pendingConsultation: MutableRefObject<PendingConsultation | null>;
|
||||
pendingSessionId: string | null;
|
||||
profile: Profile;
|
||||
subjectDeleted: boolean;
|
||||
sessions: ChatSession[];
|
||||
setAccount: Dispatch<SetStateAction<Account | null>>;
|
||||
setActiveSessionId: Dispatch<SetStateAction<string>>;
|
||||
@@ -175,6 +176,7 @@ export function useConsultationRun(params: ConsultationRunParams) {
|
||||
pendingConsultation,
|
||||
pendingSessionId,
|
||||
profile,
|
||||
subjectDeleted,
|
||||
sessions,
|
||||
setAccount,
|
||||
setActiveSessionId,
|
||||
@@ -513,6 +515,10 @@ export function useConsultationRun(params: ConsultationRunParams) {
|
||||
: null);
|
||||
const resumeRequestId = options.resumeRequestId;
|
||||
const resuming = Boolean(resumeRequestId);
|
||||
if (!resuming && subjectDeleted) {
|
||||
setComposerNotice("这份资料已经删除,这段对话只能看。");
|
||||
return false;
|
||||
}
|
||||
const liveSession = targetSessionId
|
||||
? sessions.find((session) => session.id === targetSessionId)
|
||||
: activeSession;
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
type BirthTimeConsultationConsentState,
|
||||
} from "@/lib/birth-time-consultation-consent";
|
||||
import {
|
||||
activeChartStorageKey,
|
||||
createSession,
|
||||
fetchSessionDetail,
|
||||
fetchSessions,
|
||||
@@ -34,6 +33,12 @@ import {
|
||||
isUnsavedEmptyConsultation,
|
||||
replaceUnsavedEmptyConsultations,
|
||||
} from "@/lib/session-list-filter";
|
||||
import {
|
||||
chatSessionSubjectLocked,
|
||||
emptySessionBindingWrite,
|
||||
NEW_CHAT_SUBJECT_ID,
|
||||
} from "@/lib/chat-profile-picker-model";
|
||||
import { isConsultationSubjectProfileComplete } from "@/lib/consultation-subject-resolver";
|
||||
import { chartSnapshotForSession } from "@/lib/home-profile";
|
||||
import { beginSessionPageLoad, mergeSessionPage } from "@/lib/session-groups";
|
||||
import type { ConsultationEntrypoint } from "@/lib/consultation-entrypoint";
|
||||
@@ -91,8 +96,6 @@ export type SessionManagementParams = {
|
||||
export function useSessionManagement(params: SessionManagementParams) {
|
||||
const {
|
||||
account,
|
||||
accountId,
|
||||
activeChartId,
|
||||
activeSession,
|
||||
activeSessionId,
|
||||
activeSessionIdRef,
|
||||
@@ -111,7 +114,6 @@ export function useSessionManagement(params: SessionManagementParams) {
|
||||
sessionSelectionSource,
|
||||
sessions,
|
||||
sessionsRef,
|
||||
setActiveChartId,
|
||||
setActiveSessionId,
|
||||
setBirthTimeConsultationConsent,
|
||||
setDraft,
|
||||
@@ -297,7 +299,7 @@ export function useSessionManagement(params: SessionManagementParams) {
|
||||
if (!account || !modelCatalog || creatingSession) return null;
|
||||
const nextSession = {
|
||||
...createSession(modelCatalog.defaultModelId),
|
||||
...chartSnapshotForSession(activeChartId, chartLibrary, profile),
|
||||
...chartSnapshotForSession(NEW_CHAT_SUBJECT_ID, chartLibrary, profile),
|
||||
};
|
||||
for (const session of sessions) {
|
||||
if (isUnsavedEmptyConsultation(session)) pendingCreateById.current.delete(session.id);
|
||||
@@ -330,13 +332,8 @@ export function useSessionManagement(params: SessionManagementParams) {
|
||||
setComposerNotice("");
|
||||
setRectificationError("");
|
||||
setRectificationErrorSessionId(null);
|
||||
if (nextSession?.chartProfileId) {
|
||||
const boundChart = chartLibrary.find((record) => record.id === nextSession.chartProfileId);
|
||||
if (boundChart && boundChart.id !== activeChartId && accountId) {
|
||||
setActiveChartId(boundChart.id);
|
||||
localStorage.setItem(activeChartStorageKey(accountId), boundChart.id);
|
||||
}
|
||||
}
|
||||
// Opening a stored session keeps that session's binding. It does not
|
||||
// follow or rewrite the account-level active chart.
|
||||
if (nextSession?.sessionType === "birth_time_rectification") {
|
||||
setRectificationError("");
|
||||
if (deferredRectificationSwitch) {
|
||||
@@ -490,6 +487,93 @@ export function useSessionManagement(params: SessionManagementParams) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function selectableChartRecord(chartProfileId: string) {
|
||||
const record = chartProfileId === "self"
|
||||
? chartLibrary.find((item) => item.id === "self" || item.role === "self")
|
||||
: chartLibrary.find((item) => item.id === chartProfileId && item.role === "other");
|
||||
if (!record || !isConsultationSubjectProfileComplete(record.profile)) return null;
|
||||
return record;
|
||||
}
|
||||
|
||||
async function bindEmptyChatSubject(chartProfileId: string): Promise<boolean> {
|
||||
const session = sessionsRef.current.find((item) => item.id === activeSession?.id) ?? activeSession;
|
||||
if (!session || chatSessionSubjectLocked(session)) return false;
|
||||
const record = selectableChartRecord(chartProfileId);
|
||||
if (!record) return false;
|
||||
const requestedId = record.role === "self" ? "self" : record.id;
|
||||
if (session.chartProfileId === requestedId && session.chartProfileRole === record.role) return true;
|
||||
if (cloudCreatedIds.current.has(session.id)) {
|
||||
const previousRole = session.chartProfileRole === "self" || session.chartProfileRole === "other"
|
||||
? session.chartProfileRole
|
||||
: null;
|
||||
try {
|
||||
await writeChatSession(session.id, emptySessionBindingWrite(requestedId, record.role), "update");
|
||||
const detailed = await fetchSessionDetail(session.id, modelCatalog);
|
||||
if (detailed?.chartProfileId !== requestedId || detailed.chartProfileRole !== record.role) {
|
||||
throw new Error("人物没有绑上");
|
||||
}
|
||||
updateSession(session.id, (current) => {
|
||||
if (chatSessionSubjectLocked(current)) return current;
|
||||
return {
|
||||
...current,
|
||||
chartProfileId: detailed.chartProfileId,
|
||||
chartProfileName: detailed.chartProfileName,
|
||||
chartProfileRole: detailed.chartProfileRole,
|
||||
};
|
||||
});
|
||||
return true;
|
||||
} catch (caught) {
|
||||
if (previousRole) {
|
||||
try {
|
||||
await writeChatSession(
|
||||
session.id,
|
||||
emptySessionBindingWrite(session.chartProfileId ?? "self", previousRole),
|
||||
"update",
|
||||
);
|
||||
} catch {
|
||||
setComposerNotice("人物没有绑上");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
setComposerNotice(caught instanceof Error && caught.message !== "人物没有绑上"
|
||||
? caught.message
|
||||
: "人物没有绑上");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const snapshot = chartSnapshotForSession(requestedId, chartLibrary, profile);
|
||||
updateSession(session.id, (current) => {
|
||||
if (chatSessionSubjectLocked(current)) return current;
|
||||
return { ...current, ...snapshot };
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
async function openChatBoundToProfile(chartProfileId: string): Promise<boolean> {
|
||||
if (!account || !modelCatalog || creatingSession) return false;
|
||||
if (pendingSessionId || cancellationPending) return false;
|
||||
const record = selectableChartRecord(chartProfileId);
|
||||
if (!record) return false;
|
||||
const requestedId = record.role === "self" ? "self" : record.id;
|
||||
const nextSession = {
|
||||
...createSession(modelCatalog.defaultModelId),
|
||||
...chartSnapshotForSession(requestedId, chartLibrary, profile),
|
||||
};
|
||||
for (const existing of sessions) {
|
||||
if (isUnsavedEmptyConsultation(existing)) pendingCreateById.current.delete(existing.id);
|
||||
}
|
||||
pendingCreateById.current.set(nextSession.id, {});
|
||||
setSessions((current) => replaceUnsavedEmptyConsultations(current, nextSession));
|
||||
setActiveSessionId(nextSession.id);
|
||||
setDraft("");
|
||||
setDraftTheme(null);
|
||||
setDraftEntrypoint(null);
|
||||
setComposerNotice("");
|
||||
setRequestError(null);
|
||||
return true;
|
||||
}
|
||||
|
||||
return {
|
||||
visibleSessions,
|
||||
sessionsCursor,
|
||||
@@ -508,6 +592,8 @@ export function useSessionManagement(params: SessionManagementParams) {
|
||||
togglePinnedSession,
|
||||
shareSession,
|
||||
startNewChat,
|
||||
bindEmptyChatSubject,
|
||||
openChatBoundToProfile,
|
||||
selectSession,
|
||||
selectSessionModel,
|
||||
loadMoreSessions,
|
||||
|
||||
Reference in New Issue
Block a user