fix(chat): deduplicate message ids (#810)
This commit is contained in:
1 parent
356ccc98b5
commit
87442e1a92
4 files changed
+61
-13
No files matched your search
@@ -154,25 +154,27 @@ internal fun mergeTranscriptWithLive(
|
||||
restMessages: List<ChatMessage>,
|
||||
currentMessages: List<ChatMessage>,
|
||||
): List<ChatMessage> {
|
||||
val dedupedRest = restMessages.dedupeById()
|
||||
val dedupedCurrent = currentMessages.dedupeById()
|
||||
// User rows can carry live/cache-only metadata (for example whether the
|
||||
// bubble continues the active turn). Keep that richer copy when REST
|
||||
// returns the same logical row.
|
||||
val currentUsers = currentMessages.filter { it.role == MessageRole.USER }.toMutableList()
|
||||
val currentUsers = dedupedCurrent.filter { it.role == MessageRole.USER }.toMutableList()
|
||||
val mergedRest =
|
||||
restMessages.map { rest ->
|
||||
dedupedRest.map { rest ->
|
||||
if (rest.role != MessageRole.USER) return@map rest
|
||||
val matchIndex =
|
||||
currentUsers.indexOfFirst { it.id == rest.id }.takeIf { it >= 0 }
|
||||
?: currentUsers.indexOfFirst { sameLogicalMessage(rest, it) }
|
||||
if (matchIndex >= 0) currentUsers.removeAt(matchIndex) else rest
|
||||
}
|
||||
val restIds = restMessages.map { it.id }.toSet()
|
||||
val restIds = dedupedRest.map { it.id }.toSet()
|
||||
val liveTail =
|
||||
currentMessages.filter { old ->
|
||||
old.id !in restIds && restMessages.none { sameLogicalMessage(it, old) }
|
||||
dedupedCurrent.filter { old ->
|
||||
old.id !in restIds && mergedRest.none { sameLogicalMessage(it, old) }
|
||||
}
|
||||
if (liveTail.isEmpty()) return mergedRest
|
||||
return (mergedRest + liveTail).sortedBy { it.timestamp }
|
||||
if (liveTail.isEmpty()) return mergedRest.dedupeById()
|
||||
return (mergedRest + liveTail).dedupeById().sortedBy { it.timestamp }
|
||||
}
|
||||
|
||||
/** Merge a REST page without matching it against already-settled transcript rows. */
|
||||
@@ -186,8 +188,10 @@ internal fun mergeIncrementalTranscriptPage(
|
||||
currentMessages.indexOfLast { message ->
|
||||
serverMessageIndex(message.id, sessionId)?.let { it < offset } == true
|
||||
}
|
||||
return currentMessages.take(settledEnd + 1) +
|
||||
mergeTranscriptWithLive(restMessages, currentMessages.drop(settledEnd + 1))
|
||||
return (
|
||||
currentMessages.take(settledEnd + 1) +
|
||||
mergeTranscriptWithLive(restMessages, currentMessages.drop(settledEnd + 1))
|
||||
).dedupeById()
|
||||
}
|
||||
|
||||
private fun serverMessageIndex(
|
||||
@@ -195,6 +199,8 @@ private fun serverMessageIndex(
|
||||
sessionId: String,
|
||||
): Int? = id.removePrefix("rest-$sessionId-").takeIf { it != id }?.toIntOrNull()
|
||||
|
||||
internal fun List<ChatMessage>.dedupeById(): List<ChatMessage> = associateBy { it.id }.values.toList()
|
||||
|
||||
data class ChatUiState(
|
||||
val messages: List<ChatMessage> = emptyList(),
|
||||
val currentSessionId: String? = null,
|
||||
|
||||
@@ -8,6 +8,10 @@ import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
|
||||
private fun List<ChatMessage>.upsertById(message: ChatMessage): List<ChatMessage> {
|
||||
return (this + message).dedupeById()
|
||||
}
|
||||
|
||||
/**
|
||||
* Pure state reducer for WebSocket events.
|
||||
*
|
||||
@@ -168,7 +172,7 @@ object ChatWsEventReducer {
|
||||
val finalized = streamingState.streamingMessage.copy(isStreaming = false)
|
||||
orphan = finalized
|
||||
state.copy(
|
||||
messages = state.messages + finalized,
|
||||
messages = state.messages.upsertById(finalized),
|
||||
isAgentTyping = true,
|
||||
subagentIndicators = cleanedSubagents,
|
||||
)
|
||||
@@ -319,7 +323,7 @@ object ChatWsEventReducer {
|
||||
return ReducerResult(
|
||||
state =
|
||||
state.copy(
|
||||
messages = state.messages + msg,
|
||||
messages = state.messages.upsertById(msg),
|
||||
isAgentTyping = false,
|
||||
),
|
||||
effects = effects,
|
||||
@@ -358,7 +362,7 @@ object ChatWsEventReducer {
|
||||
return ReducerResult(
|
||||
state =
|
||||
state.copy(
|
||||
messages = state.messages + msg,
|
||||
messages = state.messages.upsertById(msg),
|
||||
isAgentTyping = false,
|
||||
),
|
||||
effects = effects,
|
||||
@@ -401,7 +405,7 @@ object ChatWsEventReducer {
|
||||
)
|
||||
orphanToPersist = finalized
|
||||
state.copy(
|
||||
messages = state.messages + finalized,
|
||||
messages = state.messages.upsertById(finalized),
|
||||
)
|
||||
} else {
|
||||
state
|
||||
|
||||
Reference in new issue
Block a user