refactor(chat): decouple chat state from auth and gate quota alerts to guests

- Remove ChatAuthStatusChanged dispatch from auth login flow; chat screen now subscribes directly to auth state machine (loginStatus) to derive isGuest and drive WS lifecycle
- Gate quota-exceeded and warning dialogs to guests only (non-guests have no message limit)
- Dispatch ChatLoadMoreHistory for non-guest initial load vs ChatInit for guests
- Fix AppConstants import path from @/core/constants/app_constants to @/core/app_constants
- Add defensive isGuest checks in quota effects to prevent non-guest quota triggers
This commit is contained in:
2026-06-11 18:31:08 +08:00
parent e3d069e49f
commit ed4ccddae3
11 changed files with 233 additions and 63 deletions
+32 -19
View File
@@ -13,6 +13,16 @@
* - HTTP 操作用 `fromPromise` actor(一次性 Promise
* - 保持事件类型名与原 Dart 一致,便于业务层迁移对照
* - actions 全部 inline 在 setup() 中(确保类型推断正确)
*
* **鉴权解耦**
* chat 机器**不**感知鉴权状态 —— `isGuest` 字段已删(由 chat-screen 派生自 auth.loginStatus)。
* WebSocket 管理也由 chat-screen 监听 auth 状态机后驱动。
*
* **配额(**仅**游客)**
* - `guestRemainingQuota` / `guestTotalQuota` **仅**游客有意义
* - 非游客"无消息限制"(业务事实)—— 永不被赋值
* - chat-screen 派 `ChatInit` 时(**只**在 guest)→ onDone 条件赋
* - `appendUserMessage` 减 `Math.max(0, q - 1)` —— 非游客 0-1=0 天然 no-op
*/
import { setup, fromPromise, assign } from "xstate";
@@ -85,6 +95,8 @@ interface InitResult {
async function readInitData(): Promise<InitResult> {
const chatStorage = ChatStorage.getInstance();
// isGuest 在 chat 业务层仅用于"是否加载本地历史" + "onDone 条件赋配额" —— **不**是鉴权信号
// 鉴权判断已上提到 chat-screen 派生
const isGuest = !AuthStorage.getInstance().hasLoginToken();
const [dailyResult, totalResult, localResult] = await Promise.all([
@@ -95,13 +107,14 @@ async function readInitData(): Promise<InitResult> {
return {
isGuest,
// fallback 0 —— 非游客路径用不到;onDone 会**条件**赋
guestRemainingQuota: mapQuotaResult(
dailyResult as Result<{ remaining: number } | null>,
40,
0,
),
guestTotalQuota: mapTotalQuotaResult(
totalResult as Result<number | null>,
50,
0,
),
localMessages:
localResult && Result.isOk(localResult) && localResult.data
@@ -145,8 +158,7 @@ const loadMoreHistoryActor = fromPromise<
};
});
// ============================================================
// Machine
// ============================================================// Machine
// ============================================================
export const chatMachine = setup({
types: {
@@ -171,6 +183,8 @@ export const chatMachine = setup({
},
],
isReplyingAI: true,
// 游客减配额;非游客 0 - 1 = max(0, -1) = 0 —— **天然 no-op**(业务事实"非游客无限制")
guestRemainingQuota: Math.max(0, context.guestRemainingQuota - 1),
};
}),
@@ -187,6 +201,8 @@ export const chatMachine = setup({
},
],
isReplyingAI: true,
// 游客减配额;非游客 no-op
guestRemainingQuota: Math.max(0, context.guestRemainingQuota - 1),
};
}),
@@ -223,10 +239,6 @@ export const chatMachine = setup({
return { messages, isReplyingAI: false };
}),
refreshAuthStatus: assign(() => ({
isGuest: !AuthStorage.getInstance().hasLoginToken(),
})),
incrementQuotaExceeded: assign(({ context }) => ({
quotaExceededTrigger: context.quotaExceededTrigger + 1,
})),
@@ -248,9 +260,6 @@ export const chatMachine = setup({
ChatSendImage: {
actions: "appendUserImage",
},
ChatAuthStatusChanged: {
actions: "refreshAuthStatus",
},
ChatAISentenceReceived: {
actions: "appendOrUpdateAISentence",
},
@@ -269,11 +278,18 @@ export const chatMachine = setup({
src: "chatInit",
onDone: {
target: "ready",
actions: assign({
messages: ({ event }) => event.output.localMessages,
isGuest: ({ event }) => event.output.isGuest,
guestRemainingQuota: ({ event }) => event.output.guestRemainingQuota,
guestTotalQuota: ({ event }) => event.output.guestTotalQuota,
// **条件赋**:仅 isGuest 时初始化配额(业务事实"非游客无消息限制")
// - 游客:messages + quota 都更新
// - 非游客:仅 messageslocal 空),quota 保持 default 0(不更新其值)
actions: assign(({ event }) => {
const updates: Partial<ChatState> = {
messages: event.output.localMessages,
};
if (event.output.isGuest) {
updates.guestRemainingQuota = event.output.guestRemainingQuota;
updates.guestTotalQuota = event.output.guestTotalQuota;
}
return updates;
}),
},
onError: {
@@ -293,9 +309,6 @@ export const chatMachine = setup({
},
ChatLoadMoreHistory: "loadingMoreHistory",
ChatScreenInvisible: "idle",
ChatAuthStatusChanged: {
actions: "refreshAuthStatus",
},
ChatAISentenceReceived: {
actions: "appendOrUpdateAISentence",
},