【开源】给 DSH 的 Web GUI 做了个「汽车仪表盘」风格的实时 Token 表

正文

起因

用 DSH 写代码的时候,我总有个焦虑:这一轮到底烧了多少 token?现在解码速度正常吗?这个月离破产还有多远?

DSH 右下角有个小圆环能看上下文占用,但太小了,而且我要的是「一眼扫过去就知道状态」——就像开车时看仪表盘那样,不用思考。

所以我做了这个:三个表盘浮在界面上,想放哪拖哪。


长这样

中间大、两边小,跟汽车仪表盘一个逻辑:

表盘 量程 我在看什么
Token Usage(左) 0 – 300M 累计 token。指针进黄区,说明模型读的比产出的多太多,通常是会话已经长到超出模型舒适区了
tok/s(中,最大) 0 – 500 解码速度。读数忽高忽低,就是后端不稳的信号
Total Cost(右) 0 – $12 估算花费 / 预算。指针到红区,公司破产 :money_with_wings:

每个表盘都有真实刻度(不是百分比进度条)、绿黄红三段预警区、指针、盘内大号数字,盘下小字写细节(In/Out 拆分、会话均值、预算)。量程是我自己定的真实业务阈值——超了就是出事了,所以指针的位置本身就有意义。

另外:面板可拖拽、可缩放、可折叠,位置和尺寸会记住;标题栏那行 binding · 78f8f077 · out 297,179 是诊断信息,写着它正在读哪个会话、走的哪条解析路径。


装一下(两条命令)

dsh plugin --profile web add @stone100010/dsh-token-gauge

没有 pnpm,或者 dsh 不在 PATH(用 npx 启动的 DSH 很常见这种情况)的话,克隆下来用自带脚本:

git clone https://github.com/stone100010/dsh-token-gauge.git
cd dsh-token-gauge
./setup.sh install && ./restart-and-verify.sh

装完重启 dsh web,浏览器 Ctrl+Shift+R 硬刷一次。面板出现在右下角。


几个技术点(同行可能感兴趣)

1. 数据是现成的,一行模型调用都不用加

我一开始以为要做个宿主插件去采集数据,翻了下 DSH 源码发现 @deepseek-ai/dsh-token-meter 已经把 tokenUsage / contextPressure / contextBreakdown 做成 session projection 推给每个客户端了。所以我这插件是纯只读视图:不产生会话事件、不注册工具、不影响模型,最坏情况就是面板渲染失败,少一个面板而已。

2. 挂载点是 shell.overlay

@deepseek-ai/dsh-client-ui-layout 声明了一个 frame 级、点击穿透的 list 座位。贡献进去是加法(新 id 和已有条目并存),而且穿透意味着面板之外的区域完全不挡下面的应用。

3. 客户端插件是运行时加载的,不用重建前端

@deepseek-ai/dsh-client-modules 在启动时扫描 Loader 条目,把每个声明了 dsh.client 的包打成 bundle 走 /plugins 给浏览器。所以只要装进 profile + 重启就行,不需要源码仓库、不需要 pnpm run build。


踩过的坑(血泪)

这几个都是我实际 debug 出来的,写出来给做同类插件的朋友省时间:

① 投影的「线上形状」和「宿主状态形状」不一样

tokenUsage 的 wire view 是 view: state => state.totals——客户端收到的是扁平的四个桶,我却按宿主侧的嵌套形状读了 usage.totals.uncachedInputTokens,结果全部落空、面板显示 0。

更坑的是:我的测试夹具用的也是嵌套形状,所以这个 bug 一路绿灯通过测试,直到我在真实界面上看到 0 / 227 / 0 才发现。教训:夹具必须用真实 wire 形状。

② useSyncExternalStore 会保留上一个快照

切换会话时,它会对新的 face 重新订阅,但仍持有旧快照直到新 face 推送——于是面板会短暂显示上一个会话的数字。修法是让派生的 subscribe/getSnapshot 以投影面的身份为键。

顺带一个决策:解析路径失败时我显示空值而不是冻结的旧数字。冻结的数字比空值更危险,因为它看起来是活的。

③ (26.95).toFixed(1) 在 JS 里是 "26.9"

浮点表示误差。面板上 In 26.9K 和总量 27.84K 对不上,看着像 bug。修法是四舍五入前加几个 ULP 的偏移。同类陷阱还有 1.005 → "1.00"。

④ 手写 ModuleLoader bundle 是可行的

客户端插件要求的其实是一个固定外壳:

window.__ModuleLoader__.load({
  id: "包名",                    // 必须等于包名
  factory: (require) => { /* 内部用 require() 声明外部依赖 */ }
});

所以这个包没有构建步骤、没有依赖,lib/client.js 是手写的。改完代码重启服务即可生效。


测试

node test/harness.mjs   # 61 checks

harness 用忠实的替身跑真实 bundle(ModuleLoader 外壳、带 Object.is 依赖比较的 React hook 调度器、ctx.slots、ctx.sessions),覆盖注册、投影订阅、wire 形状归一化、会话切换、路由回退与重试、拖拽、缩放、边界夹取、持久化、销毁。

CI 跑这个,绿着的。


一个诚实的说明

右盘的金额是估算值。 deepseek-v4.1-flash 不在 DSH 内置的模型 catalog 里,DSH 不为它算成本,也没有成本投影可读。默认单价参考的是 catalog 里 deepseek 系列的条目,所以表盘上标了 est.。

建议你把自己的真实单价填进去(lib/client.js 顶部的 PRICES)——在 $12 这个量程下,单价准不准直接决定这个盘能不能信。


最后

如果你也在用 DSH,欢迎试试,有问题直接回帖或者去 GitHub 开 issue。量程(300M / 500 / $12)和预警区间都是常量,你的使用场景跟我不一样的话告诉我,我可以做成可配置的。

觉得有用的话给个 star :star: 就挺好哈哈。


标签建议:#开源 #DSH #DeepSeek #AI编程 #工具分享

3 个赞

牛啊

1 个赞

你这项目百会话多少词耗啊 :laughing:

1 个赞

仪表盘里面就是真实4.1 Flash开发消耗,哈哈哈哈,玩国模嘛,一定要尽兴~