feat(#525): render reasoning.delta / reasoning.available stream (#543)

* feat(#525): add ReasoningDelta + ReasoningAvailable WsEvent types

* feat(#525): parse reasoning.delta + reasoning.available; route through reducer + StreamingState

* feat(#525): add reasoning streaming buffer to ChatStreamingController

* feat(#525): dispatch ReasoningDelta/ReasoningAvailable in ChatViewModel

* feat(#525): render collapsible ReasoningIndicator in chat list

* fix(#525): address review — icons+semantics, LocalHermesStatusColors, drop redundant ReasoningAvailable branch

* feat(#525): persist reasoningText on model, entity, mapper + Room migration 3->4

* feat(#525): reducer + streaming carry reasoningText onto message

* feat(#525): render persisted reasoning above each assistant bubble, drop global indicator

* nix: flake

* nix: flake

---------

Co-authored-by: M57 <m57@hermes-control.app>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-07-10 23:41:53 +03:00
1 parent d04b74ee0a
commit 53b3966931
17 files changed
+441 -33

No files matched your search

@@ -0,0 +1,59 @@
package com.m57.hermescontrol.data.local
import com.m57.hermescontrol.ui.chat.ChatMessage
import com.m57.hermescontrol.ui.chat.MessageRole
import org.junit.Assert.assertEquals
import org.junit.Test
class ChatMessageMapperTest {
@Test
fun entityToUiModelCarriesReasoningText() {
val entity =
ChatMessageEntity(
id = "msg-1",
sessionId = "session-a",
role = "assistant",
content = "Answer",
reasoningText = "Let me think step by step",
timestamp = 1000L,
)
val ui = entity.toUiModel()
assertEquals("Let me think step by step", ui.reasoningText)
assertEquals("Answer", ui.content)
}
@Test
fun uiModelToEntityCarriesReasoningText() {
val ui =
ChatMessage(
id = "msg-2",
role = MessageRole.ASSISTANT,
content = "Answer",
reasoningText = "Chain of thought",
timestamp = 2000L,
)
val entity = ui.toEntity("session-b")
assertEquals("Chain of thought", entity.reasoningText)
assertEquals("session-b", entity.sessionId)
}
@Test
fun roundTripPreservesReasoningText() {
val ui =
ChatMessage(
id = "msg-3",
role = MessageRole.ASSISTANT,
content = "Answer",
reasoningText = "r",
timestamp = 3000L,
)
val roundTripped = ui.toEntity("s").toUiModel()
assertEquals("r", roundTripped.reasoningText)
}
}