feat(chat): implement WebSocket message sending and integrate with chat state machine
This commit is contained in:
@@ -42,6 +42,7 @@ import {
|
||||
loadQuotaActor,
|
||||
loadHistoryActor,
|
||||
sendMessageHttpActor,
|
||||
sendMessageWsActor,
|
||||
loadMoreHistoryActor,
|
||||
chatWebSocketActor,
|
||||
} from "./chat-machine.actors";
|
||||
@@ -69,6 +70,7 @@ export const chatMachine = setup({
|
||||
loadQuota: loadQuotaActor,
|
||||
loadHistory: loadHistoryActor,
|
||||
sendMessageHttp: sendMessageHttpActor,
|
||||
sendMessageWs: sendMessageWsActor,
|
||||
loadMoreHistory: loadMoreHistoryActor,
|
||||
chatWebSocket: chatWebSocketActor,
|
||||
},
|
||||
@@ -344,10 +346,23 @@ export const chatMachine = setup({
|
||||
// userSession(非游客会话 —— WS 父级 + history 子级)
|
||||
// ========================================================================
|
||||
userSession: {
|
||||
// 父级 on:把 WS / AI 流句 事件从 ready.on 上提到 userSession.on
|
||||
// —— 任何 child state(initializing / ready / sendingViaWs / sendingViaHttp / loadingMore)
|
||||
// 都能收到 `ChatAISentenceReceived` 并把 AI 句 push 到 messages,
|
||||
// 这点是 sendingViaWs 流式回 reply 的关键(machine 当时在 sendingViaWs)
|
||||
//
|
||||
// 子级 override 规则:
|
||||
// - sendingViaWs 重新声明 `ChatWebSocketError` 加 target: "ready"(带 action)
|
||||
// —— XState v5: child handler 替换 parent;复制 action
|
||||
on: {
|
||||
ChatAISentenceReceived: { actions: "appendOrUpdateAISentence" },
|
||||
ChatWebSocketError: { actions: "appendSocketErrorMessage" },
|
||||
ChatWebSocketConnected: { actions: "setWsConnected" },
|
||||
},
|
||||
initial: "initializing",
|
||||
exit: "clearWsConnected",
|
||||
invoke: {
|
||||
// 父级:WS 长连接(一进来就连,跨 initializing/ready/sending/loadingMore)
|
||||
// 父级:WS 长连接(一进来就连,跨 initializing/ready/sending*/loadingMore)
|
||||
src: "chatWebSocket",
|
||||
input: ({ event }) => ({
|
||||
token: event.type === "ChatNonGuestLogin" ? event.token : "",
|
||||
@@ -385,11 +400,24 @@ export const chatMachine = setup({
|
||||
},
|
||||
ready: {
|
||||
on: {
|
||||
ChatSendMessage: {
|
||||
actions: "appendUserMessage",
|
||||
guard: ({ event }) => event.content.trim().length > 0,
|
||||
target: "sending",
|
||||
},
|
||||
// 发送消息:wsConnected → 走 WS(流式回 reply),否则 → 走 HTTP(一次性回 reply)
|
||||
// 两个 branch 都有 "content 非空" guard(仅作 fallback 兜底:wsConnected=true 也需
|
||||
// 内容非空;wsConnected=false 同理)
|
||||
ChatSendMessage: [
|
||||
{
|
||||
// WS 已连 + 内容非空 → 走 WS(sendingViaWs 等 AI 流式回 reply)
|
||||
guard: ({ context, event }) =>
|
||||
context.wsConnected && event.content.trim().length > 0,
|
||||
actions: "appendUserMessage",
|
||||
target: "sendingViaWs",
|
||||
},
|
||||
{
|
||||
// WS 未连(或 fallback) + 内容非空 → 走 HTTP(sendingViaHttp 一次性回 reply)
|
||||
guard: ({ event }) => event.content.trim().length > 0,
|
||||
actions: "appendUserMessage",
|
||||
target: "sendingViaHttp",
|
||||
},
|
||||
],
|
||||
ChatSendImage: {
|
||||
actions: "appendUserImage",
|
||||
},
|
||||
@@ -398,13 +426,45 @@ export const chatMachine = setup({
|
||||
},
|
||||
ChatLogout: "#chat.idle",
|
||||
ChatGuestLogin: "#chat.guestSession",
|
||||
ChatAISentenceReceived: { actions: "appendOrUpdateAISentence" },
|
||||
ChatWebSocketError: { actions: "appendSocketErrorMessage" },
|
||||
ChatWebSocketConnected: { actions: "setWsConnected" },
|
||||
ChatQuotaExceeded: { actions: "incrementQuotaExceeded" },
|
||||
// 注:ChatAISentenceReceived / ChatWebSocketError / ChatWebSocketConnected
|
||||
// 已上提到 userSession.on,子级不再声明
|
||||
},
|
||||
},
|
||||
sending: {
|
||||
// WS 发送:invoke sendMessageWsActor 触发一次 send,
|
||||
// 之后 AI 流式回 reply 走 userSession.on 的 ChatAISentenceReceived → appendOrUpdateAISentence
|
||||
// 当句尾 done: true → isReplyingAI = false → always 跳回 ready
|
||||
sendingViaWs: {
|
||||
on: {
|
||||
// 流式回包中 WS 出错(mid-stream) → 加错误泡 + 跳回 ready(不卡在 sendingViaWs)
|
||||
// 这个 child handler 替换 userSession.on 的 ChatWebSocketError(XState v5 不合并)
|
||||
// 所以需要重复声明 action: "appendSocketErrorMessage"
|
||||
ChatWebSocketError: {
|
||||
target: "ready",
|
||||
actions: "appendSocketErrorMessage",
|
||||
},
|
||||
},
|
||||
invoke: {
|
||||
src: "sendMessageWs",
|
||||
input: ({ event }) => ({
|
||||
content: event.type === "ChatSendMessage" ? event.content : "",
|
||||
}),
|
||||
onError: {
|
||||
// sendMessageWsActor 自身抛错(无 active ws / ws not OPEN) → 回 ready + 清 typing
|
||||
target: "ready",
|
||||
actions: assign({ isReplyingAI: false }),
|
||||
},
|
||||
},
|
||||
// 监听 isReplyingAI —— appendOrUpdateAISentence 在 done: true 时设 false
|
||||
always: [
|
||||
{
|
||||
target: "ready",
|
||||
guard: ({ context }) => !context.isReplyingAI,
|
||||
},
|
||||
],
|
||||
},
|
||||
// HTTP 发送:一次发送 = 一次返回(保留原 sending 状态语义,guest 与 ws-down fallback 走此)
|
||||
sendingViaHttp: {
|
||||
invoke: {
|
||||
src: "sendMessageHttp",
|
||||
input: ({ event }) => ({
|
||||
|
||||
Reference in New Issue
Block a user