deepseek-harness-wallet 0.2.2 → 0.2.3
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/CHANGELOG.md +8 -0
- package/README.md +4 -3
- package/index.js +43 -13
- package/lib/client.js +60 -35
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.2.3 - 2026-08-21
|
|
6
|
+
|
|
7
|
+
- 修复 Provider 分桶实际未参与计费的问题;已勾选的包装路由现在真正进入官方 token/花费桶,并记录新观察到的 Provider。 / Fixed provider aliases not reaching the billing path: opted-in wrapper routes now enter the official token/cost bucket, and newly observed providers are recorded.
|
|
8
|
+
- 修复首次添加账户时凭证同步失败仍显示为当前账户的问题;失败会回滚激活状态。 / Rolled back first-account activation when the host refuses credential synchronization.
|
|
9
|
+
- 修复删除账户后账户专属阈值未写回钱包存储的问题。 / Persisted removal of account-specific thresholds when an account is deleted.
|
|
10
|
+
- 余额错误改为安全枚举,避免把上游错误原文暴露到浏览器;美元/多余额记录的明细选择也统一。 / Replaced raw balance errors with safe enums and aligned multi-currency balance selection.
|
|
11
|
+
- 统一提醒设置的本地存储格式,并兼容旧的毫秒字段;API Key 输入框默认隐藏。 / Unified reminder storage with migration from the legacy millisecond field and made API-key inputs password fields.
|
|
12
|
+
|
|
5
13
|
## 0.2.2 - 2026-08-20
|
|
6
14
|
|
|
7
15
|
- 新增 24h 峰谷计费分时时钟:在侧边栏左下角常驻展示当前时段(高峰/低谷半价)、剩余倒计时、实时余额与本场/本约花费;折叠导轨模式下自动收拢为 42px 紧凑环形钟。 / Added a 24h peak/off-peak ring clock in the sidebar footer: live pricing window (peak / 50% off-peak), switch countdown, live balance and session spend; collapses into a 42px compact circle in rail mode.
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/deepseek-harness-wallet)
|
|
4
4
|
[](https://github.com/feibi-mochi/deepseek-harness-control-center/releases)
|
|
5
5
|
[](https://github.com/feibi-mochi/deepseek-harness-control-center/actions/workflows/validate.yml)
|
|
6
|
-
[](https://github.com/deepseek-ai/DeepSeek-Harness)
|
|
7
7
|
[](./LICENSE)
|
|
8
8
|
|
|
9
9
|
**DeepSeek Harness monitoring, alerts, recharge, and session control center.**
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
- **Official DeepSeek** — live balance (60s global refresh with fast boot retries), current-session cost locked to the price active for each usage event (including the 2026-08-17 peak/off-peak rollout), and token breakdown.
|
|
26
26
|
- **24h peak/off-peak ring clock** — resident sidebar footer widget indicating real-time pricing windows (peak vs. 50% discount off-peak), countdown to next switch, and optional desktop switch notifications.
|
|
27
27
|
- **Third-party total** — current-session tokens (input / cache read / output). No balance guessing, no cost math, zero configuration.
|
|
28
|
-
- **
|
|
28
|
+
- **Provider classification** — observed wrapper routes appear in the settings page; opted-in routes join the official token/cost bucket and are priced with the official table.
|
|
29
|
+
- **Click the chip** to open the detail panel: correctly formatted per-currency balances, cost and token splits, a freely editable low-balance threshold for the active account and currency (two decimals, persisted per account; alerts never mix currencies), manual refresh, and a jump to the official recharge page (first click shows the domain for confirmation — anti-phishing).
|
|
29
30
|
- **Move, dock, and scale** — drag the chip freely, preview nearby snap targets, use compact horizontal or vertical layouts, adjust its scale from the control panel, and show official or third-party data independently. The choices are remembered locally.
|
|
30
31
|
- **Floating window mode** — detach the detail panel into a draggable window with a remembered position, or minimize it directly to a freely movable dot; the dot turns red below the threshold.
|
|
31
32
|
- **Completion reminders** — optionally notify when a conversation finishes, with persistent or timed modes, queueing and deduplication for simultaneous completions, cross-tab coordination, and an in-page fallback when system notifications are unavailable.
|
|
@@ -127,7 +128,7 @@ For buildable DSH hosts, the npm package and repository include a versioned [Age
|
|
|
127
128
|
|
|
128
129
|
| Item | Behavior |
|
|
129
130
|
| --- | --- |
|
|
130
|
-
| Token accounting | Listens to the `llm/stream` event and buckets per provider
|
|
131
|
+
| Token accounting | Listens to the `llm/stream` event and buckets per session and provider: `deepseek-official` plus explicitly opted-in wrapper routes use the official bucket; other providers stay third-party; each usage event also locks its contemporaneous official price, so multiple sessions and pricing windows never mix. |
|
|
131
132
|
| Balance | The key from the credentials seam (or the active account's key) never leaves this machine except as the `Authorization` header of the official `/user/balance` request. |
|
|
132
133
|
| Accounts | Keys live in `$DSH_HOME/storages/accounts.json` (plaintext, matching the harness's own credential storage); the UI only ever shows masked keys, and switching writes the chosen key into the credentials seam for LLM billing. |
|
|
133
134
|
| Session log | The plugin writes no events; its data lives in `$DSH_HOME/storages/wallet.json`. |
|
package/index.js
CHANGED
|
@@ -386,16 +386,18 @@ export function removeAccount(id) {
|
|
|
386
386
|
const index = accounts.accounts.findIndex((account) => account.id === id)
|
|
387
387
|
if (index < 0) return { ok: false, error: 'account not found' }
|
|
388
388
|
accounts.accounts.splice(index, 1)
|
|
389
|
-
// Drop the account's own threshold line along with it.
|
|
389
|
+
// Drop the account's own threshold line along with it. The caller persists
|
|
390
|
+
// wallet.json separately from accounts.json when this flag is true.
|
|
391
|
+
let thresholdRemoved = false
|
|
390
392
|
if (store.accountThresholds && Object.hasOwn(store.accountThresholds, id)) {
|
|
391
393
|
delete store.accountThresholds[id]
|
|
392
|
-
|
|
394
|
+
thresholdRemoved = true
|
|
393
395
|
}
|
|
394
396
|
// Deliberately NOT unsetting the credentials seam: the key currently in
|
|
395
397
|
// .credentials.yaml keeps working for LLM billing; the UI just reports
|
|
396
398
|
// "no active account" and balance falls back to the seam key.
|
|
397
399
|
if (accounts.activeId === id) accounts.activeId = null
|
|
398
|
-
return { ok: true }
|
|
400
|
+
return { ok: true, thresholdRemoved }
|
|
399
401
|
}
|
|
400
402
|
|
|
401
403
|
export function accountListView() {
|
|
@@ -485,12 +487,18 @@ function recordUsage(options, usage, atMs = Date.now()) {
|
|
|
485
487
|
if (typeof sessionId !== 'string' || sessionId === '' || typeof provider !== 'string' || provider === '') return
|
|
486
488
|
if (typeof model !== 'string' || model === '__proto__' || model === 'prototype' || model === 'constructor') return
|
|
487
489
|
if (sessionId === '__proto__' || sessionId === 'prototype' || sessionId === 'constructor') return
|
|
490
|
+
// Remember every non-builtin route observed by the stream tap. The settings
|
|
491
|
+
// page can then promote wrapper routes into the official billing bucket.
|
|
492
|
+
if (provider !== OFFICIAL_PROVIDER && !store.knownProviders.includes(provider)) {
|
|
493
|
+
store.knownProviders = normalizeProviderList([...store.knownProviders, provider])
|
|
494
|
+
}
|
|
488
495
|
if (!Object.hasOwn(store.sessions, sessionId)) {
|
|
489
496
|
store.sessions[sessionId] = { official: emptyBucket(true), third: emptyBucket(false) }
|
|
490
497
|
}
|
|
491
498
|
const session = store.sessions[sessionId]
|
|
492
|
-
const
|
|
493
|
-
|
|
499
|
+
const official = isOfficialProvider(provider, store.officialProviders)
|
|
500
|
+
const bucket = official ? session.official : session.third
|
|
501
|
+
if (official) addOfficialUsage(bucket, model, usage, atMs)
|
|
494
502
|
else {
|
|
495
503
|
if (!Object.hasOwn(bucket.models, model)) bucket.models[model] = emptyCounters()
|
|
496
504
|
addUsage(bucket.models[model], usage)
|
|
@@ -503,13 +511,13 @@ let balanceRefresh = null
|
|
|
503
511
|
async function performBalanceRefresh(ctx) {
|
|
504
512
|
const credentials = ctx.get('credentials')
|
|
505
513
|
if (credentials === undefined) {
|
|
506
|
-
balance = { fetchedAt: Date.now(), available: false, balances: [], error: 'no
|
|
514
|
+
balance = { fetchedAt: Date.now(), available: false, balances: [], error: 'no-credentials' }
|
|
507
515
|
return
|
|
508
516
|
}
|
|
509
517
|
try {
|
|
510
518
|
const key = await resolveBalanceKey(ctx)
|
|
511
519
|
if (key === undefined || key === '') {
|
|
512
|
-
balance = { fetchedAt: Date.now(), available: false, balances: [], error: 'no
|
|
520
|
+
balance = { fetchedAt: Date.now(), available: false, balances: [], error: 'no-api-key' }
|
|
513
521
|
return
|
|
514
522
|
}
|
|
515
523
|
const controller = new AbortController()
|
|
@@ -520,23 +528,36 @@ async function performBalanceRefresh(ctx) {
|
|
|
520
528
|
headers: { authorization: 'Bearer ' + key },
|
|
521
529
|
signal: controller.signal,
|
|
522
530
|
})
|
|
523
|
-
if (!response.ok)
|
|
531
|
+
if (!response.ok) {
|
|
532
|
+
const code = response.status === 401 || response.status === 403
|
|
533
|
+
? 'unauthorized'
|
|
534
|
+
: response.status === 429 ? 'rate-limited' : 'upstream-unavailable'
|
|
535
|
+
const error = new Error(code)
|
|
536
|
+
error.code = code
|
|
537
|
+
throw error
|
|
538
|
+
}
|
|
524
539
|
const data = await response.json()
|
|
540
|
+
const available = data !== null && typeof data === 'object' && data.is_available === true
|
|
525
541
|
balance = {
|
|
526
542
|
fetchedAt: Date.now(),
|
|
527
|
-
available
|
|
528
|
-
balances: Array.isArray(data.balance_infos) ? data.balance_infos : [],
|
|
529
|
-
error: null,
|
|
543
|
+
available,
|
|
544
|
+
balances: available && Array.isArray(data.balance_infos) ? data.balance_infos : [],
|
|
545
|
+
error: available ? null : 'balance-unavailable',
|
|
530
546
|
}
|
|
531
547
|
} finally {
|
|
532
548
|
clearTimeout(timer)
|
|
533
549
|
}
|
|
534
550
|
} catch (error) {
|
|
551
|
+
const code = error && typeof error === 'object' && typeof error.code === 'string'
|
|
552
|
+
? error.code
|
|
553
|
+
: error && typeof error === 'object' && error.name === 'AbortError'
|
|
554
|
+
? 'timeout'
|
|
555
|
+
: error instanceof SyntaxError ? 'invalid-response' : 'upstream-unavailable'
|
|
535
556
|
balance = {
|
|
536
557
|
fetchedAt: Date.now(),
|
|
537
558
|
available: false,
|
|
538
559
|
balances: [],
|
|
539
|
-
error:
|
|
560
|
+
error: code,
|
|
540
561
|
}
|
|
541
562
|
}
|
|
542
563
|
}
|
|
@@ -836,7 +857,8 @@ export function apply(ctx, config) {
|
|
|
836
857
|
if (body === null || typeof body.name !== 'string' || typeof body.apiKey !== 'string') {
|
|
837
858
|
return json(res, 400, { ok: false, error: 'name and apiKey are required' })
|
|
838
859
|
}
|
|
839
|
-
const
|
|
860
|
+
const previousActiveId = accounts.activeId
|
|
861
|
+
const needSync = previousActiveId === null
|
|
840
862
|
const result = addAccount(body.name, body.apiKey)
|
|
841
863
|
if (!result.ok) return json(res, 400, { ok: false, error: result.error })
|
|
842
864
|
scheduleAccountsSave(ctx.logger)
|
|
@@ -844,6 +866,13 @@ export function apply(ctx, config) {
|
|
|
844
866
|
if (needSync) {
|
|
845
867
|
// First account: activate it so the LLM seam follows the new key.
|
|
846
868
|
sync = await activateAccount(ctx, result.account.id)
|
|
869
|
+
// addAccount marks the first row active optimistically. If the host
|
|
870
|
+
// refuses the credential write, roll it back so UI/balance state does
|
|
871
|
+
// not claim a billing account that the LLM route is not using.
|
|
872
|
+
if (!sync.ok && accounts.activeId === result.account.id) {
|
|
873
|
+
accounts.activeId = previousActiveId
|
|
874
|
+
scheduleAccountsSave(ctx.logger)
|
|
875
|
+
}
|
|
847
876
|
}
|
|
848
877
|
return json(res, 200, {
|
|
849
878
|
ok: true,
|
|
@@ -884,6 +913,7 @@ export function apply(ctx, config) {
|
|
|
884
913
|
const result = removeAccount(body.id)
|
|
885
914
|
if (!result.ok) return json(res, 400, { ok: false, error: result.error })
|
|
886
915
|
scheduleAccountsSave(ctx.logger)
|
|
916
|
+
if (result.thresholdRemoved) scheduleSave(ctx.logger)
|
|
887
917
|
return json(res, 200, { ok: true, ...accountListView() })
|
|
888
918
|
},
|
|
889
919
|
})
|
package/lib/client.js
CHANGED
|
@@ -14,7 +14,7 @@ window.__ModuleLoader__.load({
|
|
|
14
14
|
|
|
15
15
|
var POLL_MS = 15000
|
|
16
16
|
// Keep in lockstep with package.json; a test enforces the sync.
|
|
17
|
-
var WALLET_VERSION = '0.2.
|
|
17
|
+
var WALLET_VERSION = '0.2.3'
|
|
18
18
|
var CONFIRM_KEY = 'dsh-wallet-recharge-confirmed'
|
|
19
19
|
var CHIP_LAYOUT_KEY = 'dshw-chip-layout-v4'
|
|
20
20
|
var PANEL_POS_KEY = 'dshw-panel-pos-v1'
|
|
@@ -599,6 +599,32 @@ window.__ModuleLoader__.load({
|
|
|
599
599
|
}
|
|
600
600
|
}
|
|
601
601
|
|
|
602
|
+
function selectBalanceInfo(balance) {
|
|
603
|
+
if (!balance || !Array.isArray(balance.balances) || balance.balances.length === 0) return null
|
|
604
|
+
var currency = typeof balance.currency === 'string' ? balance.currency.toUpperCase() : null
|
|
605
|
+
if (currency) {
|
|
606
|
+
var matching = balance.balances.find(function (info) {
|
|
607
|
+
return info && typeof info.currency === 'string' && info.currency.toUpperCase() === currency
|
|
608
|
+
})
|
|
609
|
+
if (matching) return matching
|
|
610
|
+
}
|
|
611
|
+
return balance.balances[0]
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function balanceErrorText(error) {
|
|
615
|
+
switch (error) {
|
|
616
|
+
case 'no-credentials': return '宿主凭证服务不可用'
|
|
617
|
+
case 'no-api-key': return '未配置 DeepSeek API Key'
|
|
618
|
+
case 'unauthorized': return 'API Key 无效或已过期'
|
|
619
|
+
case 'rate-limited': return '余额接口请求过于频繁'
|
|
620
|
+
case 'timeout': return '余额接口请求超时'
|
|
621
|
+
case 'invalid-response': return '余额接口返回无效数据'
|
|
622
|
+
case 'balance-unavailable': return '余额暂不可用'
|
|
623
|
+
case 'upstream-unavailable': return '余额接口暂时不可用'
|
|
624
|
+
default: return '余额暂不可用'
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
602
628
|
function computePanelPosition(chipRect, panelRect, viewportWidth, viewportHeight) {
|
|
603
629
|
var margin = 8
|
|
604
630
|
var gap = 6
|
|
@@ -664,6 +690,11 @@ window.__ModuleLoader__.load({
|
|
|
664
690
|
function normalizeNotifyConfig(value) {
|
|
665
691
|
var enabled = !value || value.enabled !== false
|
|
666
692
|
var timeout = value && Number.parseInt(value.timeout, 10)
|
|
693
|
+
// Migrate the brief settings-page schema that stored milliseconds under
|
|
694
|
+
// autoCloseMs. The canonical shared shape is { enabled, timeout }.
|
|
695
|
+
if ([0, 5, 10, 30, 60].indexOf(timeout) === -1 && value && Object.hasOwn(value, 'autoCloseMs')) {
|
|
696
|
+
timeout = value.autoCloseMs === null ? 0 : Math.round(Number(value.autoCloseMs) / 1000)
|
|
697
|
+
}
|
|
667
698
|
if ([0, 5, 10, 30, 60].indexOf(timeout) === -1) timeout = 10
|
|
668
699
|
return { enabled: enabled, timeout: timeout }
|
|
669
700
|
}
|
|
@@ -722,7 +753,7 @@ window.__ModuleLoader__.load({
|
|
|
722
753
|
}
|
|
723
754
|
var item = active.item
|
|
724
755
|
var remaining = queue.length
|
|
725
|
-
var body = item.title + (remaining > 0 ? '\n另有 ' + remaining + ' 个对话等待提醒' : '\n点击打开该对话')
|
|
756
|
+
var body = item.title + (remaining > 0 ? '\r\n另有 ' + remaining + ' 个对话等待提醒' : '\r\n点击打开该对话')
|
|
726
757
|
if (active.timer !== null) clearTimeout(active.timer)
|
|
727
758
|
if (active.notification) {
|
|
728
759
|
active.notification.onclose = null
|
|
@@ -1051,7 +1082,7 @@ window.__ModuleLoader__.load({
|
|
|
1051
1082
|
var [notifyEnabled, setNotifyEnabled] = React.useState(function () { return readNotifyConfig().enabled })
|
|
1052
1083
|
var [notifyTimeout, setNotifyTimeout] = React.useState(function () {
|
|
1053
1084
|
var cfg = readNotifyConfig()
|
|
1054
|
-
return cfg.
|
|
1085
|
+
return cfg.timeout === 0 ? 'keep' : String(cfg.timeout)
|
|
1055
1086
|
})
|
|
1056
1087
|
var [lowBlinkEnabled, setLowBlinkEnabled] = React.useState(function () {
|
|
1057
1088
|
try { return compatibility.storage.getItem(LOW_BLINK_KEY) !== 'false' } catch (e) { return true }
|
|
@@ -1120,13 +1151,13 @@ window.__ModuleLoader__.load({
|
|
|
1120
1151
|
}
|
|
1121
1152
|
|
|
1122
1153
|
function persistNotify(enabled, timeout) {
|
|
1123
|
-
var
|
|
1124
|
-
if (
|
|
1154
|
+
var timeoutSeconds = timeout === 'keep' ? 0 : Number.parseInt(timeout, 10)
|
|
1155
|
+
if ([0, 5, 10, 30, 60].indexOf(timeoutSeconds) === -1) timeoutSeconds = 10
|
|
1125
1156
|
try {
|
|
1126
|
-
compatibility.storage.setItem(NOTIFY_CONFIG_KEY, JSON.stringify({ enabled: enabled,
|
|
1157
|
+
compatibility.storage.setItem(NOTIFY_CONFIG_KEY, JSON.stringify({ enabled: enabled, timeout: timeoutSeconds }))
|
|
1127
1158
|
} catch (e) { /* ignore */ }
|
|
1128
1159
|
setNotifyEnabled(enabled)
|
|
1129
|
-
setNotifyTimeout(
|
|
1160
|
+
setNotifyTimeout(timeoutSeconds === 0 ? 'keep' : String(timeoutSeconds))
|
|
1130
1161
|
compatibility.dispatch(NOTIFY_CONFIG_EVENT)
|
|
1131
1162
|
}
|
|
1132
1163
|
|
|
@@ -1224,7 +1255,7 @@ window.__ModuleLoader__.load({
|
|
|
1224
1255
|
var lowBalance = snapshot && snapshot.lowBalance === true
|
|
1225
1256
|
var sessionCost = snapshot && snapshot.session && snapshot.session.official && snapshot.session.official.cost !== null && snapshot.session.official.cost !== undefined ? sessionCostText(snapshot.session.official.cost, snapshot && snapshot.balance ? snapshot.balance.currency : null) : '--'
|
|
1226
1257
|
var currencyCode = bal && typeof bal.currency === 'string' ? bal.currency : 'CNY'
|
|
1227
|
-
var primaryBalance =
|
|
1258
|
+
var primaryBalance = selectBalanceInfo(bal)
|
|
1228
1259
|
var toppedText = primaryBalance ? fmtCurrency(primaryBalance.topped_up_balance, primaryBalance.currency) : '--'
|
|
1229
1260
|
var grantedText = primaryBalance ? fmtCurrency(primaryBalance.granted_balance, primaryBalance.currency) : '--'
|
|
1230
1261
|
rows.push(React.createElement('div', { key: 'head', className: 'dshw_settingsHeader' },
|
|
@@ -1477,7 +1508,7 @@ window.__ModuleLoader__.load({
|
|
|
1477
1508
|
onChange: function (event) { setNameDraft(event.target.value) }
|
|
1478
1509
|
}),
|
|
1479
1510
|
React.createElement('input', {
|
|
1480
|
-
className: 'dshw_input',
|
|
1511
|
+
className: 'dshw_input', type: 'password',
|
|
1481
1512
|
placeholder: 'sk-...', 'aria-label': 'API Key',
|
|
1482
1513
|
value: keyDraft,
|
|
1483
1514
|
onInput: function (event) { setKeyDraft(event.target.value) },
|
|
@@ -1952,7 +1983,8 @@ window.__ModuleLoader__.load({
|
|
|
1952
1983
|
if (showOfficial && json.lowBalance) {
|
|
1953
1984
|
if (!notifiedRef.current) {
|
|
1954
1985
|
try {
|
|
1955
|
-
var
|
|
1986
|
+
var lowCurrency = json.balance && json.balance.currency ? json.balance.currency : 'CNY'
|
|
1987
|
+
var lowNotice = compatibility.notify('DeepSeek 余额不足', { body: '余额已低于提醒线 ' + fmtCurrency(json.threshold, lowCurrency), tag: 'dsh-wallet-low' })
|
|
1956
1988
|
if (lowNotice) notifiedRef.current = true
|
|
1957
1989
|
} catch (e) { /* ignore */ }
|
|
1958
1990
|
}
|
|
@@ -2554,7 +2586,7 @@ window.__ModuleLoader__.load({
|
|
|
2554
2586
|
|
|
2555
2587
|
function activateAccount(id, name) {
|
|
2556
2588
|
if (switchingId !== null) return
|
|
2557
|
-
if (!window.confirm('\u5207\u6362\u5230\u8d26\u6237\u300c' + name + '\u300d\uff1f\n\u5207\u6362\u540e\uff0c\u540e\u7eed LLM \u8bf7\u6c42\u5c06\u4f7f\u7528\u8be5\u8d26\u6237\u7684 Key \u8ba1\u8d39\u3002')) return
|
|
2589
|
+
if (!window.confirm('\u5207\u6362\u5230\u8d26\u6237\u300c' + name + '\u300d\uff1f\r\n\u5207\u6362\u540e\uff0c\u540e\u7eed LLM \u8bf7\u6c42\u5c06\u4f7f\u7528\u8be5\u8d26\u6237\u7684 Key \u8ba1\u8d39\u3002')) return
|
|
2558
2590
|
setSwitchingId(id)
|
|
2559
2591
|
setAccountNotice(null)
|
|
2560
2592
|
fetch('/api/wallet/accounts/activate', {
|
|
@@ -2585,7 +2617,7 @@ window.__ModuleLoader__.load({
|
|
|
2585
2617
|
}
|
|
2586
2618
|
|
|
2587
2619
|
function removeAccount(id, name) {
|
|
2588
|
-
if (!window.confirm('\u5220\u9664\u8d26\u6237\u300c' + name + '\u300d\uff1f\n\u4ec5\u5220\u9664\u672c\u63d2\u4ef6\u7684\u8d26\u6237\u8bb0\u5f55\uff0c\u4e0d\u5f71\u54cd .credentials.yaml \u4e2d\u5df2\u6709\u7684 Key\u3002')) return
|
|
2620
|
+
if (!window.confirm('\u5220\u9664\u8d26\u6237\u300c' + name + '\u300d\uff1f\r\n\u4ec5\u5220\u9664\u672c\u63d2\u4ef6\u7684\u8d26\u6237\u8bb0\u5f55\uff0c\u4e0d\u5f71\u54cd .credentials.yaml \u4e2d\u5df2\u6709\u7684 Key\u3002')) return
|
|
2589
2621
|
fetch('/api/wallet/accounts/remove', {
|
|
2590
2622
|
method: 'POST',
|
|
2591
2623
|
headers: { 'content-type': 'application/json' },
|
|
@@ -2868,7 +2900,7 @@ window.__ModuleLoader__.load({
|
|
|
2868
2900
|
type: 'number',
|
|
2869
2901
|
min: '0',
|
|
2870
2902
|
step: 'any',
|
|
2871
|
-
'aria-label': '
|
|
2903
|
+
'aria-label': '余额提醒阈值(' + (bal.currency || 'CNY') + ')',
|
|
2872
2904
|
value: thresholdDraft === null ? '' : thresholdDraft,
|
|
2873
2905
|
onChange: function (event) {
|
|
2874
2906
|
thresholdInitializedRef.current = 'editing'
|
|
@@ -3003,10 +3035,10 @@ window.__ModuleLoader__.load({
|
|
|
3003
3035
|
function balanceCard() {
|
|
3004
3036
|
if (!bal.available || !bal.balances || bal.balances.length === 0) {
|
|
3005
3037
|
return React.createElement('div', { className: 'dshw_balanceCard' },
|
|
3006
|
-
React.createElement('div', { className: 'dshw_muted' }, bal.error ? (
|
|
3038
|
+
React.createElement('div', { className: 'dshw_muted' }, bal.error ? balanceErrorText(bal.error) : '余额查询中…'))
|
|
3007
3039
|
}
|
|
3008
|
-
var first = bal
|
|
3009
|
-
var others = bal.balances.
|
|
3040
|
+
var first = selectBalanceInfo(bal)
|
|
3041
|
+
var others = bal.balances.filter(function (info) { return info !== first })
|
|
3010
3042
|
var subParts = [
|
|
3011
3043
|
React.createElement('span', { key: 'top' }, '\u5145\u503c ' + fmtCurrency(first.topped_up_balance, first.currency)),
|
|
3012
3044
|
React.createElement('span', { key: 'd1', className: 'dshw_balDot' }, '\u00b7'),
|
|
@@ -3017,25 +3049,16 @@ window.__ModuleLoader__.load({
|
|
|
3017
3049
|
subParts.push(React.createElement('span', { key: 'x' + i }, info.currency + ' ' + fmtCurrency(info.total_balance, info.currency)))
|
|
3018
3050
|
})
|
|
3019
3051
|
return React.createElement('div', {
|
|
3020
|
-
className: low ? 'dshw_balanceCard dshw_low' : 'dshw_balanceCard'
|
|
3021
|
-
style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '8px' }
|
|
3052
|
+
className: low ? 'dshw_balanceCard dshw_low' : 'dshw_balanceCard'
|
|
3022
3053
|
},
|
|
3023
|
-
React.createElement('div', {
|
|
3024
|
-
React.createElement('
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
subParts)),
|
|
3032
|
-
React.createElement('button', {
|
|
3033
|
-
type: 'button',
|
|
3034
|
-
className: 'dshw_btn dshw_btnPrimary',
|
|
3035
|
-
style: { height: '28px', padding: '0 10px', fontSize: '11px', flex: 'none', whiteSpace: 'nowrap' },
|
|
3036
|
-
onClick: onRechargeClick,
|
|
3037
|
-
'aria-label': '前往官方充值'
|
|
3038
|
-
}, '↗ 充值'))
|
|
3054
|
+
React.createElement('div', { className: 'dshw_balLine' },
|
|
3055
|
+
React.createElement('span', { className: 'dshw_muted' }, '\u4f59\u989d'),
|
|
3056
|
+
React.createElement('span', { className: 'dshw_balNum' }, fmtCurrency(first.total_balance, first.currency)),
|
|
3057
|
+
low ? React.createElement('span', { className: 'dshw_balWarn', title: '低于提醒阈值 ' + fmtCurrency(snapshot.threshold !== undefined ? snapshot.threshold : 0, bal.currency || 'CNY') }, '余额偏低') : null),
|
|
3058
|
+
React.createElement('div', { className: 'dshw_balSub' },
|
|
3059
|
+
sessionCostLabel(bal.currency) + ' ' + (official.cost === null ? '--' : sessionCostText(official.cost, bal.currency)),
|
|
3060
|
+
React.createElement('span', { className: 'dshw_balDot' }, '\u00b7'),
|
|
3061
|
+
subParts))
|
|
3039
3062
|
}
|
|
3040
3063
|
|
|
3041
3064
|
// All controls in one bordered card with compact rows; the threshold
|
|
@@ -3228,7 +3251,7 @@ window.__ModuleLoader__.load({
|
|
|
3228
3251
|
permanentDeleteControl,
|
|
3229
3252
|
showOfficial ? React.createElement(React.Fragment, null,
|
|
3230
3253
|
React.createElement('div', { className: 'dshw_row' },
|
|
3231
|
-
React.createElement('span', { className: 'dshw_muted' }, '阈值(
|
|
3254
|
+
React.createElement('span', { className: 'dshw_muted' }, '阈值(' + (bal.currency || 'CNY') + ',0=关)'),
|
|
3232
3255
|
thresholdInput),
|
|
3233
3256
|
React.createElement('div', { className: 'dshw_row' },
|
|
3234
3257
|
React.createElement('button', { type: 'button', className: 'dshw_btn', onClick: refreshBalance }, '刷新'),
|
|
@@ -3309,6 +3332,8 @@ window.__ModuleLoader__.load({
|
|
|
3309
3332
|
computePanelPosition: computePanelPosition,
|
|
3310
3333
|
createCompatibilityAdapter: createCompatibilityAdapter,
|
|
3311
3334
|
fmtCurrency: fmtCurrency,
|
|
3335
|
+
selectBalanceInfo: selectBalanceInfo,
|
|
3336
|
+
balanceErrorText: balanceErrorText,
|
|
3312
3337
|
installCompletionNotifier: installCompletionNotifier,
|
|
3313
3338
|
normalizeDataVisibility: normalizeDataVisibility,
|
|
3314
3339
|
normalizeNotifyConfig: normalizeNotifyConfig,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepseek-harness-wallet",
|
|
3
3
|
"description": "Local-first account monitoring, usage accounting, official recharge, completion reminders, flexible layout, host-gated session controls, and multi-account management with hot account switching for DeepSeek Harness.",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"exports": {
|