dsh-cost-meter 1.5.31 → 1.5.32
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 +7 -6
- package/README.md +6 -5
- package/lib/client.js +146 -30
- package/lib/pricing.js +9 -1
- package/lib/store.js +5 -0
- package/lib/typert.host.js +2 -0
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
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)
|
|
@@ -19,7 +19,7 @@ English | [中文](README.md)
|
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
22
|
-

|
|
22
|
+

|
|
23
23
|
|
|
24
24
|
## Feature overview
|
|
25
25
|
|
|
@@ -31,6 +31,7 @@ English | [中文](README.md)
|
|
|
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
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, display position and refresh interval (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
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 |
|
|
34
|
+
| Click to refresh | Sidebar balance/quota boxes | Click the official balance / custom balance / coding-plan box (collapsed rail included) to fetch the latest data immediately; the box pulses while refreshing, failures keep the previous value and surface the reason in the hover tooltip; keyboard Enter/Space also triggers; a one-time guide card appears after the update |
|
|
34
35
|
| Today's cost | Sidebar bottom (above the settings button) | “Today ¥x”, hover for call count and token details |
|
|
35
36
|
| 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 |
|
|
36
37
|
| Summary cards | Settings page | Today / this month / cumulative cost and call counts |
|
|
@@ -227,22 +228,22 @@ Real captures from an actual DSH sidebar of the period strip and collapsed verti
|
|
|
227
228
|
dsh plugin --profile web add dsh-cost-meter
|
|
228
229
|
```
|
|
229
230
|
|
|
230
|
-
**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.
|
|
231
|
+
**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.32`** — review the script before running):
|
|
231
232
|
|
|
232
233
|
```powershell
|
|
233
|
-
irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.
|
|
234
|
+
irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.32/install.ps1 | iex
|
|
234
235
|
```
|
|
235
236
|
|
|
236
237
|
**Or a plain command line** (the machine must already have pnpm and git; also pinned to the tag):
|
|
237
238
|
|
|
238
239
|
```sh
|
|
239
|
-
dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.
|
|
240
|
+
dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.32
|
|
240
241
|
```
|
|
241
242
|
|
|
242
243
|
Without git, use the GitHub tag archive:
|
|
243
244
|
|
|
244
245
|
```sh
|
|
245
|
-
dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.
|
|
246
|
+
dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.32.tar.gz
|
|
246
247
|
```
|
|
247
248
|
|
|
248
249
|
After installing, **restart** `dsh web` (plugin rows, the Typert manifest and the client bundle are all scanned at startup):
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
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)
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
| OpenCode Go 额度 | 侧边栏 / 设置页 / 右下角(dock,可配) | 滚动 5 小时 / 本周 / 本月用量百分比与重置时间,三档可分别开关,可同时显示预算已用%;Key 自动发现(DSH 凭据库 OPENCODE_GO_API_KEY / 环境变量 / opencode 登录态)或手动填写 |
|
|
32
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
33
|
| 额度横条 | 输入框上方(显示设置可开关) | 一条横排 chips 实时显示预算已用% / Go 主窗口 / 各已启用 Coding Plan 用量窗口(短标签+迷你进度条,≥80% 预警、≥100% 超支,悬停见重置时刻);首次更新弹引导卡由用户自主决定开关;无可用数据自动隐藏 |
|
|
34
|
+
| 点击立即刷新 | 侧边栏余额/额度图框 | 官方余额 / 自定义余额 / Coding Plan 图框(含窄栏收起态)点击即触发一次查询,刷新中呼吸闪烁,失败保持原值并在悬停提示说明;键盘 Enter/Space 可触发;更新后首次进入有引导提示 |
|
|
34
35
|
| 当日费用 | 侧边栏底部(设置按钮上方) | 「今日 ¥x」,悬停见调用次数与 token 明细 |
|
|
35
36
|
| 预算图框 | 侧边栏底部(余额行与设置按钮之间) | 圆角方形图框:预算、已用%、进度条、今日费用与占预算%、已用/额度,≥80% 预警、≥100% 超支 |
|
|
36
37
|
| 汇总卡片 | 设置页 | 今日 / 本月 / 累计费用与调用次数 |
|
|
@@ -229,22 +230,22 @@
|
|
|
229
230
|
dsh plugin --profile web add dsh-cost-meter
|
|
230
231
|
```
|
|
231
232
|
|
|
232
|
-
**PowerShell 一键脚本**(复制整行粘贴回车;自动补齐 pnpm、自动探测 git,无需克隆仓库;安装链**固定到发布 tag `v1.5.
|
|
233
|
+
**PowerShell 一键脚本**(复制整行粘贴回车;自动补齐 pnpm、自动探测 git,无需克隆仓库;安装链**固定到发布 tag `v1.5.32`**,建议先下载审阅再运行):
|
|
233
234
|
|
|
234
235
|
```powershell
|
|
235
|
-
irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.
|
|
236
|
+
irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.32/install.ps1 | iex
|
|
236
237
|
```
|
|
237
238
|
|
|
238
239
|
**或直接命令行**(机器上需已有 pnpm 与 git;同样固定到 tag):
|
|
239
240
|
|
|
240
241
|
```sh
|
|
241
|
-
dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.
|
|
242
|
+
dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.32
|
|
242
243
|
```
|
|
243
244
|
|
|
244
245
|
没有 git 时可用 GitHub tag 打包直链:
|
|
245
246
|
|
|
246
247
|
```sh
|
|
247
|
-
dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.
|
|
248
|
+
dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.32.tar.gz
|
|
248
249
|
```
|
|
249
250
|
|
|
250
251
|
安装后**重启** `dsh web`(插件行、Typert 清单与客户端 bundle 均在启动时扫描):
|
package/lib/client.js
CHANGED
|
@@ -272,6 +272,12 @@ window.__ModuleLoader__.load({
|
|
|
272
272
|
'.cm-bbox-section.over .cm-bbox-pct{color:var(--dsw-alias-state-error-primary)}',
|
|
273
273
|
'.cm-bbox-section.warn .cm-bbox-fill{background:var(--dsw-alias-state-warn-primary)}',
|
|
274
274
|
'.cm-bbox-section.over .cm-bbox-fill{background:var(--dsw-alias-state-error-primary)}',
|
|
275
|
+
// 点击立即刷新(issue #37):余额/额度图框可点击,刷新中呼吸闪烁。
|
|
276
|
+
'.cm-bbox.clickable,.cm-foot.clickable{cursor:pointer}',
|
|
277
|
+
'.cm-bbox.clickable:hover{border-color:var(--dsw-alias-state-business-primary)}',
|
|
278
|
+
'.cm-bbox.clickable:focus-visible,.cm-foot.clickable:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}',
|
|
279
|
+
'@keyframes cm-click-refresh-pulse{0%,100%{opacity:.55}50%{opacity:1}}',
|
|
280
|
+
'.cm-bbox.clickable.busy,.cm-foot.clickable.busy{animation:cm-click-refresh-pulse 1.1s ease-in-out infinite}',
|
|
275
281
|
'.cm-mm{padding:8px 10px;gap:4px}',
|
|
276
282
|
'.cm-mm-title{font-size:12px;font-weight:600;color:var(--dsw-alias-label-primary)}',
|
|
277
283
|
'.cm-mm-row{display:flex;align-items:center;gap:8px;padding:2px 0}',
|
|
@@ -606,6 +612,10 @@ window.__ModuleLoader__.load({
|
|
|
606
612
|
quotaStripGuideBody: '可以在输入框上方用一条横条实时查看预算与多家订阅额度(OpenCode Go / Coding Plan)的用量百分比。要现在开启吗?可随时在「设置 → 显示设置」中更改。',
|
|
607
613
|
quotaStripGuideOn: '开启横条',
|
|
608
614
|
quotaStripGuideOff: '暂不开启',
|
|
615
|
+
clickToRefresh: '点击立即刷新',
|
|
616
|
+
balanceClickGuideTitle: '新增:余额图框点击刷新',
|
|
617
|
+
balanceClickGuideBody: '现在点击侧边栏的官方余额 / 自定义余额 / Coding Plan 图框,即可立即刷新最新数据,无需等待自动刷新间隔或进入设置页;刷新中会有呼吸闪烁,失败时保持原值并在悬停提示中说明原因。',
|
|
618
|
+
balanceClickGuideOk: '知道了',
|
|
609
619
|
sidebarLabel: '当日费用显示',
|
|
610
620
|
sidebarOn: '侧边栏底部',
|
|
611
621
|
currencyLabel: '货币单位',
|
|
@@ -951,6 +961,10 @@ window.__ModuleLoader__.load({
|
|
|
951
961
|
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.',
|
|
952
962
|
quotaStripGuideOn: 'Enable the strip',
|
|
953
963
|
quotaStripGuideOff: 'Not now',
|
|
964
|
+
clickToRefresh: 'Click to refresh now',
|
|
965
|
+
balanceClickGuideTitle: 'New: click the balance box to refresh',
|
|
966
|
+
balanceClickGuideBody: 'Click the official balance / custom balance / coding-plan box in the sidebar to fetch the latest data immediately — no need to wait for the auto-refresh interval or open Settings. While refreshing the box pulses; on failure the previous value is kept and the reason appears in the hover tooltip.',
|
|
967
|
+
balanceClickGuideOk: 'Got it',
|
|
954
968
|
sidebarLabel: 'Today cost display',
|
|
955
969
|
sidebarOn: 'Sidebar footer',
|
|
956
970
|
currencyLabel: 'Currency',
|
|
@@ -1214,6 +1228,7 @@ window.__ModuleLoader__.load({
|
|
|
1214
1228
|
refreshMinutes: typeof v.balance?.refreshMinutes === 'number' && Number.isFinite(v.balance.refreshMinutes) ? v.balance.refreshMinutes : 5,
|
|
1215
1229
|
showProgressBar: v.balance?.showProgressBar === true,
|
|
1216
1230
|
budgetCap: typeof v.balance?.budgetCap === 'number' && Number.isFinite(v.balance.budgetCap) && v.balance.budgetCap > 0 ? v.balance.budgetCap : null,
|
|
1231
|
+
clickHintSeen: v.balance?.clickHintSeen === true,
|
|
1217
1232
|
},
|
|
1218
1233
|
goQuota: {
|
|
1219
1234
|
enabled: v.goQuota?.enabled !== false,
|
|
@@ -1838,14 +1853,51 @@ window.__ModuleLoader__.load({
|
|
|
1838
1853
|
return lines.join(' · ')
|
|
1839
1854
|
}
|
|
1840
1855
|
|
|
1856
|
+
// 点击立即刷新(issue #37):侧边栏余额/额度图框点击触发一次手动刷新;
|
|
1857
|
+
// busy 期间忽略连点防并发;失败保持原值(fast-fail 抛错不会写入 store),
|
|
1858
|
+
// 错误信息供 tooltip 展示,下次刷新开始时清除。
|
|
1859
|
+
function useClickRefresh(call) {
|
|
1860
|
+
const [busy, setBusy] = useState(false)
|
|
1861
|
+
const [err, setErr] = useState(null)
|
|
1862
|
+
const run = () => {
|
|
1863
|
+
if (busy || typeof call !== 'function') return
|
|
1864
|
+
setBusy(true)
|
|
1865
|
+
setErr(null)
|
|
1866
|
+
Promise.resolve().then(call)
|
|
1867
|
+
.catch(error => { setErr(error?.message ?? String(error)) })
|
|
1868
|
+
.finally(() => { setBusy(false) })
|
|
1869
|
+
}
|
|
1870
|
+
return { busy, err, run }
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
// 可点击图框的通用 a11y/事件属性(键盘 Enter/Space 同样触发;aria-busy 标记刷新中)。
|
|
1874
|
+
const clickableRefreshProps = (busy, run) => ({
|
|
1875
|
+
role: 'button',
|
|
1876
|
+
tabIndex: 0,
|
|
1877
|
+
'aria-busy': busy ? 'true' : 'false',
|
|
1878
|
+
onClick: run,
|
|
1879
|
+
onKeyDown: event => {
|
|
1880
|
+
if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); run() }
|
|
1881
|
+
},
|
|
1882
|
+
})
|
|
1883
|
+
|
|
1884
|
+
// 点击刷新的 tooltip 附加行:提示语 + 刷新中 + 最近一次失败原因。
|
|
1885
|
+
function clickRefreshTipLines(t, refresh) {
|
|
1886
|
+
const lines = [t('clickToRefresh')]
|
|
1887
|
+
if (refresh.busy) lines.push(t('refreshing'))
|
|
1888
|
+
if (refresh.err) lines.push('⚠ ' + t('balanceRefreshFailed', { message: refresh.err }))
|
|
1889
|
+
return lines
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1841
1892
|
function BalanceRowContent(props) {
|
|
1842
|
-
const { state, wide } = props
|
|
1893
|
+
const { state, wide, api } = props
|
|
1843
1894
|
const balance = state.balance
|
|
1844
1895
|
const t = makeT(resolveLocale(state.config?.locale))
|
|
1896
|
+
const refresh = useClickRefresh(api ? () => api.refreshBalance() : null)
|
|
1845
1897
|
if (!balance || balance.status === 'off') return null
|
|
1846
1898
|
if (balance.status === 'error') {
|
|
1847
|
-
return el(Tooltip, { label: t('balanceQueryFailed', { message: balance.message || t('unknownError') }), side: 'right', delayMs: 300 },
|
|
1848
|
-
el('div', { className: 'cm-foot' + (wide ? '' : ' cm-foot-rail') + ' cm-bal-err' },
|
|
1899
|
+
return el(Tooltip, { label: [t('balanceQueryFailed', { message: balance.message || t('unknownError') }), ...clickRefreshTipLines(t, refresh)].join('; '), side: 'right', delayMs: 300 },
|
|
1900
|
+
el('div', { className: 'cm-foot clickable' + (wide ? '' : ' cm-foot-rail') + ' cm-bal-err' + (refresh.busy ? ' busy' : ''), ...clickableRefreshProps(refresh.busy, refresh.run) },
|
|
1849
1901
|
wide ? el(Fragment, null, t('balance'), ' ', el('span', { className: 'cm-num' }, t('queryFailed'))) : '⚠'))
|
|
1850
1902
|
}
|
|
1851
1903
|
const detail = [
|
|
@@ -1857,17 +1909,19 @@ window.__ModuleLoader__.load({
|
|
|
1857
1909
|
}),
|
|
1858
1910
|
t('updatedAt', { time: new Date(balance.fetchedAt).toLocaleTimeString() }),
|
|
1859
1911
|
...(state.reconcile?.ok === false ? ['⚠ ' + state.reconcile.message] : []),
|
|
1912
|
+
...clickRefreshTipLines(t, refresh),
|
|
1860
1913
|
].join('; ')
|
|
1861
1914
|
return el(Tooltip, { label: detail, side: 'right', delayMs: 300 },
|
|
1862
|
-
el('div', { className: 'cm-foot' + (wide ? '' : ' cm-foot-rail') },
|
|
1915
|
+
el('div', { className: 'cm-foot clickable' + (wide ? '' : ' cm-foot-rail') + (refresh.busy ? ' busy' : ''), ...clickableRefreshProps(refresh.busy, refresh.run) },
|
|
1863
1916
|
wide ? el(Fragment, null, t('balance'), ' ', el('span', { className: 'cm-num' }, formatBalanceMoney(balance.totalBalance, state.config)), state.reconcile?.ok === false ? ' ⚠' : '') : el(WalletIcon, { size: 16 })))
|
|
1864
1917
|
}
|
|
1865
1918
|
|
|
1866
1919
|
function BalanceBox(props) {
|
|
1867
|
-
const { state, wide } = props
|
|
1920
|
+
const { state, wide, api } = props
|
|
1868
1921
|
const config = state.config
|
|
1869
1922
|
const balance = state.balance
|
|
1870
1923
|
const t = makeT(resolveLocale(config?.locale))
|
|
1924
|
+
const refresh = useClickRefresh(api ? () => api.refreshBalance() : null)
|
|
1871
1925
|
if (!balance || balance.status !== 'ok') return null
|
|
1872
1926
|
const segments = segmentsForOfficialBalance(state, config)
|
|
1873
1927
|
const remaining = formatBalanceMoney(balance.totalBalance, config)
|
|
@@ -1881,6 +1935,7 @@ window.__ModuleLoader__.load({
|
|
|
1881
1935
|
}),
|
|
1882
1936
|
t('updatedAt', { time: new Date(balance.fetchedAt).toLocaleTimeString() }),
|
|
1883
1937
|
...(state.reconcile?.ok === false ? ['⚠ ' + state.reconcile.message] : []),
|
|
1938
|
+
...clickRefreshTipLines(t, refresh),
|
|
1884
1939
|
].join('; ')
|
|
1885
1940
|
const body = el(Fragment, null,
|
|
1886
1941
|
el('div', { className: 'cm-bbox-head' },
|
|
@@ -1888,7 +1943,7 @@ window.__ModuleLoader__.load({
|
|
|
1888
1943
|
el('span', { className: 'cm-bbox-pct cm-num cm-bal-amt' }, remaining)),
|
|
1889
1944
|
el(BalanceBar, { segments }))
|
|
1890
1945
|
return el(Tooltip, { label: detail, side: 'right', delayMs: 300 },
|
|
1891
|
-
el('div', { className: 'cm-bbox' + (wide ? '' : ' rail') },
|
|
1946
|
+
el('div', { className: 'cm-bbox clickable' + (wide ? '' : ' rail') + (refresh.busy ? ' busy' : ''), ...clickableRefreshProps(refresh.busy, refresh.run) },
|
|
1892
1947
|
wide ? body : el('div', { className: 'cm-bbox-rail cm-num' }, Math.round(segments.remainingPct) + '%')))
|
|
1893
1948
|
}
|
|
1894
1949
|
|
|
@@ -1961,34 +2016,36 @@ window.__ModuleLoader__.load({
|
|
|
1961
2016
|
}
|
|
1962
2017
|
|
|
1963
2018
|
function CustomBalanceBox(props) {
|
|
1964
|
-
const { state, wide } = props
|
|
2019
|
+
const { state, wide, api } = props
|
|
1965
2020
|
const custom = state.customBalance
|
|
1966
2021
|
const config = state.config
|
|
1967
2022
|
const t = makeT(resolveLocale(config?.locale))
|
|
2023
|
+
const refresh = useClickRefresh(api ? () => api.refreshCustomBalance() : null)
|
|
1968
2024
|
if (!custom || custom.status !== 'ok') return null
|
|
1969
2025
|
const view = customBalanceBoxBody(state, config, t)
|
|
1970
|
-
const detail = customBalanceDetailText(custom, config, t, state)
|
|
2026
|
+
const detail = [customBalanceDetailText(custom, config, t, state), ...clickRefreshTipLines(t, refresh)].join(' · ')
|
|
1971
2027
|
return el(Tooltip, { label: detail, side: 'right', delayMs: 300 },
|
|
1972
|
-
el('div', { className: 'cm-bbox' + (view.level === 'ok' ? '' : ' ' + view.level) + (wide ? '' : ' rail') },
|
|
2028
|
+
el('div', { className: 'cm-bbox clickable' + (view.level === 'ok' ? '' : ' ' + view.level) + (wide ? '' : ' rail') + (refresh.busy ? ' busy' : ''), ...clickableRefreshProps(refresh.busy, refresh.run) },
|
|
1973
2029
|
wide ? view.body : el('div', { className: 'cm-bbox-rail cm-num' }, view.rail)))
|
|
1974
2030
|
}
|
|
1975
2031
|
|
|
1976
2032
|
function CustomBalanceRowContent(props) {
|
|
1977
|
-
const { state, wide } = props
|
|
2033
|
+
const { state, wide, api } = props
|
|
1978
2034
|
const custom = state.customBalance
|
|
1979
2035
|
const config = state.config
|
|
1980
2036
|
const t = makeT(resolveLocale(config?.locale))
|
|
2037
|
+
const refresh = useClickRefresh(api ? () => api.refreshCustomBalance() : null)
|
|
1981
2038
|
if (!custom || custom.status === 'off') return null
|
|
1982
2039
|
const label = resolveCustomBalanceLabel(config.customBalance ?? {}, resolveLocale(config?.locale))
|
|
1983
2040
|
if (custom.status === 'error') {
|
|
1984
|
-
return el(Tooltip, { label: custom.message || t('unknownError'), side: 'right', delayMs: 300 },
|
|
1985
|
-
el('div', { className: 'cm-foot' + (wide ? '' : ' cm-foot-rail') + ' cm-bal-err' },
|
|
2041
|
+
return el(Tooltip, { label: [custom.message || t('unknownError'), ...clickRefreshTipLines(t, refresh)].join('; '), side: 'right', delayMs: 300 },
|
|
2042
|
+
el('div', { className: 'cm-foot clickable' + (wide ? '' : ' cm-foot-rail') + ' cm-bal-err' + (refresh.busy ? ' busy' : ''), ...clickableRefreshProps(refresh.busy, refresh.run) },
|
|
1986
2043
|
wide ? el(Fragment, null, label, ' ', el('span', { className: 'cm-num' }, t('queryFailed'))) : '⚠'))
|
|
1987
2044
|
}
|
|
1988
2045
|
const amount = formatCustomBalanceMoney(custom.remaining, config, custom)
|
|
1989
|
-
const detail = customBalanceDetailText(custom, config, t, state)
|
|
2046
|
+
const detail = [customBalanceDetailText(custom, config, t, state), ...clickRefreshTipLines(t, refresh)].join(' · ')
|
|
1990
2047
|
return el(Tooltip, { label: detail, side: 'right', delayMs: 300 },
|
|
1991
|
-
el('div', { className: 'cm-foot' + (wide ? '' : ' cm-foot-rail') },
|
|
2048
|
+
el('div', { className: 'cm-foot clickable' + (wide ? '' : ' cm-foot-rail') + (refresh.busy ? ' busy' : ''), ...clickableRefreshProps(refresh.busy, refresh.run) },
|
|
1992
2049
|
wide ? el(Fragment, null, label, ' ', el('span', { className: 'cm-num' }, amount)) : el(WalletIcon, { size: 16 })))
|
|
1993
2050
|
}
|
|
1994
2051
|
|
|
@@ -2115,6 +2172,11 @@ window.__ModuleLoader__.load({
|
|
|
2115
2172
|
// 预览通道事件名:设置页按钮/控制台经 window.cmPeakAlertPreview(kind) 触发真实组件。
|
|
2116
2173
|
const PEAK_ALERT_PREVIEW_EVENT = 'cm-peak-alert-preview'
|
|
2117
2174
|
|
|
2175
|
+
// 峰谷 Web 通知去重:记录最近一次已通知的切换点时刻(nextAtMs)。
|
|
2176
|
+
// 必须放模块级——配置变化会重挂 PeakAlert(内部 ref 归零),若用组件内状态,
|
|
2177
|
+
// 重挂后同一切换点会再发一条;切换点时刻单调递增,存单值即可。
|
|
2178
|
+
let lastPeakNotifyAtMs = 0
|
|
2179
|
+
|
|
2118
2180
|
/**
|
|
2119
2181
|
* 峰/谷切换前弹窗提醒:距下次相位切换不足配置提前量(默认 2 分钟)时弹浮层,
|
|
2120
2182
|
* 位置可选屏幕右下角 / 屏幕中心;提醒类型按配置过滤(进入峰/进入谷/峰和谷);
|
|
@@ -2129,7 +2191,6 @@ window.__ModuleLoader__.load({
|
|
|
2129
2191
|
const [now, setNow] = useState(Date.now())
|
|
2130
2192
|
const [dismissedAt, setDismissedAt] = useState(null)
|
|
2131
2193
|
const [preview, setPreview] = useState(null)
|
|
2132
|
-
const notifiedAtRef = useRef(null)
|
|
2133
2194
|
useEffect(() => {
|
|
2134
2195
|
const timer = window.setInterval(() => setNow(Date.now()), 10000)
|
|
2135
2196
|
return () => window.clearInterval(timer)
|
|
@@ -2158,10 +2219,10 @@ window.__ModuleLoader__.load({
|
|
|
2158
2219
|
}
|
|
2159
2220
|
}, []) // eslint-disable-line react-hooks/exhaustive-deps
|
|
2160
2221
|
useEffect(() => {
|
|
2161
|
-
// Web 通知:每次 tick
|
|
2222
|
+
// Web 通知:每次 tick 自包含重算切换点,按切换点(nextAtMs)在模块级去重,
|
|
2223
|
+
// 同一切换点只发一次(旧实现比较 tick 时间戳,每 10 秒都"未通知过",会连发)。
|
|
2162
2224
|
if (!config || config.peakAlertEnabled !== true || config.peakEnabled !== true) return
|
|
2163
2225
|
if (config.peakAlertWebNotify !== true || !window.Notification || Notification.permission !== 'granted') return
|
|
2164
|
-
if (notifiedAtRef.current === now) return
|
|
2165
2226
|
const wv = peakPhaseAt(now, Array.isArray(config.peakWindows) ? config.peakWindows : [])
|
|
2166
2227
|
if (wv === null) return
|
|
2167
2228
|
const tgt = config.peakAlertTarget === 'peak' || config.peakAlertTarget === 'offpeak' ? config.peakAlertTarget : 'both'
|
|
@@ -2170,8 +2231,9 @@ window.__ModuleLoader__.load({
|
|
|
2170
2231
|
const mins = Number(config.peakAlertAhead)
|
|
2171
2232
|
const aheadMs = (Number.isFinite(mins) && mins >= 1 ? mins : 2) * 60000
|
|
2172
2233
|
if (now < wv.nextAtMs - aheadMs || now >= wv.nextAtMs) return
|
|
2234
|
+
if (lastPeakNotifyAtMs === wv.nextAtMs) return
|
|
2173
2235
|
const t = makeT(resolveLocale(config.locale))
|
|
2174
|
-
|
|
2236
|
+
lastPeakNotifyAtMs = wv.nextAtMs
|
|
2175
2237
|
try {
|
|
2176
2238
|
new Notification(
|
|
2177
2239
|
t(intoPeak ? 'peakAlertTitlePeak' : 'peakAlertTitleOffPeak'),
|
|
@@ -2448,7 +2510,7 @@ window.__ModuleLoader__.load({
|
|
|
2448
2510
|
}
|
|
2449
2511
|
|
|
2450
2512
|
function MiniMaxPlanCard(props) {
|
|
2451
|
-
const { five, seven, fetchedAt, t, wide } = props
|
|
2513
|
+
const { five, seven, fetchedAt, t, wide, refresh } = props
|
|
2452
2514
|
const fiveView = miniMaxRow(t('codingPlanRemain5h'), five)
|
|
2453
2515
|
const sevenView = miniMaxRow(t('codingPlanRemain7d'), seven)
|
|
2454
2516
|
const level = fiveView.level === 'over' || sevenView.level === 'over' ? 'over'
|
|
@@ -2463,6 +2525,7 @@ window.__ModuleLoader__.load({
|
|
|
2463
2525
|
lineOf(t('codingPlanRemain5h'), five, fiveView),
|
|
2464
2526
|
lineOf(t('codingPlanRemain7d'), seven, sevenView),
|
|
2465
2527
|
fetchedAt > 0 ? t('goQuotaFetchedAt', { time: new Date(fetchedAt).toLocaleTimeString() }) : null,
|
|
2528
|
+
...(refresh ? clickRefreshTipLines(t, refresh) : []),
|
|
2466
2529
|
].filter(Boolean).join('; ')
|
|
2467
2530
|
const body = el(Fragment, null,
|
|
2468
2531
|
el('div', { className: 'cm-mm-title' }, t('codingPlanMinimaxTitle')),
|
|
@@ -2472,18 +2535,23 @@ window.__ModuleLoader__.load({
|
|
|
2472
2535
|
el('div', { className: 'cm-bbox-rail cm-num' }, fiveView.remain === null ? '—' : Math.round(fiveView.remain) + '%'),
|
|
2473
2536
|
el('div', { className: 'cm-bbox-rail cm-num' }, sevenView.remain === null ? '—' : Math.round(sevenView.remain) + '%'))
|
|
2474
2537
|
return el(Tooltip, { label: detail, side: 'right', delayMs: 300 },
|
|
2475
|
-
el('div', {
|
|
2538
|
+
el('div', {
|
|
2539
|
+
className: 'cm-bbox cm-mm' + (level === 'ok' ? '' : ' ' + level) + (wide === false ? ' rail' : '')
|
|
2540
|
+
+ (refresh ? ' clickable' + (refresh.busy ? ' busy' : '') : ''),
|
|
2541
|
+
...(refresh ? clickableRefreshProps(refresh.busy, refresh.run) : {}),
|
|
2542
|
+
},
|
|
2476
2543
|
wide === false ? rail : body))
|
|
2477
2544
|
}
|
|
2478
2545
|
|
|
2479
2546
|
function MiniMaxPlanBox(props) {
|
|
2480
|
-
const { state, wide } = props
|
|
2547
|
+
const { state, wide, api } = props
|
|
2481
2548
|
const live = state.codingPlans?.minimax
|
|
2482
2549
|
const t = makeT(resolveLocale(state.config?.locale))
|
|
2550
|
+
const refresh = useClickRefresh(api ? () => api.refreshCodingPlan('minimax') : null)
|
|
2483
2551
|
if (!live || live.status !== 'ok') return null
|
|
2484
2552
|
const { five, seven } = miniMaxWindowsOf(live.windows)
|
|
2485
2553
|
if (five == null && seven == null) return null
|
|
2486
|
-
return el(MiniMaxPlanCard, { five, seven, fetchedAt: live.fetchedAt, t, wide })
|
|
2554
|
+
return el(MiniMaxPlanCard, { five, seven, fetchedAt: live.fetchedAt, t, wide, refresh })
|
|
2487
2555
|
}
|
|
2488
2556
|
|
|
2489
2557
|
/** 通用 Coding Plan 侧边栏卡片(issue #31):每窗口一行进度条;文本窗口(余额等)整行文本。 */
|
|
@@ -2495,9 +2563,10 @@ window.__ModuleLoader__.load({
|
|
|
2495
2563
|
}
|
|
2496
2564
|
|
|
2497
2565
|
function CodingPlanBox(props) {
|
|
2498
|
-
const { id, state, wide } = props
|
|
2566
|
+
const { id, state, wide, api } = props
|
|
2499
2567
|
const live = state.codingPlans?.[id]
|
|
2500
2568
|
const t = makeT(resolveLocale(state.config?.locale))
|
|
2569
|
+
const refresh = useClickRefresh(api ? () => api.refreshCodingPlan(id) : null)
|
|
2501
2570
|
const rowDef = CODING_PLAN_ROWS.find(r => r.id === id)
|
|
2502
2571
|
if (!rowDef || !live || live.status !== 'ok') return null
|
|
2503
2572
|
const windows = live.windows !== null && typeof live.windows === 'object' ? live.windows : {}
|
|
@@ -2526,6 +2595,7 @@ window.__ModuleLoader__.load({
|
|
|
2526
2595
|
return codingPlanWindowLabel(name, t) + ' ' + value + reset
|
|
2527
2596
|
})
|
|
2528
2597
|
if (live.fetchedAt > 0) detailParts.push(t('goQuotaFetchedAt', { time: new Date(live.fetchedAt).toLocaleTimeString() }))
|
|
2598
|
+
detailParts.push(...clickRefreshTipLines(t, refresh))
|
|
2529
2599
|
const detail = [t(rowDef.labelKey), ...detailParts].join('; ')
|
|
2530
2600
|
const pcts = entries.map(([, win]) => pctOf(win)).filter(p => p !== null)
|
|
2531
2601
|
const level = pcts.some(p => p >= 100) ? 'over' : pcts.some(p => p >= 80) ? 'warn' : 'ok'
|
|
@@ -2533,7 +2603,7 @@ window.__ModuleLoader__.load({
|
|
|
2533
2603
|
? pcts.slice(0, 2).map(p => p + '%').join(' ')
|
|
2534
2604
|
: (typeof entries[0][1]?.text === 'string' ? entries[0][1].text : '—')
|
|
2535
2605
|
return el(Tooltip, { label: detail, side: 'right', delayMs: 300 },
|
|
2536
|
-
el('div', { className: 'cm-bbox cm-mm' + (level === 'ok' ? '' : ' ' + level) + (wide === false ? ' rail' : '') },
|
|
2606
|
+
el('div', { className: 'cm-bbox cm-mm clickable' + (level === 'ok' ? '' : ' ' + level) + (wide === false ? ' rail' : '') + (refresh.busy ? ' busy' : ''), ...clickableRefreshProps(refresh.busy, refresh.run) },
|
|
2537
2607
|
wide === false
|
|
2538
2608
|
? el('div', { className: 'cm-bbox-rail cm-num' }, railText)
|
|
2539
2609
|
: el(Fragment, null,
|
|
@@ -2678,6 +2748,44 @@ window.__ModuleLoader__.load({
|
|
|
2678
2748
|
el('button', { type: 'button', className: 'cm-btn small primary', onClick: () => choose(true) }, t('quotaStripGuideOn'))))
|
|
2679
2749
|
}
|
|
2680
2750
|
|
|
2751
|
+
// 更新后的提醒(issue #37):告知侧边栏余额/额度图框支持点击立即刷新;
|
|
2752
|
+
// 「知道了」写回 balance.clickHintSeen=true 后永久消失(挂常驻 sidebar.footer.action)。
|
|
2753
|
+
// 仅在侧边栏确有可点击图框(官方余额 / 自定义余额 / Coding Plan)时展示,避免噪音。
|
|
2754
|
+
function BalanceClickGuide(props) {
|
|
2755
|
+
// Hook 全部前置(promptSeen 类标记翻转时会提前 return null,顺序必须稳定)。
|
|
2756
|
+
const costStore = props.useCost ? props.useCost(s => s) : undefined
|
|
2757
|
+
const busyRef = useRef(false)
|
|
2758
|
+
const [dismissed, setDismissed] = useState(false)
|
|
2759
|
+
const state = costStore?.state
|
|
2760
|
+
if (!state) return null
|
|
2761
|
+
const config = state.config
|
|
2762
|
+
const sidebarBalanceOn = config.balance?.display === 'sidebar' || config.balance?.display === 'both'
|
|
2763
|
+
const sidebarCustomOn = config.customBalance?.enabled === true
|
|
2764
|
+
&& (config.customBalance?.display === 'sidebar' || config.customBalance?.display === 'both')
|
|
2765
|
+
const sidebarPlansOn = CODING_PLAN_ROWS.some(r => {
|
|
2766
|
+
const entry = config.codingPlans?.[r.id]
|
|
2767
|
+
return entry?.enabled === true && (entry.display === 'sidebar' || entry.display === 'both')
|
|
2768
|
+
})
|
|
2769
|
+
if (dismissed || config.balance?.clickHintSeen === true || (!sidebarBalanceOn && !sidebarCustomOn && !sidebarPlansOn)) return null
|
|
2770
|
+
// 串行展示:横条引导(QuotaStripGuide)与本卡同为 fixed 顶部卡片,同屏会完全重叠,
|
|
2771
|
+
// 点掉一张露出另一张,视觉上像「点了没反应」。等横条引导处理完(promptSeen)再出现。
|
|
2772
|
+
if (config.quotaStrip?.promptSeen !== true) return null
|
|
2773
|
+
const t = makeT(resolveLocale(config?.locale))
|
|
2774
|
+
const dismiss = () => {
|
|
2775
|
+
if (busyRef.current === true) return
|
|
2776
|
+
// 乐观消失:点击立即隐藏,落盘确认失败才恢复可见(避免宿主渲染时序让卡片看起来「点不掉」)。
|
|
2777
|
+
setDismissed(true)
|
|
2778
|
+
busyRef.current = true
|
|
2779
|
+
props.api.updateConfig({ balance: { ...(config.balance ?? {}), clickHintSeen: true } })
|
|
2780
|
+
.catch(() => { busyRef.current = false; setDismissed(false) })
|
|
2781
|
+
}
|
|
2782
|
+
return el('div', { className: 'cm-qguide', role: 'dialog', 'aria-label': t('balanceClickGuideTitle') },
|
|
2783
|
+
el('h4', null, t('balanceClickGuideTitle')),
|
|
2784
|
+
el('p', null, t('balanceClickGuideBody')),
|
|
2785
|
+
el('div', { className: 'cm-buttons' },
|
|
2786
|
+
el('button', { type: 'button', className: 'cm-btn small primary', onClick: dismiss }, t('balanceClickGuideOk'))))
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2681
2789
|
function BudgetBoxContent(props) {
|
|
2682
2790
|
const { state, wide } = props
|
|
2683
2791
|
const today = state.today
|
|
@@ -2809,13 +2917,13 @@ window.__ModuleLoader__.load({
|
|
|
2809
2917
|
const showToday = config.sidebar !== false
|
|
2810
2918
|
if (!showBalance && !showCustomBalance && !goOk && !plansOn && !budgetOn && !showToday) return null
|
|
2811
2919
|
const nodes = []
|
|
2812
|
-
if (showBalanceBar) nodes.push(el(BalanceBox, { state, wide }))
|
|
2813
|
-
else if (showBalance) nodes.push(el(BalanceRowContent, { state, wide }))
|
|
2814
|
-
if (showCustomBalanceBar) nodes.push(el(CustomBalanceBox, { state, wide }))
|
|
2815
|
-
else if (showCustomBalance) nodes.push(el(CustomBalanceRowContent, { state, wide }))
|
|
2920
|
+
if (showBalanceBar) nodes.push(el(BalanceBox, { state, wide, api: props.api }))
|
|
2921
|
+
else if (showBalance) nodes.push(el(BalanceRowContent, { state, wide, api: props.api }))
|
|
2922
|
+
if (showCustomBalanceBar) nodes.push(el(CustomBalanceBox, { state, wide, api: props.api }))
|
|
2923
|
+
else if (showCustomBalance) nodes.push(el(CustomBalanceRowContent, { state, wide, api: props.api }))
|
|
2816
2924
|
if (plansOn) nodes.push(...sidebarPlanIds.map(id => id === 'minimax'
|
|
2817
|
-
? el(MiniMaxPlanBox, { state, wide })
|
|
2818
|
-
: el(CodingPlanBox, { id, state, wide })))
|
|
2925
|
+
? el(MiniMaxPlanBox, { state, wide, api: props.api })
|
|
2926
|
+
: el(CodingPlanBox, { id, state, wide, api: props.api })))
|
|
2819
2927
|
if (goOk && budgetOn && wide) {
|
|
2820
2928
|
// 同时出现:合并为一张卡片(Go 在上、预算在下,细分隔线),各自保留预警色与自己的详细信息开关。
|
|
2821
2929
|
const goView = goBoxBody(state, config, t)
|
|
@@ -5037,6 +5145,14 @@ window.__ModuleLoader__.load({
|
|
|
5037
5145
|
)
|
|
5038
5146
|
return dispose
|
|
5039
5147
|
})
|
|
5148
|
+
// 更新后的提醒(issue #37):余额/额度图框点击刷新,常驻插槽 + clickHintSeen 门控。
|
|
5149
|
+
slots.inject('sidebar.footer.action', () => {
|
|
5150
|
+
const dispose = slots.register(
|
|
5151
|
+
{ name: 'sidebar.footer.action', id: 'cost-meter-balance-click-guide', order: 2, inject: injected },
|
|
5152
|
+
BalanceClickGuide,
|
|
5153
|
+
)
|
|
5154
|
+
return dispose
|
|
5155
|
+
})
|
|
5040
5156
|
|
|
5041
5157
|
// 峰/谷切换前弹窗提醒:全局 fixed 浮层(fixed 定位与宿主位置无关),挂在常驻的
|
|
5042
5158
|
// sidebar.footer.action 插槽——conversation.composer.dock 仅在有活跃会话的页面渲染,
|
package/lib/pricing.js
CHANGED
|
@@ -199,7 +199,7 @@ export const DEFAULT_PROVIDER_PRICE_TABLE = {
|
|
|
199
199
|
|
|
200
200
|
/** 拓展价格表目录的模型家族分组(展示用;未列出的模型自成一家)。 */
|
|
201
201
|
export const PROVIDER_MODEL_FAMILIES = {
|
|
202
|
-
deepseek: { 'deepseek-v4-flash': 'DeepSeek v4', 'deepseek-v4-pro': 'DeepSeek v4' },
|
|
202
|
+
deepseek: { 'deepseek-v4-flash': 'DeepSeek v4', 'deepseek-v4-pro': 'DeepSeek v4', 'deepseek-v4-flash-vision-exp': 'DeepSeek v4' },
|
|
203
203
|
openai: {
|
|
204
204
|
'gpt-5.6-sol': 'GPT-5.6', 'gpt-5.6-terra': 'GPT-5.6', 'gpt-5.6-luna': 'GPT-5.6',
|
|
205
205
|
'gpt-5.5': 'GPT-5.5', 'gpt-5.5-pro': 'GPT-5.5',
|
|
@@ -283,6 +283,14 @@ export const DEFAULT_PRICE_TABLE = {
|
|
|
283
283
|
peak: { cacheHit: 0.044, cacheMiss: 1.32, output: 3.96 },
|
|
284
284
|
legacyBase: { cacheHit: 0.003625, cacheMiss: 0.435, output: 0.87 },
|
|
285
285
|
},
|
|
286
|
+
// Vision-Exp(实验版多模态)与 flash 同价;峰谷时代后发布,无历史基础价档。
|
|
287
|
+
'deepseek-v4-flash-vision-exp': {
|
|
288
|
+
cacheHit: 0.007,
|
|
289
|
+
cacheMiss: 0.22,
|
|
290
|
+
output: 0.66,
|
|
291
|
+
offPeak: { cacheHit: 0.007, cacheMiss: 0.22, output: 0.66 },
|
|
292
|
+
peak: { cacheHit: 0.014, cacheMiss: 0.44, output: 1.32 },
|
|
293
|
+
},
|
|
286
294
|
},
|
|
287
295
|
default: { cacheHit: 0.007, cacheMiss: 0.22, output: 0.66 },
|
|
288
296
|
}
|
package/lib/store.js
CHANGED
|
@@ -89,6 +89,7 @@ export function defaultConfig() {
|
|
|
89
89
|
showProgressBar: false, // 全局:侧边栏余额以三段进度条展示(蓝=余额,橙=当日,灰=已用)
|
|
90
90
|
budgetCap: null, // 可选:手动额度上限;留空则优先用 API 返回的 max_budget;仍无则整条蓝色
|
|
91
91
|
reconcile: true, // 余额差交叉校验:官方余额当日变动与本地账本今日合计偏差超阈值时提示
|
|
92
|
+
clickHintSeen: false, // 更新后提醒:余额图框点击刷新的引导是否已处理(issue #37)
|
|
92
93
|
},
|
|
93
94
|
goQuota: {
|
|
94
95
|
enabled: true, // 启用 OpenCode Go 订阅额度读取与显示(像预算开关一样的总开关)
|
|
@@ -282,6 +283,7 @@ const VALIDATION_MESSAGES = {
|
|
|
282
283
|
balanceDisplay: 'balance.display 必须是 sidebar / settings / both / off',
|
|
283
284
|
balanceRefresh: 'balance.refreshMinutes 必须是 1-1440 的整数',
|
|
284
285
|
balanceShowBar: 'balance.showProgressBar 必须是布尔值',
|
|
286
|
+
balanceClickHint: 'balance.clickHintSeen 必须是布尔值',
|
|
285
287
|
balanceReconcile: 'balance.reconcile 必须是布尔值',
|
|
286
288
|
balanceBudgetCap: 'balance.budgetCap 必须是非负数或 null',
|
|
287
289
|
goQuota: 'goQuota 非法',
|
|
@@ -350,6 +352,7 @@ const VALIDATION_MESSAGES = {
|
|
|
350
352
|
balanceDisplay: 'balance.display must be sidebar / settings / both / off',
|
|
351
353
|
balanceRefresh: 'balance.refreshMinutes must be an integer from 1 to 1440',
|
|
352
354
|
balanceShowBar: 'balance.showProgressBar must be a boolean',
|
|
355
|
+
balanceClickHint: 'balance.clickHintSeen must be a boolean',
|
|
353
356
|
balanceReconcile: 'balance.reconcile must be a boolean',
|
|
354
357
|
balanceBudgetCap: 'balance.budgetCap must be a non-negative number or null',
|
|
355
358
|
goQuota: 'Invalid goQuota',
|
|
@@ -517,6 +520,7 @@ export function applyConfigPatch(current, patch) {
|
|
|
517
520
|
if (!Number.isInteger(refreshMinutes) || refreshMinutes < 1 || refreshMinutes > 1440) errors.push(vmsg(locale, 'balanceRefresh'))
|
|
518
521
|
else balance.refreshMinutes = refreshMinutes
|
|
519
522
|
if (balance.showProgressBar !== undefined && typeof balance.showProgressBar !== 'boolean') errors.push(vmsg(locale, 'balanceShowBar'))
|
|
523
|
+
if (balance.clickHintSeen !== undefined && typeof balance.clickHintSeen !== 'boolean') errors.push(vmsg(locale, 'balanceClickHint'))
|
|
520
524
|
if (balance.reconcile !== undefined && typeof balance.reconcile !== 'boolean') errors.push(vmsg(locale, 'balanceReconcile'))
|
|
521
525
|
if (balance.budgetCap !== undefined && balance.budgetCap !== null) {
|
|
522
526
|
const cap = Number(balance.budgetCap)
|
|
@@ -700,6 +704,7 @@ export function sanitizeConfig(raw) {
|
|
|
700
704
|
: base.balance.showProgressBar === true
|
|
701
705
|
}
|
|
702
706
|
balance.showProgressBar = balance.showProgressBar === true
|
|
707
|
+
balance.clickHintSeen = balance.clickHintSeen === true
|
|
703
708
|
balance.reconcile = balance.reconcile !== false
|
|
704
709
|
const cap = Number(balance.budgetCap)
|
|
705
710
|
balance.budgetCap = Number.isFinite(cap) && cap > 0 ? cap : null
|
package/lib/typert.host.js
CHANGED
|
@@ -144,6 +144,8 @@ const configSchema = z.object({
|
|
|
144
144
|
showProgressBar: z.boolean().optional(),
|
|
145
145
|
budgetCap: z.union([num, z.null()]).optional(),
|
|
146
146
|
reconcile: z.boolean().optional(),
|
|
147
|
+
// 更新后提醒:余额图框点击刷新引导是否已处理(issue #37,内部标记)。
|
|
148
|
+
clickHintSeen: z.boolean().optional(),
|
|
147
149
|
}),
|
|
148
150
|
goQuota: z.object({
|
|
149
151
|
enabled: z.boolean(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-cost-meter",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.32",
|
|
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",
|