fix(web): prevent duplicate assistant message after refresh with tool-call sequences

When messages like [assistant(tool_calls), tool, assistant(text)] are
merged into a single ChatMessage by convertMessagesToChats, the merged
message uses the first assistant's ID. The SSE /messages/events backlog
(which uses >= for the since filter) could re-deliver the final assistant
message, and hasMessageWithId failed to recognize it because the merged
ID was different. This caused the last assistant message to appear twice
after a page refresh.

Track all original server message IDs in a Set (knownServerMessageIds)
so that hasMessageWithId can catch messages whose IDs were absorbed
during merging.
This commit is contained in:
Acbox
2026-03-31 18:46:48 +08:00
parent c77ba63074
commit 7de55f6b49
+26 -3
View File
@@ -112,6 +112,7 @@ export const useChatStore = defineStore('chat', () => {
let abortFn: (() => void) | null = null
let messageEventsSince = ''
let pendingAssistantStream: PendingAssistantStream | null = null
const knownServerMessageIds = new Set<string>()
const messageEventsStream = useRetryingStream()
const localStream = useRetryingStream()
let activeWs: ChatWebSocket | null = null
@@ -342,8 +343,19 @@ export const useChatStore = defineStore('chat', () => {
// ---- Message list management ----
function replaceMessages(items: ChatMessage[]) {
function replaceMessages(items: ChatMessage[], serverRowIds?: string[]) {
messages.splice(0, messages.length, ...items)
knownServerMessageIds.clear()
for (const item of items) {
const tid = String(item.id ?? '').trim()
if (tid) knownServerMessageIds.add(tid)
}
if (serverRowIds) {
for (const id of serverRowIds) {
const tid = id.trim()
if (tid) knownServerMessageIds.add(tid)
}
}
}
// ---- SSE real-time events ----
@@ -580,7 +592,9 @@ export const useChatStore = defineStore('chat', () => {
function hasMessageWithId(id: string) {
const tid = id.trim()
return tid ? messages.some((m) => String(m.id).trim() === tid) : false
if (!tid) return false
if (knownServerMessageIds.has(tid)) return true
return messages.some((m) => String(m.id).trim() === tid)
}
function resolveMessagePlatform(raw: Message): string {
@@ -667,7 +681,8 @@ export const useChatStore = defineStore('chat', () => {
async function loadMessages(botId: string, sid: string) {
const rows = await fetchMessages(botId, sid, { limit: PAGE_SIZE })
const items = convertMessagesToChats(rows)
replaceMessages(items)
const serverRowIds = rows.map((r) => r.id).filter(Boolean)
replaceMessages(items, serverRowIds)
hasMoreOlder.value = true
updateSinceFromRows(rows)
}
@@ -685,6 +700,14 @@ export const useChatStore = defineStore('chat', () => {
const rows = await fetchMessages(bid, sid, { limit: PAGE_SIZE, before })
const items = convertMessagesToChats(rows)
if (rows.length < PAGE_SIZE) hasMoreOlder.value = false
for (const r of rows) {
const tid = (r.id ?? '').trim()
if (tid) knownServerMessageIds.add(tid)
}
for (const item of items) {
const tid = String(item.id ?? '').trim()
if (tid) knownServerMessageIds.add(tid)
}
messages.unshift(...items)
return items.length
} finally {