feat(chat): display token usage and tps in message footers (#1094)
This commit is contained in:
24 files changed
+448
-3
No files matched your search
@@ -23,6 +23,8 @@ class ChatMessageMapperTest {
|
||||
toolName = "search",
|
||||
toolStatus = "RUNNING",
|
||||
isStreaming = true,
|
||||
tokenCount = 42,
|
||||
tps = 55.5,
|
||||
)
|
||||
|
||||
val ui = entity.toUiModel()
|
||||
@@ -35,6 +37,8 @@ class ChatMessageMapperTest {
|
||||
assertEquals("search", ui.toolName)
|
||||
assertEquals(ToolStatus.RUNNING, ui.toolStatus)
|
||||
assertTrue(ui.isStreaming)
|
||||
assertEquals(42, ui.tokenCount)
|
||||
assertEquals(55.5, ui.tps ?: 0.0, 0.001)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -49,6 +53,8 @@ class ChatMessageMapperTest {
|
||||
toolName = null,
|
||||
toolStatus = null,
|
||||
isStreaming = false,
|
||||
tokenCount = 100,
|
||||
tps = 24.8,
|
||||
)
|
||||
|
||||
val entity = ui.toEntity("session-b")
|
||||
@@ -62,6 +68,8 @@ class ChatMessageMapperTest {
|
||||
assertNull(entity.toolName)
|
||||
assertNull(entity.toolStatus)
|
||||
assertFalse(entity.isStreaming)
|
||||
assertEquals(100, entity.tokenCount)
|
||||
assertEquals(24.8, entity.tps ?: 0.0, 0.001)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -70,4 +70,19 @@ class HermesDatabaseMigrationsTest {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun migration6to7_executesTokenCountAndTpsColumnAdditionOnSQLiteConnection() =
|
||||
runBlocking {
|
||||
val connection = mockk<SQLiteConnection>(relaxed = true)
|
||||
HermesDatabase.MIGRATION_6_7.migrate(connection)
|
||||
verify(exactly = 1) {
|
||||
connection.execSQL(
|
||||
"ALTER TABLE `chat_messages` ADD COLUMN `token_count` INTEGER",
|
||||
)
|
||||
connection.execSQL(
|
||||
"ALTER TABLE `chat_messages` ADD COLUMN `tps` REAL",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.m57.hermescontrol.ui.chat
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class TokenEstimatorTest {
|
||||
@Test
|
||||
fun estimate_emptyOrNull_returnsZero() {
|
||||
assertEquals(0, TokenEstimator.estimate(null))
|
||||
assertEquals(0, TokenEstimator.estimate(""))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun estimate_asciiText_usesByteDiv4() {
|
||||
// "test" is 4 bytes -> (4 + 3) / 4 = 1 token
|
||||
assertEquals(1, TokenEstimator.estimate("test"))
|
||||
// 8 chars -> (8 + 3) / 4 = 2 tokens
|
||||
assertEquals(2, TokenEstimator.estimate("testtest"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun estimate_cjkText_countsDenseCharacters() {
|
||||
// 3 CJK ideographs -> 3 tokens
|
||||
assertEquals(3, TokenEstimator.estimate("你好世界".substring(0, 3)))
|
||||
// 4 Korean Hangul -> 4 tokens
|
||||
assertEquals(4, TokenEstimator.estimate("안녕하세요".substring(0, 4)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun formatTokenCount_formatsCompactUnits() {
|
||||
assertEquals("42", TokenEstimator.formatTokenCount(42))
|
||||
assertEquals("999", TokenEstimator.formatTokenCount(999))
|
||||
assertEquals("1,500", TokenEstimator.formatTokenCount(1500))
|
||||
assertEquals("12.5k", TokenEstimator.formatTokenCount(12500))
|
||||
assertEquals("1.2M", TokenEstimator.formatTokenCount(1200000))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun formatTps_roundsProperly() {
|
||||
assertEquals("42", TokenEstimator.formatTps(41.6))
|
||||
assertEquals("50", TokenEstimator.formatTps(50.2))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user