refactor: auto-init guest chat quota on first read, clean up logs

- Remove unused `quotaWarningThreshold` and `warningThreshold` getter from `GuestChatQuota` DTO
- Apply get-or-init pattern in `ChatStorage.getGuestDailyChatQuota` and `getGuestTotalQuota`: when no existing value, initialize with DTO defaults (`threshold` = max per day, `totalQuotaDefault`) and return the seeded data
- Fall back to original `null` result if initialization write fails
- Consolidate logging strategy: drop per-actor trace logs in `chat-machine.ts`, keep business-decision logs in 3 assign actions, move all actor ENTRY/API/DONE chains to `chat-machine.actors.ts` and helper load logs to `chat-machine.helpers.ts`
This commit is contained in:
2026-06-15 17:39:50 +08:00
parent fa694af723
commit 31f2433c4b
4 changed files with 88 additions and 170 deletions
+48 -154
View File
@@ -47,9 +47,11 @@
* - `chat-machine.helpers.ts` —— 纯函数 + 数据加载(readGuestQuota + readAndSyncHistory
* - `chat-machine.actors.ts` —— 5 个 XState actorloadQuota + loadHistory + sendMessage + loadMoreHistory + chatWebSocket
*
* **调试日志**(保留 —— 用户要求"不要将其删除"):
* - 命名约定:`[<file>]` 前缀标识来源
* - 4 actor **全链日志**ENTRY / API / DONE / 关键决策)—— 排查 actor ****被调时**一眼**看到
* **日**志**策略****清**理**后**):
* - **不**在**状**态**机**文**件**中**打**追踪**日**志**actor / helper / action **本**身**有** ENTRY / DONE **日**志****就**够**排查**了)
* - `appendUserMessage` / `appendUserImage` / `appendOrUpdateAISentence` 这 3 **个** action `assign` **内**部**保**留** `console.log`**记**录** action **本**身**的**业**务**决**策****比**如** quota 减**** / isFromAI 标**记**
* - **所**有** actor ENTRY / API / DONE **全**链**日**志**放**在** `chat-machine.actors.ts`
* - **所**有** helper 数据**加**载**日**志**`readGuestQuota` / `readAndSyncHistory` 3 **步**)放**在** `chat-machine.helpers.ts`
*/
import { setup, assign } from "xstate";
@@ -95,9 +97,11 @@ export const chatMachine = setup({
actions: {
appendUserMessage: assign(({ context, event }) => {
if (event.type !== "ChatSendMessage") return {};
const newQuota = context.wsConnected
? context.guestRemainingQuota
: Math.max(0, context.guestRemainingQuota - 1);
console.log("[chat-machine] appendUserMessage", {
contentLength: event.content.length,
contentPreview: event.content.slice(0, 50),
@@ -109,6 +113,7 @@ export const chatMachine = setup({
newQuota,
isReplyingAI: true,
});
return {
messages: [
...context.messages,
@@ -195,26 +200,6 @@ export const chatMachine = setup({
setWsConnected: assign({ wsConnected: true }),
clearWsConnected: assign({ wsConnected: false }),
logSendMessageReceived: ({ event, context }) =>
console.log("[chat-machine] SendMessage received", {
contentLength: event.type === "ChatSendMessage" ? event.content.length : 0,
isReplyingAI: context.isReplyingAI,
wsConnected: context.wsConnected,
currentMessagesCount: context.messages.length,
}),
logSendImageReceived: ({ event, context }) =>
console.log("[chat-machine] SendImage received", {
hasImageBase64: event.type === "ChatSendImage" ? !!event.imageBase64 : false,
wsConnected: context.wsConnected,
}),
logLoadMoreHistoryReceived: ({ context }) =>
console.log("[chat-machine] LoadMoreHistory received", {
currentOffset: context.historyOffset,
hasMore: context.hasMore,
}),
},
}).createMachine({
id: "chat",
@@ -248,17 +233,11 @@ export const chatMachine = setup({
id: "loadQuota",
src: "loadQuota",
onDone: {
actions: assign(({ event }) => {
console.log("[chat-machine] guestSession.initializing.loadQuota.onDone", {
remaining: event.output.remaining,
total: event.output.total,
});
return {
guestRemainingQuota: event.output.remaining,
guestTotalQuota: event.output.total,
quotaLoaded: true,
};
}),
actions: assign(({ event }) => ({
guestRemainingQuota: event.output.remaining,
guestTotalQuota: event.output.total,
quotaLoaded: true,
})),
},
onError: {
// 失败**也**标 loaded**不**卡 init**游**客**可**能 quota 服务**不**可**用****让** UI **进** ready
@@ -272,23 +251,12 @@ export const chatMachine = setup({
id: "loadHistory",
src: "loadHistory",
onDone: {
actions: assign(({ event }) => {
console.log(
"[chat-machine] guestSession.initializing.loadHistory.onDone",
{
finalCount: event.output.messages.length,
hasMore: event.output.hasMore,
newOffset: event.output.newOffset,
localOverwritten: event.output.localOverwritten,
},
);
return {
messages: event.output.messages,
hasMore: event.output.hasMore,
historyOffset: event.output.newOffset,
historyLoaded: true,
};
}),
actions: assign(({ event }) => ({
messages: event.output.messages,
hasMore: event.output.hasMore,
historyOffset: event.output.newOffset,
historyLoaded: true,
})),
},
onError: {
// 失败**也**标 loaded**不**卡 init**让** UI **还**是**能**进** ready**屏**幕**可**能**空**
@@ -316,9 +284,9 @@ export const chatMachine = setup({
guard: ({ context }) => context.guestRemainingQuota <= 0,
actions: "incrementQuotaExceeded",
},
// 3. 两**个**配**额**都** OK + **内**容**非**空** → **发**送**
// 3. 两**个**配**额**都** OK + **内**容**非**空** → **发**送****只**用** appendUserMessage **自**己**的**日**志**
{
actions: ["logSendMessageReceived", "appendUserMessage"],
actions: "appendUserMessage",
guard: ({ event }) => event.content.trim().length > 0,
target: "sending",
},
@@ -335,7 +303,7 @@ export const chatMachine = setup({
actions: "incrementQuotaExceeded",
},
{
actions: ["logSendImageReceived", "appendUserImage"],
actions: "appendUserImage",
target: "sending",
},
],
@@ -349,11 +317,6 @@ export const chatMachine = setup({
},
},
sending: {
entry: ({ event }) =>
console.log("[chat-machine] → guestSession.sending", {
contentLength: event.type === "ChatSendMessage" ? event.content.length : 0,
contentPreview: event.type === "ChatSendMessage" ? event.content.slice(0, 50) : "",
}),
invoke: {
src: "sendMessageHttp",
input: ({ event }) => ({
@@ -361,28 +324,14 @@ export const chatMachine = setup({
}),
onDone: {
target: "ready",
actions: [
assign(({ context, event }) => ({
messages: [...context.messages, event.output.reply],
isReplyingAI: false,
})),
({ context, event }) =>
console.log("[chat-machine] guestSession.sending.onDone", {
newReplyContentLength: event.output.reply.content.length,
totalMessagesCount: context.messages.length + 1,
isReplyingAI: false,
}),
],
actions: assign(({ context, event }) => ({
messages: [...context.messages, event.output.reply],
isReplyingAI: false,
})),
},
onError: {
target: "ready",
actions: [
assign({ isReplyingAI: false }),
({ event }) =>
console.error("[chat-machine] guestSession.sending.onError", {
error: event.error instanceof Error ? event.error.message : String(event.error),
}),
],
actions: assign({ isReplyingAI: false }),
},
},
},
@@ -416,24 +365,13 @@ export const chatMachine = setup({
// 任务 2:拉 history**只**调**用** loadHistoryActor**不**再**用** loadMoreHistoryActor
src: "loadHistory",
onDone: {
actions: assign(({ event }) => {
console.log(
"[chat-machine] userSession.initializing.loadHistory.onDone",
{
finalCount: event.output.messages.length,
hasMore: event.output.hasMore,
newOffset: event.output.newOffset,
localOverwritten: event.output.localOverwritten,
},
);
return {
messages: event.output.messages,
isLoadingMore: false,
hasMore: event.output.hasMore,
historyOffset: event.output.newOffset,
historyLoaded: true,
};
}),
actions: assign(({ event }) => ({
messages: event.output.messages,
isLoadingMore: false,
hasMore: event.output.hasMore,
historyOffset: event.output.newOffset,
historyLoaded: true,
})),
},
onError: {
// 失败**也**标 loaded**不**卡 init
@@ -447,22 +385,14 @@ export const chatMachine = setup({
ready: {
on: {
ChatSendMessage: {
actions: ["logSendMessageReceived", "appendUserMessage"],
actions: "appendUserMessage",
guard: ({ event }) => event.content.trim().length > 0,
target: "sending",
},
ChatSendImage: {
actions: ["logSendImageReceived", "appendUserImage"],
actions: "appendUserImage",
},
ChatLoadMoreHistory: {
actions: [
"logLoadMoreHistoryReceived",
({ context }) =>
console.log("[chat-machine] userSession.ready.ChatLoadMoreHistory received", {
currentOffset: context.historyOffset,
hasMore: context.hasMore,
}),
],
target: "loadingMore",
},
ChatLogout: "#chat.idle",
@@ -474,12 +404,6 @@ export const chatMachine = setup({
},
},
sending: {
entry: ({ event }) =>
console.log("[chat-machine] → userSession.sending", {
contentLength: event.type === "ChatSendMessage" ? event.content.length : 0,
contentPreview: event.type === "ChatSendMessage" ? event.content.slice(0, 50) : "",
wsConnected: true,
}),
invoke: {
src: "sendMessageHttp",
input: ({ event }) => ({
@@ -487,63 +411,33 @@ export const chatMachine = setup({
}),
onDone: {
target: "ready",
actions: [
assign(({ context, event }) => ({
messages: [...context.messages, event.output.reply],
isReplyingAI: false,
})),
({ context, event }) =>
console.log("[chat-machine] userSession.sending.onDone", {
newReplyContentLength: event.output.reply.content.length,
totalMessagesCount: context.messages.length + 1,
isReplyingAI: false,
}),
],
actions: assign(({ context, event }) => ({
messages: [...context.messages, event.output.reply],
isReplyingAI: false,
})),
},
onError: {
target: "ready",
actions: [
assign({ isReplyingAI: false }),
({ event }) =>
console.error("[chat-machine] userSession.sending.onError", {
error: event.error instanceof Error ? event.error.message : String(event.error),
}),
],
actions: assign({ isReplyingAI: false }),
},
},
},
loadingMore: {
entry: ({ context }) =>
console.log("[chat-machine] → userSession.loadingMore", { offset: context.historyOffset }),
invoke: {
src: "loadMoreHistory",
input: ({ context }) => ({ offset: context.historyOffset }),
onDone: {
target: "ready",
actions: [
assign(({ context, event }) => ({
messages: [...event.output.messages, ...context.messages],
isLoadingMore: false,
hasMore: event.output.hasMore,
historyOffset: event.output.newOffset,
})),
({ event }) =>
console.log("[chat-machine] userSession.loadingMore.onDone", {
newMessagesCount: event.output.messages.length,
newOffset: event.output.newOffset,
hasMore: event.output.hasMore,
}),
],
actions: assign(({ context, event }) => ({
messages: [...event.output.messages, ...context.messages],
isLoadingMore: false,
hasMore: event.output.hasMore,
historyOffset: event.output.newOffset,
})),
},
onError: {
target: "ready",
actions: [
assign({ isLoadingMore: false }),
({ event }) =>
console.error("[chat-machine] userSession.loadingMore.onError", {
error: event.error instanceof Error ? event.error.message : String(event.error),
}),
],
actions: assign({ isLoadingMore: false }),
},
},
},
-2
View File
@@ -29,8 +29,6 @@ export interface ChatState {
*/
wsConnected: boolean;
/** 游客剩余配额(次/天)—— **仅**游客业务展示用
* - default 0 = "未初始化" / "已耗尽"(同码,靠 `isGuest` 区分)
* - 非游客:永不被赋值(业务事实"无消息限制")
*/
guestRemainingQuota: number;
/** 游客总配额(仅游客展示用) */