dsh-cost-meter 1.5.25 → 1.5.27
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.en.md +8 -7
- package/README.md +8 -7
- package/lib/client.js +313 -7
- package/lib/store.js +59 -4
- package/lib/typert.host.js +8 -0
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
**Session cost tracking plugin for the DeepSeek Harness web GUI (bilingual UI)**
|
|
6
6
|
|
|
7
|
-
Per-conversation cost · daily totals · OpenCode Go subscription quota display · budget with usage percentage · official account balance · custom provider balance · balance progress bar · history · peak/off-peak pricing hours display (peak hours UTC 01:00–04:00, 06:00–10:00) · pre-switch popup & system-notification alerts for peak/off-peak changes (position / lead time / alert type configurable) · one-click price sync from the official docs · Codex-style token usage heat grid · multi-vendor model pricing (built-in 90+ model price catalog with auto-matching) · mainstream Coding Plan quota queries & display (Anthropic / Z.ai / MiniMax / Kimi / OpenRouter / SiliconFlow / CommandCode / SCNet)
|
|
7
|
+
Per-conversation cost · daily totals · OpenCode Go subscription quota display · budget with usage percentage · official account balance · custom provider balance · balance progress bar · history · peak/off-peak pricing hours display (peak hours UTC 01:00–04:00, 06:00–10:00) · pre-switch popup & system-notification alerts for peak/off-peak changes (position / lead time / alert type configurable) · one-click price sync from the official docs · Codex-style token usage heat grid · multi-vendor model pricing (built-in 90+ model price catalog with auto-matching) · mainstream Coding Plan quota queries & display (Anthropic / Z.ai / MiniMax / Kimi / OpenRouter / SiliconFlow / CommandCode / SCNet) · quota strip above the input box (budget / Go / coding-plan usage in one row, toggleable)
|
|
8
8
|
|
|
9
|
-
[](https://github.com/Han-1413141/dsh-cost-meter)
|
|
10
10
|
[](https://www.npmjs.com/package/dsh-cost-meter)
|
|
11
11
|
[](LICENSE)
|
|
12
12
|
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
@@ -29,7 +29,8 @@ English | [中文](README.md)
|
|
|
29
29
|
| Official balance | Sidebar top / Settings page (configurable) | Total / granted / topped-up balance, auto-refresh + manual refresh; optional three-segment progress bar (blue/orange/gray) |
|
|
30
30
|
| Custom provider balance | Sidebar / Settings page (configurable) | Configurable HTTP balance lookup (e.g. LiteLLM); bilingual labels, currency, extract rules; collapsible panel alongside Coding Plan quotas |
|
|
31
31
|
| OpenCode Go quota | Sidebar / Settings / bottom-right dock (configurable) | Rolling-5h / weekly / monthly usage percent and reset times, each window toggleable independently, budget used % can show alongside; key auto-discovered (DSH credential store OPENCODE_GO_API_KEY / env / opencode login) or entered manually |
|
|
32
|
-
| Coding plan quotas | Settings page | Multi-vendor coding-plan quota queries (Anthropic Claude Pro/Max, Z.ai / Zhipu GLM Coding Plan, MiniMax Token Plan, Kimi/Moonshot balance, OpenRouter credits, SiliconFlow balance, CommandCode 5h/weekly windows + monthly credits balance); per-vendor enable switch and
|
|
32
|
+
| Coding plan quotas | Sidebar / Settings page (per vendor) | Multi-vendor coding-plan quota queries (Anthropic Claude Pro/Max, Z.ai / Zhipu GLM Coding Plan, MiniMax Token Plan, Kimi/Moonshot balance, OpenRouter credits, SiliconFlow balance, CommandCode 5h/weekly windows + monthly credits balance); per-vendor enable switch, key and display position (sidebar card in the same box style as the Go quota; the collapsed rail shows percentages), credentials only sent to official endpoints; neutral hints when no credentials/subscription; SCNet Token Plan has no quota API — monthly usage is estimated from the local ledger via the official credits deduction table (no credentials needed) |
|
|
33
|
+
| Quota strip | Above the input box (toggle in Display settings) | One compact chip row for budget used % / the Go main window / each enabled coding-plan usage window (short label + mini progress bar, ≥80% warn, ≥100% over, hover for reset times); a first-run guide card lets you decide whether to enable it; hides itself when there is no quota data |
|
|
33
34
|
| Today's cost | Sidebar bottom (above the settings button) | “Today ¥x”, hover for call count and token details |
|
|
34
35
|
| Budget box | Sidebar bottom (between the balance row and the settings button) | Rounded-square frame: budget, used %, progress bar, today's cost & share of budget, used/limit; ≥80% warning, ≥100% over-budget |
|
|
35
36
|
| Summary cards | Settings page | Today / this month / cumulative cost and call counts |
|
|
@@ -194,22 +195,22 @@ Real captures from an actual DSH sidebar of the period strip and collapsed verti
|
|
|
194
195
|
dsh plugin --profile web add dsh-cost-meter
|
|
195
196
|
```
|
|
196
197
|
|
|
197
|
-
**PowerShell one-click script** (copy the whole line, paste, press Enter; pnpm is provisioned automatically, git is auto-detected — no clone needed; the install chain is **pinned to the release tag `v1.5.
|
|
198
|
+
**PowerShell one-click script** (copy the whole line, paste, press Enter; pnpm is provisioned automatically, git is auto-detected — no clone needed; the install chain is **pinned to the release tag `v1.5.27`** — review the script before running):
|
|
198
199
|
|
|
199
200
|
```powershell
|
|
200
|
-
irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.
|
|
201
|
+
irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.27/install.ps1 | iex
|
|
201
202
|
```
|
|
202
203
|
|
|
203
204
|
**Or a plain command line** (the machine must already have pnpm and git; also pinned to the tag):
|
|
204
205
|
|
|
205
206
|
```sh
|
|
206
|
-
dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.
|
|
207
|
+
dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.27
|
|
207
208
|
```
|
|
208
209
|
|
|
209
210
|
Without git, use the GitHub tag archive:
|
|
210
211
|
|
|
211
212
|
```sh
|
|
212
|
-
dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.
|
|
213
|
+
dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.27.tar.gz
|
|
213
214
|
```
|
|
214
215
|
|
|
215
216
|
After installing, **restart** `dsh web` (plugin rows, the Typert manifest and the client bundle are all scanned at startup):
|
package/README.md
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
**DeepSeek Harness 会话费用统计插件(界面中英双语)**
|
|
6
6
|
|
|
7
|
-
本会话费用 · 当日费用 · OpenCode Go 订阅额度显示 · 预算与已用百分比 · 官方账户余额 · 自定义 Provider 余额查询(可配任意 HTTP 端点) · 余额三段进度条 · 历史记录 · 峰谷计价时段显示(UTC 01:00–04:00、06:00–10:00 为峰时段) · 峰/谷切换前弹窗与系统通知提醒(位置/提前量/提醒类型可配) · 官方价格一键同步 · 类 Codex Token 用量热图 · 多厂商多模型价格计费(内置 90+ 模型价格目录与自动匹配) · 主流 Coding Plan 额度查询与显示(Anthropic / Z.ai / MiniMax / Kimi / OpenRouter / SiliconFlow / CommandCode / SCNet 八家)
|
|
7
|
+
本会话费用 · 当日费用 · OpenCode Go 订阅额度显示 · 预算与已用百分比 · 官方账户余额 · 自定义 Provider 余额查询(可配任意 HTTP 端点) · 余额三段进度条 · 历史记录 · 峰谷计价时段显示(UTC 01:00–04:00、06:00–10:00 为峰时段) · 峰/谷切换前弹窗与系统通知提醒(位置/提前量/提醒类型可配) · 官方价格一键同步 · 类 Codex Token 用量热图 · 多厂商多模型价格计费(内置 90+ 模型价格目录与自动匹配) · 主流 Coding Plan 额度查询与显示(Anthropic / Z.ai / MiniMax / Kimi / OpenRouter / SiliconFlow / CommandCode / SCNet 八家) · 输入框上方额度横条(预算/Go/Coding Plan 用量一条横排显示,可开关)
|
|
8
8
|
|
|
9
|
-
[](https://github.com/Han-1413141/dsh-cost-meter)
|
|
10
10
|
[](https://www.npmjs.com/package/dsh-cost-meter)
|
|
11
11
|
[](LICENSE)
|
|
12
12
|
[](https://github.com/deepseek-ai/deepseek-harness)
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
| 官方余额 | 侧边栏顶部 / 设置页(可配) | 总余额 / 赠送 / 充值,自动刷新 + 手动刷新;可选三段进度条(蓝/橙/灰) |
|
|
30
30
|
| 自定义 Provider 余额 | 侧边栏 / 设置页(可配) | 可配置 HTTP 查询任意 Provider 余额(LiteLLM 等);中/英名称、币种、extract 规则;与 Coding Plan 同区可折叠配置 |
|
|
31
31
|
| OpenCode Go 额度 | 侧边栏 / 设置页 / 右下角(dock,可配) | 滚动 5 小时 / 本周 / 本月用量百分比与重置时间,三档可分别开关,可同时显示预算已用%;Key 自动发现(DSH 凭据库 OPENCODE_GO_API_KEY / 环境变量 / opencode 登录态)或手动填写 |
|
|
32
|
-
| Coding Plan 额度 | 设置页 | 多厂商 coding plan 订阅额度查询(Anthropic Claude Pro/Max、Z.ai/智谱 GLM、MiniMax Token Plan、Kimi/Moonshot 余额、OpenRouter credits、SiliconFlow 余额、CommandCode 5h/周窗口与月度 Credits 余额)
|
|
32
|
+
| Coding Plan 额度 | 侧边栏 / 设置页(每家可配) | 多厂商 coding plan 订阅额度查询(Anthropic Claude Pro/Max、Z.ai/智谱 GLM、MiniMax Token Plan、Kimi/Moonshot 余额、OpenRouter credits、SiliconFlow 余额、CommandCode 5h/周窗口与月度 Credits 余额),各家独立启用开关、Key 与显示位置(侧边栏卡片与 Go 额度同款,收起窄栏显示百分比),凭据只发往官方端点;无凭据/无订阅为中性提示;SCNet 超算互联网 Token Plan 无 API 额度端点,按官方 Credits 抵扣表由本地账本估算月度用量(无需凭据) |
|
|
33
|
+
| 额度横条 | 输入框上方(显示设置可开关) | 一条横排 chips 实时显示预算已用% / Go 主窗口 / 各已启用 Coding Plan 用量窗口(短标签+迷你进度条,≥80% 预警、≥100% 超支,悬停见重置时刻);首次更新弹引导卡由用户自主决定开关;无可用数据自动隐藏 |
|
|
33
34
|
| 当日费用 | 侧边栏底部(设置按钮上方) | 「今日 ¥x」,悬停见调用次数与 token 明细 |
|
|
34
35
|
| 预算图框 | 侧边栏底部(余额行与设置按钮之间) | 圆角方形图框:预算、已用%、进度条、今日费用与占预算%、已用/额度,≥80% 预警、≥100% 超支 |
|
|
35
36
|
| 汇总卡片 | 设置页 | 今日 / 本月 / 累计费用与调用次数 |
|
|
@@ -196,22 +197,22 @@
|
|
|
196
197
|
dsh plugin --profile web add dsh-cost-meter
|
|
197
198
|
```
|
|
198
199
|
|
|
199
|
-
**PowerShell 一键脚本**(复制整行粘贴回车;自动补齐 pnpm、自动探测 git,无需克隆仓库;安装链**固定到发布 tag `v1.5.
|
|
200
|
+
**PowerShell 一键脚本**(复制整行粘贴回车;自动补齐 pnpm、自动探测 git,无需克隆仓库;安装链**固定到发布 tag `v1.5.27`**,建议先下载审阅再运行):
|
|
200
201
|
|
|
201
202
|
```powershell
|
|
202
|
-
irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.
|
|
203
|
+
irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.27/install.ps1 | iex
|
|
203
204
|
```
|
|
204
205
|
|
|
205
206
|
**或直接命令行**(机器上需已有 pnpm 与 git;同样固定到 tag):
|
|
206
207
|
|
|
207
208
|
```sh
|
|
208
|
-
dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.
|
|
209
|
+
dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.27
|
|
209
210
|
```
|
|
210
211
|
|
|
211
212
|
没有 git 时可用 GitHub tag 打包直链:
|
|
212
213
|
|
|
213
214
|
```sh
|
|
214
|
-
dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.
|
|
215
|
+
dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.27.tar.gz
|
|
215
216
|
```
|
|
216
217
|
|
|
217
218
|
安装后**重启** `dsh web`(插件行、Typert 清单与客户端 bundle 均在启动时扫描):
|
package/lib/client.js
CHANGED
|
@@ -53,6 +53,22 @@ window.__ModuleLoader__.load({
|
|
|
53
53
|
'.cm-corner-chip:hover{background:var(--dsw-alias-interactive-bg-hover)}',
|
|
54
54
|
'.cm-corner-chip.warn{color:var(--dsw-alias-state-warn-primary)}',
|
|
55
55
|
'.cm-corner-chip.over{color:var(--dsw-alias-state-error-primary)}',
|
|
56
|
+
// 输入框上方额度横条(issue #31 后续):横排 chips,每片 = 短标签 + 迷你进度条 + 百分比。
|
|
57
|
+
'.cm-qstrip{display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:6px;width:100%;max-width:var(--dsh-chat-content-width,720px);margin:0 auto;box-sizing:border-box;padding:0 calc(var(--dsh-composer-side-clearance,0px) + 16px)}',
|
|
58
|
+
'.cm-qchip{display:inline-flex;align-items:center;gap:6px;height:22px;padding:0 8px;border-radius:6px;background:var(--dsw-alias-bg-layer-2);font-size:11px;line-height:22px;color:var(--dsw-alias-label-secondary);white-space:nowrap;font-variant-numeric:tabular-nums;cursor:default}',
|
|
59
|
+
'.cm-qchip:hover{background:var(--dsw-alias-interactive-bg-hover)}',
|
|
60
|
+
'.cm-qchip .cm-qlabel{color:var(--dsw-alias-label-primary);font-weight:600}',
|
|
61
|
+
'.cm-qchip .cm-qbar{width:44px;height:4px;border-radius:2px;background:var(--dsw-alias-border-l1);overflow:hidden;flex:none}',
|
|
62
|
+
'.cm-qchip .cm-qfill{height:100%;border-radius:2px;background:var(--dsw-alias-state-ok-primary,#3ba272)}',
|
|
63
|
+
'.cm-qchip.warn{color:var(--dsw-alias-state-warn-primary)}',
|
|
64
|
+
'.cm-qchip.warn .cm-qfill{background:var(--dsw-alias-state-warn-primary)}',
|
|
65
|
+
'.cm-qchip.over{color:var(--dsw-alias-state-error-primary)}',
|
|
66
|
+
'.cm-qchip.over .cm-qfill{background:var(--dsw-alias-state-error-primary)}',
|
|
67
|
+
// 首次更新引导:非模态小卡片,固定屏幕顶部居中,选择后消失。
|
|
68
|
+
'.cm-qguide{position:fixed;top:18px;left:50%;transform:translateX(-50%);z-index:9999;max-width:440px;width:calc(100% - 32px);border:1px solid var(--dsw-alias-border-l1);border-radius:12px;background:var(--dsw-alias-bg-layer-1);box-shadow:0 8px 28px rgba(0,0,0,.18);padding:14px 16px;font-size:13px;color:var(--dsw-alias-label-primary);display:flex;flex-direction:column;gap:8px}',
|
|
69
|
+
'.cm-qguide h4{margin:0;font-size:13px;font-weight:600}',
|
|
70
|
+
'.cm-qguide p{margin:0;font-size:12px;line-height:1.6;color:var(--dsw-alias-label-secondary)}',
|
|
71
|
+
'.cm-qguide .cm-buttons{display:flex;gap:8px;justify-content:flex-end}',
|
|
56
72
|
'.cm-section{display:flex;flex-direction:column;gap:20px;padding:4px 2px 24px;font-size:13px;color:var(--dsw-alias-label-primary)}',
|
|
57
73
|
'.cm-cards{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}',
|
|
58
74
|
'.cm-card{border:1px solid var(--dsw-alias-border-l1);border-radius:12px;padding:14px 16px;background:var(--dsw-alias-bg-layer-1)}',
|
|
@@ -266,6 +282,8 @@ window.__ModuleLoader__.load({
|
|
|
266
282
|
'.cm-mm-row.over .cm-bbox-label,.cm-mm-row.over .cm-bbox-pct{color:var(--dsw-alias-state-error-primary)}',
|
|
267
283
|
'.cm-mm-row.warn .cm-bbox-fill{background:var(--dsw-alias-state-warn-primary)}',
|
|
268
284
|
'.cm-mm-row.over .cm-bbox-fill{background:var(--dsw-alias-state-error-primary)}',
|
|
285
|
+
'.cm-mm-row.wide .cm-bbox-label{width:auto;max-width:64px;flex:0 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
286
|
+
'.cm-mm-row.wide .cm-mm-text{flex:1;min-width:0;font-size:12px;color:var(--dsw-alias-label-secondary);text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
269
287
|
'.cm-bal-line{font-size:13px;color:var(--dsw-alias-label-secondary)}',
|
|
270
288
|
'.cm-bal-line.warn{color:var(--dsw-alias-state-warning-primary,#b45309)}',
|
|
271
289
|
'.cm-bal-line.err,.cm-bal-err{color:var(--dsw-alias-state-error-primary)}',
|
|
@@ -412,6 +430,8 @@ window.__ModuleLoader__.load({
|
|
|
412
430
|
codingPlanRemain5h: '5h',
|
|
413
431
|
codingPlanRemain7d: '7d',
|
|
414
432
|
enableCodingPlan: '启用额度查询',
|
|
433
|
+
codingPlanDisplayLabel: '显示位置',
|
|
434
|
+
codingPlanDisplayNote: '选「主页面侧边栏 / 两者」后,该厂商额度以图框卡片常驻侧边栏(与 Go 额度/余额同款,收起窄栏显示百分比)。',
|
|
415
435
|
codingPlanKeyLabel: 'API Key(可选,留空自动发现)',
|
|
416
436
|
refreshCodingPlan: '刷新',
|
|
417
437
|
codingPlanNotQueried: '未查询额度',
|
|
@@ -575,6 +595,16 @@ window.__ModuleLoader__.load({
|
|
|
575
595
|
positionDock: '输入区下方',
|
|
576
596
|
positionHeader: '会话标题栏',
|
|
577
597
|
off: '关闭',
|
|
598
|
+
quotaStripGroup: '输入框上方额度横条',
|
|
599
|
+
quotaStripEnable: '启用额度横条(输入框上方一条横排显示)',
|
|
600
|
+
quotaStripShowBudget: '横条含预算已用%',
|
|
601
|
+
quotaStripShowGo: '横条含 Go 额度',
|
|
602
|
+
quotaStripShowPlans: '横条含 Coding Plan 额度',
|
|
603
|
+
quotaStripNote: '横条悬停可见各窗口重置时刻与更新时间;无可用额度数据时自动隐藏。',
|
|
604
|
+
quotaStripGuideTitle: '新增:输入框上方额度横条',
|
|
605
|
+
quotaStripGuideBody: '可以在输入框上方用一条横条实时查看预算与多家订阅额度(OpenCode Go / Coding Plan)的用量百分比。要现在开启吗?可随时在「设置 → 显示设置」中更改。',
|
|
606
|
+
quotaStripGuideOn: '开启横条',
|
|
607
|
+
quotaStripGuideOff: '暂不开启',
|
|
578
608
|
sidebarLabel: '当日费用显示',
|
|
579
609
|
sidebarOn: '侧边栏底部',
|
|
580
610
|
currencyLabel: '货币单位',
|
|
@@ -745,6 +775,8 @@ window.__ModuleLoader__.load({
|
|
|
745
775
|
codingPlanRemain5h: '5h',
|
|
746
776
|
codingPlanRemain7d: '7d',
|
|
747
777
|
enableCodingPlan: 'Enable quota queries',
|
|
778
|
+
codingPlanDisplayLabel: 'Display position',
|
|
779
|
+
codingPlanDisplayNote: 'With "Main sidebar / Both" selected, this vendor\'s quota shows as a card in the sidebar (same box style as the Go quota / balance; the collapsed rail shows percentages).',
|
|
748
780
|
codingPlanKeyLabel: 'API key (optional; empty = auto-detect)',
|
|
749
781
|
refreshCodingPlan: 'Refresh',
|
|
750
782
|
codingPlanNotQueried: 'Quota not queried',
|
|
@@ -907,6 +939,16 @@ window.__ModuleLoader__.load({
|
|
|
907
939
|
positionDock: 'Below the composer',
|
|
908
940
|
positionHeader: 'Session title bar',
|
|
909
941
|
off: 'Off',
|
|
942
|
+
quotaStripGroup: 'Quota strip above the input',
|
|
943
|
+
quotaStripEnable: 'Enable the quota strip (one row above the input box)',
|
|
944
|
+
quotaStripShowBudget: 'Strip includes budget used %',
|
|
945
|
+
quotaStripShowGo: 'Strip includes the Go quota',
|
|
946
|
+
quotaStripShowPlans: 'Strip includes coding plan quotas',
|
|
947
|
+
quotaStripNote: 'Hover a chip for reset times and the last refresh; the strip hides itself when there is no quota data.',
|
|
948
|
+
quotaStripGuideTitle: 'New: quota strip above the input',
|
|
949
|
+
quotaStripGuideBody: 'Show budget and subscription quotas (OpenCode Go / coding plans) as one compact strip above the input box. Enable it now? You can change this anytime in Settings → Display.',
|
|
950
|
+
quotaStripGuideOn: 'Enable the strip',
|
|
951
|
+
quotaStripGuideOff: 'Not now',
|
|
910
952
|
sidebarLabel: 'Today cost display',
|
|
911
953
|
sidebarOn: 'Sidebar footer',
|
|
912
954
|
currencyLabel: 'Currency',
|
|
@@ -1196,6 +1238,13 @@ window.__ModuleLoader__.load({
|
|
|
1196
1238
|
goMonthly: v.corner?.goMonthly !== false,
|
|
1197
1239
|
budget: v.corner?.budget !== false,
|
|
1198
1240
|
},
|
|
1241
|
+
quotaStrip: {
|
|
1242
|
+
enabled: v.quotaStrip?.enabled === true,
|
|
1243
|
+
budget: v.quotaStrip?.budget !== false,
|
|
1244
|
+
go: v.quotaStrip?.go !== false,
|
|
1245
|
+
plans: v.quotaStrip?.plans !== false,
|
|
1246
|
+
promptSeen: v.quotaStrip?.promptSeen === true,
|
|
1247
|
+
},
|
|
1199
1248
|
usage: {
|
|
1200
1249
|
position: v.usage?.position === 'general' || v.usage?.position === 'section' ? v.usage.position : 'cost',
|
|
1201
1250
|
},
|
|
@@ -2417,6 +2466,195 @@ window.__ModuleLoader__.load({
|
|
|
2417
2466
|
return el(MiniMaxPlanCard, { five, seven, fetchedAt: live.fetchedAt, t, wide })
|
|
2418
2467
|
}
|
|
2419
2468
|
|
|
2469
|
+
/** 通用 Coding Plan 侧边栏卡片(issue #31):每窗口一行进度条;文本窗口(余额等)整行文本。 */
|
|
2470
|
+
function codingPlanWindowLabel(name, t) {
|
|
2471
|
+
if (name === 'fiveHour' || name === '5h') return '5h'
|
|
2472
|
+
if (name === 'weekly' || name === 'week' || name === '7d') return name === '7d' ? '7d' : t('goShortWeekly')
|
|
2473
|
+
if (name === 'monthly' || name === 'month') return t('goShortMonthly')
|
|
2474
|
+
return String(name).replace(/_/g, ' ')
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
function CodingPlanBox(props) {
|
|
2478
|
+
const { id, state, wide } = props
|
|
2479
|
+
const live = state.codingPlans?.[id]
|
|
2480
|
+
const t = makeT(resolveLocale(state.config?.locale))
|
|
2481
|
+
const rowDef = CODING_PLAN_ROWS.find(r => r.id === id)
|
|
2482
|
+
if (!rowDef || !live || live.status !== 'ok') return null
|
|
2483
|
+
const windows = live.windows !== null && typeof live.windows === 'object' ? live.windows : {}
|
|
2484
|
+
const entries = Object.entries(windows)
|
|
2485
|
+
if (entries.length === 0) return null
|
|
2486
|
+
const pctOf = win => win !== null && typeof win === 'object' && typeof win.percent === 'number'
|
|
2487
|
+
? Math.max(0, Math.min(100, Math.round(Number(win.percent) || 0))) : null
|
|
2488
|
+
const rows = entries.map(([name, win]) => {
|
|
2489
|
+
const pct = pctOf(win)
|
|
2490
|
+
if (pct === null) {
|
|
2491
|
+
return el('div', { key: name, className: 'cm-mm-row wide' },
|
|
2492
|
+
el('span', { className: 'cm-mm-text' }, typeof win?.text === 'string' ? win.text : '—'))
|
|
2493
|
+
}
|
|
2494
|
+
const level = pct >= 100 ? 'over' : pct >= 80 ? 'warn' : 'ok'
|
|
2495
|
+
return el('div', { key: name, className: 'cm-mm-row wide' + (level === 'ok' ? '' : ' ' + level) },
|
|
2496
|
+
el('span', { className: 'cm-bbox-label' }, codingPlanWindowLabel(name, t)),
|
|
2497
|
+
el('div', { className: 'cm-bbox-bar' },
|
|
2498
|
+
el('div', { className: 'cm-bbox-fill', style: { width: pct + '%' } })),
|
|
2499
|
+
el('span', { className: 'cm-bbox-pct cm-num' }, pct + '%'))
|
|
2500
|
+
})
|
|
2501
|
+
const detailParts = entries.map(([name, win]) => {
|
|
2502
|
+
const pct = pctOf(win)
|
|
2503
|
+
const reset = typeof win?.resetsAt === 'string' && win.resetsAt.length > 0
|
|
2504
|
+
? ' · ' + t('goResetAt', { time: new Date(win.resetsAt).toLocaleString() }) : ''
|
|
2505
|
+
const value = pct === null ? (typeof win?.text === 'string' ? win.text : '—') : pct + '%'
|
|
2506
|
+
return codingPlanWindowLabel(name, t) + ' ' + value + reset
|
|
2507
|
+
})
|
|
2508
|
+
if (live.fetchedAt > 0) detailParts.push(t('goQuotaFetchedAt', { time: new Date(live.fetchedAt).toLocaleTimeString() }))
|
|
2509
|
+
const detail = [t(rowDef.labelKey), ...detailParts].join('; ')
|
|
2510
|
+
const pcts = entries.map(([, win]) => pctOf(win)).filter(p => p !== null)
|
|
2511
|
+
const level = pcts.some(p => p >= 100) ? 'over' : pcts.some(p => p >= 80) ? 'warn' : 'ok'
|
|
2512
|
+
const railText = pcts.length > 0
|
|
2513
|
+
? pcts.slice(0, 2).map(p => p + '%').join(' ')
|
|
2514
|
+
: (typeof entries[0][1]?.text === 'string' ? entries[0][1].text : '—')
|
|
2515
|
+
return el(Tooltip, { label: detail, side: 'right', delayMs: 300 },
|
|
2516
|
+
el('div', { className: 'cm-bbox cm-mm' + (level === 'ok' ? '' : ' ' + level) + (wide === false ? ' rail' : '') },
|
|
2517
|
+
wide === false
|
|
2518
|
+
? el('div', { className: 'cm-bbox-rail cm-num' }, railText)
|
|
2519
|
+
: el(Fragment, null,
|
|
2520
|
+
el('div', { className: 'cm-mm-title' }, t(rowDef.labelKey)),
|
|
2521
|
+
...rows)))
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
// 输入框上方额度横条:横排 chips(短标签 + 迷你进度条 + 百分比);预算/Go/Coding Plan
|
|
2525
|
+
// 三类内容各自开关,无可用数据(未启用/未配置/查询失败)时整条自动隐藏。
|
|
2526
|
+
const STRIP_VENDOR_SHORT = {
|
|
2527
|
+
anthropic: 'Claude',
|
|
2528
|
+
zai: 'Z.ai',
|
|
2529
|
+
minimax: 'MM',
|
|
2530
|
+
kimi: 'Kimi',
|
|
2531
|
+
openrouter: 'OR',
|
|
2532
|
+
siliconflow: 'SF',
|
|
2533
|
+
commandcode: 'CC',
|
|
2534
|
+
scnet: 'SCNet',
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
function QuotaStrip(props) {
|
|
2538
|
+
const costStore = props.useCost ? props.useCost(s => s) : undefined
|
|
2539
|
+
const state = costStore?.state
|
|
2540
|
+
if (!state) return null
|
|
2541
|
+
const config = state.config
|
|
2542
|
+
const strip = config.quotaStrip ?? { enabled: false, budget: true, go: true, plans: true }
|
|
2543
|
+
if (strip.enabled !== true) return null
|
|
2544
|
+
const t = makeT(resolveLocale(config?.locale))
|
|
2545
|
+
const chips = []
|
|
2546
|
+
// 预算 chip:与预算图框同口径(≥80% 预警、≥100% 超支)。
|
|
2547
|
+
if (strip.budget !== false) {
|
|
2548
|
+
const budget = config.budget ?? { enabled: false, amount: 100, period: 'month' }
|
|
2549
|
+
if (budget.enabled === true) {
|
|
2550
|
+
const rate = Number(config.exchangeRate)
|
|
2551
|
+
const usedUsd = state.budgetUsed ?? (
|
|
2552
|
+
budget.period === 'day' ? state.today.cost
|
|
2553
|
+
: budget.period === 'all' ? state.total.cost
|
|
2554
|
+
: state.month.cost)
|
|
2555
|
+
const used = usedUsd * (Number.isFinite(rate) && rate > 0 ? rate : 1)
|
|
2556
|
+
const amount = Math.max(0, Number(budget.amount) || 0)
|
|
2557
|
+
if (amount > 0) {
|
|
2558
|
+
const pct = Math.min(999, used / amount * 100)
|
|
2559
|
+
const level = pct >= 100 ? 'over' : pct >= 80 ? 'warn' : 'ok'
|
|
2560
|
+
chips.push({
|
|
2561
|
+
key: 'budget',
|
|
2562
|
+
label: t('budgetShort'),
|
|
2563
|
+
pct: Math.round(pct),
|
|
2564
|
+
level,
|
|
2565
|
+
tip: t('budgetOf', { period: t(PERIOD_KEYS[budget.period] ?? 'periodMonth') }) + ' · '
|
|
2566
|
+
+ t('usedOf', { used: formatMoneyValue(used, config), amount: formatMoneyValue(amount, config) })
|
|
2567
|
+
+ ' · ' + pct.toFixed(1) + '%',
|
|
2568
|
+
})
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
// Go chip:主窗口一档(与右下角 chips 的主窗口口径一致)。
|
|
2573
|
+
if (strip.go !== false) {
|
|
2574
|
+
const goQuota = state.goQuota
|
|
2575
|
+
if (config?.goQuota?.enabled !== false && goQuota?.status === 'ok') {
|
|
2576
|
+
const mainKey = config.goQuota.main === 'weekly' || config.goQuota.main === 'monthly' ? config.goQuota.main : 'rolling'
|
|
2577
|
+
const win = mainKey === 'rolling' ? goQuota.rolling : mainKey === 'weekly' ? goQuota.weekly : goQuota.monthly
|
|
2578
|
+
if (win !== null && typeof win === 'object' && typeof win.percent === 'number') {
|
|
2579
|
+
const pct = Math.max(0, Math.min(100, Math.round(Number(win.percent) || 0)))
|
|
2580
|
+
const level = pct >= 100 ? 'over' : pct >= 80 ? 'warn' : 'ok'
|
|
2581
|
+
const resets = typeof win.resetsAt === 'string' && win.resetsAt.length > 0
|
|
2582
|
+
? ' · ' + t('goResetAt', { time: new Date(win.resetsAt).toLocaleString() }) : ''
|
|
2583
|
+
chips.push({
|
|
2584
|
+
key: 'go',
|
|
2585
|
+
label: 'Go ' + t(mainKey === 'rolling' ? 'goShortRolling' : mainKey === 'weekly' ? 'goShortWeekly' : 'goShortMonthly'),
|
|
2586
|
+
pct,
|
|
2587
|
+
level,
|
|
2588
|
+
tip: 'Go: ' + pct + '%' + resets,
|
|
2589
|
+
})
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
// Coding Plan chips:已启用且查询成功的厂商,每家最多取前两个百分比窗口。
|
|
2594
|
+
if (strip.plans !== false) {
|
|
2595
|
+
for (const rowDef of CODING_PLAN_ROWS) {
|
|
2596
|
+
const live = state.codingPlans?.[rowDef.id]
|
|
2597
|
+
if (config.codingPlans?.[rowDef.id]?.enabled !== true || !live || live.status !== 'ok') continue
|
|
2598
|
+
const windows = live.windows !== null && typeof live.windows === 'object' ? live.windows : {}
|
|
2599
|
+
const entries = Object.entries(windows)
|
|
2600
|
+
const vendor = STRIP_VENDOR_SHORT[rowDef.id] ?? rowDef.id
|
|
2601
|
+
let shown = 0
|
|
2602
|
+
const detailParts = []
|
|
2603
|
+
for (const [name, win] of entries) {
|
|
2604
|
+
const pct = win !== null && typeof win === 'object' && typeof win.percent === 'number'
|
|
2605
|
+
? Math.max(0, Math.min(100, Math.round(Number(win.percent) || 0))) : null
|
|
2606
|
+
const reset = typeof win?.resetsAt === 'string' && win.resetsAt.length > 0
|
|
2607
|
+
? ' · ' + t('goResetAt', { time: new Date(win.resetsAt).toLocaleString() }) : ''
|
|
2608
|
+
detailParts.push(codingPlanWindowLabel(name, t) + ' '
|
|
2609
|
+
+ (pct === null ? (typeof win?.text === 'string' ? win.text : '—') : pct + '%') + reset)
|
|
2610
|
+
if (pct === null || shown >= 2) continue
|
|
2611
|
+
shown += 1
|
|
2612
|
+
const level = pct >= 100 ? 'over' : pct >= 80 ? 'warn' : 'ok'
|
|
2613
|
+
chips.push({
|
|
2614
|
+
key: rowDef.id + ':' + name,
|
|
2615
|
+
label: vendor + ' ' + codingPlanWindowLabel(name, t),
|
|
2616
|
+
pct,
|
|
2617
|
+
level,
|
|
2618
|
+
tip: detailParts.join('; '),
|
|
2619
|
+
})
|
|
2620
|
+
}
|
|
2621
|
+
if (detailParts.length > 0 && live.fetchedAt > 0) detailParts.push(t('goQuotaFetchedAt', { time: new Date(live.fetchedAt).toLocaleTimeString() }))
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
if (chips.length === 0) return null
|
|
2625
|
+
return el('div', { className: 'cm-qstrip' },
|
|
2626
|
+
chips.map(c => el(Tooltip, { key: c.key, label: c.tip, side: 'bottom', delayMs: 400 },
|
|
2627
|
+
el('span', { className: 'cm-qchip' + (c.level === 'ok' ? '' : ' ' + c.level) },
|
|
2628
|
+
el('span', { className: 'cm-qlabel' }, c.label),
|
|
2629
|
+
el('span', { className: 'cm-qbar' }, el('span', { className: 'cm-qfill', style: { width: c.pct + '%' } })),
|
|
2630
|
+
el('span', null, c.pct + '%')))))
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
// 首次更新后的功能引导:非模态小卡片,让用户自主决定是否开启额度横条;
|
|
2634
|
+
// 选择「开启/暂不」后写回 promptSeen=true 永久消失(挂在常驻 sidebar.footer.action)。
|
|
2635
|
+
function QuotaStripGuide(props) {
|
|
2636
|
+
const costStore = props.useCost ? props.useCost(s => s) : undefined
|
|
2637
|
+
const state = costStore?.state
|
|
2638
|
+
if (!state) return null
|
|
2639
|
+
const config = state.config
|
|
2640
|
+
if (config.quotaStrip?.promptSeen === true) return null
|
|
2641
|
+
const t = makeT(resolveLocale(config?.locale))
|
|
2642
|
+
const busyRef = useRef(false)
|
|
2643
|
+
const choose = enabled => {
|
|
2644
|
+
if (busyRef.current === true) return
|
|
2645
|
+
busyRef.current = true
|
|
2646
|
+
const current = config.quotaStrip ?? { enabled: false, budget: true, go: true, plans: true }
|
|
2647
|
+
props.api.updateConfig({ quotaStrip: { ...current, enabled, promptSeen: true } })
|
|
2648
|
+
.catch(() => { busyRef.current = false })
|
|
2649
|
+
}
|
|
2650
|
+
return el('div', { className: 'cm-qguide', role: 'dialog', 'aria-label': t('quotaStripGuideTitle') },
|
|
2651
|
+
el('h4', null, t('quotaStripGuideTitle')),
|
|
2652
|
+
el('p', null, t('quotaStripGuideBody')),
|
|
2653
|
+
el('div', { className: 'cm-buttons' },
|
|
2654
|
+
el('button', { type: 'button', className: 'cm-btn small', onClick: () => choose(false) }, t('quotaStripGuideOff')),
|
|
2655
|
+
el('button', { type: 'button', className: 'cm-btn small primary', onClick: () => choose(true) }, t('quotaStripGuideOn'))))
|
|
2656
|
+
}
|
|
2657
|
+
|
|
2420
2658
|
function BudgetBoxContent(props) {
|
|
2421
2659
|
const { state, wide } = props
|
|
2422
2660
|
const today = state.today
|
|
@@ -2530,20 +2768,31 @@ window.__ModuleLoader__.load({
|
|
|
2530
2768
|
const goOk = (config.goQuota?.enabled !== false)
|
|
2531
2769
|
&& (config.goQuota?.display === 'sidebar' || config.goQuota?.display === 'both')
|
|
2532
2770
|
&& state.goQuota?.status === 'ok' && state.goQuota?.[goMainKey] !== null
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
const
|
|
2536
|
-
|
|
2537
|
-
|
|
2771
|
+
// Coding Plan 侧边栏卡片(issue #31):每家按 display 门控(侧边栏/两者),启用且查询成功才展示;
|
|
2772
|
+
// MiniMax 沿用专用 5h/7d 余量卡片,其余厂商走通用 CodingPlanBox。
|
|
2773
|
+
const sidebarPlanIds = CODING_PLAN_ROWS
|
|
2774
|
+
.map(r => r.id)
|
|
2775
|
+
.filter(id => {
|
|
2776
|
+
const entry = config.codingPlans?.[id]
|
|
2777
|
+
if (entry?.enabled !== true) return false
|
|
2778
|
+
if (entry.display !== 'sidebar' && entry.display !== 'both') return false
|
|
2779
|
+
const live = state.codingPlans?.[id]
|
|
2780
|
+
if (live?.status !== 'ok') return false
|
|
2781
|
+
const wins = live.windows !== null && typeof live.windows === 'object' ? live.windows : {}
|
|
2782
|
+
return Object.keys(wins).length > 0
|
|
2783
|
+
})
|
|
2784
|
+
const plansOn = sidebarPlanIds.length > 0
|
|
2538
2785
|
const budgetOn = (config.budget ?? {}).enabled === true
|
|
2539
2786
|
const showToday = config.sidebar !== false
|
|
2540
|
-
if (!showBalance && !showCustomBalance && !goOk && !
|
|
2787
|
+
if (!showBalance && !showCustomBalance && !goOk && !plansOn && !budgetOn && !showToday) return null
|
|
2541
2788
|
const nodes = []
|
|
2542
2789
|
if (showBalanceBar) nodes.push(el(BalanceBox, { state, wide }))
|
|
2543
2790
|
else if (showBalance) nodes.push(el(BalanceRowContent, { state, wide }))
|
|
2544
2791
|
if (showCustomBalanceBar) nodes.push(el(CustomBalanceBox, { state, wide }))
|
|
2545
2792
|
else if (showCustomBalance) nodes.push(el(CustomBalanceRowContent, { state, wide }))
|
|
2546
|
-
if (
|
|
2793
|
+
if (plansOn) nodes.push(...sidebarPlanIds.map(id => id === 'minimax'
|
|
2794
|
+
? el(MiniMaxPlanBox, { state, wide })
|
|
2795
|
+
: el(CodingPlanBox, { id, state, wide })))
|
|
2547
2796
|
if (goOk && budgetOn && wide) {
|
|
2548
2797
|
// 同时出现:合并为一张卡片(Go 在上、预算在下,细分隔线),各自保留预警色与自己的详细信息开关。
|
|
2549
2798
|
const goView = goBoxBody(state, config, t)
|
|
@@ -3779,6 +4028,18 @@ window.__ModuleLoader__.load({
|
|
|
3779
4028
|
}),
|
|
3780
4029
|
el('span', null, t('enableCodingPlan'))),
|
|
3781
4030
|
el('button', { className: 'cm-btn small', onClick: () => { void doRefresh(id) }, disabled: busyId !== null || enabled === false }, busyId === id ? t('refreshing') : t('refreshCodingPlan'))),
|
|
4031
|
+
enabled ? el('div', { className: 'cm-field' },
|
|
4032
|
+
el('label', null, t('codingPlanDisplayLabel')),
|
|
4033
|
+
el('select', {
|
|
4034
|
+
className: 'cm-input',
|
|
4035
|
+
value: typeof cfgEntry.display === 'string' ? cfgEntry.display : (id === 'minimax' ? 'both' : 'settings'),
|
|
4036
|
+
onChange: event => setPlan(id, 'display', event.target.value),
|
|
4037
|
+
},
|
|
4038
|
+
el('option', { value: 'sidebar' }, t('balanceSidebar')),
|
|
4039
|
+
el('option', { value: 'settings' }, t('balanceSettings')),
|
|
4040
|
+
el('option', { value: 'both' }, t('balanceBoth')),
|
|
4041
|
+
el('option', { value: 'off' }, t('off'))),
|
|
4042
|
+
el('span', { className: 'cm-hint' }, t('codingPlanDisplayNote'))) : null,
|
|
3782
4043
|
enabled ? (id === 'scnet'
|
|
3783
4044
|
? el(Fragment, null,
|
|
3784
4045
|
el('div', { className: 'cm-field' },
|
|
@@ -4351,6 +4612,33 @@ window.__ModuleLoader__.load({
|
|
|
4351
4612
|
setDraft({ ...draft, goQuota: { ...(draft.goQuota ?? { display: 'both', refreshMinutes: 15, apiKey: '' }), apiKey: event.target.value } })
|
|
4352
4613
|
},
|
|
4353
4614
|
})),
|
|
4615
|
+
el('div', { className: 'cm-grid-group' }, t('quotaStripGroup')),
|
|
4616
|
+
el('div', { className: 'cm-field' },
|
|
4617
|
+
el('label', { className: 'cm-check' },
|
|
4618
|
+
el('input', {
|
|
4619
|
+
type: 'checkbox',
|
|
4620
|
+
checked: draft?.quotaStrip?.enabled === true,
|
|
4621
|
+
onChange: event => {
|
|
4622
|
+
if (draft === null) return
|
|
4623
|
+
setDraft({ ...draft, quotaStrip: { ...(draft.quotaStrip ?? { enabled: false, budget: true, go: true, plans: true }), enabled: event.target.checked, promptSeen: true } })
|
|
4624
|
+
},
|
|
4625
|
+
}),
|
|
4626
|
+
el('span', null, t('quotaStripEnable')))),
|
|
4627
|
+
draft?.quotaStrip?.enabled === true
|
|
4628
|
+
? [['budget', 'quotaStripShowBudget'], ['go', 'quotaStripShowGo'], ['plans', 'quotaStripShowPlans']].map(([key, labelKey]) =>
|
|
4629
|
+
el('label', { key, className: 'cm-check' },
|
|
4630
|
+
el('input', {
|
|
4631
|
+
type: 'checkbox',
|
|
4632
|
+
checked: draft.quotaStrip[key] !== false,
|
|
4633
|
+
onChange: event => {
|
|
4634
|
+
if (draft === null) return
|
|
4635
|
+
setDraft({ ...draft, quotaStrip: { ...draft.quotaStrip, [key]: event.target.checked } })
|
|
4636
|
+
},
|
|
4637
|
+
}),
|
|
4638
|
+
el('span', null, t(labelKey))))
|
|
4639
|
+
: null,
|
|
4640
|
+
el('div', { className: 'cm-field' },
|
|
4641
|
+
el('span', { className: 'cm-hint' }, t('quotaStripNote'))),
|
|
4354
4642
|
el('div', { className: 'cm-grid-group' }, t('groupCorner')),
|
|
4355
4643
|
el('div', { className: 'cm-field' },
|
|
4356
4644
|
el('label', null, t('cornerLabel')),
|
|
@@ -4702,6 +4990,24 @@ window.__ModuleLoader__.load({
|
|
|
4702
4990
|
})
|
|
4703
4991
|
}
|
|
4704
4992
|
|
|
4993
|
+
// 输入框上方额度横条(conversation.input.dock 渲染于输入卡片上方):静态注册,
|
|
4994
|
+
// 组件内部按 quotaStrip.enabled 门控,无可用数据时整条隐藏。
|
|
4995
|
+
slots.inject('conversation.input.dock', () => {
|
|
4996
|
+
const dispose = slots.register(
|
|
4997
|
+
{ name: 'conversation.input.dock', id: 'cost-meter-qstrip', order: 5, inject: injected },
|
|
4998
|
+
QuotaStrip,
|
|
4999
|
+
)
|
|
5000
|
+
return dispose
|
|
5001
|
+
})
|
|
5002
|
+
// 首次更新引导:常驻 sidebar.footer.action 挂载,组件内部按 promptSeen 门控。
|
|
5003
|
+
slots.inject('sidebar.footer.action', () => {
|
|
5004
|
+
const dispose = slots.register(
|
|
5005
|
+
{ name: 'sidebar.footer.action', id: 'cost-meter-qstrip-guide', order: 1, inject: injected },
|
|
5006
|
+
QuotaStripGuide,
|
|
5007
|
+
)
|
|
5008
|
+
return dispose
|
|
5009
|
+
})
|
|
5010
|
+
|
|
4705
5011
|
// 峰/谷切换前弹窗提醒:全局 fixed 浮层(fixed 定位与宿主位置无关),挂在常驻的
|
|
4706
5012
|
// sidebar.footer.action 插槽——conversation.composer.dock 仅在有活跃会话的页面渲染,
|
|
4707
5013
|
// 挂那里会导致 hero/设置页不弹提醒、预览按钮失效(issue:预览点了没反应)。
|
package/lib/store.js
CHANGED
|
@@ -72,7 +72,9 @@ export function defaultConfig() {
|
|
|
72
72
|
// 各家 coding plan 额度查询(默认关闭;开启后按 Key 发现链查询并在设置页展示)。
|
|
73
73
|
anthropic: { enabled: false, display: 'settings', refreshMinutes: 15, apiKey: '' },
|
|
74
74
|
zai: { enabled: false, display: 'settings', refreshMinutes: 15, apiKey: '' },
|
|
75
|
-
|
|
75
|
+
// MiniMax 自引入起即是「启用即在侧边栏展示 5h/7d 卡片」,显示位置配置落地后默认 both 保持该惯例;
|
|
76
|
+
// 其余厂商默认 settings(仅设置页),按需在设置页切换(issue #31)。
|
|
77
|
+
minimax: { enabled: false, display: 'both', refreshMinutes: 15, apiKey: '' },
|
|
76
78
|
kimi: { enabled: false, display: 'settings', refreshMinutes: 15, apiKey: '' },
|
|
77
79
|
openrouter: { enabled: false, display: 'settings', refreshMinutes: 15, apiKey: '' },
|
|
78
80
|
siliconflow: { enabled: false, display: 'settings', refreshMinutes: 15, apiKey: '' },
|
|
@@ -122,6 +124,13 @@ export function defaultConfig() {
|
|
|
122
124
|
goMonthly: true, // 本月额度
|
|
123
125
|
budget: true, // 预算已用%
|
|
124
126
|
},
|
|
127
|
+
quotaStrip: {
|
|
128
|
+
enabled: false, // 输入框上方额度横条(conversation.input.dock)
|
|
129
|
+
budget: true, // 横条含预算已用%
|
|
130
|
+
go: true, // 横条含 Go 额度主窗口
|
|
131
|
+
plans: true, // 横条含已启用的 Coding Plan 窗口
|
|
132
|
+
promptSeen: false, // 首次更新后的功能引导是否已处理(用户自主决定开关)
|
|
133
|
+
},
|
|
125
134
|
usage: {
|
|
126
135
|
position: 'cost', // Token 用量统计显示位置:cost(费用设置) | general(通用设置) | section(独立分节)
|
|
127
136
|
},
|
|
@@ -275,6 +284,8 @@ const VALIDATION_MESSAGES = {
|
|
|
275
284
|
corner: 'corner 非法',
|
|
276
285
|
cornerEnabled: 'corner.enabled 必须是布尔值',
|
|
277
286
|
cornerFlag: 'corner.{field} 必须是布尔值',
|
|
287
|
+
quotaStrip: 'quotaStrip 非法',
|
|
288
|
+
quotaStripFlag: 'quotaStrip.{field} 必须是布尔值',
|
|
278
289
|
usage: 'usage 非法',
|
|
279
290
|
usagePosition: 'usage.position 必须是 cost / general / section',
|
|
280
291
|
prices: 'prices 非法',
|
|
@@ -341,6 +352,8 @@ const VALIDATION_MESSAGES = {
|
|
|
341
352
|
corner: 'Invalid corner',
|
|
342
353
|
cornerEnabled: 'corner.enabled must be a boolean',
|
|
343
354
|
cornerFlag: 'corner.{field} must be a boolean',
|
|
355
|
+
quotaStrip: 'Invalid quotaStrip',
|
|
356
|
+
quotaStripFlag: 'quotaStrip.{field} must be a boolean',
|
|
344
357
|
usage: 'Invalid usage',
|
|
345
358
|
usagePosition: 'usage.position must be cost / general / section',
|
|
346
359
|
prices: 'Invalid prices',
|
|
@@ -542,6 +555,15 @@ export function applyConfigPatch(current, patch) {
|
|
|
542
555
|
if (typeof corner[field] !== 'boolean') errors.push(vmsg(locale, 'cornerFlag', { field }))
|
|
543
556
|
}
|
|
544
557
|
}
|
|
558
|
+
// 输入框上方额度横条:五布尔字段;非法值直接报错(与 corner 同策略,由客户端提交完整对象)。
|
|
559
|
+
const quotaStrip = candidate.quotaStrip
|
|
560
|
+
if (quotaStrip === null || typeof quotaStrip !== 'object' || Array.isArray(quotaStrip)) {
|
|
561
|
+
errors.push(vmsg(locale, 'quotaStrip'))
|
|
562
|
+
} else {
|
|
563
|
+
for (const field of ['enabled', 'budget', 'go', 'plans', 'promptSeen']) {
|
|
564
|
+
if (typeof quotaStrip[field] !== 'boolean') errors.push(vmsg(locale, 'quotaStripFlag', { field }))
|
|
565
|
+
}
|
|
566
|
+
}
|
|
545
567
|
// Token 用量统计显示位置校验。
|
|
546
568
|
const usage = candidate.usage
|
|
547
569
|
if (usage === null || typeof usage !== 'object' || Array.isArray(usage)) {
|
|
@@ -702,6 +724,11 @@ export function sanitizeConfig(raw) {
|
|
|
702
724
|
out.customBalance = customBalance
|
|
703
725
|
const corner = out.corner
|
|
704
726
|
for (const key of ['enabled', 'goRolling', 'goWeekly', 'goMonthly', 'budget']) corner[key] = corner[key] === true || (corner[key] !== false && key !== 'enabled')
|
|
727
|
+
// 输入框上方额度横条:enabled/promptSeen 缺省 false,budget/go/plans 缺省 true。
|
|
728
|
+
const strip = out.quotaStrip
|
|
729
|
+
strip.enabled = strip.enabled === true
|
|
730
|
+
strip.promptSeen = strip.promptSeen === true
|
|
731
|
+
for (const key of ['budget', 'go', 'plans']) strip[key] = strip[key] !== false
|
|
705
732
|
// codingPlans:逐家收敛(非法条目整家回落默认;只保留已知提供商)。
|
|
706
733
|
const plans = {}
|
|
707
734
|
if (out.codingPlans !== null && typeof out.codingPlans === 'object') {
|
|
@@ -730,6 +757,23 @@ export function sanitizeConfig(raw) {
|
|
|
730
757
|
return out
|
|
731
758
|
}
|
|
732
759
|
|
|
760
|
+
/**
|
|
761
|
+
* 一次性配置迁移(账本根 migrations 标记,已应用过的不再重跑,因此不会覆盖用户后续的显式选择)。
|
|
762
|
+
* 只处理「旧版本无对应配置项、行为与新版本语义不符」的场景;迁移在 sanitizeConfig 之前对原始配置执行。
|
|
763
|
+
*/
|
|
764
|
+
const CONFIG_MIGRATIONS = [
|
|
765
|
+
{
|
|
766
|
+
id: 'v1.5.26-coding-plan-sidebar-display',
|
|
767
|
+
// v1.5.26 前 coding plan 无显示位置 UI:MiniMax 启用即在侧边栏展示卡片,而 schema 恒把 display
|
|
768
|
+
// 清洗为默认 'settings'(当时无 UI 可改,存量值必为 schema 默认而非用户选择)。迁移为 'both'
|
|
769
|
+
// 保持旧版「启用 = 侧边栏 + 设置页」的实际行为;其余厂商旧行为本就是仅设置页,无需迁移。
|
|
770
|
+
apply(cfg) {
|
|
771
|
+
const mm = cfg?.codingPlans?.minimax
|
|
772
|
+
if (mm !== null && typeof mm === 'object' && !Array.isArray(mm) && mm.enabled === true) mm.display = 'both'
|
|
773
|
+
},
|
|
774
|
+
},
|
|
775
|
+
]
|
|
776
|
+
|
|
733
777
|
/**
|
|
734
778
|
* 账本状态容器。所有聚合写内存,持久化走防抖原子写。
|
|
735
779
|
*/
|
|
@@ -738,8 +782,9 @@ export class Ledger {
|
|
|
738
782
|
* @param config - 初始配置(默认值或已持久化配置)。
|
|
739
783
|
* @param days - 已持久化的每日记录对象(date → day)。
|
|
740
784
|
* @param path - 账本文件路径。
|
|
785
|
+
* @param migrations - 已应用的一次性迁移 id 列表(随账本落盘)。
|
|
741
786
|
*/
|
|
742
|
-
constructor(config, days, path) {
|
|
787
|
+
constructor(config, days, path, migrations = []) {
|
|
743
788
|
this.config = config
|
|
744
789
|
this.days = days
|
|
745
790
|
this.path = path
|
|
@@ -748,6 +793,7 @@ export class Ledger {
|
|
|
748
793
|
this.pendingWrite = false
|
|
749
794
|
// 余额差对账参考点({ date, total, granted, topped, at }),由 open() 载入、flush() 落盘。
|
|
750
795
|
this.balanceRef = null
|
|
796
|
+
this.migrations = Array.isArray(migrations) ? migrations.filter(m => typeof m === 'string') : []
|
|
751
797
|
}
|
|
752
798
|
|
|
753
799
|
/** 在 $DSH_HOME 下创建/加载账本。 */
|
|
@@ -757,6 +803,7 @@ export class Ledger {
|
|
|
757
803
|
let config = defaultConfig()
|
|
758
804
|
let days = {}
|
|
759
805
|
let balanceRef = null
|
|
806
|
+
let migrations = []
|
|
760
807
|
try {
|
|
761
808
|
const parsed = JSON.parse(readFileSync(path, 'utf8'))
|
|
762
809
|
if (parsed !== null && typeof parsed === 'object') {
|
|
@@ -764,6 +811,14 @@ export class Ledger {
|
|
|
764
811
|
console.warn(`[dsh-cost-meter] 账本版本 ${String(parsed.version)} 不受支持,按空账本启动`)
|
|
765
812
|
} else {
|
|
766
813
|
const cfg = typeof parsed.config === 'object' && parsed.config !== null ? parsed.config : {}
|
|
814
|
+
// 一次性迁移:先于清洗对原始配置执行,已应用过的(账本 migrations 标记)跳过。
|
|
815
|
+
migrations = Array.isArray(parsed.migrations) ? parsed.migrations.filter(m => typeof m === 'string') : []
|
|
816
|
+
const applied = new Set(migrations)
|
|
817
|
+
for (const migration of CONFIG_MIGRATIONS) {
|
|
818
|
+
if (applied.has(migration.id)) continue
|
|
819
|
+
migration.apply(cfg)
|
|
820
|
+
migrations.push(migration.id)
|
|
821
|
+
}
|
|
767
822
|
// 新版本新增的配置键用默认值补齐;非法值清洗回落,防止击穿 strict codec。
|
|
768
823
|
config = sanitizeConfig(cfg)
|
|
769
824
|
if (parsed.days !== null && typeof parsed.days === 'object' && !Array.isArray(parsed.days)) {
|
|
@@ -785,7 +840,7 @@ export class Ledger {
|
|
|
785
840
|
console.warn(`[dsh-cost-meter] 账本读取失败,按空账本启动: ${String(error?.message ?? error)}`)
|
|
786
841
|
}
|
|
787
842
|
}
|
|
788
|
-
const ledger = new Ledger(config, days, path)
|
|
843
|
+
const ledger = new Ledger(config, days, path, migrations)
|
|
789
844
|
ledger.balanceRef = balanceRef
|
|
790
845
|
return ledger
|
|
791
846
|
}
|
|
@@ -903,7 +958,7 @@ export class Ledger {
|
|
|
903
958
|
try {
|
|
904
959
|
mkdirSync(dirname(this.path), { recursive: true })
|
|
905
960
|
const tmp = `${this.path}.tmp`
|
|
906
|
-
writeFileSync(tmp, JSON.stringify({ version: LEDGER_VERSION, config: this.config, days: this.days, balanceRef: this.balanceRef ?? null }), 'utf8')
|
|
961
|
+
writeFileSync(tmp, JSON.stringify({ version: LEDGER_VERSION, config: this.config, days: this.days, balanceRef: this.balanceRef ?? null, migrations: this.migrations }), 'utf8')
|
|
907
962
|
renameSync(tmp, this.path)
|
|
908
963
|
} catch (error) {
|
|
909
964
|
console.warn(`[dsh-cost-meter] 账本写入失败: ${String(error?.message ?? error)}`)
|
package/lib/typert.host.js
CHANGED
|
@@ -175,6 +175,14 @@ const configSchema = z.object({
|
|
|
175
175
|
goMonthly: z.boolean(),
|
|
176
176
|
budget: z.boolean(),
|
|
177
177
|
}),
|
|
178
|
+
// 输入框上方额度横条(v1.5.27):预算/Go/Coding Plan 用量横条 + 首次引导标记。
|
|
179
|
+
quotaStrip: z.object({
|
|
180
|
+
enabled: z.boolean(),
|
|
181
|
+
budget: z.boolean(),
|
|
182
|
+
go: z.boolean(),
|
|
183
|
+
plans: z.boolean(),
|
|
184
|
+
promptSeen: z.boolean(),
|
|
185
|
+
}),
|
|
178
186
|
historyDays: num,
|
|
179
187
|
fetchedAt: z.union([z.string(), z.null()]),
|
|
180
188
|
priceSource: z.string(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-cost-meter",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.27",
|
|
4
4
|
"description": "DeepSeek Harness 会话费用统计插件:本会话成本、当日费用、历史记录与官方价格同步,支持多厂商多模型价格计费(内置 90+ 模型价格目录与自动匹配)、主流 Coding Plan 订阅额度查询与显示(7 家,含 SCNet Token Plan 本地 Credits 计量)、自定义 Provider 余额查询(可配任意 HTTP 端点)与余额进度条、峰谷计价时段显示与切换前弹窗/系统通知提醒(位置/提前量/类型可配),界面中英双语。Session cost tracking plugin for DeepSeek Harness: per-conversation cost, daily totals, history and official price sync, with multi-vendor model pricing (built-in 90+ model catalog with auto-matching), Coding Plan quota queries & display for 7 vendors (incl. local credits metering for the SCNet Token Plan), custom provider balance lookup (configurable HTTP endpoint) and balance progress bar, plus peak/off-peak pricing display with pre-switch popup & system-notification alerts (position / lead time / type configurable), in a bilingual (Chinese/English) UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|