fix 3 bugs: pills with keyboard, token count, chat model

1. Mode pills stay visible when keyboard open (were hidden).
   Spacing reduced (6dp vs 24dp) to keep dock compact.

2. Token counter works for loaded chats — sum inputTokens +
   outputTokens from all StoredMessages when fetching from
   server. StoredMessage now parses model/inputTokens/outputTokens.

3. Model pill shows the chat's actual model (from last assistant
   message) instead of always the global default. chatModel state
   tracks per-conversation model, falls back to session default
   for new chats.
This commit is contained in:
Bruno Deanoz 2026-06-22 01:27:29 +02:00
parent 0db20bfda8
commit 163a2cee28
2 changed files with 25 additions and 18 deletions

View file

@ -108,6 +108,7 @@ fun ChatScreen(
var reasoningPreset by remember { mutableStateOf(ReasoningPreset.Standard) } var reasoningPreset by remember { mutableStateOf(ReasoningPreset.Standard) }
var samplingPrefs by remember { mutableStateOf(SamplingPrefs()) } var samplingPrefs by remember { mutableStateOf(SamplingPrefs()) }
var usedTokens by remember { mutableStateOf(0) } var usedTokens by remember { mutableStateOf(0) }
var chatModel by remember { mutableStateOf<String?>(null) }
// Navigation State // Navigation State
var currentScreen by remember { mutableStateOf(AppScreen.Chat) } var currentScreen by remember { mutableStateOf(AppScreen.Chat) }
@ -225,6 +226,7 @@ fun ChatScreen(
conversationId = null conversationId = null
viewingLockedChat = false viewingLockedChat = false
usedTokens = 0 usedTokens = 0
chatModel = null
} }
fun unlockAndOpen(summary: ConversationSummary) { fun unlockAndOpen(summary: ConversationSummary) {
@ -314,6 +316,9 @@ fun ChatScreen(
if (stored.isNotEmpty()) { if (stored.isNotEmpty()) {
val entities = stored.mapIndexed { i, m -> m.toEntity(summary.id, i) } val entities = stored.mapIndexed { i, m -> m.toEntity(summary.id, i) }
chat.messageRepo.cacheMessages(summary.id, entities) chat.messageRepo.cacheMessages(summary.id, entities)
val lastAssistant = stored.lastOrNull { it.role == "assistant" }
lastAssistant?.model?.let { chatModel = it }
usedTokens = stored.sumOf { (it.inputTokens ?: 0) + (it.outputTokens ?: 0) }
if (stored.size != cachedList.size || stored.zip(cachedList).any { (s, c) -> s.id != c.id || s.content != c.content }) { if (stored.size != cachedList.size || stored.zip(cachedList).any { (s, c) -> s.id != c.id || s.content != c.content }) {
messages.clear() messages.clear()
stored.forEach { m -> stored.forEach { m ->
@ -673,12 +678,13 @@ fun ChatScreen(
} }
} }
Spacer(Modifier.width(8.dp)) Spacer(Modifier.width(8.dp))
val displayModel = chatModel ?: session.config?.model ?: ""
ModelPill( ModelPill(
label = modelDisplayName(session.config?.model ?: "", models), label = modelDisplayName(displayModel, models),
onClick = { haptics.tick(); showModelSheet = true }, onClick = { haptics.tick(); showModelSheet = true },
) )
Spacer(Modifier.weight(1f)) Spacer(Modifier.weight(1f))
val ctxLen = models.find { it.id == session.config?.model }?.context_length ?: 1_000_000 val ctxLen = models.find { it.id == displayModel }?.context_length ?: 1_000_000
TokenBadge(used = usedTokens, contextLength = ctxLen) TokenBadge(used = usedTokens, contextLength = ctxLen)
} }
} }
@ -730,8 +736,7 @@ fun ChatScreen(
) )
.then(if (isTablet) Modifier.widthIn(max = 720.dp) else Modifier) .then(if (isTablet) Modifier.widthIn(max = 720.dp) else Modifier)
) { ) {
if (!imeVisible) { Spacer(Modifier.height(if (imeVisible) 6.dp else 24.dp))
Spacer(Modifier.height(24.dp))
ModePillsRow( ModePillsRow(
activeMode = activeMode, activeMode = activeMode,
onToggle = { mode -> onToggle = { mode ->
@ -743,8 +748,7 @@ fun ChatScreen(
samplingPrefs = samplingPrefs, samplingPrefs = samplingPrefs,
onSamplingChange = { samplingPrefs = it }, onSamplingChange = { samplingPrefs = it },
) )
Spacer(Modifier.height(10.dp)) Spacer(Modifier.height(if (imeVisible) 6.dp else 10.dp))
}
ChatInput( ChatInput(
value = input, value = input,
onValueChange = { input = it }, onValueChange = { input = it },
@ -791,7 +795,7 @@ fun ChatScreen(
models = models, models = models,
selectedModelId = session.config?.model ?: "", selectedModelId = session.config?.model ?: "",
favorites = session.favorites, favorites = session.favorites,
onSelectModel = { session.setModel(it); haptics.tick() }, onSelectModel = { session.setModel(it); chatModel = it; haptics.tick() },
onToggleFavorite = { session.toggleFavorite(it) }, onToggleFavorite = { session.toggleFavorite(it) },
onDismiss = { showModelSheet = false }, onDismiss = { showModelSheet = false },
) )

View file

@ -87,6 +87,9 @@ data class StoredMessage(
val attachments: List<Attachment> = emptyList(), val attachments: List<Attachment> = emptyList(),
val reasoning: String? = null, val reasoning: String? = null,
val sources: List<SearchSource>? = null, val sources: List<SearchSource>? = null,
val model: String? = null,
val inputTokens: Int? = null,
val outputTokens: Int? = null,
) )
@Serializable private data class ConversationDetail(val messages: List<StoredMessage> = emptyList()) @Serializable private data class ConversationDetail(val messages: List<StoredMessage> = emptyList())