bowenliang123/dsh-context
bowenliang123/dsh-contextDSH 上下文洞察面板:Context 仪表盘 + /context命令 + Context 浏览器,查看 Context的分类组成、内容详情、演进趋势、压缩/注入事件、统计等一站式 Context 全生命周期管理。
DSH 额度与用量统计统一视图:多供应商滚动/按量额度(侧边栏卡片 + 供应商运维),外加会话日志 token 聚合(趋势、热力图、按模型拆分)。已归档并冻结在固定版本:作者不再为它适配新版 DSH。
已归档(2026-10-20),不再维护。 本插件已从
plugins/移入archive/,源码与lib/构建产物仅作历史参考;安装路径为./archive/token-meter,后续不再适配新版 DSH;文中「更新」节仅作历史保留。
DeepSeek Harness(DSH)Token 总览插件:额度 + 用量二合一——一边看“外面还剩多少”(多供应商滚动额度/按量余额:opencode、DeepSeek、Command Code、手动账本),一边看“里面用了多少”(本机会话日志聚合:累计/峰值/连续天数、日/周趋势、24 小时分布、GitHub 风格热力图、模型分布、今日消耗,以及在线时长——在线 / 对话进行中 / 模型 + 工具三口径 + 每日排行)。额度只有一个入口:左侧边栏底部一枚按钮,上面画什么由该供应商的按钮级模板自由渲染(环 / 余额大数字 / 5 小时·每周·每月三条迷你条 / 供应商图标…,不限于环)——宽栏是「图标 + 主图形 + 名字 + 数值」,56px 窄栏只剩主图形;点开在按钮上方弹出浮层——当前供应商的详情(详情模板产出 + 数据新鲜度)+ 该供应商自己的显示开关(画在它的卡片底部,如 Goat 的「数值位显示余额」+ 5h / 1w / 1m)+ 「切换供应商」列表(每行左侧的图形同样来自该 provider 的按钮模板,点击即切换,一次只激活一个)。其余展示都在会话中心区注册的一个「Token 总览」tab 里(与原生「轨迹」同机制),tab 内用左侧菜单切换 用量统计 / 在线统计 / 设置 三块(设置分区与侧边栏 设置 → Token 总览 是同一个组件,两处改一处同步)。配置按 0.1.7 契约通过官方 settings API 持久化到 profile 条目 dshp-token-meter 的 config:,设置页改动即时热更新。配置只认 dshp-token-meter 一个键:旧双插件的 storages/token-quota.json 与历史 key(dshp-inx-token-quota / dshp-inx-token-stats)不再读取、不再迁移(手工迁移步骤见“常见问题”)。
设计原则:零依赖、零残留、可逆副作用。密钥只存引用(
$NAME)或脱敏展示;统计无网络上报、不额外落盘真相数据(会话日志即持久层;storages/token_stats.json只是可丢的派生缓存,删了会自动凭日志重算);卸载即干净。
git pull,旧版 DSH 可 checkout tag# 1. 克隆 monorepo(lib/ 构建产物已提交,clone 下来就能用,无需 build)
git clone git@github.com:Yinxe/deepseek-harness-plugins.git
cd deepseek-harness-plugins
pnpm install
# 2. 本地安装到 profile(路径按你执行命令时的 cwd 解析)
dsh plugin --profile web add ./archive/token-meter
# 3. 卸载旧双插件(避免双份侧边栏/设置节重复;旧 settings 段需手工并到 dshp-token-meter,
# 本插件不做任何自动迁移)
dsh plugin --profile web remove "@dshp-inx/token-quota"
dsh plugin --profile web remove "@dshp-inx/token-stats"
# 4. 重启生效
dsh web
git pull + dsh web(最快——lib 已提交,未改 src 无需 build);兼容旧版 DSH:按 compat.json 的 tag git checkout <tag> 后重跑上面的 add。DSH 是最新版就到此为止。只有 DSH 停在老版本才需要先
git checkout <历史tag>(对照根目录compat.json,现在是空的,不用管)。
dsh plugin 会把包写进 profile 的 dsh.profile.bundles —— 无需手动改配置文件。
改源码后:src/host 或 src/client 改完跑 pnpm --filter @dshp/token-meter build 重新打出 lib/host.js + lib/client.js,然后 dsh web 重启(client 半强刷页面即可)。
验证:打开 web → 设置 → Token 总览(统计设置 / 额度配置双 Tab);左侧边栏底部出现一枚额度按钮(内容由该供应商的按钮模板渲染:宽栏是「图标 + 主图形 + 名字 + 数值」,56px 窄栏只剩主图形),点开在按钮上方弹出额度浮层(当前供应商详情 + 它卡片底部的显示开关 + 供应商切换);打开任一会话,中心区标题栏出现「Token 总览」Tab(与「对话 / 轨迹」并列),点进去用左侧菜单切换 用量统计 / 在线统计 / 设置(配置项不必再绕去侧边栏,界面效果见下文「界面预览」);或 curl http://127.0.0.1:3080/ext/dshp-token-meter/state 返回 {ok:true, ...}。
一键 AI 安装:把下面这段发给你的 DSH AI 即可:
帮我安装 TokenMeter 插件(monorepo Yinxe/deepseek-harness-plugins,子目录 archive/token-meter,包名 @dshp/token-meter):
1. git clone monorepo 并 pnpm install
2. dsh plugin --profile web add ./archive/token-meter,并卸载旧 @dshp-inx/token-quota 与 @dshp-inx/token-stats
3. dsh web 重启,确认无报错,设置页出现「Token 总览」即成功
4. 打开设置页的 Token 总览,帮我添加一个供应商并刷新一次额度,再告诉我累计 Token 总量和今日用量
latest 滚动版(跟随 main 最新构建;滚动更新 = 重跑同一条 add 命令):
dsh plugin --profile web add \
https://github.com/Yinxe/deepseek-harness-plugins/releases/download/latest/dshp-token-meter-latest.tgz
dsh web # 重启生效
main 永远跟随最新 DSH。老版本 DSH 用户装静态历史版本:按 compat.json 记录的兼容 tag,到 Releases 找对应版本 Release(v* tag 触发,静态存档、永不滚动),资产名 = dshp-token-meter-<tag>.tgz:
# 以 v0.1.5-rc.1 为例
dsh plugin --profile web add \
https://github.com/Yinxe/deepseek-harness-plugins/releases/download/v0.1.5-rc.1/dshp-token-meter-v0.1.5-rc.1.tgz
dsh web # 重启生效
| 安装方式 | 更新命令 | 说明 |
|---|---|---|
| 方式一 clone(推荐) | 仓库内 git pull + dsh web | 最快;未改 src 免 build;旧版 DSH 用 git checkout <tag> |
| 方式二 Release | 重跑同一条 add 命令 + dsh web | URL 直装对 update 免疫;两个 pnpm 坑见根 README |
| 方式三 git 依赖 | dsh plugin --profile web update @dshp/\<pkg\> + dsh web | 一条命令;git 解析约 35s/插件(实测) |
截图在
images/目录(4 张,screenshots.json同步收录):分享面板(封面)/ 用量统计 / 在线统计,外加一张重构前的旧版额度查询界面(quota.png,只作历史对照,不再是现有功能)。

中心区 Tab「Token 总览」—— 一个 tab + 左侧菜单
面板不占右侧栏,而是像原生「轨迹」一样在会话中心区注册一个 tab(conversation.view,order 40,与「对话 / 轨迹」并列);tab 内用左侧菜单切换三块内容:用量统计 / 在线统计,外加一条分隔线之下的 设置(偏好、供应商增删改、数据来源与统计缓存;与侧边栏 设置 → Token 总览 共用同一个组件,改一处两边同步)。额度不在这里——它只有左侧边栏按钮一个入口(见下)。选中项记在 localStorage(tm-center-section),下次打开回到上次看的那块;设置分区沿用设置页的行式表单布局,宽屏下收窄到 920px 居中(数据分区才吃满宽度)。
布局按「宽画布仪表板」设计(不是把右侧栏的窄单列搬过来):
.wSkVaW_viewArea,激活态 flex:1 0 auto; min-height:auto)会让 height:100% 退化成 auto,所以插件用官方稳定钩子把它改成确定高度项:[data-conversation-scroll]:has([data-slot="conversation.view"] .tm-cview) > [data-slot="conversation.session"] > *。:has() 的参数刻意写全以把特异性抬到 (0,4,0),压过外壳那条 (0,3,0) 的激活态规则 —— 不依赖「插件样式晚于外壳注入」这种时序假设。随后 .tm-cmain 内的 .tm-cbody 成为唯一滚动容器(overflow:clip 而非 hidden,滚轮不会被截住)。两个坑:① 左菜单容器必须保持 overflow:visible,一旦 overflow:auto/hidden 它自己就成了滚动容器,里面的 position:sticky 会改为相对它定位、而它永远不滚,于是外层一滚菜单就跟着滚;② 万一钩子失效,会退化成「外层滚动 + 菜单 position:sticky 钉住」的旧模型,仍可用(两条路径都已验证)。.tm-cview 后代且直接挂着 [data-width-handle]」的那层隐藏。grid-auto-rows:1fr 让同一行等高,卡片末行 margin-top:auto 让数值落在同一条网格线上,说明允许两行(不再被省略号切掉)。在线时长固定列数(≥1320 容器 4 列 / 820~1320 两列 / 更窄一列),7 张卡里让「引擎合计」(= 模型生成 + 工具执行)跨两列,最后一行永远是满的;标题行右侧带 准确性徽标图例(精确 = 时间戳直接算出 / 估算 = 由区间推断 / 下界 = 实际只会更多),不用猜颜色含义。width:100%/height:auto」——后者会按 viewBox 比例等比放大:趋势图原来固定 780×250,铺满中心区(约 1166px)时整幅被放到 374px 高,连 10.5px 的轴标签、1px 网格线一起被放大 1.49×,表现就是「图太大、太高、字太大」。改成 1:1 后:高度不再随宽度增长(趋势图 clamp(150, W*0.24, 230),中心区 230px、卡片总高 463 → 319px,字号回到 10.8px)、同样的 30 天拿到更多横向像素。每日在线图同理(原固定 320 宽会在宽屏下等比缩到 320px 居中留白,现在真正铺满并自动加高)。define 在构建期从 package.json 注入(PKG,仓库取 homepage 去掉协议与 #readme),不写死字面量 —— 只注入版本而把包名/仓库写死的话,改包名时就会对不上。不自绘、不改配色:颜色、字号、图形、分档全部由组件自己走 --dsw-alias-* 主题 token 决定。尺寸模型:板宽跟随容器(width:100%)→ 组件按真实像素宽度排版、字号 100%;高度由内容决定,装不下就在舞台内滚动(与视图 .tm-body 的滚动模型一致),长图无所谓,不再为了塞进 16:9 而整体缩放。导出走 DOM → SVG <foreignObject> → Image → canvas → toBlob(零依赖)。这里有三个必须记住的坑:① 必须禁掉动画/过渡(animation:none!important)—— 卡片带入场动画 .tm-card{animation:tm-fadeup .42s backwards},而 @keyframes tm-fadeup{from{opacity:0}},<img> 里的 SVG 是静态图像上下文不会推进 CSS 动画,于是每张卡片都以 opacity:0 被光栅化,导出的图只剩没有动画的页头页脚(「下载/复制出来什么都没有」的真因);② 导出前要等两帧再量尺寸,否则首次导出会拿到还没排定的高度、切掉最后一行;③ 内联 CSS 必须做 XML 转义 —— SVG 是 XML 文档,而压缩后的样式表里带着容器查询的范围语法(@container i1tZMq_tmc (width<=1320px),当前产物里 16 处),文本里一个裸 < 就让整份文档解析失败:下载与复制同时报「SVG 渲染失败」,一张图都拿不到(CSS Module 迁移前内联的是未压缩的 styles.ts 文本,所以那时踩不到)。组装统一走 share-svg.ts 的 shareSvgDocument(),check-share-svg.mjs 拿构建产物里那份真实 CSS 做往返断言:XML 合法,且解码后与原文逐字节相同(只「能加载」不够 —— < 若不解码,(width<=300px) 变成 (width<=300px),容器查询整条被丢弃,图能出来但样式是塌的)。另外导出时要把插件 CSS 与用到的 28 个主题 token 计算值内联进 SVG(SHARE_TOKENS,pnpm test 里有覆盖自检),因为 foreignObject 读不到 DSH 壳层的样式表;输出为板子尺寸的 2×。板子上会隐藏档位/小组件开关(纯 CSS,组件代码一行没改)—— 静态图上的可点控件没有意义。「额度」—— 侧边栏按钮 + 上方浮层
额度不再是中心区的一块面板,而是左侧边栏底部的一枚按钮:注册进官方 sidebar.footer.action(list / root 作用域,owner props 只有 { wide };本插件 id dshp-token-meter,order 10),与 sidebar 自带的两条 footer action 同尺寸(高 42px / 圆角 12px)。footer actions 在侧边栏里是垂直堆叠的(既有规则 div:has(> div[data-slot='sidebar.footer.action']) 把容器改成 flex-direction: column,否则与 Cordis / 手机端徽章并排会横向溢出被裁),所以每个入口独占一行、拉满宽度。
按钮里画什么是该供应商自己的事(ProviderTemplates.button → ReactNode,见下文「provider 两套模板」):可以是一枚环、余额大数字、5 小时 / 每周 / 每月三条迷你条、供应商图标,或任意组合,只要装得进一枚按钮。三档位置由 ctx.variant 告诉模板:
wide,42px 高、拉满宽)= 供应商图标 + 主图形 + 名字 + 右对齐数值(如 ⌘ ◍ Goat …… 92%),名字长了走省略号;rail,56px 轨道)只剩 36×36 圆里那枚表盘(缺省 = 环当外圈 + 供应商图标 15px 居中在环心,见 ButtonLayout),没有可见文字也没有数值位,说明走 tooltip(side: top);放不下全精度金额的 balance 画法统一用 K.tinyMoney() 换成极短金额($46.6 → $47、¥12345 → ¥1.2万),不再各自写一份 compactMoney;row,只留主图形(16px,与那一行的图标槽对齐)。环的推荐尺寸统一由 ringSizeOf(variant) 给(宽栏 / 切换行 16px:与 42px 行高、14px 字号相称,且要与浮层里 16px 的图标槽对齐;窄栏 28px:36px 的圆里「图标 + 小环并排」(早先是 14 + 16 + 3px = 33)会读成两个互不相干的小记号,16px 的弧长也看不出比例,所以让环放大成表盘外圈,28 + 2.2 描边还剩 4px 呼吸)。供应商图标两种画法并存:官方品牌路径直接按官方 viewBox 填充 currentColor(deepseek = 官方鲸鱼 0 0 23.16 17.04,按原宽高比渲染、不硬塞方框),自绘线稿按 16×16 网格 / 描边 1.4 画(commandcode 命令键 ⌘ / opencode 竖向圆角方框 / manual 账本 / 通用兜底),尺寸定为宽栏 16 / 窄栏 15(RAIL_ICON)/ 切换行 16;名字由 Host 元数据 icon 下发,未知名字与未知 type 都有兜底。
「按钮上显示什么」的控件由该供应商自己画(不提供统一的偏好面板 / 单选按钮组):供应商在自己的详情模板里用 ProviderUIKit.PrefSwitch(原生 checkbox)与 PrefChoice(分段单选,role="radiogroup")摆控件,写在 ctx.prefs 这个按供应商分作用域的键值袋里(浏览器本地)。Goat 的卡片底部就是「数值位显示余额」开关 + 5h / 1w / 1m 单选。
点开在按钮上方弹出浮层(useAnchoredPosition side top,portal 到 document.body,宽 344px,max-height: min(70vh, 560px),Esc / 点外部关闭):
┌──────────────────────────────────┐
│ 额度 ⟳ │ ← 面板头:标题 + 刷新(拉取中显示转圈)
├──────────────────────────────────┤
│ ◍ Goat │ ← 卡片头只有 16px 主图形 + 供应商名(不重复类型标签、也不重复按钮上的名字/比例)
│ $12.34 │ ← 当前供应商的**详情模板**产出(余额币种只出现一次)
│ 5 小时 20% │ ← 窗口行右缘对齐:百分比定宽 34px、重置倒计时定宽 62px
│ 每周 55% │
│ 每月 92% │
│ ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ │
│ 数值位显示余额 ☐ │ ← Goat 自己的控件(画在它的详情卡片底部):勾上 → 数值位换成余额
│ 按钮画哪个窗口 [5h][1w][1m] │ ← 图表(环)**一直画**,默认 1m(月额度);这三项写浏览器本地偏好
│ 10 秒钟前 ⟳ │ ← 数据新鲜度 + 刷新
├──────────────────────────────────┤
│ 切换供应商 │
│ ◍ DeepSeek 官方 当前 ✓ │ ← 每行左侧的图形来自该 provider 的按钮模板(`row` 档,16px)
│ ◍ Opencode 官方 拉取正常 │
│ ◍ 不常看的旧账号 已禁用 │
├──────────────────────────────────┤
│ 连接 Host 失败 │ ← 错误提示挪到**页脚**,不再占第一屏第一行
│ 增删改供应商:设置 → TokenMeter │
└──────────────────────────────────┘
.prefBlock / .prefRow):谁有偏好谁负责画,插件不提供统一的「显示设置」面板。activeVendor(Host 只主动拉取「已启用」的供应商);行右侧状态分 拉取正常 / 拉取失败 / 未拉取 / 已禁用,状态点与失败分类同源着色。localStorage 键 tm-quota-prefs 里该供应商那一小袋键值(键名与取值由供应商模板自己定),按钮立刻重画 —— 组件级通信,零往返、不会失败;不进持久化配置、没有 Host 路由(换机器 / 换浏览器就是另一份口味,详见下文「供应商自己的偏好袋」)。上一版的单键 tm-button-metric 已废弃,加载时顺手删掉。ErrorCard:结论 + 处置 + 有序排查步骤 + 原始信息 + 重试),不是一句原始报错;同时按钮上的模板若走失败分支,会画空环 + 满圈警告色(红 = 要人管的、黄 = 临时性的)。分类学见下文「失败提示」。config: 顺序),见下文「供应商排序」。
上图是重构前的旧版额度查询界面(中心区面板 + 供应商卡片网格 + 峰谷横幅),相关显示面已全部删除;现在额度只在侧边栏按钮的上方浮层里。
「用量统计」—— 统计仪表盘
顶部指标卡(模型调用次数 / 首次使用 / 最近使用)+ Token 使用趋势折线(近 24 小时/7 天/30 天,按天聚合,悬浮查看明细)+ Token 活力热力图(6 / 12 个月两档 —— 卡片够宽自动铺 12 个月、窄了自动退回 6 个月,也可手动切换并记住;悬浮查看当日明细:总消耗、会话数、输入/输出构成、缓存读/写与占比)+ 模型用量分布环形图(中心累计 Token,悬浮查看各供应商×模型构成)。

「在线统计」—— 三口径 + 每日在线 + 排行
顶部常驻空闲阈值说明:三条规则(相邻事件间隔 ≤ 阈值则整段计入含中间空档、超过则断开、每段只算到最后一个事件所以任何档位都是下界)+ 五档实测对比(1/5/15/30/60 分钟各自的累计在线与边际增量,直接取自快照,点击即切换,推荐 15 分钟并标注)。下方是 7 张三口径指标卡(今日在线 / 累计在线 / 活跃日均 / 对话进行中 / 模型生成 / 工具执行 / 引擎合计,各带 精确 / 估算 / 下界 徽标)、每日在线柱线图(柱 = 对话中 + 空档,虚线 = 引擎合计,峰值标注)、每日在线排行(柱状图与列表合体,悬浮看当日明细)与「口径与准确性」表。

| 部分 | 内容 |
|---|---|
Host(src/host/ → lib/host.js) | 额度:provider 注册表委托拉取(opencode / deepseek 自动选路 / commandcode / manual,别名 opencode-go/opencode-zen/deepseek-api/deepseek-web 读路径自动归一),每个 provider 同时产出自己的数据与 UI 视图(ProviderView 声明式区块 + icon 图标名,见「provider 两套模板」),密钥 $NAME/{env:}/{cred:} 解析(凭据服务优先、环境变量兜底),明文脱敏(只写不读、留空保留)、secret-to-cred 一键转存、内存快照(st.snaps,失败保旧)。统计:sessionQuery 全量扫描 + 文件指纹缓存(storageDomain 域 token_stats,域名保持不变;记录版本 v=4)+ 在线时长估算(stats/online.ts)+ 后台分批泵(每批 3 会话——批内并发受内存约束、setImmediate 让出、单会话超时 20s 起按次退避至 90s、失败 3 次跳过)+ session/event 失效。通过官方 ctx.settings + schemastery 持久化到 profile 条目 dshp-token-meter 的 config:;配置只认该 NS,不读旧文件/旧 key。 |
Client(src/client/ → lib/client.js) | 「设置 → Token 总览」分成两个 Tab: |
$DSH_HOME/sessions/ 的会话日志重算,迁移/备份必须保留该目录;storages/token_stats.json 可丢)、缓存与扫描状态(复用 / 缓存命中 / 扫描失败 / 直读 / 存储)、清除统计缓存(确认后清空派生缓存,下一次快照全量重算)、默认范围、在线时长空闲阈值;图表全部搬到中心区。失效的侧边栏显示开关已移除。中心区注册一个 tab(官方 conversation.view,order 40,与原生「对话 / 轨迹」并列),tab 内用左侧菜单切换三块:用量统计为完整统计(指标卡、趋势线、热力图、模型环形图 + 顶部的「小组件」工具条);在线统计为每日/累计时长统计:在线(你 + DSH)/ 对话进行中(DSH 的钟)/ 模型 + 工具(DSH 的活,精确)三口径并列,柱线混合图(柱 = 对话中 + 空档,虚线 = 引擎合计)带悬浮数据提示;每日在线排行把柱状图与列表合体(每行既是名次也是一根横向堆叠条,悬浮出当日明细,可展开全部活跃日);另有口径与准确性表(精确/估算/下界徽标,含会话口径分解)。(在线统计面板不支持弹出为浮窗,顶栏抓手已移除。)额度入口是左侧边栏底部的一枚按钮(官方 sidebar.footer.action,不占活动栏图标、也不占右侧栏原生 tab):按钮内容、浮层详情、以及卡片底部那些显示控件都来自该 provider 的两套模板;点开在按钮上方弹出浮层。供应商级余额查询开关:每个供应商可单独「禁用」——禁用后它退出 Host 主动定时拉取,浮层里那一行标注「已禁用」,但手动「拉取」、已有快照展示与切换为当前均不受影响(开关在 设置 → Token 总览 的「额度配置」里)。小组件化:统计图表(指标卡 / 趋势 / 热力 / 模型分布 / 今日消耗)各是一个独立小组件,入口是中心区「用量统计」顶部的「小组件」工具条(逐张开合)。装了 @dshp/widget-kit 时窗口由框架画(含吸附预览、主题色玻璃、动效、刷新后原地恢复),没装时自动退回插件自带浮层——两种承载下原位都会消失,回归 按钮收回原位。详见「小组件」。UI 全部使用 DSH 官方设计 token(dsw-alias-*),与官方设置页风格一致。图标全部自绘、零依赖:导航图标在 src/client/icons.ts,供应商图标(官方鲸鱼路径 / 命令键 ⌘ / 圆角方框 / 账本 / 通用兜底)在 src/client/provider-icons.tsx,数据语义图标在 src/client/glyphs.ts(23 枚:累计=堆叠、缓存=圆柱、峰值=闪电/山、日均=基准线+起伏、中位=中位线、连续=火苗/奖杯、活跃=日历、调用=芯片、首次=旗、最近/在线=时钟、今日=太阳、累计在线=沙漏、对话=气泡、工具=终端、引擎=齿轮、热力=九宫格、模型分布=圆环、每日在线=柱状、排行=榜单、口径=同心靶…)。同一枚图标用两处:1314px 贴在指标标签前 / 卡片标题前,以及放大到 58px、约 8% 透明度摆在各指标卡右下角当水印底纹(被卡片圆角裁切),让每张基础数据卡带一点与数据意境相符的图形而不是纯色块;色调态卡略强(12%)。未知图标名回退成圆点,不会崩也不会空白。圆角只用产品默认值,且不覆盖全局角形:不设私有半径变量层(卡片/浮窗 12px · 按钮/输入 8px · chip 6px · 胶囊 999px · 圆形 50%,与官方组件同一套),也不声明 4px、圆环 50%)属于图形几何,不受主题半径影响。无额外依赖。 |
| 同源路由 | corner-shape —— 主题包已用 *,:before,:after{corner-shape:var(--dsw-corner-shape)} 全局接管,插件再写一遍(哪怕写 round)就会把调主题插件的选择顶掉。图表内部的微圆角(点/条/热力格 1.5GET /ext/dshp-token-meter/state(配置脱敏 + 快照 + provider 元数据)、POST /ext/dshp-token-meter/config(通用偏好补丁)、POST /ext/dshp-token-meter/refresh、POST /ext/dshp-token-meter/set-active、POST /ext/dshp-token-meter/set-refresh、POST /ext/dshp-token-meter/set-enabled(旧客户端兼容,当前界面不再调用)、POST /ext/dshp-token-meter/set-vendor-enabled(单供应商余额查询开关,{ id, enabled })、POST /ext/dshp-token-meter/add-vendor、POST /ext/dshp-token-meter/update-vendor、POST /ext/dshp-token-meter/delete-vendor、POST /ext/dshp-token-meter/secret-to-cred、GET /ext/dshp-token-meter/stats(聚合快照,别名 /data)、POST /ext/dshp-token-meter/clear-cache(清空派生统计缓存 → 下次快照全量重算),均带同源校验(Origin 与 Host 一致或缺失才放行)+ no-store。 || 同源路由 | GET /ext/dshp-token-meter/state(配置脱敏 + 快照 + provider 元数据)、POST /ext/dshp-token-meter/config(通用偏好补丁)、POST /ext/dshp-token-meter/refresh、POST /ext/dshp-token-meter/set-active、POST /ext/dshp-token-meter/set-refresh、POST /ext/dshp-token-meter/set-enabled(旧客户端兼容,当前界面不再调用)、POST /ext/dshp-token-meter/set-vendor-enabled(单供应商余额查询开关,{ id, enabled })、POST /ext/dshp-token-meter/add-vendor、POST /ext/dshp-token-meter/update-vendor、POST /ext/dshp-token-meter/delete-vendor、POST /ext/dshp-token-meter/secret-to-cred、GET /ext/dshp-token-meter/identity(分享卡用的 git 身份:先 ctx.get('shell') 跑 git config --get user.name|user.email,拿不到 shell 服务或命令失败就退化为直接解析 $XDG_CONFIG_HOME/git/config / ~/.gitconfig;可用 DSHP_TOKEN_METER_GIT_NAME / _GIT_EMAIL 覆盖;结果含失败都会缓存)、GET /ext/dshp-token-meter/stats(聚合快照,别名 /data)、POST /ext/dshp-token-meter/clear-cache(清空派生统计缓存 → 下次快照全量重算),均带同源校验(Origin 与 Host 一致或缺失才放行)+ no-store。 |
| 工具 | 无模型工具(本插件为运维/展示型,不注册 tools)。 |
入口:中心区「用量统计」顶部的**「小组件」工具条** —— 五张统计卡(指标卡 / 用量趋势 / 用量热力 / 模型分布 / 今日消耗)各有一个开关按钮,就地开合。本插件不占活动栏图标(一个都不占): 额度入口是侧边栏按钮,统计卡的开关是这块工具条,所以没有「一个图标点开小组件菜单」这回事。
每张统计图表都可以从工具条(或卡片自己的开关)弹出为独立小组件,原位随之隐藏(回归 收回)。
同一份组件、两种承载:
| 条件 | 谁画窗口 | 你得到什么 |
|---|---|---|
装了 @dshp/widget-kit(推荐) | 框架(ctx.widgets.register,trayIcon: false 的自由卡片) | 拖拽 / 八向缩放 / 最小化成胶囊 / 锁定位置 / 八向吸附与主题色预览 / 外观与动效跟随全局设置 / 刷新后位置与开关原地恢复 |
| 没装(或服务形状不符 / 被停用) | 插件自带浮层(widgets.tsx 的 WidgetFloat + portal,坐标存 localStorage: tm-widgets) | 老行为:任意非交互区域按住可拖,原位让位、回归 收回 |
两条硬约束(也是这套设计的由来):
inject:export const inject = ['slots'],宿主用
ctx.inject(['widgets'], …) 挂载。否则用户没装框架时,本插件的额度按钮与设置页会一起消失 ——
小组件只是增强,不是本插件的存在前提。trayIcon: false),所以注册是常驻的:启动时与供应商增删时
由 syncFloatWidgets() 把注册表对齐到当前该有的卡片,否则刷新后「还开着」的卡片长不回来。2026-09 UI 重构后只剩统计族(stats:<kind>),宿主 id 的映射因此变得很直白
(额度已不是可弹出的卡片,峰谷显示器随额度显示面一起删除,供应商 id 的 slug + 哈希编码一并下线):
| 插件内部 id | 宿主 id(@dshp/widget-kit) |
|---|---|
stats:cards | token-meter:stats-cards |
stats:trend | token-meter:stats-trend |
stats:heat | token-meter:stats-heat |
stats:donut | token-meter:stats-donut |
stats:today | token-meter:stats-today |
每张卡的初始尺寸各自适配(不是一套尺寸打天下):热力图是宽而扁的周 × 日网格,给它高度纯属浪费;
模型分布是圆环 + 多列列表,矮了会压成滚动条;今日消耗只有一张卡,天生就小。数值只有一处
(widget-bridge.ts 的 FLOAT_SIZES),改尺寸只改那里:
| 小组件 | 初始 | 可缩到最小 | 依据 |
|---|---|---|---|
指标卡(stats:cards) | 460 × 360 | 300 × 220 | 3 列 minmax(148px) 网格 + 三段(全量 / 可视化 / 构成) |
趋势(stats:trend) | 520 × 320 | 340 × 200 | 折线图:横向空间直接决定可读性 |
热力(stats:heat) | 560 × 230 | 360 × 170 | 周 × 日热力网格宽而扁 |
模型分布(stats:donut) | 460 × 420 | 320 × 320 | 圆环 + 多列模型列表(窄于 480 会塌成一列,底部要留高) |
今日消耗(stats:today) | 320 × 200 | 260 × 150 | 单张卡,天生小 |
宽度是按 styles.module.css 里的容器查询断点(300 / 340 / 420 / 480)挑的:取在断点之上,
内容才是「宽屏那套排版」,而不是把窄屏排版塞进一个宽窗里。
编码/标题/尺寸是纯函数(src/client/widget-bridge.ts),由 scripts/check-widget-bridge.mjs 看守
(7 组:id 合法 / 稳定 / 不撞车、统计族可反解、isFloatId 只认统计族、标题表、尺寸表 5 族);
另有 scripts/check-widget-kit-bridge.mjs 把两个插件的真实产物对接起来跑一遍(无浏览器),
确认「5 张统计卡真的进了框架注册表、没有框架时也照常退化」、0 个带活动栏图标的描述符、
侧边栏按钮真的注册进了 sidebar.footer.action(宽栏 = 图标 + 名字 + 数值,窄栏 = 一枚表盘:环当外圈、供应商图标居中在环心),
以及在供应商自己的卡片里操作控件:断言详情里恰好 1 个 role="radiogroup"(含 5h/1w/1m 三项)与 1 个
input[type=checkbox];prefs.window=5h|weekly|monthly → 按钮分别显示 20%|55%|92%;勾上开关 → 数值位变
$12.34 且环仍在(百分比消失);点选项 / 勾开关都写进 localStorage 的 tm-quota-prefs,重开一份
bundle(≈刷新页面)仍读得到。另有 scripts/check-quota-templates.mjs(9 组)看守按钮模板的通用语义、
偏好袋与配色护栏(占用档位、多窗口取最高、失败环、没有写死色值、偏好袋作用域隔离 / 坏数据不崩 /
可订阅 / 能清孤儿、色调类不许有裸规则)。
readSession.inheritedEventCount,即落盘 seedLength;父会话已计),不重复计数数据源是 sessionQuery 服务实时扫描会话日志:会话日志是唯一真相源,storages/token_stats.json 只是可丢的派生缓存(每会话一行,含 token 桶与时长区间),删了会自动重算;因此备份/迁移请保留 $DSH_HOME/sessions/。设置页提供「清除统计缓存并重算」作为手动兜底。
stats/online.ts)「在线」= 会话日志里有事件、且相邻事件间隔不超过阈值的墙钟时间:
inheritedEventCount 跳过(父会话已计),所以历史对话不会重复计时;