feat(chat): show agent message reactions (#1422)
This commit is contained in:
14 files changed
+289
No files matched your search
@@ -1,5 +1,6 @@
|
||||
package com.m57.hermescontrol.data.ws
|
||||
|
||||
import com.m57.hermescontrol.data.model.MessageReaction
|
||||
import io.mockk.every
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.unmockkAll
|
||||
@@ -928,6 +929,52 @@ class EventParserTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseMessageReaction_returnsMessageReactionUpdated() {
|
||||
val response =
|
||||
createJsonRpcResponse(
|
||||
jsonrpc = "2.0",
|
||||
id = null,
|
||||
result = null,
|
||||
error = null,
|
||||
method = "event",
|
||||
params =
|
||||
mapOf(
|
||||
"type" to "message.reaction",
|
||||
"session_id" to "s1",
|
||||
"payload" to
|
||||
mapOf(
|
||||
"row_id" to 42,
|
||||
"role" to "user",
|
||||
"reactions" to
|
||||
listOf(
|
||||
mapOf("emoji" to "\uD83D\uDE02", "author" to "agent", "at" to 1.0),
|
||||
mapOf("emoji" to "", "author" to "user"),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
val event = EventParser.parse(response, "") as WsEvent.MessageReactionUpdated
|
||||
assertEquals(42L, event.rowId)
|
||||
assertEquals("user", event.role)
|
||||
assertEquals("s1", event.sessionId)
|
||||
assertEquals(listOf(MessageReaction("\uD83D\uDE02", "agent")), event.reactions)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseMessageReaction_withoutRowId_isUnknown() {
|
||||
val response =
|
||||
createJsonRpcResponse(
|
||||
jsonrpc = "2.0",
|
||||
id = null,
|
||||
result = null,
|
||||
error = null,
|
||||
method = "event",
|
||||
params = mapOf("type" to "message.reaction", "payload" to mapOf("reactions" to emptyList<Any>())),
|
||||
)
|
||||
assertTrue(EventParser.parse(response, "") is WsEvent.Unknown)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testParseUnknownType_returnsUnknownEvent() {
|
||||
val response =
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package com.m57.hermescontrol.ui.chat
|
||||
|
||||
import com.m57.hermescontrol.data.model.MessageReaction
|
||||
import com.m57.hermescontrol.data.model.SessionMessage
|
||||
import com.m57.hermescontrol.notification.ReplyNotificationTarget
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.buildJsonArray
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.put
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Test
|
||||
@@ -59,4 +63,35 @@ class ChatServerMessageMapperPurityTest {
|
||||
|
||||
assertEquals(42L, mapped.single().timestamp)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun persistedReactionsHydrateFromDisplayMetadata() {
|
||||
val row =
|
||||
SessionMessage(
|
||||
id = 5,
|
||||
role = "user",
|
||||
content = JsonPrimitive("hello"),
|
||||
display_metadata =
|
||||
buildJsonObject {
|
||||
put(
|
||||
"reactions",
|
||||
buildJsonArray {
|
||||
add(
|
||||
buildJsonObject {
|
||||
put("emoji", "\uD83D\uDE02")
|
||||
put("author", "agent")
|
||||
},
|
||||
)
|
||||
add(buildJsonObject { put("author", "user") })
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
val mapped = mapServerMessages("s", listOf(row), 0, true, emptyList()).single()
|
||||
assertEquals(listOf(MessageReaction("\uD83D\uDE02", "agent")), mapped.reactions)
|
||||
assertEquals(
|
||||
emptyList<MessageReaction>(),
|
||||
mapServerMessages("s", listOf(assistant(6, "x")), 0, true, emptyList()).single().reactions,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.m57.hermescontrol.ui.chat
|
||||
|
||||
import com.m57.hermescontrol.data.model.MessageReaction
|
||||
import com.m57.hermescontrol.data.model.UsageSnapshotResponse
|
||||
import com.m57.hermescontrol.data.ws.WsEvent
|
||||
import org.junit.Assert.assertEquals
|
||||
@@ -1642,4 +1643,60 @@ class ChatWsEventReducerTest {
|
||||
assertEquals("idle", result.state.compressionStatus)
|
||||
assertEquals(initialMessages, result.state.messages)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun messageReactionPaintsOnlyTheRowWithTheMatchingServerId() {
|
||||
val target = ChatMessage(id = "a", role = MessageRole.USER, content = "hi", serverRowId = 7L)
|
||||
val other = ChatMessage(id = "b", role = MessageRole.ASSISTANT, content = "yo", serverRowId = 8L)
|
||||
val state = ChatUiState(currentSessionId = "s", messages = listOf(target, other))
|
||||
val reactions = listOf(MessageReaction("\u2764\uFE0F", "agent"))
|
||||
val result =
|
||||
ChatWsEventReducer.reduce(
|
||||
state,
|
||||
StreamingState(),
|
||||
WsEvent.MessageReactionUpdated(7L, reactions, "user", "s"),
|
||||
"s",
|
||||
)
|
||||
assertEquals(reactions, result.state.messages[0].reactions)
|
||||
assertTrue(
|
||||
result.state.messages[1]
|
||||
.reactions
|
||||
.isEmpty(),
|
||||
)
|
||||
val retracted =
|
||||
ChatWsEventReducer.reduce(
|
||||
result.state,
|
||||
result.streamingState,
|
||||
WsEvent.MessageReactionUpdated(7L, emptyList(), "user", "s"),
|
||||
"s",
|
||||
)
|
||||
assertTrue(
|
||||
retracted.state.messages[0]
|
||||
.reactions
|
||||
.isEmpty(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun messageReactionForUnknownRowOrOtherSessionIsIgnored() {
|
||||
val msg = ChatMessage(id = "a", role = MessageRole.USER, content = "hi", serverRowId = 7L)
|
||||
val state = ChatUiState(currentSessionId = "s", messages = listOf(msg))
|
||||
val reactions = listOf(MessageReaction("\uD83D\uDC4D", "agent"))
|
||||
val unknownRow =
|
||||
ChatWsEventReducer.reduce(
|
||||
state,
|
||||
StreamingState(),
|
||||
WsEvent.MessageReactionUpdated(99L, reactions, "user", "s"),
|
||||
"s",
|
||||
)
|
||||
assertEquals(state, unknownRow.state)
|
||||
val otherSession =
|
||||
ChatWsEventReducer.reduce(
|
||||
state,
|
||||
StreamingState(),
|
||||
WsEvent.MessageReactionUpdated(7L, reactions, "user", "zzz"),
|
||||
"s",
|
||||
)
|
||||
assertEquals(state, otherSession.state)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user