feat(web): 前端 P0 收尾(多路忙态收口 / 握手 history 落 store / 清理幽灵键)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

This commit is contained in:
AskaEth committed 2026-10-05 11:29:17 +08:00
1 parent e852e60af9
commit 012e9f3ad4
10 files changed
+365 -31

No files matched your search

+16
View File
@@ -18,6 +18,7 @@ import { now, uid } from "../lib/util";
import {
currentGraph,
dropMessage,
mergeHistory,
mergeMessage,
patchAssistantTail,
patchMessage,
@@ -144,6 +145,13 @@ export interface AppStore extends TurnSlice {
* renders twice (design/16 §4.3 dedup). A message without a session id (a local
* optimistic bubble) belongs to the active session. */
appendOrUpdateMessage: (msg: ChatMessage) => void;
/** Merge one handshake `history` replay page (contract §3.2) into the session the
* frame names — up to 50 already-persisted rows, once per (re)connect. Dedup by
* id and adopt a local optimistic bubble only on an exact key (`clientMsgId` for
* users / `turnId` for assistants), never by the content/time heuristic: this is
* the reconnect catch-up path, so a wrong adopt would rewrite a streaming bubble
* with an old reply. */
mergeHistoryPage: (sessionId: string, msgs: ChatMessage[]) => void;
/** Patch one already-rendered message in place by id — the `message_edited` WS
* path. With `sessionId` the patch is confined to that session's bucket; without
* one the id is searched across the active list and every bucket (ids are
@@ -280,6 +288,14 @@ export const useAppStore = create<AppStore>()(
)
),
mergeHistoryPage: (sessionId, list) =>
set((s) => {
if (!list.length) return s;
return writeSession(s, sessionId || s.activeSessionId, (cur) =>
mergeHistory(cur, list)
);
}),
updateMessageById: (id, patch, sessionId) =>
set((s) =>
writeSessionById(s, sessionId, (list) => patchMessage(list, id, patch))