@ds-hu/dsh-token-quota 2.5.0 → 2.6.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/README.md CHANGED
@@ -1,13 +1,36 @@
1
1
  # dsh-token-quota
2
2
 
3
- DeepSeek Harness 用量监控插件。查看当前会话对应供应商的周期限额、账户余额、报告费用和今日 Token 用量,支持 Windows 桌面端与 Web 界面。
3
+ [![npm](https://img.shields.io/npm/v/@ds-hu/dsh-token-quota)](https://www.npmjs.com/package/@ds-hu/dsh-token-quota)
4
+ [![verify](https://github.com/ds-hu/dsh-usage-monitor/actions/workflows/verify.yml/badge.svg)](https://github.com/ds-hu/dsh-usage-monitor/actions/workflows/verify.yml)
5
+ [![license](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
6
+ [![DSH](https://img.shields.io/badge/DSH-%E2%89%A5%200.1.7--rc.2-4c6ef5)](#安装)
4
7
 
5
- 当前版本:**v2.5.0**。要求 **DSH ≥ 0.1.7-rc.2**;真实桌面及 Web 验收环境为 **DSH 0.2.0-rc.2**。
8
+ DeepSeek Harness 用量监控插件:查看当前会话对应供应商的周期限额、账户余额、报告费用与今日 Token 用量,
9
+ 也能统计任何未内置供应商的 Token 消耗。支持 Windows 桌面端与 Web 界面。
10
+
11
+ 中文 · [English](README.en.md)
12
+
13
+ ![用量监控概览:可查额度的供应商与一条「仅计量」行](docs/images/overview.png)
14
+
15
+ 当前版本:**v2.6.0**。要求 **DSH ≥ 0.1.7-rc.2**;真实桌面及 Web 验收环境为 **DSH 0.2.0-rc.2**。
6
16
 
7
17
  Plugins 面板中显示为 **用量监控**(英文 **Usage monitor**):图标是根目录 `icon.svg`,标题与一句话说明来自 `locale/zh.json` 与 `locale/en.json` 的 `meta`。
8
18
 
9
19
  [npm](https://www.npmjs.com/package/@ds-hu/dsh-token-quota) · [问题反馈](https://github.com/ds-hu/dsh-usage-monitor/issues)
10
20
 
21
+ ## v2.6.0 更新
22
+
23
+ - **任意供应商的 Token 统计**:DSH 里未归属到内置供应商的 LLM 路由,跑过一次就在概览里多出一条
24
+ **「仅计量」**行——只有今日与近 N 天的 Token、五分量分布与归一比例,不查额度、不画百分比、
25
+ 不发任何新的外部请求。此前这类路由的用量被整段丢弃。
26
+ - **今日总量改为「概览列表里每一行之和」**:一行只要有今日用量就一定在列表里、也一定计入,
27
+ 可以逐行相加核对;「当前」标从此只表示小组件在用哪一家,不再等于「计入」。
28
+ - **DeepSeek 桌面登录与 API Key 合并成一行**:登录态产生的 Token 也计入今日用量,
29
+ 只有额度查询仍需要 API Key。
30
+ - 路由后来被插件支持时,`local:<route>` 的历史用量并入正式供应商,不从 0 开始;收编过程中
31
+ 在途步骤的替换样本不再被重复计入。
32
+ - 「近 N 天」的 N 取自用量保留期,不再一律写「近 7 天」。
33
+
11
34
  ## v2.5.0 更新
12
35
 
13
36
  - 展开行的「Today」换成选择器:可切「今天 / 近 7 天」,token 数、归一比例与五分量分布条一起换成该区间的合计;图表固定 7 天,不跟着变。
@@ -15,84 +38,11 @@ Plugins 面板中显示为 **用量监控**(英文 **Usage monitor**):图
15
38
  - 估算按取样内的斜率折算(本地 token ÷ Δpp),不再除以累计已用%:月窗口比本地留存长时,旧算法只拿得到最近几天的 token,会把总量算成一半。
16
39
  - 修复图表柱值数字被折线压花:数字底下垫一层卡片底色,折线从数字背后穿过。
17
40
 
18
- ## v2.4.1 更新
19
-
20
- - 包名改为 `@ds-hu/dsh-token-quota`:`dsh plugin add @ds-hu/dsh-token-quota` 直接安装,不必再下载压缩包;插件 id、设置命名空间与数据目录不变,升级不需要迁移。
21
- - 发布改由 CI 执行:`fork-vX.Y.Z` 标签触发测试与 `npm publish`,凭据走 OIDC 可信发布并附 provenance,仓库里不存 npm token。
22
-
23
- ## v2.4.0 更新
24
-
25
- - 四个浮层收敛为单一面板:删除用量浮卡、供应商限额明细弹窗与用量统计弹窗,单击侧栏卡片直接打开「用量监控」;面板只剩「概览」「供应商」两个页签,原「设置」页签并入供应商页底部的折叠「全局设置」。卡片进入默认概览,宿主「设置 → 用量监控」进入默认供应商。
26
- - 明细与统计并入概览的供应商行:一次只展开一行,展开区给出分组指标表格、今日五分量分布与归一比例、DeepSeek 计费时段拆分与额度历史图;额度历史改为首次展开时按需取一次,同一面板会话内复用。
27
- - 默认视图不再常显解释文案:说明收进可展开的提示,`·` 拼接改为表格或徽标,词典键由 198 降到 158;会改变判断的告警(缺次年节假日、保存失败、取数失败)仍常显。
28
- - 移动端不再有独立分支:视口 < 1024px 也打开同一面板,不再有一次点击直达详情的窄屏路径。
29
- - 侧栏卡片补留白(横向 6px、上下 8px),长倒计时不再贴边被裁;修复指标表格列头与数值错位、展开明细重复画两条分布条、图表底部单独一行「近 N 天」,以及更新插件后样式仍是旧版的问题。
30
-
31
- ## v2.3.0 更新
32
-
33
- - 统计图的额度折线补上采样锚点,并在换窗口(已用百分比回落)处用同色虚线把两段接上:断开表示换了窗口,不是画漏了;实线仍不跨窗口连线。
34
- - 统计图支持悬停读数:指针移到图上(聚焦后可用 ←/→,触屏点一下即固定)显示那一刻的时刻、当日 Token 与各窗口剩余百分比;读数取最近一次采样、不插值,靠近采样点会吸附到实际采样时刻。
35
- - 修复采样圆点被横向拉成椭圆的问题:锚点改画在不参与坐标拉伸的叠加层上,半径按像素。
36
-
37
- ## v2.2.0 更新
38
-
39
- - 新增用量统计弹窗:从卡片标题行(供应商名 + 今日用量)或明细头部的「统计」进入,给出今日总量、五分量分布与归一比例,并把每日 Token 消耗(柱)与各窗口剩余额度(线)画在同一张时间图上,共用时间轴与日期刻度。
40
- - 新增额度历史采集:每次查询成功按「供应商 × 指标」记录已用百分比的变化点(不变的值不重复记),存 `$DSH_HOME/dsh-token-quota/quota.json`,磁盘保留 14 天;历史只在打开统计时随 `/state?quota=1` 传输。
41
- - 详情弹窗的今日用量旁显示归一比例「缓存命中 : 缓存未命中 : 输出」(如 `30.0 : 1.00 : 0.150`)与同色的三段分布条。
42
- - 新增关注限额:供应商配置页可勾选卡片要看哪些限额并调序,卡片第 2 行可多条并排,每条自带短标签、百分比与该窗口自己的重置倒计时。
43
- - 本地用量文件升级为版本 2:小时桶改存五项互不相交的分量,旧文件的总数归入「未细分」,不猜分布也不丢总量。
44
- - 密钥改存凭据引用(`apiKeyEnv`),本体不再落进 profile 配置;旧配置里的 `apiKey` 继续生效,无需迁移。
45
- - 手机/窄布局(视口 < 1024px)点击侧栏入口直达「供应商限额明细」;Plugins 面板显示「用量监控」名称与图标。
46
-
47
- ## v2.1.0 更新
48
-
49
- - 新增 DeepSeek 峰谷计费时段:侧栏常显当前档位(峰价/谷价)与「下次切换」,浮卡与明细给出成因(法定节假日/周末)和北京时间,刷新历史新增「时段」列。时段判定在宿主侧完成,客户端只消费结论。
50
- - 新增今日用量时段拆分:DeepSeek 的当日用量按小时桶推导为高峰/空闲/未标记三段并给出占比,其他供应商不串用该口径。
51
- - 新增节假日覆盖表:设置页可粘贴 `YYYY-MM-DD` 清单替换内置表;缺次年安排时在同一处点名,不再静默把工作日假期当峰价。
52
- - 常驻卡片重新排版为四行:plan 名(不再显示模型名)、周窗口已用百分比进度条、今日 Token 用量与该窗口重置倒计时、计费时段档位。阈值颜色融入进度条,取消阈值圆点与插件标题行。
53
- - Token 量级统一 3 位有效数字(`1.32M`、`12.0K`、`999.6K → 1.00M`),不再固定一位小数;用量浮卡不再重复计费时段块。
54
-
55
- ## v2.0.5 更新
56
-
57
- - 修复智谱/Z.ai Coding Plan 查询失效:供应商已把用量窗口的类型名由 `TOKENS_LIMIT` 改名为 `CREDIT_LIMIT`,插件此前只认旧名,窗口全部被跳过,供应商显示为「暂时无法获取」。
58
- - 窗口改按 `unit` 标识分类(5 小时窗口、周窗口、月度 MCP 上限),不再依赖数组位置;新增周窗口与供应商公布的重置时刻。新旧拼写并存时都能读取,重复或无法命名的窗口如实报错,不猜一个窗口顶上。
59
- - 业务失败改按响应信封判定:密钥失效(HTTP 200 响应体内的 `code:401`)报鉴权失败,不再误报成「响应缺少 data.limits」。
60
-
61
- ## v2.0.4 更新
62
-
63
- - 修复 Command Code 等供应商未公布重置时刻时错误显示“1月1日”的问题。
64
- - 统一验证日期文案与原始时刻,零值、无效日期和误传入毫秒字段的秒级值显示为未知,不推算重置时间。
65
- - 保留有效毫秒、数字串和带时区日期的准确时刻,OpenCode Go 的正常窗口倒计时保持不变。
66
-
67
- ## v2.0.3 更新
68
-
69
- - 点击侧栏打开贴在卡片上方的用量浮卡,显示供应商主指标、查询状态、刷新间隔和上次刷新时间。
70
- - 浮卡底部提供“详情”“设置”“关闭”;完整供应商明细和现有配置面板继续保留。
71
- - 浮卡适配完整、紧凑和图标侧栏,支持再次点击、Esc 和外部点击关闭,随窗口变化调整位置。
72
-
73
- ## v2.0.2 更新
74
-
75
- - 修复停用、重新启用和宿主重启插件时的 `webserver: duplicate exact route` 启用失败。
76
- - 启动失败时释放已登记的资源;某项清理报错不会阻止其他资源释放。
77
-
78
- ## v2.0.1 更新
79
-
80
- - 侧栏单击恢复独立「供应商限额明细」弹窗,按 v1.4.0 分栏展示已添加供应商与周期指标,刷新历史恢复为表格,沿用当前主题配色和按钮样式。
81
- - 详情中的「设置」按钮进入现有概览、供应商、设置面板;保留完整/紧凑侧栏和显示偏好。
82
- - 修复供应商扫描中的 `cannot get required service "settings" in inactive context` 错误;设置服务暂不可用时保留最近成功目录,恢复后可重新扫描。
83
-
84
- ## v2.0.0 更新
85
-
86
- - 侧栏支持完整与紧凑显示,单击直接打开概览,显示偏好自动保存。
87
- - 用量监控面板提供「概览」「供应商」「设置」三个页签,侧栏入口与宿主设置入口使用相同内容。
88
- - 概览支持需关注筛选、自适应一列或两列布局,保留各周期与币种的独立指标。
89
- - 配置编辑保留未保存草稿,提供放弃修改确认;保存失败保留输入,避免旧请求覆盖新的编辑内容。
90
- - 查询失败时保留上次成功数据并标明状态;未知值与真实零值分别显示。
91
- - 适配 Windows 桌面端的当前会话选择、原生设置入口和侧栏收起/恢复。
41
+ 更早的逐版本记录见 [CHANGELOG](CHANGELOG.md)。
92
42
 
93
43
  ## 安装
94
44
 
95
- 插件从 npm 安装:**`@ds-hu/dsh-token-quota`**。离线部署时可以在源码目录 `npm pack` 打出 `ds-hu-dsh-token-quota-2.5.0.tgz`,用压缩包安装。插件包不包含 DeepSeek Harness;首次安装可能需要联网下载依赖。
45
+ 插件从 npm 安装:**`@ds-hu/dsh-token-quota`**。离线部署时可以在源码目录 `npm pack` 打出 `ds-hu-dsh-token-quota-2.6.0.tgz`,用压缩包安装。插件包不包含 DeepSeek Harness;首次安装可能需要联网下载依赖。
96
46
 
97
47
  ### Windows 桌面端
98
48
 
@@ -107,7 +57,7 @@ $desktopCli = 'D:\AI\DeepSeek- Harness\resources\runtime\cli\bin\dsh.cmd'
107
57
  离线安装时把包名换成压缩包路径:
108
58
 
109
59
  ```powershell
110
- $archivePath = (Resolve-Path './ds-hu-dsh-token-quota-2.5.0.tgz').Path
60
+ $archivePath = (Resolve-Path './ds-hu-dsh-token-quota-2.6.0.tgz').Path
111
61
  & $desktopCli plugin --profile desktop add "file:$archivePath"
112
62
  ```
113
63
 
@@ -129,14 +79,14 @@ dsh --profile web
129
79
  Linux / macOS:
130
80
 
131
81
  ```bash
132
- dsh plugin --profile web add "file:$(pwd)/ds-hu-dsh-token-quota-2.5.0.tgz"
82
+ dsh plugin --profile web add "file:$(pwd)/ds-hu-dsh-token-quota-2.6.0.tgz"
133
83
  dsh --profile web
134
84
  ```
135
85
 
136
86
  Windows PowerShell:
137
87
 
138
88
  ```powershell
139
- $archivePath = (Resolve-Path './ds-hu-dsh-token-quota-2.5.0.tgz').Path
89
+ $archivePath = (Resolve-Path './ds-hu-dsh-token-quota-2.6.0.tgz').Path
140
90
  dsh plugin --profile web add "file:$archivePath"
141
91
  dsh --profile web
142
92
  ```
@@ -179,20 +129,22 @@ dsh plugin --profile web add "link:$pluginDirectory"
179
129
  且**任一已填即视为手动接管**(扫描不再改动该供应商的密钥字段)。「测试连接」对编辑器里刚填、还没保存的引用
180
130
  也会先解析一次,不必先保存再测。
181
131
 
182
- **DeepSeek 桌面账户登录凭证与 API Key 不可互换。** 仅登录桌面账户时,请在现有 DeepSeek 供应商配置中填写同一账户的 API Key,或配置 Harness 的 DeepSeek API Key 路由后重新扫描。
132
+ **DeepSeek 桌面登录的 Token 会计入,额度查询仍需要 API Key。** 登录态产生的调用以 `deepseek-account` 路由上报,插件把它并进同一行 DeepSeek,因此今日与近 N 天的 Token 不配 Key 也能看到;余额只能按 API Key 查——只想看额度时,请在现有 DeepSeek 供应商配置中填写同一账户的 API Key,或配置 Harness 的 DeepSeek API Key 路由后重新扫描。
183
133
 
184
134
  ## 侧栏与面板
185
135
 
186
- 完整侧栏分三段:**plan 名**(供应商显示名,不显示模型名)、**关注限额进度条**(可多条并排,每条自带真名短标签、已用百分比与该窗口的重置倒计时)、以及常显的**计费时段档位**(峰价/谷价与下次切换)。阈值颜色直接落在进度条上:绿 < 警告 < 黄 < 临界 < 红;多条时每条按自己的百分比分档,并共享列宽严格对齐。没有配置关注限额时退回**周窗口**,没有周窗口才改用已用比例最高的有效窗口,只有余额或报告费用时保留文字而不画条;旧数据与已停用保持中性色。今日 Token 用量不再占侧栏一行,改在概览的供应商行里按供应商展示(无障碍标签仍可读到)。Token 数量一律 3 位有效数字(如 `1.32M`、`12.0K`)。紧凑侧栏保留供应商名与主指标文字。独立箭头切换显示状态,偏好保存在当前浏览器;宽度不足时自动使用紧凑显示,恢复宽度后恢复用户选择。
136
+ 完整侧栏分三段:**plan 名**(供应商显示名,不显示模型名)、**关注限额进度条**(可多条并排,每条自带真名短标签、已用百分比与该窗口的重置倒计时)、以及常显的**计费时段档位**(峰价/谷价与下次切换)。阈值颜色直接落在进度条上:绿 < 警告 < 黄 < 临界 < 红;多条时每条按自己的百分比分档,并共享列宽严格对齐。没有配置关注限额时退回**周窗口**,没有周窗口才改用已用比例最高的有效窗口,只有余额或报告费用时保留文字而不画条;旧数据与已停用保持中性色。今日 Token 用量不再占侧栏一行,改在概览的供应商行里按供应商展示(无障碍标签仍可读到)。没有可查询指标的「仅计量」行第 2 行不画条,改显这个窗口自己的量。Token 数量一律 3 位有效数字(如 `1.32M`、`12.0K`)。紧凑侧栏保留供应商名与主指标文字。独立箭头切换显示状态,偏好保存在当前浏览器;宽度不足时自动使用紧凑显示,恢复宽度后恢复用户选择。
187
137
 
188
138
  **关注限额**在「供应商 → 配置」页勾选:勾选顺序即卡片排列顺序,行内 ↑↓ 调整,未勾选 = 用默认规则。候选项来自上一次成功查询里带身份的可画条目(`5h 滚动`/`周用量`/`月用量`/`allowance` 等);已勾选但这次查询没返回的项标注「当前不可用」并暂不画条,数据回来自动恢复。配置只影响侧栏卡片第 2 行,不改变概览、紧凑行与供应商明细里的主指标口径。
189
139
 
190
140
  单击侧栏卡片打开「用量监控」面板。面板只有「概览」「供应商」两个页签;卡片进入默认概览,宿主「设置 → 用量监控」进入默认供应商。对话框形态下按 Esc、点遮罩或「关闭」收起,焦点回到侧栏卡片。
191
141
 
192
- 概览按已添加供应商列出名称、状态、主指标与今日量;一次只展开一行。展开后给出按额度、余额、报告分组的指标表格,今日五分量分布(只用一条五分量条)与**归一比例**(「缓存命中 : 缓存未命中 : 输出」,未命中固定为 1,例如 `30.0 : 1.00 : 0.150`,三段各保留 3 位有效数字),DeepSeek 的计费时段拆分,以及一张**把柱状图与折线图合成的时间图**:柱是每日 Token 消耗,每根有量的柱标出当日用量(窗口过长时只标峰值),图顶给出窗口合计;折线是各窗口的剩余额度,共用同一条时间轴;折线在额度回落处断开(说明换了新窗口),图例给出各窗口当前剩余百分比。首次展开时按需取一次额度历史,同一次面板会话内复用。默认收起的刷新历史表格最多展示最近 50 条。
142
+ 概览按已添加供应商列出名称、状态、主指标与今日量(「仅计量」行也在其中,状态写作「仅计量」),一次只展开一行;列表上方的今日总量就是这些行今日量之和,可以逐行相加核对。展开后给出按额度、余额、报告分组的指标表格,今日五分量分布(只用一条五分量条)与**归一比例**(「缓存命中 : 缓存未命中 : 输出」,未命中固定为 1,例如 `30.0 : 1.00 : 0.150`,三段各保留 3 位有效数字),DeepSeek 的计费时段拆分,以及一张**把柱状图与折线图合成的时间图**:柱是每日 Token 消耗,每根有量的柱标出当日用量(窗口过长时只标峰值),图顶给出窗口合计;折线是各窗口的剩余额度,共用同一条时间轴;折线在额度回落处断开(说明换了新窗口),图例给出各窗口当前剩余百分比。首次展开时按需取一次额度历史,同一次面板会话内复用。默认收起的刷新历史表格最多展示最近 50 条。
193
143
 
194
144
  「供应商」页签列出已添加目录、启用开关、配置入口与重新扫描;底部折叠「全局设置」(查询间隔、用量保留期、节假日覆盖表;缺次年时在摘要外点名告警)与侧栏显示偏好。可添加供应商默认收起。
195
145
 
146
+ ![供应商页:配置、启用与重新扫描](docs/images/providers.png)
147
+
196
148
  | 页签 | 内容 |
197
149
  | --- | --- |
198
150
  | 概览 | 今日总量、全部/需关注筛选、可展开的供应商明细、默认收起的刷新历史。 |
@@ -202,7 +154,7 @@ dsh plugin --profile web add "link:$pluginDirectory"
202
154
 
203
155
  ## 数据含义
204
156
 
205
- - **今日用量**:当前供应商通过 Harness 产生的当日 Token 用量,按宿主本地日统计。独立 CLI 或其他应用中的调用不计入。
157
+ - **今日用量**:概览列表里每一行(已添加供应商,含仅计量行)通过 Harness 产生的当日 Token 用量之和,按宿主本地日统计。一行只要有今日用量就必然在列表里,所以这个合计可以逐行相加核对。独立 CLI 或其他应用中的调用不计入。
206
158
  - **用量分布**:今日用量按 DSH 的互不相交口径拆成五项(未缓存输入、输出、缓存命中、缓存写入、未细分),五项之和恒等于今日总量。比例只取前三项并归一到「缓存未命中 = 1」。
207
159
  - **剩余额度**:按 `100 − 已用百分比` 派生。额度在一个窗口内只增不减,因此百分比回落即表示换了新窗口,折线在此断开。只对带百分比的窗口(`5h`/`周`/`月` 等)成立;余额与报告费用没有百分比,不进折线。
208
160
  - **周期限额**:百分比表示已用比例;常驻卡片第 2 行画的是用户配置的**关注限额**(可多条、按配置顺序),没配置时固定取周窗口、没有周窗口再取已用比例最高的有效窗口;每条的重置时间始终对应它自己的窗口。概览与紧凑侧栏的主指标仍取已用比例最高的有效窗口。
@@ -231,6 +183,20 @@ dsh plugin --profile web add "link:$pluginDirectory"
231
183
 
232
184
  普通 API Key、Management Key 与 Admin Key 分别配置。插件不会将普通聊天密钥用于组织管理接口,也不会覆盖手动填写的密钥或重新启用显式关闭的供应商。
233
185
 
186
+ ### 任意供应商(仅计量)
187
+
188
+ 上表没有的供应商也有 token 统计:DSH 里未归属到上表的 LLM 路由,产生过一次真实调用后就会在概览里出现一条
189
+ **「仅计量」**行——只有今日与近 N 天的 Token、五分量分布与归一比例,不查额度、不画百分比、不发新的外部请求。
190
+ 此前这类路由的用量被整段丢弃,「今日总量」因此比真实调用少一块。
191
+
192
+ - 行由流量产生:不能手工添加,也不能停用(没有可关掉的采集器);在「供应商」页可改名,默认名取路由 id 或 DSH 的显示名。
193
+ 行跟着本地用量一起进退:保留期(默认 7 天)内没有新用量、旧桶被清理后,这一行也随之消失。
194
+ - 卡片的第 1 行仍是名字与今日数,第 2 行没有进度条,给的是这个窗口(近 N 天,N 取保留期)的量。
195
+ - 这些行恒进「今日总量」,但不属于「需关注」,也不进关注限额与阈值分档;明细里没有指标表,也没有「数据时间」——它从不查询。
196
+ - 路由后来被本插件支持时,`local:<route>` 的历史用量并入正式供应商,不从 0 开始,并入前后今日总量不变。
197
+ - DeepSeek 桌面登录(`deepseek-account`)与 API Key 是同一条账户的两条路,合并成同一行 DeepSeek,不会出现两行重复记账;
198
+ 只有登录态、没有可查额度的 Key 时,这一行照样出现在概览里并计入今日总量(只是状态显示「未查询」)。
199
+
234
200
  官方查询仅接受对应的 HTTPS 主机与已知基础路径;不符合要求的地址不会发送请求。OpenCode 与 Command Code 使用兼容接口,其可用性取决于供应商接口。表格表示插件支持的查询能力,不代表所有真实账户均已完成验证。
235
201
 
236
202
  ## 数据保存与升级
@@ -248,6 +214,7 @@ dsh plugin --profile web add "link:$pluginDirectory"
248
214
  ## 限制
249
215
 
250
216
  - 仅观测经过 Harness 的调用;本机 Codex CLI 登录状态和独立 CLI 用量不在范围内。
217
+ - 「仅计量」行只有 Token:没有额度、余额与百分比可看,也不能手工添加或停用。
251
218
  - 供应商未返回重置时间时,不推算或补造时间;无限额度和未知余额保留未知。
252
219
  - 趋势图只覆盖本地记录:额度折线依赖插件运行期间采样,装插件之前的历史无从补录;额度历史磁盘保留 14 天,展示窗口跟随用量保留期(默认 7 天),期间没有查询就没有点。
253
220
  - 本地用量统计与供应商账单可能因周期、时区和统计范围不同而不同。
@@ -269,14 +236,14 @@ npm pack
269
236
  真实宿主验收使用独立数据目录与 profile,不修改日常配置:
270
237
 
271
238
  ```powershell
272
- npm run test:desktop -- --app 'C:/path/DeepSeek Harness.exe' --package 'C:/path/ds-hu-dsh-token-quota-2.5.0.tgz' --output '.scratch/desktop-v2-check'
273
- node scripts/verify-web.mjs --app 'C:/path/DeepSeek Harness.exe' --package 'C:/path/ds-hu-dsh-token-quota-2.5.0.tgz' --output '.scratch/web-v2-check'
239
+ npm run test:desktop -- --app 'C:/path/DeepSeek Harness.exe' --package 'C:/path/ds-hu-dsh-token-quota-2.6.0.tgz' --output '.scratch/desktop-v2-check'
240
+ node scripts/verify-web.mjs --app 'C:/path/DeepSeek Harness.exe' --package 'C:/path/ds-hu-dsh-token-quota-2.6.0.tgz' --output '.scratch/web-v2-check'
274
241
  npm run test:lifecycle -- --app 'C:/path/DeepSeek Harness.exe' --output '.scratch/lifecycle-v2-check'
275
242
  ```
276
243
 
277
244
  输出目录需为未使用过的目录。供应商解析测试使用模拟响应;真实 Desktop 与 Web 验收检查安装、界面、配置持久化和重新扫描,不发送聊天消息或配置真实供应商密钥。
278
245
 
279
- [桌面兼容性说明](docs/windows-desktop-adaptation.md) · [单一面板重构记录](docs/single-panel-refactor-execution.md) · [ADR-0002](docs/adr/0002-single-monitor-surface.md) · [侧栏与设置实施记录](docs/sidebar-settings-iteration-plan.md) · [v2.5.0 发布记录](docs/v2.5.0-release-execution.md)
246
+ [桌面兼容性说明](docs/windows-desktop-adaptation.md) · [单一面板重构记录](docs/single-panel-refactor-execution.md) · [ADR-0002](docs/adr/0002-single-monitor-surface.md) · [侧栏与设置实施记录](docs/sidebar-settings-iteration-plan.md) · [v2.5.0 发布记录](docs/v2.5.0-release-execution.md) · [v2.6.0 发布记录](docs/v2.6.0-release-execution.md)
280
247
 
281
248
  ## 来源与致谢
282
249
 
@@ -286,4 +253,4 @@ npm run test:lifecycle -- --app 'C:/path/DeepSeek Harness.exe' --output '.scratc
286
253
 
287
254
  ## 许可证
288
255
 
289
- MIT。
256
+ MIT。完整文本见 [LICENSE](LICENSE),其中一并保留了上游 shxtmaker 的版权声明。
Binary file
Binary file
package/lib/client.js CHANGED
@@ -98,7 +98,17 @@
98
98
  tone: supplier.state === "err" || supplier.enabled === false || status === "unknown" ? "neutral" : status,
99
99
  warning: supplier.state !== "err" && supplier.enabled !== false && ["warn", "crit"].includes(status) ? tr(status === "crit" ? "stateCrit" : "stateWarn") : null };
100
100
  };
101
- const selectPrimaryMetric = (supplier, tr = (key) => key) => {
101
+ /** 仅计量供应商:只有本地 token,没有任何可查询的额度。 */
102
+ const isMeteredSupplier = (supplier) => supplier?.queryable === false;
103
+ /** 仅计量行「近 N 天」的合计:与图表同一份 usageDays,不另算口径。 */
104
+ const weekTokensOf = (supplier) => (Array.isArray(supplier?.usageDays)
105
+ ? supplier.usageDays.reduce((sum, day) => sum + (Number(day?.total) || 0), 0) : 0);
106
+ const selectPrimaryMetric = (supplier, tr = (key) => key, windowDays = 7) => {
107
+ // 仅计量行没有指标可选:主指标就是它自己的量。今日在行尾另给一次是既有版式。
108
+ if (isMeteredSupplier(supplier)) {
109
+ const text = tr("windowTokens", { d: windowDays, n: fmtBig(weekTokensOf(supplier)) });
110
+ return { kind: "usage", value: text, label: "", name: "", reset: null, tone: "neutral", more: 0, text };
111
+ }
102
112
  const entries = supplier?.entries || [];
103
113
  const entry = tightestOf(entries) || entries.find(e => e.kind === "bal") || entries.find(e => e.kind === "cost") || entries[0];
104
114
  if (!entry) return { kind: "unknown", value: "—", label: "", name: "", reset: null, tone: "neutral", more: 0, text: "—" };
@@ -134,10 +144,12 @@
134
144
  columns: [{ key: "name", labelKey: "colMetric" }, ...METRIC_COLUMNS[kind]], rows,
135
145
  }));
136
146
  };
137
- const supplierStatus = (s, tr) => s?.state === "err"
147
+ const supplierStatus = (s, tr) => isMeteredSupplier(s) ? tr("meteredOnly") : s?.state === "err"
138
148
  ? tr(s.entries?.length ? "staleFailure" : "unavailable")
139
149
  : s?.enabled === false ? tr("disabled") : !s?.entries?.length ? tr("notQueried") : tr(stateToken(s));
140
- const todayTotal = (state) => (state?.suppliers || []).filter(s => s.current)
150
+ // 今日总量 = 列表里各行的和(口径可对账):只要这一行有 token,它就在列表里、也在总量里。
151
+ // 旧口径只加 current 行,于是「未配置但在跑」的供应商 token 会既看不见也不算数。
152
+ const todayTotal = (state) => (state?.suppliers || []).filter(s => s.added !== false)
141
153
  .reduce((sum, s) => sum + (typeof s.todayTokens === "number" ? s.todayTokens : 0), 0);
142
154
 
143
155
  // ---------- 今日 Token 分布(数据来自宿主 todayUsage / usageDays,五分量互不相交) ----------
@@ -716,6 +728,8 @@
716
728
  enabled: !!sup.enabled, baseUrl: sup.baseUrl || sup.baseUrlDefault || "",
717
729
  warnPct: sup.warnPct ?? 80, critPct: sup.critPct ?? 95,
718
730
  apiKeyEnv: sup.apiKeyEnv || "",
731
+ // 仅计量行只有一个可改的东西:显示名(存进 config.meteredNames[route])。
732
+ ...(isMeteredSupplier(sup) ? { name: sup.name || "" } : {}),
719
733
  // 关注限额:有序数组;空数组 = 未配置(卡片走默认规则)。草稿复制一份,避免直接改 state。
720
734
  displayKeys: Array.isArray(sup.displayKeys) ? [...sup.displayKeys] : [],
721
735
  ...Object.fromEntries((sup.meta?.needs || []).map((need) => [need.key, need.secret ? "" : (sup[need.key] ?? "")])),
@@ -751,9 +765,15 @@
751
765
  const compact = narrow || preference === "compact";
752
766
  const active = state?.active;
753
767
  const supplier = active && state?.suppliers?.find(s => s.id === active.supplierId);
754
- const metric = selectPrimaryMetric(supplier, t);
768
+ // 「近 N 天」的 N 来自保留期(retentionDays < 7 时窗口确实更短),不是写死的 7。
769
+ const windowDays = state?.poll?.usageHistoryDays || 7;
770
+ const metric = selectPrimaryMetric(supplier, t, windowDays);
755
771
  const name = active?.name || t("noneActive");
756
772
  const todayCount = typeof supplier?.todayTokens === "number" ? supplier.todayTokens : 0;
773
+ // 仅计量行没有条也没有百分比:第 2 行直接给这个窗口里它自己的量(今日 chip 已在第 1 行)。
774
+ const metricText = isMeteredSupplier(supplier)
775
+ ? t("windowTokens", { d: windowDays, n: fmtBig(weekTokensOf(supplier)) })
776
+ : metric.text;
757
777
  const bars = selectBarMetrics(supplier, t);
758
778
  const resetText = bars.length ? bars.map(bar => bar.reset).filter(Boolean).join(" · ") : metric.reset;
759
779
  const failure = loadError ? t(state ? "staleFailure" : "unavailable") : supplier?.state === "err" ? supplierStatus(supplier, t) : "";
@@ -766,7 +786,7 @@
766
786
  seasonUsesBeijingNote() ? t("seasonBeijingOnly") : null].filter(Boolean).join(" · ")
767
787
  : "";
768
788
  // 卡片上的可见文字只留数据;完整状态(今日口径、连接、峰谷成因)进 aria/title。
769
- const title = [name, metric.text, resetText, seasonTitle, failure, t("todayScope"), t("today", { n: state ? fmtBig(todayTotal(state)) : "—" }), connText].filter(Boolean).join(" · ");
789
+ const title = [name, metricText, resetText, seasonTitle, failure, t("todayScope"), t("today", { n: state ? fmtBig(todayTotal(state)) : "—" }), connText].filter(Boolean).join(" · ");
770
790
  return h(React.Fragment, null,
771
791
  !wide ? h("button", { type: "button", ref: entryRef, "data-qm-entry": "", className: "qm-rail", "aria-label": t("title"), "aria-haspopup": "dialog", "aria-expanded": !!panel.host, title, onClick: activateEntry }, "◉") :
772
792
  h("div", { "data-qm-linerow": "", "data-qm-density": compact ? "compact" : "expanded", ref: widthRef, className: "qm-footer" },
@@ -791,7 +811,7 @@
791
811
  h("i", { className: `qm-bar-fill ${bar.tone}`, style: { width: `${Math.max(0, Math.min(100, bar.entry.pct))}%` } })),
792
812
  h("span", { key: `p${i}`, className: "qm-bar-pct" }, pctText(bar.entry.pct)),
793
813
  bar.reset ? h("span", { key: `r${i}`, className: "qm-bar-reset" }, bar.reset) : null,
794
- ])) : h("div", { className: "qm-l2" }, h("span", { className: "qm-primary", title: metric.text }, metric.text)),
814
+ ])) : h("div", { className: "qm-l2" }, h("span", { className: "qm-primary", title: metricText }, metricText)),
795
815
  season ? h("div", { className: `qm-season ${season.peak ? "peak" : "valley"}`, "data-qm-season": season.tier, title: seasonTitle },
796
816
  h("span", { className: `qm-season-dot ${season.peak ? "peak" : "valley"}` }),
797
817
  h("b", null, seasonTierOf(season, t)),
@@ -825,9 +845,10 @@
825
845
  const { state, loadError, refresh, refreshing, sessionKey } = useQuotaState();
826
846
  const snap = useSurface();
827
847
  const [expandedId, setExpandedId] = React.useState(null);
828
- // 没有本地用量行的供应商不画图,也就不必为它取额度历史。
848
+ // 没有本地用量行的供应商不画图,也就不必为它取额度历史;仅计量行从不查询,同样不必取。
829
849
  const expandedSupplier = (state?.suppliers || []).find((sup) => sup.id === expandedId);
830
- const quota = useQuotaHistory(sessionKey, Array.isArray(expandedSupplier?.usageDays) && expandedSupplier.usageDays.length > 0);
850
+ const quota = useQuotaHistory(sessionKey, expandedSupplier?.queryable !== false
851
+ && Array.isArray(expandedSupplier?.usageDays) && expandedSupplier.usageDays.length > 0);
831
852
  const dialog = host === "dialog";
832
853
  // Escape / 外部点击必须走 controller 的未保存确认;useDialog 只拿到这个稳定转发器。
833
854
  const closeRef = React.useRef(() => {});
@@ -1039,7 +1060,8 @@
1039
1060
  const sup = (state?.suppliers || []).find((s) => s.id === supplierId);
1040
1061
  if (!sup) return;
1041
1062
  const warn = Number(form.warnPct), crit = Number(form.critPct);
1042
- if (!Number.isFinite(warn) || !Number.isFinite(crit) || warn < 1 || warn >= crit || crit > 100) {
1063
+ if (!isMeteredSupplier(sup)
1064
+ && (!Number.isFinite(warn) || !Number.isFinite(crit) || warn < 1 || warn >= crit || crit > 100)) {
1043
1065
  setSaveError(t("invalidThreshold")); setAdvanced(true);
1044
1066
  requestAnimationFrame(() => advancedRef.current?.querySelector('input[type="number"]')?.focus()); return;
1045
1067
  }
@@ -1051,8 +1073,10 @@
1051
1073
  opBusyRef.current = true;
1052
1074
  setOpBusy(true);
1053
1075
  try {
1054
- const p = supplierPatch(sup, form);
1055
- await post(`${API}/settings`, { suppliers: { [supplierId]: p } });
1076
+ // 仅计量行不改任何查询参数:只有名字,写进 config.meteredNames[route]。
1077
+ await post(`${API}/settings`, isMeteredSupplier(sup)
1078
+ ? { meteredNames: { [sup.route]: String(form.name || "").trim() } }
1079
+ : { suppliers: { [supplierId]: supplierPatch(sup, form) } });
1056
1080
  await reload();
1057
1081
  outcome = { ok: true };
1058
1082
  if (!latest()) return;
@@ -1164,13 +1188,14 @@
1164
1188
  shown.length
1165
1189
  ? h("div", { className: "qm-rows" }, shown.map(sup => h(SupplierRow, {
1166
1190
  key: sup.id, sup, t, season: state.season, quota: quotaOf(sup.id),
1191
+ windowDays: state.poll?.usageHistoryDays || 7,
1167
1192
  expanded: expandedId === sup.id, onToggle: () => onExpand(expandedId === sup.id ? null : sup.id), onEdit })))
1168
1193
  : h(Empty, { text: t(filter === "attention" ? "noAttention" : "overviewEmpty"), action: filter === "all" ? t("manageSuppliers") : null, onAction: onManage }),
1169
1194
  h(HistoryTable, { rows: (state.history || []).slice(0, 50), t }));
1170
1195
  }
1171
1196
 
1172
- function SupplierRow({ sup, t, season, quota, expanded, onToggle, onEdit }) {
1173
- const metric = selectPrimaryMetric(sup, t);
1197
+ function SupplierRow({ sup, t, season, quota, windowDays, expanded, onToggle, onEdit }) {
1198
+ const metric = selectPrimaryMetric(sup, t, windowDays);
1174
1199
  const todayNumber = typeof sup.todayTokens === "number" ? sup.todayTokens : 0;
1175
1200
  const error = String(sup.error?.message || sup.error?.code || "");
1176
1201
  const detailId = `qm-detail-${sup.id}`;
@@ -1188,7 +1213,9 @@
1188
1213
  h("button", { type: "button", className: "qm-row-head", "data-qm-row": "", "aria-expanded": expanded, "aria-controls": detailId, onClick: onToggle },
1189
1214
  h("span", { className: `qm-dot ${stateClass(sup)}` }),
1190
1215
  h("b", { className: "qm-row-name" }, sup.name),
1191
- sup.current ? h("span", { className: "qm-cur" }, t("current")) : null,
1216
+ // 「当前」= 在用/计入今日总量的那个;仅计量行恒计入总量,但不是一个「当前供应商」,
1217
+ // 给它打这个标会让每一行都读作当前,所以只对注册表行显示。
1218
+ sup.current && !isMeteredSupplier(sup) ? h("span", { className: "qm-cur" }, t("current")) : null,
1192
1219
  h("span", { className: `qm-pill ${stateClass(sup)}` }, supplierStatus(sup, t)),
1193
1220
  h("span", { className: "qm-row-metric" }, metric.value),
1194
1221
  h("span", { className: "qm-row-today" }, `${t("todayLabel")} ${fmtBig(todayNumber)}`),
@@ -1196,15 +1223,16 @@
1196
1223
  expanded ? h("div", { id: detailId, className: "qm-row-detail", role: "region", "aria-label": `${sup.name} ${t("detailTitle")}` },
1197
1224
  sup.state === "err" ? h("div", { className: "ci-err", role: "status" }, supplierStatus(sup, t), error ? `:${error.slice(0, 180)}` : "",
1198
1225
  error.length > 180 ? h("details", null, h("summary", null, t("errorDetails")), h("p", null, error)) : null) : null,
1226
+ // 仅计量行不查询,所以没有「最后成功」也可能没有指标表——空白不是失败态,是这类行的常态。
1199
1227
  sup.lastSuccessAt ? h("div", { className: "qm-data-time" }, `${t("lastSuccess")} ${new Date(sup.lastSuccessAt).toLocaleString()}`) : null,
1200
- h(MetricTable, { supplier: sup, t }),
1228
+ isMeteredSupplier(sup) ? null : h(MetricTable, { supplier: sup, t }),
1201
1229
  h(TodayLine, {
1202
1230
  sup, t, tokens: scopedTokens, usage: scopedUsage,
1203
- range, onRange: setRange, mode: estMode, onMode: setEstMode,
1231
+ range, onRange: setRange, mode: estMode, onMode: setEstMode, windowDays,
1204
1232
  }),
1205
1233
  h(UsageSplit, { usage: scopedUsage, t }),
1206
1234
  h(SeasonBlock, { season, split: sup.seasonSplit, t }),
1207
- h("div", { className: "qm-row-actions" }, h("button", { type: "button", onClick: () => onEdit(sup.id) }, t("openPage"))),
1235
+ h("div", { className: "qm-row-actions" }, h("button", { type: "button", onClick: () => onEdit(sup.id) }, t(isMeteredSupplier(sup) ? "rename" : "openPage"))),
1208
1236
  Array.isArray(sup.usageDays) && sup.usageDays.length
1209
1237
  ? h(UsageChart, { sup, days: sup.usageDays, quota, windowTotal: sup.usageDays.reduce((sum, day) => sum + (Number(day?.total) || 0), 0), t })
1210
1238
  : null) : null);
@@ -1227,7 +1255,7 @@
1227
1255
  * 今日行:区间选择器 + 数量 + 归一比例 + 窗口估算。
1228
1256
  * 「Today」两个字换成选择器后,同一行读作「哪个区间、多少 token、什么比例、各窗口还能用多少」。
1229
1257
  */
1230
- function TodayLine({ sup, t, tokens, usage, range, onRange, mode, onMode }) {
1258
+ function TodayLine({ sup, t, tokens, usage, range, onRange, mode, onMode, windowDays = 7 }) {
1231
1259
  const total = fmtBig(tokens || 0);
1232
1260
  const ratio = usageRatioOf(usage);
1233
1261
  const detailText = ratio ? t("ratioAria", { hit: fmtBig(ratio.hitValue), miss: fmtBig(ratio.missValue), out: fmtBig(ratio.outValue) }) : "";
@@ -1240,7 +1268,7 @@
1240
1268
  onChange: (ev) => onRange(ev.target.value === "7d" ? "7d" : "today"),
1241
1269
  },
1242
1270
  h("option", { value: "today" }, t("todayLabel")),
1243
- h("option", { value: "7d" }, t("range7d")))),
1271
+ h("option", { value: "7d" }, t("statsWindow", { n: windowDays })))),
1244
1272
  h("span", { className: "qm-today-text" }, text),
1245
1273
  ratio ? h("span", { className: "qm-ratio", title: detailText },
1246
1274
  h("b", { className: "r-hit" }, ratio.hit), " : ", h("b", { className: "r-miss" }, ratio.miss), " : ", h("b", { className: "r-out" }, ratio.out)) : null,
@@ -1352,7 +1380,7 @@
1352
1380
  h("span", { className: "chip" }, t("connectedChip", { n: added.length })),
1353
1381
  h("button", { type: "button", className: `qm-rescan-btn${ctl.scanBusy ? " busy" : ""}`, disabled: ctl.scanBusy, onClick: ctl.runRescan }, `⟳ ${t("rescan")}`))),
1354
1382
  h("div", { className: "qm-scanline" }, scanLine()),
1355
- added.length ? orderedRows(added, t, ctl) : h(Empty, { text: t("emptyAdded"), action: t("rescan"), onAction: ctl.runRescan }),
1383
+ added.length ? orderedRows(added, t, ctl, state.poll?.usageHistoryDays || 7) : h(Empty, { text: t("emptyAdded"), action: t("rescan"), onAction: ctl.runRescan }),
1356
1384
  addable.length ? h("details", { className: "qm-addable", open: addableOpen, onToggle: (e) => setAddableOpen(e.currentTarget.open) },
1357
1385
  h("summary", null, h("span", null, t("addableTitle")), h("span", { className: "cnt" }, addable.length)),
1358
1386
  h("div", { className: "addable-list" }, addable.map((a) => h("div", { key: a.id, className: `qm-add-row${ctl.freshIds.includes(a.id) ? " fresh" : ""}` },
@@ -1380,20 +1408,24 @@
1380
1408
  h("option", { value: "expanded" }, t("expanded")), h("option", { value: "compact" }, t("compact")))));
1381
1409
  }
1382
1410
 
1383
- function orderedRows(added, t, ctl) {
1384
- return h("div", { className: "qm-page-list" }, added.map((sup) =>
1385
- h("div", { key: sup.id, "data-supplier": sup.id, className: "qm-page-row" },
1411
+ function orderedRows(added, t, ctl, days = 7) {
1412
+ return h("div", { className: "qm-page-list" }, added.map((sup) => {
1413
+ const metered = isMeteredSupplier(sup);
1414
+ return h("div", { key: sup.id, "data-supplier": sup.id, className: "qm-page-row" },
1386
1415
  h("div", { className: "qm-page-main" },
1387
1416
  h("span", { className: `qm-dot ${stateClass(sup)}` }),
1388
- h("span", { className: "qm-page-name", title: sup.meta?.compat ? t("compatSource") : undefined }, sup.name),
1389
- h("span", { className: "qm-row-metric" }, selectPrimaryMetric(sup, t).value),
1417
+ h("span", { className: "qm-page-name", title: sup.meta?.compat ? t("compatSource") : metered ? sup.route : undefined }, sup.name),
1418
+ h("span", { className: "qm-row-metric" }, selectPrimaryMetric(sup, t, days).value),
1390
1419
  h("span", { className: `qm-pill ${stateClass(sup)}` }, supplierStatus(sup, t)),
1391
- h("span", { className: "qm-page-pill" }, t(sup.meta?.credentialLabelKey || "apiKey"))),
1420
+ // 仅计量行没有凭据类别可标:它的凭据就是宿主自己的路由,插件不持有任何密钥。
1421
+ metered ? null : h("span", { className: "qm-page-pill" }, t(sup.meta?.credentialLabelKey || "apiKey"))),
1392
1422
  h("div", { className: "qm-page-actions" },
1393
- h("label", { className: "qm-page-toggle" },
1423
+ // 没有可启用/停用的东西:行是流量的产物,不是一个可以关掉的采集器。
1424
+ metered ? null : h("label", { className: "qm-page-toggle" },
1394
1425
  h("input", { type: "checkbox", checked: !!sup.enabled, disabled: !!ctl.toggling[sup.id] || [...settingsWrites].some(operation => operation.supplierId === sup.id), onChange: () => ctl.toggleEnabled(sup) }),
1395
1426
  h("span", null, t("enable"))),
1396
- h("button", { type: "button", className: "s-test qm-pri-soft", onClick: () => ctl.openSupplier(sup.id) }, t("openPage"))))));
1427
+ h("button", { type: "button", className: "s-test qm-pri-soft", onClick: () => ctl.openSupplier(sup.id) }, t(metered ? "rename" : "openPage"))));
1428
+ }));
1397
1429
  }
1398
1430
 
1399
1431
  // ---------- 供应商配置子页 ----------
@@ -1421,6 +1453,23 @@
1421
1453
  const editor = ctl.editor;
1422
1454
  if (!sup || !editor) return h("div", { className: "qm-sub", role: "status" }, t("loading"));
1423
1455
  const f = editor.form;
1456
+ // 仅计量子页:没有凭据、没有阈值、没有关注限额——只有名字与它挂在哪条路由上。
1457
+ if (isMeteredSupplier(sup)) {
1458
+ return h(React.Fragment, null,
1459
+ h("div", { className: "qm-page-head" },
1460
+ h("button", { type: "button", className: "s-test", onClick: ctl.closeEditor }, `← ${t("back")}`),
1461
+ h("b", null, sup.name),
1462
+ h("span", { className: "qm-pill" }, t("meteredOnly"))),
1463
+ h("div", { className: "s-group" },
1464
+ h("label", null, h("span", null, t("meteredName")),
1465
+ h("input", { type: "text", value: f.name || "", maxLength: 60, placeholder: sup.route, disabled: ctl.opBusy,
1466
+ onChange: (e) => ctl.setEditorField("name", e.target.value) })),
1467
+ h("p", { className: "qm-empty-line" }, `${t("meteredRoute")} · ${sup.route}`)),
1468
+ h("div", { className: "s-actions" },
1469
+ ctl.saved === sup.id ? h("span", { className: "s-saved" }, t("saved")) : null,
1470
+ h("button", { type: "button", onClick: ctl.closeEditor, disabled: ctl.opBusy }, t("cancel")),
1471
+ h("button", { type: "button", className: "qm-pri", onClick: ctl.saveSupplier, disabled: ctl.opBusy }, t("save"))));
1472
+ }
1424
1473
  const needsInputs = (sup.meta?.needs || []).map((need) =>
1425
1474
  h("label", { key: need.key }, needsLabel(sup, need, t),
1426
1475
  h("input", {
@@ -1711,7 +1760,7 @@
1711
1760
  disabled: "未启用",
1712
1761
  notQueried: "尚未查询",
1713
1762
  lastSuccess: "数据时间",
1714
- todayScope: "当前供应商的今日 Token 用量,按宿主本地日统计",
1763
+ todayScope: "已添加供应商的今日 Token 用量合计,按宿主本地日统计",
1715
1764
  eventsObserved: "已观测到调用事件",
1716
1765
  previewSaved: "已保存配置的最近查询结果;编辑草稿不会改变此预览。",
1717
1766
  advanced: "高级选项",
@@ -1772,7 +1821,6 @@
1772
1821
  statsWindow: "近 {n} 天",
1773
1822
  remainLabel: "剩余 {n}%",
1774
1823
  rangeLabel: "统计区间",
1775
- range7d: "近 7 天",
1776
1824
  estLeft: "剩余",
1777
1825
  estTotal: "套餐总量",
1778
1826
  estNoData: "本地样本不足",
@@ -1793,6 +1841,12 @@
1793
1841
  stateCrit: "临界",
1794
1842
  stateErr: "取数失败",
1795
1843
  stateOff: "未配置",
1844
+ // 仅计量供应商:只有经过 Harness 的 token,没有可查询的额度与百分比
1845
+ meteredOnly: "仅计量",
1846
+ windowTokens: "近 {d} 天 {n}",
1847
+ rename: "改名",
1848
+ meteredName: "名称",
1849
+ meteredRoute: "DSH 路由",
1796
1850
  quota: "限额",
1797
1851
  usedShort: "已用",
1798
1852
  detailTitle: "明细",
@@ -1849,7 +1903,7 @@
1849
1903
  critPct: "临界阈值 %",
1850
1904
  interval: "轮询间隔(秒,10–3600)",
1851
1905
  retention: "用量保留期(天,1–90)",
1852
- unmapped: "另探测到 DSH 内已添加但本插件暂不支持的供应商",
1906
+ unmapped: "另探测到 DSH 内已添加、但没有额度查询的供应商",
1853
1907
  },
1854
1908
  en: {
1855
1909
  current: "Current",
@@ -1883,7 +1937,7 @@
1883
1937
  disabled: "Disabled",
1884
1938
  notQueried: "Not queried yet",
1885
1939
  lastSuccess: "Data updated",
1886
- todayScope: "Today's Token usage for current suppliers, measured by the host local day",
1940
+ todayScope: "Today's Token usage across every listed supplier, measured by the host local day",
1887
1941
  eventsObserved: "Call events observed",
1888
1942
  previewSaved: "Latest query for the saved configuration. Draft edits do not change this preview.",
1889
1943
  advanced: "Advanced options",
@@ -1943,7 +1997,6 @@
1943
1997
  statsWindow: "Last {n} days",
1944
1998
  remainLabel: "{n}% left",
1945
1999
  rangeLabel: "Range",
1946
- range7d: "Last 7 days",
1947
2000
  estLeft: "Left",
1948
2001
  estTotal: "Total",
1949
2002
  estNoData: "Not enough local samples",
@@ -1964,6 +2017,11 @@
1964
2017
  stateCrit: "Critical",
1965
2018
  stateErr: "Fetch failed",
1966
2019
  stateOff: "Not configured",
2020
+ meteredOnly: "Usage only",
2021
+ windowTokens: "{d}d {n}",
2022
+ rename: "Rename",
2023
+ meteredName: "Name",
2024
+ meteredRoute: "DSH route",
1967
2025
  quota: "Limit",
1968
2026
  usedShort: "Used",
1969
2027
  detailTitle: "Details",
@@ -2020,7 +2078,7 @@
2020
2078
  critPct: "Critical threshold %",
2021
2079
  interval: "Poll interval (s, 10–3600)",
2022
2080
  retention: "Usage retention (days, 1–90)",
2023
- unmapped: "Detected DSH suppliers this plugin does not support yet",
2081
+ unmapped: "Detected DSH suppliers with no quota query",
2024
2082
  },
2025
2083
  };
2026
2084