xinmo114514/dsh-usage-widget ↗★ 2

dsh-usage-widget

DSH web plugin: 用量统计悬浮窗(token 用量曲线/热力/圆点,窗口与圆点均可拖动;宿主半扫描会话日志并提供 /usage/api/snapshot) 适合需在 DSH 中本地查看 token 用量统计的用户。

Package
dsh-usage-widget
Compatibility
Unverified
Harness peer range
^0.1.0-rc.6
Cordis peer range
^4.0.0-rc.7
Version
0.1.0
License
MIT
Last updated
Aug 13, 2026

Other repositories with this package name

Install

This plugin has no verified bundle, or compatibility checks failed. Read the repository notes first. Read the full README ↗

dsh-usage-widget

DSH(DeepSeek Harness)Web 插件:Token 用量统计悬浮窗

一个常驻页面右上角的悬浮小窗:实时展示你的 DSH 会话消耗了多少 tokens —— 有可拖动的窗口、可拖动的圆点、曲线图、热力图、今日/累计大数字,全部本地聚合、不上传任何数据。


目录


1. 这是什么

dsh-usage-widget 是 DSH 的持久化 Web 插件(dsh.client 双面包:宿主半运行在 Node 进程里,客户端半打包进浏览器)。

  • 为什么是"持久化":它挂在 web profile 的组合配置里,随 dsh web 启动自动加载 —— 不需要每次批准、不随会话/重启消失。对比"动态插件"(会话内临时定义),这是"装一次,永远用"。
  • 它统计什么:所有会话日志中 assistant/message 事件的 data.usage(input/output/cacheRead/cacheWrite/reasoning tokens),按会话 + 按本地日聚合。
  • 数据去哪了:全部留在本机进程内存里,只有浏览器页面从宿主自己的 /usage/api 路由拉取聚合结果。不调用任何第三方 API、不发送任何数据。

2. 功能特性

特性说明
🪟 窗口可拖动整窗任意位置按住拖动(按钮除外);置顶状态下拖动自动取消置顶并跟随指针
🔴 圆点可拖动最小化后变成圆点,圆点本身可自由拖动;点击圆点恢复窗口,拖动圆点只移动位置
📌 置顶一键置顶到右上角固定位置(会话头部下方);再点取消
🏠 最小化一键收起为圆点(圆点实时显示"今日 tokens"小数字 + 迷你走势线)
🔢 总 tokens 大数字窗口左下角常驻显示全部会话全时段累计总量(千分位大数字)
📈 曲线图近 7 天 / 2 周 / 1 月 / 全部,平滑贝塞尔曲线 + 渐变面积 + 悬停明细
🔥 热力图同范围切换热力视图,按日相对强度分级着色
⚡ 实时刷新每 4 秒拉取一次快照;扫描完成前显示"扫描中…"
💾 状态记忆模式(窗口/圆点)、置顶、位置保存在 localStorage,刷新不丢
🌗 深色模式跟随页面 data-theme 自动切换深浅配色
🩺 自愈扫描每 60 秒增量重扫一次,水位去重不重复计数;某会话临时不可读时自动计数并在恢复后清除提示

3. 界面与交互说明

3.1 窗口模式(默认)

┌────────────────────────────┐
│ 用量            [📌] [─]   │  ← 标题栏(也是拖拽区;按钮区不触发拖动)
├────────────────────────────┤
│ 全部会话   [全部会话][当前] │  ← 范围切换(全部会话 / 当前会话)
│ 输入  输出  缓存命中        │  ← 当前范围小计卡片
│ 调用 N 次 · 缓存读 M        │
│ [7天][2周][1月][全部]       │  ← 时间范围 chips
│        [曲线] [热力]        │  ← 视图切换
│   ┌──────────────────┐     │
│   │   曲线/热力图     │     │  ← 悬停显示当日明细 tooltip
│   └──────────────────┘     │
│ 总 tokens                  │
│ 98,690,484     近7天 · 命中 │  ← 左下角大数字(总量) + 右侧筛选摘要
└────────────────────────────┘
  • 拖动:按住窗口任意位置(按钮除外)移动,指针捕获保证拖出窗口也不断。
  • 置顶:窗口右上角 📌;置顶时位置固定右上角;置顶状态下拖动超过 4px 自动取消置顶并跟随指针。
  • 点击/拖动区分:按下后移动 ≤4px 视为点击(按钮正常触发),>4px 视为拖动。

3.2 圆点模式(最小化后)

  • 56px 圆形,中央是今日累计 tokens(万/亿缩写),下方一条迷你走势线。
  • 点击圆点 → 恢复窗口;拖动圆点 → 自由移动位置。
  • 圆点与窗口共享同一个位置状态(pos),互相切换时位置连续。

3.3 左下角"总 tokens"大数字

  • 显示 data.all.usage.total(全部会话、全时段,input+output+cacheRead+cacheWrite,不含 reasoning)。
  • 若个别会话日志暂不可读(例如由更新版本的 harness 写入),大数字照常显示,旁边只出现一个小徽标 "缺 N 会话",鼠标悬停可看具体原因;待这些会话可读后徽标自动消失。

4. 系统架构

┌────────────────────────── DSH 进程(Node) ──────────────────────────┐
│  dsh-usage-widget 宿主半 (lib/index.js)                              │
│  ┌──────────────────────────────────────────────────────────────┐   │
│  │ 聚合存储(内存)                                              │   │
│  │  sessions: sessionId → { daily: Map, allAgg, maxSeq } │   │
│  │  allDaily / allAgg:全部会话按日 / 累计                        │   │
│  └──────────────────────────────────────────────────────────────┘   │
│        ▲ 增量折叠 (session/event)          ▲ 全量扫描(并发4)        │
│        │  maxSeq 水位去重                  │  sessionQuery 优先       │
│        │                                   │  sessionPersistence 兜底 │
│  ┌────────────────────┐        ┌──────────────────────────────┐    │
│  │ ctx.on('session/event') │        │ 每 60s 自愈重扫 + 初始扫描  │    │
│  └────────────────────┘        └──────────────────────────────┘    │
│        │                                                            │
│        ▼  POST /usage/api/snapshot  ←── webServer 前缀路由          │
└──────────────────────────────────────────────────────────────────────┘
                          ▲ HTTP (JSON)
┌──────────────────────── 浏览器页面 ──────────────────────────────────┐
│  dsh-usage-widget 客户端半 (lib/client.js,经 __ModuleLoader__ 加载)  │
│  · 注册到 shell.overlay 槽位(id: uw-usage-widget)                  │
│  · 每 4s fetch 快照 → React 渲染窗口/圆点                            │
└──────────────────────────────────────────────────────────────────────┘

关键设计:

  • 宿主半零运行时第三方依赖:只用 Node 内置模块 + 通过 Cordis inject 注入的服务(webServer / sessionQuery / sessionPersistence / timer),因此随 profile 安装时不需要额外 @deepseek-ai/* 依赖。
  • 客户端半只 require react:其余全部内联进 bundle;通过 window.__ModuleLoader__.load({ id: 'dsh-usage-widget', factory }) 注册,由 DSH 的 client-modules 系统(dsh.client 扫描 + 引导清单注入)加载。
  • 通信:客户端 → 宿主走 HTTP POST /usage/api/snapshot(宿主自己的前缀路由),宿主 → 客户端仅返回 lossless JSON。不使用动态插件的私有 RPC。
  • 样式:CSS 以字符串内嵌,插件 apply 时注入 ``,卸载时自动移除。

5. 数据口径与统计规则

规则说明
计入事件仅 assistant/message 且 data.usage.inputTokens 为 number 的事件(会话标题等系统辅助 LLM 调用不计)
累加字段input=usage.inputTokens、output=usage.outputTokens、cacheRead=usage.cacheReadTokens、cacheWrite=usage.cacheWriteTokens、reasoning=usage.reasoningTokens
total 口径total = input + output + cacheRead + cacheWrite(不含 reasoning;reasoning 仅在会话卡与序列字段中单独体现)
RAW 优先扫描首选直接解析每个会话自己的 session.jsonl.zstd(多帧 zstd,经 zstd CLI 解压),只提取 assistant/message 的 usage —— 不受 harness 解释器的未知事件拒读影响,覆盖 100% 会话(本机审计 32/32 会话可读,与独立逐条审计结果完全一致)
harness 兜底RAW 路径失败时(如日志正在写入的最后帧不完整)回退 sessionQuery.readSession / sessionPersistence.readFrom
按天分桶该事件当天本地时区 0 点的 epoch ms(避免 UTC 偏差)
去重live 事件与扫描共用会话级 maxSeq 水位,`seq ~/Code/dsh-usage-widget
cd ~/Code/dsh-usage-widget
pnpm install
pnpm build # 生成 lib/index.js(宿主)+ lib/client.js(客户端 bundle)

**第 2 步:在 profile 中声明依赖**

编辑 `~/.dsh/profiles/web/package.json`:

```jsonc
{
  "dependencies": {
    // ...已有依赖
    "dsh-usage-widget": "link:/home/xinmo/Code/dsh-usage-widget"  // 或 file:/path
  }
}

第 3 步:挂载到组合(cordis.patch.yml)

编辑 ~/.dsh/profiles/web/cordis.patch.yml:

- insert:
    - id: better-sidebar        # 已有行……
      name: 'dsh-better-sidebar'
    - id: usage-widget          # ← 新增
      name: 'dsh-usage-widget'

第 4 步:安装依赖并重启

cd ~/.dsh/profiles/web
CI=true pnpm install --no-frozen-lockfile   # 更新锁文件并链接新包