dsh-billing-badge 0.1.1 → 0.1.2
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/CHANGELOG.md +29 -0
- package/README.md +8 -48
- package/README.zh.md +108 -0
- package/lib/client.js +63 -14
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,35 @@ All notable changes to this project are documented in this file.
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this
|
|
6
6
|
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.2] - 2026-09-18
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- The chip follows the statistics reading into the composer dock on DeepSeek Harness
|
|
13
|
+
0.1.6, where the labelled statistics row was replaced by a row of pill buttons and the
|
|
14
|
+
old anchor no longer existed. Both shapes are supported: the labelled row stays first
|
|
15
|
+
in the resolution order, the dock row is the fallback.
|
|
16
|
+
- The panel no longer closes on every scroll or resize. The composer scrolls on its own
|
|
17
|
+
while a turn renders, so a click could be dismissed a moment after it opened. The panel
|
|
18
|
+
now follows the chip and closes only once the chip itself is out of sight.
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- `README.zh.md`, a full Chinese README. The catalog card renders a per-language README
|
|
23
|
+
and its language sniff classified the mixed-language file as Chinese only, which the
|
|
24
|
+
card then printed. The main README is English again, with a language link.
|
|
25
|
+
- `screenshots.json`, the catalog convention for author-curated card screenshots, with
|
|
26
|
+
the panel and the pill in its row. Both the catalog detail page and the market's
|
|
27
|
+
storefront read it from this repository, so a screenshot no longer waits on a
|
|
28
|
+
maintainer.
|
|
29
|
+
|
|
30
|
+
### Removed
|
|
31
|
+
|
|
32
|
+
- The panel note saying that prices are published in CNY per million tokens while the
|
|
33
|
+
balance keeps the currency the API reports. The plugin shows no prices, so the sentence
|
|
34
|
+
explained nothing, and on an account reporting USD it read as if the account's own
|
|
35
|
+
numbers were CNY. The empty note no longer reserves its spacing.
|
|
36
|
+
|
|
8
37
|
## [0.1.1] - 2026-09-13
|
|
9
38
|
|
|
10
39
|
### Changed
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# dsh-billing-badge
|
|
2
2
|
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
3
5
|
[](https://www.npmjs.com/package/dsh-billing-badge)
|
|
4
6
|
[](LICENSE)
|
|
5
7
|
|
|
@@ -11,6 +13,8 @@ opens a small panel with the full picture.
|
|
|
11
13
|
2288M tok · Cache hit 99.8% · ● Off-peak · 2h13m
|
|
12
14
|
```
|
|
13
15
|
|
|
16
|
+

|
|
17
|
+
|
|
14
18
|
## What it shows
|
|
15
19
|
|
|
16
20
|
| Where | What |
|
|
@@ -77,6 +81,10 @@ dsh plugin --profile web add github:devacc8/dsh-billing-badge
|
|
|
77
81
|
then restart `dsh web`. The package declares `dsh.bundle.patch`, so the host half is
|
|
78
82
|
reconciled into the profile's bundle list automatically.
|
|
79
83
|
|
|
84
|
+
Works on DeepSeek Harness 0.1.5 and 0.1.6. Older builds render the composer statistics
|
|
85
|
+
as one labelled row and the chip joins it; 0.1.6 moved those readings into the composer
|
|
86
|
+
dock as pill buttons, and the chip follows the cache-hit pill into that row.
|
|
87
|
+
|
|
80
88
|
Working on the plugin itself, install the checkout by path instead:
|
|
81
89
|
|
|
82
90
|
```sh
|
|
@@ -129,52 +137,4 @@ test/ season, sync, host and bundle tests
|
|
|
129
137
|
|
|
130
138
|
GitHub Actions runs `npm test` and `npm run check` on Node 20 and 22.
|
|
131
139
|
|
|
132
|
-
## 中文说明
|
|
133
|
-
|
|
134
|
-
DeepSeek Harness 网页界面的计费时段与账户余额插件。它在输入框下方的统计行里、原生 **Cache hit** 之后加一个小胶囊,点击后展开一个小面板。
|
|
135
|
-
|
|
136
|
-
```
|
|
137
|
-
2288M tok · Cache hit 99.8% · ● Off-peak · 2h13m
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
- **胶囊**:一个圆点(高峰为琥珀色,非高峰为绿色)、当前时段,以及距离切换的倒计时。
|
|
141
|
-
- **面板**(点击展开):计费时段、下次切换时间与北京时间、当前北京时间、账户余额及其货币、赠送额度与充值额度的拆分,以及刷新按钮。
|
|
142
|
-
|
|
143
|
-
计费时段采用官方公布的规则:高峰为北京时间周一至周五 09:00-12:00 与 14:00-18:00,其余时间(含整个周六与周日)均为非高峰,价格为半价。
|
|
144
|
-
|
|
145
|
-
余额来自官方 `GET /user/balance` 接口,其中三个数字含义不同:
|
|
146
|
-
|
|
147
|
-
```
|
|
148
|
-
total_balance = granted_balance + topped_up_balance
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
- `total_balance`(Account balance):可用总额。
|
|
152
|
-
- `granted_balance`(Granted):官方赠送的额度,接口只返回尚未过期的部分,过期的赠送额度会自动从这一行消失。
|
|
153
|
-
- `topped_up_balance`(Topped up):你自己充值的金额。
|
|
154
|
-
|
|
155
|
-
前两行数值相等时,说明账户没有赠送额度。`is_available` 是响应顶层的字段,回答一个问题:余额是否足够调用接口。只有接口报告余额不足时,面板才会加一行警告;该字段对任何有余额的账户都是 true,因此平时不显示。货币一律取自接口返回值,不做假设:返回 USD 的账户不会被标上人民币符号。
|
|
156
|
-
|
|
157
|
-
### 安装
|
|
158
|
-
|
|
159
|
-
从 npm 安装:
|
|
160
|
-
|
|
161
|
-
```sh
|
|
162
|
-
dsh plugin --profile web add dsh-billing-badge
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
或直接从仓库安装:
|
|
166
|
-
|
|
167
|
-
```sh
|
|
168
|
-
dsh plugin --profile web add github:devacc8/dsh-billing-badge
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
然后重启 `dsh web`。包内声明了 `dsh.bundle.patch`,宿主部分会自动写入 profile 的 bundle 列表。
|
|
172
|
-
|
|
173
|
-
### 安全
|
|
174
|
-
|
|
175
|
-
- API key 只在宿主进程中通过 DSH credentials 接口读取(`ctx.credentials.resolve('DEEPSEEK_API_KEY')`,环境变量兜底),不会进入浏览器;
|
|
176
|
-
- 唯一的路由要求请求头 `x-dsh-billing-badge: 1`,并拒绝跨站 `Origin`;
|
|
177
|
-
- 不写任何文件,除 `api.deepseek.com` 外不访问其他地址;
|
|
178
|
-
- 缺少 key、HTTP 错误或网络故障都会降级为面板可显示的状态,不会抛异常。
|
|
179
|
-
|
|
180
140
|
MIT.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
[English](README.md) | 中文
|
|
2
|
+
|
|
3
|
+
# dsh-billing-badge
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/dsh-billing-badge)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
DeepSeek Harness 网页界面的计费时段与账户余额插件。它在输入框的统计行里、原生 **Cache hit** 读数之后加一个小胶囊,点击后展开一个小面板,把完整信息一次说清。
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
2288M tok · Cache hit 99.8% · ● Off-peak · 2h13m
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
## 显示内容
|
|
17
|
+
|
|
18
|
+
| 位置 | 内容 |
|
|
19
|
+
|---|---|
|
|
20
|
+
| 胶囊 | 一个圆点(高峰为琥珀色,非高峰为绿色)、当前时段,以及距离切换的倒计时 |
|
|
21
|
+
| 面板(点击展开) | 计费时段、下次切换时间与北京时间、当前北京时间、账户余额及其货币、赠送与充值的拆分,以及刷新按钮 |
|
|
22
|
+
|
|
23
|
+
计费时段采用官方公布的规则:高峰为北京时间周一至周五 09:00-12:00 与 14:00-18:00,其余时间(含整个周六与周日)均为非高峰,价格为半价。
|
|
24
|
+
|
|
25
|
+
## 余额
|
|
26
|
+
|
|
27
|
+
三个数字来自官方 `GET /user/balance` 接口,含义各不相同:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
total_balance = granted_balance + topped_up_balance
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- `total_balance`,界面显示为 **Account balance**,是你可以动用的全部金额。
|
|
34
|
+
- `granted_balance`,界面显示为 **Granted**,是官方赠送的额度。接口只返回尚未过期的部分,过期的赠送额度会自动从这一行消失。
|
|
35
|
+
- `topped_up_balance`,界面显示为 **Topped up**,是你自己充值的金额。
|
|
36
|
+
|
|
37
|
+
前两行数值相等时,说明账户没有赠送额度。`is_available` 是响应顶层的字段,只回答一个问题:余额是否足够调用接口。只有接口报告余额不足时,面板才会加一行警告,其余时候保持安静,因为任何有余额的账户这个字段都是 true。
|
|
38
|
+
|
|
39
|
+
货币一律取自接口返回值,不做假设:返回 USD 的账户不会被标上人民币符号。
|
|
40
|
+
|
|
41
|
+
## 为什么再做一版
|
|
42
|
+
|
|
43
|
+
社区里已经有两个插件覆盖了其中一部分,它们各自教了一件事:
|
|
44
|
+
|
|
45
|
+
- [dsh-price-phase](https://github.com/lijunyu726/dsh-price-phase) 显示计费时段。它的倒计时曾在周五收盘后指向周六 09:00,而那个事件根本不会发生;它的徽标用哈希 CSS 类居中,模型名一长就与模型标签重叠。本插件把每个候选边界与其前一瞬对比,只计入真实的状态翻转;胶囊是原生统计行里的普通 flex 子元素。
|
|
46
|
+
- [dsh-usage-monitor](https://github.com/liyiersan/dsh-usage-monitor) 显示余额,但无论接口返回什么货币都按人民币格式化。
|
|
47
|
+
|
|
48
|
+
本插件有意**不做**费用与 token 记账。
|
|
49
|
+
|
|
50
|
+
## 安装
|
|
51
|
+
|
|
52
|
+
从 npm 安装:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
dsh plugin --profile web add dsh-billing-badge
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
或直接从仓库安装:
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
dsh plugin --profile web add github:devacc8/dsh-billing-badge
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
然后重启 `dsh web`。包内声明了 `dsh.bundle.patch`,宿主部分会自动写入 profile 的 bundle 列表。
|
|
65
|
+
|
|
66
|
+
兼容 DeepSeek Harness 0.1.5 与 0.1.6。旧版本把输入框统计渲染成一行带标记的元素,胶囊加入这一行;0.1.6 把这些读数移进输入框底栏,变成一排胶囊按钮,胶囊会跟随缓存命中那个胶囊加入同一行。
|
|
67
|
+
|
|
68
|
+
在本地开发这个插件时,改为按路径安装检出目录:
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
dsh plugin --profile web add link:/absolute/path/to/dsh-billing-badge
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## 安全
|
|
75
|
+
|
|
76
|
+
余额的暴露面很小,插件就让它保持小:
|
|
77
|
+
|
|
78
|
+
- API key 只在宿主进程中通过 DSH credentials 接口读取(`ctx.credentials.resolve('DEEPSEEK_API_KEY')`,环境变量兜底),不会进入浏览器;
|
|
79
|
+
- 唯一的路由要求请求头 `x-dsh-billing-badge: 1` 并拒绝跨站 `Origin`,因此第三方页面无法访问它;
|
|
80
|
+
- 不写任何文件,除 `api.deepseek.com` 外不访问任何地址;
|
|
81
|
+
- 缺少 key、HTTP 错误或网络故障都会降级为面板可渲染的状态,不会抛异常。
|
|
82
|
+
|
|
83
|
+
## 开发
|
|
84
|
+
|
|
85
|
+
时段逻辑放在 `lib/season.js`,是一个普通 ESM 模块,可以直接测试。浏览器端包无法导入同目录文件(加载器只解析平台种子、已物化的包和已注册的工厂,用子路径 require 自身会抛 "missed the module table"),因此 `scripts/inline-season.mjs` 把该模块去掉 `export` 后复制进 `lib/client.js` 的两个标记之间,`test/client-sync.test.mjs` 在副本漂移时失败。
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
npm test # 30 项测试:时段规则、倒计时不变量、宿主路由、浏览器端包
|
|
89
|
+
npm run sync # 把 season.js 重新内联进浏览器端包
|
|
90
|
+
npm run check # 校验内联是否同步,并对两半做语法检查
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
倒计时用的是不变量测试而不是固定样本:九天之内每 13 分钟取一个时刻,报告的目标必须在未来、必须改变时段,并且时段不能在它之前改变。
|
|
94
|
+
|
|
95
|
+
## 目录结构
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
lib/season.js 时段规则、倒计时与格式化(唯一事实来源,有测试)
|
|
99
|
+
lib/index.js 宿主部分:余额路由
|
|
100
|
+
lib/client.js 浏览器部分:胶囊与面板,season.js 已内联
|
|
101
|
+
cordis.patch.yml 把宿主部分挂载进 profile
|
|
102
|
+
scripts/ 内联脚本
|
|
103
|
+
test/ 时段、内联同步、宿主与浏览器端包的测试
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
GitHub Actions 在 Node 20 与 22 上运行 `npm test` 与 `npm run check`。
|
|
107
|
+
|
|
108
|
+
MIT.
|
package/lib/client.js
CHANGED
|
@@ -12,13 +12,16 @@ window.__ModuleLoader__.load({
|
|
|
12
12
|
* composer's statistics row, immediately after the native "Cache hit" pill,
|
|
13
13
|
* and opens a small panel with the full reading on click.
|
|
14
14
|
*
|
|
15
|
-
* The native row is a
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
15
|
+
* The native row is not a slot the chip could be a sibling in, and the composer
|
|
16
|
+
* container is a column, so a sibling of the dock would land underneath instead
|
|
17
|
+
* of beside it. Up to 0.1.5 the row is one labelled component
|
|
18
|
+
* (`[data-composer-stats]`); from 0.1.6 the readings are pill buttons inside the
|
|
19
|
+
* composer dock. The slot registration below is therefore a mount point only:
|
|
20
|
+
* it renders nothing and appends the chip into whichever row `statsRow()`
|
|
21
|
+
* resolves. The data attribute and the slot name are deliberate, unlike the
|
|
22
|
+
* hashed class an earlier community plugin centred itself on (which collided
|
|
23
|
+
* with the model chip), and the chip is an ordinary flex child so it cannot
|
|
24
|
+
* overlap anything.
|
|
22
25
|
*/
|
|
23
26
|
|
|
24
27
|
// >>> billing-badge:season (generated from lib/season.js by scripts/inline-season.mjs) >>>
|
|
@@ -216,6 +219,7 @@ window.__ModuleLoader__.load({
|
|
|
216
219
|
.dsh-billing-rows dt { color: var(--dsw-alias-label-secondary); }
|
|
217
220
|
.dsh-billing-rows dd { margin: 0; color: var(--dsw-alias-label-primary); font-variant-numeric: tabular-nums; text-align: right; }
|
|
218
221
|
.dsh-billing-note { margin-top: 10px; color: var(--dsw-alias-label-caption); }
|
|
222
|
+
.dsh-billing-note:empty { display: none; }
|
|
219
223
|
.dsh-billing-err { color: var(--dsw-alias-state-warn-label); }
|
|
220
224
|
`;
|
|
221
225
|
|
|
@@ -277,10 +281,33 @@ window.__ModuleLoader__.load({
|
|
|
277
281
|
return rows;
|
|
278
282
|
}
|
|
279
283
|
|
|
284
|
+
/**
|
|
285
|
+
* The element the chip joins: the native statistics row, in either shape the
|
|
286
|
+
* harness has shipped it. Up to 0.1.5 it is a single labelled element; from
|
|
287
|
+
* 0.1.6 the readings are pill buttons in the composer dock, so the chip follows
|
|
288
|
+
* the pill that carries the cache-hit reading into the row that holds it.
|
|
289
|
+
*
|
|
290
|
+
* @returns {HTMLElement|null} Null while the row does not exist yet.
|
|
291
|
+
*/
|
|
292
|
+
function statsRow() {
|
|
293
|
+
const legacy = document.querySelector('[data-composer-stats="true"]');
|
|
294
|
+
if (legacy !== null) return legacy;
|
|
295
|
+
const dock = document.querySelector('[data-slot="conversation.composer.dock"]');
|
|
296
|
+
if (dock === null) return null;
|
|
297
|
+
const reading = [...dock.querySelectorAll("button")].find((el) =>
|
|
298
|
+
/cache hit/i.test(el.textContent || ""),
|
|
299
|
+
);
|
|
300
|
+
if (reading === undefined) return null;
|
|
301
|
+
for (const child of dock.children) {
|
|
302
|
+
if (child.contains(reading)) return child;
|
|
303
|
+
}
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
|
|
280
307
|
/**
|
|
281
308
|
* Mount the chip and its panel into the native statistics row.
|
|
282
309
|
*
|
|
283
|
-
* @param {HTMLElement} row - The `
|
|
310
|
+
* @param {HTMLElement} row - The native statistics row, from `statsRow()`.
|
|
284
311
|
* @returns {() => void} Teardown.
|
|
285
312
|
*/
|
|
286
313
|
function mountChip(row) {
|
|
@@ -327,9 +354,7 @@ window.__ModuleLoader__.load({
|
|
|
327
354
|
if (tone === "warn") dd.style.color = "var(--dsw-alias-state-warn-label)";
|
|
328
355
|
body.append(dt, dd);
|
|
329
356
|
}
|
|
330
|
-
note.textContent = balance && balance.ok === false && balance.error
|
|
331
|
-
? String(balance.error)
|
|
332
|
-
: "Prices are published in CNY per million tokens; the balance keeps the currency the API reports.";
|
|
357
|
+
note.textContent = balance && balance.ok === false && balance.error ? String(balance.error) : "";
|
|
333
358
|
note.classList.toggle("dsh-billing-err", Boolean(balance && balance.ok === false));
|
|
334
359
|
};
|
|
335
360
|
|
|
@@ -356,7 +381,31 @@ window.__ModuleLoader__.load({
|
|
|
356
381
|
const onKey = (event) => {
|
|
357
382
|
if (event.key === "Escape") closePanel();
|
|
358
383
|
};
|
|
359
|
-
|
|
384
|
+
/**
|
|
385
|
+
* Follow the chip instead of dismissing the panel. The composer scrolls
|
|
386
|
+
* under the reader on its own while a turn renders, and closing on any
|
|
387
|
+
* scroll made the panel flicker away right after a click. A chip scrolled
|
|
388
|
+
* out of sight still closes, because a panel parked at the viewport edge
|
|
389
|
+
* no longer points at anything.
|
|
390
|
+
*/
|
|
391
|
+
let repositioning = false;
|
|
392
|
+
const reposition = () => {
|
|
393
|
+
if (panel === null) return;
|
|
394
|
+
const rect = chip.getBoundingClientRect();
|
|
395
|
+
if (rect.bottom < 0 || rect.top > window.innerHeight) {
|
|
396
|
+
closePanel();
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
placePanel();
|
|
400
|
+
};
|
|
401
|
+
const onViewport = () => {
|
|
402
|
+
if (panel === null || repositioning) return;
|
|
403
|
+
repositioning = true;
|
|
404
|
+
window.requestAnimationFrame(() => {
|
|
405
|
+
repositioning = false;
|
|
406
|
+
reposition();
|
|
407
|
+
});
|
|
408
|
+
};
|
|
360
409
|
|
|
361
410
|
function closePanel() {
|
|
362
411
|
if (panel === null) return;
|
|
@@ -428,7 +477,7 @@ window.__ModuleLoader__.load({
|
|
|
428
477
|
|
|
429
478
|
const attach = () => {
|
|
430
479
|
if (teardown !== null && document.querySelector(".dsh-billing-badge") !== null) return;
|
|
431
|
-
const row =
|
|
480
|
+
const row = statsRow();
|
|
432
481
|
if (row === null) return;
|
|
433
482
|
if (teardown !== null) teardown();
|
|
434
483
|
teardown = mountChip(row);
|
|
@@ -467,7 +516,7 @@ window.__ModuleLoader__.load({
|
|
|
467
516
|
|
|
468
517
|
exports.apply = apply;
|
|
469
518
|
exports.inject = inject;
|
|
470
|
-
exports.__internal = { mountChip, panelBody };
|
|
519
|
+
exports.__internal = { mountChip, panelBody, statsRow };
|
|
471
520
|
return module.exports;
|
|
472
521
|
},
|
|
473
522
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-billing-badge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Billing season and account balance chip for the DeepSeek Harness web GUI composer: a native-looking pill after the cache-hit stat, with a panel on click. DeepSeek Harness 计费时段与余额插件",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|