chore(chat): refactor chat init into separate quota and history actors

Split `chatInitActor` into `loadQuotaActor` (guest quota fetch) and `loadHistoryActor` (local → network → save sync), and add `quotaLoaded` / `historyLoaded` state flags so the UI can display loading status during the new local-first history hydration flow. Also drop the now-unused `ChatInit` event and pipe Next.js stdout/stderr to a persistent log file in the post-receive hook for easier troubleshooting.
This commit is contained in:
2026-06-15 16:21:40 +08:00
parent cfc6de5a8c
commit 1f3980d461
8 changed files with 296 additions and 152 deletions
+56 -9
View File
@@ -2,9 +2,10 @@
* Chat 状态机:4 个 XState actor
*
* 从 `chat-machine.ts` 抽出的"Actors"段:
* - `chatInitActor`fromPromise**仅**游客有意义)
* - `loadQuotaActor`fromPromise**仅**游客有意义 —— 拉**日**配 + 总**配**
* - `loadHistoryActor`fromPromise —— local → network → save network to local 3 **步**
* - `sendMessageHttpActor`fromPromise**已** wire —— "一次发送 = 一次返回"
* - `loadMoreHistoryActor`fromPromise
* - `loadMoreHistoryActor`fromPromise —— 翻**页****不**走 local-first
* - `chatWebSocketActor`fromCallback,长生命周期 —— **仅** userSession 期间 invoke
*
* 设计:
@@ -23,17 +24,62 @@ import {
PAGE_SIZE,
chatRepo,
localMessagesToUi,
readInitData,
readAndSyncHistory,
readGuestQuota,
sendResponseToUiMessage,
type InitResult,
} from "./chat-machine.helpers";
import type { ChatEvent } from "./chat-events";
// ============================================================// HTTP: one-shot Promise
// ============================================================
/** 拉初始数据(****游客有意义) */
export const chatInitActor = fromPromise<InitResult>(async () => readInitData());
// Init 任务 1:拉游客配额(fromPromise**** local
// ============================================================
/**
* 拉游客**日**配 + 总**配****仅** guestSession 期间 invoke
* - 失**败**返 `{ remaining: 0, total: 0 }`**不**抛,**让** UI **还**是**能**进** ready
* - onDone 在 state machine **里**赋 guestRemainingQuota + guestTotalQuota + 设 quotaLoaded: true
*/
export const loadQuotaActor = fromPromise<{
remaining: number;
total: number;
}>(async () => {
console.log("[chat-machine] loadQuotaActor ENTRY");
const result = await readGuestQuota();
console.log("[chat-machine] loadQuotaActor DONE", result);
return result;
});
// ============================================================
// Init 任务 2:拉 history 3 步流(fromPromise**返**回**最**终**结**果**
// ============================================================
/**
* local → network → save network to local 3 步流(**返**回** network **最**终** messages**
* - 选**方**案 AfromPromise):UI **不**会**看**到** local 中**间**态****直**接**显**示** network **结**果**
* - 内部 3 步**走** helper `readAndSyncHistory`**日**志**全**在** helper **里**
* - 失**败**返 local**退**而**求**其**次**
*/
export const loadHistoryActor = fromPromise<{
messages: UiMessage[];
hasMore: boolean;
newOffset: number;
localOverwritten: boolean;
localCount: number;
networkCount: number;
}>(async () => {
console.log("[chat-machine] loadHistoryActor ENTRY");
const result = await readAndSyncHistory();
console.log("[chat-machine] loadHistoryActor DONE", {
finalCount: result.messages.length,
localCount: result.localCount,
networkCount: result.networkCount,
hasMore: result.hasMore,
localOverwritten: result.localOverwritten,
});
return result;
});
// ============================================================
// HTTP: one-shot Promise
// ============================================================
/**
* HTTP 发送消息(**一次发送 = 一次返回**)
* - 后端响应**就**是 AI 回复(`ChatSendResponse.reply: string`
@@ -76,7 +122,7 @@ export const sendMessageHttpActor = fromPromise<
return { reply };
});
/** 翻历史(pagination */
/** 翻历史(pagination—— **不**走 local-first**纯** server fetch */
export const loadMoreHistoryActor = fromPromise<
{ messages: UiMessage[]; hasMore: boolean; newOffset: number },
{ offset: number }
@@ -123,7 +169,8 @@ export const loadMoreHistoryActor = fromPromise<
};
});
// ============================================================// WebSocket: long-lived callback
// ============================================================
// WebSocket: long-lived callback
// ============================================================
/**
* 长生命周期 WebSocket**仅** `userSession` 期间 invoke