@dougen/dsh-deepseek-usage 0.10.2 → 0.11.1
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/README.md +24 -6
- package/lib/client.js +33 -19
- package/lib/pricing.js +26 -8
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -12,7 +12,16 @@ A sidebar plugin for the **DeepSeek Harness (DSH) Web UI** that answers two ques
|
|
|
12
12
|
- **Balance** in the sidebar footer: your DeepSeek account balance (the symbol follows the account currency: `¥` / `$`).
|
|
13
13
|
- **Unit prices of the active model**: input / input cache / output for the billing window in effect right now. They change when you switch models and when the peak/off-peak window flips.
|
|
14
14
|
- **Peak / off-peak dot with a countdown**: orange during peak hours (Mon–Fri, excluding Chinese public holidays, 9:00–12:00 and 14:00–18:00 Beijing Time), green otherwise — weekends, public holidays and the make-up workdays that fall on a weekend are all off-peak; next to it, the time left until the next switch (`2d 15h`, `5h 47m`, `23m`).
|
|
15
|
-
- Refreshes every
|
|
15
|
+
- Refreshes every 30 seconds, and immediately when you click the card or switch models. Follows the app light/dark theme and the DSH language (Chinese / English).
|
|
16
|
+
|
|
17
|
+
## DSH compatibility
|
|
18
|
+
|
|
19
|
+
| DSH | Plugin version |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| `0.2.x` (`0.2.0-rc.1` and later) | `0.11.0` and later |
|
|
22
|
+
| `0.1.x` (`0.1.0-rc.6` and later) | `0.10.x`, or `0.11.0` and later |
|
|
23
|
+
|
|
24
|
+
`dsh --version` prints your DSH version. Installing without a version picks the newest compatible release; name an exact version (`@dougen/dsh-deepseek-usage@0.10.2`) when the newest does not match your DSH.
|
|
16
25
|
|
|
17
26
|
## Install, uninstall, common errors
|
|
18
27
|
|
|
@@ -42,12 +51,12 @@ The card then shows the reason instead of numbers:
|
|
|
42
51
|
|
|
43
52
|
- Prices are the built-in table `PRICE_TABLE` in `lib/pricing.js`, a snapshot of the official pricing page. It changes only when a new version of the plugin ships — there is no local price file and no background sync.
|
|
44
53
|
- If the active model is not in the table, the card falls back to `deepseek-chat` pricing.
|
|
45
|
-
- Off-peak days come from the holiday table `HOLIDAY_RANGES` in `lib/pricing.js
|
|
54
|
+
- Off-peak days come from the holiday table `HOLIDAY_RANGES` in `lib/pricing.js`. The State Council usually publishes next year's arrangement in November; update it then. Until you do, holidays are treated as ordinary workdays and the card shows peak prices during them.
|
|
46
55
|
|
|
47
56
|
## How it works
|
|
48
57
|
|
|
49
58
|
- The **host half** (`lib/index.js`, `lib/pricing.js`) registers `GET /api/deepseek-usage`. Per request it reads the API key from DSH credentials, asks the official balance API, resolves the active model's price from the built-in price table, and answers with balance, tier prices, the peak flag and the next switch time. `GET /api/deepseek-usage/holidays` hands the browser half the same public-holiday table.
|
|
50
|
-
- The **browser half** (`lib/client.js`) renders that answer into the `sidebar.footer.action` slot,
|
|
59
|
+
- The **browser half** (`lib/client.js`) renders that answer into the `sidebar.footer.action` slot, re-reads it every 30 seconds, and refreshes on click or on a model change. The peak state and the countdown come from the same answer, so the card itself holds no pricing logic.
|
|
51
60
|
|
|
52
61
|
---
|
|
53
62
|
|
|
@@ -60,7 +69,16 @@ The card then shows the reason instead of numbers:
|
|
|
60
69
|
- **余额**:侧边栏底部显示 DeepSeek 账号余额(符号随账号币种 `¥` / `$`)。
|
|
61
70
|
- **当前模型的档单价**:输入 / 输入缓存 / 输出,按此刻生效的计费时段显示;切换模型、跨过峰谷切换点时会跟着变。
|
|
62
71
|
- **峰谷指示灯 + 倒计时**:高峰(周一至周五、不含中国法定节假日,9:00–12:00、14:00–18:00 北京时间)橙色,其余时间绿色——周末、法定节假日全天,以及调休上班的周末都是空闲;指示灯旁给出距下次切换的剩余时间(`2d 15h`、`5h 47m`、`23m`)。
|
|
63
|
-
- 每
|
|
72
|
+
- 每 30 秒自动刷新;点击卡片或切换模型时立即刷新。跟随应用明暗主题与 DSH 语言(中文 / English)。
|
|
73
|
+
|
|
74
|
+
### DSH 版本对应关系
|
|
75
|
+
|
|
76
|
+
| DSH | 插件版本 |
|
|
77
|
+
| --- | --- |
|
|
78
|
+
| `0.2.x`(`0.2.0-rc.1` 起) | `0.11.0` 及以后 |
|
|
79
|
+
| `0.1.x`(`0.1.0-rc.6` 起) | `0.10.x`,或 `0.11.0` 及以后 |
|
|
80
|
+
|
|
81
|
+
`dsh --version` 查看当前 DSH 版本。不带版本号安装会取最新的兼容版本;最新版与你的 DSH 不匹配时,用 `@dougen/dsh-deepseek-usage@0.10.2` 这样的完整版本号指定。
|
|
64
82
|
|
|
65
83
|
### 安装、卸载与常见错误
|
|
66
84
|
|
|
@@ -88,12 +106,12 @@ dsh plugin --profile web remove @dougen/dsh-deepseek-usage
|
|
|
88
106
|
|
|
89
107
|
- 价格是 `lib/pricing.js` 的内置表 `PRICE_TABLE`(官方价格页的快照),只在发布新版本时更新——没有本地价格文件,也没有后台同步。
|
|
90
108
|
- 当前模型不在价格表里时,按 `deepseek-chat` 的价格显示。
|
|
91
|
-
- 放假日期维护在 `lib/pricing.js` 的 `HOLIDAY_RANGES
|
|
109
|
+
- 放假日期维护在 `lib/pricing.js` 的 `HOLIDAY_RANGES`。国务院办公厅通常在前一年 11 月发布次年安排,届时更新;未更新前,节假日会按普通工作日处理,白天显示高峰价。
|
|
92
110
|
|
|
93
111
|
### 简单的工作原理
|
|
94
112
|
|
|
95
113
|
- **宿主侧**(`lib/index.js`、`lib/pricing.js`)注册 `GET /api/deepseek-usage`:每次请求读取 DSH 凭据里的 API Key、调用官方余额接口、从内置价格表解析当前模型档单价,返回余额、档单价、峰谷状态与下次切换时刻;`GET /api/deepseek-usage/holidays` 把同一份法定节假日表交给浏览器侧。
|
|
96
|
-
- **浏览器侧**(`lib/client.js`)把这份响应渲染进 `sidebar.footer.action` 槽位,每
|
|
114
|
+
- **浏览器侧**(`lib/client.js`)把这份响应渲染进 `sidebar.footer.action` 槽位,每 30 秒重新读一次,点击卡片或模型变化时立即刷新;峰谷状态与倒计时同样来自这次响应,界面自身不做计价逻辑。
|
|
97
115
|
|
|
98
116
|
## License
|
|
99
117
|
|
package/lib/client.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// dsh-deepseek-usage — browser half.
|
|
2
2
|
//
|
|
3
3
|
// 侧边栏底部用量卡片(注册进 `sidebar.footer.action` 槽位,独占一行,位于
|
|
4
|
-
// Cordis
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// Cordis 面板按钮下方)。每 30 秒心跳一次:重算倒计时并重新拉取宿主路由
|
|
5
|
+
// `/api/deepseek-usage`(见 lib/index.js),因此余额、档单价与峰谷指示灯都随心跳更新,
|
|
6
|
+
// 跨过峰谷切换点后最多滞后一次心跳;点击卡片任意位置可立即刷新;还没拿到余额
|
|
7
|
+
// (宿主刚重启/接口刚起来)时按 QUICK_RETRY_MS 阶梯快速补几次请求,卡片几秒内收敛。
|
|
7
8
|
// 两种形态:
|
|
8
9
|
// · 侧边栏展开——完整卡片:余额(放大显示,符号随币种 ¥/$)+ 当前模型档单价
|
|
9
10
|
// (输入/输入缓存/输出,图标 + 数字,位于卡片右下角)+ 峰谷指示灯(右上角,
|
|
@@ -27,7 +28,9 @@ window.__ModuleLoader__.load({
|
|
|
27
28
|
const h = react.createElement;
|
|
28
29
|
|
|
29
30
|
// ---- constants -------------------------------------------------
|
|
30
|
-
|
|
31
|
+
// 卡片心跳:每 30 秒重算一次倒计时并重新取一次数(余额 + 当前档单价)。
|
|
32
|
+
// 峰谷切换也因此不需要额外的检测逻辑——每次心跳都重新取数,切换后最多 30 秒翻正。
|
|
33
|
+
const POLL_MS = 30 * 1000;
|
|
31
34
|
const USAGE_PATH = "/api/deepseek-usage";
|
|
32
35
|
// 放假日期:宿主路由(见 lib/index.js),浏览器启动时读一次,覆盖内置镜像表;
|
|
33
36
|
// 读不到就继续用内置表(两边都是同一份国务院办公厅日期表)。
|
|
@@ -38,7 +41,7 @@ window.__ModuleLoader__.load({
|
|
|
38
41
|
// 异常态把卡片置空时用它兜住同一个高度,侧边栏高度不跳动。
|
|
39
42
|
const CARD_MIN_HEIGHT = 54;
|
|
40
43
|
// 卡片还没拿到余额时的快速重试节奏(宿主刚重启、接口刚起来、或本次请求失败):
|
|
41
|
-
//
|
|
44
|
+
// 让空卡片在几秒内收敛,而不必等满下一次心跳。
|
|
42
45
|
const QUICK_RETRY_MS = [4000, 8000, 15000, 30000];
|
|
43
46
|
|
|
44
47
|
// ---- i18n(DSH locale 服务;zh 为兜底)----
|
|
@@ -69,6 +72,8 @@ window.__ModuleLoader__.load({
|
|
|
69
72
|
const SHANGHAI_OFFSET_MS = 8 * 60 * 60 * 1000;
|
|
70
73
|
const PEAK_WINDOWS = [[9, 12], [14, 18]];
|
|
71
74
|
const PEAK_WEEKDAYS_ONLY = true;
|
|
75
|
+
/** 可能的切换时刻(北京时间整点):高峰窗口的起止整点,nextChange 逐日枚举它们。 */
|
|
76
|
+
const BOUNDARY_HOURS = [...new Set(PEAK_WINDOWS.flat())].sort((a, b) => a - b);
|
|
72
77
|
// 放假日期(含起止,闭区间):国务院办公厅 2026 年节假日安排。
|
|
73
78
|
const HOLIDAY_RANGES = [
|
|
74
79
|
['2026-01-01', '2026-01-03'],
|
|
@@ -116,15 +121,23 @@ window.__ModuleLoader__.load({
|
|
|
116
121
|
|
|
117
122
|
/**
|
|
118
123
|
* 距下一个计费时段边界的剩余时间;与 lib/pricing.js 的 nextChange 等价。
|
|
119
|
-
*
|
|
124
|
+
* 候选时刻是 PEAK_WINDOWS 起止整点落在各天的那些点,逐日枚举取第一个让 isPeak
|
|
125
|
+
* 翻转的,因此返回精确时刻(上限 32 天,覆盖春节长假 → 假期后首个工作日早晨)。
|
|
120
126
|
*/
|
|
121
127
|
function nextChange(ms) {
|
|
122
128
|
const now = Number.isFinite(ms) ? ms : Date.now();
|
|
123
129
|
const current = isPeak(now);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
130
|
+
const DAY = 24 * 60 * 60 * 1000;
|
|
131
|
+
const hourMs = 60 * 60 * 1000;
|
|
132
|
+
const dayStart = Date.parse(new Date(now + SHANGHAI_OFFSET_MS).toISOString().slice(0, 10) + 'T00:00:00Z') - SHANGHAI_OFFSET_MS;
|
|
133
|
+
for (let day = 0; day < 32; day += 1) {
|
|
134
|
+
const base = dayStart + day * DAY;
|
|
135
|
+
for (const hour of BOUNDARY_HOURS) {
|
|
136
|
+
const at = base + hour * hourMs;
|
|
137
|
+
if (at <= now) continue;
|
|
138
|
+
const peak = isPeak(at);
|
|
139
|
+
if (peak !== current) return { peak, at, ms: at - now };
|
|
140
|
+
}
|
|
128
141
|
}
|
|
129
142
|
return { peak: !current, at: now, ms: 0 };
|
|
130
143
|
}
|
|
@@ -250,7 +263,7 @@ window.__ModuleLoader__.load({
|
|
|
250
263
|
};
|
|
251
264
|
|
|
252
265
|
// 宿主还在后台忙(重启后首次余额、接口刚起来)时按 QUICK_RETRY_MS 阶梯补几次请求,
|
|
253
|
-
//
|
|
266
|
+
// 让卡片几秒内收敛,不必等下一次心跳(POLL_MS)。失败同样按此节奏重试。
|
|
254
267
|
// 判断依据只看"有没有拿到余额",不依赖宿主内部字段。
|
|
255
268
|
const load = useCallback(async () => {
|
|
256
269
|
try {
|
|
@@ -293,7 +306,6 @@ window.__ModuleLoader__.load({
|
|
|
293
306
|
useEffect(() => {
|
|
294
307
|
mounted.current = true;
|
|
295
308
|
load();
|
|
296
|
-
const timer = setInterval(load, POLL_MS);
|
|
297
309
|
// 模型切换(写入 settings 触发 document-updated)时立即刷新,单价跟着变。
|
|
298
310
|
let off = null;
|
|
299
311
|
try {
|
|
@@ -307,7 +319,6 @@ window.__ModuleLoader__.load({
|
|
|
307
319
|
}
|
|
308
320
|
return () => {
|
|
309
321
|
mounted.current = false;
|
|
310
|
-
clearInterval(timer);
|
|
311
322
|
if (retryTimer.current !== null) {
|
|
312
323
|
clearTimeout(retryTimer.current);
|
|
313
324
|
retryTimer.current = null;
|
|
@@ -335,12 +346,16 @@ window.__ModuleLoader__.load({
|
|
|
335
346
|
return () => { cancelled = true; };
|
|
336
347
|
}, []);
|
|
337
348
|
|
|
338
|
-
//
|
|
339
|
-
//
|
|
349
|
+
// 心跳:每 POLL_MS 重算一次倒计时(setTick 触发重渲染)并重新取一次数。
|
|
350
|
+
// 取数会带回最新的余额、峰谷状态与档单价,所以跨过峰谷切换点后单价与指示灯
|
|
351
|
+
// 最多滞后一次心跳——不需要单独检测切换时刻。
|
|
340
352
|
useEffect(() => {
|
|
341
|
-
const timer = setInterval(() =>
|
|
353
|
+
const timer = setInterval(() => {
|
|
354
|
+
setTick((v) => v + 1);
|
|
355
|
+
load(); // 失败已在 load 内部转成卡片文案并按 QUICK_RETRY_MS 重试
|
|
356
|
+
}, POLL_MS);
|
|
342
357
|
return () => clearInterval(timer);
|
|
343
|
-
}, []);
|
|
358
|
+
}, [load]);
|
|
344
359
|
|
|
345
360
|
const balance = data && data.balance ? data.balance : null;
|
|
346
361
|
const tier = data && data.tier ? data.tier : null;
|
|
@@ -351,8 +366,7 @@ window.__ModuleLoader__.load({
|
|
|
351
366
|
// 把卡片置空并说明原因——否则余额是空的,用户无从判断。
|
|
352
367
|
const errs = (Array.isArray(data && data.errors) ? data.errors : []).concat(error ? [error] : []);
|
|
353
368
|
const blanked = errs.length > 0;
|
|
354
|
-
// 每次渲染都以"当前时刻"求剩余时间;tick 每 30 秒 bump
|
|
355
|
-
// 所以跨过切换瞬间时不需要等下一次 5 分钟轮询。
|
|
369
|
+
// 每次渲染都以"当前时刻"求剩余时间;tick 每 30 秒 bump 一次触发这次重算。
|
|
356
370
|
// nextAt 优先用宿主给的 next.at(避免宿主与浏览器时钟不一致导致数字跳变),
|
|
357
371
|
// 宿主没给(旧版本 / 字段缺失)时用同一套规则在本地推算。
|
|
358
372
|
const nowMs = Date.now();
|
package/lib/pricing.js
CHANGED
|
@@ -127,6 +127,12 @@ export function isPeak(ms) {
|
|
|
127
127
|
return PEAK_WINDOWS.some(([start, end]) => h >= start && h < end);
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
/**
|
|
131
|
+
* 可能的切换时刻(北京时间的整点):高峰窗口的起止整点。
|
|
132
|
+
* 翻转只可能发生在这些整点上——窗口外的整点不改变 isPeak,工作日之外则整天不变。
|
|
133
|
+
*/
|
|
134
|
+
const BOUNDARY_HOURS = [...new Set(PEAK_WINDOWS.flat())].sort((a, b) => a - b);
|
|
135
|
+
|
|
130
136
|
/**
|
|
131
137
|
* 距下一个计费时段边界的剩余时间(供卡片显示倒计时)。
|
|
132
138
|
*
|
|
@@ -137,8 +143,11 @@ export function isPeak(ms) {
|
|
|
137
143
|
* · 周五 18:00 之后、整个周末 → 下一次切换是下周一 09:00(跨 2 天多)。
|
|
138
144
|
* 因此倒计时必须能显示「天」。
|
|
139
145
|
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
146
|
+
* 实现:候选时刻就是 BOUNDARY_HOURS 落在各天的那些整点,从今天起逐日枚举、取第一个
|
|
147
|
+
* 让 isPeak 翻转的,因此返回的是**精确**时刻。纯算术,无时区/日历依赖。
|
|
148
|
+
* (旧实现逐分钟探测,返回的 at 最多晚 59.75 秒,倒计时会因此多显示 1m 或
|
|
149
|
+
* 提前进入 switching。)
|
|
150
|
+
*
|
|
142
151
|
* 任何口径变更必须同时改 isPeak / PEAK_WINDOWS / PEAK_WEEKDAYS_ONLY / HOLIDAY_RANGES,
|
|
143
152
|
* 以及 lib/client.js 里为浏览器侧镜像的同名常量(那边无法 import 本模块)。
|
|
144
153
|
*
|
|
@@ -148,13 +157,22 @@ export function isPeak(ms) {
|
|
|
148
157
|
export function nextChange(ms) {
|
|
149
158
|
const now = Number.isFinite(ms) ? ms : Date.now();
|
|
150
159
|
const current = isPeak(now);
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
160
|
+
const DAY = 24 * 60 * 60 * 1000;
|
|
161
|
+
const hourMs = 60 * 60 * 1000;
|
|
162
|
+
// 北京时间的今天零点
|
|
163
|
+
const dayStart = Date.parse(`${beijingDateKey(now)}T00:00:00Z`) - SHANGHAI_OFFSET_MS;
|
|
164
|
+
// 上限 32 天:远超"长假最后一天 18:00 → 假期后第一个工作日 09:00"的最长空档
|
|
165
|
+
// (2026 年春节为 11 天)
|
|
166
|
+
for (let day = 0; day < 32; day += 1) {
|
|
167
|
+
const base = dayStart + day * DAY;
|
|
168
|
+
for (const hour of BOUNDARY_HOURS) {
|
|
169
|
+
const at = base + hour * hourMs;
|
|
170
|
+
if (at <= now) continue;
|
|
171
|
+
const peak = isPeak(at);
|
|
172
|
+
if (peak !== current) return { peak, at, ms: at - now };
|
|
173
|
+
}
|
|
156
174
|
}
|
|
157
|
-
//
|
|
175
|
+
// 理论上不可达(不变量:一个月内必有工作日整点边界)
|
|
158
176
|
return { peak: !current, at: now, ms: 0 };
|
|
159
177
|
}
|
|
160
178
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dougen/dsh-deepseek-usage",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "A DeepSeek Harness sidebar plugin: your balance (official API), the active model's unit prices and a peak/off-peak indicator, in a Chinese/English UI. / DeepSeek Harness 侧边栏插件:余额(官方 API)、当前模型档单价与峰谷时段指示灯,界面支持中英文。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@deepseek-ai/dsh-credentials": "^0.1.0-rc.6",
|
|
42
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6"
|
|
41
|
+
"@deepseek-ai/dsh-credentials": "^0.1.0-rc.6 || ^0.2.0-rc.1",
|
|
42
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6 || ^0.2.0-rc.1"
|
|
43
43
|
},
|
|
44
44
|
"keywords": [
|
|
45
45
|
"dsh",
|