fix(chat): render cached history before network sync

This commit is contained in:
2026-07-02 11:51:20 +08:00
parent cd25fa35f2
commit 5821a4d062
7 changed files with 339 additions and 114 deletions
+118 -51
View File
@@ -34,6 +34,7 @@ import type { ChatEvent } from "./chat-events";
import {
applyHttpSendOutput,
applyHistoryLoadedOutput,
applyNetworkHistoryLoadedOutput,
countLockedHistoryMessages,
shouldAutoUnlockHistory,
shouldPromptUnlockHistory,
@@ -78,6 +79,46 @@ export const chatMachine = setup({
clearChatSession: assign(() => ({
...initialState,
})),
applyLocalHistoryLoaded: assign(({ event }) => {
if (event.type !== "ChatLocalHistoryLoaded") return {};
return applyHistoryLoadedOutput(event.output);
}),
applyNetworkHistoryLoaded: assign(({ context, event }) => {
if (event.type !== "ChatNetworkHistoryLoaded") return {};
return applyNetworkHistoryLoadedOutput(context, event.output);
}),
applyNetworkHistoryLoadedAndClearPayment: assign(({ context, event }) => {
if (event.type !== "ChatNetworkHistoryLoaded") return {};
return {
...applyNetworkHistoryLoadedOutput(context, event.output),
isLoadingMore: false,
paymentUnlockPending: false,
};
}),
showUnlockHistoryPromptFromNetwork: assign(({ context, event }) => {
if (event.type !== "ChatNetworkHistoryLoaded") return {};
const nextHistory = applyNetworkHistoryLoadedOutput(
context,
event.output,
);
return {
...nextHistory,
isLoadingMore: false,
paymentUnlockPending: false,
unlockHistoryPromptVisible: true,
lockedHistoryCount: countLockedHistoryMessages(nextHistory.messages),
unlockHistoryError: null,
};
}),
markHistoryLoadFailed: assign({
isLoadingMore: false,
historyLoaded: true,
}),
},
}).createMachine({
id: "chat",
@@ -98,15 +139,27 @@ export const chatMachine = setup({
},
guestSession: {
invoke: {
id: "messageQueue",
src: "httpMessageQueue",
},
invoke: [
{
id: "messageQueue",
src: "httpMessageQueue",
},
{
id: "loadHistory",
src: "loadHistory",
},
],
on: {
ChatUserLogin: {
target: "#chat.userSession",
actions: "startUserSession",
},
ChatNetworkHistoryLoaded: {
actions: "applyNetworkHistoryLoaded",
},
ChatHistoryLoadFailed: {
actions: "markHistoryLoadFailed",
},
ChatQueuedSendStarted: {
actions: "markQueuedSendStarted",
},
@@ -120,25 +173,18 @@ export const chatMachine = setup({
initial: "initializing",
states: {
initializing: {
always: [
{
on: {
ChatLocalHistoryLoaded: {
target: "ready",
guard: ({ context }) => context.historyLoaded,
actions: "applyLocalHistoryLoaded",
},
],
invoke: {
id: "loadHistory",
src: "loadHistory",
onDone: {
actions: assign(({ event }) =>
applyHistoryLoadedOutput(event.output),
),
ChatNetworkHistoryLoaded: {
target: "ready",
actions: "applyNetworkHistoryLoaded",
},
onError: {
// 失败也标 loaded,不卡 init(让 UI 还是能进 ready,屏幕可能空)
actions: assign({
historyLoaded: true,
}),
ChatHistoryLoadFailed: {
target: "ready",
actions: "markHistoryLoadFailed",
},
},
},
@@ -178,15 +224,50 @@ export const chatMachine = setup({
},
userSession: {
invoke: {
id: "messageQueue",
src: "httpMessageQueue",
},
invoke: [
{
id: "messageQueue",
src: "httpMessageQueue",
},
{
id: "loadHistory",
src: "loadHistory",
},
],
on: {
ChatLogout: {
target: "#chat.idle",
actions: "clearChatSession",
},
ChatNetworkHistoryLoaded: [
{
guard: ({ context, event }) =>
event.type === "ChatNetworkHistoryLoaded" &&
context.paymentUnlockPending &&
shouldAutoUnlockHistory(event.output.messages),
target: ".unlockingHistory",
actions: [
"applyNetworkHistoryLoadedAndClearPayment",
"markUnlockHistoryStarted",
],
},
{
guard: ({ context, event }) =>
event.type === "ChatNetworkHistoryLoaded" &&
context.paymentUnlockPending &&
shouldPromptUnlockHistory(event.output.messages),
target: ".ready",
actions: "showUnlockHistoryPromptFromNetwork",
},
{
guard: ({ context }) =>
!context.isUnlockingHistory && !context.isUnlockingMessage,
actions: "applyNetworkHistoryLoaded",
},
],
ChatHistoryLoadFailed: {
actions: "markHistoryLoadFailed",
},
ChatQueuedSendStarted: {
actions: "markQueuedSendStarted",
},
@@ -232,53 +313,39 @@ export const chatMachine = setup({
initial: "initializing",
states: {
initializing: {
invoke: {
src: "loadHistory",
onDone: [
on: {
ChatLocalHistoryLoaded: {
target: "ready",
actions: "applyLocalHistoryLoaded",
},
ChatNetworkHistoryLoaded: [
{
guard: ({ context, event }) =>
event.type === "ChatNetworkHistoryLoaded" &&
context.paymentUnlockPending &&
shouldAutoUnlockHistory(event.output.messages),
target: "unlockingHistory",
actions: [
assign(({ event }) => ({
...applyHistoryLoadedOutput(event.output),
isLoadingMore: false,
})),
"applyNetworkHistoryLoadedAndClearPayment",
"markUnlockHistoryStarted",
],
},
{
guard: ({ context, event }) =>
event.type === "ChatNetworkHistoryLoaded" &&
context.paymentUnlockPending &&
shouldPromptUnlockHistory(event.output.messages),
target: "ready",
actions: assign(({ event }) => ({
...applyHistoryLoadedOutput(event.output),
isLoadingMore: false,
paymentUnlockPending: false,
unlockHistoryPromptVisible: true,
lockedHistoryCount: countLockedHistoryMessages(
event.output.messages,
),
unlockHistoryError: null,
})),
actions: "showUnlockHistoryPromptFromNetwork",
},
{
target: "ready",
actions: assign(({ event }) => ({
...applyHistoryLoadedOutput(event.output),
isLoadingMore: false,
paymentUnlockPending: false,
})),
actions: "applyNetworkHistoryLoadedAndClearPayment",
},
],
onError: {
ChatHistoryLoadFailed: {
target: "ready",
actions: assign({
isLoadingMore: false,
historyLoaded: true,
}),
actions: "markHistoryLoadFailed",
},
},
},