diff --git a/jam-ui/src/store/features/sessionChatSlice.js b/jam-ui/src/store/features/sessionChatSlice.js index 414d6edff..2738aa903 100644 --- a/jam-ui/src/store/features/sessionChatSlice.js +++ b/jam-ui/src/store/features/sessionChatSlice.js @@ -185,9 +185,18 @@ const sessionChatSlice = createSlice({ state.messagesByChannel[channel] = []; } - // Deduplicate by msg_id - const exists = state.messagesByChannel[channel].some(m => m.id === message.id); - if (exists) return; + // Deduplicate by msg_id and attachmentId (for attachment messages) + const existsById = state.messagesByChannel[channel].some(m => m.id === message.id); + if (existsById) return; + + // For attachment messages, also check by attachmentId to avoid duplicates + // from optimistic updates that use different ID format + if (message.attachmentId) { + const existsByAttachmentId = state.messagesByChannel[channel].some( + m => m.attachmentId && m.attachmentId === message.attachmentId + ); + if (existsByAttachmentId) return; + } // Add message state.messagesByChannel[channel].push(message); @@ -335,9 +344,23 @@ const sessionChatSlice = createSlice({ attachmentSize: null // Not available in REST API response (only in WebSocket) })); - // Deduplicate messages + // Deduplicate messages by ID and by attachmentId (for attachment messages) + // This handles the case where optimistic attachment message uses 'attachment-{id}' format + // but the REST API returns the chat message ID const existingIds = new Set(state.messagesByChannel[channel].map(m => m.id)); - const newMessages = transformedMessages.filter(m => !existingIds.has(m.id)); + const existingAttachmentIds = new Set( + state.messagesByChannel[channel] + .filter(m => m.attachmentId) + .map(m => m.attachmentId) + ); + const newMessages = transformedMessages.filter(m => { + // Check by message ID first + if (existingIds.has(m.id)) return false; + // For attachment messages, also check by attachmentId to avoid duplicates + // from optimistic updates that use different ID format + if (m.attachmentId && existingAttachmentIds.has(m.attachmentId)) return false; + return true; + }); // Prepend new messages (oldest first for pagination) state.messagesByChannel[channel] = [...newMessages, ...state.messagesByChannel[channel]];