






















@@ -74,6 +74,7 @@ import kotlinx.coroutines.withContext
7474import java.text.DateFormat
7575import java.util.Date
7676import java.util.Locale
77+import kotlin.math.roundToInt
77787879/** Full chat surface that wires MainViewModel state to messages, attachments, voice, and composer actions. */
7980@Composable
@@ -95,6 +96,7 @@ fun ChatScreen(
9596val sessions by viewModel.chatSessions.collectAsState()
9697val chatDraft by viewModel.chatDraft.collectAsState()
9798val pendingAssistantAutoSend by viewModel.pendingAssistantAutoSend.collectAsState()
99+val contextUsage = resolveChatContextUsage(sessionKey = sessionKey, mainSessionKey = mainSessionKey, sessions = sessions)
98100val context = LocalContext.current
99101val resolver = context.contentResolver
100102val scope = rememberCoroutineScope()
@@ -196,6 +198,7 @@ fun ChatScreen(
196198 onValueChange = { input = it },
197199 attachments = attachments,
198200 thinkingLevel = thinkingLevel,
201+ contextUsage = contextUsage,
199202 healthOk = healthOk,
200203 pendingRunCount = pendingRunCount,
201204 onThinkingLevelChange = viewModel::setChatThinkingLevel,
@@ -685,6 +688,7 @@ private fun ChatComposer(
685688onValueChange: (String) -> Unit,
686689attachments: List<PendingImageAttachment>,
687690thinkingLevel: String,
691+contextUsage: ChatContextUsage,
688692healthOk: Boolean,
689693pendingRunCount: Int,
690694onThinkingLevelChange: (String) -> Unit,
@@ -699,7 +703,11 @@ private fun ChatComposer(
699703AttachmentStrip(attachments = attachments, onRemoveAttachment = onRemoveAttachment)
700704 }
701705702-ChatContextMeter(thinkingLevel = thinkingLevel, onClick = { onThinkingLevelChange(nextThinkingValue(thinkingLevel)) })
706+ChatContextMeter(
707+ thinkingLevel = thinkingLevel,
708+ contextUsage = contextUsage,
709+ onClick = { onThinkingLevelChange(nextThinkingValue(thinkingLevel)) },
710+ )
703711704712Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) {
705713ChatInputPill(value = value, onValueChange = onValueChange, onPickImages = onPickImages, onVoice = onVoice, modifier = Modifier.weight(1f))
@@ -735,8 +743,10 @@ private fun ChatComposer(
735743@Composable
736744private fun ChatContextMeter(
737745thinkingLevel: String,
746+contextUsage: ChatContextUsage,
738747onClick: () -> Unit,
739748) {
749+val contextFraction = contextMeterWidth(contextUsage) ?: 0f
740750Row(
741751 modifier = Modifier.width(178.dp),
742752 verticalAlignment = Alignment.CenterVertically,
@@ -755,7 +765,13 @@ private fun ChatContextMeter(
755765 horizontalArrangement = Arrangement.spacedBy(6.dp),
756766 ) {
757767Icon(imageVector = Icons.Default.Refresh, contentDescription = null, modifier = Modifier.size(12.dp), tint = ClawTheme.colors.textSubtle)
758-Text(text = "Context ${contextPercent(thinkingLevel)}%", style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textMuted)
768+Text(
769+ text = contextMeterLabel(contextUsage, thinkingLevel),
770+ style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp),
771+ color = ClawTheme.colors.textMuted,
772+ maxLines = 1,
773+ overflow = TextOverflow.Ellipsis,
774+ )
759775 }
760776 }
761777Box(
@@ -768,7 +784,7 @@ private fun ChatContextMeter(
768784Box(
769785 modifier =
770786Modifier
771- .fillMaxWidth(thinkingMeterWidth(thinkingLevel))
787+ .fillMaxWidth(contextFraction)
772788 .height(3.dp)
773789 .background(ClawTheme.colors.primary, RoundedCornerShape(999.dp)),
774790 )
@@ -902,6 +918,32 @@ private fun isActiveSessionChoice(
902918return choiceKey == current
903919}
904920921+internal data class ChatContextUsage(
922+val totalTokens: Long?,
923+val totalTokensFresh: Boolean?,
924+val contextTokens: Long?,
925+)
926+927+internal fun resolveChatContextUsage(
928+sessionKey: String,
929+mainSessionKey: String,
930+sessions: List<ChatSessionEntry>,
931+): ChatContextUsage {
932+val entry =
933+ sessions.firstOrNull {
934+ isActiveSessionChoice(
935+ choiceKey = it.key,
936+ sessionKey = sessionKey,
937+ mainSessionKey = mainSessionKey,
938+ )
939+ }
940+return ChatContextUsage(
941+ totalTokens = entry?.totalTokens,
942+ totalTokensFresh = entry?.totalTokensFresh,
943+ contextTokens = entry?.contextTokens,
944+ )
945+}
946+905947@Composable
906948private fun SendButton(
907949enabled: Boolean,
@@ -958,17 +1000,29 @@ private fun nextThinkingValue(value: String): String =
9581000else -> "off"
9591001 }
9601002961-/** Maps thinking presets to the visual context meter fill fraction. */
962-private fun thinkingMeterWidth(value: String): Float =
1003+internal fun contextMeterWidth(usage: ChatContextUsage): Float? {
1004+if (usage.totalTokensFresh == false) return null
1005+val total = usage.totalTokens?.takeIf { it >= 0L } ?: return null
1006+val context = usage.contextTokens?.takeIf { it > 0L } ?: return null
1007+return (total.toDouble() / context.toDouble()).coerceIn(0.0, 1.0).toFloat()
1008+}
1009+1010+internal fun contextMeterLabel(
1011+usage: ChatContextUsage,
1012+thinkingLevel: String,
1013+): String {
1014+val contextLabel = contextMeterWidth(usage)?.let { "Context ${(it * 100).roundToInt()}%" } ?: "Context --"
1015+return "$contextLabel · ${contextMeterThinkingLabel(thinkingLevel)}"
1016+}
1017+1018+internal fun contextMeterThinkingLabel(value: String): String =
9631019when (value.lowercase(Locale.US)) {
964-"low" -> 0.34f
965-"medium" -> 0.58f
966-"high" -> 0.82f
967-else -> 0.18f
1020+"low" -> "low"
1021+"medium" -> "medium"
1022+"high" -> "high"
1023+else -> "off"
9681024 }
9691025970-private fun contextPercent(value: String): Int = (thinkingMeterWidth(value) * 100).toInt()
971-9721026private fun formatChatTimestamp(timestampMs: Long): String = DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault()).format(Date(timestampMs))
97310279741028/** Quick markdown detector used to avoid routing plain chat text through the markdown renderer. */
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。