feat(chat): add configurable message stats (#1134)

This commit is contained in:
M57 authored and GitHub committed 2026-09-16 00:00:09 +03:00
1 parent afffcd95e8
commit 529b424eb4
20 files changed
+606 -44

No files matched your search

@@ -89,6 +89,10 @@ class ChatUpdateCommandTest {
every { AuthManager.getToken() } returns "test-token"
every { AuthManager.isTypingEffectEnabled() } returns true
every { AuthManager.getTypingEffectDelayMs() } returns 30
every { AuthManager.isMessageStatsEnabled() } returns false
every { AuthManager.isUserMessageTokensEnabled() } returns true
every { AuthManager.isAssistantMessageTokensEnabled() } returns true
every { AuthManager.isTokensPerSecondEnabled() } returns true
every { AuthManager.isAutoReconnect() } returns false
every { AuthManager.isRestoreLastSession() } returns false
every { AuthManager.getLastOpenedSessionId() } returns null
@@ -165,6 +165,10 @@ class ChatViewModelTest {
every { ProfileSwitchCoordinator.connectionSwitched } returns MutableSharedFlow<String>()
every { AuthManager.isTypingEffectEnabled() } returns true
every { AuthManager.getTypingEffectDelayMs() } returns 30
every { AuthManager.isMessageStatsEnabled() } returns false
every { AuthManager.isUserMessageTokensEnabled() } returns true
every { AuthManager.isAssistantMessageTokensEnabled() } returns true
every { AuthManager.isTokensPerSecondEnabled() } returns true
every { AuthManager.isAutoReconnect() } returns false
every { AuthManager.isRestoreLastSession() } returns false
every { AuthManager.getLastOpenedSessionId() } returns null
@@ -4140,13 +4144,16 @@ class ChatViewModelTest {
fun testRefreshSettings_updatesUiState() =
runTest {
// Given the default setup, init{} already calls refreshSettings() once,
// so the initial state reflects the setUp defaults (typingEffectEnabled=true,
// typingEffectDelayMs=30).
// so the initial state reflects the setUp defaults.
val viewModel = createViewModel()
advanceUntilIdle()
with(viewModel.uiState.value) {
assertTrue(typingEffectEnabled)
assertEquals(30, typingEffectDelayMs)
assertFalse(messageStatsEnabled)
assertTrue(showUserMessageTokens)
assertTrue(showAssistantMessageTokens)
assertTrue(showTokensPerSecond)
}
// When settings change after construction and refreshSettings() is re-invoked,
@@ -4154,6 +4161,10 @@ class ChatViewModelTest {
// re-reads AuthManager live (the real regression scenario).
every { AuthManager.isTypingEffectEnabled() } returns false
every { AuthManager.getTypingEffectDelayMs() } returns 50
every { AuthManager.isMessageStatsEnabled() } returns true
every { AuthManager.isUserMessageTokensEnabled() } returns false
every { AuthManager.isAssistantMessageTokensEnabled() } returns false
every { AuthManager.isTokensPerSecondEnabled() } returns false
viewModel.refreshSettings()
advanceUntilIdle()
@@ -4161,6 +4172,10 @@ class ChatViewModelTest {
val state = viewModel.uiState.value
assertFalse(state.typingEffectEnabled)
assertEquals(50, state.typingEffectDelayMs)
assertTrue(state.messageStatsEnabled)
assertFalse(state.showUserMessageTokens)
assertFalse(state.showAssistantMessageTokens)
assertFalse(state.showTokensPerSecond)
}
@Test
@@ -94,6 +94,10 @@ class SlashCommandDispatchRpcTest {
every { AuthManager.getToken() } returns "test-token"
every { AuthManager.isTypingEffectEnabled() } returns true
every { AuthManager.getTypingEffectDelayMs() } returns 30
every { AuthManager.isMessageStatsEnabled() } returns false
every { AuthManager.isUserMessageTokensEnabled() } returns true
every { AuthManager.isAssistantMessageTokensEnabled() } returns true
every { AuthManager.isTokensPerSecondEnabled() } returns true
every { AuthManager.isAutoReconnect() } returns false
every { AuthManager.isRestoreLastSession() } returns false
every { AuthManager.getLastOpenedSessionId() } returns null
@@ -73,6 +73,10 @@ class SettingsViewModelTest {
every { AuthManager.isTypingEffectEnabled() } returns true
every { AuthManager.getTypingEffectDelayMs() } returns 30
every { AuthManager.getChatFontScale() } returns 1.0f
every { AuthManager.isMessageStatsEnabled() } returns false
every { AuthManager.isUserMessageTokensEnabled() } returns true
every { AuthManager.isAssistantMessageTokensEnabled() } returns true
every { AuthManager.isTokensPerSecondEnabled() } returns true
every { AuthManager.getConnectionProfiles() } returns emptyList()
every { AuthManager.getSelectedProfileId() } answers { storedSelectedProfileId }
every { AuthManager.baseUrl() } returns "http://127.0.0.1:9119/"
@@ -85,6 +89,10 @@ class SettingsViewModelTest {
every { AuthManager.setTypingEffectEnabled(any()) } returns Unit
every { AuthManager.setTypingEffectDelayMs(any()) } returns Unit
every { AuthManager.setChatFontScale(any()) } returns Unit
every { AuthManager.setMessageStatsEnabled(any()) } returns Unit
every { AuthManager.setUserMessageTokensEnabled(any()) } returns Unit
every { AuthManager.setAssistantMessageTokensEnabled(any()) } returns Unit
every { AuthManager.setTokensPerSecondEnabled(any()) } returns Unit
every { AuthManager.setSelectedProfileId(any()) } answers {
storedSelectedProfileId = firstArg()
}
@@ -170,6 +178,46 @@ class SettingsViewModelTest {
verify { AuthManager.setChatFontScale(1.15f) }
}
@Test
fun testMessageStats_loadsAndTogglesIndependently() =
runTest {
every { AuthManager.isMessageStatsEnabled() } returns false
every { AuthManager.isUserMessageTokensEnabled() } returns false
every { AuthManager.isAssistantMessageTokensEnabled() } returns true
every { AuthManager.isTokensPerSecondEnabled() } returns false
val viewModel = SettingsViewModel(ioDispatcher = testDispatcher)
testDispatcher.scheduler.advanceUntilIdle()
assertEquals(false, viewModel.uiState.value.messageStatsEnabled)
assertEquals(false, viewModel.uiState.value.showUserMessageTokens)
assertEquals(true, viewModel.uiState.value.showAssistantMessageTokens)
assertEquals(false, viewModel.uiState.value.showTokensPerSecond)
viewModel.onMessageStatsEnabledChange(true)
assertEquals(true, viewModel.uiState.value.messageStatsEnabled)
verify { AuthManager.setMessageStatsEnabled(true) }
verify(exactly = 0) { AuthManager.setUserMessageTokensEnabled(any()) }
verify(exactly = 0) { AuthManager.setAssistantMessageTokensEnabled(any()) }
verify(exactly = 0) { AuthManager.setTokensPerSecondEnabled(any()) }
viewModel.onUserMessageTokensChange(true)
viewModel.onAssistantMessageTokensChange(false)
viewModel.onTokensPerSecondChange(true)
assertEquals(true, viewModel.uiState.value.showUserMessageTokens)
assertEquals(false, viewModel.uiState.value.showAssistantMessageTokens)
assertEquals(true, viewModel.uiState.value.showTokensPerSecond)
verify { AuthManager.setUserMessageTokensEnabled(true) }
verify { AuthManager.setAssistantMessageTokensEnabled(false) }
verify { AuthManager.setTokensPerSecondEnabled(true) }
viewModel.onMessageStatsEnabledChange(false)
assertEquals(false, viewModel.uiState.value.messageStatsEnabled)
assertEquals(true, viewModel.uiState.value.showUserMessageTokens)
assertEquals(false, viewModel.uiState.value.showAssistantMessageTokens)
assertEquals(true, viewModel.uiState.value.showTokensPerSecond)
verify(exactly = 2) { AuthManager.setMessageStatsEnabled(any()) }
}
@Test
fun testLoadSettings_noSelectedProfile_renameEmpty() {
every { AuthManager.getConnectionProfiles() } returns testProfiles