dsh-token-chart 0.0.0-stage → 0.1.1-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +202 -2
- package/cordis.patch.yml +7 -0
- package/icon.svg +7 -0
- package/lib/buckets.js +115 -0
- package/lib/chunk/echarts.entry.js +25 -0
- package/lib/client.echarts.js +58 -0
- package/lib/client.js +1743 -0
- package/lib/constants.js +22 -0
- package/lib/index.js +640 -0
- package/lib/paths.js +24 -0
- package/lib/prefs.js +54 -0
- package/lib/store.js +273 -0
- package/lib/token-chart.css +568 -0
- package/lib/types/buckets.d.ts +24 -0
- package/lib/types/chunk/echarts.entry.d.ts +11 -0
- package/lib/types/constants.d.ts +22 -0
- package/lib/types/index.d.ts +8 -0
- package/lib/types/paths.d.ts +3 -0
- package/lib/types/prefs.d.ts +8 -0
- package/lib/types/store.d.ts +72 -0
- package/lib/types/types.d.ts +230 -0
- package/lib/types/update.d.ts +24 -0
- package/lib/types.js +10 -0
- package/lib/update.js +110 -0
- package/locale/en.json +4 -0
- package/locale/zh.json +4 -0
- package/package.json +110 -4
- package/src/buckets.ts +120 -0
- package/src/chunk/echarts.entry.ts +27 -0
- package/src/client/api.ts +126 -0
- package/src/client/charts.tsx +255 -0
- package/src/client/chrome.tsx +127 -0
- package/src/client/contract.ts +62 -0
- package/src/client/drawer.tsx +113 -0
- package/src/client/format.ts +36 -0
- package/src/client/globals.d.ts +21 -0
- package/src/client/index.tsx +227 -0
- package/src/client/locales.ts +164 -0
- package/src/client/models.ts +76 -0
- package/src/client/mount.tsx +312 -0
- package/src/client/panel.tsx +152 -0
- package/src/client/pill.ts +21 -0
- package/src/client/react.ts +12 -0
- package/src/client/series.tsx +140 -0
- package/src/client/settings.tsx +347 -0
- package/src/client/styles/components.less +191 -0
- package/src/client/styles/index.less +7 -0
- package/src/client/styles/tailwind.css +21 -0
- package/src/client/styles/tokens.less +25 -0
- package/src/client/table.tsx +153 -0
- package/src/client/ui.tsx +87 -0
- package/src/constants.ts +26 -0
- package/src/index.ts +673 -0
- package/src/paths.ts +28 -0
- package/src/prefs.ts +61 -0
- package/src/store.ts +295 -0
- package/src/types.ts +216 -0
- package/src/update.ts +106 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 lugezuishuai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,203 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-token-chart
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
一个 **DeepSeek Harness** 插件:把本机会话的 Token 用量采集下来,以图表与统计的形式放回你正在用的界面里——会话抽屉、每日面板、可自由配置的图表形式。交互参考 DSH 产品自带的「用量」,数据口径与它一致(计费口径,含缓存重读)。
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## 🚀 快速开始
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
dsh plugin --profile web add dsh-token-chart
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
> 安装的是发布时预构建好的产物(`lib/` 已构建好),在你的机器上不执行任何构建脚本;用量数据全部留在本机,不上报任何遥测。装好之后怎么用见 [使用指南](#-使用指南)。
|
|
16
|
+
|
|
17
|
+
## 📖 使用指南
|
|
18
|
+
|
|
19
|
+
### 三个入口
|
|
20
|
+
|
|
21
|
+
| 位置 | 入口 | 内容 |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| 左侧边栏 | 「Token 用量」菜单 | 今天 / 近 3 天 / 近 7 天:KPI 卡片 + 趋势图 + 分页表格 |
|
|
24
|
+
| 会话标题栏 | 「用量」小按钮(与 ⋯ 菜单同一行,**常驻**) | 右侧边栏抽屉:总 Token、输入/输出、缓存读取/写入、命中率、轮次、模型 + 逐轮趋势图 |
|
|
25
|
+
| 设置页 | 设置 →「Token 用量」 | 图表形式、展示指标、开关;标题行含插件版本与「更新版本」按钮 |
|
|
26
|
+
|
|
27
|
+
页面上会出现三个「用量」入口,它们彼此独立,各由不同的东西控制:
|
|
28
|
+
|
|
29
|
+
| 你看到的「Token 用量」 | 由什么控制 |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| 消息底部 / 输入框上方的「用量」胶囊 | 产品自身的组件;插件只拦截点击,把它改成打开本抽屉(开关:**拦截产品用量按钮**) |
|
|
32
|
+
| 会话标题栏右上角的小按钮 | 本插件注册在 `conversation.session.header.utilities` 的入口,**常驻显示**(不设开关) |
|
|
33
|
+
| 右侧边栏的标签页标题 | 本插件的 `sidebar.right.pane.tab.title` 座位,**不受**上面那个开关影响 |
|
|
34
|
+
|
|
35
|
+
### 图表形式与交互
|
|
36
|
+
|
|
37
|
+
图表形式有 7 种:柱状、折线、饼图、表格、多柱状、多折线、柱状折线组合,会话抽屉与每日面板可以分别配置。
|
|
38
|
+
|
|
39
|
+
- **缓存命中率叠加**:柱状 / 折线 / 多柱状 / 多折线 / 组合图都可在右轴叠加一条命中率折线(默认开)。
|
|
40
|
+
- **范围选择条**:坐标轴下方的 dataZoom,拖动两端或中间窗口即可只看某一段 x 范围(滚轮与触控板捏合同样生效)。缩放只改变可见窗口,不改统计口径——数据表、KPI 卡片与总量始终是完整区间的值。
|
|
41
|
+
- **分页数据表**:默认每页 20 条,页大小用下拉切换(10 / 20 / 50),页码为数字按钮 + 省略号(`1 … 4 5 [6] 7 8 … 50`),整条分页栏常驻右下角。切换页大小或数据变化会回到第一页。
|
|
42
|
+
|
|
43
|
+
x 轴按视图区分,口径如下:
|
|
44
|
+
|
|
45
|
+
| 视图 | x 轴 | 一个点是什么 |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| 每日面板「今天」 | 时间 `HH:mm`(同分钟多轮自动补到 `HH:mm:ss`) | 一轮的用量 |
|
|
48
|
+
| 每日面板「近 3 天 / 近 7 天」 | 日期 | 一天 |
|
|
49
|
+
| 会话抽屉 | `#轮次` | 会话内的一轮 |
|
|
50
|
+
|
|
51
|
+
### 配置
|
|
52
|
+
|
|
53
|
+
配置在**设置 →「Token 用量」**里改,落盘到 `$DSH_HOME/storages/dsh-token-chart/prefs.json`(默认 `~/.dsh/...`)。
|
|
54
|
+
|
|
55
|
+
| 字段 | 类型 | 默认 | 说明 |
|
|
56
|
+
| --- | --- | --- | --- |
|
|
57
|
+
| `sessionChart` | 枚举 | `multi-bar` | 会话抽屉的图表形式 |
|
|
58
|
+
| `dailyChart` | 枚举 | `multi-bar` | 每日面板的图表形式 |
|
|
59
|
+
| `sessionMetrics` | 数组 | `["input","output","cache","total"]` | 会话抽屉展示的指标 |
|
|
60
|
+
| `dailyMetrics` | 数组 | `["input","output","cache","total"]` | 每日面板展示的指标 |
|
|
61
|
+
| `showTable` | 布尔 | `true` | 图表下方是否渲染数据表 |
|
|
62
|
+
| `showCacheHitRate` | 布尔 | `true` | 组合图是否叠加缓存命中率折线 |
|
|
63
|
+
| `interceptUsageButton` | 布尔 | `true` | 点击产品「用量」胶囊时打开本插件抽屉 |
|
|
64
|
+
|
|
65
|
+
枚举值:`bar` / `line` / `pie` / `table` / `multi-bar` / `multi-line` / `bar-line`;指标值:`input` / `output` / `cache` / `total`。非法值会被 `normalizePrefs()` 丢弃并回退到默认,不会让界面挂掉。
|
|
66
|
+
|
|
67
|
+
### 数据与口径
|
|
68
|
+
|
|
69
|
+
- 数据目录:`$DSH_HOME/storages/dsh-token-chart/`(默认 `~/.dsh/storages/dsh-token-chart/`),`usage.json` 是用量库,`prefs.json` 是界面偏好。
|
|
70
|
+
- **只保留最近 7 个自然日**,7 天前的轮次在每次落盘前裁掉。
|
|
71
|
+
- **幂等采集**:按 `(轮次, 步骤, 重试世代)` 存槽位,重放会话日志不重复计数;存储 schema 不符时丢弃并从日志重建。
|
|
72
|
+
- **会话总量**取会话级折叠,而不是把逐轮数字相加;运行时还会与产品的 `tokenUsage` 投影交叉校验,不一致时抽屉里会显式提示。
|
|
73
|
+
- **口径提醒**:这里的「总 Token」是计费口径——每次请求的上下文(含缓存重读)都计入,所以一个约 1.5M 上下文的会话跑 350 步会得到约 150M;它不是「上下文大小」。
|
|
74
|
+
- **网络**:唯一的出网行为是版本检查(profile 的 `.npmrc` registry,缺省 `https://registry.npmmirror.com/`,宿主侧缓存 30 分钟、超时 12 秒);用量数据只写本机。
|
|
75
|
+
|
|
76
|
+
### 进阶:用 HTTP API 读写
|
|
77
|
+
|
|
78
|
+
宿主半边在 Harness 的 web server 上提供这些路由(`/token-chart/api` 前缀),脚本与外部工具可以直接用:
|
|
79
|
+
|
|
80
|
+
| 方法 | 路径 | 说明 |
|
|
81
|
+
| --- | --- | --- |
|
|
82
|
+
| GET | `/token-chart/api/health` | 采集器状态(保留天数、已跟踪会话数、偏好是否加载、回填是否完成) |
|
|
83
|
+
| GET | `/token-chart/api/prefs` | 读取配置(含默认值) |
|
|
84
|
+
| POST | `/token-chart/api/prefs` | 保存配置补丁 |
|
|
85
|
+
| GET | `/token-chart/api/overview?range=today\|3d\|7d` | 区间统计(按天分桶 + 今天逐轮明细) |
|
|
86
|
+
| GET | `/token-chart/api/session?sessionId=…` | 单会话逐轮用量 |
|
|
87
|
+
| GET | `/token-chart/api/version` | 插件版本、registry 最新版与更新状态 |
|
|
88
|
+
| POST | `/token-chart/api/update` | 触发一次版本更新 |
|
|
89
|
+
| POST | `/token-chart/api/reset` | 清空本地用量库 |
|
|
90
|
+
|
|
91
|
+
改配置(补丁式,只写要改的键):
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
curl -s -X POST http://127.0.0.1:19387/token-chart/api/prefs \
|
|
95
|
+
-H 'content-type: application/json' \
|
|
96
|
+
-d '{"dailyChart":"bar-line","showCacheHitRate":false}'
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## ✨ 特性
|
|
100
|
+
|
|
101
|
+
- **会话抽屉**:单个会话的逐轮用量趋势 + 文字统计,从会话标题栏一键打开。
|
|
102
|
+
- **每日面板**:今天 / 近 3 天 / 近 7 天三个区间,KPI + 趋势图 + 数据表。
|
|
103
|
+
- **7 种图表形式**:柱状、折线、饼图、表格、多柱状、多折线、柱状折线组合,会话与面板可分别配置。
|
|
104
|
+
- **缓存命中率叠加**:柱状 / 折线 / 多柱状 / 多折线 / 组合图都可在右轴叠加命中率折线(默认开)。
|
|
105
|
+
- **范围选择条**:坐标轴下方的 dataZoom,拖动只看某一段 x 范围;缩放不改变统计口径(KPI、总量、表格始终是完整区间)。
|
|
106
|
+
- **分页数据表**:默认 20 条/页,页大小 10 / 20 / 50 下拉切换,页码为数字按钮 + 省略号,常驻右下角。
|
|
107
|
+
- **接管产品「用量」胶囊**:点击改为打开本插件抽屉,而不是产品自带的对话框(可关)。
|
|
108
|
+
- **口径与产品一致**:总 Token 采用计费口径(每次请求的上下文含缓存重读都计入);运行时与产品的 `tokenUsage` 投影交叉校验,不一致会在抽屉里显式提示。
|
|
109
|
+
- **幂等采集**:按 `(轮次, 步骤, 重试世代)` 存槽位,重放会话日志不重复计数;存储 schema 不符时丢弃并从日志重建。
|
|
110
|
+
- **降级可用**:ECharts chunk 拉取失败(离线、被拦、产物损坏)时自动回退到内置自绘 SVG,界面不会空掉。
|
|
111
|
+
- **零运行时依赖**:宿主半边不 import 任何 `@deepseek-ai/*`,浏览器半边只把 react 平台种子留作 external,安装不需要 peer 解析或共享实例授权。
|
|
112
|
+
- **懒加载图表库**:启动只需 ~70 KB 的 `client.js`(gzip 20 KB),首次出图才拉 618 KB 的 ECharts chunk(gzip 234 KB)。
|
|
113
|
+
- **可热更新**:设置页自动查一次新版本(30 分钟缓存),有新版时给出「更新版本」按钮,走 `ctx.pluginManager.installBundle`。
|
|
114
|
+
|
|
115
|
+
## 🛠 本地开发与调试
|
|
116
|
+
|
|
117
|
+
源码全部是 TypeScript(浏览器半边 TSX + JSX automatic runtime);`src/` 是唯一事实来源,**一切产物落在 `lib/` 且不入 git**。
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
git clone https://github.com/lugezuishuai/dsh-token-chart.git && cd dsh-token-chart
|
|
121
|
+
npm install # 触发 prepare:全量构建出 lib/
|
|
122
|
+
npm run typecheck # 两个工程分别类型检查(宿主 + 浏览器)
|
|
123
|
+
npm run build # 清空 lib/ 后全量重建
|
|
124
|
+
npm run check # 发布门禁:typecheck + build + preflight + 宿主用例
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
| 命令 | 作用 |
|
|
128
|
+
| --- | --- |
|
|
129
|
+
| `npm run build:host` / `:styles` / `:client` / `:chunk` | 单独重跑某一段(tsc / tailwind+less / esbuild / esbuild) |
|
|
130
|
+
| `npm run preflight` | 产物与元数据自检:loader id、包名、四件产物、registry、lockfile |
|
|
131
|
+
| `npm run test:host` | 宿主套件:折叠语义、跨天分桶、配置校验、落盘、版本检查 |
|
|
132
|
+
| `npm run test:client` | 浏览器套件:jsdom 加载**真实产物**,通过 HTTP 连**运行中的** Harness |
|
|
133
|
+
| `npm run format` | prettier:`src/**`、`tests/**`、`scripts/**` |
|
|
134
|
+
|
|
135
|
+
调试期建议隔离数据目录,避免污染真实用量库:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
DSH_HOME=$(mktemp -d) npm run test:host # 宿主用例必须用独立 DSH_HOME
|
|
139
|
+
|
|
140
|
+
# 浏览器用例需要运行中的 Harness(默认 http://127.0.0.1:19387)与一个真实会话 id
|
|
141
|
+
DSH_ORIGIN=http://127.0.0.1:19387 DSH_SESSION_ID=<session-id> npm run test:client
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
| 环境变量 | 作用 | 默认 |
|
|
145
|
+
| --- | --- | --- |
|
|
146
|
+
| `DSH_HOME` | 数据根目录(`usage.json` / `prefs.json` 在其下) | `~/.dsh` |
|
|
147
|
+
| `DSH_ORIGIN` | 浏览器用例连接的 Harness 地址 | `http://127.0.0.1:19387` |
|
|
148
|
+
| `DSH_SESSION_ID` | 浏览器用例打开的会话 | 内置一个示例 id |
|
|
149
|
+
| `REACT_DIR` / `JSDOM_DIR` | 用例解析 `react` / `jsdom` 的目录 | npx 缓存目录 |
|
|
150
|
+
|
|
151
|
+
> 浏览器用例会在跑前快照、跑后恢复你的真实偏好;jsdom 没有 canvas 与布局,脚本会补 `HTMLCanvasElement.getContext` 与 `clientWidth` 垫片,浏览器端不需要。
|
|
152
|
+
|
|
153
|
+
让宿主半边也热加载(默认只热替换浏览器半边):把插件目录加进 profile 的 hmr 监听根,改 `src/**` 重建后即时生效,无需重启。
|
|
154
|
+
|
|
155
|
+
```yaml
|
|
156
|
+
# ~/.dsh/profiles/<profile>/cordis.patch.yml
|
|
157
|
+
- id: hmr
|
|
158
|
+
name: '@deepseek-ai/dsh-hmr'
|
|
159
|
+
config:
|
|
160
|
+
root:
|
|
161
|
+
- /path/to/dsh-token-chart
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
> 构建请用你自己的 Node(本机为 `~/.nvm/versions/node/v22.22.0`)。DSH 自带的 Node 在 macOS 上无法 dlopen tailwind 的 `lightningcss` 原生模块,`build:styles` 会失败。
|
|
165
|
+
|
|
166
|
+
## 📦 构建与发布
|
|
167
|
+
|
|
168
|
+
构建产物与 DSH 包契约一一对应,全部落在 `lib/`:
|
|
169
|
+
|
|
170
|
+
| 产物 | 生成方式 | 被谁读 |
|
|
171
|
+
| --- | --- | --- |
|
|
172
|
+
| `lib/index.js` + `lib/*.js` | `tsc`(`package.json#main`) | DSH 宿主进程 |
|
|
173
|
+
| `lib/types/*.d.ts` | `tsc`(`package.json#types`) | 消费方类型检查 |
|
|
174
|
+
| `lib/client.js` | `esbuild` → `__ModuleLoader__` CJS 工厂(**不压缩**,逐行可审阅) | 浏览器(`exports["./client"]`) |
|
|
175
|
+
| `lib/client.echarts.js` | `esbuild` → 压缩、自包含的懒加载 chunk | 浏览器(`require.async`) |
|
|
176
|
+
| `lib/token-chart.css` | `tailwindcss` + `lessc` | 浏览器(宿主路由下发) |
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npm run check # 发布门禁:typecheck + build + preflight + 宿主用例
|
|
180
|
+
npm pack # prepack 自动重建 + preflight,然后打包
|
|
181
|
+
npm version patch # 版本号即 tag,更新检查按版本号比较,两者务必一致
|
|
182
|
+
npm publish # publishConfig 固定发往 registry.npmjs.org
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
发布也可以交给 CI:[.github/workflows/release.yml](.github/workflows/release.yml) 在**推送到 main**(含 PR 合并)与**手动触发**时运行——跑门禁 → 版本还不在 npm 上就自动发布、打 `v<version>` tag、建 GitHub Release;版本已存在则只跑门禁。认证走 **npm Trusted Publishing(OIDC)**,仓库里不存任何 npm token;预发布版本按 preid 走独立 dist-tag(`0.1.1-rc.1` → `rc`),不会顶掉 `latest`。
|
|
186
|
+
|
|
187
|
+
发布流水线要点:
|
|
188
|
+
|
|
189
|
+
1. **`prepack` = 全量构建 + `scripts/preflight.mjs`**,因此 tarball 一定带着四件产物与 `lib/types`;产物不进 git,`files` 白名单决定包内容。
|
|
190
|
+
2. **安装与发布都走 npmjs 源站**:项目 `.npmrc` 与 `package.json#publishConfig.registry` 都指向 `https://registry.npmjs.org/`——镜像是只读的、不接受发布,统一在源站可以让"消费者装到的版本"与"发布出去的版本"始终一致。
|
|
191
|
+
3. **preflight 检查**:包名与 loader id 一致(改名事故的护栏)、四件产物存在、`cordis.patch.yml` 按包名插入、`private` 已移除、`engines.dsh` 已声明、lockfile 不含内网/私有 registry。
|
|
192
|
+
|
|
193
|
+
三种分发方式的产物来源:
|
|
194
|
+
|
|
195
|
+
| 分发方式 | 产物从哪来 |
|
|
196
|
+
| --- | --- |
|
|
197
|
+
| npm / tarball | 发布机上的 `prepack` 预构建,消费者零构建 |
|
|
198
|
+
| git 安装 | 消费者机器上的 `prepare` 现场构建(需先授权构建脚本) |
|
|
199
|
+
| 本地 link | 工作区里的 `lib/`,由你 `npm run build` 产出 |
|
|
200
|
+
|
|
201
|
+
## 📄 License
|
|
202
|
+
|
|
203
|
+
MIT © 2026 lugezuishuai. 详见 [LICENSE](./LICENSE)。
|
package/cordis.patch.yml
ADDED
package/icon.svg
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="Token usage chart">
|
|
2
|
+
<rect x="6" y="34" width="10" height="22" rx="2" fill="#4C6EF5"/>
|
|
3
|
+
<rect x="20" y="22" width="10" height="34" rx="2" fill="#4C6EF5" opacity="0.8"/>
|
|
4
|
+
<rect x="34" y="28" width="10" height="28" rx="2" fill="#4C6EF5" opacity="0.6"/>
|
|
5
|
+
<rect x="48" y="14" width="10" height="42" rx="2" fill="#4C6EF5" opacity="0.45"/>
|
|
6
|
+
<path d="M8 40 L24 26 L38 32 L52 16" fill="none" stroke="#F59F00" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/>
|
|
7
|
+
</svg>
|
package/lib/buckets.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The usage model: provider samples, bucket arithmetic, and the time helpers
|
|
3
|
+
* that turn them into chart-ready rows.
|
|
4
|
+
*
|
|
5
|
+
* @module dsh-token-chart/buckets
|
|
6
|
+
*/
|
|
7
|
+
import { DAY_MS } from './constants.js';
|
|
8
|
+
/** One zeroed bucket set. */
|
|
9
|
+
export function zeroBuckets() {
|
|
10
|
+
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
11
|
+
}
|
|
12
|
+
export function isCount(value) {
|
|
13
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The last `usage` chunk embedded in one durable Assistant settlement stream.
|
|
17
|
+
* @param stream - compact records of the settlement.
|
|
18
|
+
* @returns the provider usage object, or undefined.
|
|
19
|
+
*/
|
|
20
|
+
export function lastStreamUsage(stream) {
|
|
21
|
+
if (!Array.isArray(stream))
|
|
22
|
+
return undefined;
|
|
23
|
+
for (let index = stream.length - 1; index >= 0; index -= 1) {
|
|
24
|
+
const record = stream[index];
|
|
25
|
+
if (record === null || typeof record !== 'object')
|
|
26
|
+
continue;
|
|
27
|
+
if (record.type !== 'chunk')
|
|
28
|
+
continue;
|
|
29
|
+
const chunk = record.chunk;
|
|
30
|
+
if (chunk !== null && typeof chunk === 'object' && chunk.type === 'usage')
|
|
31
|
+
return chunk.usage;
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
/** The usage one durable settlement reports, if any. */
|
|
36
|
+
export function usageOf(event) {
|
|
37
|
+
const data = event.data;
|
|
38
|
+
if (data === null || typeof data !== 'object')
|
|
39
|
+
return undefined;
|
|
40
|
+
if (event.type === 'assistant/message' && data.usage !== undefined)
|
|
41
|
+
return data.usage;
|
|
42
|
+
if (event.type !== 'assistant/message' && event.type !== 'assistant/attempt')
|
|
43
|
+
return undefined;
|
|
44
|
+
return lastStreamUsage(data.stream);
|
|
45
|
+
}
|
|
46
|
+
/** Normalize one provider usage sample into the four billed buckets. */
|
|
47
|
+
export function bucketsFrom(usage) {
|
|
48
|
+
if (usage === null || typeof usage !== 'object')
|
|
49
|
+
return undefined;
|
|
50
|
+
const input = usage.inputTokens;
|
|
51
|
+
const output = usage.outputTokens;
|
|
52
|
+
if (!isCount(input) || !isCount(output))
|
|
53
|
+
return undefined;
|
|
54
|
+
const cacheRead = usage.cacheReadTokens === undefined ? 0 : usage.cacheReadTokens;
|
|
55
|
+
const cacheWrite = usage.cacheWriteTokens === undefined ? 0 : usage.cacheWriteTokens;
|
|
56
|
+
if (!isCount(cacheRead) || !isCount(cacheWrite))
|
|
57
|
+
return undefined;
|
|
58
|
+
return { input, output, cacheRead, cacheWrite };
|
|
59
|
+
}
|
|
60
|
+
export function bucketsEqual(left, right) {
|
|
61
|
+
return (left.input === right.input &&
|
|
62
|
+
left.output === right.output &&
|
|
63
|
+
left.cacheRead === right.cacheRead &&
|
|
64
|
+
left.cacheWrite === right.cacheWrite);
|
|
65
|
+
}
|
|
66
|
+
export function addInto(total, buckets) {
|
|
67
|
+
total.input += buckets.input;
|
|
68
|
+
total.output += buckets.output;
|
|
69
|
+
total.cacheRead += buckets.cacheRead;
|
|
70
|
+
total.cacheWrite += buckets.cacheWrite;
|
|
71
|
+
return total;
|
|
72
|
+
}
|
|
73
|
+
export function subtract(total, buckets) {
|
|
74
|
+
total.input -= buckets.input;
|
|
75
|
+
total.output -= buckets.output;
|
|
76
|
+
total.cacheRead -= buckets.cacheRead;
|
|
77
|
+
total.cacheWrite -= buckets.cacheWrite;
|
|
78
|
+
return total;
|
|
79
|
+
}
|
|
80
|
+
/** Derived figures shared by every response. */
|
|
81
|
+
export function derived(buckets, extra = {}) {
|
|
82
|
+
const { input, output, cacheRead, cacheWrite } = buckets;
|
|
83
|
+
const cache = cacheRead + cacheWrite;
|
|
84
|
+
const billedInput = input + cacheRead + cacheWrite;
|
|
85
|
+
const total = billedInput + output;
|
|
86
|
+
return {
|
|
87
|
+
input,
|
|
88
|
+
output,
|
|
89
|
+
cacheRead,
|
|
90
|
+
cacheWrite,
|
|
91
|
+
cache,
|
|
92
|
+
total,
|
|
93
|
+
cacheHitRate: billedInput > 0 ? cacheRead / billedInput : 0,
|
|
94
|
+
...extra,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/** Local calendar-day key `YYYY-MM-DD`. */
|
|
98
|
+
export function dayKey(time) {
|
|
99
|
+
const date = new Date(time);
|
|
100
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
101
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
102
|
+
return `${date.getFullYear()}-${month}-${day}`;
|
|
103
|
+
}
|
|
104
|
+
/** Epoch ms of local midnight, `offset` days back. */
|
|
105
|
+
export function startOfLocalDay(offset = 0) {
|
|
106
|
+
const date = new Date();
|
|
107
|
+
date.setHours(0, 0, 0, 0);
|
|
108
|
+
return date.getTime() - offset * DAY_MS;
|
|
109
|
+
}
|
|
110
|
+
export function timeLabel(time) {
|
|
111
|
+
const date = new Date(time);
|
|
112
|
+
const hours = String(date.getHours()).padStart(2, '0');
|
|
113
|
+
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
114
|
+
return `${hours}:${minutes}`;
|
|
115
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build entry for the package-local lazy chunk `client.echarts.js`.
|
|
3
|
+
*
|
|
4
|
+
* Only what the token-usage surfaces actually draw is pulled in: bar, line and
|
|
5
|
+
* pie series plus the grid/tooltip/legend/dataZoom components and the SVG
|
|
6
|
+
* renderer.
|
|
7
|
+
* Everything else ECharts ships (maps, 3D, GL, canvas renderer, the full
|
|
8
|
+
* component set) is tree-shaken away by the bundler.
|
|
9
|
+
*/
|
|
10
|
+
import * as echarts from 'echarts/core';
|
|
11
|
+
import { BarChart, LineChart, PieChart } from 'echarts/charts';
|
|
12
|
+
import { GridComponent, TooltipComponent, LegendComponent, DataZoomComponent } from 'echarts/components';
|
|
13
|
+
import { SVGRenderer } from 'echarts/renderers';
|
|
14
|
+
echarts.use([
|
|
15
|
+
BarChart,
|
|
16
|
+
LineChart,
|
|
17
|
+
PieChart,
|
|
18
|
+
GridComponent,
|
|
19
|
+
TooltipComponent,
|
|
20
|
+
LegendComponent,
|
|
21
|
+
// The x-axis range slider under the chart.
|
|
22
|
+
DataZoomComponent,
|
|
23
|
+
SVGRenderer,
|
|
24
|
+
]);
|
|
25
|
+
export default echarts;
|