@linxin666/dsh-usage 0.3.23 → 0.4.0
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 +7 -9
- package/README.zh.md +7 -9
- package/icon.svg +3 -0
- package/lib/client.js +621 -65
- package/lib/client.js.map +1 -1
- package/lib/index.js +258 -271
- package/lib/types/client/UsageFootCard.d.ts +55 -0
- package/lib/types/client/UsageSectionCard.d.ts +3 -4
- package/lib/types/client/body-mutations.d.ts +45 -0
- package/lib/types/client/foot-card-mount.d.ts +34 -0
- package/lib/types/client/index.d.ts +21 -8
- package/lib/types/client/locales.d.ts +6 -4
- package/lib/types/core/provider-routes.d.ts +26 -0
- package/lib/types/core/types.d.ts +14 -1
- package/lib/types/host/access.d.ts +17 -0
- package/lib/types/host/http.d.ts +18 -1
- package/lib/types/host/pair-access.d.ts +34 -0
- package/lib/types/host/routes.d.ts +16 -7
- package/lib/types/host/usage-service.d.ts +32 -76
- package/lib/types/index.d.ts +14 -2
- package/package.json +20 -17
- package/src/client/UsageFootCard.tsx +300 -0
- package/src/client/UsageSectionCard.tsx +41 -23
- package/src/client/body-mutations.ts +133 -0
- package/src/client/foot-card-mount.tsx +112 -0
- package/src/client/index.ts +57 -16
- package/src/client/locales.ts +12 -8
- package/src/client/usage.module.css +188 -0
- package/src/core/adapters.ts +40 -6
- package/src/core/provider-routes.ts +37 -0
- package/src/core/types.ts +14 -1
- package/src/host/access.ts +21 -0
- package/src/host/http.ts +23 -1
- package/src/host/pair-access.ts +49 -0
- package/src/host/routes.ts +19 -10
- package/src/host/usage-service.ts +74 -218
- package/src/index.ts +59 -60
package/lib/client.js
CHANGED
|
@@ -6,6 +6,7 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
8
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
+
let react_dom_client = require("react-dom/client");
|
|
9
10
|
//#region \0dsh-store-engine
|
|
10
11
|
const platform = ["@deepseek-ai/dsh-client", "-store"].join("");
|
|
11
12
|
const legacy = ["@deepseek-ai/dsh-client-runtime", "/client"].join("");
|
|
@@ -97,10 +98,7 @@ window.__ModuleLoader__.load({
|
|
|
97
98
|
"usage.config.title": "设置",
|
|
98
99
|
"usage.config.enabled": "启用插件",
|
|
99
100
|
"usage.config.pollIntervalSec": "轮询间隔(秒)",
|
|
100
|
-
"usage.config.
|
|
101
|
-
"usage.config.bubbleMode.always": "常驻显示",
|
|
102
|
-
"usage.config.bubbleMode.change": "仅变化时",
|
|
103
|
-
"usage.config.bubbleMode.off": "关闭",
|
|
101
|
+
"usage.config.saveFailed": "保存失败:设置未被接受。",
|
|
104
102
|
"usage.bank.title": "鲸元券",
|
|
105
103
|
"usage.bank.hint": "官方 API 每消耗 100 万 tokens 铸造 1 鲸元;保存或分享这张票券。",
|
|
106
104
|
"usage.bank.noUsage": "暂无 DeepSeek 官方用量数据(统计自插件启用起)",
|
|
@@ -110,7 +108,12 @@ window.__ModuleLoader__.load({
|
|
|
110
108
|
"usage.bank.window": "统计窗口 {from} ~ {to}",
|
|
111
109
|
"usage.bank.save": "保存图片",
|
|
112
110
|
"usage.bank.share": "分享",
|
|
113
|
-
"usage.bank.drawError": "票券生成失败:{error}"
|
|
111
|
+
"usage.bank.drawError": "票券生成失败:{error}",
|
|
112
|
+
"usage.foot.cost": "今日消费",
|
|
113
|
+
"usage.foot.noData": "今日暂无用量",
|
|
114
|
+
"usage.foot.open": "打开使用统计设置",
|
|
115
|
+
"usage.foot.collapse": "收起用量卡片",
|
|
116
|
+
"usage.foot.expand": "展开用量卡片"
|
|
114
117
|
};
|
|
115
118
|
/** English mirror; every zh key present. */
|
|
116
119
|
const en = {
|
|
@@ -153,10 +156,7 @@ window.__ModuleLoader__.load({
|
|
|
153
156
|
"usage.config.title": "Settings",
|
|
154
157
|
"usage.config.enabled": "Enable plugin",
|
|
155
158
|
"usage.config.pollIntervalSec": "Poll interval (seconds)",
|
|
156
|
-
"usage.config.
|
|
157
|
-
"usage.config.bubbleMode.always": "Always visible",
|
|
158
|
-
"usage.config.bubbleMode.change": "On change",
|
|
159
|
-
"usage.config.bubbleMode.off": "Off",
|
|
159
|
+
"usage.config.saveFailed": "Save failed: the deployment did not accept the setting.",
|
|
160
160
|
"usage.bank.title": "Whale-yuan voucher",
|
|
161
161
|
"usage.bank.hint": "Every 1,000,000 tokens spent on the official API mint one whale yuan; save or share the note.",
|
|
162
162
|
"usage.bank.noUsage": "No official DeepSeek usage yet (counting starts when the plugin is enabled)",
|
|
@@ -166,7 +166,12 @@ window.__ModuleLoader__.load({
|
|
|
166
166
|
"usage.bank.window": "Window {from} - {to}",
|
|
167
167
|
"usage.bank.save": "Save image",
|
|
168
168
|
"usage.bank.share": "Share",
|
|
169
|
-
"usage.bank.drawError": "Failed to render the voucher: {error}"
|
|
169
|
+
"usage.bank.drawError": "Failed to render the voucher: {error}",
|
|
170
|
+
"usage.foot.cost": "Today spend",
|
|
171
|
+
"usage.foot.noData": "No usage yet today",
|
|
172
|
+
"usage.foot.open": "Open usage statistics settings",
|
|
173
|
+
"usage.foot.collapse": "Collapse the usage card",
|
|
174
|
+
"usage.foot.expand": "Expand the usage card"
|
|
170
175
|
};
|
|
171
176
|
/**
|
|
172
177
|
* Active dictionary, picked by the document language at call time. The
|
|
@@ -184,7 +189,7 @@ window.__ModuleLoader__.load({
|
|
|
184
189
|
}
|
|
185
190
|
//#endregion
|
|
186
191
|
//#region \0dsh-css:packages/dsh-usage/src/client/usage.module.css.mjs
|
|
187
|
-
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_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);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}";
|
|
192
|
+
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_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);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}";
|
|
188
193
|
const tagId = "@linxin666/dsh-usage/packages/dsh-usage/src/client/usage.module.css";
|
|
189
194
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
190
195
|
const tag = document.createElement("style");
|
|
@@ -214,6 +219,19 @@ window.__ModuleLoader__.load({
|
|
|
214
219
|
"currentBadge": "cvtkAW_currentBadge",
|
|
215
220
|
"currentProvider": "cvtkAW_currentProvider",
|
|
216
221
|
"errorLine": "cvtkAW_errorLine",
|
|
222
|
+
"footCard": "cvtkAW_footCard",
|
|
223
|
+
"footCardCollapsed": "cvtkAW_footCardCollapsed",
|
|
224
|
+
"footHead": "cvtkAW_footHead",
|
|
225
|
+
"footLine": "cvtkAW_footLine",
|
|
226
|
+
"footMain": "cvtkAW_footMain",
|
|
227
|
+
"footMeta": "cvtkAW_footMeta",
|
|
228
|
+
"footStrip": "cvtkAW_footStrip",
|
|
229
|
+
"footStripLabel": "cvtkAW_footStripLabel",
|
|
230
|
+
"footStripProvider": "cvtkAW_footStripProvider",
|
|
231
|
+
"footStripValue": "cvtkAW_footStripValue",
|
|
232
|
+
"footTitle": "cvtkAW_footTitle",
|
|
233
|
+
"footToggle": "cvtkAW_footToggle",
|
|
234
|
+
"footValue": "cvtkAW_footValue",
|
|
217
235
|
"header": "cvtkAW_header",
|
|
218
236
|
"muted": "cvtkAW_muted",
|
|
219
237
|
"planCard": "cvtkAW_planCard",
|
|
@@ -254,6 +272,10 @@ window.__ModuleLoader__.load({
|
|
|
254
272
|
function str(value) {
|
|
255
273
|
return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
|
|
256
274
|
}
|
|
275
|
+
/** Clamp a provider percentage into the renderable 0-100 band. */
|
|
276
|
+
function clampPercent(value) {
|
|
277
|
+
return Math.max(0, Math.min(100, value));
|
|
278
|
+
}
|
|
257
279
|
/** Format a number to a fixed 2-decimal display string. */
|
|
258
280
|
function money(value) {
|
|
259
281
|
return value.toFixed(2);
|
|
@@ -386,7 +408,28 @@ window.__ModuleLoader__.load({
|
|
|
386
408
|
}
|
|
387
409
|
}
|
|
388
410
|
};
|
|
389
|
-
/**
|
|
411
|
+
/**
|
|
412
|
+
* GLM Coding Plan quota windows, keyed by the provider's own `unit` code
|
|
413
|
+
* rather than by row position so an added or reordered limit cannot shift a
|
|
414
|
+
* window's meaning: 3 = the rolling 5-hour window, 6 = the weekly window,
|
|
415
|
+
* 5 = the monthly one. Two row kinds carry quota — `TOKENS_LIMIT` (token
|
|
416
|
+
* plans) and `CREDIT_LIMIT` (credit plans, whose exact ratio is
|
|
417
|
+
* `currentValue` / `usage`) — while `TIME_LIMIT` is the MCP request cap and
|
|
418
|
+
* is not a plan window at all. Auth is the RAW key without a Bearer prefix.
|
|
419
|
+
*/
|
|
420
|
+
const GLM_UNIT_KEYS = {
|
|
421
|
+
3: "5h",
|
|
422
|
+
5: "month",
|
|
423
|
+
6: "week"
|
|
424
|
+
};
|
|
425
|
+
/** Percent from a credit row's exact ratio, else the provider's own percentage. */
|
|
426
|
+
function glmCreditPercent(row) {
|
|
427
|
+
const used = toNum(row.currentValue);
|
|
428
|
+
const limit = toNum(row.usage);
|
|
429
|
+
if (used !== void 0 && limit !== void 0 && limit > 0) return clampPercent(used / limit * 100);
|
|
430
|
+
const percentage = toNum(row.percentage);
|
|
431
|
+
return percentage === void 0 ? void 0 : clampPercent(percentage);
|
|
432
|
+
}
|
|
390
433
|
function glmPlan(host, ids) {
|
|
391
434
|
return {
|
|
392
435
|
ids,
|
|
@@ -411,11 +454,17 @@ window.__ModuleLoader__.load({
|
|
|
411
454
|
for (const entry of limits) {
|
|
412
455
|
if (typeof entry !== "object" || entry === null) continue;
|
|
413
456
|
const row = entry;
|
|
414
|
-
const
|
|
415
|
-
|
|
457
|
+
const kind = str(row.type);
|
|
458
|
+
if (kind === "TIME_LIMIT") continue;
|
|
459
|
+
const key = GLM_UNIT_KEYS[toNum(row.unit) ?? NaN];
|
|
460
|
+
if (key === void 0) continue;
|
|
461
|
+
if (kind !== "TOKENS_LIMIT" && kind !== "CREDIT_LIMIT") continue;
|
|
416
462
|
windows.push({
|
|
417
|
-
key
|
|
418
|
-
percent:
|
|
463
|
+
key,
|
|
464
|
+
percent: kind === "CREDIT_LIMIT" ? glmCreditPercent(row) : (() => {
|
|
465
|
+
const percentage = toNum(row.percentage);
|
|
466
|
+
return percentage === void 0 ? void 0 : clampPercent(percentage);
|
|
467
|
+
})(),
|
|
419
468
|
resetsAt: toIso(row.nextResetTime)
|
|
420
469
|
});
|
|
421
470
|
}
|
|
@@ -629,7 +678,7 @@ window.__ModuleLoader__.load({
|
|
|
629
678
|
moonshotBalance("api.moonshot.ai", "USD", ["moonshotai"]),
|
|
630
679
|
KIMI_CODING,
|
|
631
680
|
glmPlan("open.bigmodel.cn", ["zai-coding-cn"]),
|
|
632
|
-
glmPlan("api.z.ai", ["zai-coding"]),
|
|
681
|
+
glmPlan("api.z.ai", ["zai", "zai-coding"]),
|
|
633
682
|
OPENCODE_GO,
|
|
634
683
|
minimaxPlan("api.minimaxi.com", ["minimax-cn"]),
|
|
635
684
|
minimaxPlan("api.minimax.io", ["minimax"]),
|
|
@@ -862,7 +911,7 @@ window.__ModuleLoader__.load({
|
|
|
862
911
|
return "";
|
|
863
912
|
}
|
|
864
913
|
}
|
|
865
|
-
function formatClock(ms) {
|
|
914
|
+
function formatClock$1(ms) {
|
|
866
915
|
try {
|
|
867
916
|
return new Date(ms).toLocaleTimeString([], {
|
|
868
917
|
hour: "2-digit",
|
|
@@ -953,7 +1002,7 @@ window.__ModuleLoader__.load({
|
|
|
953
1002
|
]
|
|
954
1003
|
});
|
|
955
1004
|
}
|
|
956
|
-
function balanceLine(provider) {
|
|
1005
|
+
function balanceLine$1(provider) {
|
|
957
1006
|
if (provider.balance !== void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
958
1007
|
className: usage_module_css_default.providerBalance,
|
|
959
1008
|
children: [
|
|
@@ -989,7 +1038,7 @@ window.__ModuleLoader__.load({
|
|
|
989
1038
|
})]
|
|
990
1039
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
991
1040
|
className: usage_module_css_default.providerTokens,
|
|
992
|
-
children: balanceLine(provider)
|
|
1041
|
+
children: balanceLine$1(provider)
|
|
993
1042
|
})]
|
|
994
1043
|
});
|
|
995
1044
|
}
|
|
@@ -1125,7 +1174,7 @@ window.__ModuleLoader__.load({
|
|
|
1125
1174
|
deepseekVisible && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1126
1175
|
className: usage_module_css_default.muted,
|
|
1127
1176
|
"data-dsh-part": "peak-status",
|
|
1128
|
-
children: t(deepseekPeriod.peak ? "usage.peak.on" : "usage.peak.off", { time: formatClock(deepseekPeriod.boundaryMs) })
|
|
1177
|
+
children: t(deepseekPeriod.peak ? "usage.peak.on" : "usage.peak.off", { time: formatClock$1(deepseekPeriod.boundaryMs) })
|
|
1129
1178
|
}),
|
|
1130
1179
|
snapshot.usage.today.totals.calls === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1131
1180
|
className: usage_module_css_default.muted,
|
|
@@ -1507,35 +1556,52 @@ window.__ModuleLoader__.load({
|
|
|
1507
1556
|
]
|
|
1508
1557
|
});
|
|
1509
1558
|
}
|
|
1559
|
+
/**
|
|
1560
|
+
* The compact settings row. Both controls write through the shared form the
|
|
1561
|
+
* moment the user changes them, and the Host answers each write with a
|
|
1562
|
+
* boolean: a refused (or transport-failed) write is surfaced as a failed save,
|
|
1563
|
+
* because a value that did not land must never read as applied.
|
|
1564
|
+
*/
|
|
1510
1565
|
function SettingsRow(props) {
|
|
1511
1566
|
const { settings, snapshot, value } = props;
|
|
1512
1567
|
const disabled = snapshot === void 0 || !snapshot.writable;
|
|
1513
|
-
const
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1568
|
+
const [failure, setFailure] = (0, react.useState)(void 0);
|
|
1569
|
+
const write = (field, next) => {
|
|
1570
|
+
setFailure(void 0);
|
|
1571
|
+
let answer;
|
|
1572
|
+
try {
|
|
1573
|
+
answer = settings.set(field, next);
|
|
1574
|
+
} catch (error) {
|
|
1575
|
+
setFailure(error instanceof Error ? error.message : String(error));
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
Promise.resolve(answer).then((accepted) => {
|
|
1579
|
+
if (!accepted) setFailure("");
|
|
1580
|
+
}, (error) => {
|
|
1581
|
+
setFailure(error instanceof Error ? error.message : String(error));
|
|
1582
|
+
});
|
|
1583
|
+
};
|
|
1518
1584
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1519
1585
|
className: usage_module_css_default.card,
|
|
1520
1586
|
"data-dsh-part": "settings-row",
|
|
1521
|
-
children: [
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1587
|
+
children: [
|
|
1588
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1589
|
+
className: usage_module_css_default.cardTitle,
|
|
1590
|
+
children: t("usage.config.title")
|
|
1591
|
+
}),
|
|
1592
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1593
|
+
className: usage_module_css_default.settingsGrid,
|
|
1594
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1528
1595
|
className: usage_module_css_default.settingItem,
|
|
1529
1596
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1530
1597
|
type: "checkbox",
|
|
1531
1598
|
checked: value.enabled ?? true,
|
|
1532
1599
|
disabled,
|
|
1533
1600
|
onChange: (event) => {
|
|
1534
|
-
|
|
1601
|
+
write("enabled", event.target.checked);
|
|
1535
1602
|
}
|
|
1536
1603
|
}), t("usage.config.enabled")]
|
|
1537
|
-
}),
|
|
1538
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1604
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1539
1605
|
className: usage_module_css_default.settingItem,
|
|
1540
1606
|
children: [t("usage.config.pollIntervalSec"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1541
1607
|
type: "number",
|
|
@@ -1545,38 +1611,513 @@ window.__ModuleLoader__.load({
|
|
|
1545
1611
|
disabled,
|
|
1546
1612
|
onChange: (event) => {
|
|
1547
1613
|
const parsed = Number(event.target.value);
|
|
1548
|
-
if (Number.isFinite(parsed) && parsed >= 30 && parsed <= 3600)
|
|
1614
|
+
if (Number.isFinite(parsed) && parsed >= 30 && parsed <= 3600) write("pollIntervalSec", Math.round(parsed));
|
|
1549
1615
|
}
|
|
1550
1616
|
})]
|
|
1551
|
-
})
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1617
|
+
})]
|
|
1618
|
+
}),
|
|
1619
|
+
failure !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1620
|
+
className: usage_module_css_default.errorLine,
|
|
1621
|
+
role: "status",
|
|
1622
|
+
children: [t("usage.config.saveFailed"), failure === "" ? "" : " - " + failure]
|
|
1623
|
+
})
|
|
1624
|
+
]
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
//#endregion
|
|
1628
|
+
//#region src/client/body-mutations.ts
|
|
1629
|
+
/** Cross-bundle registry key; `Symbol.for` so every module copy agrees. */
|
|
1630
|
+
const HUB_KEY = Symbol.for("dsh-web.body-mutation-hub");
|
|
1631
|
+
const INVALIDATION_ONLY = Symbol.for("dsh-web.body-mutation-invalidation");
|
|
1632
|
+
function needsRecords(subscribers) {
|
|
1633
|
+
for (const listener of subscribers) if (!listener[INVALIDATION_ONLY]) return true;
|
|
1634
|
+
return false;
|
|
1635
|
+
}
|
|
1636
|
+
/**
|
|
1637
|
+
* Subscribe to a coalesced DOM re-check without retaining mutation records.
|
|
1638
|
+
* The marked wrapper also works with an older hub, which delivers records
|
|
1639
|
+
* that it simply ignores until a page reload picks up the updated hub.
|
|
1640
|
+
*/
|
|
1641
|
+
function subscribeBodyInvalidations(subscriber) {
|
|
1642
|
+
const listener = () => {
|
|
1643
|
+
subscriber();
|
|
1644
|
+
};
|
|
1645
|
+
listener[INVALIDATION_ONLY] = true;
|
|
1646
|
+
return subscribeBodyMutations(listener);
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* Subscribe to body-level childList mutations.
|
|
1650
|
+
* @param subscriber - called at most once per animation frame with the records
|
|
1651
|
+
* collected since the previous flush; must be safe to run repeatedly.
|
|
1652
|
+
* @returns the disposer removing this subscriber (and the observer when it was
|
|
1653
|
+
* the last one).
|
|
1654
|
+
*/
|
|
1655
|
+
function subscribeBodyMutations(subscriber) {
|
|
1656
|
+
if (typeof globalThis === "undefined" || typeof document === "undefined") return () => {};
|
|
1657
|
+
if (typeof MutationObserver !== "function") return () => {};
|
|
1658
|
+
const registry = globalThis;
|
|
1659
|
+
let hub = registry[HUB_KEY];
|
|
1660
|
+
if (hub === void 0) {
|
|
1661
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
1662
|
+
const created = {
|
|
1663
|
+
observer: void 0,
|
|
1664
|
+
subscribers,
|
|
1665
|
+
pending: [],
|
|
1666
|
+
scheduled: false
|
|
1667
|
+
};
|
|
1668
|
+
const flush = () => {
|
|
1669
|
+
created.frame = void 0;
|
|
1670
|
+
created.scheduled = false;
|
|
1671
|
+
const batch = created.pending;
|
|
1672
|
+
created.pending = [];
|
|
1673
|
+
for (const listener of [...subscribers]) {
|
|
1674
|
+
if (!subscribers.has(listener)) continue;
|
|
1675
|
+
try {
|
|
1676
|
+
listener(batch);
|
|
1677
|
+
} catch {}
|
|
1678
|
+
}
|
|
1679
|
+
};
|
|
1680
|
+
const schedule = () => {
|
|
1681
|
+
if (created.scheduled) return;
|
|
1682
|
+
created.scheduled = true;
|
|
1683
|
+
if (typeof requestAnimationFrame === "function") created.frame = requestAnimationFrame(flush);
|
|
1684
|
+
else flush();
|
|
1685
|
+
};
|
|
1686
|
+
created.observer = new MutationObserver((records) => {
|
|
1687
|
+
if (needsRecords(subscribers)) for (const record of records) created.pending.push(record);
|
|
1688
|
+
schedule();
|
|
1689
|
+
});
|
|
1690
|
+
created.observer.observe(document.body ?? document.documentElement, {
|
|
1691
|
+
childList: true,
|
|
1692
|
+
subtree: true
|
|
1693
|
+
});
|
|
1694
|
+
registry[HUB_KEY] = created;
|
|
1695
|
+
hub = created;
|
|
1696
|
+
}
|
|
1697
|
+
const active = hub;
|
|
1698
|
+
active.subscribers.add(subscriber);
|
|
1699
|
+
let subscribed = true;
|
|
1700
|
+
return () => {
|
|
1701
|
+
if (!subscribed) return;
|
|
1702
|
+
subscribed = false;
|
|
1703
|
+
active.subscribers.delete(subscriber);
|
|
1704
|
+
if (!needsRecords(active.subscribers)) active.pending = [];
|
|
1705
|
+
if (active.subscribers.size === 0 && registry[HUB_KEY] === active) {
|
|
1706
|
+
active.observer.disconnect();
|
|
1707
|
+
if (active.frame !== void 0 && typeof cancelAnimationFrame === "function") cancelAnimationFrame(active.frame);
|
|
1708
|
+
active.frame = void 0;
|
|
1709
|
+
active.pending = [];
|
|
1710
|
+
active.scheduled = false;
|
|
1711
|
+
delete registry[HUB_KEY];
|
|
1712
|
+
}
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
//#endregion
|
|
1716
|
+
//#region src/client/UsageFootCard.tsx
|
|
1717
|
+
/**
|
|
1718
|
+
* Sidebar foot card: the compact usage glance seated below the shell sidebar's
|
|
1719
|
+
* Settings row. It is deliberately a GLANCE, not the panel the 2026-09-18
|
|
1720
|
+
* removal took out. Two states, persisted across reloads:
|
|
1721
|
+
*
|
|
1722
|
+
* - expanded (default): a price-first headline (today's estimated spend,
|
|
1723
|
+
* falling back to today's tokens when nothing priced was recorded), a
|
|
1724
|
+
* tokens/calls line, up to two configured-provider balances, and an
|
|
1725
|
+
* updated-at footer;
|
|
1726
|
+
* - collapsed: a one-line strip with the gauge glyph, the spending
|
|
1727
|
+
* provider's name, the label and the headline value.
|
|
1728
|
+
*
|
|
1729
|
+
* The card body is one button that opens the settings panel on the usage
|
|
1730
|
+
* section, so detail lives in exactly one place; the corner chevron toggles
|
|
1731
|
+
* the collapse state (sibling buttons — buttons inside a button are invalid
|
|
1732
|
+
* HTML).
|
|
1733
|
+
*
|
|
1734
|
+
* Data comes from the same shared store the settings section reads; the card
|
|
1735
|
+
* runs its own relaxed poll loop (30 s, visible-tab only) because the sidebar
|
|
1736
|
+
* foot is permanently mounted. The card disappears while the plugin is
|
|
1737
|
+
* disabled (settings flag) or while the host serves no usage routes (a 404
|
|
1738
|
+
* means the host half is off), matching the section's own gating.
|
|
1739
|
+
* @module @linxin666/dsh-usage/client/UsageFootCard
|
|
1740
|
+
*/
|
|
1741
|
+
/** Poll cadence of the permanently seated card; the section's 10 s loop stays its own. */
|
|
1742
|
+
const FOOT_CARD_POLL_MS = 3e4;
|
|
1743
|
+
/** localStorage key holding the collapsed flag ('1' = collapsed strip). */
|
|
1744
|
+
const FOOT_CARD_COLLAPSED_KEY = "dsh-usage.foot-card.collapsed";
|
|
1745
|
+
/** At most this many provider balances render before a +N overflow marker. */
|
|
1746
|
+
const BALANCE_CAP = 2;
|
|
1747
|
+
/** Read the persisted collapsed flag (absent = expanded). */
|
|
1748
|
+
function readFootCardCollapsed() {
|
|
1749
|
+
try {
|
|
1750
|
+
return window.localStorage.getItem(FOOT_CARD_COLLAPSED_KEY) === "1";
|
|
1751
|
+
} catch {
|
|
1752
|
+
return false;
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
/** Persist the collapsed flag; storage failures keep the session state. */
|
|
1756
|
+
function writeFootCardCollapsed(collapsed) {
|
|
1757
|
+
try {
|
|
1758
|
+
window.localStorage.setItem(FOOT_CARD_COLLAPSED_KEY, collapsed ? "1" : "0");
|
|
1759
|
+
} catch {}
|
|
1760
|
+
}
|
|
1761
|
+
/** Inline gauge glyph, 14px beside the card title. */
|
|
1762
|
+
function GaugeIcon() {
|
|
1763
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
|
|
1764
|
+
viewBox: "0 0 16 16",
|
|
1765
|
+
width: "14",
|
|
1766
|
+
height: "14",
|
|
1767
|
+
fill: "none",
|
|
1768
|
+
stroke: "currentColor",
|
|
1769
|
+
strokeWidth: "1.3",
|
|
1770
|
+
strokeLinecap: "round",
|
|
1771
|
+
strokeLinejoin: "round",
|
|
1772
|
+
"aria-hidden": "true",
|
|
1773
|
+
children: [
|
|
1774
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M2.5 12.5a7 7 0 0 1 11 -4.3" }),
|
|
1775
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 12.5V8.2l2.9-2.1" }),
|
|
1776
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
|
|
1777
|
+
cx: "8",
|
|
1778
|
+
cy: "12.5",
|
|
1779
|
+
r: "1"
|
|
1780
|
+
})
|
|
1781
|
+
]
|
|
1782
|
+
});
|
|
1783
|
+
}
|
|
1784
|
+
/** Disclosure chevrons: down collapses the expanded card, up expands the strip. */
|
|
1785
|
+
function ChevronIcon(props) {
|
|
1786
|
+
return props.collapsed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
1787
|
+
viewBox: "0 0 16 16",
|
|
1788
|
+
width: "12",
|
|
1789
|
+
height: "12",
|
|
1790
|
+
fill: "none",
|
|
1791
|
+
stroke: "currentColor",
|
|
1792
|
+
strokeWidth: "1.4",
|
|
1793
|
+
strokeLinecap: "round",
|
|
1794
|
+
strokeLinejoin: "round",
|
|
1795
|
+
"aria-hidden": "true",
|
|
1796
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m4 10 4-4 4 4" })
|
|
1797
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
1798
|
+
viewBox: "0 0 16 16",
|
|
1799
|
+
width: "12",
|
|
1800
|
+
height: "12",
|
|
1801
|
+
fill: "none",
|
|
1802
|
+
stroke: "currentColor",
|
|
1803
|
+
strokeWidth: "1.4",
|
|
1804
|
+
strokeLinecap: "round",
|
|
1805
|
+
strokeLinejoin: "round",
|
|
1806
|
+
"aria-hidden": "true",
|
|
1807
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m4 6 4 4 4-4" })
|
|
1808
|
+
});
|
|
1809
|
+
}
|
|
1810
|
+
/** A provider row worth showing a balance for: configured, with a probed balance. */
|
|
1811
|
+
function balanceRow(provider) {
|
|
1812
|
+
const supported = provider.balanceSupported === true || provider.balanceSupported === void 0 && provider.supported;
|
|
1813
|
+
return provider.credential !== "none" && supported && provider.balance !== void 0;
|
|
1814
|
+
}
|
|
1815
|
+
/** Currency-symbol prefix for the priced balances (CNY/USD), else the ISO code. */
|
|
1816
|
+
function formatBalance(balance) {
|
|
1817
|
+
const currency = balance.currency.toUpperCase();
|
|
1818
|
+
if (currency === "CNY") return "¥" + balance.totalBalance;
|
|
1819
|
+
if (currency === "USD") return "$" + balance.totalBalance;
|
|
1820
|
+
return currency + " " + balance.totalBalance;
|
|
1821
|
+
}
|
|
1822
|
+
function formatClock(ms) {
|
|
1823
|
+
try {
|
|
1824
|
+
return new Date(ms).toLocaleTimeString([], {
|
|
1825
|
+
hour: "2-digit",
|
|
1826
|
+
minute: "2-digit"
|
|
1827
|
+
});
|
|
1828
|
+
} catch {
|
|
1829
|
+
return "";
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
/**
|
|
1833
|
+
* Display name of the provider owning today's priced spend: the day's highest
|
|
1834
|
+
* cost row, else the current session route, resolved through the provider
|
|
1835
|
+
* snapshot (an older host without the day rows falls back to the current
|
|
1836
|
+
* route). Undefined when there is no route to name.
|
|
1837
|
+
*/
|
|
1838
|
+
function spendProvider(snapshot) {
|
|
1839
|
+
let top;
|
|
1840
|
+
for (const row of snapshot.usage.today.providers) {
|
|
1841
|
+
if (row.totals.cost <= 0) continue;
|
|
1842
|
+
if (top === void 0 || row.totals.cost > top.totals.cost) top = row;
|
|
1843
|
+
}
|
|
1844
|
+
const route = top?.provider ?? snapshot.current.provider;
|
|
1845
|
+
if (route === void 0) return void 0;
|
|
1846
|
+
return snapshot.providers.find((entry) => entry.provider === route)?.displayName ?? (route === snapshot.current.provider ? snapshot.current.displayName : void 0) ?? route;
|
|
1847
|
+
}
|
|
1848
|
+
/**
|
|
1849
|
+
* The headline triple: today's priced spend, else today's tokens, else zero.
|
|
1850
|
+
* tokenFallback tells the sub-line to keep only the call count instead of
|
|
1851
|
+
* repeating the total the headline already shows; provider names the spender
|
|
1852
|
+
* beside the collapsed cost headline.
|
|
1853
|
+
*/
|
|
1854
|
+
function headline(snapshot) {
|
|
1855
|
+
const totals = snapshot.usage.today.totals;
|
|
1856
|
+
if (totals.cost > 0) return {
|
|
1857
|
+
label: t("usage.foot.cost"),
|
|
1858
|
+
value: "¥" + totals.cost.toFixed(2),
|
|
1859
|
+
tokenFallback: false,
|
|
1860
|
+
provider: spendProvider(snapshot)
|
|
1861
|
+
};
|
|
1862
|
+
if (totals.calls > 0) return {
|
|
1863
|
+
label: t("usage.today"),
|
|
1864
|
+
value: formatTokens(totalTokens(totals)) + " tokens",
|
|
1865
|
+
tokenFallback: true
|
|
1866
|
+
};
|
|
1867
|
+
return {
|
|
1868
|
+
label: t("usage.foot.cost"),
|
|
1869
|
+
value: "¥0.00",
|
|
1870
|
+
tokenFallback: false
|
|
1871
|
+
};
|
|
1872
|
+
}
|
|
1873
|
+
/** The tokens/calls sub-line under the headline; a quiet no-data line on an empty day. */
|
|
1874
|
+
function usageLine(snapshot, tokenFallback) {
|
|
1875
|
+
const totals = snapshot.usage.today.totals;
|
|
1876
|
+
if (totals.calls === 0) return t("usage.foot.noData");
|
|
1877
|
+
const calls = t("usage.calls", { n: totals.calls });
|
|
1878
|
+
return tokenFallback ? calls : formatTokens(totalTokens(totals)) + " tokens · " + calls;
|
|
1879
|
+
}
|
|
1880
|
+
/** The balance line: up to BALANCE_CAP providers plus a +N overflow marker. */
|
|
1881
|
+
function balanceLine(snapshot) {
|
|
1882
|
+
const rows = snapshot.providers.filter(balanceRow);
|
|
1883
|
+
if (rows.length === 0) return void 0;
|
|
1884
|
+
return rows.slice(0, BALANCE_CAP).map((provider) => provider.displayName + " " + formatBalance(provider.balance)).join(" · ") + (rows.length > BALANCE_CAP ? " +" + String(rows.length - BALANCE_CAP) : "");
|
|
1885
|
+
}
|
|
1886
|
+
/** Card chrome classes for the current collapse state. */
|
|
1887
|
+
function cardClass(collapsed) {
|
|
1888
|
+
return collapsed ? usage_module_css_default.footCard + " " + usage_module_css_default.footCardCollapsed : usage_module_css_default.footCard;
|
|
1889
|
+
}
|
|
1890
|
+
/**
|
|
1891
|
+
* Render the foot card content for the current store snapshot.
|
|
1892
|
+
* @param props - store, poller, settings form and open callback from the mount.
|
|
1893
|
+
* @returns the card, or null while disabled / host-off.
|
|
1894
|
+
*/
|
|
1895
|
+
function UsageFootCard(props) {
|
|
1896
|
+
const { store, poll, onOpen, settings, locale } = props;
|
|
1897
|
+
const ui = (0, react.useSyncExternalStore)(store.subscribe, store.getSnapshot);
|
|
1898
|
+
const [, bump] = (0, react.useState)(0);
|
|
1899
|
+
(0, react.useEffect)(() => settings.subscribe(() => bump((count) => count + 1)), [settings]);
|
|
1900
|
+
(0, react.useEffect)(() => locale?.subscribe(() => bump((count) => count + 1)), [locale]);
|
|
1901
|
+
const enabled = settings.getSnapshot().value?.enabled ?? true;
|
|
1902
|
+
const [collapsed, setCollapsed] = (0, react.useState)(readFootCardCollapsed);
|
|
1903
|
+
const toggleCollapsed = () => {
|
|
1904
|
+
setCollapsed((current) => {
|
|
1905
|
+
writeFootCardCollapsed(!current);
|
|
1906
|
+
return !current;
|
|
1907
|
+
});
|
|
1908
|
+
};
|
|
1909
|
+
(0, react.useEffect)(() => {
|
|
1910
|
+
if (!enabled) return void 0;
|
|
1911
|
+
poll();
|
|
1912
|
+
let timer;
|
|
1913
|
+
const start = () => {
|
|
1914
|
+
if (timer === void 0 && document.visibilityState === "visible") timer = window.setInterval(poll, FOOT_CARD_POLL_MS);
|
|
1915
|
+
};
|
|
1916
|
+
const onVisibility = () => {
|
|
1917
|
+
if (document.visibilityState === "visible") {
|
|
1918
|
+
poll();
|
|
1919
|
+
start();
|
|
1920
|
+
} else if (timer !== void 0) {
|
|
1921
|
+
window.clearInterval(timer);
|
|
1922
|
+
timer = void 0;
|
|
1923
|
+
}
|
|
1924
|
+
};
|
|
1925
|
+
start();
|
|
1926
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
1927
|
+
return () => {
|
|
1928
|
+
if (timer !== void 0) window.clearInterval(timer);
|
|
1929
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
1930
|
+
};
|
|
1931
|
+
}, [poll, enabled]);
|
|
1932
|
+
if (!enabled) return null;
|
|
1933
|
+
const snapshot = ui.snapshot;
|
|
1934
|
+
if (snapshot === null && ui.status === "error" && /failed: 404/.test(ui.error ?? "")) return null;
|
|
1935
|
+
const toggle = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1936
|
+
type: "button",
|
|
1937
|
+
className: usage_module_css_default.footToggle,
|
|
1938
|
+
"data-dsh-part": "foot-card-toggle",
|
|
1939
|
+
"aria-label": t(collapsed ? "usage.foot.expand" : "usage.foot.collapse"),
|
|
1940
|
+
title: t(collapsed ? "usage.foot.expand" : "usage.foot.collapse"),
|
|
1941
|
+
onClick: toggleCollapsed,
|
|
1942
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronIcon, { collapsed })
|
|
1943
|
+
});
|
|
1944
|
+
if (snapshot === null) {
|
|
1945
|
+
const quiet = ui.status === "error" ? t("usage.error", { error: ui.error ?? "" }) : t("usage.loading");
|
|
1946
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1947
|
+
className: cardClass(collapsed),
|
|
1948
|
+
"data-dsh-plugin": "usage",
|
|
1949
|
+
"data-dsh-part": "foot-card",
|
|
1950
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1951
|
+
type: "button",
|
|
1952
|
+
className: usage_module_css_default.footMain,
|
|
1953
|
+
"data-dsh-part": "foot-card-main",
|
|
1954
|
+
"aria-label": t("usage.foot.open"),
|
|
1955
|
+
title: t("usage.foot.open"),
|
|
1956
|
+
onClick: onOpen,
|
|
1957
|
+
children: collapsed ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1958
|
+
className: usage_module_css_default.footStrip,
|
|
1959
|
+
"data-dsh-part": "foot-card-strip",
|
|
1960
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GaugeIcon, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1961
|
+
className: usage_module_css_default.footStripValue,
|
|
1962
|
+
children: "—"
|
|
1963
|
+
})]
|
|
1964
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1965
|
+
className: usage_module_css_default.footHead,
|
|
1966
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1967
|
+
className: usage_module_css_default.footTitle,
|
|
1968
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GaugeIcon, {}), t("usage.title")]
|
|
1969
|
+
})
|
|
1970
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1971
|
+
className: usage_module_css_default.footLine,
|
|
1972
|
+
children: quiet
|
|
1973
|
+
})] })
|
|
1974
|
+
}), toggle]
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
const head = headline(snapshot);
|
|
1978
|
+
const balances = balanceLine(snapshot);
|
|
1979
|
+
if (collapsed) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1980
|
+
className: cardClass(true),
|
|
1981
|
+
"data-dsh-plugin": "usage",
|
|
1982
|
+
"data-dsh-part": "foot-card",
|
|
1983
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1984
|
+
type: "button",
|
|
1985
|
+
className: usage_module_css_default.footMain,
|
|
1986
|
+
"data-dsh-part": "foot-card-main",
|
|
1987
|
+
"aria-label": t("usage.foot.open"),
|
|
1988
|
+
title: t("usage.foot.open"),
|
|
1989
|
+
onClick: onOpen,
|
|
1990
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1991
|
+
className: usage_module_css_default.footStrip,
|
|
1992
|
+
"data-dsh-part": "foot-card-strip",
|
|
1993
|
+
children: [
|
|
1994
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GaugeIcon, {}),
|
|
1995
|
+
head.provider !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1996
|
+
className: usage_module_css_default.footStripProvider,
|
|
1997
|
+
children: head.provider
|
|
1998
|
+
}),
|
|
1999
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2000
|
+
className: usage_module_css_default.footStripLabel,
|
|
2001
|
+
children: head.label
|
|
2002
|
+
}),
|
|
2003
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2004
|
+
className: usage_module_css_default.footStripValue,
|
|
2005
|
+
children: head.value
|
|
2006
|
+
})
|
|
2007
|
+
]
|
|
2008
|
+
})
|
|
2009
|
+
}), toggle]
|
|
2010
|
+
});
|
|
2011
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2012
|
+
className: cardClass(false),
|
|
2013
|
+
"data-dsh-plugin": "usage",
|
|
2014
|
+
"data-dsh-part": "foot-card",
|
|
2015
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2016
|
+
type: "button",
|
|
2017
|
+
className: usage_module_css_default.footMain,
|
|
2018
|
+
"data-dsh-part": "foot-card-main",
|
|
2019
|
+
"aria-label": t("usage.foot.open"),
|
|
2020
|
+
title: t("usage.foot.open"),
|
|
2021
|
+
onClick: onOpen,
|
|
2022
|
+
children: [
|
|
2023
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2024
|
+
className: usage_module_css_default.footHead,
|
|
2025
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2026
|
+
className: usage_module_css_default.footTitle,
|
|
2027
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GaugeIcon, {}), head.label]
|
|
2028
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2029
|
+
className: usage_module_css_default.footValue,
|
|
2030
|
+
children: head.value
|
|
1574
2031
|
})]
|
|
2032
|
+
}),
|
|
2033
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2034
|
+
className: usage_module_css_default.footLine,
|
|
2035
|
+
"data-dsh-part": "foot-card-usage",
|
|
2036
|
+
children: usageLine(snapshot, head.tokenFallback)
|
|
2037
|
+
}),
|
|
2038
|
+
balances !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2039
|
+
className: usage_module_css_default.footLine,
|
|
2040
|
+
"data-dsh-part": "foot-card-balances",
|
|
2041
|
+
children: balances
|
|
2042
|
+
}),
|
|
2043
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2044
|
+
className: usage_module_css_default.footMeta,
|
|
2045
|
+
children: t("usage.updated", { time: formatClock(snapshot.updatedAt) })
|
|
1575
2046
|
})
|
|
1576
2047
|
]
|
|
1577
|
-
})]
|
|
2048
|
+
}), toggle]
|
|
1578
2049
|
});
|
|
1579
2050
|
}
|
|
2051
|
+
/** The shell sidebar's foot area (footer actions + the Settings row), when mounted. */
|
|
2052
|
+
function footArea() {
|
|
2053
|
+
return document.querySelector("[data-pane=\"sidebar\"], [class*=\"sidebarCol\"]")?.querySelector("[class*=\"footArea\"]") ?? void 0;
|
|
2054
|
+
}
|
|
2055
|
+
/**
|
|
2056
|
+
* Open the settings panel on the usage section. The shell owns the panel's
|
|
2057
|
+
* open state and exposes no service for it, so the card replays the user's
|
|
2058
|
+
* own path: activate the sidebar Settings trigger, then pick the nav row
|
|
2059
|
+
* carrying the section's (localized) label. Every step degrades silently —
|
|
2060
|
+
* with the panel already open only the nav pick runs, and a missing row
|
|
2061
|
+
* leaves the panel on its default section.
|
|
2062
|
+
* @param label - the usage section's nav label in the active locale.
|
|
2063
|
+
*/
|
|
2064
|
+
function openUsageSettings(label) {
|
|
2065
|
+
/** Click the nav row carrying the usage label inside the open panel. */
|
|
2066
|
+
const pick = (panel) => {
|
|
2067
|
+
const wanted = label();
|
|
2068
|
+
for (const row of panel.querySelectorAll("nav button")) if (row.textContent !== null && row.textContent.includes(wanted)) {
|
|
2069
|
+
row.click();
|
|
2070
|
+
return;
|
|
2071
|
+
}
|
|
2072
|
+
};
|
|
2073
|
+
const open = document.querySelector("[role=\"dialog\"]");
|
|
2074
|
+
if (open !== null) {
|
|
2075
|
+
pick(open);
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
(document.querySelector("[data-pane=\"sidebar\"], [class*=\"sidebarCol\"]")?.querySelector("[class*=\"settingsArea\"] button"))?.click();
|
|
2079
|
+
let tries = 0;
|
|
2080
|
+
const attempt = () => {
|
|
2081
|
+
const panel = document.querySelector("[role=\"dialog\"]");
|
|
2082
|
+
if (panel !== null) {
|
|
2083
|
+
pick(panel);
|
|
2084
|
+
return;
|
|
2085
|
+
}
|
|
2086
|
+
tries += 1;
|
|
2087
|
+
if (tries <= 20) window.setTimeout(attempt, 50);
|
|
2088
|
+
};
|
|
2089
|
+
window.setTimeout(attempt, 0);
|
|
2090
|
+
}
|
|
2091
|
+
/**
|
|
2092
|
+
* Mount the sidebar foot card directly above the Settings row.
|
|
2093
|
+
* @param props - the store/poll/settings/open inputs of the apply body.
|
|
2094
|
+
* @returns disposer removing the container and its observers.
|
|
2095
|
+
*/
|
|
2096
|
+
function mountUsageFootCard(props) {
|
|
2097
|
+
if (typeof document !== "undefined" && document.querySelector("[data-dsh-usage-foot-card]") !== null) return () => {};
|
|
2098
|
+
const container = document.createElement("div");
|
|
2099
|
+
container.setAttribute("data-dsh-usage-foot-card", "");
|
|
2100
|
+
const root = (0, react_dom_client.createRoot)(container);
|
|
2101
|
+
root.render((0, react.createElement)(UsageFootCard, props));
|
|
2102
|
+
/** Keep the container directly above the Settings seat inside the foot area. */
|
|
2103
|
+
const place = () => {
|
|
2104
|
+
const foot = footArea();
|
|
2105
|
+
if (foot === void 0) return;
|
|
2106
|
+
const settings = foot.querySelector("[class*=\"settingsArea\"]");
|
|
2107
|
+
if (settings !== null) {
|
|
2108
|
+
if (container.nextElementSibling !== settings) foot.insertBefore(container, settings);
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
2111
|
+
if (foot.lastElementChild !== container) foot.append(container);
|
|
2112
|
+
};
|
|
2113
|
+
place();
|
|
2114
|
+
const unsubscribeBody = subscribeBodyInvalidations(place);
|
|
2115
|
+
return () => {
|
|
2116
|
+
unsubscribeBody();
|
|
2117
|
+
root.unmount();
|
|
2118
|
+
container.remove();
|
|
2119
|
+
};
|
|
2120
|
+
}
|
|
1580
2121
|
//#endregion
|
|
1581
2122
|
//#region src/client/index.ts
|
|
1582
2123
|
/** Hard ceiling for one usage API call; a stalled host must not pile up requests. */
|
|
@@ -1593,7 +2134,7 @@ window.__ModuleLoader__.load({
|
|
|
1593
2134
|
overview: () => usageFetch("/api/dsh-usage/overview", "GET"),
|
|
1594
2135
|
refresh: () => usageFetch("/api/dsh-usage/refresh", "POST")
|
|
1595
2136
|
};
|
|
1596
|
-
/** Settings namespace the section edits (
|
|
2137
|
+
/** Settings namespace the section edits (dsh-web-settings maps it onto this row's profile entry id). */
|
|
1597
2138
|
const USAGE_SETTINGS_NS = "dsh-usage";
|
|
1598
2139
|
/** First-level nav position: directly below the Workshop section (order 150). */
|
|
1599
2140
|
const SECTION_ORDER = 151;
|
|
@@ -1602,7 +2143,7 @@ window.__ModuleLoader__.load({
|
|
|
1602
2143
|
"slots",
|
|
1603
2144
|
"locale",
|
|
1604
2145
|
"connection",
|
|
1605
|
-
"
|
|
2146
|
+
"configForms",
|
|
1606
2147
|
"remote"
|
|
1607
2148
|
];
|
|
1608
2149
|
/**
|
|
@@ -1622,7 +2163,8 @@ window.__ModuleLoader__.load({
|
|
|
1622
2163
|
return () => {};
|
|
1623
2164
|
}
|
|
1624
2165
|
}, "dsh-usage: dictionaries");
|
|
1625
|
-
const
|
|
2166
|
+
const binder = ctx.get("webUiSettings");
|
|
2167
|
+
const settingsForm = binder !== void 0 ? binder.bind({ namespace: USAGE_SETTINGS_NS }) : ctx.configForms.get(USAGE_SETTINGS_NS);
|
|
1626
2168
|
const store = createUsageStore().create();
|
|
1627
2169
|
let pollSeq = 0;
|
|
1628
2170
|
const poll = () => {
|
|
@@ -1651,8 +2193,22 @@ window.__ModuleLoader__.load({
|
|
|
1651
2193
|
store,
|
|
1652
2194
|
poll,
|
|
1653
2195
|
refresh,
|
|
1654
|
-
settings:
|
|
2196
|
+
settings: settingsForm
|
|
1655
2197
|
});
|
|
2198
|
+
const disposeFootCard = mountUsageFootCard({
|
|
2199
|
+
store,
|
|
2200
|
+
poll,
|
|
2201
|
+
settings: settingsForm,
|
|
2202
|
+
onOpen: () => {
|
|
2203
|
+
openUsageSettings(() => t("usage.title"));
|
|
2204
|
+
},
|
|
2205
|
+
locale: ctx.locale
|
|
2206
|
+
});
|
|
2207
|
+
ctx.effect(() => () => {
|
|
2208
|
+
try {
|
|
2209
|
+
disposeFootCard();
|
|
2210
|
+
} catch {}
|
|
2211
|
+
}, "dsh-usage: sidebar foot card");
|
|
1656
2212
|
ctx.slots.inject("settings.section", () => {
|
|
1657
2213
|
try {
|
|
1658
2214
|
const unregister = ctx.slots.register({
|