# dsh-ui-web

> 为 DSH Web 提供彩色用量看板：自动记录每次响应的 token 用量，按会话/天/模型聚合并展示 14 天趋势、模型分布、会话排行与按 DeepSeek 定价的费用估算，数据落地本地 ~/.dsh/usage.json。

## Metadata

- Author: [@CAPTAIN1275](https://github.com/CAPTAIN1275)
- Repo: <https://github.com/CAPTAIN1275/dsh-ui-web.git>
- GitHub: [CAPTAIN1275/dsh-ui-web](https://github.com/CAPTAIN1275/dsh-ui-web)
- Stars: 34
- Language: TypeScript
- License: [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)
- Topics: `dsh-plugin`, `dsh-plugin-market`, `dsh-plugins`
- Forks: 2
- Open Issues: 0
- Last push: 2026-08-16T18:08:27.000Z
- Added: 2026-08-20T00:00:00.000Z

## Install

```bash
dsh plugin --profile web add github:CAPTAIN1275/dsh-ui-web/packages/dsh-usage-dashboard
```

## Wiki

## 一句话定位
为 DSH Web 提供一个彩色用量看板，自动记录每次模型响应的 token 用量并落地到 `~/.dsh/usage.json`，按会话、按天、按模型聚合展示 14 天趋势柱状图、模型分布环形图与会话排行 Top 20，并按 DeepSeek 官方定价估算费用。

## 核心能力
- 自动采集 token 用量：在会话底栏挂一个不可见的录制席位，监听 token 用量投影流式增长，2 秒静默后把会话累计快照上报给宿主（不会把刷新前已存在的历史当成新增算入）
- 彩色统计看板：点击侧边栏入口弹出全屏浮层，渲染四张渐变卡片（累计 token / 调用次数 / 缓存命中 / 估算费用）并按 14 天、模型分布、会话排行三个维度展开
- 费用估算：按模型名匹配 DeepSeek 公开档位（v4-flash、v4-pro、reasoner、legacy），缓存命中单独走缓存档单价，避免双计费
- 会话快照替换语义：同会话在 host 侧只保留最新快照，多次上报以"增量"形式累加到天/模型/总计，刷新与切换会话都不会重复入账
- 局域网二维码入口：侧边栏"手机端查看"按钮调宿主接口枚举本机 IPv4，按 192.168 / 10 / 172.x 优先级排序，结合当前端口渲染二维码与可复制链接
- 检查更新入口：复用同一玻璃样式弹窗，调用宿主 /api/web-ui/version 显示当前与最新版本号
- 设置面板信息卡：在 Web UI 插件组的设置页挂一张纯说明卡片（无配置字段），介绍看板的记录位置与数据落点

## 技术实现
- **语言**: TypeScript（peer 依赖 React 18.2，`dsh.client.platform="web"`）
- **关键依赖**: `@deepseek-ai/dsh-host-webserver`（路由宿主）、`@deepseek-ai/dsh-token-meter`（token 用量投影类型）、`@deepseek-ai/dsh-client-connection`（拉取会话模型）、`qrcode-generator`（手机端查看二维码）
- **架构模式**: Cordis 双向插件。Host 半体向 `webServer` 注入前缀路由 `/api/usage/{record,lan,summary}`，把聚合数据写回 `~/.dsh/usage.json`；Client 半体在 `conversation.composer.dock` 槽挂不可见录制席位、用 DOM 注入的方式在 sidebar 插入三枚按钮，并在 `web-ui.plugin.item` 槽挂说明卡片
- **入口文件**: 宿主侧 `src/index.ts`（导出 `name`/`USAGE_API_PREFIX`/`apply` 及聚合纯函数），客户端 `src/client/index.ts`；费用档位集中在 `src/cost.ts`，UI 渲染集中在 `src/client/DashboardPanel.tsx` 与 `src/client/UsageEntry.tsx`

## 适用场景
当你想知道"过去两周我都用了多少 token、哪些模型最费钱、哪几个会话跑得最猛"时，这个插件把所有数据按可视化图表呈现，本地存档、不上传服务端。适合对调用成本敏感的重度用户、需要在团队内对账的开发者，以及对模型速率/调用分布做长期回看的运维/PM。

## 前置依赖与兼容性
| 依赖 | 最低版本 | 说明 |
|---|---|---|
| DSH Web | 0.1.0-rc.6 | peerDependencies 钉在 `^0.1.0-rc.6`（含 `@deepseek-ai/dsh-client-connection`、`@deepseek-ai/dsh-host-webserver`），cordis patch `ui-usage-dashboard` 需被 host 解析 |
| React | 18.2.0 | 客户端卡片、看板浮层、侧边栏 DOM 注入均依赖 React 18 |
| 平台 | 跨平台（macOS / Windows / Linux） | 运行时不限操作系统；看板只渲染在 Web 宿主，持久化文件走 host 侧 `~/.dsh` 目录 |
| 原生模块 | 无 | 全部逻辑在 TypeScript / React 内完成，二维码用纯浏览器 `qrcode-generator` 实现，无 node-gyp 依赖 |
| Node | 未声明 | 包内无 `engines` 字段，但运行时需要 Node 内置 `node:fs`、`node:os`、`node:http` 模块 |

## 安装方式
```bash
dsh plugin --profile web add github:CAPTAIN1275/dsh-ui-web/packages/dsh-usage-dashboard
```

安装完成后**重启 `dsh web`**，宿主路由 `/api/usage/{record,lan,summary}` 才会被注册；侧边栏会自动多出"用量 / 手机端查看 / 检查更新"三枚按钮（与任务看板同源 DOM 注入模式，遇到 React 重渲染会自愈）。

可选：通过 `DSH_HOME` 环境变量改写持久化目录（例如 `export DSH_HOME=/path/to/dir`），数据将落到 `$DSH_HOME/usage.json` 而非默认 `~/.dsh/usage.json`。

## 配置项
本插件无需额外配置。三个行为参数均在源码中固化：

| 行为 | 取值 | 来源 |
|---|---|---|
| 录制静默阈值（每轮响应结束判定） | 2 秒 | `src/client/UsageRecorder.tsx:36`（`SETTLE_MS = 2000`） |
| 模型轮询间隔（拉取当前会话模型名） | 5 秒 | `src/client/index.ts:95`（`window.setInterval(..., 5000)`） |
| 看板回看天数 | 14 天 | `src/index.ts:304`（`recentDays(store, 14)`） |

设置页 "Web UI 插件" 组下挂的卡片为纯说明，不暴露任何开关或输入字段。

## 常见问题

**Q: 安装后需要重启 DSH Web 吗？**

A: 需要。宿主半体通过 `ctx.inject(['webServer'])` 注册路由，host 进程不重启就不会调起 `apply`；浏览器侧硬刷新只会重新挂录制席位与侧边栏 DOM，无法让 `/api/usage/*` 路由上线。

**Q: 数据存在哪里？怎么清理？**

A: 默认落在 `~/.dsh/usage.json`。可通过 `DSH_HOME` 环境变量改写目录（`process.env.DSH_HOME ?? join(homedir(), '.dsh')`，`src/index.ts:86`）。删除该文件即完全清空历史；插件不会自动迁移或备份，下次重启会重新从空文件开始累计。

**Q: 估算费用是按什么单价算的？准确吗？**

A: 按模型名匹配 DeepSeek 官方公开档位：`flash` → v4-flash、`reasoner` / `r1` → 推理档、`pro` / `v4-pro` → v4-pro、`deepseek` → 旧标准档，其余回退通用档（`src/cost.ts:39-46`）。属于看板展示用估算，并非计费依据；缓存命中单独走缓存档单价，输入 token 不包含缓存部分，不会与缓存档重复计算。

**Q: 计费会不会把一次会话的 token 重复算多次？**

A: 不会。客户端用会话累计快照替换语义上报：录制组件首次挂载只记录基准线不上报（`src/client/UsageRecorder.tsx:106-110`），后续只在 token 用量"真正增长"且 2 秒静默后才上报一次（`flush` 函数）。宿主对同会话只保留最新快照，按"新值减旧值"的增量写入天/模型/总计桶；遇到投影归零（例如刷新会话）会用 `Math.max(0, ...)` 钳住不减（`src/index.ts:148-151`）。

**Q: 看板里"今日"对应哪个时区？**

A: 本地时区。`dayKey` 用 `Date.getFullYear/getMonth/getDate` 拼出 `YYYY-MM-DD`（`src/index.ts:90-95`），所以跨过本地午夜 0 点之后才会出现新一天的柱状图；服务器时区与浏览器时区都不会影响聚合。

**Q: 卸载后需要做什么吗？**

A: 卸载插件并重启 `dsh web` 即可。宿主侧 `dispose` 会注销 `/api/usage/*` 路由，客户端 `mountUsageEntry` 返回的 disposer 会移除侧边栏三枚按钮并停止 MutationObserver。历史 JSON 文件不会被自动删除，留在原位置方便回看或人工备份。

**Q: 侧边栏的"手机端查看"是怎么工作的？**

A: 点击后调 `/api/usage/lan` 让宿主枚举本机所有非内部 IPv4，按"192.168 → 10. → 172.16-31 → 其他"四档优先级排序（`src/index.ts:274-287`），结合浏览器当前端口渲染二维码与可复制链接，方便手机在同一 Wi-Fi 下扫码访问。需要浏览器允许 `navigator.clipboard.writeText` 才能复制（不安全上下文会静默失败）。

**Q: 支持 TUI、桌面端或移动端吗？**

A: 暂不支持。客户端半体的 UI 全部基于 DSH Web 的 sidebar shell 与 composer dock 槽位，TUI、桌面端、移动端没有等价挂载位置；宿主侧 `/api/usage/*` 路由在非 Web 形态下也无 UI 渲染，但 host 侧持久化逻辑本身是平台无关的，仍会写 `~/.dsh/usage.json`。

## 上手难度
入门 — 安装一行命令、重启 DSH Web 即可看到效果，所有行为参数均固化无需调整；只有想改持久化目录或对账单价时需要碰环境变量。

## 已知问题与限制
- 零配置但不可调：录制静默阈值、模型轮询间隔、看板回看天数均硬编码（2s / 5s / 14 天），未提供设置项；需要调整必须改源码后自构建
- 单步录制：录制组件只看当前会话的 `tokenUsage` 投影累计，不会区分同一会话里的多次往返；多会话并行时各自独立互不串扰，但同一会话切换模型后旧模型用量仍归在旧模型名上
- 会话标题回填：录制组件只在首次增长时把 `currentTitle` 当作会话标题写进快照（`src/client/UsageRecorder.tsx:118-124`），会话在客户端改名后下一轮响应才会刷新看板上的标题
- 估算单价滞后：费用档位表在 2026-08 由源码硬编码（`src/cost.ts:18-27`），DeepSeek 调价后需要改 `cost.ts` 并重新构建，看板上不会自动拉取官方最新价
- 二维码复制权限：手机端查看的"复制地址"按钮走 `navigator.clipboard.writeText`，浏览器在非 https / 非 localhost 下可能静默失败，但二维码和地址文本仍可见
- 模型名"unknown"：若宿主侧 `/api/usage/record` 上报的 `model` 字段缺失，规范化为字符串 `unknown` 并归入 `byModel.unknown` 桶（`src/index.ts:244`），不会让整条记录被丢弃

---

This document is auto-generated by [deepseek-plugin.org](https://deepseek-plugin.org). HTML page: [dsh-ui-web](https://deepseek-plugin.org/plugins/CAPTAIN1275/dsh-ui-web/packages/dsh-usage-dashboard)
Wiki generated by AI (model: `MiniMax-M3`)
