@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/README.i18n.yaml +2 -2
- package/README.md +5 -5
- package/README.zh.md +5 -5
- package/lib/client.js +329 -141
- package/lib/client.js.map +1 -1
- package/lib/index.js +75 -7
- package/lib/types/client/PeakBadge.d.ts +22 -0
- package/lib/types/client/UsageSectionCard.d.ts +5 -1
- package/lib/types/client/foot-card-mount.d.ts +52 -8
- package/lib/types/client/locales.d.ts +6 -0
- package/lib/types/client/voucher.d.ts +20 -25
- package/lib/types/core/spend.d.ts +46 -0
- package/lib/types/core/types.d.ts +7 -0
- package/lib/types/host/loopback.d.ts +41 -1
- package/lib/types/host/usage-service.d.ts +7 -0
- package/lib/types/index.d.ts +11 -0
- package/package.json +14 -14
- package/src/client/PeakBadge.tsx +48 -0
- package/src/client/UsageFootCard.tsx +30 -16
- package/src/client/UsageSectionCard.tsx +52 -32
- package/src/client/foot-card-mount.tsx +161 -37
- package/src/client/index.ts +12 -3
- package/src/client/locales.ts +20 -8
- package/src/client/usage.module.css +59 -1
- package/src/client/voucher.ts +20 -30
- package/src/core/spend.ts +62 -0
- package/src/core/types.ts +8 -0
- package/src/host/loopback.ts +57 -1
- package/src/host/pair-access.ts +26 -3
- package/src/host/usage-service.ts +9 -0
- package/src/index.ts +17 -0
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": "
|
|
99
|
-
"usage.bank.noUsage": "
|
|
104
|
+
"usage.bank.hint": "所有模型每消耗 100 万 tokens 铸造 1 鲸元;保存或分享这张票券。",
|
|
105
|
+
"usage.bank.noUsage": "暂无用量数据",
|
|
100
106
|
"usage.bank.minted": "累计铸造 {minted} 鲸元({tokens} tokens)",
|
|
101
|
-
"usage.bank.spend.observed": "官方余额实测花费
|
|
102
|
-
"usage.bank.spend.estimated": "消费估算:约
|
|
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
|
|
165
|
-
"usage.bank.noUsage": "No
|
|
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
|
|
168
|
-
"usage.bank.spend.estimated": "Estimated spend: about
|
|
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:
|
|
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
|
|
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
|
-
*
|
|
1082
|
-
*
|
|
1083
|
-
*
|
|
1084
|
-
*
|
|
1085
|
-
*
|
|
1086
|
-
*
|
|
1087
|
-
*
|
|
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
|
-
*
|
|
1103
|
-
*
|
|
1104
|
-
*
|
|
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
|
|
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)(
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
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:
|
|
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 ? ` · ${
|
|
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:
|
|
1803
|
-
*
|
|
1804
|
-
*
|
|
1805
|
-
* host
|
|
1806
|
-
*
|
|
1807
|
-
*
|
|
1808
|
-
*
|
|
1809
|
-
*
|
|
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
|
|
1963
|
+
cost: formatSpend(observedSpend.cny, display),
|
|
1913
1964
|
since: formatDay(observedSpend.since)
|
|
1914
|
-
}) : t("usage.bank.spend.estimated", { cost: data.cost
|
|
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: [
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
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
|
-
*
|
|
2272
|
-
*
|
|
2273
|
-
*
|
|
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
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
|
2606
|
+
return sidebarColumn()?.querySelector("[class*=\"footArea\"]") ?? void 0;
|
|
2492
2607
|
}
|
|
2493
2608
|
/**
|
|
2494
|
-
*
|
|
2495
|
-
*
|
|
2496
|
-
*
|
|
2497
|
-
*
|
|
2498
|
-
*
|
|
2499
|
-
*
|
|
2500
|
-
*
|
|
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
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
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
|
-
|
|
2703
|
+
pickSectionRow(open, label);
|
|
2514
2704
|
return;
|
|
2515
2705
|
}
|
|
2516
|
-
(
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
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(
|
|
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(
|
|
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:
|
|
2946
|
+
id: USAGE_SECTION_ID,
|
|
2759
2947
|
order: SECTION_ORDER,
|
|
2760
2948
|
label: () => ctx.locale.bind(NS)("usage.title"),
|
|
2761
2949
|
locale: NS,
|