dsh-usage-badge 0.1.8 → 0.1.10
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 +4 -2
- package/assets/logo.jpg +0 -0
- package/assets/usage-dialog.png +0 -0
- package/docs/design-notes.md +22 -3
- package/lib/client.js +101 -51
- package/lib/holidays-cn.json +17 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ DeepSeek Harness 的用量徽标插件:侧边栏底部一行「今日用量」
|
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|
|
|
7
|
+
[](https://awesome-dsh-plugin.com)
|
|
8
|
+
|
|
7
9
|
零依赖、零构建:宿主半只用 Node 内建模块,浏览器半是手写的单文件 bundle,折线图是手写 SVG。
|
|
8
10
|
|
|
9
11
|
## 安装
|
|
@@ -59,7 +61,7 @@ dsh plugin --profile web remove dsh-usage-badge
|
|
|
59
61
|
|
|
60
62
|
官方规则:**周一至周五(不含中国法定节假日)9:00-12:00、14:00-18:00 是高峰时段**,其余时段 —— 包括周末和法定节假日**全天** —— 都按空闲价。
|
|
61
63
|
|
|
62
|
-
插件内置了中国法定节假日日历(2025–
|
|
64
|
+
插件内置了中国法定节假日日历(2025–2027,取自国务院办公厅的放假安排通知)。**但它是价格表里 `holidays.source` 这个开关**:写 `"cn"`(或点一次「应用官方价格」,它会自动打开)才算数,缺省时工作日节假日会被当成普通工作日按峰价计费 —— 面板在未启用时会这么写明。
|
|
63
65
|
|
|
64
66
|
**峰谷对价格表里的每一行都生效**,包括你后来手写加的行 —— 不用每行都写一遍 `timeOfUse`。想让某一行完全不参与峰谷(比如网关那边已经是固定折后价),在行编辑器里把「峰谷」选成**不参与峰谷**;想让它按别的倍率算,选**自定义**并写一整套 `timeOfUse`(写全:行内规则是**整体替换**表级规则的,漏了 `peakMultiplier` 就等于那一行不加价)。**周末**用 `days` 逐行控制,编辑器给的是 周一至周五 / 周末 / 每天 三档,数组写法(`[6,7]`)要手写 JSON;**法定节假日**用 `"honorHolidays": false` 逐行关掉(有些网关在国庆照样按自己的峰时收费),这个勾选框在峰谷选成「自定义」后出现。
|
|
65
67
|
|
|
@@ -95,7 +97,7 @@ dsh plugin --profile web remove dsh-usage-badge
|
|
|
95
97
|
|
|
96
98
|
```sh
|
|
97
99
|
npm install # 只为测试装 react / react-dom
|
|
98
|
-
npm test # 11 个套件
|
|
100
|
+
npm test # 11 个套件 908 条断言:包契约、定价、节假日、官方定价解析、缓存存储、同步分片、同步编排、宿主同步接线、自动触发、WebDAV(Digest)、浏览器半渲染
|
|
99
101
|
```
|
|
100
102
|
|
|
101
103
|
云同步的前五步已经在真实的 `bytemark/webdav` 容器上核对通过。要自己核对一遍:
|
package/assets/logo.jpg
ADDED
|
Binary file
|
package/assets/usage-dialog.png
CHANGED
|
Binary file
|
package/docs/design-notes.md
CHANGED
|
@@ -131,6 +131,9 @@ DeepSeek 官方政策原文:
|
|
|
131
131
|
|---|---|---|---|
|
|
132
132
|
| 2025 | 国办发明电〔2024〕7号 | 28 | 5 |
|
|
133
133
|
| 2026 | 国办发明电〔2025〕7号 | 33 | 6 |
|
|
134
|
+
| 2027 | 国办发明电〔2026〕10号 | 29 | 5 |
|
|
135
|
+
|
|
136
|
+
2027 年的端午(6 月 9 日)与中秋(9 月 15 日)都落在周三、各只放一天 —— 不处理就是两天全按峰价计费,这正是日历存在的意义。所以它也成了最纯粹的一组断言:同一个 10:30,节当天按 x1,旁边的周三仍按 x2。
|
|
134
137
|
|
|
135
138
|
**这在你自己的历史数据上是可测的,而且我在这里犯过错**:本机 `2026-09-20` 是调休上班的周日,我最初把它按工作日计价,得出 **¥13.78**;按政策原文(周末全天空闲)应为 **¥7.46** —— 也就是说我**多收了 ¥6.32**,而当时我以为是自己修好了桌面壳的「少算」。现已改正,`2026-09-19`(普通周六)与 `2026-09-21`(普通周一)的数字不受影响。这个错误现在被 `test/holidays.verify.mjs` 里三条断言钉住(调休周日、调休周六、以及旧配置里的 `holidays.workdays` 必须被忽略)。
|
|
136
139
|
|
|
@@ -267,6 +270,22 @@ dsh-usage-badge
|
|
|
267
270
|
2. 折叠结果交给 `cache-store.js`:默认写进 `$DSH_HOME/storages/usage-badge/cache.sqlite`,**一次只写变化的那几天**。只重读 size / mtime / 路径变化的日志;日志被删除时那条记录再也不会被写,于是缓存里的历史贡献原样留下(与桌面壳一致)。
|
|
268
271
|
3. 浏览器半轮询 `GET /usage-badge/summary`,本地完成区间聚合与筛选;切到「单价配置」时才去抓官方定价;切到「按年」时按年向 `GET /usage-badge/year` 取那一年的 12 个月(快照里没有更早的年份)。
|
|
269
272
|
|
|
273
|
+
### 弹窗不读主题令牌:它自带一套调色板
|
|
274
|
+
|
|
275
|
+
`.dub-pill`(侧边栏那一行)仍然用 DSH 的颜色令牌 —— 它要混在宿主自己的行里,跟着主题走才对。**弹窗是另一回事**:`.dub-box` 与图表 / 热力图的悬停卡片(`.dub-tip`)现在**不引用任何 `--dsw-*` 令牌**,颜色全部写死,挂在插件自己的 `--dub-*` 名下,深色变体跟着宿主的主题开关(`body[data-ds-dark-theme]`)切换;字体、字号、间距仍然继承宿主(面板该看起来像这个界面的一部分)。
|
|
276
|
+
|
|
277
|
+
为什么走到这一步,有两层原因:
|
|
278
|
+
|
|
279
|
+
1. **令牌的语义会变**。DSH 0.1.7 把 `--dsw-specific-menu` 从「不透明层」改成磨砂 + `backdrop-filter`,只填令牌、不加 blur 的规则就读成半透明 —— 当时改成直接填 `--dsw-alias-bg-layer-3`(那个 token 当时是不透明的),并由 `test/client.render.mjs` 钉住。
|
|
280
|
+
2. **令牌是任何人都能重定义的值**。open-sea-skin 把 `bg-base` / `bg-layer-1/2/3` 全部改写成 `rgba(…, 40%–90%)` 并带 `!important`,于是弹窗跟着整个界面一起变半透明:背后的对话与代码直接透到折线图上,数字没法读。先试过「只取令牌的颜色、把 alpha 钉成 1」(`rgb(from … r g b / 1)`),但那种修法仍然是**依赖**:下一个被皮肤改掉的令牌会再次失效。所以改成本插件自己拥有一套颜色。
|
|
281
|
+
|
|
282
|
+
代价与边界,写清楚:
|
|
283
|
+
|
|
284
|
+
- **不再跟随宿主的配色**:自定义主题(暖色、彩色)下弹窗仍是中性的白/深灰,不会跟着变色 —— 这正是「不受别的插件影响」的代价,也是要的。
|
|
285
|
+
- **只挡令牌**:如果某个插件直接对本插件的元素写 `style`/`!important`(例如 `* { background: … !important }`),或者给**祖先**加 `opacity`/`filter`,那仍然会影响到它 —— CSS 层面没有更硬的隔离。实测过这三种:前两种正常,第三种(全局 `!important` 填充、祖先 opacity)能穿透,属于「有插件故意针对」的情形。
|
|
286
|
+
- 断言见 `test/client.render.mjs`:**弹窗那段 CSS 里不许出现任何 `--dsw-`**(先剥掉注释再测,注释里允许点名令牌)、`.dub-box` / `.dub-tip` 必须填 `var(--dub-surface)`、浅色与深色两套调色板都在。
|
|
287
|
+
- 验证方式:把插件真实的样式表抽出来,在 headless Chromium 里把**所有** `--dsw-*` 令牌改成半透明/洋红,再读计算值 —— 浅色 `rgb(255,255,255)` / `rgb(17,20,24)`,深色 `rgb(22,26,33)` / `rgb(232,236,241)`,与令牌无关。
|
|
288
|
+
|
|
270
289
|
### 缓存为什么不是「一个 JSON 文件」
|
|
271
290
|
|
|
272
291
|
历史是不删的,所以「每次保存重写全部历史」这件事迟早会变成主问题。实测(把真实的一天桶形状铺成两年、761 天):
|
|
@@ -352,10 +371,10 @@ npm test
|
|
|
352
371
|
|---|---|
|
|
353
372
|
| `test/manifest.verify.mjs` | 包契约:`dsh.bundle.patch` / `dsh.client` / `exports["./client"]` 是否与磁盘上的文件对得上;patch 行名与包名是否一致;宿主半**没有** `export default`(有的话 Loader 的 `unwrapExports` 会丢弃 `inject`);节假日数据文件的结构与来源完整性 |
|
|
354
373
|
| `test/pricing.verify.mjs` | 用临时 `DSH_HOME` + 合成会话日志跑真实宿主半,断言精确金额。第一组用例**逐字移植自桌面壳的 `pricing-key-case.verify.mjs`**(同样的 fixture、同样的期望值),用来证明移植后的定价与原实现一致;另覆盖上下文档位(严格大于、`"128K"` 紧凑写法、缓存 token 计入上下文)、峰谷 `days` 规则、跨币种换算、前缀行的四条优先级规则(精确行优先、长前缀优先、星号不是分隔符、裸 `*` 仍是死配置)、隐式前缀回退(官方行兜住自己的变体、分隔符边界让 `gpt-4` 吞不掉 `gpt-4o`、开关经 PUT 路由后在**同一个进程内**立刻重算),以及未匹配模型不计价的语义(哪些行算「匹配」、token 仍计入、快照列出被跳过的路由)、逐行/表级的 `honorHolidays`(既有「节假日盖过行内规则」也有「行内拒绝日历后照样收峰价」)、行编辑器的写入路径(整份 `overrides` 经 PUT 落盘、其它字段与两个开关不受影响)、**`default` 行是整体替换而非字段合并**(清空的字段真的消失、`{}` 被当成「没有这一行」)、以及**一行 `null` 覆盖行**被跳过而不是让 `/summary` 500 |
|
|
355
|
-
| `test/holidays.verify.mjs` | 节假日日历的行为:工作日节假日全天空闲、**调休周末仍按空闲价**(政策原文说周末全天空闲)、旧配置里的 `holidays.workdays` 被忽略、关闭日历时回到旧的按星期行为、自定义 `extra
|
|
374
|
+
| `test/holidays.verify.mjs` | 节假日日历的行为:工作日节假日全天空闲、**调休周末仍按空闲价**(政策原文说周末全天空闲)、旧配置里的 `holidays.workdays` 被忽略、关闭日历时回到旧的按星期行为、自定义 `extra`、同一个月内不同日子的分类互不干扰,以及**2027 年的单日端午/中秋**(同一天 10:30,节当天 x1、旁边的周三 x2)与 2027-02-14 调休周日 |
|
|
356
375
|
| `test/official-pricing.verify.mjs` | 解析器对**中英文两个页面快照**的逐字段断言(含旧模型别名、时区识别与换算);页面结构变化时会抛错而不是静默返回错价;`应用` 的合并语义(未知字段存活、已有的 `default` 行不动、没有 `default` 行时按官方第一条补一个并报告 `defaultFrom`、页面第一条确实是最便宜那档);并用**被 stub 掉的 fetch** 跑通两个路由,最后验证应用后的价格真的参与计费(含未列出模型按生成的 default 行计价),以及兜底行的完整生命周期:删除 → 回到内置模板并把 `defaultSource` 报成 `template` → 再次应用从官方第一条生成回来 → 手改过的兜底行不会被应用覆盖 |
|
|
357
376
|
| `test/cache.verify.mjs` | 缓存存储层:**两个后端跑同一套契约断言**(会话游标与桶的往返、`hourly` 槽位与逐请求记录、重写会话即替换它的日期集合、`years()` 的年份列表与有界区间读),以及只有跨重启才看得见的行为 —— **删掉会话日志后历史仍在**(新挂载的宿主照旧报出同样的金额)、**没有变化的一轮刷新不写文件**、**超出加载窗口的那天留在库里只是不读进来**(换个更宽的窗口还能取到)、旧 `cache.json` **迁移一次**并把旧文件改名为 `.migrated`(且 `.migrated` 之后**不再被读**)、**读不出来的缓存文件被改名保留并新建一个**、**更新版本的库只读使用且绝不改名**、加载窗口必须大于快照窗口这条不变量,以及 `按年` 路由(三年以前那一年照样读得到、12 个月恒定、`daily` 带节假日标记与 provider 拆分、没有数据的年份是 12 个空月而不是报错、非法年份 400)。 |
|
|
358
|
-
| `test/client.render.mjs` | 用替身 Loader 加载浏览器 bundle,检查 slot 注册形状与 `id`,再用 `react-dom/server` 渲染两个面板,断言金额格式、区间页签(含 `按年`:下拉只列出宿主报的那几年、选中的那一年被选中、月份轴恒为 12、点数是 12 × 序列数、四张卡片是那一年的合计、这一档不画热力图、没有年份时不渲染空下拉)、四序列折线的条数与配色、图例开关(单独显示某一条时的折线条数,以及面积填充有没有跟着走)、悬停 tooltip 的装配、provider 页签、热力图的格数(371 − 今天之后的那几天)与分档、官方价格表内容、两个开关(前缀回退与未匹配模型计价:开/关两种状态各自渲染出的说明与动作、default 行的来源标注、以及「哪些模型没价格」的提示)、**覆盖行编辑器**(每行的编辑/删除动作、倍率与峰谷列恒定存在、表单用被编辑行的真实数值预填而不是空模板、自定义规则才展开规则字段、新增行不给删除按钮、**兜底行同样可编辑可删除且不提供那些不生效的字段**、**重名键与「改过的行」会被保存拦下**、**旧宿主不会被说成「兜底行还没有」**、以及「保留未显示字段」与拒绝写 NaN
|
|
377
|
+
| `test/client.render.mjs` | 用替身 Loader 加载浏览器 bundle,检查 slot 注册形状与 `id`,再用 `react-dom/server` 渲染两个面板,断言金额格式、区间页签(含 `按年`:下拉只列出宿主报的那几年、选中的那一年被选中、月份轴恒为 12、点数是 12 × 序列数、四张卡片是那一年的合计、这一档不画热力图、没有年份时不渲染空下拉)、四序列折线的条数与配色、图例开关(单独显示某一条时的折线条数,以及面积填充有没有跟着走)、悬停 tooltip 的装配、provider 页签、热力图的格数(371 − 今天之后的那几天)与分档、官方价格表内容、两个开关(前缀回退与未匹配模型计价:开/关两种状态各自渲染出的说明与动作、default 行的来源标注、以及「哪些模型没价格」的提示)、**覆盖行编辑器**(每行的编辑/删除动作、倍率与峰谷列恒定存在、表单用被编辑行的真实数值预填而不是空模板、自定义规则才展开规则字段、新增行不给删除按钮、**兜底行同样可编辑可删除且不提供那些不生效的字段**、**重名键与「改过的行」会被保存拦下**、**旧宿主不会被说成「兜底行还没有」**、以及「保留未显示字段」与拒绝写 NaN 的源码断言)、节假日徽标与日历区块、展开/收起两种行形态、**弹窗那一段 CSS 里不许出现任何宿主颜色令牌(`--dsw-`),必须自带浅色/深色两套 `--dub-*` 调色板**,以及空数据 / 稀疏 provider 两个边界不崩 |
|
|
359
378
|
| `test/smoke.mjs` | 对着**本机真实**的 `$DSH_HOME` 跑一遍,打印徽标金额、今日 token 构成、当前峰谷档位、节假日日历状态、最近 7 天,以及滚动热力图那 371 天的分档分布(有用量的天数与四档各多少天,都挤在一档说明这张图会显得很平) |
|
|
360
379
|
| `test/official-pricing.live.mjs` | **联网**核对:抓真实页面并与 `test/fixtures/` 的快照逐字段比对,确认官方页面仍能解析、价格未变 |
|
|
361
380
|
|
|
@@ -798,7 +817,7 @@ storages/usage-badge/host.lock # { pid, startedAt, instanceId, hostname, at
|
|
|
798
817
|
## 已知限制
|
|
799
818
|
|
|
800
819
|
- **节假日与峰谷按本机时区判定**:官方窗口按北京时间定义,应用官方价格时已把窗口换算到本机。但**日历匹配用的是本地日期**,主机不在 UTC+8 时,跨午夜的请求可能落到错误的本地日期从而判错节假日。中国主机(UTC+8)下完全精确。如需任意时区精确,可加 `timeOfUse.zone` 选项(尚未实现)。
|
|
801
|
-
- **节假日数据需要逐年更新**:内置 2025 与
|
|
820
|
+
- **节假日数据需要逐年更新**:内置 2025、2026 与 2027(共 90 个放假日)。国务院通常在每年 11 月公布次年安排,届时需补 `lib/holidays-cn.json`(放假日与调休上班日、文号、链接);缺失年份会在启动日志与面板提示,不会静默算错。
|
|
802
821
|
- **官方页面解析依赖当前页面结构**:改版后「获取官方定价」会报错(面板显示原因并保留原价格),用 `node test/official-pricing.live.mjs` 可以随时核对;价格仍可手改 JSON 兜底。
|
|
803
822
|
- **定价数字仅供参考**:token 列是**四桶合计**(输入 / 缓存读 / 缓存写 / 输出 —— 代码里没有第五个桶,reasoning token 没有被读取),与官方「三列」天然对不齐,与官方实时数字也存在分钟级时差;**对账请以金额为准**。
|
|
804
823
|
- **只统计 DSH 记进日志的调用**:统计口径是会话日志里的 `assistant/message` + `usage`;某个插件直接请求外部 API 且不向宿主上报 usage,这部分消耗统计不到。
|
package/lib/client.js
CHANGED
|
@@ -77,81 +77,131 @@ window.__ModuleLoader__.load({
|
|
|
77
77
|
.dub-dot-peak{background:var(--dsw-static-amber-500,#f59e0b)}
|
|
78
78
|
.dub-pill-tokens{margin-left:auto;flex:none;color:var(--dsw-alias-label-tertiary,#999);font-size:11px;font-variant-numeric:tabular-nums}
|
|
79
79
|
.dub-pill-amount{flex:none;margin-left:6px;color:var(--dsw-alias-label-primary,#111);font-variant-numeric:tabular-nums;font-weight:500}
|
|
80
|
-
|
|
81
|
-
.dub-
|
|
80
|
+
/* ── 弹窗自带一套样式,不读宿主的颜色令牌 ─────────────────────────────────────────────────────
|
|
81
|
+
.dub-pill(侧边栏那一行)仍然用 DSH 的令牌 —— 它要混在宿主自己的行里,跟着主题走才是对的。
|
|
82
|
+
弹窗是另一回事:它是**读数字**的地方,而令牌是任何人都能重新定义的值。皮肤完全可以把
|
|
83
|
+
--dsw-alias-bg-layer-3 改成玻璃(open-sea-skin 就是,40%–90% 可调),于是背后的对话直接透到
|
|
84
|
+
折线图上。所以弹窗改用自己的一套调色板:写死的颜色、挂在插件自己的 --dub-* 名下,深色变体
|
|
85
|
+
跟着宿主的主题开关走。这样任何插件靠重定义令牌都改不动它的观感 —— 要的就是这个独立性。
|
|
86
|
+
布局、间距和字体仍然继承宿主(字体故意不写死:面板该看起来像这个界面的一部分,只是不能透明)。 */
|
|
87
|
+
.dub-mask{
|
|
88
|
+
/* Light palette. This element also owns the scrim, so it is the natural place for it. */
|
|
89
|
+
color-scheme:light;
|
|
90
|
+
--dub-surface:#fff;
|
|
91
|
+
--dub-surface-soft:#f3f5f8;
|
|
92
|
+
--dub-ink:#111418;
|
|
93
|
+
--dub-ink-2:#5b6472;
|
|
94
|
+
--dub-ink-3:#8a929e;
|
|
95
|
+
--dub-line:rgba(15,23,42,.14);
|
|
96
|
+
--dub-line-soft:rgba(15,23,42,.08);
|
|
97
|
+
--dub-line-strong:rgba(15,23,42,.26);
|
|
98
|
+
--dub-grid:rgba(15,23,42,.10);
|
|
99
|
+
--dub-guide:rgba(15,23,42,.45);
|
|
100
|
+
--dub-fill:rgba(15,23,42,.07);
|
|
101
|
+
--dub-fill-strong:rgba(15,23,42,.18);
|
|
102
|
+
--dub-hover:rgba(15,23,42,.07);
|
|
103
|
+
--dub-link:#2f6fed;
|
|
104
|
+
--dub-warn:#a8590a;
|
|
105
|
+
--dub-mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
|
|
106
|
+
--dub-shadow:0 14px 44px rgba(15,23,42,.22);
|
|
107
|
+
position:fixed;inset:0;z-index:2147483600;pointer-events:auto;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.34)}
|
|
108
|
+
/* Dark palette. Keyed on the host's theme switch, which is the signal every DSH skin uses too; the
|
|
109
|
+
data-theme spellings are hedges for a future host that renames it (a selector that never matches
|
|
110
|
+
costs nothing, while a panel left light in a dark window would be a real regression). */
|
|
111
|
+
body[data-ds-dark-theme] .dub-mask,
|
|
112
|
+
body[data-theme="dark"] .dub-mask,
|
|
113
|
+
html[data-theme="dark"] .dub-mask{
|
|
114
|
+
color-scheme:dark;
|
|
115
|
+
--dub-surface:#161a21;
|
|
116
|
+
--dub-surface-soft:#1d222b;
|
|
117
|
+
--dub-ink:#e8ecf1;
|
|
118
|
+
--dub-ink-2:#aab3bf;
|
|
119
|
+
--dub-ink-3:#7c8695;
|
|
120
|
+
--dub-line:rgba(255,255,255,.16);
|
|
121
|
+
--dub-line-soft:rgba(255,255,255,.09);
|
|
122
|
+
--dub-line-strong:rgba(255,255,255,.26);
|
|
123
|
+
--dub-grid:rgba(255,255,255,.12);
|
|
124
|
+
--dub-guide:rgba(255,255,255,.45);
|
|
125
|
+
--dub-fill:rgba(255,255,255,.10);
|
|
126
|
+
--dub-fill-strong:rgba(255,255,255,.22);
|
|
127
|
+
--dub-hover:rgba(255,255,255,.10);
|
|
128
|
+
--dub-link:#8ab4ff;
|
|
129
|
+
--dub-warn:#f0b45f;
|
|
130
|
+
--dub-shadow:0 14px 44px rgba(0,0,0,.55)}
|
|
131
|
+
.dub-box{width:min(720px,100vw - 48px);max-height:min(680px,100vh - 80px);display:flex;flex-direction:column;overflow:hidden;border-radius:14px;border:.5px solid var(--dub-line);background:var(--dub-surface);color:var(--dub-ink);box-shadow:var(--dub-shadow)}
|
|
82
132
|
.dub-head{display:flex;align-items:center;gap:10px;padding:14px 18px 10px}
|
|
83
133
|
.dub-title{font-size:14px;font-weight:600;flex:1}
|
|
84
|
-
.dub-chip{font-size:11px;padding:1px 7px;border-radius:9px;background:var(--
|
|
85
|
-
.dub-chip-peak{background
|
|
134
|
+
.dub-chip{font-size:11px;padding:1px 7px;border-radius:9px;background:var(--dub-fill);color:var(--dub-ink-2);white-space:nowrap}
|
|
135
|
+
.dub-chip-peak{background:#f59e0b;color:#111}
|
|
86
136
|
.dub-chip-holiday{background:#16a34a;color:#fff}
|
|
87
137
|
.dub-x{border:0;background:0 0;color:inherit;font-size:18px;line-height:1;cursor:pointer;padding:2px 6px;border-radius:6px;opacity:.7}
|
|
88
|
-
.dub-x:hover{opacity:1;background:var(--
|
|
138
|
+
.dub-x:hover{opacity:1;background:var(--dub-hover)}
|
|
89
139
|
.dub-tabs{display:flex;gap:6px;padding:0 18px 10px;flex-wrap:wrap;align-items:center}
|
|
90
|
-
.dub-tab{font-size:12px;padding:3px 10px;border-radius:8px;border:.5px solid var(--
|
|
91
|
-
.dub-tab-on{background:var(--
|
|
140
|
+
.dub-tab{font-size:12px;padding:3px 10px;border-radius:8px;border:.5px solid var(--dub-line);background:0 0;color:inherit;cursor:pointer}
|
|
141
|
+
.dub-tab-on{background:var(--dub-fill);border-color:transparent;font-weight:600}
|
|
92
142
|
/* The year picker sits in the range row, so it is styled as one more tab. */
|
|
93
|
-
.dub-year{font:inherit;font-size:12px;padding:3px 6px;border-radius:8px;border:.5px solid var(--
|
|
143
|
+
.dub-year{font:inherit;font-size:12px;padding:3px 6px;border-radius:8px;border:.5px solid var(--dub-line);background:var(--dub-surface);color:inherit;cursor:pointer}
|
|
94
144
|
.dub-panetabs{padding:0 18px 8px;gap:0}
|
|
95
145
|
.dub-panetabs .dub-tab{border:0;border-bottom:2px solid transparent;border-radius:0;padding:4px 12px;font-size:13px;opacity:.7}
|
|
96
|
-
.dub-panetabs .dub-tab-on{background:0 0;border-bottom-color:var(--
|
|
97
|
-
.dub-sep{width:1px;height:16px;background:var(--
|
|
146
|
+
.dub-panetabs .dub-tab-on{background:0 0;border-bottom-color:var(--dub-ink);font-weight:600;opacity:1}
|
|
147
|
+
.dub-sep{width:1px;height:16px;background:var(--dub-line);margin:0 4px}
|
|
98
148
|
.dub-body{padding:0 18px 14px;overflow:auto}
|
|
99
|
-
.dub-stats{display:flex;gap:22px;flex-wrap:wrap;padding:10px 0 14px;border-bottom:.5px solid var(--
|
|
149
|
+
.dub-stats{display:flex;gap:22px;flex-wrap:wrap;padding:10px 0 14px;border-bottom:.5px solid var(--dub-line)}
|
|
100
150
|
.dub-stat{display:flex;flex-direction:column;gap:2px}
|
|
101
|
-
.dub-stat-k{font-size:11px;color:var(--
|
|
151
|
+
.dub-stat-k{font-size:11px;color:var(--dub-ink-3)}
|
|
102
152
|
.dub-stat-v{font-size:15px;font-weight:600;font-variant-numeric:tabular-nums}
|
|
103
153
|
.dub-chartwrap{padding-top:6px}
|
|
104
154
|
.dub-plot{position:relative}
|
|
105
|
-
.dub-legend{display:flex;gap:14px;flex-wrap:wrap;padding:2px 0 6px;font-size:11px;color:var(--
|
|
155
|
+
.dub-legend{display:flex;gap:14px;flex-wrap:wrap;padding:2px 0 6px;font-size:11px;color:var(--dub-ink-2)}
|
|
106
156
|
/* Legend entries are toggles, so a series can be read on its own — a lone line at
|
|
107
157
|
its own scale says more than four lines sharing one plot. The swatch of a hidden
|
|
108
158
|
series loses its colour rather than being restyled, so the inline background stays
|
|
109
159
|
the only place a series colour is written. */
|
|
110
160
|
.dub-legend-item{display:inline-flex;align-items:center;gap:5px;font:inherit;font-size:11px;color:inherit;background:0 0;border:0;padding:0;cursor:pointer}
|
|
111
|
-
.dub-legend-item:hover{color:var(--
|
|
161
|
+
.dub-legend-item:hover{color:var(--dub-ink)}
|
|
112
162
|
.dub-legend-off{opacity:.5}
|
|
113
|
-
.dub-legend-off .dub-legend-swatch{background:var(--
|
|
114
|
-
.dub-legend-reset{font:inherit;font-size:11px;background:0 0;border:0;padding:0;cursor:pointer;color:var(--
|
|
163
|
+
.dub-legend-off .dub-legend-swatch{background:var(--dub-fill-strong)}
|
|
164
|
+
.dub-legend-reset{font:inherit;font-size:11px;background:0 0;border:0;padding:0;cursor:pointer;color:var(--dub-link);text-decoration:underline}
|
|
115
165
|
.dub-legend-swatch{flex:none;width:11px;height:11px;border-radius:3px}
|
|
116
166
|
.dub-svg{display:block;width:100%;height:auto;overflow:visible}
|
|
117
|
-
.dub-grid{stroke:var(--
|
|
118
|
-
.dub-guide{stroke:var(--
|
|
167
|
+
.dub-grid{stroke:var(--dub-grid);stroke-width:1}
|
|
168
|
+
.dub-guide{stroke:var(--dub-guide);stroke-width:1;stroke-dasharray:3 3}
|
|
119
169
|
.dub-band-holiday{fill:rgba(22,163,74,.10)}
|
|
120
|
-
.dub-axis{fill:var(--
|
|
121
|
-
.dub-tip{position:absolute;top:18px;transform:translateX(-50%);pointer-events:none;box-sizing:border-box;width:172px;padding:8px 10px;border-radius:8px;border:.5px solid var(--
|
|
122
|
-
.dub-tip-title{font-weight:600;color:var(--
|
|
170
|
+
.dub-axis{fill:var(--dub-ink-3);font-size:10px;text-anchor:middle}
|
|
171
|
+
.dub-tip{position:absolute;top:18px;transform:translateX(-50%);pointer-events:none;box-sizing:border-box;width:172px;padding:8px 10px;border-radius:8px;border:.5px solid var(--dub-line);background:var(--dub-surface);box-shadow:0 6px 20px rgba(0,0,0,.18);font-size:11px;line-height:1.6;color:var(--dub-ink-2);z-index:2}
|
|
172
|
+
.dub-tip-title{font-weight:600;color:var(--dub-ink);margin-bottom:2px}
|
|
123
173
|
.dub-tip-row{display:flex;align-items:center;gap:6px;white-space:nowrap}
|
|
124
174
|
.dub-tip-label{flex:1}
|
|
125
|
-
.dub-tip-value{font-variant-numeric:tabular-nums;color:var(--
|
|
126
|
-
.dub-empty{padding:34px 0;text-align:center;font-size:12px;color:var(--
|
|
127
|
-
.dub-block{border:.5px solid var(--
|
|
175
|
+
.dub-tip-value{font-variant-numeric:tabular-nums;color:var(--dub-ink)}
|
|
176
|
+
.dub-empty{padding:34px 0;text-align:center;font-size:12px;color:var(--dub-ink-3)}
|
|
177
|
+
.dub-block{border:.5px solid var(--dub-line);border-radius:10px;padding:12px 14px;margin:12px 0}
|
|
128
178
|
.dub-block-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:8px}
|
|
129
179
|
.dub-block-title{font-size:13px;font-weight:600;flex:1}
|
|
130
|
-
.dub-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:12px;color:var(--
|
|
180
|
+
.dub-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:12px;color:var(--dub-ink-2);padding:3px 0}
|
|
131
181
|
.dub-table{width:100%;border-collapse:collapse;font-size:12px;margin:6px 0}
|
|
132
|
-
.dub-table th{text-align:left;font-weight:600;color:var(--
|
|
133
|
-
.dub-table td{padding:4px 8px 4px 0;border-bottom:.5px solid var(--
|
|
134
|
-
.dub-table td.dub-model{font-family:var(--
|
|
135
|
-
.dub-note{color:var(--
|
|
136
|
-
.dub-warn{color
|
|
182
|
+
.dub-table th{text-align:left;font-weight:600;color:var(--dub-ink-2);padding:4px 8px 4px 0;border-bottom:.5px solid var(--dub-line);white-space:nowrap}
|
|
183
|
+
.dub-table td{padding:4px 8px 4px 0;border-bottom:.5px solid var(--dub-line-soft);font-variant-numeric:tabular-nums;white-space:nowrap}
|
|
184
|
+
.dub-table td.dub-model{font-family:var(--dub-mono);color:var(--dub-ink)}
|
|
185
|
+
.dub-note{color:var(--dub-ink-3);font-size:11px;line-height:1.5}
|
|
186
|
+
.dub-warn{color:var(--dub-warn);font-size:11px}
|
|
137
187
|
/* The version handshake lives at the top of the dialog, above the tabs, so a mismatch is stated
|
|
138
188
|
on whichever tab the reader opened. */
|
|
139
|
-
.dub-version{padding:8px 18px;border-bottom:.5px solid var(--
|
|
189
|
+
.dub-version{padding:8px 18px;border-bottom:.5px solid var(--dub-line);background:var(--dub-surface-soft)}
|
|
140
190
|
/* Explanatory prose lives in these tooltips instead of under every block: the panel is a table of
|
|
141
191
|
numbers, and a wall of notes under each one is what made it hard to scan. */
|
|
142
192
|
.dub-info{flex:none;display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;border-radius:50%;border:1px solid currentColor;font-size:9px;font-style:normal;line-height:1;opacity:.5;cursor:help;user-select:none}
|
|
143
193
|
.dub-info:hover{opacity:.9}
|
|
144
|
-
.dub-foot{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:10px 18px;border-top:.5px solid var(--
|
|
145
|
-
.dub-foot input,.dub-foot select,.dub-block input,.dub-block select{font:inherit;font-size:12px;padding:2px 6px;border-radius:6px;border:.5px solid var(--
|
|
146
|
-
.dub-btn{font:inherit;font-size:12px;padding:3px 10px;border-radius:7px;border:.5px solid var(--
|
|
147
|
-
.dub-btn:hover{background:var(--
|
|
194
|
+
.dub-foot{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:10px 18px;border-top:.5px solid var(--dub-line);font-size:12px;color:var(--dub-ink-2)}
|
|
195
|
+
.dub-foot input,.dub-foot select,.dub-block input,.dub-block select{font:inherit;font-size:12px;padding:2px 6px;border-radius:6px;border:.5px solid var(--dub-line-strong);background:var(--dub-surface);color:inherit;width:88px}
|
|
196
|
+
.dub-btn{font:inherit;font-size:12px;padding:3px 10px;border-radius:7px;border:.5px solid var(--dub-line-strong);background:0 0;color:inherit;cursor:pointer;white-space:nowrap}
|
|
197
|
+
.dub-btn:hover{background:var(--dub-hover)}
|
|
148
198
|
.dub-btn:disabled{opacity:.5;cursor:default}
|
|
149
|
-
/* The one filled action in the plugin. --dsw-alias-brand-primary is a
|
|
150
|
-
token in this design system (DSH uses it for text, outlines and the switch track),
|
|
151
|
-
so filling with it left white text on near-white in the dark theme. The DeepSeek
|
|
152
|
-
|
|
153
|
-
.dub-btn-primary{border-color:transparent;background
|
|
154
|
-
.dub-btn-primary:hover{opacity:.9;background
|
|
199
|
+
/* The one filled action in the plugin. Not a theme token: --dsw-alias-brand-primary is a
|
|
200
|
+
*foreground* token in this design system (DSH uses it for text, outlines and the switch track),
|
|
201
|
+
so filling with it left white text on near-white in the dark theme. The DeepSeek blue is written
|
|
202
|
+
out here for the same reason the rest of the panel's palette is. */
|
|
203
|
+
.dub-btn-primary{border-color:transparent;background:#4176e6;color:#fff}
|
|
204
|
+
.dub-btn-primary:hover{opacity:.9;background:#4176e6}
|
|
155
205
|
/* Contributions heatmap: one square per calendar day of the last year. The five
|
|
156
206
|
steps are the same rgba green at rising opacity, so the ramp reads correctly on
|
|
157
207
|
both a light and a dark panel without a theme query. Each step sets fill for the
|
|
@@ -160,30 +210,30 @@ window.__ModuleLoader__.load({
|
|
|
160
210
|
.dub-hm-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:6px}
|
|
161
211
|
.dub-hm-title{flex:1;font-size:12px;font-weight:600}
|
|
162
212
|
.dub-hm-metrics{display:flex;gap:4px}
|
|
163
|
-
.dub-hm-metric{font:inherit;font-size:11px;padding:2px 8px;border-radius:7px;border:.5px solid var(--
|
|
164
|
-
.dub-hm-metric:hover{background:var(--
|
|
165
|
-
.dub-hm-metric-on{background:var(--
|
|
213
|
+
.dub-hm-metric{font:inherit;font-size:11px;padding:2px 8px;border-radius:7px;border:.5px solid var(--dub-line);background:0 0;color:inherit;cursor:pointer}
|
|
214
|
+
.dub-hm-metric:hover{background:var(--dub-hover)}
|
|
215
|
+
.dub-hm-metric-on{background:var(--dub-fill);border-color:transparent;font-weight:600}
|
|
166
216
|
/* The tooltip's percentages are squares of the grid, so it hangs off a wrapper that
|
|
167
217
|
is exactly as tall as the svg rather than off the block (title and legend included). */
|
|
168
218
|
.dub-hm-plot{position:relative}
|
|
169
219
|
.dub-hm-cell{stroke:none}
|
|
170
|
-
.dub-hm-l0{fill:var(--
|
|
220
|
+
.dub-hm-l0{fill:var(--dub-fill);background:var(--dub-fill)}
|
|
171
221
|
.dub-hm-l1{fill:rgba(45,164,78,.25);background:rgba(45,164,78,.25)}
|
|
172
222
|
.dub-hm-l2{fill:rgba(45,164,78,.45);background:rgba(45,164,78,.45)}
|
|
173
223
|
.dub-hm-l3{fill:rgba(45,164,78,.7);background:rgba(45,164,78,.7)}
|
|
174
224
|
.dub-hm-l4{fill:rgba(45,164,78,.95);background:rgba(45,164,78,.95)}
|
|
175
|
-
.dub-hm-month,.dub-hm-wd{fill:var(--
|
|
225
|
+
.dub-hm-month,.dub-hm-wd{fill:var(--dub-ink-3);font-size:9px}
|
|
176
226
|
/* The hover card is the chart's .dub-tip, so this only re-anchors it: the style is
|
|
177
227
|
shared, the position is a fraction of the grid. */
|
|
178
228
|
.dub-hm-tip{margin-top:5px}
|
|
179
229
|
.dub-hm-tip-up{margin-top:-5px;transform:translate(-50%,-100%)}
|
|
180
|
-
.dub-hm-foot{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding-top:6px;font-size:11px;color:var(--
|
|
230
|
+
.dub-hm-foot{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding-top:6px;font-size:11px;color:var(--dub-ink-3)}
|
|
181
231
|
.dub-hm-legend{margin-left:auto;display:inline-flex;align-items:center;gap:4px}
|
|
182
232
|
.dub-hm-swatch{display:inline-block;width:10px;height:10px;border-radius:2px}
|
|
183
233
|
/* The price-row editor. The block already styles its inputs at 88px wide, so the fields
|
|
184
234
|
that need another width are written as a two-class selector to outrank that rule. */
|
|
185
|
-
.dub-form{border-top:.5px solid var(--
|
|
186
|
-
.dub-form-row{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:12px;color:var(--
|
|
235
|
+
.dub-form{border-top:.5px solid var(--dub-line);margin-top:10px;padding-top:10px}
|
|
236
|
+
.dub-form-row{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:12px;color:var(--dub-ink-2);padding:3px 0}
|
|
187
237
|
.dub-form-row label{white-space:nowrap}
|
|
188
238
|
.dub-block .dub-form-key{width:250px}
|
|
189
239
|
.dub-block .dub-form-num{width:74px}
|
|
@@ -191,8 +241,8 @@ window.__ModuleLoader__.load({
|
|
|
191
241
|
.dub-form-check{display:inline-flex;align-items:center;gap:4px}
|
|
192
242
|
.dub-form-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end;padding-top:8px}
|
|
193
243
|
.dub-table-ops{display:flex;gap:6px}
|
|
194
|
-
.dub-op{font:inherit;font-size:11px;padding:1px 7px;border-radius:6px;border:.5px solid var(--
|
|
195
|
-
.dub-op:hover{background:var(--
|
|
244
|
+
.dub-op{font:inherit;font-size:11px;padding:1px 7px;border-radius:6px;border:.5px solid var(--dub-line-strong);background:0 0;color:inherit;cursor:pointer;white-space:nowrap}
|
|
245
|
+
.dub-op:hover{background:var(--dub-hover)}
|
|
196
246
|
`
|
|
197
247
|
|
|
198
248
|
/** Inject the stylesheet once per document, guarded the way shipped plugins do. */
|
package/lib/holidays-cn.json
CHANGED
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
"document": "国办发明电〔2025〕7号",
|
|
12
12
|
"published": "2025-11-04",
|
|
13
13
|
"url": "https://www.gov.cn/zhengce/zhengceku/202511/content_7047091.htm"
|
|
14
|
+
},
|
|
15
|
+
"2027": {
|
|
16
|
+
"document": "国办发明电〔2026〕10号",
|
|
17
|
+
"published": "2026-11-05",
|
|
18
|
+
"url": "https://www.gov.cn/zhengce/content/202611/content_7085101.htm"
|
|
14
19
|
}
|
|
15
20
|
},
|
|
16
21
|
"years": {
|
|
@@ -36,6 +41,18 @@
|
|
|
36
41
|
{ "name": "国庆节", "from": "2026-10-01", "to": "2026-10-07" }
|
|
37
42
|
],
|
|
38
43
|
"workdays": ["2026-01-04", "2026-02-14", "2026-02-28", "2026-05-09", "2026-09-20", "2026-10-10"]
|
|
44
|
+
},
|
|
45
|
+
"2027": {
|
|
46
|
+
"blocks": [
|
|
47
|
+
{ "name": "元旦", "from": "2027-01-01", "to": "2027-01-03" },
|
|
48
|
+
{ "name": "春节", "from": "2027-02-05", "to": "2027-02-13" },
|
|
49
|
+
{ "name": "清明节", "from": "2027-04-03", "to": "2027-04-05" },
|
|
50
|
+
{ "name": "劳动节", "from": "2027-05-01", "to": "2027-05-05" },
|
|
51
|
+
{ "name": "端午节", "from": "2027-06-09", "to": "2027-06-09" },
|
|
52
|
+
{ "name": "中秋节", "from": "2027-09-15", "to": "2027-09-15" },
|
|
53
|
+
{ "name": "国庆节", "from": "2027-10-01", "to": "2027-10-07" }
|
|
54
|
+
],
|
|
55
|
+
"workdays": ["2027-01-31", "2027-02-14", "2027-05-08", "2027-09-26", "2027-10-09"]
|
|
39
56
|
}
|
|
40
57
|
}
|
|
41
58
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-usage-badge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "DeepSeek Harness 的用量徽标插件:侧边栏底部一行显示当天的 token 总量与估算费用,点开可看 24 小时 / 近 7 日 / 近 30 日 / 近 12 个月 / 按年用量,按 provider 筛选,并按 DeepSeek 官方定价与中国法定节假日计价。A usage badge plugin for DeepSeek Harness: today's tokens and estimated spend in the sidebar, with 24h / 7d / 30d / 12-month and per-year charts, official DeepSeek pricing and Chinese public holidays.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|