"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"; import { appendPromotionMessage } from "./chat-promotion"; /** * 对外暴露的 State 形状 * * isGuest 字段已删 —— 由 chat-screen 派生自 `auth.loginStatus === "guest"`。 * 消费方(chat-screen)通过 `useAuthState()` 取 loginStatus,本地派生 isGuest。 */ interface ChatState { messages: MachineContext["messages"]; historyMessages: MachineContext["messages"]; promotion: MachineContext["promotion"]; isReplyingAI: boolean; upgradePromptVisible: boolean; upgradeReason: MachineContext["upgradeReason"]; /** history 初始加载完成标志(local → network → save 跑完) —— UI 可用此显示 loading */ historyLoaded: boolean; unlockHistoryPromptVisible: boolean; lockedHistoryCount: number; isUnlockingHistory: boolean; unlockHistoryError: string | null; isUnlockingMessage: boolean; unlockingMessageId: string | null; unlockMessageError: string | null; unlockPaywallRequest: MachineContext["unlockPaywallRequest"]; } 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: appendPromotionMessage( state.context.messages, state.context.promotion, ), historyMessages: state.context.messages, promotion: state.context.promotion, isReplyingAI: state.context.isReplyingAI, upgradePromptVisible: state.context.upgradePromptVisible, upgradeReason: state.context.upgradeReason, historyLoaded: state.context.historyLoaded, unlockHistoryPromptVisible: state.context.unlockHistoryPromptVisible, lockedHistoryCount: state.context.lockedHistoryCount, isUnlockingHistory: state.context.isUnlockingHistory, unlockHistoryError: state.context.unlockHistoryError, isUnlockingMessage: state.context.isUnlockingMessage, unlockingMessageId: state.context.unlockingMessageId, unlockMessageError: state.context.unlockMessageError, unlockPaywallRequest: state.context.unlockPaywallRequest, }), [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; }