refactor(home): 校正面 15 个状态从 Home 下沉
Home() useState 66->52, useRef 41->39. Shell-facing fields merge into one object; subtree state lives in useRectificationSurface. Chat props 20->7. Growth contract rebaselined. Zero behavior/copy change.
This commit is contained in:
+31
-51
@@ -21,13 +21,11 @@ import {
|
||||
entrySummaryFromResponse,
|
||||
rectificationEntryLabels,
|
||||
resolveRectificationEntryAction,
|
||||
type RectificationEntrySummary,
|
||||
} from "@/lib/rectification-entry";
|
||||
import { ConversationalBirthTimeRectification, type PersistedRectificationTurn } from "@/components/conversational-birth-time-rectification";
|
||||
import { ConversationalBirthTimeRectification } from "@/components/conversational-birth-time-rectification";
|
||||
import {
|
||||
declaredBirthTime,
|
||||
RECTIFICATION_OPENING_LABEL,
|
||||
type RectificationCaseSnapshotPayload,
|
||||
} from "@/lib/rectification-surface-state";
|
||||
import {
|
||||
toggleChatMessageFeedback,
|
||||
@@ -72,7 +70,11 @@ import { useBirthTimeGuidedJourney } from "@/hooks/use-birth-time-guided-journey
|
||||
import { useConsultationRun } from "@/hooks/use-consultation-run";
|
||||
import { useConversationScrollAnchor } from "@/hooks/use-conversation-scroll-anchor";
|
||||
import { useProfileOnboarding } from "@/hooks/use-profile-onboarding";
|
||||
import { useRectificationSurface } from "@/hooks/use-rectification-surface";
|
||||
import {
|
||||
createRectificationShellSetters,
|
||||
EMPTY_RECTIFICATION_SHELL,
|
||||
useRectificationSurface,
|
||||
} from "@/hooks/use-rectification-surface";
|
||||
import { useSessionManagement } from "@/hooks/use-session-management";
|
||||
import { sortSessions } from "@/lib/session-groups";
|
||||
import { showChatNotice as setComposerNotice } from "@/lib/chat-notice";
|
||||
@@ -282,23 +284,26 @@ export default function Home() {
|
||||
const [birthTimeConsultationConsent, setBirthTimeConsultationConsent] = useState<BirthTimeConsultationConsentState>(
|
||||
createBirthTimeConsultationConsentState,
|
||||
);
|
||||
const [rectificationSessionId, setRectificationSessionId] = useState<string | null>(null);
|
||||
const [rectificationCaseId, setRectificationCaseId] = useState<string | null>(null);
|
||||
const [rectificationHeaderSlot, setRectificationHeaderSlot] = useState<HTMLElement | null>(null);
|
||||
const [rectificationPendingQuestion, setRectificationPendingQuestion] = useState<string | null>(null);
|
||||
const [rectificationLoading, setRectificationLoading] = useState(false);
|
||||
const [rectificationMutationPending, setRectificationMutationPending] = useState(false);
|
||||
const [rectificationError, setRectificationError] = useState("");
|
||||
const [rectificationErrorSessionId, setRectificationErrorSessionId] = useState<string | null>(null);
|
||||
const [rectificationReadonly, setRectificationReadonly] = useState(false);
|
||||
const [rectificationShouldStartOpening, setRectificationShouldStartOpening] = useState(false);
|
||||
const [rectificationTurns, setRectificationTurns] = useState<PersistedRectificationTurn[]>([]);
|
||||
const [rectificationSnapshot, setRectificationSnapshot] = useState<RectificationCaseSnapshotPayload | null>(null);
|
||||
const [rectificationOpeningSessionId, setRectificationOpeningSessionId] = useState<string | null>(null);
|
||||
const [rectificationEntrySummary, setRectificationEntrySummary] = useState<RectificationEntrySummary | null>(null);
|
||||
const [rectification, setRectification] = useState(EMPTY_RECTIFICATION_SHELL);
|
||||
const {
|
||||
sessionId: rectificationSessionId,
|
||||
caseId: rectificationCaseId,
|
||||
openingSessionId: rectificationOpeningSessionId,
|
||||
loading: rectificationLoading,
|
||||
mutationPending: rectificationMutationPending,
|
||||
entrySummary: rectificationEntrySummary,
|
||||
entrySummarySettled: rectificationEntrySummarySettled,
|
||||
error: rectificationError,
|
||||
errorSessionId: rectificationErrorSessionId,
|
||||
} = rectification;
|
||||
const {
|
||||
setRectificationError,
|
||||
setRectificationErrorSessionId,
|
||||
setRectificationEntrySummary,
|
||||
setRectificationEntrySummarySettled,
|
||||
} = createRectificationShellSetters(setRectification);
|
||||
const [hydrated, setHydrated] = useState(false);
|
||||
const [bootstrapPhase, setBootstrapPhase] = useState<BootstrapPhase>("account");
|
||||
const [rectificationEntrySummarySettled, setRectificationEntrySummarySettled] = useState(false);
|
||||
const prepareStartedAt = useRef<number | null>(null);
|
||||
const [guidedJourneyPreview, setGuidedJourneyPreview] = useState(false);
|
||||
const [profileSaving, setProfileSaving] = useState(false);
|
||||
@@ -347,8 +352,6 @@ export default function Home() {
|
||||
onFollowUp() {},
|
||||
});
|
||||
const accountRefreshGuard = useRef(createLatestAccountRequestGuard());
|
||||
const resumeRectificationSession = useRef<(session: ChatSession) => void>(() => undefined);
|
||||
const rectificationOpenInFlight = useRef(false);
|
||||
const sessionDetailInFlight = useRef(new Set<string>());
|
||||
const sessionsRef = useRef(sessions);
|
||||
const rectificationSessionOpenerRef = useRef(async (_exactSessionId: string) => {});
|
||||
@@ -465,21 +468,15 @@ export default function Home() {
|
||||
});
|
||||
|
||||
const {
|
||||
refreshRectificationCase,
|
||||
setRectificationHeaderSlot,
|
||||
rectificationPanel,
|
||||
resumeRectificationSession,
|
||||
openRectificationFromHomepage,
|
||||
startNewRectification,
|
||||
handleRectificationProfileIncomplete,
|
||||
handleRectificationMessagesChange,
|
||||
} = useRectificationSurface({
|
||||
account, activeChartId, activeSessionIdRef, chartLibrary, creatingSession, modelCatalog,
|
||||
persistSession, profile, rectificationLoading, rectificationMutationPending,
|
||||
rectificationOpenInFlight, rectificationSessionId, resumeRectificationSession,
|
||||
persistSession, profile, rectification, setRectification,
|
||||
sessionSelectionSource, sessions, setActiveSessionId, setDraft, setDraftEntrypoint,
|
||||
setDraftTheme, setOnboardingStep, setProfileNotice, setRectificationCaseId,
|
||||
setRectificationEntrySummary, setRectificationError, setRectificationErrorSessionId, setRectificationLoading,
|
||||
setRectificationPendingQuestion, setRectificationReadonly, setRectificationSessionId,
|
||||
setRectificationShouldStartOpening, setRectificationSnapshot, setRectificationOpeningSessionId,
|
||||
setRectificationTurns, setSessions, uiPreview,
|
||||
setDraftTheme, setOnboardingStep, setProfileNotice, setSessions, uiPreview,
|
||||
updateSession, openAccountDialog, refreshAccount, rectificationSessionOpenerRef,
|
||||
});
|
||||
|
||||
@@ -540,6 +537,7 @@ export default function Home() {
|
||||
rectificationLoading,
|
||||
rectificationMutationPending,
|
||||
rectificationSessionId,
|
||||
resumeRectificationSession,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -1855,31 +1853,13 @@ export default function Home() {
|
||||
{rectificationSurfaceOpen && rectificationCaseId && (
|
||||
<ConversationalBirthTimeRectification
|
||||
key={`${rectificationSessionId}-${rectificationCaseId}`}
|
||||
caseId={rectificationCaseId}
|
||||
sessionId={rectificationSessionId ?? ""}
|
||||
readonly={rectificationReadonly}
|
||||
shouldStartOpening={rectificationShouldStartOpening}
|
||||
initialTurns={rectificationTurns}
|
||||
initialSnapshot={rectificationSnapshot}
|
||||
declaredTime={rectificationDeclaredTime}
|
||||
models={modelCatalog?.models ?? []}
|
||||
selectedModelId={activeSession?.modelId ?? ""}
|
||||
onSelectModel={(modelId) => void selectSessionModel(modelId)}
|
||||
onMessagesChange={handleRectificationMessagesChange}
|
||||
onOpeningConsumed={() => setRectificationShouldStartOpening(false)}
|
||||
onCompleted={() => {
|
||||
void refreshAccount();
|
||||
if (rectificationCaseId && rectificationSessionId) {
|
||||
void refreshRectificationCase(rectificationCaseId, rectificationSessionId);
|
||||
}
|
||||
}}
|
||||
onPendingChange={setRectificationMutationPending}
|
||||
onProfileIncomplete={handleRectificationProfileIncomplete}
|
||||
onOpenBilling={(options) => openAccountDialog("billing", { source: options?.source ?? "rectification" })}
|
||||
onSaved={() => void refreshAccount()}
|
||||
pendingConsultationQuestion={rectificationPendingQuestion}
|
||||
onRestart={() => void startNewRectification()}
|
||||
headerSlot={rectificationHeaderSlot}
|
||||
panel={rectificationPanel}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ export type PersistedRectificationTurn = Readonly<{
|
||||
}> | null;
|
||||
}>;
|
||||
|
||||
export type ConversationalBirthTimeRectificationProps = Readonly<{
|
||||
export type RectificationChatPanel = Readonly<{
|
||||
caseId: string;
|
||||
sessionId: string;
|
||||
readonly: boolean;
|
||||
@@ -39,23 +39,44 @@ export type ConversationalBirthTimeRectificationProps = Readonly<{
|
||||
initialTurns: readonly PersistedRectificationTurn[];
|
||||
/** The Case snapshot read together with the turns before the surface mounted; null when hydration failed. */
|
||||
initialSnapshot: RectificationCaseSnapshotPayload | null;
|
||||
pendingConsultationQuestion?: string | null;
|
||||
headerSlot: HTMLElement | null;
|
||||
onMessagesChange?: (messages: ChatMessage[]) => void;
|
||||
onCompleted?: () => void;
|
||||
onPendingChange?: (pending: boolean) => void;
|
||||
onProfileIncomplete?: () => void;
|
||||
onOpeningConsumed?: () => void;
|
||||
onRestart?: () => void;
|
||||
}>;
|
||||
|
||||
export type ConversationalBirthTimeRectificationProps = Readonly<{
|
||||
/** The declared birth minute from the profile, shown on the board before any candidate exists. */
|
||||
declaredTime: string | null;
|
||||
models: readonly PublicLanguageModel[];
|
||||
selectedModelId: string;
|
||||
onSelectModel: (modelId: string) => void;
|
||||
onMessagesChange?: (messages: ChatMessage[]) => void;
|
||||
onCompleted?: () => void;
|
||||
onPendingChange?: (pending: boolean) => void;
|
||||
onProfileIncomplete?: () => void;
|
||||
onOpenBilling?: (options?: { source?: string }) => void;
|
||||
onSaved?: (time: string, status: "accepted" | "confirmed") => void;
|
||||
onOpeningConsumed?: () => void;
|
||||
pendingConsultationQuestion?: string | null;
|
||||
onRestart?: () => void;
|
||||
headerSlot: HTMLElement | null;
|
||||
panel: RectificationChatPanel;
|
||||
}>;
|
||||
|
||||
export function ConversationalBirthTimeRectification(props: ConversationalBirthTimeRectificationProps) {
|
||||
export function ConversationalBirthTimeRectification({
|
||||
declaredTime,
|
||||
models,
|
||||
selectedModelId,
|
||||
onSelectModel,
|
||||
onOpenBilling,
|
||||
onSaved,
|
||||
panel,
|
||||
}: ConversationalBirthTimeRectificationProps) {
|
||||
const props = {
|
||||
declaredTime,
|
||||
models,
|
||||
selectedModelId,
|
||||
onSelectModel,
|
||||
onOpenBilling,
|
||||
onSaved,
|
||||
...panel,
|
||||
};
|
||||
return <RectificationAgenticChat {...props} />;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import type { Dispatch, MutableRefObject, SetStateAction } from "react";
|
||||
import { useEffect, useRef, useState, type Dispatch, type MutableRefObject, type SetStateAction } from "react";
|
||||
|
||||
import { resolveSessionTitle } from "@/lib/agent-reply";
|
||||
import { rectificationOpenIdentity } from "@/lib/rectification-session-open";
|
||||
@@ -27,7 +27,10 @@ import {
|
||||
openResponseFromPayload,
|
||||
type RectificationEntrySummary,
|
||||
} from "@/lib/rectification-entry";
|
||||
import type { PersistedRectificationTurn } from "@/components/conversational-birth-time-rectification";
|
||||
import type {
|
||||
PersistedRectificationTurn,
|
||||
RectificationChatPanel,
|
||||
} from "@/components/conversational-birth-time-rectification";
|
||||
import type { PublicLanguageModelCatalog } from "@/lib/public-models";
|
||||
import {
|
||||
hydrateRectificationCase,
|
||||
@@ -38,6 +41,60 @@ import {
|
||||
type RectificationCaseSnapshotPayload,
|
||||
} from "@/lib/rectification-surface-state";
|
||||
|
||||
export type RectificationShellState = {
|
||||
sessionId: string | null;
|
||||
caseId: string | null;
|
||||
openingSessionId: string | null;
|
||||
loading: boolean;
|
||||
mutationPending: boolean;
|
||||
entrySummary: RectificationEntrySummary | null;
|
||||
entrySummarySettled: boolean;
|
||||
error: string;
|
||||
errorSessionId: string | null;
|
||||
};
|
||||
|
||||
export const EMPTY_RECTIFICATION_SHELL: RectificationShellState = {
|
||||
sessionId: null,
|
||||
caseId: null,
|
||||
openingSessionId: null,
|
||||
loading: false,
|
||||
mutationPending: false,
|
||||
entrySummary: null,
|
||||
entrySummarySettled: false,
|
||||
error: "",
|
||||
errorSessionId: null,
|
||||
};
|
||||
|
||||
function fieldSetter<K extends keyof RectificationShellState>(
|
||||
setRectification: Dispatch<SetStateAction<RectificationShellState>>,
|
||||
key: K,
|
||||
): Dispatch<SetStateAction<RectificationShellState[K]>> {
|
||||
return (value) => {
|
||||
setRectification((current) => ({
|
||||
...current,
|
||||
[key]: typeof value === "function"
|
||||
? (value as (prev: RectificationShellState[K]) => RectificationShellState[K])(current[key])
|
||||
: value,
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
||||
export function createRectificationShellSetters(
|
||||
setRectification: Dispatch<SetStateAction<RectificationShellState>>,
|
||||
) {
|
||||
return {
|
||||
setRectificationSessionId: fieldSetter(setRectification, "sessionId"),
|
||||
setRectificationCaseId: fieldSetter(setRectification, "caseId"),
|
||||
setRectificationOpeningSessionId: fieldSetter(setRectification, "openingSessionId"),
|
||||
setRectificationLoading: fieldSetter(setRectification, "loading"),
|
||||
setRectificationMutationPending: fieldSetter(setRectification, "mutationPending"),
|
||||
setRectificationEntrySummary: fieldSetter(setRectification, "entrySummary"),
|
||||
setRectificationEntrySummarySettled: fieldSetter(setRectification, "entrySummarySettled"),
|
||||
setRectificationError: fieldSetter(setRectification, "error"),
|
||||
setRectificationErrorSessionId: fieldSetter(setRectification, "errorSessionId"),
|
||||
};
|
||||
}
|
||||
|
||||
export type RectificationSurfaceParams = {
|
||||
account: Account | null;
|
||||
activeChartId: string;
|
||||
@@ -47,11 +104,8 @@ export type RectificationSurfaceParams = {
|
||||
modelCatalog: PublicLanguageModelCatalog | null;
|
||||
persistSession: (session: ChatSession, mode?: "create" | "update") => Promise<void>;
|
||||
profile: Profile;
|
||||
rectificationLoading: boolean;
|
||||
rectificationMutationPending: boolean;
|
||||
rectificationOpenInFlight: MutableRefObject<boolean>;
|
||||
rectificationSessionId: string | null;
|
||||
resumeRectificationSession: MutableRefObject<(session: ChatSession) => void>;
|
||||
rectification: RectificationShellState;
|
||||
setRectification: Dispatch<SetStateAction<RectificationShellState>>;
|
||||
sessionSelectionSource: MutableRefObject<"user" | "history">;
|
||||
sessions: ChatSession[];
|
||||
setActiveSessionId: Dispatch<SetStateAction<string>>;
|
||||
@@ -60,18 +114,6 @@ export type RectificationSurfaceParams = {
|
||||
setDraftTheme: (theme: Theme | null) => void;
|
||||
setOnboardingStep: Dispatch<SetStateAction<OnboardingStep>>;
|
||||
setProfileNotice: Dispatch<SetStateAction<string>>;
|
||||
setRectificationCaseId: Dispatch<SetStateAction<string | null>>;
|
||||
setRectificationEntrySummary: Dispatch<SetStateAction<RectificationEntrySummary | null>>;
|
||||
setRectificationError: Dispatch<SetStateAction<string>>;
|
||||
setRectificationErrorSessionId: Dispatch<SetStateAction<string | null>>;
|
||||
setRectificationLoading: Dispatch<SetStateAction<boolean>>;
|
||||
setRectificationPendingQuestion: Dispatch<SetStateAction<string | null>>;
|
||||
setRectificationReadonly: Dispatch<SetStateAction<boolean>>;
|
||||
setRectificationSessionId: Dispatch<SetStateAction<string | null>>;
|
||||
setRectificationShouldStartOpening: Dispatch<SetStateAction<boolean>>;
|
||||
setRectificationSnapshot: Dispatch<SetStateAction<RectificationCaseSnapshotPayload | null>>;
|
||||
setRectificationOpeningSessionId: Dispatch<SetStateAction<string | null>>;
|
||||
setRectificationTurns: Dispatch<SetStateAction<PersistedRectificationTurn[]>>;
|
||||
setSessions: Dispatch<SetStateAction<ChatSession[]>>;
|
||||
uiPreview: MutableRefObject<boolean>;
|
||||
updateSession: (sessionId: string, change: (session: ChatSession) => ChatSession) => void;
|
||||
@@ -90,11 +132,8 @@ export function useRectificationSurface(params: RectificationSurfaceParams) {
|
||||
modelCatalog,
|
||||
persistSession,
|
||||
profile,
|
||||
rectificationLoading,
|
||||
rectificationMutationPending,
|
||||
rectificationOpenInFlight,
|
||||
rectificationSessionId,
|
||||
resumeRectificationSession,
|
||||
rectification,
|
||||
setRectification,
|
||||
sessionSelectionSource,
|
||||
sessions,
|
||||
setActiveSessionId,
|
||||
@@ -103,18 +142,6 @@ export function useRectificationSurface(params: RectificationSurfaceParams) {
|
||||
setDraftTheme,
|
||||
setOnboardingStep,
|
||||
setProfileNotice,
|
||||
setRectificationCaseId,
|
||||
setRectificationEntrySummary,
|
||||
setRectificationError,
|
||||
setRectificationErrorSessionId,
|
||||
setRectificationLoading,
|
||||
setRectificationPendingQuestion,
|
||||
setRectificationReadonly,
|
||||
setRectificationSessionId,
|
||||
setRectificationShouldStartOpening,
|
||||
setRectificationSnapshot,
|
||||
setRectificationOpeningSessionId,
|
||||
setRectificationTurns,
|
||||
setSessions,
|
||||
uiPreview,
|
||||
updateSession,
|
||||
@@ -123,6 +150,29 @@ export function useRectificationSurface(params: RectificationSurfaceParams) {
|
||||
rectificationSessionOpenerRef,
|
||||
} = params;
|
||||
|
||||
const rectificationSessionId = rectification.sessionId;
|
||||
const rectificationCaseId = rectification.caseId;
|
||||
const rectificationLoading = rectification.loading;
|
||||
const rectificationMutationPending = rectification.mutationPending;
|
||||
|
||||
const setRectificationSessionId = fieldSetter(setRectification, "sessionId");
|
||||
const setRectificationCaseId = fieldSetter(setRectification, "caseId");
|
||||
const setRectificationOpeningSessionId = fieldSetter(setRectification, "openingSessionId");
|
||||
const setRectificationLoading = fieldSetter(setRectification, "loading");
|
||||
const setRectificationMutationPending = fieldSetter(setRectification, "mutationPending");
|
||||
const setRectificationEntrySummary = fieldSetter(setRectification, "entrySummary");
|
||||
const setRectificationError = fieldSetter(setRectification, "error");
|
||||
const setRectificationErrorSessionId = fieldSetter(setRectification, "errorSessionId");
|
||||
|
||||
const [rectificationHeaderSlot, setRectificationHeaderSlot] = useState<HTMLElement | null>(null);
|
||||
const [rectificationPendingQuestion, setRectificationPendingQuestion] = useState<string | null>(null);
|
||||
const [rectificationReadonly, setRectificationReadonly] = useState(false);
|
||||
const [rectificationShouldStartOpening, setRectificationShouldStartOpening] = useState(false);
|
||||
const [rectificationTurns, setRectificationTurns] = useState<PersistedRectificationTurn[]>([]);
|
||||
const [rectificationSnapshot, setRectificationSnapshot] = useState<RectificationCaseSnapshotPayload | null>(null);
|
||||
const rectificationOpenInFlight = useRef(false);
|
||||
const resumeRectificationSession = useRef<(session: ChatSession) => void>(() => undefined);
|
||||
|
||||
async function refreshRectificationEntrySummary() {
|
||||
if (!account) return;
|
||||
try {
|
||||
@@ -289,12 +339,6 @@ export function useRectificationSurface(params: RectificationSurfaceParams) {
|
||||
await openRectificationCase("new", null, null);
|
||||
}
|
||||
|
||||
resumeRectificationSession.current = (session) => {
|
||||
sessionSelectionSource.current = "history";
|
||||
void openRectificationSession(session.id);
|
||||
};
|
||||
rectificationSessionOpenerRef.current = openRectificationSession;
|
||||
|
||||
function handleRectificationProfileIncomplete() {
|
||||
setRectificationError("profile_incomplete");
|
||||
setRectificationSessionId(null);
|
||||
@@ -320,7 +364,41 @@ export function useRectificationSurface(params: RectificationSurfaceParams) {
|
||||
}));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
resumeRectificationSession.current = (session) => {
|
||||
sessionSelectionSource.current = "history";
|
||||
void openRectificationSession(session.id);
|
||||
};
|
||||
rectificationSessionOpenerRef.current = openRectificationSession;
|
||||
});
|
||||
|
||||
const rectificationPanel: RectificationChatPanel = {
|
||||
caseId: rectificationCaseId ?? "",
|
||||
sessionId: rectificationSessionId ?? "",
|
||||
readonly: rectificationReadonly,
|
||||
shouldStartOpening: rectificationShouldStartOpening,
|
||||
initialTurns: rectificationTurns,
|
||||
initialSnapshot: rectificationSnapshot,
|
||||
pendingConsultationQuestion: rectificationPendingQuestion,
|
||||
headerSlot: rectificationHeaderSlot,
|
||||
onMessagesChange: handleRectificationMessagesChange,
|
||||
onCompleted: () => {
|
||||
void refreshAccount();
|
||||
if (rectificationCaseId && rectificationSessionId) {
|
||||
void refreshRectificationCase(rectificationCaseId, rectificationSessionId);
|
||||
}
|
||||
},
|
||||
onPendingChange: setRectificationMutationPending,
|
||||
onProfileIncomplete: handleRectificationProfileIncomplete,
|
||||
onOpeningConsumed: () => setRectificationShouldStartOpening(false),
|
||||
onRestart: () => void startNewRectification(),
|
||||
};
|
||||
|
||||
return {
|
||||
rectificationHeaderSlot,
|
||||
setRectificationHeaderSlot,
|
||||
rectificationPanel,
|
||||
resumeRectificationSession,
|
||||
refreshRectificationEntrySummary,
|
||||
refreshRectificationCase,
|
||||
openRectificationCase,
|
||||
|
||||
Reference in New Issue
Block a user