@linxin666/dsh-usage 0.4.7 → 0.4.9

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/lib/client.js CHANGED
@@ -69,6 +69,10 @@ window.__ModuleLoader__.load({
69
69
  "usage.day.error": "该日用量加载失败:{error}",
70
70
  "usage.peak.on": "DeepSeek 高峰时段:计价 ×2,{time} 结束",
71
71
  "usage.peak.off": "DeepSeek 空闲时段:计价为高峰一半,{time} 进入高峰",
72
+ "usage.peak.tag.on": "高峰",
73
+ "usage.peak.tag.off": "空闲",
74
+ "usage.peak.ends": "{time} 结束",
75
+ "usage.peak.starts": "{time} 进入高峰",
72
76
  "usage.calls": "{n} 次调用",
73
77
  "usage.tokens.total": "总 tokens",
74
78
  "usage.tokens.input": "输入",
@@ -93,13 +97,15 @@ window.__ModuleLoader__.load({
93
97
  "usage.config.title": "设置",
94
98
  "usage.config.enabled": "启用插件",
95
99
  "usage.config.pollIntervalSec": "轮询间隔(秒)",
100
+ "usage.config.spendCurrency": "消费显示货币",
101
+ "usage.config.cnyPerUsd": "汇率(1 美元兑人民币)",
96
102
  "usage.config.saveFailed": "保存失败:设置未被接受。",
97
103
  "usage.bank.title": "鲸元券",
98
- "usage.bank.hint": "DeepSeek 官方 API 与 MiMo 每消耗 100 万 tokens 铸造 1 鲸元;保存或分享这张票券。",
99
- "usage.bank.noUsage": "暂无 DeepSeek 官方 / MiMo 用量数据(统计自插件启用起)",
104
+ "usage.bank.hint": "所有模型每消耗 100 万 tokens 铸造 1 鲸元;保存或分享这张票券。",
105
+ "usage.bank.noUsage": "暂无用量数据",
100
106
  "usage.bank.minted": "累计铸造 {minted} 鲸元({tokens} tokens)",
101
- "usage.bank.spend.observed": "官方余额实测花费 ¥{cost}(自 {since} 起)",
102
- "usage.bank.spend.estimated": "消费估算:约 ¥{cost}",
107
+ "usage.bank.spend.observed": "官方余额实测花费 {cost}(自 {since} 起)",
108
+ "usage.bank.spend.estimated": "消费估算:约 {cost}",
103
109
  "usage.bank.window": "统计窗口 {from} ~ {to}",
104
110
  "usage.bank.windowAll": "全部保留期",
105
111
  "usage.bank.save": "保存图片",
@@ -135,6 +141,10 @@ window.__ModuleLoader__.load({
135
141
  "usage.day.error": "Failed to load that day: {error}",
136
142
  "usage.peak.on": "DeepSeek peak hours: 2x pricing, ends {time}",
137
143
  "usage.peak.off": "DeepSeek off-peak: half of peak pricing, peak returns {time}",
144
+ "usage.peak.tag.on": "Peak",
145
+ "usage.peak.tag.off": "Off-peak",
146
+ "usage.peak.ends": "ends {time}",
147
+ "usage.peak.starts": "peak at {time}",
138
148
  "usage.calls": "{n} calls",
139
149
  "usage.tokens.total": "Total tokens",
140
150
  "usage.tokens.input": "Input",
@@ -159,13 +169,15 @@ window.__ModuleLoader__.load({
159
169
  "usage.config.title": "Settings",
160
170
  "usage.config.enabled": "Enable plugin",
161
171
  "usage.config.pollIntervalSec": "Poll interval (seconds)",
172
+ "usage.config.spendCurrency": "Spend display currency",
173
+ "usage.config.cnyPerUsd": "Exchange rate (CNY per USD)",
162
174
  "usage.config.saveFailed": "Save failed: the deployment did not accept the setting.",
163
175
  "usage.bank.title": "Whale-yuan voucher",
164
- "usage.bank.hint": "Every 1,000,000 tokens spent on the DeepSeek official API or on MiMo mint one whale yuan; save or share the note.",
165
- "usage.bank.noUsage": "No DeepSeek official or MiMo usage yet (counting starts when the plugin is enabled)",
176
+ "usage.bank.hint": "Every 1,000,000 tokens spent across all models mint one whale yuan; save or share the note.",
177
+ "usage.bank.noUsage": "No usage data yet",
166
178
  "usage.bank.minted": "Minted {minted} whale yuan ({tokens} tokens)",
167
- "usage.bank.spend.observed": "Spent ¥{cost} observed on the official balance (watching since {since})",
168
- "usage.bank.spend.estimated": "Estimated spend: about ¥{cost}",
179
+ "usage.bank.spend.observed": "Spent {cost} observed on the official balance (watching since {since})",
180
+ "usage.bank.spend.estimated": "Estimated spend: about {cost}",
169
181
  "usage.bank.window": "Window {from} - {to}",
170
182
  "usage.bank.windowAll": "All retained days",
171
183
  "usage.bank.save": "Save image",
@@ -193,7 +205,7 @@ window.__ModuleLoader__.load({
193
205
  }
194
206
  //#endregion
195
207
  //#region \0dsh-css:packages/dsh-usage/src/client/usage.module.css.mjs
196
- const css = ".cvtkAW_section{color:inherit;flex-direction:column;gap:16px;display:flex}.cvtkAW_header{justify-content:space-between;align-items:center;gap:12px;display:flex}.cvtkAW_currentProvider{opacity:.75;font-size:13px}.cvtkAW_refreshBtn{appearance:none;color:inherit;font:inherit;cursor:pointer;opacity:.85;background:0 0;border:1px solid;border-radius:8px;padding:4px 12px;font-size:12px;transition:opacity .12s,background-color .12s}.cvtkAW_refreshBtn:hover:not(:disabled){opacity:1;background:color-mix(in srgb, currentColor 8%, transparent)}.cvtkAW_refreshBtn:disabled{cursor:default;opacity:.5}.cvtkAW_refreshBtn:focus-visible{box-shadow:0 0 0 2px color-mix(in srgb, currentColor 45%, transparent);outline:none}.cvtkAW_tabs{border-bottom:1px solid color-mix(in srgb, currentColor 14%, transparent);gap:4px;display:flex}.cvtkAW_tab{appearance:none;color:inherit;font:inherit;cursor:pointer;opacity:.65;background:0 0;border:none;border-bottom:2px solid #0000;margin-bottom:-1px;padding:6px 14px;font-size:13px}.cvtkAW_tab:hover{opacity:.9}.cvtkAW_tab:focus-visible{box-shadow:0 0 0 2px color-mix(in srgb, currentColor 45%, transparent);outline:none}.cvtkAW_tabActive{opacity:1;border-bottom-color:currentColor;font-weight:600}.cvtkAW_card{border:1px solid color-mix(in srgb, currentColor 14%, transparent);border-radius:12px;flex-direction:column;gap:10px;padding:14px 16px;display:flex}.cvtkAW_cardTitle{letter-spacing:.04em;text-transform:uppercase;opacity:.6;font-size:12px;font-weight:600}.cvtkAW_dayPicker{align-items:center;gap:8px;display:flex}.cvtkAW_dayPickerLabel{opacity:.6;font-size:11px}.cvtkAW_daySelect{border:1px solid color-mix(in srgb, currentColor 25%, transparent);color:inherit;font:inherit;font-variant-numeric:tabular-nums;background:0 0;border-radius:6px;padding:3px 8px;font-size:12px}.cvtkAW_daySelect:focus-visible{box-shadow:0 0 0 2px color-mix(in srgb, currentColor 45%, transparent);outline:none}.cvtkAW_daySelect option,.cvtkAW_settingItem select option{background-color:var(--dsw-alias-bg-multi-select,Canvas);color:var(--dsw-alias-label-primary,CanvasText)}.cvtkAW_statRow{flex-wrap:wrap;gap:18px;display:flex}.cvtkAW_stat{flex-direction:column;gap:2px;display:flex}.cvtkAW_statValue{font-variant-numeric:tabular-nums;font-size:18px;font-weight:600}.cvtkAW_statLabel{opacity:.6;font-size:11px}.cvtkAW_providerRow{border-top:1px solid color-mix(in srgb, currentColor 8%, transparent);flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px;padding:6px 0;font-size:13px;display:flex}.cvtkAW_providerRow:first-of-type{border-top:none}.cvtkAW_providerName{align-items:center;gap:8px;min-width:0;display:flex}.cvtkAW_providerTokens{font-variant-numeric:tabular-nums;opacity:.75;white-space:nowrap}.cvtkAW_providerBalance{font-variant-numeric:tabular-nums;white-space:nowrap;font-weight:600}.cvtkAW_currentBadge{border:1px solid color-mix(in srgb, currentColor 35%, transparent);opacity:.8;border-radius:999px;flex:none;padding:1px 7px;font-size:10px;font-weight:600}.cvtkAW_chart{flex-direction:column;gap:12px;display:flex}.cvtkAW_chartProvider{flex-direction:column;gap:4px;display:flex}.cvtkAW_chartHead{justify-content:space-between;align-items:baseline;gap:10px;font-size:13px;display:flex}.cvtkAW_chartTokens{font-variant-numeric:tabular-nums;opacity:.65;white-space:nowrap;font-size:11px}.cvtkAW_chartBar{background:color-mix(in srgb, currentColor 8%, transparent);border-radius:999px;height:8px;display:block;overflow:hidden}.cvtkAW_chartFill{background:color-mix(in srgb, currentColor 55%, transparent);border-radius:999px;height:100%;transition:width .3s;display:block}.cvtkAW_chartModel{opacity:.8;grid-template-columns:minmax(80px,180px) 1fr auto;align-items:center;gap:8px;padding-left:14px;font-size:11px;display:grid}.cvtkAW_chartModelName{text-overflow:ellipsis;white-space:nowrap;opacity:.8;overflow:hidden}.cvtkAW_chartModelBar{background:color-mix(in srgb, currentColor 6%, transparent);border-radius:999px;height:4px;display:block;overflow:hidden}.cvtkAW_chartModelFill{background:color-mix(in srgb, currentColor 35%, transparent);border-radius:999px;height:100%;transition:width .3s;display:block}.cvtkAW_trendAxis{opacity:.5;font-variant-numeric:tabular-nums;justify-content:space-between;font-size:10px;display:flex}.cvtkAW_muted{opacity:.6;font-size:12px}.cvtkAW_voucherPreview canvas{border-radius:8px;width:100%;height:auto;display:block}.cvtkAW_buttonRow{gap:8px;display:flex}.cvtkAW_errorLine{opacity:.75;font-size:12px}.cvtkAW_planCard{flex-direction:column;gap:8px;display:flex}.cvtkAW_planHead{justify-content:space-between;align-items:baseline;gap:10px;display:flex}.cvtkAW_planName{font-size:14px;font-weight:600}.cvtkAW_windowRow{flex-direction:column;gap:4px;display:flex}.cvtkAW_windowLabel{opacity:.8;font-variant-numeric:tabular-nums;justify-content:space-between;font-size:12px;display:flex}.cvtkAW_bar{background:color-mix(in srgb, currentColor 10%, transparent);border-radius:999px;height:6px;overflow:hidden}.cvtkAW_barFill{background:color-mix(in srgb, currentColor 55%, transparent);border-radius:999px;height:100%;transition:width .3s}.cvtkAW_barWarn{background:#d97706}.cvtkAW_barLow{background:#dc2626}.cvtkAW_resetLine{opacity:.55;font-variant-numeric:tabular-nums;font-size:11px}.cvtkAW_settingsGrid{flex-wrap:wrap;align-items:center;gap:16px;display:flex}.cvtkAW_settingItem{align-items:center;gap:8px;font-size:13px;display:flex}.cvtkAW_settingItem input[type=checkbox]{accent-color:currentColor}.cvtkAW_settingItem input[type=number]{border:1px solid color-mix(in srgb, currentColor 25%, transparent);width:90px;color:inherit;font:inherit;background:0 0;border-radius:6px;padding:3px 8px;font-size:13px}.cvtkAW_settingItem select{border:1px solid color-mix(in srgb, currentColor 25%, transparent);color:inherit;font:inherit;background:0 0;border-radius:6px;padding:3px 8px;font-size:13px}.cvtkAW_settingItem input:focus-visible,.cvtkAW_settingItem select:focus-visible{box-shadow:0 0 0 2px color-mix(in srgb, currentColor 45%, transparent);outline:none}.cvtkAW_footCard{box-sizing:border-box;background:color-mix(in srgb, currentColor 4%, transparent);width:100%;color:inherit;border:none;border-radius:12px;margin:2px 0 4px;transition:background-color .12s;position:relative}.cvtkAW_footCard:hover{background:var(--dsw-alias-interactive-bg-hover)}.cvtkAW_footCardCollapsed{background:0 0}.cvtkAW_footMain{appearance:none;box-sizing:border-box;width:100%;color:inherit;font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;border-radius:12px;flex-direction:column;gap:4px;padding:8px;display:flex}.cvtkAW_footMain:focus-visible{box-shadow:0 0 0 2px color-mix(in srgb, currentColor 45%, transparent);outline:none}.cvtkAW_footToggle{appearance:none;width:20px;height:20px;color:inherit;cursor:pointer;opacity:.55;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:0;transition:opacity .12s,background-color .12s;display:inline-flex;position:absolute;top:6px;right:6px}.cvtkAW_footCard:hover .cvtkAW_footToggle{opacity:.8}.cvtkAW_footToggle:hover{opacity:1;background:color-mix(in srgb, currentColor 10%, transparent)}.cvtkAW_footToggle:focus-visible{box-shadow:0 0 0 2px color-mix(in srgb, currentColor 45%, transparent);outline:none}.cvtkAW_footToggle svg{display:block}.cvtkAW_footHead{justify-content:space-between;align-items:baseline;gap:8px;display:flex}.cvtkAW_footTitle{opacity:.65;white-space:nowrap;align-items:center;gap:6px;min-width:0;font-size:12px;display:inline-flex}.cvtkAW_footTitle svg{flex:none;display:block}.cvtkAW_footValue{font-variant-numeric:tabular-nums;white-space:nowrap;margin-right:18px;font-size:15px;font-weight:600}.cvtkAW_footLine{opacity:.7;font-variant-numeric:tabular-nums;text-overflow:ellipsis;white-space:nowrap;font-size:11px;overflow:hidden}.cvtkAW_footMeta{opacity:.5;font-variant-numeric:tabular-nums;font-size:10px}.cvtkAW_footCardCollapsed .cvtkAW_footMain{min-height:36px;padding:7px 8px}.cvtkAW_footStrip{white-space:nowrap;align-items:center;gap:6px;padding-right:20px;font-size:12px;display:flex;overflow:hidden}.cvtkAW_footStrip svg{opacity:.65;flex:none;display:block}.cvtkAW_footStripProvider{text-overflow:ellipsis;flex:none;max-width:45%;font-weight:500;overflow:hidden}.cvtkAW_footStripLabel{opacity:.65;text-overflow:ellipsis;overflow:hidden}.cvtkAW_footStripValue{font-variant-numeric:tabular-nums;margin-left:auto;font-weight:600}[data-dsh-frame][data-sidebar-collapsed] .cvtkAW_footCard,[data-sidebar-collapsed] .cvtkAW_footCard{display:none}";
208
+ const css = ".cvtkAW_section{color:inherit;flex-direction:column;gap:16px;display:flex}.cvtkAW_header{justify-content:space-between;align-items:center;gap:12px;display:flex}.cvtkAW_currentProvider{opacity:.75;font-size:13px}.cvtkAW_refreshBtn{appearance:none;color:inherit;font:inherit;cursor:pointer;opacity:.85;background:0 0;border:1px solid;border-radius:8px;padding:4px 12px;font-size:12px;transition:opacity .12s,background-color .12s}.cvtkAW_refreshBtn:hover:not(:disabled){opacity:1;background:color-mix(in srgb, currentColor 8%, transparent)}.cvtkAW_refreshBtn:disabled{cursor:default;opacity:.5}.cvtkAW_refreshBtn:focus-visible{box-shadow:0 0 0 2px color-mix(in srgb, currentColor 45%, transparent);outline:none}.cvtkAW_tabs{border-bottom:1px solid color-mix(in srgb, currentColor 14%, transparent);gap:4px;display:flex}.cvtkAW_tab{appearance:none;color:inherit;font:inherit;cursor:pointer;opacity:.65;background:0 0;border:none;border-bottom:2px solid #0000;margin-bottom:-1px;padding:6px 14px;font-size:13px}.cvtkAW_tab:hover{opacity:.9}.cvtkAW_tab:focus-visible{box-shadow:0 0 0 2px color-mix(in srgb, currentColor 45%, transparent);outline:none}.cvtkAW_tabActive{opacity:1;border-bottom-color:currentColor;font-weight:600}.cvtkAW_card{border:1px solid color-mix(in srgb, currentColor 14%, transparent);border-radius:12px;flex-direction:column;gap:10px;padding:14px 16px;display:flex}.cvtkAW_cardTitle{letter-spacing:.04em;text-transform:uppercase;opacity:.6;font-size:12px;font-weight:600}.cvtkAW_dayPicker{align-items:center;gap:8px;display:flex}.cvtkAW_dayPickerLabel{opacity:.6;font-size:11px}.cvtkAW_daySelect{border:1px solid color-mix(in srgb, currentColor 25%, transparent);color:inherit;font:inherit;font-variant-numeric:tabular-nums;background:0 0;border-radius:6px;padding:3px 8px;font-size:12px}.cvtkAW_daySelect:focus-visible{box-shadow:0 0 0 2px color-mix(in srgb, currentColor 45%, transparent);outline:none}.cvtkAW_daySelect option,.cvtkAW_settingItem select option{background-color:var(--dsw-alias-bg-multi-select,Canvas);color:var(--dsw-alias-label-primary,CanvasText)}.cvtkAW_statRow{flex-wrap:wrap;gap:18px;display:flex}.cvtkAW_stat{flex-direction:column;gap:2px;display:flex}.cvtkAW_statValue{font-variant-numeric:tabular-nums;font-size:18px;font-weight:600}.cvtkAW_statLabel{opacity:.6;font-size:11px}.cvtkAW_providerRow{border-top:1px solid color-mix(in srgb, currentColor 8%, transparent);flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px;padding:6px 0;font-size:13px;display:flex}.cvtkAW_providerRow:first-of-type{border-top:none}.cvtkAW_providerName{align-items:center;gap:8px;min-width:0;display:flex}.cvtkAW_providerTokens{font-variant-numeric:tabular-nums;opacity:.75;white-space:nowrap}.cvtkAW_providerBalance{font-variant-numeric:tabular-nums;white-space:nowrap;font-weight:600}.cvtkAW_currentBadge{border:1px solid color-mix(in srgb, currentColor 35%, transparent);opacity:.8;border-radius:999px;flex:none;padding:1px 7px;font-size:10px;font-weight:600}.cvtkAW_chart{flex-direction:column;gap:12px;display:flex}.cvtkAW_chartProvider{flex-direction:column;gap:4px;display:flex}.cvtkAW_chartHead{justify-content:space-between;align-items:baseline;gap:10px;font-size:13px;display:flex}.cvtkAW_chartTokens{font-variant-numeric:tabular-nums;opacity:.65;white-space:nowrap;font-size:11px}.cvtkAW_chartBar{background:color-mix(in srgb, currentColor 8%, transparent);border-radius:999px;height:8px;display:block;overflow:hidden}.cvtkAW_chartFill{background:color-mix(in srgb, currentColor 55%, transparent);border-radius:999px;height:100%;transition:width .3s;display:block}.cvtkAW_chartModel{opacity:.8;grid-template-columns:minmax(80px,180px) 1fr auto;align-items:center;gap:8px;padding-left:14px;font-size:11px;display:grid}.cvtkAW_chartModelName{text-overflow:ellipsis;white-space:nowrap;opacity:.8;overflow:hidden}.cvtkAW_chartModelBar{background:color-mix(in srgb, currentColor 6%, transparent);border-radius:999px;height:4px;display:block;overflow:hidden}.cvtkAW_chartModelFill{background:color-mix(in srgb, currentColor 35%, transparent);border-radius:999px;height:100%;transition:width .3s;display:block}.cvtkAW_trendAxis{opacity:.5;font-variant-numeric:tabular-nums;justify-content:space-between;font-size:10px;display:flex}.cvtkAW_muted{opacity:.6;font-size:12px}.cvtkAW_voucherPreview canvas{border-radius:8px;width:100%;height:auto;display:block}.cvtkAW_buttonRow{gap:8px;display:flex}.cvtkAW_errorLine{opacity:.75;font-size:12px}.cvtkAW_planCard{flex-direction:column;gap:8px;display:flex}.cvtkAW_planHead{justify-content:space-between;align-items:baseline;gap:10px;display:flex}.cvtkAW_planName{font-size:14px;font-weight:600}.cvtkAW_windowRow{flex-direction:column;gap:4px;display:flex}.cvtkAW_windowLabel{opacity:.8;font-variant-numeric:tabular-nums;justify-content:space-between;font-size:12px;display:flex}.cvtkAW_bar{background:color-mix(in srgb, currentColor 10%, transparent);border-radius:999px;height:6px;overflow:hidden}.cvtkAW_barFill{background:color-mix(in srgb, currentColor 55%, transparent);border-radius:999px;height:100%;transition:width .3s}.cvtkAW_barWarn{background:#d97706}.cvtkAW_barLow{background:#dc2626}.cvtkAW_resetLine{opacity:.55;font-variant-numeric:tabular-nums;font-size:11px}.cvtkAW_settingsGrid{flex-wrap:wrap;align-items:center;gap:16px;display:flex}.cvtkAW_settingItem{align-items:center;gap:8px;font-size:13px;display:flex}.cvtkAW_settingItem input[type=checkbox]{accent-color:currentColor}.cvtkAW_settingItem input[type=number]{border:1px solid color-mix(in srgb, currentColor 25%, transparent);width:90px;color:inherit;font:inherit;background:0 0;border-radius:6px;padding:3px 8px;font-size:13px}.cvtkAW_settingItem select{border:1px solid color-mix(in srgb, currentColor 25%, transparent);color:inherit;font:inherit;background:0 0;border-radius:6px;padding:3px 8px;font-size:13px}.cvtkAW_settingItem input:focus-visible,.cvtkAW_settingItem select:focus-visible{box-shadow:0 0 0 2px color-mix(in srgb, currentColor 45%, transparent);outline:none}.cvtkAW_footCard{box-sizing:border-box;background:color-mix(in srgb, currentColor 4%, transparent);width:100%;color:inherit;border:none;border-radius:12px;margin:2px 0 4px;transition:background-color .12s;position:relative}.cvtkAW_footCard:hover{background:var(--dsw-alias-interactive-bg-hover)}.cvtkAW_footCardCollapsed{background:0 0}.cvtkAW_footMain{appearance:none;box-sizing:border-box;width:100%;color:inherit;font:inherit;text-align:left;cursor:pointer;background:0 0;border:none;border-radius:12px;flex-direction:column;gap:4px;padding:8px;display:flex}.cvtkAW_footMain:focus-visible{box-shadow:0 0 0 2px color-mix(in srgb, currentColor 45%, transparent);outline:none}.cvtkAW_footToggle{appearance:none;width:20px;height:20px;color:inherit;cursor:pointer;opacity:.55;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:0;transition:opacity .12s,background-color .12s;display:inline-flex;position:absolute;top:6px;right:6px}.cvtkAW_footCard:hover .cvtkAW_footToggle{opacity:.8}.cvtkAW_footToggle:hover{opacity:1;background:color-mix(in srgb, currentColor 10%, transparent)}.cvtkAW_footToggle:focus-visible{box-shadow:0 0 0 2px color-mix(in srgb, currentColor 45%, transparent);outline:none}.cvtkAW_footToggle svg{display:block}.cvtkAW_footHead{justify-content:space-between;align-items:baseline;gap:8px;display:flex}.cvtkAW_footTitle{opacity:.65;white-space:nowrap;align-items:center;gap:6px;min-width:0;font-size:12px;display:inline-flex}.cvtkAW_footTitle svg{flex:none;display:block}.cvtkAW_footValue{font-variant-numeric:tabular-nums;white-space:nowrap;margin-right:18px;font-size:15px;font-weight:600}.cvtkAW_footLine{opacity:.7;font-variant-numeric:tabular-nums;text-overflow:ellipsis;white-space:nowrap;font-size:11px;overflow:hidden}.cvtkAW_footMeta{opacity:.5;font-variant-numeric:tabular-nums;font-size:10px}.cvtkAW_footCardCollapsed .cvtkAW_footMain{min-height:36px;padding:7px 8px}.cvtkAW_footStrip{white-space:nowrap;align-items:center;gap:6px;padding-right:20px;font-size:12px;display:flex;overflow:hidden}.cvtkAW_footStrip svg{opacity:.65;flex:none;display:block}.cvtkAW_footStripProvider{text-overflow:ellipsis;flex:none;max-width:38%;font-weight:500;overflow:hidden}.cvtkAW_footStripLabel{opacity:.65;text-overflow:ellipsis;overflow:hidden}.cvtkAW_footStripValue{font-variant-numeric:tabular-nums;margin-left:auto;font-weight:600}[data-dsh-frame][data-sidebar-collapsed] .cvtkAW_footCard,[data-sidebar-collapsed] .cvtkAW_footCard{display:none}.cvtkAW_peakBadge,.cvtkAW_peakLine{font-variant-numeric:tabular-nums;white-space:nowrap;align-items:center;gap:4px;font-size:11px;display:inline-flex}.cvtkAW_peakBadge{flex:none}.cvtkAW_peakLine{gap:6px}.cvtkAW_peakBadge[data-peak=on],.cvtkAW_peakLine[data-peak=on]{color:var(--dsw-alias-state-error-primary,#b42318)}.cvtkAW_peakBadge[data-peak=off],.cvtkAW_peakLine[data-peak=off]{color:var(--dsw-alias-state-success-primary,#067647)}.cvtkAW_peakDot{background:currentColor;border-radius:50%;flex:none;width:6px;height:6px}.cvtkAW_footPeak{white-space:nowrap;align-items:center;gap:5px;font-size:10px;display:flex;overflow:hidden}.cvtkAW_footPeakClock{opacity:.7;font-variant-numeric:tabular-nums}";
197
209
  const tagId = "@linxin666/dsh-usage/packages/dsh-usage/src/client/usage.module.css";
198
210
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
199
211
  const tag = document.createElement("style");
@@ -232,6 +244,8 @@ window.__ModuleLoader__.load({
232
244
  "footLine": "cvtkAW_footLine",
233
245
  "footMain": "cvtkAW_footMain",
234
246
  "footMeta": "cvtkAW_footMeta",
247
+ "footPeak": "cvtkAW_footPeak",
248
+ "footPeakClock": "cvtkAW_footPeakClock",
235
249
  "footStrip": "cvtkAW_footStrip",
236
250
  "footStripLabel": "cvtkAW_footStripLabel",
237
251
  "footStripProvider": "cvtkAW_footStripProvider",
@@ -241,6 +255,9 @@ window.__ModuleLoader__.load({
241
255
  "footValue": "cvtkAW_footValue",
242
256
  "header": "cvtkAW_header",
243
257
  "muted": "cvtkAW_muted",
258
+ "peakBadge": "cvtkAW_peakBadge",
259
+ "peakDot": "cvtkAW_peakDot",
260
+ "peakLine": "cvtkAW_peakLine",
244
261
  "planCard": "cvtkAW_planCard",
245
262
  "planHead": "cvtkAW_planHead",
246
263
  "planName": "cvtkAW_planName",
@@ -266,6 +283,34 @@ window.__ModuleLoader__.load({
266
283
  "windowRow": "cvtkAW_windowRow"
267
284
  };
268
285
  //#endregion
286
+ //#region src/client/PeakBadge.tsx
287
+ /** HH:MM of one instant; an unparsable instant drops the time. */
288
+ function formatClock(ms) {
289
+ try {
290
+ return new Date(ms).toLocaleTimeString([], {
291
+ hour: "2-digit",
292
+ minute: "2-digit"
293
+ });
294
+ } catch {
295
+ return "";
296
+ }
297
+ }
298
+ /** The peak/off-peak marker itself; nothing renders outside the DeepSeek family. */
299
+ function PeakBadge(props) {
300
+ const { peak, boundaryMs, variant } = props;
301
+ const sentence = t(peak ? "usage.peak.on" : "usage.peak.off", { time: formatClock(boundaryMs) });
302
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
303
+ className: variant === "full" ? usage_module_css_default.peakLine : usage_module_css_default.peakBadge,
304
+ "data-peak": peak ? "on" : "off",
305
+ "data-dsh-part": "peak-status",
306
+ title: sentence,
307
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
308
+ className: usage_module_css_default.peakDot,
309
+ "aria-hidden": "true"
310
+ }), variant === "full" ? sentence : t(peak ? "usage.peak.tag.on" : "usage.peak.tag.off")]
311
+ });
312
+ }
313
+ //#endregion
269
314
  //#region src/core/adapters.ts
270
315
  /** Parse a string/number into a finite number, else undefined. */
271
316
  function toNum(value) {
@@ -1055,7 +1100,7 @@ window.__ModuleLoader__.load({
1055
1100
  //#endregion
1056
1101
  //#region src/client/voucher.ts
1057
1102
  /**
1058
- * The Whale-yuan voucher (鲸元券): the mint family's retained ledger totals
1103
+ * The Whale-yuan voucher (鲸元券): the profile's retained ledger totals
1059
1104
  * stamped onto the banknote artwork as a denomination, ready
1060
1105
  * to save or share. The canvas copy is deliberately locale-neutral (digits,
1061
1106
  * latin captions, ISO dates) so the exported image needs no dictionary;
@@ -1078,30 +1123,23 @@ window.__ModuleLoader__.load({
1078
1123
  return `${date.getFullYear()}-${month}-${day}`;
1079
1124
  }
1080
1125
  /**
1081
- * Provider route ids minted beside the DeepSeek official family: the MiMo
1082
- * gateway, which a profile may run as its default route and which the ledger
1083
- * records under its own route id. The bank counts the tokens a profile spends,
1084
- * not only the ones DeepSeek bills. Route ids rather than adapter families
1085
- * because minting is an accounting decision that must never widen the
1086
- * credential or probing paths — the same split `isDeepSeekProviderRoute()`
1087
- * keeps against `adapterFor()` (issue #1772). An empty list mints exactly the
1088
- * official family, which is how a profile that never runs MiMo behaves.
1089
- */
1090
- const EXTRA_MINT_ROUTE_IDS = ["mimo"];
1091
- /** Whether one provider route's ledger rows mint whale yuan. */
1092
- function mintsWhaleYuan(provider) {
1093
- return isDeepSeekProviderRoute(provider) || EXTRA_MINT_ROUTE_IDS.includes(provider);
1094
- }
1095
- /**
1096
- * Sum the mint family's rows out of a usage window: the whole DeepSeek
1097
- * official family (the `deepseek` catalog alias, the live `deepseek-official`
1098
- * route and the signed-in `deepseek-account` route all fold into one voucher)
1099
- * plus {@link EXTRA_MINT_ROUTE_IDS}. Undefined when the window is absent
1100
- * (older host) or the family has no usage — a zero-token note mints nothing.
1126
+ * Sum every provider's rows out of a usage window: the note is a
1127
+ * proof-of-spend for what the profile actually used, so the tokens and calls
1128
+ * of every route in the ledger mint — the DeepSeek official family (the
1129
+ * `deepseek` catalog alias, the live `deepseek-official` route and the
1130
+ * signed-in `deepseek-account` route) beside every subscription and relay
1131
+ * route. Undefined when the window is absent (older host) or nothing was used
1132
+ * — a zero-token note mints nothing.
1101
1133
  *
1102
- * Only the priced family contributes `cost`: MiMo ships no price book, so it
1103
- * mints tokens and calls while the spend line keeps reporting the official
1104
- * family's fold-time estimate (and the observed-balance watch above it).
1134
+ * Minting is an accounting decision over ledger rows, so it reads no adapter:
1135
+ * widening it must never widen the credential or balance-probe paths, which
1136
+ * keep reading `adapterFor()` (the split `isDeepSeekProviderRoute()` also
1137
+ * keeps, issue #1772).
1138
+ *
1139
+ * Only the priced family contributes `cost`: subscription and relay routes
1140
+ * ship no price book, so they mint tokens and calls while the spend line keeps
1141
+ * reporting the official family's fold-time estimate (and the observed-balance
1142
+ * watch above it).
1105
1143
  */
1106
1144
  function voucherData(window) {
1107
1145
  if (window === void 0) return void 0;
@@ -1109,7 +1147,6 @@ window.__ModuleLoader__.load({
1109
1147
  let calls = 0;
1110
1148
  let cost = 0;
1111
1149
  for (const row of window.providers) {
1112
- if (!mintsWhaleYuan(row.provider)) continue;
1113
1150
  tokens += totalTokens(row.totals);
1114
1151
  calls += row.totals.calls;
1115
1152
  if (isDeepSeekProviderRoute(row.provider)) cost += row.totals.cost;
@@ -1188,11 +1225,38 @@ window.__ModuleLoader__.load({
1188
1225
  ctx.fillText(`NO.${voucherSerial(face)} ${data.from} - ${data.to}`, centerX, Math.round(art.height * .845));
1189
1226
  }
1190
1227
  //#endregion
1228
+ //#region src/core/spend.ts
1229
+ /**
1230
+ * The rate used when the row configures none: a plain default, so an
1231
+ * unconfigured display still has a defined美元 figure instead of a blank card.
1232
+ * The plugin never fetches a live rate (its host routes are loopback-fenced and
1233
+ * its browser half has no network), so there is no fetch that could fail and
1234
+ * leave the card empty.
1235
+ */
1236
+ const DEFAULT_CNY_PER_USD = 7.2;
1237
+ /** The display used when the row carries no setting: the historical CNY rendering. */
1238
+ const DEFAULT_SPEND_DISPLAY = {
1239
+ currency: "CNY",
1240
+ cnyPerUsd: DEFAULT_CNY_PER_USD
1241
+ };
1242
+ /**
1243
+ * Render one stored CNY cost in the display currency.
1244
+ * A non-finite or negative cost renders as zero rather than as `NaN`/`¥-0.10`.
1245
+ * @param costCny - the stored cost in CNY.
1246
+ * @param display - the display to render in; defaults to CNY.
1247
+ * @returns the formatted amount with its currency symbol.
1248
+ */
1249
+ function formatSpend(costCny, display = DEFAULT_SPEND_DISPLAY) {
1250
+ const safe = Number.isFinite(costCny) && costCny > 0 ? costCny : 0;
1251
+ if (display.currency === "USD") return "$" + (safe / (Number.isFinite(display.cnyPerUsd) && display.cnyPerUsd > 0 ? display.cnyPerUsd : DEFAULT_CNY_PER_USD)).toFixed(2);
1252
+ return "¥" + safe.toFixed(2);
1253
+ }
1254
+ //#endregion
1191
1255
  //#region src/client/UsageSectionCard.tsx
1192
1256
  /**
1193
1257
  * The usage statistics settings section: three tabs (用量: today's usage,
1194
1258
  * balances, trend; 个人套餐: per-provider plan quota windows; Token 银行:
1195
- * the whale-yuan voucher minted from the DeepSeek official family's usage)
1259
+ * the whale-yuan voucher minted from every provider's ledger usage)
1196
1260
  * plus a compact settings row. Data comes from the host's loopback-fenced
1197
1261
  * /api/dsh-usage/overview document; polling runs only while the section is
1198
1262
  * mounted and the tab is visible.
@@ -1223,20 +1287,6 @@ window.__ModuleLoader__.load({
1223
1287
  return "";
1224
1288
  }
1225
1289
  }
1226
- function formatClock$1(ms) {
1227
- try {
1228
- return new Date(ms).toLocaleTimeString([], {
1229
- hour: "2-digit",
1230
- minute: "2-digit"
1231
- });
1232
- } catch {
1233
- return "";
1234
- }
1235
- }
1236
- /** Formatted CNY spend estimate (the only priced currency today). */
1237
- function formatCost(cost) {
1238
- return "¥" + cost.toFixed(2);
1239
- }
1240
1290
  function toneClass(percent) {
1241
1291
  if (percent >= 90) return usage_module_css_default.barLow;
1242
1292
  if (percent >= 70) return usage_module_css_default.barWarn;
@@ -1582,10 +1632,10 @@ window.__ModuleLoader__.load({
1582
1632
  setUsageDay(date === todayDate ? void 0 : date);
1583
1633
  }
1584
1634
  }),
1585
- deepseekVisible && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1586
- className: usage_module_css_default.muted,
1587
- "data-dsh-part": "peak-status",
1588
- children: t(deepseekPeriod.peak ? "usage.peak.on" : "usage.peak.off", { time: formatClock$1(deepseekPeriod.boundaryMs) })
1635
+ deepseekVisible && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PeakBadge, {
1636
+ peak: deepseekPeriod.peak,
1637
+ boundaryMs: deepseekPeriod.boundaryMs,
1638
+ variant: "full"
1589
1639
  }),
1590
1640
  usageDayLoad?.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1591
1641
  className: usage_module_css_default.errorLine,
@@ -1605,7 +1655,7 @@ window.__ModuleLoader__.load({
1605
1655
  children: isToday ? t("usage.today.cost") : t("usage.day.cost")
1606
1656
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1607
1657
  className: usage_module_css_default.providerTokens,
1608
- children: formatCost(day?.totals.cost ?? 0)
1658
+ children: formatSpend(day?.totals.cost ?? 0, snapshot.display)
1609
1659
  })]
1610
1660
  }),
1611
1661
  (day?.providers.length ?? 0) > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -1624,7 +1674,7 @@ window.__ModuleLoader__.load({
1624
1674
  formatTokens(row.totals.inputTokens + row.totals.cacheReadTokens + row.totals.cacheWriteTokens + row.totals.outputTokens),
1625
1675
  " · ",
1626
1676
  t("usage.calls", { n: row.totals.calls }),
1627
- row.totals.cost > 0 ? ` · ${formatCost(row.totals.cost)}` : ""
1677
+ row.totals.cost > 0 ? ` · ${formatSpend(row.totals.cost, snapshot.display)}` : ""
1628
1678
  ]
1629
1679
  })]
1630
1680
  }, row.provider))
@@ -1694,6 +1744,7 @@ window.__ModuleLoader__.load({
1694
1744
  window: bankWindow,
1695
1745
  observedSpend: bankSelection === void 0 ? snapshot.usage.observedSpend : void 0,
1696
1746
  loading: bankSelection !== void 0 && bankDayLoad?.status === "loading",
1747
+ display: snapshot.display,
1697
1748
  ...bankDayLoad?.status === "error" ? { error: bankDayLoad.error ?? "" } : {}
1698
1749
  })] })
1699
1750
  ]
@@ -1799,17 +1850,17 @@ window.__ModuleLoader__.load({
1799
1850
  return totals.inputTokens + totals.cacheReadTokens + totals.cacheWriteTokens + totals.outputTokens;
1800
1851
  }
1801
1852
  /**
1802
- * The Token 银行 card: the mint family's (DeepSeek official + MiMo)
1803
- * retained-ledger usage minted onto the whale-yuan note at 1,000,000 tokens
1804
- * per whale yuan. The window is the whole retained ledger by default (the
1805
- * host's aggregate, falling back to the 30-day trend when an older host serves
1806
- * no `all`) or the single day the user picked; the spend line prefers the
1807
- * official balance watch (whole window only) and falls back to the fold-time
1808
- * estimate, which only the priced family feeds; the artwork draw failure
1809
- * degrades to an error line and never takes the section down.
1853
+ * The Token 银行 card: every provider's retained-ledger usage minted onto the
1854
+ * whale-yuan note at 1,000,000 tokens per whale yuan. The window is the whole
1855
+ * retained ledger by default (the host's aggregate, falling back to the 30-day
1856
+ * trend when an older host serves no `all`) or the single day the user picked;
1857
+ * the spend line prefers the official balance watch (whole window only) and
1858
+ * falls back to the fold-time estimate, which only the priced family feeds;
1859
+ * the artwork draw failure degrades to an error line and never takes the
1860
+ * section down.
1810
1861
  */
1811
1862
  function VoucherCard(props) {
1812
- const { window: ledger, observedSpend, loading, error } = props;
1863
+ const { window: ledger, observedSpend, loading, error, display } = props;
1813
1864
  const data = voucherData(ledger);
1814
1865
  const canvasRef = (0, react.useRef)(null);
1815
1866
  const [drawError, setDrawError] = (0, react.useState)(void 0);
@@ -1909,9 +1960,9 @@ window.__ModuleLoader__.load({
1909
1960
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1910
1961
  className: usage_module_css_default.muted,
1911
1962
  children: observedSpend !== void 0 ? t("usage.bank.spend.observed", {
1912
- cost: observedSpend.cny.toFixed(2),
1963
+ cost: formatSpend(observedSpend.cny, display),
1913
1964
  since: formatDay(observedSpend.since)
1914
- }) : t("usage.bank.spend.estimated", { cost: data.cost.toFixed(2) })
1965
+ }) : t("usage.bank.spend.estimated", { cost: formatSpend(data.cost, display) })
1915
1966
  }),
1916
1967
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1917
1968
  className: usage_module_css_default.muted,
@@ -2029,30 +2080,65 @@ window.__ModuleLoader__.load({
2029
2080
  }),
2030
2081
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2031
2082
  className: usage_module_css_default.settingsGrid,
2032
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2033
- className: usage_module_css_default.settingItem,
2034
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2035
- type: "checkbox",
2036
- checked: value.enabled ?? true,
2037
- disabled,
2038
- onChange: (event) => {
2039
- write("enabled", event.target.checked);
2040
- }
2041
- }), t("usage.config.enabled")]
2042
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2043
- className: usage_module_css_default.settingItem,
2044
- children: [t("usage.config.pollIntervalSec"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2045
- type: "number",
2046
- min: 30,
2047
- max: 3600,
2048
- value: typeof value.pollIntervalSec === "number" ? value.pollIntervalSec : 60,
2049
- disabled,
2050
- onChange: (event) => {
2051
- const parsed = Number(event.target.value);
2052
- if (Number.isFinite(parsed) && parsed >= 30 && parsed <= 3600) write("pollIntervalSec", Math.round(parsed));
2053
- }
2054
- })]
2055
- })]
2083
+ children: [
2084
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2085
+ className: usage_module_css_default.settingItem,
2086
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2087
+ type: "checkbox",
2088
+ checked: value.enabled ?? true,
2089
+ disabled,
2090
+ onChange: (event) => {
2091
+ write("enabled", event.target.checked);
2092
+ }
2093
+ }), t("usage.config.enabled")]
2094
+ }),
2095
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2096
+ className: usage_module_css_default.settingItem,
2097
+ children: [t("usage.config.pollIntervalSec"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2098
+ type: "number",
2099
+ min: 30,
2100
+ max: 3600,
2101
+ value: typeof value.pollIntervalSec === "number" ? value.pollIntervalSec : 60,
2102
+ disabled,
2103
+ onChange: (event) => {
2104
+ const parsed = Number(event.target.value);
2105
+ if (Number.isFinite(parsed) && parsed >= 30 && parsed <= 3600) write("pollIntervalSec", Math.round(parsed));
2106
+ }
2107
+ })]
2108
+ }),
2109
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2110
+ className: usage_module_css_default.settingItem,
2111
+ children: [t("usage.config.spendCurrency"), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2112
+ value: value.spendCurrency === "USD" ? "USD" : "CNY",
2113
+ disabled,
2114
+ onChange: (event) => {
2115
+ write("spendCurrency", event.target.value === "USD" ? "USD" : "CNY");
2116
+ },
2117
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2118
+ value: "CNY",
2119
+ children: "CNY"
2120
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2121
+ value: "USD",
2122
+ children: "USD"
2123
+ })]
2124
+ })]
2125
+ }),
2126
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
2127
+ className: usage_module_css_default.settingItem,
2128
+ children: [t("usage.config.cnyPerUsd"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
2129
+ type: "number",
2130
+ min: .01,
2131
+ max: 1e3,
2132
+ step: .01,
2133
+ value: typeof value.cnyPerUsd === "number" ? value.cnyPerUsd : 7.2,
2134
+ disabled,
2135
+ onChange: (event) => {
2136
+ const parsed = Number(event.target.value);
2137
+ if (Number.isFinite(parsed) && parsed >= .01 && parsed <= 1e3) write("cnyPerUsd", Math.round(parsed * 100) / 100);
2138
+ }
2139
+ })]
2140
+ })
2141
+ ]
2056
2142
  }),
2057
2143
  failure !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2058
2144
  className: usage_module_css_default.errorLine,
@@ -2257,29 +2343,22 @@ window.__ModuleLoader__.load({
2257
2343
  if (currency === "USD") return "$" + balance.totalBalance;
2258
2344
  return currency + " " + balance.totalBalance;
2259
2345
  }
2260
- function formatClock(ms) {
2261
- try {
2262
- return new Date(ms).toLocaleTimeString([], {
2263
- hour: "2-digit",
2264
- minute: "2-digit"
2265
- });
2266
- } catch {
2267
- return "";
2268
- }
2269
- }
2270
2346
  /**
2271
- * Display name of the provider owning today's priced spend: the day's highest
2272
- * cost row, else the current session route, resolved through the provider
2273
- * snapshot (an older host without the day rows falls back to the current
2274
- * route). Undefined when there is no route to name.
2347
+ * Route key owning today's priced spend: the day's highest cost row, else the
2348
+ * current session route (an older host without the day rows falls back to the
2349
+ * current route). Undefined when neither names one.
2275
2350
  */
2276
- function spendProvider(snapshot) {
2351
+ function spendRoute(snapshot) {
2277
2352
  let top;
2278
2353
  for (const row of snapshot.usage.today.providers) {
2279
2354
  if (row.totals.cost <= 0) continue;
2280
2355
  if (top === void 0 || row.totals.cost > top.totals.cost) top = row;
2281
2356
  }
2282
- const route = top?.provider ?? snapshot.current.provider;
2357
+ return top?.provider ?? snapshot.current.provider;
2358
+ }
2359
+ /** Display name of the route spendRoute() names, resolved through the snapshot. */
2360
+ function spendProvider(snapshot) {
2361
+ const route = spendRoute(snapshot);
2283
2362
  if (route === void 0) return void 0;
2284
2363
  return snapshot.providers.find((entry) => entry.provider === route)?.displayName ?? (route === snapshot.current.provider ? snapshot.current.displayName : void 0) ?? route;
2285
2364
  }
@@ -2293,7 +2372,7 @@ window.__ModuleLoader__.load({
2293
2372
  const totals = snapshot.usage.today.totals;
2294
2373
  if (totals.cost > 0) return {
2295
2374
  label: t("usage.foot.cost"),
2296
- value: "¥" + totals.cost.toFixed(2),
2375
+ value: formatSpend(totals.cost, snapshot.display),
2297
2376
  tokenFallback: false,
2298
2377
  provider: spendProvider(snapshot)
2299
2378
  };
@@ -2304,7 +2383,7 @@ window.__ModuleLoader__.load({
2304
2383
  };
2305
2384
  return {
2306
2385
  label: t("usage.foot.cost"),
2307
- value: "¥0.00",
2386
+ value: formatSpend(0, snapshot.display),
2308
2387
  tokenFallback: false
2309
2388
  };
2310
2389
  }
@@ -2414,6 +2493,8 @@ window.__ModuleLoader__.load({
2414
2493
  }
2415
2494
  const head = headline(snapshot);
2416
2495
  const balances = balanceLine(snapshot);
2496
+ const route = spendRoute(snapshot);
2497
+ const period = route !== void 0 && isDeepSeekProviderRoute(route) ? deepseekPeriodAt(Date.now()) : void 0;
2417
2498
  if (collapsed) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2418
2499
  className: cardClass(true),
2419
2500
  "data-dsh-plugin": "usage",
@@ -2434,6 +2515,11 @@ window.__ModuleLoader__.load({
2434
2515
  className: usage_module_css_default.footStripProvider,
2435
2516
  children: head.provider
2436
2517
  }),
2518
+ period !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PeakBadge, {
2519
+ peak: period.peak,
2520
+ boundaryMs: period.boundaryMs,
2521
+ variant: "compact"
2522
+ }),
2437
2523
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2438
2524
  className: usage_module_css_default.footStripLabel,
2439
2525
  children: head.label
@@ -2478,6 +2564,17 @@ window.__ModuleLoader__.load({
2478
2564
  "data-dsh-part": "foot-card-balances",
2479
2565
  children: balances
2480
2566
  }),
2567
+ period !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2568
+ className: usage_module_css_default.footPeak,
2569
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PeakBadge, {
2570
+ peak: period.peak,
2571
+ boundaryMs: period.boundaryMs,
2572
+ variant: "compact"
2573
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2574
+ className: usage_module_css_default.footPeakClock,
2575
+ children: t(period.peak ? "usage.peak.ends" : "usage.peak.starts", { time: formatClock(period.boundaryMs) })
2576
+ })]
2577
+ }),
2481
2578
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2482
2579
  className: usage_module_css_default.footMeta,
2483
2580
  children: t("usage.updated", { time: formatClock(snapshot.updatedAt) })
@@ -2486,45 +2583,130 @@ window.__ModuleLoader__.load({
2486
2583
  }), toggle]
2487
2584
  });
2488
2585
  }
2586
+ /** The shell slot whose occupant is the settings shell (and carries its store). */
2587
+ const SETTINGS_SEAT_SLOT = "sidebar.settings";
2588
+ /** The shell sidebar column, which also scopes the settings seat. */
2589
+ const SIDEBAR_SELECTOR = "[data-pane=\"sidebar\"], [class*=\"sidebarCol\"]";
2590
+ /** The shell's settings row: the launcher seat plus the Settings trigger fallback. */
2591
+ const SETTINGS_AREA_SELECTOR = "[class*=\"settingsArea\"]";
2592
+ /** A launcher that opens the settings dialog by itself (the shell's own gear). */
2593
+ const DIALOG_TRIGGER_SELECTOR = "button[aria-haspopup=\"dialog\"]";
2594
+ /** A launcher that opens a menu whose settings item opens the dialog (the account row). */
2595
+ const MENU_ITEM_SELECTOR = "[role=\"menu\"] button[role=\"menuitem\"]";
2596
+ /** One shell frame of the replay poll: menus and the panel mount a frame later. */
2597
+ const REPLAY_STEP_MS = 50;
2598
+ /** Replay poll budget in steps (about one second of shell frames). */
2599
+ const REPLAY_STEPS = 20;
2600
+ /** The shell sidebar column, when the shell has mounted one. */
2601
+ function sidebarColumn() {
2602
+ return document.querySelector(SIDEBAR_SELECTOR) ?? void 0;
2603
+ }
2489
2604
  /** The shell sidebar's foot area (footer actions + the Settings row), when mounted. */
2490
2605
  function footArea() {
2491
- return document.querySelector("[data-pane=\"sidebar\"], [class*=\"sidebarCol\"]")?.querySelector("[class*=\"footArea\"]") ?? void 0;
2606
+ return sidebarColumn()?.querySelector("[class*=\"footArea\"]") ?? void 0;
2492
2607
  }
2493
2608
  /**
2494
- * Open the settings panel on the usage section. The shell owns the panel's
2495
- * open state and exposes no service for it, so the card replays the user's
2496
- * own path: activate the sidebar Settings trigger, then pick the nav row
2497
- * carrying the section's (localized) label. Every step degrades silently —
2498
- * with the panel already open only the nav pick runs, and a missing row
2499
- * leaves the panel on its default section.
2500
- * @param label - the usage section's nav label in the active locale.
2609
+ * Resolve the settings shell's store handle from the `sidebar.settings` seat.
2610
+ *
2611
+ * The seat is a single-occupant slot and the shell's own registration carries
2612
+ * the store that owns the panel's open state and active section, so reading it
2613
+ * is the one step that opens the panel without touching the DOM. A shell that
2614
+ * exposes no seat, a seat without a store, or a registry answering mid-teardown
2615
+ * all yield undefined, and the caller replays the DOM path instead.
2616
+ * @param slots - the renderer-owned slot registry.
2617
+ * @returns the store handle, or undefined when the seat carries none.
2501
2618
  */
2502
- function openUsageSettings(label) {
2503
- /** Click the nav row carrying the usage label inside the open panel. */
2504
- const pick = (panel) => {
2505
- const wanted = label();
2506
- for (const row of panel.querySelectorAll("nav button")) if (row.textContent !== null && row.textContent.includes(wanted)) {
2507
- row.click();
2508
- return;
2509
- }
2510
- };
2619
+ function settingsShellStore(slots) {
2620
+ let seat;
2621
+ try {
2622
+ seat = slots?.entries(SETTINGS_SEAT_SLOT)[0]?.store;
2623
+ } catch {
2624
+ return;
2625
+ }
2626
+ if (seat === void 0 || seat === null) return void 0;
2627
+ return typeof seat.create === "function" ? seat : void 0;
2628
+ }
2629
+ /**
2630
+ * Drive the shell's settings store onto the usage section.
2631
+ * @param store - the seat's store handle.
2632
+ * @param id - the section's slot registration id.
2633
+ * @returns true when the store accepted the call, false when it threw.
2634
+ */
2635
+ function openThroughStore(store, id) {
2636
+ try {
2637
+ store.create().actions.openSection(id);
2638
+ return true;
2639
+ } catch {
2640
+ return false;
2641
+ }
2642
+ }
2643
+ /**
2644
+ * Click the nav row carrying the section's label inside the open panel.
2645
+ * @param panel - the open settings dialog.
2646
+ * @param label - the section's nav label in the active locale.
2647
+ */
2648
+ function pickSectionRow(panel, label) {
2649
+ const wanted = label();
2650
+ for (const row of panel.querySelectorAll("nav button")) if (row.textContent !== null && row.textContent.includes(wanted)) {
2651
+ row.click();
2652
+ return;
2653
+ }
2654
+ }
2655
+ /**
2656
+ * Poll the page over the shell's next frames until a lookup yields a value.
2657
+ * @param find - the lookup, run once per step.
2658
+ * @param steps - remaining frame budget.
2659
+ * @returns the first value found, or undefined once the budget is spent.
2660
+ */
2661
+ function until(find, steps = REPLAY_STEPS) {
2662
+ const found = find();
2663
+ if (found !== void 0 && found !== null) return Promise.resolve(found);
2664
+ if (steps <= 0) return Promise.resolve(void 0);
2665
+ return new Promise((resolve) => {
2666
+ window.setTimeout(resolve, REPLAY_STEP_MS);
2667
+ }).then(() => until(find, steps - 1));
2668
+ }
2669
+ /**
2670
+ * Replay the user's own settings path, one shell frame at a time.
2671
+ * @param launcher - the settings seat's trigger.
2672
+ * @param label - the section's nav label in the active locale.
2673
+ */
2674
+ async function replay(launcher, label) {
2675
+ launcher.click();
2676
+ if (launcher.getAttribute("aria-haspopup") === "menu") {
2677
+ const item = await until(() => document.querySelector(MENU_ITEM_SELECTOR));
2678
+ if (item === void 0) return;
2679
+ item.click();
2680
+ }
2681
+ const panel = await until(() => document.querySelector("[role=\"dialog\"]"));
2682
+ if (panel !== void 0) pickSectionRow(panel, label);
2683
+ }
2684
+ /**
2685
+ * Open the settings panel on the usage section.
2686
+ *
2687
+ * The seat's store is the primary path: `openSection` opens the dialog on the
2688
+ * usage row in one call, with no dependence on which plugin occupies the
2689
+ * `settings.launcher` seat — an account row there turns a trigger click into a
2690
+ * menu, which is how this click used to land on the account menu instead of the
2691
+ * panel. Without such a store the card replays the user's own path: activate
2692
+ * the launcher (preferring the dialog trigger over a menu trigger), follow a
2693
+ * menu launcher's first settings item, then pick the nav row carrying the
2694
+ * section's label. Every step degrades silently — a missing surface leaves the
2695
+ * page untouched, and with the panel already open only the nav pick runs.
2696
+ * @param target - the section id, its localized label, and the seat's store.
2697
+ */
2698
+ function openUsageSettings(target) {
2699
+ const { id, label, store } = target;
2700
+ if (store !== void 0 && openThroughStore(store, id)) return;
2511
2701
  const open = document.querySelector("[role=\"dialog\"]");
2512
2702
  if (open !== null) {
2513
- pick(open);
2703
+ pickSectionRow(open, label);
2514
2704
  return;
2515
2705
  }
2516
- (document.querySelector("[data-pane=\"sidebar\"], [class*=\"sidebarCol\"]")?.querySelector("[class*=\"settingsArea\"] button"))?.click();
2517
- let tries = 0;
2518
- const attempt = () => {
2519
- const panel = document.querySelector("[role=\"dialog\"]");
2520
- if (panel !== null) {
2521
- pick(panel);
2522
- return;
2523
- }
2524
- tries += 1;
2525
- if (tries <= 20) window.setTimeout(attempt, 50);
2526
- };
2527
- window.setTimeout(attempt, 0);
2706
+ const area = sidebarColumn()?.querySelector(SETTINGS_AREA_SELECTOR);
2707
+ const launcher = area?.querySelector(DIALOG_TRIGGER_SELECTOR) ?? area?.querySelector("button");
2708
+ if (launcher === void 0 || launcher === null) return;
2709
+ replay(launcher, label);
2528
2710
  }
2529
2711
  /**
2530
2712
  * Mount the sidebar foot card directly above the Settings row.
@@ -2541,7 +2723,7 @@ window.__ModuleLoader__.load({
2541
2723
  const place = () => {
2542
2724
  const foot = footArea();
2543
2725
  if (foot === void 0) return;
2544
- const settings = foot.querySelector("[class*=\"settingsArea\"]");
2726
+ const settings = foot.querySelector(SETTINGS_AREA_SELECTOR);
2545
2727
  if (settings !== null) {
2546
2728
  if (container.nextElementSibling !== settings) foot.insertBefore(container, settings);
2547
2729
  return;
@@ -2675,6 +2857,8 @@ window.__ModuleLoader__.load({
2675
2857
  ];
2676
2858
  /** First-level nav position: directly below the Workshop section (order 150). */
2677
2859
  const SECTION_ORDER = 151;
2860
+ /** The section's nav row id — what the shell's store selects when the card opens. */
2861
+ const USAGE_SECTION_ID = "dsh-usage";
2678
2862
  /** Required services. */
2679
2863
  const inject = [
2680
2864
  "slots",
@@ -2742,7 +2926,11 @@ window.__ModuleLoader__.load({
2742
2926
  poll,
2743
2927
  settings: settingsForm,
2744
2928
  onOpen: () => {
2745
- openUsageSettings(() => t("usage.title"));
2929
+ openUsageSettings({
2930
+ id: USAGE_SECTION_ID,
2931
+ label: () => t("usage.title"),
2932
+ store: settingsShellStore(ctx.slots)
2933
+ });
2746
2934
  },
2747
2935
  locale: ctx.locale
2748
2936
  });
@@ -2755,7 +2943,7 @@ window.__ModuleLoader__.load({
2755
2943
  try {
2756
2944
  const unregister = ctx.slots.register({
2757
2945
  name: "settings.section",
2758
- id: "dsh-usage",
2946
+ id: USAGE_SECTION_ID,
2759
2947
  order: SECTION_ORDER,
2760
2948
  label: () => ctx.locale.bind(NS)("usage.title"),
2761
2949
  locale: NS,