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 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.1'
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
- if (costCNY === null || costCNY === undefined) return '--'
490
+ var val = (costCNY === null || costCNY === undefined) ? 0 : costCNY
462
491
  if (currency === 'USD') {
463
- var usd = costCNY / USD_ESTIMATE_PER_CNY
492
+ var usd = val / USD_ESTIMATE_PER_CNY
464
493
  return fmtCurrency(usd, 'USD')
465
494
  }
466
- return fmtCurrency(costCNY, currency || 'CNY')
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
- function fmtCurrency(value, currency) {
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
- var chipLiftHostRef = React.useRef(null)
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', { className: low ? 'dshw_balanceCard dshw_low' : 'dshw_balanceCard' },
2577
- React.createElement('div', { className: 'dshw_balLine' },
2578
- React.createElement('span', { className: 'dshw_muted' }, '\u4f59\u989d'),
2579
- React.createElement('span', { className: 'dshw_balNum' }, fmtCurrency(first.total_balance, first.currency)),
2580
- 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),
2581
- React.createElement('div', { className: 'dshw_balSub' },
2582
- sessionCostLabel(bal.currency) + ' ' + (official.cost === null ? '--' : sessionCostText(official.cost, bal.currency)),
2583
- React.createElement('span', { className: 'dshw_balDot' }, '\u00b7'),
2584
- subParts))
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) ? '\u5b98\u65b9 DeepSeek \u00b7 ' + snapshot.accounts.activeName : '\u5b98\u65b9 DeepSeek'),
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 }, '\u5237\u65b0\u4f59\u989d'),
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
- }, '\u6e05\u9664')),
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.1",
4
+ "version": "0.2.2",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "exports": {