dsh-cost-meter 1.5.15 → 1.5.16
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 +6 -5
- package/README.md +6 -5
- package/lib/client.js +126 -0
- package/lib/store.js +19 -0
- package/lib/typert.host.js +4 -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) · 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)
|
|
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)
|
|
@@ -40,6 +40,7 @@ English | [中文](README.md)
|
|
|
40
40
|
| Budget settings | Settings page, top | Limit, period (today / month / cumulative / custom date range), used % |
|
|
41
41
|
| Price table | Settings page | Per-model off-peak / peak prices (input/output shorthand supported; cache prices derived automatically); fully editable |
|
|
42
42
|
| Peak/off-peak hours display | Settings / budget / today | Shows UTC peak hours 01:00–04:00 and 06:00–10:00 with the current tier; expanded view shows a peak/off-peak period strip (current period + countdown), collapsed (rail) view shows a vertical peak/off-peak progress bar; independently toggleable |
|
|
43
|
+
| Peak/off-peak switch popup alert | Global overlay | A bottom-right popup appears when the next tier switch is within the configured lead time (default 2 minutes, 1–30); alert type selectable (entering peak / entering off-peak / both); one alert per switch point; toggle lives in the peak pricing panel in Settings |
|
|
43
44
|
| Official price sync | Settings page | Fetches and parses the official pricing page, applies with one click |
|
|
44
45
|
| UI language | Settings → Display settings | Simplified Chinese / English / Follow browser (auto); switches instantly and auto-saves |
|
|
45
46
|
| AI price sync | [prompt](docs/AI-PRICE-SYNC-PROMPT.en.md) | DeepSeek official sync; other providers use the verified official price catalog and manual configuration |
|
|
@@ -192,22 +193,22 @@ Real captures from an actual DSH sidebar of the period strip and collapsed verti
|
|
|
192
193
|
dsh plugin --profile web add dsh-cost-meter
|
|
193
194
|
```
|
|
194
195
|
|
|
195
|
-
**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.
|
|
196
|
+
**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.16`** — review the script before running):
|
|
196
197
|
|
|
197
198
|
```powershell
|
|
198
|
-
irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.
|
|
199
|
+
irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.16/install.ps1 | iex
|
|
199
200
|
```
|
|
200
201
|
|
|
201
202
|
**Or a plain command line** (the machine must already have pnpm and git; also pinned to the tag):
|
|
202
203
|
|
|
203
204
|
```sh
|
|
204
|
-
dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.
|
|
205
|
+
dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.16
|
|
205
206
|
```
|
|
206
207
|
|
|
207
208
|
Without git, use the GitHub tag archive:
|
|
208
209
|
|
|
209
210
|
```sh
|
|
210
|
-
dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.
|
|
211
|
+
dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.16.tar.gz
|
|
211
212
|
```
|
|
212
213
|
|
|
213
214
|
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 六家)
|
|
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)
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
| 预算设置 | 设置页顶部 | 额度、周期(今日/本月/累计/自定义日期区间)、已用% |
|
|
42
42
|
| 价格表 | 设置页 | 每模型 谷时/峰时 两档价格(支持 input/output 简写,缓存价自动补齐),增删改自由 |
|
|
43
43
|
| 峰谷计价时段显示 | 设置页 / 预算 / 今日费用 | 显示 UTC 峰时段 01:00–04:00、06:00–10:00 与当前档位;展开态显示峰时/平价时段条(当前时段 + 倒计时),收起(rail)态显示竖向峰谷进度条,可单独开关 |
|
|
44
|
+
| 峰/谷切换弹窗提醒 | 全局浮层 | 距进入峰/谷时段不足设定提前量(默认 2 分钟,1-30 可配)时右下角弹窗提醒;提醒类型可选(进入峰 / 进入谷 / 峰和谷),同一切换点只提醒一次,设置页峰谷计价面板内开关 |
|
|
44
45
|
| 官方价格同步 | 设置页 | 抓取解析官方定价页,一键应用 |
|
|
45
46
|
| 界面语言 | 设置页 → 显示设置 | 简体中文 / English / 跟随浏览器(自动);切换即时生效并自动保存 |
|
|
46
47
|
| AI 价格同步 | [提示词](docs/AI-PRICE-SYNC-PROMPT.md) | DeepSeek 官方同步;其他 provider 使用已核对的官方价格目录与手动配置 |
|
|
@@ -194,22 +195,22 @@
|
|
|
194
195
|
dsh plugin --profile web add dsh-cost-meter
|
|
195
196
|
```
|
|
196
197
|
|
|
197
|
-
**PowerShell 一键脚本**(复制整行粘贴回车;自动补齐 pnpm、自动探测 git,无需克隆仓库;安装链**固定到发布 tag `v1.5.
|
|
198
|
+
**PowerShell 一键脚本**(复制整行粘贴回车;自动补齐 pnpm、自动探测 git,无需克隆仓库;安装链**固定到发布 tag `v1.5.16`**,建议先下载审阅再运行):
|
|
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.16/install.ps1 | iex
|
|
201
202
|
```
|
|
202
203
|
|
|
203
204
|
**或直接命令行**(机器上需已有 pnpm 与 git;同样固定到 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.16
|
|
207
208
|
```
|
|
208
209
|
|
|
209
210
|
没有 git 时可用 GitHub tag 打包直链:
|
|
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.16.tar.gz
|
|
213
214
|
```
|
|
214
215
|
|
|
215
216
|
安装后**重启** `dsh web`(插件行、Typert 清单与客户端 bundle 均在启动时扫描):
|
package/lib/client.js
CHANGED
|
@@ -110,6 +110,13 @@ window.__ModuleLoader__.load({
|
|
|
110
110
|
'.cm-msg.err{color:var(--dsw-alias-state-error-primary)}',
|
|
111
111
|
'.cm-hint{font-size:12px;color:var(--dsw-alias-label-tertiary)}',
|
|
112
112
|
'.cm-budget{border:1px solid var(--dsw-alias-border-l1);border-radius:12px;padding:14px 16px;background:var(--dsw-alias-bg-layer-1);display:flex;flex-direction:column;gap:10px}',
|
|
113
|
+
'.cm-peak-alert{position:fixed;right:20px;bottom:20px;z-index:9999;max-width:320px;padding:12px 14px;border-radius:10px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);box-shadow:0 8px 24px rgba(0,0,0,.18);display:flex;flex-direction:column;gap:6px;font-size:12px;animation:cm-peak-alert-in .18s ease-out}',
|
|
114
|
+
'.cm-peak-alert-peak{border-left:3px solid #f59e0b}',
|
|
115
|
+
'.cm-peak-alert-offpeak{border-left:3px solid var(--dsw-alias-brand-primary)}',
|
|
116
|
+
'.cm-peak-alert-title{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary)}',
|
|
117
|
+
'.cm-peak-alert-body{color:var(--dsw-alias-label-secondary);line-height:1.5}',
|
|
118
|
+
'.cm-peak-alert-actions{display:flex;justify-content:flex-end}',
|
|
119
|
+
'@keyframes cm-peak-alert-in{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}',
|
|
113
120
|
'.cm-budget-head{display:flex;align-items:center;justify-content:space-between;gap:8px}',
|
|
114
121
|
'.cm-budget-bar{height:8px;border-radius:999px;background:var(--dsw-alias-bg-layer-3);overflow:hidden}',
|
|
115
122
|
'.cm-budget-fill{height:100%;border-radius:999px;background:var(--dsw-alias-state-business-primary);transition:width .3s ease}',
|
|
@@ -422,6 +429,18 @@ window.__ModuleLoader__.load({
|
|
|
422
429
|
peakStyleLabel: '峰谷时段条样式',
|
|
423
430
|
peakStyleCompact: '简洁(单行紧凑)',
|
|
424
431
|
peakStyleClassic: '经典(分段与胶囊芯片)',
|
|
432
|
+
peakAlertLabel: '峰/谷切换前弹窗提醒',
|
|
433
|
+
peakAlertAheadLabel: '提前提醒(分钟,1-30)',
|
|
434
|
+
peakAlertTargetLabel: '提醒类型',
|
|
435
|
+
peakAlertTargetPeak: '进入峰时',
|
|
436
|
+
peakAlertTargetOffPeak: '进入谷时',
|
|
437
|
+
peakAlertTargetBoth: '峰和谷',
|
|
438
|
+
peakAlertTitlePeak: '即将进入峰时',
|
|
439
|
+
peakAlertTitleOffPeak: '即将进入谷时',
|
|
440
|
+
peakAlertBody: '约 {time} 后计费档位切换为{phase}价,请注意本时段调用成本。',
|
|
441
|
+
peakAlertPhasePeak: '峰时',
|
|
442
|
+
peakAlertPhaseOffPeak: '谷时',
|
|
443
|
+
peakAlertBtn: '知道了',
|
|
425
444
|
peakPanelTitle: '峰谷计价与提示',
|
|
426
445
|
peakNoticeHiddenHint: '提示已隐藏:需启用峰谷计价并开启「峰时高价时段显著提示」。',
|
|
427
446
|
groupGeneral: '常规',
|
|
@@ -723,6 +742,18 @@ window.__ModuleLoader__.load({
|
|
|
723
742
|
peakStyleLabel: 'Peak period strip style',
|
|
724
743
|
peakStyleCompact: 'Compact (one-line)',
|
|
725
744
|
peakStyleClassic: 'Classic (segments & chip)',
|
|
745
|
+
peakAlertLabel: 'Popup alert before peak/off-peak switch',
|
|
746
|
+
peakAlertAheadLabel: 'Lead time (minutes, 1-30)',
|
|
747
|
+
peakAlertTargetLabel: 'Alert type',
|
|
748
|
+
peakAlertTargetPeak: 'Entering peak',
|
|
749
|
+
peakAlertTargetOffPeak: 'Entering off-peak',
|
|
750
|
+
peakAlertTargetBoth: 'Both',
|
|
751
|
+
peakAlertTitlePeak: 'Entering peak hours soon',
|
|
752
|
+
peakAlertTitleOffPeak: 'Entering off-peak hours soon',
|
|
753
|
+
peakAlertBody: 'Billing switches to {phase} pricing in about {time}; mind your call costs in this window.',
|
|
754
|
+
peakAlertPhasePeak: 'peak',
|
|
755
|
+
peakAlertPhaseOffPeak: 'off-peak',
|
|
756
|
+
peakAlertBtn: 'Got it',
|
|
726
757
|
peakPanelTitle: 'Peak/off-peak pricing & notice',
|
|
727
758
|
peakNoticeHiddenHint: 'Notice hidden: enable peak/off-peak pricing and the “prominent notice” toggle.',
|
|
728
759
|
groupGeneral: 'General',
|
|
@@ -1024,6 +1055,9 @@ window.__ModuleLoader__.load({
|
|
|
1024
1055
|
? v.peakWindows.map((w, i) => ({ start: needNum(w.start, path + '.peakWindows[' + i + '].start'), end: needNum(w.end, path + '.peakWindows[' + i + '].end') }))
|
|
1025
1056
|
: [],
|
|
1026
1057
|
peakNotice: v.peakNotice !== false,
|
|
1058
|
+
peakAlertEnabled: v.peakAlertEnabled !== false,
|
|
1059
|
+
peakAlertAhead: Number.isFinite(v.peakAlertAhead) && v.peakAlertAhead >= 1 && v.peakAlertAhead <= 30 ? v.peakAlertAhead : 2,
|
|
1060
|
+
peakAlertTarget: v.peakAlertTarget === 'peak' || v.peakAlertTarget === 'offpeak' ? v.peakAlertTarget : 'both',
|
|
1027
1061
|
peakStyle: v.peakStyle === 'classic' ? 'classic' : 'compact',
|
|
1028
1062
|
priceMatch: v.priceMatch === 'exact' ? 'exact' : 'auto',
|
|
1029
1063
|
priceOverrides: (() => {
|
|
@@ -1954,6 +1988,43 @@ window.__ModuleLoader__.load({
|
|
|
1954
1988
|
: t('countdownMinute', { m: minutes })
|
|
1955
1989
|
}
|
|
1956
1990
|
|
|
1991
|
+
/**
|
|
1992
|
+
* 峰/谷切换前弹窗提醒:距下次相位切换不足配置提前量(默认 2 分钟)时,
|
|
1993
|
+
* 右下角弹全局浮层;提醒类型按配置过滤(进入峰/进入谷/峰和谷);
|
|
1994
|
+
* 同一切换点只弹一次(手动关闭即记点),切换完成后浮层自然消失。
|
|
1995
|
+
*/
|
|
1996
|
+
function PeakAlert(props) {
|
|
1997
|
+
const costStore = props.useCost ? props.useCost(s => s) : undefined
|
|
1998
|
+
const config = costStore?.state?.config
|
|
1999
|
+
const [now, setNow] = useState(Date.now())
|
|
2000
|
+
const [dismissedAt, setDismissedAt] = useState(null)
|
|
2001
|
+
useEffect(() => {
|
|
2002
|
+
const timer = window.setInterval(() => setNow(Date.now()), 10000)
|
|
2003
|
+
return () => window.clearInterval(timer)
|
|
2004
|
+
}, [])
|
|
2005
|
+
if (!config || config.peakAlertEnabled !== true || config.peakEnabled !== true) return null
|
|
2006
|
+
const effectiveAtMs = Date.parse(config.peakEffectiveAt || '')
|
|
2007
|
+
if (Number.isFinite(effectiveAtMs) && now < effectiveAtMs) return null
|
|
2008
|
+
const view = peakPhaseAt(now, Array.isArray(config.peakWindows) ? config.peakWindows : [])
|
|
2009
|
+
if (view === null) return null
|
|
2010
|
+
const target = config.peakAlertTarget === 'peak' || config.peakAlertTarget === 'offpeak' ? config.peakAlertTarget : 'both'
|
|
2011
|
+
if (target !== 'both' && target !== (view.nextIntoPeak ? 'peak' : 'offpeak')) return null
|
|
2012
|
+
const aheadMinutes = Number(config.peakAlertAhead)
|
|
2013
|
+
const aheadMs = (Number.isFinite(aheadMinutes) && aheadMinutes >= 1 ? aheadMinutes : 2) * 60000
|
|
2014
|
+
if (now < view.nextAtMs - aheadMs || now >= view.nextAtMs) return null
|
|
2015
|
+
if (dismissedAt === view.nextAtMs) return null
|
|
2016
|
+
const t = makeT(resolveLocale(config.locale))
|
|
2017
|
+
const intoPeak = view.nextIntoPeak === true
|
|
2018
|
+
return el('div', { className: 'cm-peak-alert ' + (intoPeak ? 'cm-peak-alert-peak' : 'cm-peak-alert-offpeak'), role: 'alert' },
|
|
2019
|
+
el('div', { className: 'cm-peak-alert-title' }, t(intoPeak ? 'peakAlertTitlePeak' : 'peakAlertTitleOffPeak')),
|
|
2020
|
+
el('div', { className: 'cm-peak-alert-body' }, t('peakAlertBody', {
|
|
2021
|
+
time: countdownText(view, now, t),
|
|
2022
|
+
phase: t(intoPeak ? 'peakAlertPhasePeak' : 'peakAlertPhaseOffPeak'),
|
|
2023
|
+
})),
|
|
2024
|
+
el('div', { className: 'cm-peak-alert-actions' },
|
|
2025
|
+
el('button', { className: 'cm-btn', onClick: () => setDismissedAt(view.nextAtMs) }, t('peakAlertBtn'))))
|
|
2026
|
+
}
|
|
2027
|
+
|
|
1957
2028
|
/** 展开态简洁样式:单行紧凑时段条——细轨道(左橙右蓝,非当前段淡化)+ 标记线 + 右侧倒计时文本。 */
|
|
1958
2029
|
function PeakStrip(props) {
|
|
1959
2030
|
const { config, t } = props
|
|
@@ -2718,6 +2789,35 @@ window.__ModuleLoader__.load({
|
|
|
2718
2789
|
},
|
|
2719
2790
|
el('option', { value: 'compact' }, t('peakStyleCompact')),
|
|
2720
2791
|
el('option', { value: 'classic' }, t('peakStyleClassic'))))),
|
|
2792
|
+
el('div', { className: 'cm-grid' },
|
|
2793
|
+
el('div', { className: 'cm-field' },
|
|
2794
|
+
el('label', { className: 'cm-check' },
|
|
2795
|
+
el('input', {
|
|
2796
|
+
type: 'checkbox',
|
|
2797
|
+
checked: draft?.peakAlertEnabled !== false,
|
|
2798
|
+
onChange: event => setField('peakAlertEnabled', event.target.checked),
|
|
2799
|
+
}),
|
|
2800
|
+
el('span', null, t('peakAlertLabel')))),
|
|
2801
|
+
el('div', { className: 'cm-field' },
|
|
2802
|
+
el('label', null, t('peakAlertAheadLabel')),
|
|
2803
|
+
el('input', {
|
|
2804
|
+
className: 'cm-input narrow', type: 'number', min: '1', max: '30', step: '1',
|
|
2805
|
+
value: String(typeof draft?.peakAlertAhead === 'number' && Number.isFinite(draft.peakAlertAhead) && draft.peakAlertAhead >= 1 && draft.peakAlertAhead <= 30 ? draft.peakAlertAhead : 2),
|
|
2806
|
+
onChange: event => {
|
|
2807
|
+
const parsed = Number(event.target.value)
|
|
2808
|
+
if (Number.isInteger(parsed) && parsed >= 1 && parsed <= 30) setField('peakAlertAhead', parsed)
|
|
2809
|
+
},
|
|
2810
|
+
})),
|
|
2811
|
+
el('div', { className: 'cm-field' },
|
|
2812
|
+
el('label', null, t('peakAlertTargetLabel')),
|
|
2813
|
+
el('select', {
|
|
2814
|
+
className: 'cm-input',
|
|
2815
|
+
value: draft?.peakAlertTarget === 'peak' || draft?.peakAlertTarget === 'offpeak' ? draft.peakAlertTarget : 'both',
|
|
2816
|
+
onChange: event => setField('peakAlertTarget', event.target.value),
|
|
2817
|
+
},
|
|
2818
|
+
el('option', { value: 'both' }, t('peakAlertTargetBoth')),
|
|
2819
|
+
el('option', { value: 'peak' }, t('peakAlertTargetPeak')),
|
|
2820
|
+
el('option', { value: 'offpeak' }, t('peakAlertTargetOffPeak'))))),
|
|
2721
2821
|
el('div', { className: 'cm-peak-preview' },
|
|
2722
2822
|
draft?.peakEnabled !== false && draft?.peakNotice !== false
|
|
2723
2823
|
? peakNoticeEl(state, previewConfig, t)
|
|
@@ -4406,6 +4506,26 @@ window.__ModuleLoader__.load({
|
|
|
4406
4506
|
})
|
|
4407
4507
|
}
|
|
4408
4508
|
|
|
4509
|
+
// 峰/谷切换前弹窗提醒:全局 fixed 浮层,挂在 dock 插槽常驻渲染,组件内部
|
|
4510
|
+
// 再按配置门控(peakAlertEnabled + peakEnabled);开关变化时重挂/卸载。
|
|
4511
|
+
const peakAlertActive = { gen: 0, dispose: null }
|
|
4512
|
+
const registerPeakAlert = enabled => {
|
|
4513
|
+
if (peakAlertActive.dispose !== null) { peakAlertActive.dispose(); peakAlertActive.dispose = null }
|
|
4514
|
+
peakAlertActive.gen += 1
|
|
4515
|
+
const gen = peakAlertActive.gen
|
|
4516
|
+
if (!enabled) return
|
|
4517
|
+
slots.inject('conversation.composer.dock', () => {
|
|
4518
|
+
if (peakAlertActive.gen !== gen) return
|
|
4519
|
+
const dispose = slots.register({ name: 'conversation.composer.dock', id: 'cost-meter-peak-alert', order: 8, inject: injected }, PeakAlert)
|
|
4520
|
+
if (peakAlertActive.gen !== gen) { dispose(); return }
|
|
4521
|
+
peakAlertActive.dispose = dispose
|
|
4522
|
+
return () => {
|
|
4523
|
+
if (peakAlertActive.dispose === dispose) peakAlertActive.dispose = null
|
|
4524
|
+
dispose()
|
|
4525
|
+
}
|
|
4526
|
+
})
|
|
4527
|
+
}
|
|
4528
|
+
|
|
4409
4529
|
// 设置页「费用/Cost」分节:语言变化时撤销旧注册并重建,让侧边栏标签同步。
|
|
4410
4530
|
const sectionActive = { gen: 0, dispose: null }
|
|
4411
4531
|
const registerSection = locale => {
|
|
@@ -4478,6 +4598,7 @@ window.__ModuleLoader__.load({
|
|
|
4478
4598
|
let lastCorner = null
|
|
4479
4599
|
let lastSectionLocale = null
|
|
4480
4600
|
let lastUsagePosition = null
|
|
4601
|
+
let lastPeakAlert = null
|
|
4481
4602
|
const sync = () => {
|
|
4482
4603
|
const state = store.getSnapshot().state
|
|
4483
4604
|
const position = state?.config?.position ?? 'dock'
|
|
@@ -4500,6 +4621,11 @@ window.__ModuleLoader__.load({
|
|
|
4500
4621
|
registerCorner(cornerEnabled)
|
|
4501
4622
|
lastCorner = cornerEnabled
|
|
4502
4623
|
}
|
|
4624
|
+
const peakAlertOn = state?.config?.peakAlertEnabled !== false && state?.config?.peakEnabled === true
|
|
4625
|
+
if (peakAlertOn !== lastPeakAlert) {
|
|
4626
|
+
registerPeakAlert(peakAlertOn)
|
|
4627
|
+
lastPeakAlert = peakAlertOn
|
|
4628
|
+
}
|
|
4503
4629
|
if (sectionLocale !== lastSectionLocale) {
|
|
4504
4630
|
registerSection(sectionLocale)
|
|
4505
4631
|
lastSectionLocale = sectionLocale
|
package/lib/store.js
CHANGED
|
@@ -38,6 +38,9 @@ export function defaultConfig() {
|
|
|
38
38
|
peakEffectiveAt: DEFAULT_PEAK_EFFECTIVE_AT,
|
|
39
39
|
peakWindows: DEFAULT_PEAK_WINDOWS.map(w => ({ ...w })),
|
|
40
40
|
peakNotice: true, // 峰时高价时段显著提示(侧边栏预算框/今日费用/设置页预算面板)
|
|
41
|
+
peakAlertEnabled: true, // 峰/谷切换前弹窗提醒(全局浮层)
|
|
42
|
+
peakAlertAhead: 2, // 提前提醒量(分钟,1-30)
|
|
43
|
+
peakAlertTarget: 'both', // 提醒类型:peak(进入峰) | offpeak(进入谷) | both(峰和谷)
|
|
41
44
|
showSessionId: false, // 会话列表中附显会话 ID(默认只显示标题,需要时开启)
|
|
42
45
|
legacyAutoImportedAt: 0, // 安装前历史自动导入标记(issue #27):完成时刻 ms;0 = 尚未跑过
|
|
43
46
|
peakStyle: 'compact', // 峰谷时段条样式:compact(简洁单行/竖向同构) | classic(经典分段/胶囊芯片)
|
|
@@ -221,6 +224,9 @@ const VALIDATION_MESSAGES = {
|
|
|
221
224
|
peakEffectiveAt: 'peakEffectiveAt 非法',
|
|
222
225
|
peakWindows: 'peakWindows 必须是数组',
|
|
223
226
|
peakNotice: 'peakNotice 必须是布尔值',
|
|
227
|
+
peakAlertEnabled: 'peakAlertEnabled 必须是布尔值',
|
|
228
|
+
peakAlertAhead: 'peakAlertAhead 必须是 1-30 的整数',
|
|
229
|
+
peakAlertTarget: 'peakAlertTarget 必须是 peak / offpeak / both',
|
|
224
230
|
showSessionId: 'showSessionId 必须是布尔值',
|
|
225
231
|
peakStyle: 'peakStyle 必须是 compact / classic',
|
|
226
232
|
priceMatch: 'priceMatch 必须是 auto / exact',
|
|
@@ -282,6 +288,9 @@ const VALIDATION_MESSAGES = {
|
|
|
282
288
|
peakEffectiveAt: 'Invalid peakEffectiveAt',
|
|
283
289
|
peakWindows: 'peakWindows must be an array',
|
|
284
290
|
peakNotice: 'peakNotice must be a boolean',
|
|
291
|
+
peakAlertEnabled: 'peakAlertEnabled must be a boolean',
|
|
292
|
+
peakAlertAhead: 'peakAlertAhead must be an integer between 1 and 30',
|
|
293
|
+
peakAlertTarget: 'peakAlertTarget must be peak / offpeak / both',
|
|
285
294
|
showSessionId: 'showSessionId must be a boolean',
|
|
286
295
|
peakStyle: 'peakStyle must be compact / classic',
|
|
287
296
|
priceMatch: 'priceMatch must be auto / exact',
|
|
@@ -405,6 +414,11 @@ export function applyConfigPatch(current, patch) {
|
|
|
405
414
|
if (typeof candidate.peakEffectiveAt !== 'string') errors.push(vmsg(locale, 'peakEffectiveAt'))
|
|
406
415
|
if (!Array.isArray(candidate.peakWindows)) errors.push(vmsg(locale, 'peakWindows'))
|
|
407
416
|
if (typeof candidate.peakNotice !== 'boolean') errors.push(vmsg(locale, 'peakNotice'))
|
|
417
|
+
// 峰/谷切换前弹窗提醒:开关可缺省(默认开);提前量 1-30 分钟;类型三选一。
|
|
418
|
+
if (candidate.peakAlertEnabled !== undefined && typeof candidate.peakAlertEnabled !== 'boolean') errors.push(vmsg(locale, 'peakAlertEnabled'))
|
|
419
|
+
const alertAhead = Number(candidate.peakAlertAhead)
|
|
420
|
+
if (!Number.isInteger(alertAhead) || alertAhead < 1 || alertAhead > 30) errors.push(vmsg(locale, 'peakAlertAhead'))
|
|
421
|
+
if (!['peak', 'offpeak', 'both'].includes(candidate.peakAlertTarget)) errors.push(vmsg(locale, 'peakAlertTarget'))
|
|
408
422
|
if (candidate.showSessionId !== undefined && typeof candidate.showSessionId !== 'boolean') errors.push(vmsg(locale, 'showSessionId'))
|
|
409
423
|
if (candidate.peakStyle !== 'compact' && candidate.peakStyle !== 'classic') errors.push(vmsg(locale, 'peakStyle'))
|
|
410
424
|
if (candidate.priceMatch !== 'auto' && candidate.priceMatch !== 'exact') errors.push(vmsg(locale, 'priceMatch'))
|
|
@@ -589,6 +603,11 @@ export function sanitizeConfig(raw) {
|
|
|
589
603
|
out.decimals = Math.max(0, Math.min(10, Math.floor(Number(out.decimals) || 0)))
|
|
590
604
|
out.historyDays = Math.max(7, Math.min(3650, Math.floor(Number(out.historyDays) || 180)))
|
|
591
605
|
out.showSessionId = out.showSessionId === true
|
|
606
|
+
// 峰/谷切换弹窗提醒:非法值定向收敛(开关默认开、提前量回 2、类型回 both)。
|
|
607
|
+
out.peakAlertEnabled = out.peakAlertEnabled !== false
|
|
608
|
+
const alertAhead = Number(out.peakAlertAhead)
|
|
609
|
+
out.peakAlertAhead = Number.isInteger(alertAhead) && alertAhead >= 1 && alertAhead <= 30 ? alertAhead : 2
|
|
610
|
+
out.peakAlertTarget = oneOf(out.peakAlertTarget, ['peak', 'offpeak', 'both'], 'both')
|
|
592
611
|
// 安装前历史自动导入标记:有限正数保留,否则归零(下次启动重试)。
|
|
593
612
|
out.legacyAutoImportedAt = isNum(Number(out.legacyAutoImportedAt)) && Number(out.legacyAutoImportedAt) > 0 ? Number(out.legacyAutoImportedAt) : 0
|
|
594
613
|
if (!isNum(out.exchangeRate) || out.exchangeRate <= 0) out.exchangeRate = base.exchangeRate
|
package/lib/typert.host.js
CHANGED
|
@@ -103,6 +103,10 @@ const configSchema = z.object({
|
|
|
103
103
|
peakEffectiveAt: z.string(),
|
|
104
104
|
peakWindows: z.array(z.object({ start: num, end: num })),
|
|
105
105
|
peakNotice: z.boolean().optional(),
|
|
106
|
+
// 峰/谷切换前弹窗提醒:开关(默认开)/提前分钟数(1-30)/类型(峰|谷|两者)。
|
|
107
|
+
peakAlertEnabled: z.boolean().optional(),
|
|
108
|
+
peakAlertAhead: num.optional(),
|
|
109
|
+
peakAlertTarget: z.enum(['peak', 'offpeak', 'both']).optional(),
|
|
106
110
|
showSessionId: z.boolean().optional(),
|
|
107
111
|
// 安装前历史自动导入完成时刻(issue #27,内部标记;0/缺席 = 尚未跑过)。
|
|
108
112
|
legacyAutoImportedAt: num.optional(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-cost-meter",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.16",
|
|
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, in a bilingual (Chinese/English) UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|