Skip to content

Releases

0.1.5 — 曲线统一 / 双指标同图 / 侧边栏今日明细

Choose a tag to compare

@HTempt HTempt released this 08 Nov 04:37

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 层。

重构

  • 三张图的图例抽成一个共用组件。 趋势卡、时段页签、今日详情的小时卡各自长了一份
    逐字节相同的 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 最忙、调用最忙各占一项(都是 14:00),隐藏哪条就少一项。
    • hover 时两条可见曲线各出一个圆点,tooltip 只报可见系列。
    • 两条都关时保留基准线、时刻刻度和 24 个命中带,中央显示「两条曲线都已隐藏…」。
    • 明细表不再跟图例切换变序:固定按 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 行过滤 / 日详情工作区分布

Choose a tag to compare

@HTempt HTempt released this 06 Nov 10:30

新增

  • 模型行显示可读的模型名。 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

Choose a tag to compare

@HTempt HTempt released this 06 Nov 07:31

安装

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