dsh-cost-meter 1.5.16 → 1.5.18
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 -6
- package/README.md +6 -6
- package/lib/client.js +86 -11
- package/lib/store.js +10 -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) · 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,7 +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
|
|
43
|
+
| Peak/off-peak switch popup alert | Global overlay | A full-width bracketed popup appears when the next tier switch is within the configured lead time (default 2 minutes, 1–30), with an alert-colored badge distinguishing entering peak vs off-peak; position selectable (**bottom-right / screen center**), alert type selectable (entering peak / entering off-peak / both), one alert per switch point; optionally **sends a browser (system) notification** (so you still get alerted when the page is backgrounded; requires granting notification permission); configured in the peak pricing panel in Settings |
|
|
44
44
|
| Official price sync | Settings page | Fetches and parses the official pricing page, applies with one click |
|
|
45
45
|
| UI language | Settings → Display settings | Simplified Chinese / English / Follow browser (auto); switches instantly and auto-saves |
|
|
46
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 |
|
|
@@ -193,22 +193,22 @@ Real captures from an actual DSH sidebar of the period strip and collapsed verti
|
|
|
193
193
|
dsh plugin --profile web add dsh-cost-meter
|
|
194
194
|
```
|
|
195
195
|
|
|
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.
|
|
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.18`** — review the script before running):
|
|
197
197
|
|
|
198
198
|
```powershell
|
|
199
|
-
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.18/install.ps1 | iex
|
|
200
200
|
```
|
|
201
201
|
|
|
202
202
|
**Or a plain command line** (the machine must already have pnpm and git; also pinned to the tag):
|
|
203
203
|
|
|
204
204
|
```sh
|
|
205
|
-
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.18
|
|
206
206
|
```
|
|
207
207
|
|
|
208
208
|
Without git, use the GitHub tag archive:
|
|
209
209
|
|
|
210
210
|
```sh
|
|
211
|
-
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.18.tar.gz
|
|
212
212
|
```
|
|
213
213
|
|
|
214
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,7 +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
|
+
| 峰/谷切换弹窗提醒 | 全局浮层 | 距进入峰/谷时段不足设定提前量(默认 2 分钟,1-30 可配)时全屏色条徽标弹窗(提醒色区分进入峰/谷);弹窗位置可选**右下角 / 屏幕中心**,提醒类型可选(进入峰 / 进入谷 / 峰和谷),同一切换点只提醒一次;可选**同步发送浏览器(系统)通知**(页面最小化也能收到,需授权通知权限),设置页峰谷计价面板内配置 |
|
|
45
45
|
| 官方价格同步 | 设置页 | 抓取解析官方定价页,一键应用 |
|
|
46
46
|
| 界面语言 | 设置页 → 显示设置 | 简体中文 / English / 跟随浏览器(自动);切换即时生效并自动保存 |
|
|
47
47
|
| AI 价格同步 | [提示词](docs/AI-PRICE-SYNC-PROMPT.md) | DeepSeek 官方同步;其他 provider 使用已核对的官方价格目录与手动配置 |
|
|
@@ -195,22 +195,22 @@
|
|
|
195
195
|
dsh plugin --profile web add dsh-cost-meter
|
|
196
196
|
```
|
|
197
197
|
|
|
198
|
-
**PowerShell 一键脚本**(复制整行粘贴回车;自动补齐 pnpm、自动探测 git,无需克隆仓库;安装链**固定到发布 tag `v1.5.
|
|
198
|
+
**PowerShell 一键脚本**(复制整行粘贴回车;自动补齐 pnpm、自动探测 git,无需克隆仓库;安装链**固定到发布 tag `v1.5.18`**,建议先下载审阅再运行):
|
|
199
199
|
|
|
200
200
|
```powershell
|
|
201
|
-
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.18/install.ps1 | iex
|
|
202
202
|
```
|
|
203
203
|
|
|
204
204
|
**或直接命令行**(机器上需已有 pnpm 与 git;同样固定到 tag):
|
|
205
205
|
|
|
206
206
|
```sh
|
|
207
|
-
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.18
|
|
208
208
|
```
|
|
209
209
|
|
|
210
210
|
没有 git 时可用 GitHub tag 打包直链:
|
|
211
211
|
|
|
212
212
|
```sh
|
|
213
|
-
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.18.tar.gz
|
|
214
214
|
```
|
|
215
215
|
|
|
216
216
|
安装后**重启** `dsh web`(插件行、Typert 清单与客户端 bundle 均在启动时扫描):
|
package/lib/client.js
CHANGED
|
@@ -110,13 +110,20 @@ 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;
|
|
114
|
-
'.cm-peak-alert-peak{
|
|
115
|
-
'.cm-peak-alert-
|
|
116
|
-
'.cm-peak-alert-
|
|
117
|
-
'.cm-peak-alert-
|
|
118
|
-
'.cm-peak-alert-
|
|
119
|
-
'
|
|
113
|
+
'.cm-peak-alert{position:fixed;z-index:9999;width:340px;max-width:calc(100vw - 32px);padding:16px;border-radius:14px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);box-shadow:0 14px 36px rgba(0,0,0,.22);display:flex;flex-direction:column;gap:8px;font-size:12px;animation:cm-peak-alert-in .22s cubic-bezier(.2,.8,.2,1)}',
|
|
114
|
+
'.cm-peak-alert.cm-peak-alert-corner{right:20px;bottom:20px}',
|
|
115
|
+
'.cm-peak-alert.cm-peak-alert-center{top:50%;left:50%;transform:translate(-50%,-50%);animation-name:cm-peak-alert-in-center}',
|
|
116
|
+
'.cm-peak-alert-peak{border-top:3px solid var(--dsw-alias-state-warn-primary)}',
|
|
117
|
+
'.cm-peak-alert-offpeak{border-top:3px solid var(--dsw-alias-state-info-primary,#3b82f6)}',
|
|
118
|
+
'.cm-peak-alert-badge{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:600;letter-spacing:.4px;text-transform:uppercase}',
|
|
119
|
+
'.cm-peak-alert-badge::before{content:"";width:8px;height:8px;border-radius:50%;background:currentColor;box-shadow:0 0 0 4px color-mix(in srgb,currentColor 16%,transparent)}',
|
|
120
|
+
'.cm-peak-alert-peak .cm-peak-alert-badge{color:var(--dsw-alias-state-warn-primary)}',
|
|
121
|
+
'.cm-peak-alert-offpeak .cm-peak-alert-badge{color:var(--dsw-alias-state-info-primary,#3b82f6)}',
|
|
122
|
+
'.cm-peak-alert-title{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary)}',
|
|
123
|
+
'.cm-peak-alert-body{color:var(--dsw-alias-label-secondary);line-height:1.55}',
|
|
124
|
+
'.cm-peak-alert-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:2px}',
|
|
125
|
+
'@keyframes cm-peak-alert-in{from{opacity:0;transform:translateY(10px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}',
|
|
126
|
+
'@keyframes cm-peak-alert-in-center{from{opacity:0;transform:translate(-50%,calc(-50% + 10px))}to{opacity:1;transform:translate(-50%,-50%)}}',
|
|
120
127
|
'.cm-budget-head{display:flex;align-items:center;justify-content:space-between;gap:8px}',
|
|
121
128
|
'.cm-budget-bar{height:8px;border-radius:999px;background:var(--dsw-alias-bg-layer-3);overflow:hidden}',
|
|
122
129
|
'.cm-budget-fill{height:100%;border-radius:999px;background:var(--dsw-alias-state-business-primary);transition:width .3s ease}',
|
|
@@ -441,6 +448,13 @@ window.__ModuleLoader__.load({
|
|
|
441
448
|
peakAlertPhasePeak: '峰时',
|
|
442
449
|
peakAlertPhaseOffPeak: '谷时',
|
|
443
450
|
peakAlertBtn: '知道了',
|
|
451
|
+
peakAlertBadgePeak: '峰价提醒',
|
|
452
|
+
peakAlertBadgeOffPeak: '谷价提醒',
|
|
453
|
+
peakAlertPositionLabel: '弹窗位置',
|
|
454
|
+
peakAlertPositionCorner: '右下角',
|
|
455
|
+
peakAlertPositionCenter: '屏幕中心',
|
|
456
|
+
peakAlertWebNotifyLabel: '同步发送系统通知',
|
|
457
|
+
peakAlertWebNotifyHint: '开关后请在浏览器地址栏允许通知权限,才能在页面最小化/切走时收到提醒。',
|
|
444
458
|
peakPanelTitle: '峰谷计价与提示',
|
|
445
459
|
peakNoticeHiddenHint: '提示已隐藏:需启用峰谷计价并开启「峰时高价时段显著提示」。',
|
|
446
460
|
groupGeneral: '常规',
|
|
@@ -754,6 +768,13 @@ window.__ModuleLoader__.load({
|
|
|
754
768
|
peakAlertPhasePeak: 'peak',
|
|
755
769
|
peakAlertPhaseOffPeak: 'off-peak',
|
|
756
770
|
peakAlertBtn: 'Got it',
|
|
771
|
+
peakAlertBadgePeak: 'Peak alert',
|
|
772
|
+
peakAlertBadgeOffPeak: 'Off-peak alert',
|
|
773
|
+
peakAlertPositionLabel: 'Popup position',
|
|
774
|
+
peakAlertPositionCorner: 'Bottom-right',
|
|
775
|
+
peakAlertPositionCenter: 'Screen center',
|
|
776
|
+
peakAlertWebNotifyLabel: 'Also send a system notification',
|
|
777
|
+
peakAlertWebNotifyHint: 'When enabled, allow notification permission in the browser address bar so you get alerted even when the page is minimized or backgrounded.',
|
|
757
778
|
peakPanelTitle: 'Peak/off-peak pricing & notice',
|
|
758
779
|
peakNoticeHiddenHint: 'Notice hidden: enable peak/off-peak pricing and the “prominent notice” toggle.',
|
|
759
780
|
groupGeneral: 'General',
|
|
@@ -1058,6 +1079,8 @@ window.__ModuleLoader__.load({
|
|
|
1058
1079
|
peakAlertEnabled: v.peakAlertEnabled !== false,
|
|
1059
1080
|
peakAlertAhead: Number.isFinite(v.peakAlertAhead) && v.peakAlertAhead >= 1 && v.peakAlertAhead <= 30 ? v.peakAlertAhead : 2,
|
|
1060
1081
|
peakAlertTarget: v.peakAlertTarget === 'peak' || v.peakAlertTarget === 'offpeak' ? v.peakAlertTarget : 'both',
|
|
1082
|
+
peakAlertPosition: v.peakAlertPosition === 'center' ? 'center' : 'corner',
|
|
1083
|
+
peakAlertWebNotify: v.peakAlertWebNotify === true,
|
|
1061
1084
|
peakStyle: v.peakStyle === 'classic' ? 'classic' : 'compact',
|
|
1062
1085
|
priceMatch: v.priceMatch === 'exact' ? 'exact' : 'auto',
|
|
1063
1086
|
priceOverrides: (() => {
|
|
@@ -1989,19 +2012,42 @@ window.__ModuleLoader__.load({
|
|
|
1989
2012
|
}
|
|
1990
2013
|
|
|
1991
2014
|
/**
|
|
1992
|
-
* 峰/谷切换前弹窗提醒:距下次相位切换不足配置提前量(默认 2 分钟)
|
|
1993
|
-
*
|
|
2015
|
+
* 峰/谷切换前弹窗提醒:距下次相位切换不足配置提前量(默认 2 分钟)时弹浮层,
|
|
2016
|
+
* 位置可选屏幕右下角 / 屏幕中心;提醒类型按配置过滤(进入峰/进入谷/峰和谷);
|
|
1994
2017
|
* 同一切换点只弹一次(手动关闭即记点),切换完成后浮层自然消失。
|
|
2018
|
+
* 若开启 Web 通知且有授权,还会在同一切换点向系统发送一次浏览器通知。
|
|
1995
2019
|
*/
|
|
1996
2020
|
function PeakAlert(props) {
|
|
1997
2021
|
const costStore = props.useCost ? props.useCost(s => s) : undefined
|
|
1998
2022
|
const config = costStore?.state?.config
|
|
1999
2023
|
const [now, setNow] = useState(Date.now())
|
|
2000
2024
|
const [dismissedAt, setDismissedAt] = useState(null)
|
|
2025
|
+
const notifiedAtRef = useRef(null)
|
|
2001
2026
|
useEffect(() => {
|
|
2002
2027
|
const timer = window.setInterval(() => setNow(Date.now()), 10000)
|
|
2003
2028
|
return () => window.clearInterval(timer)
|
|
2004
2029
|
}, [])
|
|
2030
|
+
useEffect(() => {
|
|
2031
|
+
// Web 通知:每次 tick 自包含重算切换点,同一切换点只发一次。
|
|
2032
|
+
if (!config || config.peakAlertEnabled !== true || config.peakEnabled !== true) return
|
|
2033
|
+
if (config.peakAlertWebNotify !== true || !window.Notification || Notification.permission !== 'granted') return
|
|
2034
|
+
if (notifiedAtRef.current === now) return
|
|
2035
|
+
const wv = peakPhaseAt(now, Array.isArray(config.peakWindows) ? config.peakWindows : [])
|
|
2036
|
+
if (wv === null) return
|
|
2037
|
+
const tgt = config.peakAlertTarget === 'peak' || config.peakAlertTarget === 'offpeak' ? config.peakAlertTarget : 'both'
|
|
2038
|
+
const intoPeak = wv.nextIntoPeak === true
|
|
2039
|
+
if (tgt !== 'both' && tgt !== (intoPeak ? 'peak' : 'offpeak')) return
|
|
2040
|
+
const mins = Number(config.peakAlertAhead)
|
|
2041
|
+
const aheadMs = (Number.isFinite(mins) && mins >= 1 ? mins : 2) * 60000
|
|
2042
|
+
if (now < wv.nextAtMs - aheadMs || now >= wv.nextAtMs) return
|
|
2043
|
+
const t = makeT(resolveLocale(config.locale))
|
|
2044
|
+
notifiedAtRef.current = now
|
|
2045
|
+
try {
|
|
2046
|
+
new Notification(
|
|
2047
|
+
t(intoPeak ? 'peakAlertTitlePeak' : 'peakAlertTitleOffPeak'),
|
|
2048
|
+
{ body: t('peakAlertBody', { time: countdownText(wv, now, t), phase: t(intoPeak ? 'peakAlertPhasePeak' : 'peakAlertPhaseOffPeak') }) })
|
|
2049
|
+
} catch (_) { /* 通知被系统拒绝时静默 */ }
|
|
2050
|
+
}, [now]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
2005
2051
|
if (!config || config.peakAlertEnabled !== true || config.peakEnabled !== true) return null
|
|
2006
2052
|
const effectiveAtMs = Date.parse(config.peakEffectiveAt || '')
|
|
2007
2053
|
if (Number.isFinite(effectiveAtMs) && now < effectiveAtMs) return null
|
|
@@ -2015,14 +2061,17 @@ window.__ModuleLoader__.load({
|
|
|
2015
2061
|
if (dismissedAt === view.nextAtMs) return null
|
|
2016
2062
|
const t = makeT(resolveLocale(config.locale))
|
|
2017
2063
|
const intoPeak = view.nextIntoPeak === true
|
|
2018
|
-
|
|
2064
|
+
const position = config.peakAlertPosition === 'center' ? 'cm-peak-alert-center' : 'cm-peak-alert-corner'
|
|
2065
|
+
const actionBtn = (className, label, fn) => el('button', { className, onClick: fn }, label)
|
|
2066
|
+
return el('div', { className: 'cm-peak-alert ' + position + ' ' + (intoPeak ? 'cm-peak-alert-peak' : 'cm-peak-alert-offpeak'), role: 'alert' },
|
|
2067
|
+
el('div', { className: 'cm-peak-alert-badge' }, t(intoPeak ? 'peakAlertBadgePeak' : 'peakAlertBadgeOffPeak')),
|
|
2019
2068
|
el('div', { className: 'cm-peak-alert-title' }, t(intoPeak ? 'peakAlertTitlePeak' : 'peakAlertTitleOffPeak')),
|
|
2020
2069
|
el('div', { className: 'cm-peak-alert-body' }, t('peakAlertBody', {
|
|
2021
2070
|
time: countdownText(view, now, t),
|
|
2022
2071
|
phase: t(intoPeak ? 'peakAlertPhasePeak' : 'peakAlertPhaseOffPeak'),
|
|
2023
2072
|
})),
|
|
2024
2073
|
el('div', { className: 'cm-peak-alert-actions' },
|
|
2025
|
-
|
|
2074
|
+
actionBtn('cm-btn', t('peakAlertBtn'), () => setDismissedAt(view.nextAtMs))))
|
|
2026
2075
|
}
|
|
2027
2076
|
|
|
2028
2077
|
/** 展开态简洁样式:单行紧凑时段条——细轨道(左橙右蓝,非当前段淡化)+ 标记线 + 右侧倒计时文本。 */
|
|
@@ -2818,6 +2867,32 @@ window.__ModuleLoader__.load({
|
|
|
2818
2867
|
el('option', { value: 'both' }, t('peakAlertTargetBoth')),
|
|
2819
2868
|
el('option', { value: 'peak' }, t('peakAlertTargetPeak')),
|
|
2820
2869
|
el('option', { value: 'offpeak' }, t('peakAlertTargetOffPeak'))))),
|
|
2870
|
+
el('div', { className: 'cm-grid' },
|
|
2871
|
+
el('div', { className: 'cm-field' },
|
|
2872
|
+
el('label', null, t('peakAlertPositionLabel')),
|
|
2873
|
+
el('select', {
|
|
2874
|
+
className: 'cm-input',
|
|
2875
|
+
value: draft?.peakAlertPosition === 'center' ? 'center' : 'corner',
|
|
2876
|
+
onChange: event => setField('peakAlertPosition', event.target.value),
|
|
2877
|
+
},
|
|
2878
|
+
el('option', { value: 'corner' }, t('peakAlertPositionCorner')),
|
|
2879
|
+
el('option', { value: 'center' }, t('peakAlertPositionCenter')))),
|
|
2880
|
+
el('div', { className: 'cm-field' },
|
|
2881
|
+
el('label', { className: 'cm-check' },
|
|
2882
|
+
el('input', {
|
|
2883
|
+
type: 'checkbox',
|
|
2884
|
+
checked: draft?.peakAlertWebNotify === true,
|
|
2885
|
+
onChange: event => {
|
|
2886
|
+
setField('peakAlertWebNotify', event.target.checked)
|
|
2887
|
+
// 开启需用户手势申请浏览器通知权限(地址栏授权)。
|
|
2888
|
+
if (event.target.checked && window.Notification && Notification.permission === 'default') {
|
|
2889
|
+
// 旧 Safari 的 requestPermission 不返回 Promise,包一层防抛错。
|
|
2890
|
+
Promise.resolve(Notification.requestPermission()).catch(() => {})
|
|
2891
|
+
}
|
|
2892
|
+
},
|
|
2893
|
+
}),
|
|
2894
|
+
el('span', null, t('peakAlertWebNotifyLabel'))),
|
|
2895
|
+
el('p', { className: 'cm-hint' }, t('peakAlertWebNotifyHint')))),
|
|
2821
2896
|
el('div', { className: 'cm-peak-preview' },
|
|
2822
2897
|
draft?.peakEnabled !== false && draft?.peakNotice !== false
|
|
2823
2898
|
? peakNoticeEl(state, previewConfig, t)
|
package/lib/store.js
CHANGED
|
@@ -41,6 +41,8 @@ export function defaultConfig() {
|
|
|
41
41
|
peakAlertEnabled: true, // 峰/谷切换前弹窗提醒(全局浮层)
|
|
42
42
|
peakAlertAhead: 2, // 提前提醒量(分钟,1-30)
|
|
43
43
|
peakAlertTarget: 'both', // 提醒类型:peak(进入峰) | offpeak(进入谷) | both(峰和谷)
|
|
44
|
+
peakAlertPosition: 'corner', // 弹窗位置:corner(右下角) | center(屏幕中心)
|
|
45
|
+
peakAlertWebNotify: false, // 弹窗时额外发浏览器(系统)通知,需用户在地址栏授权
|
|
44
46
|
showSessionId: false, // 会话列表中附显会话 ID(默认只显示标题,需要时开启)
|
|
45
47
|
legacyAutoImportedAt: 0, // 安装前历史自动导入标记(issue #27):完成时刻 ms;0 = 尚未跑过
|
|
46
48
|
peakStyle: 'compact', // 峰谷时段条样式:compact(简洁单行/竖向同构) | classic(经典分段/胶囊芯片)
|
|
@@ -227,6 +229,8 @@ const VALIDATION_MESSAGES = {
|
|
|
227
229
|
peakAlertEnabled: 'peakAlertEnabled 必须是布尔值',
|
|
228
230
|
peakAlertAhead: 'peakAlertAhead 必须是 1-30 的整数',
|
|
229
231
|
peakAlertTarget: 'peakAlertTarget 必须是 peak / offpeak / both',
|
|
232
|
+
peakAlertPosition: 'peakAlertPosition 必须是 corner / center',
|
|
233
|
+
peakAlertWebNotify: 'peakAlertWebNotify 必须是布尔值',
|
|
230
234
|
showSessionId: 'showSessionId 必须是布尔值',
|
|
231
235
|
peakStyle: 'peakStyle 必须是 compact / classic',
|
|
232
236
|
priceMatch: 'priceMatch 必须是 auto / exact',
|
|
@@ -291,6 +295,8 @@ const VALIDATION_MESSAGES = {
|
|
|
291
295
|
peakAlertEnabled: 'peakAlertEnabled must be a boolean',
|
|
292
296
|
peakAlertAhead: 'peakAlertAhead must be an integer between 1 and 30',
|
|
293
297
|
peakAlertTarget: 'peakAlertTarget must be peak / offpeak / both',
|
|
298
|
+
peakAlertPosition: 'peakAlertPosition must be corner / center',
|
|
299
|
+
peakAlertWebNotify: 'peakAlertWebNotify must be a boolean',
|
|
294
300
|
showSessionId: 'showSessionId must be a boolean',
|
|
295
301
|
peakStyle: 'peakStyle must be compact / classic',
|
|
296
302
|
priceMatch: 'priceMatch must be auto / exact',
|
|
@@ -419,6 +425,8 @@ export function applyConfigPatch(current, patch) {
|
|
|
419
425
|
const alertAhead = Number(candidate.peakAlertAhead)
|
|
420
426
|
if (!Number.isInteger(alertAhead) || alertAhead < 1 || alertAhead > 30) errors.push(vmsg(locale, 'peakAlertAhead'))
|
|
421
427
|
if (!['peak', 'offpeak', 'both'].includes(candidate.peakAlertTarget)) errors.push(vmsg(locale, 'peakAlertTarget'))
|
|
428
|
+
if (!['corner', 'center'].includes(candidate.peakAlertPosition)) errors.push(vmsg(locale, 'peakAlertPosition'))
|
|
429
|
+
if (typeof candidate.peakAlertWebNotify !== 'boolean') errors.push(vmsg(locale, 'peakAlertWebNotify'))
|
|
422
430
|
if (candidate.showSessionId !== undefined && typeof candidate.showSessionId !== 'boolean') errors.push(vmsg(locale, 'showSessionId'))
|
|
423
431
|
if (candidate.peakStyle !== 'compact' && candidate.peakStyle !== 'classic') errors.push(vmsg(locale, 'peakStyle'))
|
|
424
432
|
if (candidate.priceMatch !== 'auto' && candidate.priceMatch !== 'exact') errors.push(vmsg(locale, 'priceMatch'))
|
|
@@ -608,6 +616,8 @@ export function sanitizeConfig(raw) {
|
|
|
608
616
|
const alertAhead = Number(out.peakAlertAhead)
|
|
609
617
|
out.peakAlertAhead = Number.isInteger(alertAhead) && alertAhead >= 1 && alertAhead <= 30 ? alertAhead : 2
|
|
610
618
|
out.peakAlertTarget = oneOf(out.peakAlertTarget, ['peak', 'offpeak', 'both'], 'both')
|
|
619
|
+
out.peakAlertPosition = oneOf(out.peakAlertPosition, ['corner', 'center'], 'corner')
|
|
620
|
+
out.peakAlertWebNotify = out.peakAlertWebNotify === true
|
|
611
621
|
// 安装前历史自动导入标记:有限正数保留,否则归零(下次启动重试)。
|
|
612
622
|
out.legacyAutoImportedAt = isNum(Number(out.legacyAutoImportedAt)) && Number(out.legacyAutoImportedAt) > 0 ? Number(out.legacyAutoImportedAt) : 0
|
|
613
623
|
if (!isNum(out.exchangeRate) || out.exchangeRate <= 0) out.exchangeRate = base.exchangeRate
|
package/lib/typert.host.js
CHANGED
|
@@ -107,6 +107,8 @@ const configSchema = z.object({
|
|
|
107
107
|
peakAlertEnabled: z.boolean().optional(),
|
|
108
108
|
peakAlertAhead: num.optional(),
|
|
109
109
|
peakAlertTarget: z.enum(['peak', 'offpeak', 'both']).optional(),
|
|
110
|
+
peakAlertPosition: z.enum(['corner', 'center']).optional(),
|
|
111
|
+
peakAlertWebNotify: z.boolean().optional(),
|
|
110
112
|
showSessionId: z.boolean().optional(),
|
|
111
113
|
// 安装前历史自动导入完成时刻(issue #27,内部标记;0/缺席 = 尚未跑过)。
|
|
112
114
|
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.18",
|
|
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",
|