Repository navigation
Releases
Releases · HTempt/dsh-token-board
0.1.5 — 曲线统一 / 双指标同图 / 侧边栏今日明细
npm: dsh-token-board@0.1.5
dsh plugin --profile web add "dsh-token-board@0.1.5"或下载下方 .tgz 离线安装。
新增
- 侧边栏行的「今日 N 次」变成独立点击区,点它直接弹今日明细。 原来行尾的
「6327万 · 171次」是一整段 CSS::after文本,整行只有「打开面板」一个动作;现在这段
文字拆成两个紧邻的 span(6327万 ·+171次),外观完全不变,只是「171次」那一段
单独可点:点它直接弹当日明细对话框(与日历弹出的同一个DayDialog,按需从 day 路由
读数据),点行内其他任何位置仍是打开使用统计面板。- 两段 span 拼接后与原来的字符串逐字相同(
6327万 · 171次),颜色、字重、6px 间距
全部沿用原规则;实测两版渲染宽度 161.47 / 161.48px、像素墨迹 905 / 905,逐像素一致。
(::after的空内容盒仍会吃掉 6px 外边距,因此挂上 span 后把间距变量归零。) - 点击与 Enter/Space 都阻止冒泡,所以对话框弹出时面板不会在背后切换。
- 今日无数据(或首次读取未落地)时该段不渲染内容,点击无效——不会弹空对话框。
- 数据每次刷新原地更新文字;shell 重建行后由 label 回调重新挂载。
- 若标题节点拒绝子节点,自动退回原来的
::after单段文本(功能降级但不丢数字)。 - 对话框渲染在
document.body的独立 React 根里(面板关闭时页面树不存在),
position:fixed; z-index:1500覆盖全部 shell 层。
- 两段 span 拼接后与原来的字符串逐字相同(
重构
- 三张图的图例抽成一个共用组件。 趋势卡、时段页签、今日详情的小时卡各自长了一份
逐字节相同的legendItem实现(按钮 +aria-pressed+data-active+ 色条):
同一个开关写三遍,改一处就得记得改另外两处,漏一处就出现"两个图例行为不一致"的怪现象。- 新增
MetricLegend(图例即开关)与toggleSeries(统一的状态 reducer)、
SERIES(系列名/颜色/文案的唯一来源)——三张卡现在只传入visible和onToggle。 keys参数负责"只列画了的系列":今日卡在无调用的那天只传["tokens"],
不再需要调用方自己写hasCalls ? item : null。- 状态仍由各卡自己持有(图例在卡片头、图在卡片体,一个所有者才不会互相打架)。
- 实测三张卡状态机一致:单关任一曲线、双关(今日卡出现"都已隐藏"提示且保留基准线
与 24 个命中带)、恢复,行为逐项相同。JS 里已无重复实现。
- 新增
变更
- 今日详情里的逐小时卡片改成双曲线,并补上峰值与合计。 原来是一排 24 根细竖条,
只编码了 Token,调用次数只能靠 hover 一个个读;顶部没有峰值、没有合计,也没有
纵轴参照,只能看出"形状"。现在与「时段」页签同一套语言:- Token 蓝线 + 调用紫线,各按本日自己的最大值缩放(两者单位不同,不共轴),
曲线下保留蓝色渐变,底部有基准线。 - 图例即开关(与趋势卡、时段页签同一个控件):点「Token 消耗」「调用次数」分别
隐藏/显示对应曲线,关闭时转灰。隐藏不重缩放——轴上限由数据算,关掉一条时
另一条逐字符不变(实测两条路径d完全相同)。 - 峰值那一行同时报该小时的 Token 与调用次数(
峰值 14:00 · 2.17万 · 9 次):
按 Token 选出最忙的一小时,紧跟着把它的次数也写出来——读者看到峰值的第一反应
就是"那这小时多少次",不该再去 hover 一次才拿到。两个数都描述这一小时,
不是全天。当天没有任何调用时退回纯 Token 文案(峰值 14:00 · 2.17万)。 - 峰值与两个图例挤在同一行(放不下时自动折行),绘图区压在 80px:
整张卡片实测 148px(上一版 186px),只比旧柱状卡的约 122px 多出一行文字;
弹窗仍无需滚动、无横向溢出。本日合计不在这张卡里重复——对话框顶部的摘要行
已经写了「累计 … · 调用 … 次」,同一弹窗里印两遍只会让人去核对两者是否一致。 - 两条都关时保留基准线、时刻刻度和 24 个命中带,中央显示「两条曲线都已隐藏…」。
- 调用为 0 的那天不画紫线也不列紫图例——贴底的直线是噪声,列一个没画的系列是撒谎。
- hover 从"整格高亮"变成游标 + 每条可见曲线的圆点 + tooltip 只报可见系列;
命中带仍是 24 个整小时竖带,且首次支持键盘(可聚焦,aria-label直接带该小时的
Token 与次数)。 - 刻度从每 6 小时改为每 3 小时(00/03/…/21)。
实测:两条曲线 y 范围都严格等于数据范围(无过冲)、x 不回折、不超出绘图区;
亮色 / 暗色 / 640px / 460px 窄屏均正常,460px 时头部折成两行且无溢出。
- Token 蓝线 + 调用紫线,各按本日自己的最大值缩放(两者单位不同,不共轴),
- 时段图把 Token 和调用合并到一张图,图例分开控制。 原来是「按 Token / 按调用」
二选一的切换开关,两条曲线不能同屏对比;现在与趋势卡同一套双轴语言:Token 蓝线在
左轴、调用紫线在右轴,各按自己的最大值缩放(两个单位本就不同,不能假装共轴)。- 卡片头部的图例变成开关:点「Token 消耗」「调用次数」分别隐藏/显示对应曲线,
关闭时图例转灰。隐藏不重缩放——轴上限始终由数据算,关掉一条时另一条不动。 - 摘要栏随系列走:Token 最忙、调用最忙各占一项(都是 14:00),隐藏哪条就少一项。
- hover 时两条可见曲线各出一个圆点,tooltip 只报可见系列。
- 两条都关时保留基准线、时刻刻度和 24 个命中带,中央显示「两条曲线都已隐藏…」。
- 明细表不再跟图例切换变序:固定按 Token 排序(看板的主指标),Token / 调用两列
并排,共用一行。
- 卡片头部的图例变成开关:点「Token 消耗」「调用次数」分别隐藏/显示对应曲线,
- 时段图的柱状图改成平滑曲线。 24 根柱子换成一条单调三次曲线(与趋势卡同一套
smoothPathOf,保证不会过冲),点画在每个小时的中心而不是柱位边缘——00:00 的点
不贴着 y 轴,23:00 的点不被右缘裁掉。曲线下面积保留同色渐变填充,底部补一条基准线
(柱状图原本免费获得的地板)。hover 从"柱高亮"变成游标 + 线上圆点 + 完整 tooltip,
命中区仍是完整的整小时竖带(24 个 band),键盘可达。
实测:23 段贝塞尔、曲线 y 范围与数据点完全一致(14.00–174.00,无过冲)、x 不回折;
峰值点(14:00)琥珀色圆点标记;亮暗主题、中英文、400px 窄屏均正常。
同一轮顺带优化了时段页签的表格:按用量排序(最忙时段排第一)、空时段默认折叠
(一键展开/收起)、补「日均」列、加表头行——原来 24 行按钟点铺开,前 8 行全是零。 - 消耗趋势图改成平滑曲线。 原来两条线是折线(点与点之间直连),每天的起伏都是一段
生硬的折角;现在用单调三次曲线(Fritsch–Carlson)连接相邻两天,线条连续。
选单调而不是常见的 Catmull-Rom / 二次中点平滑,是因为后两者会过冲:单日尖峰会在
峰顶再鼓出一个包,低谷会被画到零线以下——图上出现数据里没有的值。单调切线在局部极值
处取水平切线,曲线始终夹在相邻两天的数值之间,既不超出峰值也不跌破谷值。
实测 70 天数据:曲线 y 范围 47.66–213.92,数据点 y 范围 47.56–213.92,无过冲,
曲线不回折(x 单调)。面积填充跟随同一条曲线,所以渐变底与描边严丝合缝。
单点/两点/全零这些边界也验证过:单点画成点,两点是一条直连曲线,全零是一条水平线。 - 顺带修掉趋势图的一处对齐偏差。 曲线点用的是左右对称内边距,而图表的左右留白
并不相等(左 44、右 40,因为两侧各有一条数值轴),于是最后一个数据点比绘图区右边缘
短 4px、零值日比底部基准线低 10px——hover 圆点和游标落在那里时对不上线。
现在点、面积、游标、圆点共用同一组内边距,末点精确落在绘图区右边缘(680),
零值日精确落在基准线(204)。 - 趋势图的图例变成开关,可以单独隐藏/显示某条曲线。 卡片右上角的
「Token 消耗」「调用次数」现在是两个按钮(原生<button>+aria-pressed),
点一下隐藏对应曲线,再点恢复;关闭时图例线条转灰、文字降到 tertiary,仍看得出是控件。
隐藏是成套的:曲线、渐变面积、那一侧的纵轴刻度、hover 圆点、tooltip 里对应的一行
一起消失,屏幕上不会留下一个还在报数字的隐藏系列。两条都关掉时图表外框(网格线、
日期、70 个 hover 热区)保留,中间显示「两条曲线都已隐藏,点上面的图例可以重新显示」,
避免卡片塌成空白。
隐藏不改变缩放:纵轴上限始终由数据算,不随显示状态变,所以关掉一条时另一条
不会跳动、不会在鼠标下重画。无障碍上仍报完整的一天:hover 热区的aria-label
始终含 Token 与调用两个数字,隐藏只是视觉偏好。
口径说明(时段统计)
- 小时按浏览器本地时区切,与日历、趋势的日界线一致。DST 逐 bucket 解析:实测
2026 年美国秋季回拨,60 个 UTC bucket 全部计数一次、无重复,重复的 01:00 只算
1 个活跃日;春季跳变时被跳过的 02:00 正确为空。 - 只统计带时间戳的会话记录。 插件自己的
statistics.json里history.days按天
归档、没有小时字段,那部分历史不计入时段图,也不按任何规则摊到 24 个钟点——
摊出来的位置是编的。页签底部写明「本图覆盖窗口内 X% 的消耗(覆盖量 / 窗口总量)」,
实测本机为 94%(146亿 / 155亿)。 renderHourOfDay与renderDays对同一份数据算出的总额一致(实测 420 = 420),
小时聚合没有漏记或重复计入。- 日均按「有记录天数」均分:某钟点用了 30 天、另一钟点只用了 2 天,总量不可比,
日均才是「这是不是我最贵的时段」的答案。
0.1.4 — 可读模型名 / 0 行过滤 / 日详情工作区分布
新增
- 模型行显示可读的模型名。
qoder/qodercn两个渠道的目录键是短码
(dfmodel、qfmodel、gfmodel……),日详情里原来直接显示短码,读不出是哪个模型。
现在显示真名(dfmodel→DeepSeek-Flash、qfmodel→Qwen3.8-Flash、
gfmodel→GLM-5.3-Flash等),鼠标悬停仍是原始路由 id。 - 同一张表也用在同一页的 分布 · 按模型:那里保留提供商前缀
(qodercn/DeepSeek-Flash),因为该列表混合了多个 provider,去掉前缀就分不清来源;
环形图和图例同样用它,鼠标悬停仍是原始键qodercn/dfmodel。 - 新增
lib/model-names.js:短码 → 模型名的显示表。按提供商分开,因为同一个
短码在两个渠道可能指不同模型(mmodel:国际版 MiniMax-M3 / 中国版 MiniMax-M2.7;
q37fmodel、gm51model只在 CN 版;smodel、cmodel、ultimate、
performance、efficient只在国际版)。表的内容对齐渠道包
dsh-our-free-model/vendor/channel-pack/pack.js的两个模型目录
(QODER_FALLBACK_MODELS/QODER_CN_FALLBACK_MODELS)。 - 0 消耗 + 0 调用的行不再显示。 一条路由的请求全部失败时,它的 token 与调用都是 0,
只有 attempts 有值,日详情里就出现一张0 / 0的提供商卡片(例如一直在失败重试的
qoder/dfmodel)。现在日详情卡片与「提供商 / 模型」分布列表都跳过这类行;
接口数据里仍保留该行,所以失败统计与 attempts 不受影响。整天没有任何有效用量时,
日详情显示「这一天没有消耗记录」而不是一张空列表。 - README「口径」补了两条:可读模型名与 0 行的显示规则。
- 日详情新增「工作区分布」。 对话框底部按项目列出当天花了多少:工作区名(目录末段)、
token、占当天的比例、调用次数,最多列 6 条,其余收成「另有 N 个未列出」。
它是一张与上面提供商完全同款的卡片:同样的底色、边框、圆角(10px)、内边距
(10/12)、标题字号字重与计数胶囊(2 个模型↔3 个工作区)。行内版式与模型行
也完全一致——同一条 140px 占比条、同样的数字列宽(token 64 / 占比 46 / 调用 44)、
同样的字号与灰度,所以「某天各来源」和「某天各项目」读起来是同一张表。
标题里不重复 token 合计:对话框顶部已经有当天总量,这里再说一遍只是同一数字的第三次出场;
卡片标题下也不画汇总条——那张卡片没有「本卡片占全天多少」这个读者要看的量。 - 工作区的行块也带上模型行那条左侧竖线:两处行块现在都用
.udw_modelList,卡片标题
贴在卡片内边距上、行缩进 10px 挂在一条 2px 发丝线下——「提供商卡片」和「工作区卡片」
因此是同一套结构(实测标题同在 x=191,行同在 x=203)。
早期历史(外部 ledger 导入的日子)没有cwd,这些天只写「这一天的用量来自历史归档,
没有工作区记录」,不猜测归属。数据来自宿主在每日行上新加的byWorkspace字段
(summary 与/day两个接口都带)。 - 日详情顶部加了一行「资格说明」:缓存命中率、
失败 4 / 共 2,154 次尝试、重试次数。
用原始比例而不是成功率:成功率四舍五入后会出现「100% · 失败 4」这种自相矛盾的组合。
只在真有失败 / 重试时才出现,干净的天下这一行只有缓存命中率。 - 提供商卡片补上失败数:
输入 · 输出 · 缓存读 · 失败 2,只在该来源确有失败时追加,
用来解释顶部那一行失败是谁造成的。
未变(有意保留)
- 行身份、
provider/model行键、排序、ledger 合并、占比、缓存命中率全部按原始 id,
没有因为改名而改变任何数字。 - 表里没有的 id(渠道新增的模型、
workbuddy/trae等其它 provider)原样显示,
不会因为缺映射而从面板消失。 - provider 分组标题仍是原始 id(
qodercn、qoder各一张卡片),本次不改。
内部
lib/usage.js:renderRows()给模型行附加name字段(仅在有映射时写入)。
这是纯增量的线上字段:没有name的来源(如旧的预览 fixture)客户端仍按原始 id 显示。lib/client.js:日详情的模型行、分布 · 按模型的列表/图例改读name;
分布页的窗口聚合(客户端按天求和的那段windowModels)原来只复制model/tokens/calls,
会把name丢掉,已改为一起带上。排名组件ModelSection也同步改了,
但它目前没有挂到任何页签上(改动只是防止它被重新接入时又退回短码)。lib/client.js:新增hasFigure()谓词(tokens > 0 || calls > 0),日详情分组与两个
分布列表共用;分组是遍历模型行时先过滤,所以一个 provider 的模型全被过滤掉时,
整张提供商卡片一起消失,不用额外判空。scripts/unit.mjs:新增 7 组断言(按 provider 解析、大小写、未知码回退、
原型键不解析、模型行带name、provider 行不带、未映射行无name)。package.json→files[]增补lib/model-names.js与CHANGELOG.md
(漏了lib/model-names.js会发布出一个 import 失败的空包)。lib/index.js:renderWorkspaces()现在可传dayRows(Map<date, rows>),一次遍历
同时产出窗口排行和「某天 × 某工作区」行,避免同一个工作区在两个视图里算出不同数字;
日行在summaryPayload/dayPayload里挂上byWorkspace(空数组 = 该天不可归因)。lib/client.js:日详情新增dayQualityOf()资格行与工作区卡片;卡片直接用
article.udw_providerCard(所以在结构上不可能与提供商卡片漂移),标题行用
.udw_providerTop/.udw_providerName、计数胶囊用.udw_providerModelCount、
说明文字用.udw_providerMeta,行网格直接用.udw_modelList本身——模型行与工作区行
完全同构(含左侧 2px 竖线与 10px 缩进),原来的.udw_dayWsList因此删除,
工作区行自动继承那条竖线,不需要第二套规则。
区块只在byWorkspace是数组时渲染:字段缺失(宿主半未更新)与「空数组」(真不可归因)
是两件事,前者不显示该区块,后者显示说明文字。scripts/smoke-host.mjs:新增 3 条断言——每天都有byWorkspace数组、工作区行合计
不超过当天 token(归档差额不归因)、同一天内按 token 降序。
验证记录(2026-11-06)
node scripts/unit.mjs → usage.js unit: 15 passed, 0 failed
node scripts/check-client.mjs → OK(id / patch name / exports / seats 全部匹配)
node scripts/smoke-host.mjs → OK: payload is self-consistent(558 会话真实数据)
真实会话复算 2026-11-06 → qodercn/dfmodel → "DeepSeek-Flash",
300,108,383 tokens / 599 calls,与面板当日一致
headless Chrome + 真实数据预览 → 日详情只剩 4 张卡片(qodercn / workbuddy / trae /
lobsterai),qoder 的 0/0 卡片消失;qodercn 行显示
"DeepSeek-Flash"(title 仍是 "dfmodel");分布·按模型
显示 "qodercn/DeepSeek-Flash"
2026-11-04 日详情(渲染实测) → 资格行 "缓存命中 12% · 失败 1 / 共 1,587 次尝试";
trae 卡片 meta 带 "失败 1";
「工作区分布 [3 个工作区]」:.dsh 3.88亿 80% 873、
小说创作 6163万 13% 396、ZY 3800万 7.8% 317;
工作区卡与上方 provider 卡实测同 x/同宽/同底色/同圆角
(x=178、w=722、radius 10px、padding 10px 12px),
行内数字列逐列同宽:bar 140 / token 64 / 占比 46 / 调用 44;
两处卡片标题实测同在 x=191、两处行块实测同在 x=203,
行块 borderLeft 2px solid + paddingLeft 10px 完全一致
2026-08-07(仅归档的日期) → 「工作区分布」显示"这一天的用量来自历史归档,
没有工作区记录",不出空白条
v0.1.3
安装
dsh plugin --profile web add "dsh-token-board@0.1.3"或下载本页下方的 dsh-token-board-0.1.3.tgz 后按路径安装:
dsh plugin --profile web add "/path/to/dsh-token-board-0.1.3.tgz"本版内容
- 新增概览页配图(
docs/images/overview.png),随包一起分发 - README 补充「从 Release 下载安装」与内容核对方法
- 版本号、锁定版本示例统一到 0.1.3
校验
tar -tzf dsh-token-board-0.1.3.tgz # 15 个文件内容与 npm 上的 dsh-token-board@0.1.3 完全一致。
完整变更:v0.1.2...v0.1.3