Add used/full context meter to chat screen (#717)
* Adopt /api/health REST route (issue #713) Cheap liveness probe on the connection screen: GET /api/health surfaces backend version + auth mode (Token vs Gated/OAuth) without the heavier /api/status payload. Added HermesApiService.getHealth + HealthStatus model; ConnectViewModel.loadHealth probes on open (best-effort, silent on failure); ConnectScreen shows a read-only health card when reachable. Verified: ktlint clean, assembleDebug BUILD SUCCESSFUL locally. * Add used/full context meter to chat screen Shows session prompt tokens vs model context window above the composer, polled every 5s. Color-coded bar warns at 70%/90%. * fix(chat): fix unit tests for context usage meter Format >=100k tokens cleanly in ContextUsageChip and stub AuthManager profile ID in ChatViewModelTest. * Fix context meter: source used tokens from input_tokens Gateway /api/sessions/{id} has no last_prompt_tokens column; use input_tokens instead. Show meter as soon as full context is known. * Add tappable context breakdown sheet Chip opens a bottom sheet with input/output/cache/read/write/reasoning token rows + message count. Source breakdown from /api/sessions/{id}. * Refresh context meter on streaming complete Trigger fetchContextUsage immediately when a message finishes streaming instead of waiting for the 5s session-sync poll. * Fix context meter: source used tokens from live WS session.info The REST /api/sessions/{id} endpoint reads the DB input_tokens column, which is stale/0 for active sessions — that's why the meter was stuck at 0. Switch the meter to the live values the gateway pushes over the WS session.info payload after each turn: - used = last_prompt_tokens (real used context for the turn) - full = context_length (model-accurate window; keep /api/model/info as a fallback before the first turn completes) fetchContextUsage now only tops up the denominator from /api/model/info and no longer overwrites the live WS value with the stale REST input_tokens. The 5s poll remains as a fallback. * Revert "Fix context meter: source used tokens from live WS session.info" This reverts commit 932393fd80993d57e66fe93552a949460d646674. --------- Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
1 parent
c181d1d593
commit
0180fb12c2
15 files changed
+641
-2
No files matched your search
@@ -83,6 +83,7 @@ class ChatViewModelTest {
|
||||
mockConnectionStatus.value = ConnectionStatus.DISCONNECTED
|
||||
|
||||
every { AuthManager.getToken() } returns "test-token"
|
||||
every { AuthManager.getSelectedProfileId() } returns null
|
||||
every { AuthManager.isTypingEffectEnabled() } returns true
|
||||
every { AuthManager.getTypingEffectDelayMs() } returns 30
|
||||
every { AuthManager.isAutoReconnect() } returns false
|
||||
@@ -1742,7 +1743,10 @@ class ChatViewModelTest {
|
||||
"hasOlderMessages must be false when initial page is empty",
|
||||
viewModel.uiState.value.hasOlderMessages,
|
||||
)
|
||||
assertTrue(viewModel.uiState.value.messages.isEmpty())
|
||||
assertTrue(
|
||||
viewModel.uiState.value.messages
|
||||
.isEmpty(),
|
||||
)
|
||||
assertFalse(viewModel.uiState.value.isLoading)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.m57.hermescontrol.ui.chat.components
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Unit tests for the token-compaction math in [ContextUsageChip] — the only
|
||||
* non-trivial pure logic in the context meter. Formats must stay compact so
|
||||
* the chip fits above the composer on a narrow phone screen.
|
||||
*/
|
||||
class ContextUsageChipTest {
|
||||
@Test
|
||||
fun formatTokens_subThousand_returnsRawCount() {
|
||||
assertEquals("950", formatTokens(950))
|
||||
assertEquals("0", formatTokens(0))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun formatTokens_thousands_dropsTrailingPointZero() {
|
||||
// 262_144 -> "262k" (no ".0"), not "262.1k".
|
||||
assertEquals("262k", formatTokens(262_144))
|
||||
// 12_300 -> "12.3k" (one decimal preserved).
|
||||
assertEquals("12.3k", formatTokens(12_300))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun formatTokens_millions_usesMSuffix() {
|
||||
assertEquals("1M", formatTokens(1_000_000))
|
||||
assertEquals("2M", formatTokens(2_048_000))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun formatTokens_exactThousandBoundary_roundsDownCleanly() {
|
||||
// 1_000 -> "1k" not "1.0k".
|
||||
assertEquals("1k", formatTokens(1_000))
|
||||
assertEquals("100k", formatTokens(100_000))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user