deepseek-harness-wallet 0.2.1 → 0.2.2
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 +1 -0
- package/index.js +12 -0
- package/lib/client.js +486 -18
- 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.2 - 2026-08-20
|
|
6
|
+
|
|
7
|
+
- 新增 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.
|
|
8
|
+
- 侧边栏时钟卡片视觉重构:三行式清爽信息流,字号与容器全面放大,右侧内置紧凑竖排「充值」按钮直达官方充值后台,消除横向挤压与文本截断。 / Restyled the sidebar footer card: three-line spacious flow, larger typography, and a compact vertical recharge button that opens official top-up without text truncation.
|
|
9
|
+
- 峰谷切换系统通知:跨越峰谷时段时自动推送桌面系统通知(可在设置中随心开启/关闭),不错过半价调用的省钱窗口。 / Added peak/off-peak desktop switch reminders with a dedicated toggle in settings.
|
|
10
|
+
- 跨组件毫秒级实时同步:输入框钱包芯片与侧边栏时钟卡片通过全局事件总线(`dshw-snapshot-update` / `dshw-refresh`)毫秒级联动,账户切换与会话消耗实时双向一致。 / Cross-component event sync: wallet composer chip and sidebar clock card stay 100% in sync across account switches and live session token usage.
|
|
11
|
+
- 会话花费彻底杜绝 `--` 占位:始终常驻真实货币金额(无消耗显示 $0.00 / ¥0.00),随多币种账户(USD/CNY)实时切换对应格式与估算/精确标签。 / Always-visible numeric session cost: never drops to `--`, defaults to $0.00/¥0.00, and hot-adapts to active account currency (USD/CNY).
|
|
12
|
+
|
|
5
13
|
## 0.2.1 - 2026-08-20
|
|
6
14
|
|
|
7
15
|
- 标签上「本场」恢复常显(¥0.00 也显示,新会话不再缺席);仅未定价模型仍隐藏。/ The chip shows the session cost again even at ¥0.00; only unpriced models stay hidden.
|
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
```
|
|
24
24
|
|
|
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
|
+
- **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.
|
|
26
27
|
- **Third-party total** — current-session tokens (input / cache read / output). No balance guessing, no cost math, zero configuration.
|
|
27
28
|
- **Click the chip** to open the detail panel: correctly formatted per-currency balances, cost and token splits, a freely editable low-balance threshold in CNY (two decimals, persisted globally; alerts only compare a CNY balance and never mix currencies), manual refresh, and a jump to the official recharge page (first click shows the domain for confirmation — anti-phishing).
|
|
28
29
|
- **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.
|
package/index.js
CHANGED
|
@@ -36,6 +36,10 @@ const BALANCE_REFRESH_MS = 60_000
|
|
|
36
36
|
const STORE_VERSION = 2
|
|
37
37
|
|
|
38
38
|
// Beijing (UTC+8, no DST) peak windows: 09:00-12:00 and 14:00-18:00.
|
|
39
|
+
// Exposed to clients via snapshot.pricingWindows so the ring clock renders
|
|
40
|
+
// from the active policy instead of hard-coding hours in the bundle.
|
|
41
|
+
const PEAK_WINDOWS = [{ startHour: 9, endHour: 12 }, { startHour: 14, endHour: 18 }]
|
|
42
|
+
const OFF_PEAK_RATE = 0.5
|
|
39
43
|
const BEIJING_OFFSET_MS = 8 * 3600_000
|
|
40
44
|
|
|
41
45
|
export function isBeijingPeak(atMs) {
|
|
@@ -627,6 +631,14 @@ function snapshotView(sessionId) {
|
|
|
627
631
|
return line > 0 && balanceTotal() < line
|
|
628
632
|
})(),
|
|
629
633
|
rechargeUrl: RECHARGE_URL,
|
|
634
|
+
// Peak/off-peak policy for the ring clock; billed in Asia/Shanghai.
|
|
635
|
+
pricingWindows: {
|
|
636
|
+
timezone: 'Asia/Shanghai',
|
|
637
|
+
offsetMinutes: 480,
|
|
638
|
+
windows: PEAK_WINDOWS.map(w => ({ startHour: w.startHour, endHour: w.endHour })),
|
|
639
|
+
offPeakRate: OFF_PEAK_RATE,
|
|
640
|
+
isPeak: isBeijingPeak(Date.now()),
|
|
641
|
+
},
|
|
630
642
|
accounts: {
|
|
631
643
|
activeId: accounts.activeId,
|
|
632
644
|
activeName: active !== null ? active.name : null,
|
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.2'
|
|
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'
|
|
@@ -24,6 +24,11 @@ window.__ModuleLoader__.load({
|
|
|
24
24
|
var NOTIFY_CONFIG_EVENT = 'dshw-completion-notify-change'
|
|
25
25
|
var SETTINGS_EVENT = 'dshw-settings-change'
|
|
26
26
|
var LOW_BLINK_KEY = 'dshw-low-blink-v1'
|
|
27
|
+
var PEAK_RING_KEY = 'dshw-peakring-v1'
|
|
28
|
+
var CLASSIC_CARD_KEY = 'dshw-classic-card-v1'
|
|
29
|
+
var PEAK_NOTIFY_KEY = 'dshw-peaknotify-v1'
|
|
30
|
+
var PEAK_NOTIFY_LAST_KEY = 'dshw-peaknotify-last-v1'
|
|
31
|
+
var PEAK_RING_EVENT = 'dshw-peakring-change'
|
|
27
32
|
var NOTIFY_LEADER_KEY = 'dshw-completion-notify-leader-v1'
|
|
28
33
|
var PERMANENT_DELETE_KEY = 'dshw-permanent-delete-v1'
|
|
29
34
|
var PERMANENT_DELETE_EVENT = 'dshw-permanent-delete-change'
|
|
@@ -409,6 +414,30 @@ window.__ModuleLoader__.load({
|
|
|
409
414
|
'.dshw_settingsFooter{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:2px}',
|
|
410
415
|
'.dshw_settingsFooterActions{display:flex;align-items:center;gap:7px}',
|
|
411
416
|
'.dshw_settingsFooter .dshw_btn{height:30px;padding:0 14px}',
|
|
417
|
+
'.dshw_footRing{width:100%;box-sizing:border-box;display:flex;align-items:center;gap:9px;min-height:70px;margin:2px 0 5px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.08));border-radius:11px;background:var(--dsw-alias-bg-l1,rgba(127,127,127,.035));color:var(--dsw-alias-label-primary,#1f2328);font-size:12.5px;cursor:default;user-select:none;transition:all .15s ease}',
|
|
418
|
+
'.dshw_footRing:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.06));border-color:var(--dsw-alias-border-l2,rgba(0,0,0,.15))}',
|
|
419
|
+
'.dshw_footRing.dshw_low{border-color:var(--dsw-alias-state-error-primary,#e5534b)}',
|
|
420
|
+
'.dshw_footRingRail{width:42px;height:42px;min-height:42px;margin:3px 0 6px;padding:0;justify-content:center;border-radius:50%;background:transparent;border:none;cursor:pointer}',
|
|
421
|
+
'.dshw_footRingRail:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(0,0,0,.06))}',
|
|
422
|
+
'.dshw_footRingLabel{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;justify-content:center;gap:3px;overflow:hidden}',
|
|
423
|
+
'.dshw_footRingHeader{display:flex;align-items:center;gap:6px;white-space:nowrap}',
|
|
424
|
+
'.dshw_footRingTitle{font-size:13.5px;font-weight:700;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
425
|
+
'.dshw_footRingBadge{font-size:10px;font-weight:600;padding:1px 5px;line-height:15px;border-radius:3px;flex:none}',
|
|
426
|
+
'.dshw_footRingBadgePeak{background:var(--dsw-alias-state-error-soft,rgba(229,83,75,.12));color:var(--dsw-alias-state-error-primary,#e5534b)}',
|
|
427
|
+
'.dshw_footRingBadgeOff{background:var(--dsw-alias-state-success-soft,rgba(26,127,55,.12));color:var(--dsw-alias-state-success-primary,#1a7f37)}',
|
|
428
|
+
'.dshw_footRingMoney{font-size:12.5px;font-variant-numeric:tabular-nums;line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
429
|
+
'.dshw_footRingMoney.dshw_low .dshw_footBalNum{color:var(--dsw-alias-state-error-primary,#e5534b);font-weight:700}',
|
|
430
|
+
'.dshw_footRingCountdown{font-size:11px;color:var(--dsw-alias-label-secondary,rgba(31,35,40,.72));line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
431
|
+
'.dshw_footRingRecharge{flex:none;display:flex;align-items:center}',
|
|
432
|
+
'.dshw_footRingBtnRechargeVertical{border:1px solid var(--dsw-alias-brand-primary,#4aa3ff);background:var(--dsw-alias-brand-soft,rgba(74,163,255,.10));color:var(--dsw-alias-brand-primary,#4aa3ff);border-radius:6px;width:24px;min-height:48px;padding:5px 0;font-size:12px;font-weight:650;cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;line-height:1;transition:all .15s ease;flex:none;box-sizing:border-box;user-select:none}',
|
|
433
|
+
'.dshw_footRingBtnRechargeVertical:hover{background:var(--dsw-alias-brand-primary,#4aa3ff);color:#fff}',
|
|
434
|
+
'.dshw_ringPeak{stroke:var(--dsw-alias-state-error-primary,#e5534b)}',
|
|
435
|
+
'.dshw_ringOff{stroke:var(--dsw-alias-state-success-primary,#1a7f37)}',
|
|
436
|
+
'.dshw_ringNeutral{stroke:var(--dsw-alias-border-l3,rgba(0,0,0,.18))}',
|
|
437
|
+
'.dshw_peakRing circle{transition:stroke-width .3s ease}',
|
|
438
|
+
'.dshw_ringNow{stroke-width:6;filter:brightness(1.15)}',
|
|
439
|
+
'.dshw_ringPointer{fill:var(--dsw-alias-label-primary,#1f2328)}',
|
|
440
|
+
'.dshw_ringTick{fill:var(--dsw-alias-label-quaternary,rgba(31,35,40,.45))}',
|
|
412
441
|
'@media (max-width:760px){.dshw_settingsGrid,.dshw_settingsSection .dshw_setCard{grid-template-columns:1fr}.dshw_settingsSection .dshw_setCard>.dshw_setCell.wide{grid-column:1}.dshw_accountAdd{grid-template-columns:1fr}.dshw_settingsFooter{align-items:flex-start;flex-direction:column-reverse}.dshw_settingsFooterActions{width:100%}.dshw_settingsFooterActions .dshw_btn{flex:1}.dshw_settingsHeroMeta{white-space:normal;flex-wrap:wrap}}',
|
|
413
442
|
'.dshw_actionRow{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin:2px 0}',
|
|
414
443
|
'.dshw_textDanger{display:inline-flex;align-items:center;gap:5px;height:22px;padding:0 4px;border:none;background:transparent;font:inherit;font-size:11px;color:var(--dsw-alias-label-quaternary,rgba(31,35,40,.45));cursor:pointer;border-radius:4px;transition:color .12s,background-color .12s}',
|
|
@@ -458,12 +487,12 @@ window.__ModuleLoader__.load({
|
|
|
458
487
|
// ratio, not a live FX quote.
|
|
459
488
|
var USD_ESTIMATE_PER_CNY = 7.25
|
|
460
489
|
function sessionCostText(costCNY, currency) {
|
|
461
|
-
|
|
490
|
+
var val = (costCNY === null || costCNY === undefined) ? 0 : costCNY
|
|
462
491
|
if (currency === 'USD') {
|
|
463
|
-
var usd =
|
|
492
|
+
var usd = val / USD_ESTIMATE_PER_CNY
|
|
464
493
|
return fmtCurrency(usd, 'USD')
|
|
465
494
|
}
|
|
466
|
-
return fmtCurrency(
|
|
495
|
+
return fmtCurrency(val, currency || 'CNY')
|
|
467
496
|
}
|
|
468
497
|
// 本约 = "本次为估算值":美元账户的花费由 CNY 价折算,标签本身承担
|
|
469
498
|
// 约算含义,数字前不再加 ≈ 符号;CNY 账户是精确值,用"本场"。
|
|
@@ -471,7 +500,92 @@ window.__ModuleLoader__.load({
|
|
|
471
500
|
return currency === 'USD' ? '本约' : '本场'
|
|
472
501
|
}
|
|
473
502
|
|
|
474
|
-
|
|
503
|
+
// Ring clock: 24h circle, peak arcs from snapshot.pricingWindows (never
|
|
504
|
+
// hard-coded in the bundle), pointer at current time, active arc bolded.
|
|
505
|
+
// Noon = 0h at 12 o'clock, clockwise. Optional size scales the 76-unit
|
|
506
|
+
// viewBox down for tight hosts (sidebar foot row / rail circle); optional
|
|
507
|
+
// ariaText overrides the fallback label with the full screen-reader state.
|
|
508
|
+
function peakRingSVG(windows, nowHour, size, ariaText) {
|
|
509
|
+
var sizePx = typeof size === 'number' && size > 0 ? size : 76
|
|
510
|
+
var R = 28
|
|
511
|
+
var CX = 38
|
|
512
|
+
var CY = 38
|
|
513
|
+
var CIRC = 2 * Math.PI * R
|
|
514
|
+
// No pricing policy resolved: one neutral full ring, no arcs, no pointer
|
|
515
|
+
// — the clock shows an unconfigured state and claims no price.
|
|
516
|
+
if (!Array.isArray(windows) || windows.length === 0) {
|
|
517
|
+
return React.createElement('svg', {
|
|
518
|
+
width: sizePx, height: sizePx, viewBox: '0 0 76 76',
|
|
519
|
+
role: 'img', className: 'dshw_peakRing',
|
|
520
|
+
'aria-label': ariaText || '峰谷计费时段未配置'
|
|
521
|
+
},
|
|
522
|
+
React.createElement('circle', { key: 'neutral', cx: CX, cy: CY, r: R, fill: 'none', className: 'dshw_ringNeutral', strokeWidth: 4 }))
|
|
523
|
+
}
|
|
524
|
+
var segs = []
|
|
525
|
+
// Build segments: which arcs are peak vs off
|
|
526
|
+
var cursor = 0
|
|
527
|
+
var sorted = (windows || []).slice().sort(function (a, b) { return a.startHour - b.startHour })
|
|
528
|
+
var marks = []
|
|
529
|
+
for (var i = 0; i < sorted.length; i++) {
|
|
530
|
+
var w = sorted[i]
|
|
531
|
+
if (w.startHour > cursor) segs.push({ start: cursor, end: w.startHour, peak: false })
|
|
532
|
+
segs.push({ start: w.startHour, end: w.endHour, peak: true })
|
|
533
|
+
marks.push(w.startHour, w.endHour)
|
|
534
|
+
cursor = w.endHour
|
|
535
|
+
}
|
|
536
|
+
if (cursor < 24) segs.push({ start: cursor, end: 24, peak: false })
|
|
537
|
+
// Is the current hour inside a peak window?
|
|
538
|
+
var inPeak = false
|
|
539
|
+
for (var j = 0; j < sorted.length; j++) {
|
|
540
|
+
if (nowHour >= sorted[j].startHour && nowHour < sorted[j].endHour) { inPeak = true; break }
|
|
541
|
+
}
|
|
542
|
+
var children = []
|
|
543
|
+
for (var k = 0; k < segs.length; k++) {
|
|
544
|
+
var seg = segs[k]
|
|
545
|
+
var frac = (seg.end - seg.start) / 24
|
|
546
|
+
var offset = 1 - (seg.start / 24) // stroke-dashoffset rotates clockwise from 3 o'clock; we want 0h at 12
|
|
547
|
+
var isNow = (nowHour >= seg.start && nowHour < seg.end)
|
|
548
|
+
children.push(React.createElement('circle', {
|
|
549
|
+
key: 'seg' + k,
|
|
550
|
+
cx: CX, cy: CY, r: R,
|
|
551
|
+
fill: 'none',
|
|
552
|
+
className: (seg.peak ? 'dshw_ringPeak' : 'dshw_ringOff') + (isNow ? ' dshw_ringNow' : ''),
|
|
553
|
+
strokeWidth: isNow ? 6.5 : 4,
|
|
554
|
+
strokeDasharray: (frac * CIRC).toFixed(2) + ' ' + (CIRC - frac * CIRC).toFixed(2),
|
|
555
|
+
strokeDashoffset: (offset * CIRC).toFixed(2),
|
|
556
|
+
transform: 'rotate(-90 ' + CX + ' ' + CY + ')' // 0h at 12 o'clock
|
|
557
|
+
}))
|
|
558
|
+
}
|
|
559
|
+
// Boundary ticks: dots at rest, the exact times ride the hover tooltip
|
|
560
|
+
for (var m = 0; m < marks.length; m++) {
|
|
561
|
+
var angle = (marks[m] / 24) * 360 - 90
|
|
562
|
+
var rad = angle * Math.PI / 180
|
|
563
|
+
var tx = CX + (R + 7) * Math.cos(rad)
|
|
564
|
+
var ty = CY + (R + 7) * Math.sin(rad)
|
|
565
|
+
children.push(React.createElement('circle', { key: 'tick' + m, cx: tx.toFixed(1), cy: ty.toFixed(1), r: 2, className: 'dshw_ringTick' }))
|
|
566
|
+
}
|
|
567
|
+
// Pointer: a small triangle riding the ring, apex touching the arc —
|
|
568
|
+
// secondary to the bolded current segment (a 1-min move is 0.25°, invisible)
|
|
569
|
+
var pAngle = (nowHour / 24) * 360 - 90
|
|
570
|
+
var pRad = pAngle * Math.PI / 180
|
|
571
|
+
var cosR = Math.cos(pRad)
|
|
572
|
+
var sinR = Math.sin(pRad)
|
|
573
|
+
var apexX = CX + (R - 1) * cosR
|
|
574
|
+
var apexY = CY + (R - 1) * sinR
|
|
575
|
+
var baseCx = CX + (R + 7) * cosR
|
|
576
|
+
var baseCy = CY + (R + 7) * sinR
|
|
577
|
+
var tri = (baseCx + 3.5 * -sinR).toFixed(1) + ',' + (baseCy + 3.5 * cosR).toFixed(1)
|
|
578
|
+
+ ' ' + apexX.toFixed(1) + ',' + apexY.toFixed(1)
|
|
579
|
+
+ ' ' + (baseCx - 3.5 * -sinR).toFixed(1) + ',' + (baseCy - 3.5 * cosR).toFixed(1)
|
|
580
|
+
children.push(React.createElement('polygon', { key: 'ptr', points: tri, className: 'dshw_ringPointer' }))
|
|
581
|
+
return React.createElement('svg', {
|
|
582
|
+
width: sizePx, height: sizePx, viewBox: '0 0 76 76',
|
|
583
|
+
role: 'img', className: 'dshw_peakRing',
|
|
584
|
+
'aria-label': ariaText || (inPeak ? '当前为高峰时段' : '当前为低谷时段(半价)')
|
|
585
|
+
}, children)
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function fmtCurrency(value, currency) {
|
|
475
589
|
var number = typeof value === 'number' ? value : Number.parseFloat(value)
|
|
476
590
|
if (!Number.isFinite(number)) return '--'
|
|
477
591
|
var code = typeof currency === 'string' && /^[A-Z]{3}$/.test(currency.toUpperCase()) ? currency.toUpperCase() : 'CNY'
|
|
@@ -948,6 +1062,12 @@ function fmtCurrency(value, currency) {
|
|
|
948
1062
|
var [pdSupported, setPdSupported] = React.useState(function () {
|
|
949
1063
|
return compatibility.hasCapability('permanentDelete')
|
|
950
1064
|
})
|
|
1065
|
+
var [ringEnabled, setRingEnabled] = React.useState(function () {
|
|
1066
|
+
try { return compatibility.storage.getItem(PEAK_RING_KEY) !== 'false' } catch (e) { return true }
|
|
1067
|
+
})
|
|
1068
|
+
var [peakNotifyEnabled, setPeakNotifyEnabled] = React.useState(function () {
|
|
1069
|
+
try { return compatibility.storage.getItem(PEAK_NOTIFY_KEY) === 'true' } catch (e) { return false }
|
|
1070
|
+
})
|
|
951
1071
|
React.useEffect(function () {
|
|
952
1072
|
if (typeof window.addEventListener !== 'function') return
|
|
953
1073
|
function refreshCap() { setPdSupported(compatibility.hasCapability('permanentDelete')) }
|
|
@@ -1232,6 +1352,41 @@ function fmtCurrency(value, currency) {
|
|
|
1232
1352
|
}
|
|
1233
1353
|
}),
|
|
1234
1354
|
React.createElement('span', { className: 'dshw_track', 'aria-hidden': 'true' }),
|
|
1355
|
+
React.createElement('span', { className: 'dshw_knob', 'aria-hidden': 'true' }))),
|
|
1356
|
+
React.createElement('div', { className: 'dshw_settingChoice' },
|
|
1357
|
+
React.createElement('span', { className: 'dshw_settingChoiceCopy' },
|
|
1358
|
+
React.createElement('strong', null, '峰谷时钟'),
|
|
1359
|
+
React.createElement('span', null, '侧边栏底部环形钟(零文字)')),
|
|
1360
|
+
React.createElement('label', { className: 'dshw_switch', title: '在侧边栏设置按钮上方显示峰谷环形钟' },
|
|
1361
|
+
React.createElement('input', {
|
|
1362
|
+
type: 'checkbox', checked: ringEnabled,
|
|
1363
|
+
'aria-label': '显示侧边栏峰谷时钟',
|
|
1364
|
+
onChange: function (event) {
|
|
1365
|
+
var enabled = event.target.checked
|
|
1366
|
+
setRingEnabled(enabled)
|
|
1367
|
+
try { compatibility.storage.setItem(PEAK_RING_KEY, String(enabled)) } catch (e) { /* ignore */ }
|
|
1368
|
+
try { compatibility.dispatch(SETTINGS_EVENT) } catch (e) { /* ignore */ }
|
|
1369
|
+
try { compatibility.dispatch(PEAK_RING_EVENT) } catch (e) { /* ignore */ }
|
|
1370
|
+
}
|
|
1371
|
+
}),
|
|
1372
|
+
React.createElement('span', { className: 'dshw_track', 'aria-hidden': 'true' }),
|
|
1373
|
+
React.createElement('span', { className: 'dshw_knob', 'aria-hidden': 'true' }))),
|
|
1374
|
+
React.createElement('div', { className: 'dshw_settingChoice' },
|
|
1375
|
+
React.createElement('span', { className: 'dshw_settingChoiceCopy' },
|
|
1376
|
+
React.createElement('strong', null, '峰谷切换提醒'),
|
|
1377
|
+
React.createElement('span', null, '进入高峰/低谷时通知一次')),
|
|
1378
|
+
React.createElement('label', { className: 'dshw_switch', title: '峰谷切换时间点各通知一次,不重复弹出' },
|
|
1379
|
+
React.createElement('input', {
|
|
1380
|
+
type: 'checkbox', checked: peakNotifyEnabled,
|
|
1381
|
+
'aria-label': '开启峰谷切换提醒',
|
|
1382
|
+
onChange: function (event) {
|
|
1383
|
+
var enabled = event.target.checked
|
|
1384
|
+
setPeakNotifyEnabled(enabled)
|
|
1385
|
+
try { compatibility.storage.setItem(PEAK_NOTIFY_KEY, String(enabled)) } catch (e) { /* ignore */ }
|
|
1386
|
+
try { compatibility.dispatch(SETTINGS_EVENT) } catch (e) { /* ignore */ }
|
|
1387
|
+
}
|
|
1388
|
+
}),
|
|
1389
|
+
React.createElement('span', { className: 'dshw_track', 'aria-hidden': 'true' }),
|
|
1235
1390
|
React.createElement('span', { className: 'dshw_knob', 'aria-hidden': 'true' })))))
|
|
1236
1391
|
)
|
|
1237
1392
|
rows.push(React.createElement('div', { key: 'setcard', className: 'dshw_setCard' }, cells))
|
|
@@ -1352,6 +1507,246 @@ function fmtCurrency(value, currency) {
|
|
|
1352
1507
|
return React.createElement('div', { className: 'dshw_settingsSection', style: { display: 'flex', flexDirection: 'column', gap: '6px', fontSize: '12px', color: 'var(--dsw-alias-label-primary,#1f2328)' } }, rows)
|
|
1353
1508
|
}
|
|
1354
1509
|
|
|
1510
|
+
// Wall-clock hour (0-24, fractional) inside an IANA timezone, via Intl —
|
|
1511
|
+
// never a fixed UTC offset, so DST-observing zones stay correct. Falls back
|
|
1512
|
+
// to the policy's declared offset only if Intl cannot resolve the zone.
|
|
1513
|
+
function wallHourIn(tz, offsetMinutes, date) {
|
|
1514
|
+
try {
|
|
1515
|
+
var text = new Intl.DateTimeFormat('en-GB', {
|
|
1516
|
+
timeZone: tz, hour: '2-digit', minute: '2-digit', hour12: false
|
|
1517
|
+
}).format(date)
|
|
1518
|
+
var parts = text.split(':')
|
|
1519
|
+
var h = Number.parseFloat(parts[0])
|
|
1520
|
+
var m = Number.parseFloat(parts[1])
|
|
1521
|
+
if (Number.isFinite(h) && Number.isFinite(m)) return (h % 24) + m / 60
|
|
1522
|
+
} catch (e) { /* fall through to the fixed offset */ }
|
|
1523
|
+
return (date.getTime() + (offsetMinutes || 0) * 60000) % 86400000 / 3600000
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
// Pure peak-clock math shared by the sidebar ring: current period, next
|
|
1527
|
+
// boundary (wrapping past midnight), the reminder dedup id, and the
|
|
1528
|
+
// zero-text-friendly tooltip / screen-reader strings. windows come from
|
|
1529
|
+
// the wallet snapshot; anything malformed collapses to the neutral state.
|
|
1530
|
+
function peakClockState(policy, nowHour, nowMs) {
|
|
1531
|
+
var windows = policy && Array.isArray(policy.windows) ? policy.windows.filter(function (w) {
|
|
1532
|
+
return w && Number.isFinite(w.startHour) && Number.isFinite(w.endHour) && w.endHour > w.startHour
|
|
1533
|
+
}) : []
|
|
1534
|
+
if (windows.length === 0) {
|
|
1535
|
+
return { configured: false, windows: [], ariaText: '峰谷计费时段未配置', tip: '峰谷时钟 · 计费时段未配置', periodId: null }
|
|
1536
|
+
}
|
|
1537
|
+
var inPeak = false
|
|
1538
|
+
var segStart = null
|
|
1539
|
+
var segEnd = null
|
|
1540
|
+
for (var i = 0; i < windows.length; i++) {
|
|
1541
|
+
if (nowHour >= windows[i].startHour && nowHour < windows[i].endHour) {
|
|
1542
|
+
inPeak = true; segStart = windows[i].startHour; segEnd = windows[i].endHour; break
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
if (!inPeak) {
|
|
1546
|
+
// Off-peak segment runs from the last passed boundary to the next one.
|
|
1547
|
+
var bounds = []
|
|
1548
|
+
for (var b = 0; b < windows.length; b++) bounds.push(windows[b].startHour, windows[b].endHour)
|
|
1549
|
+
bounds.sort(function (a, b2) { return a - b2 })
|
|
1550
|
+
segStart = -1
|
|
1551
|
+
segEnd = 25
|
|
1552
|
+
for (var c = 0; c < bounds.length; c++) {
|
|
1553
|
+
if (bounds[c] <= nowHour && bounds[c] > segStart) segStart = bounds[c]
|
|
1554
|
+
if (bounds[c] > nowHour && bounds[c] < segEnd) segEnd = bounds[c]
|
|
1555
|
+
}
|
|
1556
|
+
if (segStart < 0) segStart = bounds[bounds.length - 1] - 24 // late evening wraps to yesterday's last edge
|
|
1557
|
+
}
|
|
1558
|
+
// Late evening off-peak is past every boundary: the next switch is the
|
|
1559
|
+
// FIRST boundary of tomorrow (segEnd still sits at its 25 sentinel).
|
|
1560
|
+
var nextHour = !inPeak && segEnd > 24 ? bounds[0] : segEnd
|
|
1561
|
+
function hh(h) { return String(Math.floor(((h % 24) + 24) % 24)).padStart(2, '0') + ':00' }
|
|
1562
|
+
var winText = windows.map(function (w) { return hh(w.startHour) + '–' + hh(w.endHour) }).join(' / ')
|
|
1563
|
+
var tzName = policy.timezone || 'Asia/Shanghai'
|
|
1564
|
+
var rate = typeof policy.offPeakRate === 'number' && policy.offPeakRate > 0 ? policy.offPeakRate : 0.5
|
|
1565
|
+
var rateWord = rate === 0.5 ? '半价' : '×' + rate
|
|
1566
|
+
var hoursLeft = (nextHour - nowHour + 24) % 24
|
|
1567
|
+
var msLeft = Math.round(hoursLeft * 3600000)
|
|
1568
|
+
var hLeft = Math.floor(msLeft / 3600000)
|
|
1569
|
+
var mLeft = Math.floor((msLeft % 3600000) / 60000)
|
|
1570
|
+
var leftText = hLeft > 0 ? hLeft + ' 小时 ' + mLeft + ' 分' : mLeft + ' 分钟'
|
|
1571
|
+
var leftShort = hLeft > 0 ? (hLeft + 'h' + (mLeft > 0 ? mLeft + 'm' : '')) : (mLeft + 'm')
|
|
1572
|
+
var switchText = inPeak ? (hh(nextHour) + ' 后' + rateWord) : (hh(nextHour) + ' 恢复标准价')
|
|
1573
|
+
var period = inPeak ? '高峰' : '低谷' + rateWord
|
|
1574
|
+
var periodName = inPeak ? '高峰时段' : '低谷时段'
|
|
1575
|
+
var rateBadge = inPeak ? '标准价' : (rate === 0.5 ? '半价' : '×' + rate)
|
|
1576
|
+
var countdownSummary = hh(nextHour) + ' 切换 · 剩 ' + leftShort
|
|
1577
|
+
var windowSummary = '高峰 ' + winText
|
|
1578
|
+
// Dual timezone: billing is judged in the policy's base zone; a device
|
|
1579
|
+
// elsewhere also sees the local-clock span of the CURRENT segment.
|
|
1580
|
+
var localNote = ''
|
|
1581
|
+
try {
|
|
1582
|
+
var localTz = Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
1583
|
+
if (localTz && localTz !== tzName && Number.isFinite(nowMs)) {
|
|
1584
|
+
var hoursAgo = (nowHour - segStart + 24) % 24
|
|
1585
|
+
var segLenH = ((nextHour - segStart) % 24 + 24) % 24 || 24
|
|
1586
|
+
var startInstant = nowMs - hoursAgo * 3600000
|
|
1587
|
+
var endInstant = startInstant + segLenH * 3600000
|
|
1588
|
+
var fmtLocal = new Intl.DateTimeFormat('en-GB', { hour: '2-digit', minute: '2-digit', hour12: false })
|
|
1589
|
+
localNote = ' · 当地 ' + fmtLocal.format(startInstant) + '–' + fmtLocal.format(endInstant)
|
|
1590
|
+
}
|
|
1591
|
+
} catch (e) { /* timezone introspection is best-effort */ }
|
|
1592
|
+
var ariaText = inPeak
|
|
1593
|
+
? '当前为高峰时段,' + tzName + ' ' + winText + ',按标准价计费'
|
|
1594
|
+
: '当前为低谷时段,按平价的 ' + rate + ' 倍计费,' + tzName + ' 高峰 ' + winText
|
|
1595
|
+
var tip = '峰谷时钟 · 当前' + period + ' · ' + switchText + ' · 还有 ' + leftText
|
|
1596
|
+
+ ' · 高峰 ' + winText + '(' + tzName + ')' + localNote
|
|
1597
|
+
return {
|
|
1598
|
+
configured: true, windows: windows, inPeak: inPeak,
|
|
1599
|
+
nextHour: nextHour, ariaText: ariaText, tip: tip,
|
|
1600
|
+
periodName: periodName, rateBadge: rateBadge,
|
|
1601
|
+
countdownSummary: countdownSummary, windowSummary: windowSummary,
|
|
1602
|
+
// Reminder dedup id: entering this period at this boundary fires once.
|
|
1603
|
+
periodId: (inPeak ? 'p' : 'o') + Math.round(segStart * 100),
|
|
1604
|
+
switchBody: inPeak ? '已进入高峰时段,按标准价计费' : '已进入低谷时段,按平价的 ' + rate + ' 倍计费',
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
// Sidebar foot occupant: the 24h peak/off-peak ring clock, registered on
|
|
1609
|
+
// the host's sidebar.footer.action list so it sits in the strip the
|
|
1610
|
+
// Sidebar foot occupant: the 24h peak/off-peak ring clock, registered on
|
|
1611
|
+
// the host's sidebar.footer.action list so it sits in the strip the
|
|
1612
|
+
// sidebar keeps right above its Settings row (bottom left). Zero in-ring text —
|
|
1613
|
+
// the arcs, the bolded current segment and the hover tooltip carry the meaning.
|
|
1614
|
+
// In wide mode, it displays the 46px ring clock alongside status, balance/cost, and countdown.
|
|
1615
|
+
// In rail mode, it collapses to a 34px centered circular widget.
|
|
1616
|
+
function PeakRingFooter(props) {
|
|
1617
|
+
props = props || {}
|
|
1618
|
+
var wide = props.wide !== false
|
|
1619
|
+
var [shown, setShown] = React.useState(function () {
|
|
1620
|
+
try { return compatibility.storage.getItem(PEAK_RING_KEY) !== 'false' } catch (e) { return true }
|
|
1621
|
+
})
|
|
1622
|
+
var [snapshot, setSnapshot] = React.useState(undefined) // undefined = loading
|
|
1623
|
+
var [nowMs, setNowMs] = React.useState(function () { return Date.now() })
|
|
1624
|
+
|
|
1625
|
+
React.useEffect(function () {
|
|
1626
|
+
var stopped = false
|
|
1627
|
+
function loadSnap() {
|
|
1628
|
+
fetch('/api/wallet/snapshot').then(function (resp) { return resp.json() }).then(function (json) {
|
|
1629
|
+
if (!stopped && json && json.ok) setSnapshot(json)
|
|
1630
|
+
}).catch(function () { if (!stopped) setSnapshot({ ok: false }) })
|
|
1631
|
+
}
|
|
1632
|
+
loadSnap()
|
|
1633
|
+
var listensEvents = typeof window.addEventListener === 'function'
|
|
1634
|
+
function onRingChange() {
|
|
1635
|
+
try { setShown(compatibility.storage.getItem(PEAK_RING_KEY) !== 'false') } catch (e) { /* ignore */ }
|
|
1636
|
+
loadSnap()
|
|
1637
|
+
}
|
|
1638
|
+
function onSnapUpdate(event) {
|
|
1639
|
+
if (!stopped && event && event.detail && event.detail.ok) {
|
|
1640
|
+
setSnapshot(event.detail)
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
if (listensEvents) {
|
|
1644
|
+
window.addEventListener(PEAK_RING_EVENT, onRingChange)
|
|
1645
|
+
window.addEventListener(SETTINGS_EVENT, onRingChange)
|
|
1646
|
+
window.addEventListener('dshw-refresh', onRingChange)
|
|
1647
|
+
window.addEventListener('dshw-snapshot-update', onSnapUpdate)
|
|
1648
|
+
}
|
|
1649
|
+
var timer = window.setInterval(function () {
|
|
1650
|
+
setNowMs(Date.now())
|
|
1651
|
+
loadSnap()
|
|
1652
|
+
}, 30000)
|
|
1653
|
+
return function () {
|
|
1654
|
+
stopped = true
|
|
1655
|
+
window.clearInterval(timer)
|
|
1656
|
+
if (listensEvents) {
|
|
1657
|
+
window.removeEventListener(PEAK_RING_EVENT, onRingChange)
|
|
1658
|
+
window.removeEventListener(SETTINGS_EVENT, onRingChange)
|
|
1659
|
+
window.removeEventListener('dshw-refresh', onRingChange)
|
|
1660
|
+
window.removeEventListener('dshw-snapshot-update', onSnapUpdate)
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
}, [])
|
|
1664
|
+
|
|
1665
|
+
var policy = snapshot && snapshot.pricingWindows ? snapshot.pricingWindows : null
|
|
1666
|
+
|
|
1667
|
+
React.useEffect(function () {
|
|
1668
|
+
if (policy === undefined || policy === null) return
|
|
1669
|
+
try {
|
|
1670
|
+
if (compatibility.storage.getItem(PEAK_NOTIFY_KEY) !== 'true') return
|
|
1671
|
+
} catch (e) { return }
|
|
1672
|
+
var tzName = policy.timezone || 'Asia/Shanghai'
|
|
1673
|
+
var state = peakClockState(policy, wallHourIn(tzName, policy.offsetMinutes, new Date(nowMs)), nowMs)
|
|
1674
|
+
if (!state.periodId) return
|
|
1675
|
+
var last = null
|
|
1676
|
+
try { last = compatibility.storage.getItem(PEAK_NOTIFY_LAST_KEY) } catch (e) { /* ignore */ }
|
|
1677
|
+
if (last === state.periodId) return
|
|
1678
|
+
try { compatibility.storage.setItem(PEAK_NOTIFY_LAST_KEY, state.periodId) } catch (e) { /* ignore */ }
|
|
1679
|
+
if (last === null) return
|
|
1680
|
+
try {
|
|
1681
|
+
compatibility.notify('DeepSeek Harness · 峰谷切换', {
|
|
1682
|
+
body: state.switchBody, tag: 'dsh-wallet-peak'
|
|
1683
|
+
})
|
|
1684
|
+
} catch (e) { /* ignore */ }
|
|
1685
|
+
}, [policy, nowMs])
|
|
1686
|
+
|
|
1687
|
+
if (!shown || snapshot === undefined) return null
|
|
1688
|
+
var tzName = policy && policy.timezone ? policy.timezone : 'Asia/Shanghai'
|
|
1689
|
+
var offsetMinutes = policy && typeof policy.offsetMinutes === 'number' ? policy.offsetMinutes : 480
|
|
1690
|
+
var state = peakClockState(policy, wallHourIn(tzName, offsetMinutes, new Date(nowMs)), nowMs)
|
|
1691
|
+
|
|
1692
|
+
var bal = snapshot && snapshot.balance ? snapshot.balance : {}
|
|
1693
|
+
var balCurrency = bal && bal.currency ? bal.currency : 'CNY'
|
|
1694
|
+
var balText = bal.total !== null && bal.total !== undefined ? fmtCurrency(bal.total, balCurrency) : fmtCurrency(0, balCurrency)
|
|
1695
|
+
var session = snapshot && snapshot.session ? snapshot.session : {}
|
|
1696
|
+
var official = session && session.official ? session.official : {}
|
|
1697
|
+
var costValue = (official.cost === null || official.cost === undefined) ? 0 : official.cost
|
|
1698
|
+
var costText = sessionCostText(costValue, balCurrency)
|
|
1699
|
+
var costLabel = sessionCostLabel(balCurrency)
|
|
1700
|
+
var low = snapshot && snapshot.lowBalance === true
|
|
1701
|
+
|
|
1702
|
+
return React.createElement('div', {
|
|
1703
|
+
className: 'dshw_footRing' + (wide ? '' : ' dshw_footRingRail') + (low ? ' dshw_low' : ''),
|
|
1704
|
+
title: state.tip + ' · 余额 ' + balText + ' · ' + costLabel + ' ' + costText + (wide ? '' : ' · 点击前往官方充值'),
|
|
1705
|
+
'aria-label': state.ariaText + ',余额 ' + balText + ',' + costLabel + ' ' + costText,
|
|
1706
|
+
role: 'region',
|
|
1707
|
+
onClick: wide ? undefined : function () { window.open('https://platform.deepseek.com/top_up', '_blank', 'noopener') }
|
|
1708
|
+
},
|
|
1709
|
+
React.createElement('div', { style: { flex: 'none', display: 'flex', alignItems: 'center', justifyContent: 'center' } },
|
|
1710
|
+
peakRingSVG(state.configured ? state.windows : null, wallHourIn(tzName, offsetMinutes, new Date(nowMs)), wide ? 50 : 36, state.ariaText)
|
|
1711
|
+
),
|
|
1712
|
+
wide ? React.createElement('div', { className: 'dshw_footRingLabel' },
|
|
1713
|
+
React.createElement('div', { className: 'dshw_footRingHeader' },
|
|
1714
|
+
React.createElement('span', {
|
|
1715
|
+
className: 'dshw_footRingTitle',
|
|
1716
|
+
style: { color: state.configured ? (state.inPeak ? 'var(--dsw-alias-state-error-primary,#e5534b)' : 'var(--dsw-alias-state-success-primary,#1a7f37)') : 'inherit' }
|
|
1717
|
+
}, state.configured ? state.periodName : '峰谷时钟'),
|
|
1718
|
+
state.configured && !state.inPeak ? React.createElement('span', {
|
|
1719
|
+
className: 'dshw_footRingBadge dshw_footRingBadgeOff'
|
|
1720
|
+
}, state.rateBadge) : null
|
|
1721
|
+
),
|
|
1722
|
+
React.createElement('div', { className: 'dshw_footRingMoney' + (low ? ' dshw_low' : '') },
|
|
1723
|
+
React.createElement('span', { className: 'dshw_muted' }, '余额 '),
|
|
1724
|
+
React.createElement('span', { className: 'dshw_footBalNum', style: { fontWeight: '600' } }, balText),
|
|
1725
|
+
React.createElement('span', { className: 'dshw_balDot', style: { margin: '0 3px' } }, '·'),
|
|
1726
|
+
React.createElement('span', { className: 'dshw_muted' }, costLabel + ' '),
|
|
1727
|
+
React.createElement('span', { style: { fontWeight: '600' } }, costText)
|
|
1728
|
+
),
|
|
1729
|
+
React.createElement('span', { className: 'dshw_footRingCountdown' },
|
|
1730
|
+
state.configured ? state.countdownSummary : '计费时段未配置')
|
|
1731
|
+
) : null,
|
|
1732
|
+
wide ? React.createElement('div', { className: 'dshw_footRingRecharge' },
|
|
1733
|
+
React.createElement('button', {
|
|
1734
|
+
type: 'button',
|
|
1735
|
+
className: 'dshw_footRingBtnRechargeVertical',
|
|
1736
|
+
title: 'DeepSeek 开放平台 · 前往官方充值',
|
|
1737
|
+
'aria-label': '前往官方充值',
|
|
1738
|
+
onClick: function (e) {
|
|
1739
|
+
if (e && typeof e.stopPropagation === 'function') e.stopPropagation()
|
|
1740
|
+
window.open('https://platform.deepseek.com/top_up', '_blank', 'noopener')
|
|
1741
|
+
}
|
|
1742
|
+
},
|
|
1743
|
+
React.createElement('span', { style: { lineHeight: 1 } }, '充'),
|
|
1744
|
+
React.createElement('span', { style: { lineHeight: 1 } }, '值')
|
|
1745
|
+
)
|
|
1746
|
+
) : null
|
|
1747
|
+
)
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1355
1750
|
function WalletChip(props) {
|
|
1356
1751
|
props = props || {}
|
|
1357
1752
|
var sessionId = props.sessionId
|
|
@@ -1411,7 +1806,7 @@ function fmtCurrency(value, currency) {
|
|
|
1411
1806
|
try { return normalizeChipScale(compatibility.storage.getItem(CHIP_SCALE_KEY)) } catch (e) { return 1 }
|
|
1412
1807
|
})
|
|
1413
1808
|
var [homeMode, setHomeMode] = React.useState('full')
|
|
1414
|
-
|
|
1809
|
+
var chipLiftHostRef = React.useRef(null)
|
|
1415
1810
|
var [dataVisibility, setDataVisibility] = React.useState(function () {
|
|
1416
1811
|
try {
|
|
1417
1812
|
var savedVisibility = compatibility.storage.getItem(DATA_VISIBILITY_KEY)
|
|
@@ -1452,6 +1847,34 @@ function fmtCurrency(value, currency) {
|
|
|
1452
1847
|
refreshHostCapabilities()
|
|
1453
1848
|
return function () { window.removeEventListener(HOST_CAPABILITY_EVENT, refreshHostCapabilities) }
|
|
1454
1849
|
}, [])
|
|
1850
|
+
|
|
1851
|
+
React.useEffect(function () {
|
|
1852
|
+
var pw = data && data.pricingWindows
|
|
1853
|
+
if (!pw || !pw.windows) return
|
|
1854
|
+
var checkNotify = function () {
|
|
1855
|
+
try {
|
|
1856
|
+
if (compatibility.storage.getItem(PEAK_NOTIFY_KEY) !== 'true') return
|
|
1857
|
+
} catch (e) { return }
|
|
1858
|
+
var now = Date.now()
|
|
1859
|
+
var tz = pw.timezone || 'Asia/Shanghai'
|
|
1860
|
+
var offMin = typeof pw.offsetMinutes === 'number' ? pw.offsetMinutes : 480
|
|
1861
|
+
var st = peakClockState(pw, wallHourIn(tz, offMin, new Date(now)), now)
|
|
1862
|
+
if (!st.periodId) return
|
|
1863
|
+
var last = null
|
|
1864
|
+
try { last = compatibility.storage.getItem(PEAK_NOTIFY_LAST_KEY) } catch (e) { /* ignore */ }
|
|
1865
|
+
if (last === st.periodId) return
|
|
1866
|
+
try { compatibility.storage.setItem(PEAK_NOTIFY_LAST_KEY, st.periodId) } catch (e) { /* ignore */ }
|
|
1867
|
+
if (last === null) return
|
|
1868
|
+
try {
|
|
1869
|
+
compatibility.notify('DeepSeek Harness · 峰谷切换', {
|
|
1870
|
+
body: st.switchBody, tag: 'dsh-wallet-peak'
|
|
1871
|
+
})
|
|
1872
|
+
} catch (e) { /* ignore */ }
|
|
1873
|
+
}
|
|
1874
|
+
checkNotify()
|
|
1875
|
+
var timer = setInterval(checkNotify, 60000)
|
|
1876
|
+
return function () { clearInterval(timer) }
|
|
1877
|
+
}, [data])
|
|
1455
1878
|
var showOfficial = dataVisibility.official
|
|
1456
1879
|
var showThird = dataVisibility.third
|
|
1457
1880
|
var chipScaleRef = React.useRef(chipScale)
|
|
@@ -1512,6 +1935,11 @@ function fmtCurrency(value, currency) {
|
|
|
1512
1935
|
if (!alive) return
|
|
1513
1936
|
dataRef.current = json
|
|
1514
1937
|
setData(json)
|
|
1938
|
+
try {
|
|
1939
|
+
if (typeof window.dispatchEvent === 'function' && typeof CustomEvent === 'function') {
|
|
1940
|
+
window.dispatchEvent(new CustomEvent('dshw-snapshot-update', { detail: json }))
|
|
1941
|
+
}
|
|
1942
|
+
} catch (e) { /* ignore */ }
|
|
1515
1943
|
if (json.threshold !== undefined && json.threshold !== null) {
|
|
1516
1944
|
// Follow the active currency's own threshold line: switching
|
|
1517
1945
|
// accounts (and thus currency) refreshes the draft unless the
|
|
@@ -2110,6 +2538,11 @@ function fmtCurrency(value, currency) {
|
|
|
2110
2538
|
: '\u5df2\u6dfb\u52a0' + (json.syncError ? '\uff08\u540c\u6b65\u5931\u8d25: ' + json.syncError + '\uff09' : ''))
|
|
2111
2539
|
loadAccounts()
|
|
2112
2540
|
refreshBalance()
|
|
2541
|
+
try {
|
|
2542
|
+
if (typeof window.dispatchEvent === 'function' && typeof CustomEvent === 'function') {
|
|
2543
|
+
window.dispatchEvent(new CustomEvent('dshw-refresh'))
|
|
2544
|
+
}
|
|
2545
|
+
} catch (e) { /* ignore */ }
|
|
2113
2546
|
} else {
|
|
2114
2547
|
setAccountNotice((json && json.error) || '\u6dfb\u52a0\u5931\u8d25')
|
|
2115
2548
|
}
|
|
@@ -2134,6 +2567,11 @@ function fmtCurrency(value, currency) {
|
|
|
2134
2567
|
setAccountNotice('\u5df2\u5207\u6362\u5230\u300c' + json.account.name + '\u300d')
|
|
2135
2568
|
loadAccounts()
|
|
2136
2569
|
refreshBalance()
|
|
2570
|
+
try {
|
|
2571
|
+
if (typeof window.dispatchEvent === 'function' && typeof CustomEvent === 'function') {
|
|
2572
|
+
window.dispatchEvent(new CustomEvent('dshw-refresh'))
|
|
2573
|
+
}
|
|
2574
|
+
} catch (e) { /* ignore */ }
|
|
2137
2575
|
// 切换账号: 解锁 + 激活响应自带阈值, 输入框零等待跳转
|
|
2138
2576
|
thresholdInitializedRef.current = false
|
|
2139
2577
|
if (json.threshold !== undefined && json.threshold !== null) setThresholdDraft(json.threshold.toFixed(2))
|
|
@@ -2157,6 +2595,11 @@ function fmtCurrency(value, currency) {
|
|
|
2157
2595
|
setAccountNotice('\u5df2\u5220\u9664')
|
|
2158
2596
|
loadAccounts()
|
|
2159
2597
|
refreshBalance()
|
|
2598
|
+
try {
|
|
2599
|
+
if (typeof window.dispatchEvent === 'function' && typeof CustomEvent === 'function') {
|
|
2600
|
+
window.dispatchEvent(new CustomEvent('dshw-refresh'))
|
|
2601
|
+
}
|
|
2602
|
+
} catch (e) { /* ignore */ }
|
|
2160
2603
|
} else {
|
|
2161
2604
|
setAccountNotice((json && json.error) || '\u5220\u9664\u5931\u8d25')
|
|
2162
2605
|
}
|
|
@@ -2573,15 +3016,26 @@ function fmtCurrency(value, currency) {
|
|
|
2573
3016
|
subParts.push(React.createElement('span', { key: 'd' + i, className: 'dshw_balDot' }, '\u00b7'))
|
|
2574
3017
|
subParts.push(React.createElement('span', { key: 'x' + i }, info.currency + ' ' + fmtCurrency(info.total_balance, info.currency)))
|
|
2575
3018
|
})
|
|
2576
|
-
return React.createElement('div', {
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
3019
|
+
return React.createElement('div', {
|
|
3020
|
+
className: low ? 'dshw_balanceCard dshw_low' : 'dshw_balanceCard',
|
|
3021
|
+
style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '8px' }
|
|
3022
|
+
},
|
|
3023
|
+
React.createElement('div', { style: { flex: '1 1 auto', minWidth: 0 } },
|
|
3024
|
+
React.createElement('div', { className: 'dshw_balLine' },
|
|
3025
|
+
React.createElement('span', { className: 'dshw_muted' }, '\u4f59\u989d'),
|
|
3026
|
+
React.createElement('span', { className: 'dshw_balNum' }, fmtCurrency(first.total_balance, first.currency)),
|
|
3027
|
+
low ? React.createElement('span', { className: 'dshw_balWarn', title: '\u4f4e\u4e8e\u63d0\u9192\u9608\u503c \u00a5' + (snapshot.threshold !== undefined ? snapshot.threshold : '') }, '\u4f59\u989d\u504f\u4f4e') : null),
|
|
3028
|
+
React.createElement('div', { className: 'dshw_balSub' },
|
|
3029
|
+
sessionCostLabel(bal.currency) + ' ' + (official.cost === null ? '--' : sessionCostText(official.cost, bal.currency)),
|
|
3030
|
+
React.createElement('span', { className: 'dshw_balDot' }, '\u00b7'),
|
|
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
|
+
}, '↗ 充值'))
|
|
2585
3039
|
}
|
|
2586
3040
|
|
|
2587
3041
|
// All controls in one bordered card with compact rows; the threshold
|
|
@@ -2689,7 +3143,7 @@ function fmtCurrency(value, currency) {
|
|
|
2689
3143
|
},
|
|
2690
3144
|
floatBtnRow,
|
|
2691
3145
|
showOfficial ? React.createElement(React.Fragment, null,
|
|
2692
|
-
React.createElement('div', { className: 'dshw_title' }, (snapshot.accounts && snapshot.accounts.activeName) ? '
|
|
3146
|
+
React.createElement('div', { className: 'dshw_title' }, (snapshot.accounts && snapshot.accounts.activeName) ? '官方 DeepSeek · ' + snapshot.accounts.activeName : '官方 DeepSeek'),
|
|
2693
3147
|
balanceCard(),
|
|
2694
3148
|
official.tokens ? React.createElement('div', { key: 'o-t', className: 'dshw_row' },
|
|
2695
3149
|
React.createElement('span', { className: 'dshw_muted' }, '\u5b98\u65b9 token'),
|
|
@@ -2707,7 +3161,7 @@ function fmtCurrency(value, currency) {
|
|
|
2707
3161
|
className: 'dshw_btn dshw_btnPrimary',
|
|
2708
3162
|
onClick: onRechargeClick
|
|
2709
3163
|
}, '\u2197 \u5145\u503c'),
|
|
2710
|
-
React.createElement('button', { type: 'button', className: 'dshw_btn', onClick: refreshBalance }, '
|
|
3164
|
+
React.createElement('button', { type: 'button', className: 'dshw_btn', onClick: refreshBalance }, '刷新余额'),
|
|
2711
3165
|
React.createElement('button', {
|
|
2712
3166
|
type: 'button',
|
|
2713
3167
|
className: 'dshw_btn',
|
|
@@ -2716,7 +3170,7 @@ function fmtCurrency(value, currency) {
|
|
|
2716
3170
|
onClick: function () {
|
|
2717
3171
|
if (window.confirm('确认清除本会话的余额与 token 数据?不可恢复。')) clearSession()
|
|
2718
3172
|
}
|
|
2719
|
-
}, '
|
|
3173
|
+
}, '清除')),
|
|
2720
3174
|
React.createElement('div', { className: 'dshw_divider' })) : null,
|
|
2721
3175
|
accountsSection(),
|
|
2722
3176
|
React.createElement('div', { style: { marginTop: '8px', textAlign: 'right', fontSize: '10px', color: 'var(--dsw-alias-label-quaternary,rgba(31,35,40,.45))', userSelect: 'none' } }, 'DeepSeek Harness Control Center v' + WALLET_VERSION)
|
|
@@ -2831,6 +3285,16 @@ function fmtCurrency(value, currency) {
|
|
|
2831
3285
|
inject: function () { return {} }
|
|
2832
3286
|
}, WalletSettingsSection)
|
|
2833
3287
|
})
|
|
3288
|
+
try {
|
|
3289
|
+
ctx.slots.inject('sidebar.footer.action', function () {
|
|
3290
|
+
return ctx.slots.register({
|
|
3291
|
+
name: 'sidebar.footer.action',
|
|
3292
|
+
id: 'wallet-peak-ring',
|
|
3293
|
+
order: 50,
|
|
3294
|
+
inject: function () { return {} }
|
|
3295
|
+
}, PeakRingFooter)
|
|
3296
|
+
})
|
|
3297
|
+
} catch (e) { /* host without the sidebar footer slot */ }
|
|
2834
3298
|
}
|
|
2835
3299
|
}
|
|
2836
3300
|
|
|
@@ -2850,6 +3314,10 @@ function fmtCurrency(value, currency) {
|
|
|
2850
3314
|
normalizeNotifyConfig: normalizeNotifyConfig,
|
|
2851
3315
|
normalizeChipLayout: normalizeChipLayout,
|
|
2852
3316
|
normalizeChipScale: normalizeChipScale,
|
|
3317
|
+
peakClockState: peakClockState,
|
|
3318
|
+
wallHourIn: wallHourIn,
|
|
3319
|
+
peakRingSVG: peakRingSVG,
|
|
3320
|
+
PeakRingFooter: PeakRingFooter,
|
|
2853
3321
|
WalletSettingsSection: WalletSettingsSection,
|
|
2854
3322
|
settleDotPosition: settleDotPosition
|
|
2855
3323
|
}
|
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.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"exports": {
|