fix(chat): resolve compilation errors and formatting violations

This commit is contained in:
Hy4ri committed 2026-06-20 11:12:34 +03:00
1 parent d0eedf053f
commit 5ba266cefd
3 files changed
+67 -53

No files matched your search

@@ -72,10 +72,10 @@ import com.m57.hermescontrol.theme.SystemMessageColor
import com.m57.hermescontrol.theme.ToolChipColor
import com.m57.hermescontrol.theme.ToolChipColorLight
import com.m57.hermescontrol.theme.UserBubble
import kotlinx.coroutines.delay
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import kotlinx.coroutines.delay
@Composable
fun ChatBubble(
@@ -811,8 +811,9 @@ private fun RichText(
)
}
private val TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("HH:mm")
.withZone(ZoneId.systemDefault())
private val TIMESTAMP_FORMATTER =
DateTimeFormatter.ofPattern("HH:mm")
.withZone(ZoneId.systemDefault())
private fun formatTimestamp(timestamp: Long): String {
return TIMESTAMP_FORMATTER.format(Instant.ofEpochMilli(timestamp))
@@ -171,9 +171,10 @@ fun ChatScreen(
// Auto-scroll to bottom on new messages
LaunchedEffect(state.messages.size, state.streamingMessage?.content?.length, state.isThinking) {
val totalItems = state.messages.size +
(if (state.streamingMessage != null) 1 else 0) +
(if (state.isThinking) 1 else 0)
val totalItems =
state.messages.size +
(if (state.streamingMessage != null) 1 else 0) +
(if (state.isThinking) 1 else 0)
if (totalItems > 0) {
listState.animateScrollToItem(totalItems - 1)
}
@@ -354,8 +355,9 @@ fun ChatScreen(
.imePadding(),
) {
androidx.compose.animation.AnimatedVisibility(
visible = state.connectionStatus == ConnectionStatus.RECONNECTING ||
state.connectionStatus == ConnectionStatus.DISCONNECTED,
visible =
state.connectionStatus == ConnectionStatus.RECONNECTING ||
state.connectionStatus == ConnectionStatus.DISCONNECTED,
enter = androidx.compose.animation.expandVertically() + androidx.compose.animation.fadeIn(),
exit = androidx.compose.animation.shrinkVertically() + androidx.compose.animation.fadeOut(),
) {
@@ -365,26 +367,29 @@ fun ChatScreen(
contentColor = MaterialTheme.colorScheme.onErrorContainer,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
modifier =
Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = if (state.connectionStatus == ConnectionStatus.RECONNECTING) {
"Reconnecting to Hermes…"
} else {
"Disconnected from Hermes"
},
text =
if (state.connectionStatus == ConnectionStatus.RECONNECTING) {
"Reconnecting to Hermes…"
} else {
"Disconnected from Hermes"
},
style = MaterialTheme.typography.bodyMedium,
)
if (state.connectionStatus == ConnectionStatus.DISCONNECTED) {
TextButton(
onClick = { viewModel.reconnect() },
colors = androidx.compose.material3.ButtonDefaults.textButtonColors(
contentColor = MaterialTheme.colorScheme.onErrorContainer,
),
colors =
androidx.compose.material3.ButtonDefaults.textButtonColors(
contentColor = MaterialTheme.colorScheme.onErrorContainer,
),
) {
Text("Reconnect")
}
@@ -254,13 +254,12 @@ class ChatViewModel(
private fun handleMessageComplete(event: WsEvent.MessageComplete) {
if (!isCurrentSession(event.sessionId)) return
// Finalize: move the streaming message into the main list
val finalizedMsg: ChatMessage
val sessionId: String?
var finalizedMsg: ChatMessage? = null
var sessionId: String? = null
_uiState.update { state ->
val streaming = state.streamingMessage
finalizedMsg = if (streaming != null) {
val msg = if (streaming != null) {
streaming.copy(
content = event.text,
isStreaming = false,
@@ -271,9 +270,10 @@ class ChatViewModel(
content = event.text,
)
}
finalizedMsg = msg
sessionId = state.currentSessionId
state.copy(
messages = state.messages + finalizedMsg,
messages = state.messages + msg,
streamingMessage = null,
isAgentTyping = false,
isThinking = false,
@@ -283,10 +283,11 @@ class ChatViewModel(
streamingMessageId = null
// Persist finalized message — OUTSIDE update{} to avoid CAS retry
val msgToPersist = finalizedMsg
val sid = sessionId ?: _uiState.value.currentSessionId
if (sid != null) {
if (msgToPersist != null && sid != null) {
viewModelScope.launch(Dispatchers.IO) {
dao.upsert(finalizedMsg.toEntity(sid))
dao.upsert(msgToPersist.toEntity(sid))
}
}
}
@@ -296,16 +297,17 @@ class ChatViewModel(
// If we still have an un-finalized streaming message (no MessageComplete
// was sent, which happens on interrupts), fold it into the list.
val orphan: ChatMessage?
val sessionId: String?
var orphan: ChatMessage? = null
var sessionId: String? = null
_uiState.update { state ->
val streaming = state.streamingMessage
orphan = streaming?.copy(isStreaming = false)
val msg = streaming?.copy(isStreaming = false)
orphan = msg
sessionId = state.currentSessionId
if (orphan != null) {
if (msg != null) {
state.copy(
messages = state.messages + orphan,
messages = state.messages + msg,
streamingMessage = null,
isAgentTyping = false,
isThinking = false,
@@ -322,10 +324,11 @@ class ChatViewModel(
streamingMessageId = null
// Persist orphaned streaming message
val msgToPersist = orphan
val sid = sessionId ?: _uiState.value.currentSessionId
if (orphan != null && sid != null) {
if (msgToPersist != null && sid != null) {
viewModelScope.launch(Dispatchers.IO) {
dao.upsert(orphan.toEntity(sid))
dao.upsert(msgToPersist.toEntity(sid))
}
}
}
@@ -358,16 +361,18 @@ class ChatViewModel(
_uiState.update { state ->
val messages = state.messages.toMutableList()
val toolIdx = messages.indexOfLast {
it.role == MessageRole.TOOL &&
it.toolName == event.name &&
it.toolStatus == ToolStatus.RUNNING
}
val toolIdx =
messages.indexOfLast {
it.role == MessageRole.TOOL &&
it.toolName == event.name &&
it.toolStatus == ToolStatus.RUNNING
}
if (toolIdx >= 0) {
val updated = messages[toolIdx].copy(
toolStatus = ToolStatus.COMPLETED,
content = event.data.toString(),
)
val updated =
messages[toolIdx].copy(
toolStatus = ToolStatus.COMPLETED,
content = event.data.toString(),
)
messages[toolIdx] = updated
completedTool = updated
}
@@ -494,10 +499,11 @@ class ChatViewModel(
return
}
val userMessage = ChatMessage(
role = MessageRole.USER,
content = text,
)
val userMessage =
ChatMessage(
role = MessageRole.USER,
content = text,
)
// Update UI — no DB writes inside update{}
_uiState.update { state ->
@@ -892,13 +898,15 @@ class ChatViewModel(
* responded to. Surfaces a timeout error for the user.
*/
private fun startPendingRequestCleanup() {
pendingCleanupJob = viewModelScope.launch {
while (true) {
delay(PENDING_REQUEST_TIMEOUT_MS)
val now = System.currentTimeMillis()
val stale = pendingRequests.entries.filter {
now - it.value.createdAt > PENDING_REQUEST_TIMEOUT_MS
}
pendingCleanupJob =
viewModelScope.launch {
while (true) {
delay(PENDING_REQUEST_TIMEOUT_MS)
val now = System.currentTimeMillis()
val stale =
pendingRequests.entries.filter {
now - it.value.createdAt > PENDING_REQUEST_TIMEOUT_MS
}
for (entry in stale) {
pendingRequests.remove(entry.key)
Log.w(TAG, "Request timed out: ${entry.value.method} (id=${entry.key})")