dsh-token-heatmap
DSH Web GUI 插件:在新会话(hero)屏幕的输入框下方显示一个 GitHub 风格的 token 用量热力图 —— 可在年视图(当前自然年 1月–12月)与月视图(单月日历,逐日数值)之间切换,颜色深浅表示用量多少;同一行展示今日 / 本月 / 累计 token 用量。所有设置都在卡片自己身上(⚙),不在 DSH 设置里。
A DeepSeek Harness web plugin: a GitHub-style daily token-usage heatmap rendered below the composer input card on the new-session screen only, switchable between a calendar-year grid (Jan–Dec) and a single-month calendar with per-day numbers, with today / this-month / all-time totals on the same line. The card configures itself (⚙) — nothing lives in DSH settings.
界面 / What you get
新会话屏幕输入框正下方出现一张统计卡(只在新会话显示;已对话的会话不显示):
新会话页面上的月视图
月视图 / Month view
月视图
单月日历:7 列(周一起,表头一~日)× 5–6 行,每格显示日号 + 当日 token 数(如 4 44m),底色沿用同一套绝对阈值配色,一眼看出这个月哪几天在烧 token;周末列有浅色底以便区分。标题行的 ‹ 2026年9月 › 按月步进(最新到本月,最早到有数据的第一天)。
年视图 / Year view
年视图
GitHub 风格自然年热力图:覆盖所选自然年 1月–12月(‹ 2026 › 按年步进,最多到当前年),列为周(周一起),行为星期(左侧标注一~日全部 7 天);顶部月份标签按列跨度标注(左侧与格线对齐),今日之后的日期显示为空格。
共同特性 / Shared
- 🔀 年 / 月切换:标题行里的分段按钮即时切换视图(在
‹ › 步进器右边,跟着它一起管当前视图);刷新 与 ⚙ 在行的最右端。
- 🎨 六套配色:绿色(经典 GitHub 风格)、蓝色、橙色、红色、紫色、青色,点卡片上的 ⚙ 切换(见下);颜色按绝对阈值分档(按天 token 数,非相对排名):0 / <1M / 1M–10M / 10M–100M / ≥100M 共 5 级,卡片右下角图例悬停显示各档范围;61M/天 显示为第 3 级。悬停任意格子(年视图的 10px 格子或月视图的日期格)显示日期与精确 token 数。
- 🔢 统计行(与标题同一行):今日 / 本月 / 累计,悬停显示完整数值;本月/累计与当前视图无关,始终是实时值。
- 🔄 自动每 5 分钟刷新,窗口重新可见时也会刷新;行尾可手动刷新。
卡片设置 / In-card settings
⚙ 悬浮设置面板
- ⚙️ 设置就在卡片上:点标题行最右端的 ⚙(
刷新 [⚙])弹出悬浮设置面板 —— 位置在 ⚙ 正上方 8px、水平居中对齐、贴边留 12px,超出视口会自动钳制;点面板外的任意位置、按 Esc、或再点一次 ⚙ 都会收起。插件不再往 DSH 设置(设置 → 插件 → 插件配置)里注册任何卡片,所以那里看不到本插件。
- 配色方案:六个色板按钮,点击即时生效(不需要"保存");默认视图:年 / 月,决定新会话页面首次打开时显示哪个视图(当次会话手动切换只影响当前页面)。面板底部是阈值图例(悬停看各档范围)与一行说明。
- 写入失败时面板底部会红字提示"保存失败,已回到服务端的值"(settings scope 复核后回滚乐观值)。
- 配置作为本插件的 profile 配置持久化:DSH 0.2.0 用插件导出的
Config schema 生成设置表单,表单以 profile entry id(token-heatmap)为键,值写进 profile patch(旧的 <DSH_HOME>/settings.yaml 段与 0.1.1 及更早的 <DSH_HOME>/storages/token-heatmap-config.json 都会在启动时一次性迁移/抢救回配置)。⚠️ 两个字段必须带 .volatile():DSH 只从 schema 的 volatile 子树生成表单,全是普通字段的条目会被 settings.describe() 整个丢弃、settings.update() 则抛 Plugin entry "token-heatmap" has no volatile fields——两种情况下 ⚙ 面板都只会静默回落到默认值(即"设置不生效")。
安装 / Install
需要 web profile 与 pnpm。DSH 兼容版本见下方「兼容性 / Compatibility」;运行于 @deepseek-ai/dsh ^0.2.0-rc.2(0.2.0 版本线;0.1.2 线请用 0.4.2)。
从 npm 安装:
dsh plugin --profile web add @kidli1412/dsh-token-heatmap
从 GitHub 安装:
dsh plugin --profile web add github:KIDLi1412/dsh-token-heatmap
本地开发(手动,本地链接):
dsh plugin --profile web add "link:path/to/dsh-token-heatmap"
安装完成后重启正在运行的 dsh web,并在浏览器中硬刷新(Ctrl+Shift+R)。侧边栏无新增入口——统计卡直接出现在新会话输入框下方。卸载:
dsh plugin --profile web remove @kidli1412/dsh-token-heatmap
工作原理 / How it works
- 服务端(
lib/index.js + lib/usage.js + lib/config.js):作为 profile bundle 挂载,实时折叠会话事件(监听官方 session/event,每个 assistant/chunk/assistant/message 的 usage 事件即时写入缓存,不依赖 hero 屏挂载);启动时一次性补折叠已存在的 live 会话(如 resumed 会话);请求时 collectUsage 再做一次增量同步兜底,并枚举 已归档(stored)会话补齐历史——两种 sessionPersistence 接口都支持:0.1.2 线的 listSnapshots() + readFrom(),以及 0.1.3 起取代它们的 list() + open()/handle.read()。同 (turn, step) 的重复样本按"替换"语义处理,归属后一天;fork(isSeeded)会话从它的继承切点开始折叠——日志开头那批属于父会话的事件在父会话侧已折叠,跳过它们才不会把同一批 token 计两次;按天、按模型聚合,缓存到 <DSH_HOME>/storages/token-heatmap-cache.json(跨进程复用:revision 证明"这份日志没变"就完全跳过、只有追加时才折新尾巴、重写才从切点重建,见 0.5.2 条目)。通过回环受限端点 GET /api/token-heatmap/usage 提供;显示配置(配色 + 默认视图)由导出的 Config schema 持有(0.2.0 的设置模型:以 entry id token-heatmap 为键的表单,值写进 profile patch),GET/POST /api/token-heatmap/config 作为回环兼容 API 读写同一份配置(读经 settings.describe(),写经 settings.update();0.1.x 的 enabled 开关已废弃,该字段只作为常量 true 回给旧客户端),0.1.1 及更早的 token-heatmap-config.json 文档在启动时一次性迁移。
- 客户端(
lib/client.js):手写 __ModuleLoader__ bundle,注册进会话 conversation.input.dock 列表插槽,仅在 session.blank(新会话 hero 屏;旧宿主回退 composerPhase === "blank")时渲染——卡片没有显示开关,hero 屏上始终显示。框架真正的"卡片下方"插槽 conversation.composer.dock 在 hero 屏被 !hero 门控禁用,因此本插件利用 input.dock 容器(flex 列)的 CSS order 把自己排到输入卡片之后。同一份数据由 buildGrid()(年,53 列 × 7 行)与 buildMonthGrid()(月,7 列 × 5–6 行,带日号)两个纯函数分别铺格,共用 levelOf() 的绝对阈值分档与 palette 配色;‹ › 按钮按当前视图步进年或月,边界取"当前年/月"与"数据里最早的月",年/月分段按钮紧跟在步进器后面,⚙ 设置面板在卡片底部展开(内嵌面板,动作:⚙ 切换 / × / Esc / 焦点移出)。不注册 settings.plugin.item(官方"插件配置"页签只渲染"Host 实际 serve 的 namespace ∩ 客户端已注册 key"的卡片,本插件不再占这个位置),配置经 ctx.configForms.get("token-heatmap") 读写本插件配置(0.2.0 的设置模型,取代 0.1.x 的 settings scope)。
- 语义与
dsh-token-meter 的 tokenUsage 投影一致(参考插件 dsh-usage-stats,MIT)。
说明 / Notes
- 仅回环地址可访问数据端点,凭据不外发;插件只读,不修改任何会话数据。
- 无会话/无工作区时(
input.dock 需要会话上下文)统计卡不渲染。
- 服务端与客户端都随
dsh web 启动加载,因此新增/更新插件后需要重启。
兼容性 / Compatibility
-
DSH:manifest 通过 dsh.compatibility.dshReleases 把验证过的版本 0.2.0-rc.2、0.2.1-rc.2、0.2.1-rc.5 都声明为 compatible(DSH STORE 的精确逐版本兼容证据;仅范围声明不会恢复上架)。插件使用的客户端注入(dsh-api-remotes / dsh-client-connection / dsh-client-locale / dsh-client-ui-conversation / dsh-client-ui-settings)、客户端服务(slots / locale / configForms)与 Host 服务(webServer 精确路由、settings.describe / settings.update、sessions、sessionPersistence)在 0.2.0 与 0.2.1(含 rc.5)两条版本线上均已在真实安装中逐项核对(走查见下)。
-
Node:^22.19.0 || >=24.0.0(与 DSH 一致)。
-
宿主要求(dsh-market 显示):engines.dsh: ^0.2.0-rc.2 || ^0.2.1-rc.5,并将运行时依赖的 lockstep 宿主包声明为 peerDependencies(dsh-host-webserver / dsh-session / dsh-session-persistence / dsh-settings 与客户端模块 dsh-api-remotes / dsh-client-connection / dsh-client-locale / dsh-client-ui-conversation / dsh-client-ui-settings,同样各写 ^0.2.0-rc.2 || ^0.2.1-rc.5);插件市场会据此显示"宿主要求"并判断与当前 DSH 是否匹配。第二段范围在语义上是冗余的(^0.2.0-rc.2 本就覆盖整个 0.2.x),但把验证过的版本线显式写出来才是市场和闸门要读的那份声明——0.2.1 起兼容性判定优先只看 engines.dsh,声明了它就完全不再看 peer。这些包由 DSH 运行时提供、本插件从不 import,所以同时标为 peerDependenciesMeta.optional——npm/pnpm 不再把它们装进依赖树(0.2.0 起它们彼此还有 peer 关系,沿用旧的自动安装会直接解析冲突),而"宿主要求"的语义不受影响。
-
依赖:@deepseek-ai/schemastery ^3.18.2(唯一的真实依赖;dsh-settings 自 0.2.0 起不再需要作为 peer 之外的运行时依赖)。
-
0.5.0(DSH 0.2.0 适配):DSH 0.2.0 换了三套契约,宿主兼容闸门会直接拒绝挂载声明不符的 bundle(现象:插件在市场里显示"不兼容"、rows: []、hero 屏上什么都没有)——
- 宿主要求必须落在 0.2.0 线上:闸门逐条比对
peerDependencies 里的 @deepseek-ai/dsh* 与运行时版本(semver.satisfies(..., { includePrerelease: true })),^0.1.2-rc.1 不满足 0.2.0-rc.2,因此本版把 engines.dsh 与 9 个宿主 peer 一起提到 ^0.2.0-rc.2(并把宿主包标为 optional peer,见上)。
- 设置模型重做:0.1.x 的"插件自建 settings namespace + "没了, 也没了。现在,以 (bundle patch 里的 )为键;Host 侧读别人用 ( 已改,否则配置端点会静默回默认值)、写自己用 ,浏览器端用 ( / / ),旧的 已从客户端消失。卡片 ⚙ 面板的 UI 与"点击即时生效"完全不变。
License
MIT。聚合与回环端点实现参考了 dsh-usage-stats(MIT © Ychris12138)。