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:
@@ -11,9 +11,7 @@ export class GuestChatQuota {
|
|||||||
// 静态常量
|
// 静态常量
|
||||||
static readonly maxQuotaPerDay = 40;
|
static readonly maxQuotaPerDay = 40;
|
||||||
static readonly maxQuotaPerDayTest = 4;
|
static readonly maxQuotaPerDayTest = 4;
|
||||||
static readonly quotaWarningThreshold = 20;
|
|
||||||
|
|
||||||
static readonly quotaWarningThresholdTest = 2;
|
|
||||||
static readonly defaultTotalQuota = 50;
|
static readonly defaultTotalQuota = 50;
|
||||||
static readonly defaultTotalQuotaDev = 5;
|
static readonly defaultTotalQuotaDev = 5;
|
||||||
static readonly quotaExhausted = 0;
|
static readonly quotaExhausted = 0;
|
||||||
@@ -49,15 +47,6 @@ export class GuestChatQuota {
|
|||||||
return process.env.NODE_ENV !== "production";
|
return process.env.NODE_ENV !== "production";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取当前环境的配额警告阈值
|
|
||||||
*/
|
|
||||||
static get warningThreshold(): number {
|
|
||||||
return GuestChatQuota.isDevelopment
|
|
||||||
? GuestChatQuota.quotaWarningThresholdTest
|
|
||||||
: GuestChatQuota.quotaWarningThreshold;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前环境的每日最大配额
|
* 获取当前环境的每日最大配额
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -18,9 +18,11 @@ import {
|
|||||||
GuestChatQuotaSchema,
|
GuestChatQuotaSchema,
|
||||||
type GuestChatQuotaData,
|
type GuestChatQuotaData,
|
||||||
} from "@/data/schemas/chat/guest_chat_quota";
|
} from "@/data/schemas/chat/guest_chat_quota";
|
||||||
|
import { GuestChatQuota as GuestChatQuotaDto } from "@/data/dto/chat/guest_chat_quota";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { type Result as ResultT } from "@/utils/result";
|
import { type Result as ResultT, Result } from "@/utils/result";
|
||||||
|
import { formatDate } from "@/utils/date";
|
||||||
import { SpAsyncUtil } from "@/utils/storage";
|
import { SpAsyncUtil } from "@/utils/storage";
|
||||||
import { StorageKeys } from "../storage_keys";
|
import { StorageKeys } from "../storage_keys";
|
||||||
import type { IChatStorage } from "./ichat_storage";
|
import type { IChatStorage } from "./ichat_storage";
|
||||||
@@ -41,7 +43,27 @@ export class ChatStorage implements IChatStorage {
|
|||||||
// ---- guest daily chat quota ----
|
// ---- guest daily chat quota ----
|
||||||
|
|
||||||
getGuestDailyChatQuota(): Promise<ResultT<GuestChatQuotaData | null>> {
|
getGuestDailyChatQuota(): Promise<ResultT<GuestChatQuotaData | null>> {
|
||||||
return SpAsyncUtil.getJson(StorageKeys.guestChatQuota, GuestChatQuotaSchema);
|
// get-or-init:**有**返**有**,**无**则**用** DTO 默认**值**(`threshold` = max per day)**初**始**化**并**返**回**
|
||||||
|
return SpAsyncUtil.getJson(
|
||||||
|
StorageKeys.guestChatQuota,
|
||||||
|
GuestChatQuotaSchema,
|
||||||
|
).then(async (existing) => {
|
||||||
|
if (existing.success && existing.data != null) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
const today = formatDate(new Date());
|
||||||
|
const initResult = await this.setGuestDailyChatQuota(
|
||||||
|
GuestChatQuotaDto.threshold,
|
||||||
|
today,
|
||||||
|
);
|
||||||
|
if (Result.isErr(initResult)) {
|
||||||
|
return existing; // **初**始**化**失**败** → **返**原**始** null
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: { remaining: GuestChatQuotaDto.threshold, date: today },
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setGuestDailyChatQuota(remaining: number, date: string): Promise<ResultT<void>> {
|
setGuestDailyChatQuota(remaining: number, date: string): Promise<ResultT<void>> {
|
||||||
@@ -56,7 +78,22 @@ export class ChatStorage implements IChatStorage {
|
|||||||
// ---- guest total quota ----
|
// ---- guest total quota ----
|
||||||
|
|
||||||
getGuestTotalQuota(): Promise<ResultT<number | null>> {
|
getGuestTotalQuota(): Promise<ResultT<number | null>> {
|
||||||
return SpAsyncUtil.getJson(StorageKeys.guestTotalQuotaRemaining, z.number());
|
// get-or-init:**有**返**有**,**无**则**用** DTO 默认**值**(`totalQuotaDefault`)**初**始**化**并**返**回**
|
||||||
|
return SpAsyncUtil.getJson(
|
||||||
|
StorageKeys.guestTotalQuotaRemaining,
|
||||||
|
z.number(),
|
||||||
|
).then(async (existing) => {
|
||||||
|
if (existing.success && existing.data != null) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
const initResult = await this.setGuestTotalQuota(
|
||||||
|
GuestChatQuotaDto.totalQuotaDefault,
|
||||||
|
);
|
||||||
|
if (Result.isErr(initResult)) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
return { success: true, data: GuestChatQuotaDto.totalQuotaDefault };
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setGuestTotalQuota(remaining: number): Promise<ResultT<void>> {
|
setGuestTotalQuota(remaining: number): Promise<ResultT<void>> {
|
||||||
|
|||||||
+48
-154
@@ -47,9 +47,11 @@
|
|||||||
* - `chat-machine.helpers.ts` —— 纯函数 + 数据加载(readGuestQuota + readAndSyncHistory)
|
* - `chat-machine.helpers.ts` —— 纯函数 + 数据加载(readGuestQuota + readAndSyncHistory)
|
||||||
* - `chat-machine.actors.ts` —— 5 个 XState actor(loadQuota + loadHistory + sendMessage + loadMoreHistory + chatWebSocket)
|
* - `chat-machine.actors.ts` —— 5 个 XState actor(loadQuota + loadHistory + sendMessage + loadMoreHistory + chatWebSocket)
|
||||||
*
|
*
|
||||||
* **调试日志**(保留 —— 用户要求"不要将其删除"):
|
* **日**志**策略**(**清**理**后**):
|
||||||
* - 命名约定:`[<file>]` 前缀标识来源
|
* - **不**在**状**态**机**文**件**中**打**追踪**日**志**(actor / helper / action **本**身**有** ENTRY / DONE **日**志**,**就**够**排查**了)
|
||||||
* - 4 actor **全链日志**(ENTRY / API / DONE / 关键决策)—— 排查 actor **没**被调时**一眼**看到
|
* - `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";
|
import { setup, assign } from "xstate";
|
||||||
@@ -95,9 +97,11 @@ export const chatMachine = setup({
|
|||||||
actions: {
|
actions: {
|
||||||
appendUserMessage: assign(({ context, event }) => {
|
appendUserMessage: assign(({ context, event }) => {
|
||||||
if (event.type !== "ChatSendMessage") return {};
|
if (event.type !== "ChatSendMessage") return {};
|
||||||
|
|
||||||
const newQuota = context.wsConnected
|
const newQuota = context.wsConnected
|
||||||
? context.guestRemainingQuota
|
? context.guestRemainingQuota
|
||||||
: Math.max(0, context.guestRemainingQuota - 1);
|
: Math.max(0, context.guestRemainingQuota - 1);
|
||||||
|
|
||||||
console.log("[chat-machine] appendUserMessage", {
|
console.log("[chat-machine] appendUserMessage", {
|
||||||
contentLength: event.content.length,
|
contentLength: event.content.length,
|
||||||
contentPreview: event.content.slice(0, 50),
|
contentPreview: event.content.slice(0, 50),
|
||||||
@@ -109,6 +113,7 @@ export const chatMachine = setup({
|
|||||||
newQuota,
|
newQuota,
|
||||||
isReplyingAI: true,
|
isReplyingAI: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
messages: [
|
messages: [
|
||||||
...context.messages,
|
...context.messages,
|
||||||
@@ -195,26 +200,6 @@ export const chatMachine = setup({
|
|||||||
|
|
||||||
setWsConnected: assign({ wsConnected: true }),
|
setWsConnected: assign({ wsConnected: true }),
|
||||||
clearWsConnected: assign({ wsConnected: false }),
|
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({
|
}).createMachine({
|
||||||
id: "chat",
|
id: "chat",
|
||||||
@@ -248,17 +233,11 @@ export const chatMachine = setup({
|
|||||||
id: "loadQuota",
|
id: "loadQuota",
|
||||||
src: "loadQuota",
|
src: "loadQuota",
|
||||||
onDone: {
|
onDone: {
|
||||||
actions: assign(({ event }) => {
|
actions: assign(({ event }) => ({
|
||||||
console.log("[chat-machine] guestSession.initializing.loadQuota.onDone", {
|
guestRemainingQuota: event.output.remaining,
|
||||||
remaining: event.output.remaining,
|
guestTotalQuota: event.output.total,
|
||||||
total: event.output.total,
|
quotaLoaded: true,
|
||||||
});
|
})),
|
||||||
return {
|
|
||||||
guestRemainingQuota: event.output.remaining,
|
|
||||||
guestTotalQuota: event.output.total,
|
|
||||||
quotaLoaded: true,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
onError: {
|
onError: {
|
||||||
// 失败**也**标 loaded,**不**卡 init(**游**客**可**能 quota 服务**不**可**用**,**让** UI **进** ready)
|
// 失败**也**标 loaded,**不**卡 init(**游**客**可**能 quota 服务**不**可**用**,**让** UI **进** ready)
|
||||||
@@ -272,23 +251,12 @@ export const chatMachine = setup({
|
|||||||
id: "loadHistory",
|
id: "loadHistory",
|
||||||
src: "loadHistory",
|
src: "loadHistory",
|
||||||
onDone: {
|
onDone: {
|
||||||
actions: assign(({ event }) => {
|
actions: assign(({ event }) => ({
|
||||||
console.log(
|
messages: event.output.messages,
|
||||||
"[chat-machine] guestSession.initializing.loadHistory.onDone",
|
hasMore: event.output.hasMore,
|
||||||
{
|
historyOffset: event.output.newOffset,
|
||||||
finalCount: event.output.messages.length,
|
historyLoaded: true,
|
||||||
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,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
onError: {
|
onError: {
|
||||||
// 失败**也**标 loaded,**不**卡 init(**让** UI **还**是**能**进** ready,**屏**幕**可**能**空**)
|
// 失败**也**标 loaded,**不**卡 init(**让** UI **还**是**能**进** ready,**屏**幕**可**能**空**)
|
||||||
@@ -316,9 +284,9 @@ export const chatMachine = setup({
|
|||||||
guard: ({ context }) => context.guestRemainingQuota <= 0,
|
guard: ({ context }) => context.guestRemainingQuota <= 0,
|
||||||
actions: "incrementQuotaExceeded",
|
actions: "incrementQuotaExceeded",
|
||||||
},
|
},
|
||||||
// 3. 两**个**配**额**都** OK + **内**容**非**空** → **发**送**
|
// 3. 两**个**配**额**都** OK + **内**容**非**空** → **发**送**(**只**用** appendUserMessage **自**己**的**日**志**)
|
||||||
{
|
{
|
||||||
actions: ["logSendMessageReceived", "appendUserMessage"],
|
actions: "appendUserMessage",
|
||||||
guard: ({ event }) => event.content.trim().length > 0,
|
guard: ({ event }) => event.content.trim().length > 0,
|
||||||
target: "sending",
|
target: "sending",
|
||||||
},
|
},
|
||||||
@@ -335,7 +303,7 @@ export const chatMachine = setup({
|
|||||||
actions: "incrementQuotaExceeded",
|
actions: "incrementQuotaExceeded",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
actions: ["logSendImageReceived", "appendUserImage"],
|
actions: "appendUserImage",
|
||||||
target: "sending",
|
target: "sending",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -349,11 +317,6 @@ export const chatMachine = setup({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
sending: {
|
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: {
|
invoke: {
|
||||||
src: "sendMessageHttp",
|
src: "sendMessageHttp",
|
||||||
input: ({ event }) => ({
|
input: ({ event }) => ({
|
||||||
@@ -361,28 +324,14 @@ export const chatMachine = setup({
|
|||||||
}),
|
}),
|
||||||
onDone: {
|
onDone: {
|
||||||
target: "ready",
|
target: "ready",
|
||||||
actions: [
|
actions: assign(({ context, event }) => ({
|
||||||
assign(({ context, event }) => ({
|
messages: [...context.messages, event.output.reply],
|
||||||
messages: [...context.messages, event.output.reply],
|
isReplyingAI: false,
|
||||||
isReplyingAI: false,
|
})),
|
||||||
})),
|
|
||||||
({ context, event }) =>
|
|
||||||
console.log("[chat-machine] guestSession.sending.onDone", {
|
|
||||||
newReplyContentLength: event.output.reply.content.length,
|
|
||||||
totalMessagesCount: context.messages.length + 1,
|
|
||||||
isReplyingAI: false,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
onError: {
|
onError: {
|
||||||
target: "ready",
|
target: "ready",
|
||||||
actions: [
|
actions: assign({ isReplyingAI: false }),
|
||||||
assign({ isReplyingAI: false }),
|
|
||||||
({ event }) =>
|
|
||||||
console.error("[chat-machine] guestSession.sending.onError", {
|
|
||||||
error: event.error instanceof Error ? event.error.message : String(event.error),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -416,24 +365,13 @@ export const chatMachine = setup({
|
|||||||
// 任务 2:拉 history(**只**调**用** loadHistoryActor,**不**再**用** loadMoreHistoryActor)
|
// 任务 2:拉 history(**只**调**用** loadHistoryActor,**不**再**用** loadMoreHistoryActor)
|
||||||
src: "loadHistory",
|
src: "loadHistory",
|
||||||
onDone: {
|
onDone: {
|
||||||
actions: assign(({ event }) => {
|
actions: assign(({ event }) => ({
|
||||||
console.log(
|
messages: event.output.messages,
|
||||||
"[chat-machine] userSession.initializing.loadHistory.onDone",
|
isLoadingMore: false,
|
||||||
{
|
hasMore: event.output.hasMore,
|
||||||
finalCount: event.output.messages.length,
|
historyOffset: event.output.newOffset,
|
||||||
hasMore: event.output.hasMore,
|
historyLoaded: true,
|
||||||
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,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
onError: {
|
onError: {
|
||||||
// 失败**也**标 loaded,**不**卡 init
|
// 失败**也**标 loaded,**不**卡 init
|
||||||
@@ -447,22 +385,14 @@ export const chatMachine = setup({
|
|||||||
ready: {
|
ready: {
|
||||||
on: {
|
on: {
|
||||||
ChatSendMessage: {
|
ChatSendMessage: {
|
||||||
actions: ["logSendMessageReceived", "appendUserMessage"],
|
actions: "appendUserMessage",
|
||||||
guard: ({ event }) => event.content.trim().length > 0,
|
guard: ({ event }) => event.content.trim().length > 0,
|
||||||
target: "sending",
|
target: "sending",
|
||||||
},
|
},
|
||||||
ChatSendImage: {
|
ChatSendImage: {
|
||||||
actions: ["logSendImageReceived", "appendUserImage"],
|
actions: "appendUserImage",
|
||||||
},
|
},
|
||||||
ChatLoadMoreHistory: {
|
ChatLoadMoreHistory: {
|
||||||
actions: [
|
|
||||||
"logLoadMoreHistoryReceived",
|
|
||||||
({ context }) =>
|
|
||||||
console.log("[chat-machine] userSession.ready.ChatLoadMoreHistory received", {
|
|
||||||
currentOffset: context.historyOffset,
|
|
||||||
hasMore: context.hasMore,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
target: "loadingMore",
|
target: "loadingMore",
|
||||||
},
|
},
|
||||||
ChatLogout: "#chat.idle",
|
ChatLogout: "#chat.idle",
|
||||||
@@ -474,12 +404,6 @@ export const chatMachine = setup({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
sending: {
|
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: {
|
invoke: {
|
||||||
src: "sendMessageHttp",
|
src: "sendMessageHttp",
|
||||||
input: ({ event }) => ({
|
input: ({ event }) => ({
|
||||||
@@ -487,63 +411,33 @@ export const chatMachine = setup({
|
|||||||
}),
|
}),
|
||||||
onDone: {
|
onDone: {
|
||||||
target: "ready",
|
target: "ready",
|
||||||
actions: [
|
actions: assign(({ context, event }) => ({
|
||||||
assign(({ context, event }) => ({
|
messages: [...context.messages, event.output.reply],
|
||||||
messages: [...context.messages, event.output.reply],
|
isReplyingAI: false,
|
||||||
isReplyingAI: false,
|
})),
|
||||||
})),
|
|
||||||
({ context, event }) =>
|
|
||||||
console.log("[chat-machine] userSession.sending.onDone", {
|
|
||||||
newReplyContentLength: event.output.reply.content.length,
|
|
||||||
totalMessagesCount: context.messages.length + 1,
|
|
||||||
isReplyingAI: false,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
onError: {
|
onError: {
|
||||||
target: "ready",
|
target: "ready",
|
||||||
actions: [
|
actions: assign({ isReplyingAI: false }),
|
||||||
assign({ isReplyingAI: false }),
|
|
||||||
({ event }) =>
|
|
||||||
console.error("[chat-machine] userSession.sending.onError", {
|
|
||||||
error: event.error instanceof Error ? event.error.message : String(event.error),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
loadingMore: {
|
loadingMore: {
|
||||||
entry: ({ context }) =>
|
|
||||||
console.log("[chat-machine] → userSession.loadingMore", { offset: context.historyOffset }),
|
|
||||||
invoke: {
|
invoke: {
|
||||||
src: "loadMoreHistory",
|
src: "loadMoreHistory",
|
||||||
input: ({ context }) => ({ offset: context.historyOffset }),
|
input: ({ context }) => ({ offset: context.historyOffset }),
|
||||||
onDone: {
|
onDone: {
|
||||||
target: "ready",
|
target: "ready",
|
||||||
actions: [
|
actions: assign(({ context, event }) => ({
|
||||||
assign(({ context, event }) => ({
|
messages: [...event.output.messages, ...context.messages],
|
||||||
messages: [...event.output.messages, ...context.messages],
|
isLoadingMore: false,
|
||||||
isLoadingMore: false,
|
hasMore: event.output.hasMore,
|
||||||
hasMore: event.output.hasMore,
|
historyOffset: event.output.newOffset,
|
||||||
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,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
onError: {
|
onError: {
|
||||||
target: "ready",
|
target: "ready",
|
||||||
actions: [
|
actions: assign({ isLoadingMore: false }),
|
||||||
assign({ isLoadingMore: false }),
|
|
||||||
({ event }) =>
|
|
||||||
console.error("[chat-machine] userSession.loadingMore.onError", {
|
|
||||||
error: event.error instanceof Error ? event.error.message : String(event.error),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ export interface ChatState {
|
|||||||
*/
|
*/
|
||||||
wsConnected: boolean;
|
wsConnected: boolean;
|
||||||
/** 游客剩余配额(次/天)—— **仅**游客业务展示用
|
/** 游客剩余配额(次/天)—— **仅**游客业务展示用
|
||||||
* - default 0 = "未初始化" / "已耗尽"(同码,靠 `isGuest` 区分)
|
|
||||||
* - 非游客:永不被赋值(业务事实"无消息限制")
|
|
||||||
*/
|
*/
|
||||||
guestRemainingQuota: number;
|
guestRemainingQuota: number;
|
||||||
/** 游客总配额(仅游客展示用) */
|
/** 游客总配额(仅游客展示用) */
|
||||||
|
|||||||
Reference in New Issue
Block a user