ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Operit Token 统计显示单位切换(M/B)的落地实现解析

2026/9/29 6:32:13 拓冰建站 浏览量
Operit Token 统计显示单位切换(M/B)的落地实现解析 AI Agent人工智能大模型AI 应用工具调用本地部署MCP ClientsAgent 记忆【免费下载链接】OperitThe most powerful AI agent and AI chat software on Android/Operit是一款Android上能力最为强大、发展最久的AI Agent项目地址https://gitcode.com/gh_mirrors/op/Operit点击查看免费下载导读本文深入解析 OperitAndroid 端 AI Agent / AI 聊天软件中 Token 统计页的显示单位切换功能在不新增设置项、不破坏统计页整体布局的前提下让页面上的两个大型 Token 数值作为隐藏的 M/B百万/十亿切换入口实现全局统一显示单位并通过 DataStore 持久化用户选择。读完本文你将掌握TokenStatsDisplayUnit枚举、formatTokenCount纯格式化器的完整规则、单位切换的 ViewModel 状态流转与 UI 接入方式以及如何用纯单元测试锁定格式化行为。一、功能背景与设计意图1.1 背景单一紧凑格式化的局限Operit 的 Token 统计模块此前对 Token 数值使用单一自动紧凑格式化器K/M 自适应。当数值达到百万级时阅读与对比尚可但主总览周期总览、生命周期总览与峰值等大数值在百万M口径下位数仍然偏多跨数量级对比不够直观——例如917.4M与1.2B之间的大小关系不如统一到十亿B口径时一目了然。1.2 意图两个大数值即隐藏切换入口设计文档docs/TODO/token_stats_display_unit_20260821/index.md明确给出三条核心设计约束让两个大型 Token 数值充当不可见的 M/B 切换入口即隐藏点击入口不显示按钮、不新增可见行全统计页保持同一显示单位一次切换处处生效不在统计设置Statistics Settings中新增一行可见设置项。这一设计刻意避免了设置页 切换按钮的常规做法将交互收敛到用户最常注视的大数字本身符合界面越少可见控件越好的移动端设计取向。二、核心实现枚举与纯格式化函数2.1 显示单位枚举显示单位模型定义于 TokenStatsDisplayUnit.ktenum class TokenStatsDisplayUnit { MILLIONS, BILLIONS; fun toggled(): TokenStatsDisplayUnit when (this) { MILLIONS - BILLIONS BILLIONS - MILLIONS } }toggled()是纯状态切换函数当前为MILLIONS则返回BILLIONS反之亦然。该函数被 ViewModel 的切换入口与 UI 的无障碍描述文本共同复用保证切换目标在逻辑与文案上永远一致。2.2 纯格式化器 formatTokenCount同一文件中的顶层函数formatTokenCount(value: Long, unit: TokenStatsDisplayUnit)承担全部格式化职责规则分两段fun formatTokenCount(value: Long, unit: TokenStatsDisplayUnit): String { if (value 1_000_000L) { return when { value 1_000L - String.format(Locale.US, %.1fK, value / 1_000.0) else - value.toString() } } return when (unit) { TokenStatsDisplayUnit.MILLIONS - String.format(Locale.US, %.1fM, value / 1_000_000.0) TokenStatsDisplayUnit.BILLIONS - String.format(Locale.US, %.3fB, value / 1_000_000_000.0) } }格式化规则要点数值区间行为示例 1_000原样输出raw880→8801_000 ~ 999_999保留一位小数的 K 紧凑格式8_800→8.8K999_900→999.9K≥ 1_000_000单位 MILLIONS保留一位小数的 M 格式1_000_000→1.0M917_400_000→917.4M≥ 1_000_000单位 BILLIONS保留三位小数的 B 格式917_400_000→0.917B1_000_000_000→1.000B两点值得注意的工程细节单位切换只影响百万级及以上的数值。低于百万K/raw的展示不受unit参数影响这正是设计文档中保留百万量级以下的紧凑 K/raw 格式Preserve compact K/raw formatting for values below the million scale的直接实现——切换单位不会让小数值在 K/B 之间跳变产生噪声。小数位数随量级收紧M 用 1 位小数、B 用 3 位小数保证十亿口径下仍保有两位有效对比精度0.917B比0.9B更能体现真实用量。格式化统一使用Locale.US规避了不同区域设置下小数点符号差异导致的展示不一致问题。三、持久化复用现有统计 DataStore设计文档要求将所选显示单位持久化到现有统计 DataStore。实现位于 TokenStatsPreferences.ktprivate val Context.tokenStatsDataStore: DataStorePreferences by preferencesDataStore(name token_stats_preferences) private val TOKEN_DISPLAY_UNIT stringPreferencesKey(token_display_unit) suspend fun loadTokenDisplayUnit(): TokenStatsDisplayUnit { val stored dataStore.data.first()[TOKEN_DISPLAY_UNIT] return stored?.let { TokenStatsDisplayUnit.valueOf(it) } ?: TokenStatsDisplayUnit.MILLIONS } suspend fun saveTokenDisplayUnit(unit: TokenStatsDisplayUnit) { dataStore.edit { preferences - preferences[TOKEN_DISPLAY_UNIT] unit.name } }要点复用既有 DataStoretoken_stats_preferences与币种target_currency、汇率usd_to_cny_rate、时间范围time_range_start/end、活跃视图模式activity_view_mode等标量统计设置存放在同一个 DataStore 中不新建存储文件文件内注释亦指明结构化用量、分组与定价数据留在 Room标量设置走 DataStore按枚举名持久化以stringPreferencesKey(token_display_unit)存unit.name即MILLIONS/BILLIONS读取时valueOf反序列化默认值为 MILLIONS首次使用或键缺失时回退到百万口径与旧行为保持一致实现平滑升级该类标注为internal仅对统计模块内部暴露存储细节对外不可见。四、状态流转ViewModel 中的切换与加载4.1 UI 状态持有当前单位TokenUsageStatisticsViewModel.kt 中的TokenStatsUiState以默认值MILLIONS持有tokenDisplayUnit字段loadInternal()在每次加载时调用settings.loadTokenDisplayUnit()并在_state.update中写入状态——因此页面每次重建、每次数据刷新都会从 DataStore 恢复用户上次选择的单位。4.2 切换入口乐观更新 异步持久化fun toggleTokenDisplayUnit() { val unit _state.value.tokenDisplayUnit.toggled() _state.update { it.copy(tokenDisplayUnit unit) } viewModelScope.launch(dispatcher) { try { settings.saveTokenDisplayUnit(unit) } catch (e: CancellationException) { throw e } catch (e: Exception) { AppLogger.e(tag, Failed to save token display unit, e) } } }切换采用乐观更新策略先同步更新内存中的 StateFlow 使 UI 立即生效再异步落盘到 DataStore落盘失败仅记录日志不阻断用户操作。CancellationException被显式重新抛出以保持协程取消语义。五、UI 接入两个隐藏入口与全局统一单位5.1 入口一周期总览卡的大数值TokenStatsComponents.kt 中的TokenStatsOverviewCard将 总 Token 大数值38.sp 字体包在clickable(role Role.Button)内Row( modifier Modifier .clickable(role Role.Button, onClick onToggleTokenDisplayUnit) .semantics { contentDescription tokenUnitToggleDescription }, verticalAlignment Alignment.Bottom, ) { /* 大号 Token 数值 Token 后缀 */ }可见样式没有任何按钮痕迹——它只是看起来普通的大数字但点击即触发单位切换。5.2 入口二2×2 核心指标网格的峰值TokenStatsMetricGrid的峰值 TokenpeakTokens数值同样接入切换入口复用同一个onToggleTokenDisplayUnit回调对应屏幕代码 TokenUsageStatisticsScreen.kt 中TokenStatsMetricGrid的tokenUnitToggleDescription与onToggleTokenDisplayUnit参数。两个入口共享同一toggleTokenDisplayUnit回调点击后全页单位同步翻转。5.3 无障碍与多语言描述切换入口虽不可见但对读屏用户完全可感知。屏幕层通过资源文案构造 contentDescriptionval tokenUnitToggleDescription stringResource( R.string.token_stats_unit_toggle_description, stringResource(state.tokenDisplayUnit.labelResource()), stringResource(state.tokenDisplayUnit.toggled().labelResource()), )其中labelResource()将枚举映射为M/B文案toggled()提供切换目标。多语言字符串已在values、values-en、values-es、values-ko、values-pt-rBR、values-ms、values-id、values-ro等多套资源中就位例如中文values/strings.xmlstring nametoken_stats_unit_millionsM/string string nametoken_stats_unit_billionsB/string string nametoken_stats_unit_toggle_description当前 Token 单位为 %1$s点击切换为 %2$s/string5.4 全局统一单位的应用范围tokenDisplayUnit从状态一路下钻到页面所有 Token 数值展示实现一次切换、处处生效周期总览卡总 Token 大数值与面积趋势图TokenStatsAreaChart的formatValue { formatTokenCount(it.toLong(), tokenDisplayUnit) }2×2 核心指标峰值 Token、输出 Token活跃记录三视图日热力图、周图、累计图TokenActivitySection.kt 中按viewMode分发tokenDisplayUnit各图对单日、单点数值统一格式化Token 构成卡缓存读取 / 未缓存输入 / 输出三条构成的数值模型排名与模型详情各模型用量、构成行、配置详情趋势卡片与详情弹窗同口径格式化。设计文档中列举的 Token summaries, cards, charts, activity details, rankings, composition rows, configuration details, and detail dialogs 均已覆盖。六、边界约束什么保持不变设计文档对功能边界有严格约束实现也严格遵守币种、请求次数、百分比不变费用展示继续使用formatLifetimeMoneytargetCurrency口径请求数用formatCount缓存率仍为%.1f%%百分比格式数据库记录与 Room 迁移不变本功能不触碰任何表结构Token 统计的原始事件与聚合数据仍走 RoomTokenStatsQueryService/TokenUsageDao显示单位只是展示层的映射百万以下格式不变K/raw 紧凑格式与单位无关见 2.2 节。这种只改显示口径、不动数据模型的边界设计把功能风险压缩到纯展示层。七、测试验证纯函数覆盖设计文档要求为 M/B 值与单位切换添加纯格式化器覆盖。测试实现于 TokenStatsDisplayUnitTest.kt四个用例精确锁定了 2.2 节的全部规则Test fun million display keeps compact values below one million() { assertEquals(8.8K, formatTokenCount(8_800L, TokenStatsDisplayUnit.MILLIONS)) assertEquals(999.9K, formatTokenCount(999_900L, TokenStatsDisplayUnit.MILLIONS)) } Test fun million display formats large values in millions() { assertEquals(1.0M, formatTokenCount(1_000_000L, TokenStatsDisplayUnit.MILLIONS)) assertEquals(917.4M, formatTokenCount(917_400_000L, TokenStatsDisplayUnit.MILLIONS)) } Test fun billion display formats large values in billions() { assertEquals(0.917B, formatTokenCount(917_400_000L, TokenStatsDisplayUnit.BILLIONS)) assertEquals(1.000B, formatTokenCount(1_000_000_000L, TokenStatsDisplayUnit.BILLIONS)) } Test fun display unit toggles between millions and billions() { assertEquals(TokenStatsDisplayUnit.BILLIONS, TokenStatsDisplayUnit.MILLIONS.toggled()) assertEquals(TokenStatsDisplayUnit.MILLIONS, TokenStatsDisplayUnit.BILLIONS.toggled()) }测试特意覆盖了917_400_000这一边界样例在两种单位下的不同输出917.4Mvs0.917B以及999_900→999.9K的即将破百万阈值确保格式化规则在量级边界处不被破坏。由于formatTokenCount是纯函数无 IO、无状态测试无需任何 Android 环境即可在 JVM 上运行。八、落地流程与交付从设计文档的 Steps 可以看出该功能的完整落地路径模型与存储新增TokenStatsDisplayUnit枚举、DataStore 偏好读写、ViewModel 状态字段与toggleTokenDisplayUnit()已完成UI 接入接通两个隐藏点击入口周期总览大数值、峰值指标并让全页所有 Token 展示统一使用当前单位已完成测试与评审新增纯格式化测试审查最终 diff已完成构建交付推送变更并通过构建器 API 构建 Release APK已完成。该功能已处于完成状态Steps 全部标记 DONE可以作为 Android 端统计模块显示层的一个完整可参考的实现范例。九、小结Operit 的 Token 统计显示单位切换是一个典型的小而精展示层功能用枚举 纯函数收敛格式化逻辑用 DataStore 复用持久化用两个隐藏点击入口替代可见设置项用纯单元测试锁定边界行为。它不触碰任何数据模型与 Room 迁移却让百万级与十亿级 Token 用量的阅读与对比体验得到整体提升——这一实现思路同样适用于其他需要全局单位口径切换的统计类页面。关键源码索引格式化器与单位模型app/src/main/java/com/ai/assistance/operit/data/stats/TokenStatsDisplayUnit.ktDataStore 持久化app/src/main/java/com/ai/assistance/operit/data/stats/TokenStatsPreferences.kt切换与加载逻辑app/src/main/java/com/ai/assistance/operit/ui/features/tokenstats/TokenUsageStatisticsViewModel.kt页面组装app/src/main/java/com/ai/assistance/operit/ui/features/tokenstats/TokenUsageStatisticsScreen.kt卡片与图表接入app/src/main/java/com/ai/assistance/operit/ui/features/tokenstats/TokenStatsComponents.kt活跃记录三视图app/src/main/java/com/ai/assistance/operit/ui/features/tokenstats/TokenActivitySection.kt纯函数测试app/src/test/java/com/ai/assistance/operit/data/stats/TokenStatsDisplayUnitTest.kt多语言文案app/src/main/res/values/strings.xml赞分享AI Agent人工智能大模型AI 应用工具调用本地部署MCP ClientsAgent 记忆【免费下载链接】OperitThe most powerful AI agent and AI chat software on Android/Operit是一款Android上能力最为强大、发展最久的AI Agent项目地址https://gitcode.com/gh_mirrors/op/Operit点击查看免费下载相关推荐Operit 构建系统重构外部制品清单External Artifact Manifest设计与落地指南Operit 构建系统重构外部制品清单External Artifact Manifest设计与落地指南 本文档对应 Operit 仓库构建系统重构计划的AI Agent人工智能大模型AI 应用工具调用本地部署MCP ClientsAgent 记忆GUI 自动化Open Design Modern 设计系统实战指南从 DESIGN.md 到语义 Token 的落地实现Open Design Modern 设计系统实战指南从 DESIGN.md 到语义 Token 的落地实现 导读 本文以 Open Design 仓库中 dAI 应用人工智能AI 技能设计系统媒体生成Megatron-DeepSpeed vs 传统训练框架为什么它是大规模语言模型的首选Megatron DeepSpeed vs 传统训练框架为什么它是大规模语言模型的首选 在人工智能快速发展的今天大规模语言模型LLM的训练面临着计算资上一篇Alibi将你的手机变身终极行车记录仪自动保存关键时刻的最后30分钟下一篇如何在5分钟内快速部署PP-FormulaNet_plus-L_safetensors完整安装与配置教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考