"use client"; import { type Dispatch, type ReactNode, createContext, useContext, useMemo, } from "react"; import { useMachine } from "@xstate/react"; import { chatMachine } from "./chat-machine"; import type { ChatEvent, ChatState as MachineContext } from "./chat-machine"; /** * 对外暴露的 State 形状 * * isGuest 字段已删 —— 由 chat-screen 派生自 `auth.loginStatus === "guest"`。 * 消费方(chat-screen)通过 `useAuthState()` 取 loginStatus,本地派生 isGuest。 */ interface ChatState { messages: MachineContext["messages"]; isReplyingAI: boolean; /** 游客剩余配额(次/天)—— 业务展示用,鉴权判断由 chat-screen 派生 loginStatus */ guestRemainingQuota: number; /** 游客总配额 */ guestTotalQuota: number; quotaExceededTrigger: number; paywallTriggered: boolean; paywallReason: MachineContext["paywallReason"]; paywallDetail: MachineContext["paywallDetail"]; isLoadingMore: boolean; hasMore: boolean; historyOffset: number; /** 游客配额加载完成标志(仅 guestSession 期间) —— UI 可用此显示 loading */ quotaLoaded: boolean; /** history 初始加载完成标志(local → network → save 跑完) —— UI 可用此显示 loading */ historyLoaded: boolean; } const ChatStateCtx = createContext(null); const ChatDispatchCtx = createContext | null>(null); export interface ChatProviderProps { children: ReactNode; } export function ChatProvider({ children }: ChatProviderProps) { const [state, send] = useMachine(chatMachine, {}); // 映射 XState 状态机快照 → 原 ChatState 形状 const chatState = useMemo( () => ({ messages: state.context.messages, isReplyingAI: state.context.isReplyingAI, guestRemainingQuota: state.context.guestRemainingQuota, guestTotalQuota: state.context.guestTotalQuota, quotaExceededTrigger: state.context.quotaExceededTrigger, paywallTriggered: state.context.paywallTriggered, paywallReason: state.context.paywallReason, paywallDetail: state.context.paywallDetail, isLoadingMore: state.context.isLoadingMore, hasMore: state.context.hasMore, historyOffset: state.context.historyOffset, quotaLoaded: state.context.quotaLoaded, historyLoaded: state.context.historyLoaded, }), [state], ); return ( {children} ); } export function useChatState(): ChatState { const ctx = useContext(ChatStateCtx); if (!ctx) throw new Error("useChatState must be used inside "); return ctx; } export function useChatDispatch(): Dispatch { const ctx = useContext(ChatDispatchCtx); if (!ctx) throw new Error("useChatDispatch must be used inside "); return ctx; }