@linxin666/dsh-usage 0.3.24 → 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 +4 -4
- package/README.zh.md +4 -4
- package/icon.svg +3 -0
- package/lib/client.js +540 -655
- package/lib/client.js.map +1 -1
- package/lib/index.js +92 -75
- package/lib/types/client/UsageFootCard.d.ts +55 -0
- package/lib/types/client/UsageSectionCard.d.ts +3 -3
- 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 -9
- package/lib/types/host/http.d.ts +18 -1
- package/lib/types/host/usage-service.d.ts +4 -3
- package/lib/types/index.d.ts +14 -0
- package/package.json +19 -17
- package/src/client/UsageFootCard.tsx +300 -0
- package/src/client/UsageSectionCard.tsx +41 -9
- package/src/client/foot-card-mount.tsx +112 -0
- package/src/client/index.ts +53 -33
- package/src/client/locales.ts +12 -18
- package/src/client/usage.module.css +101 -165
- package/src/host/http.ts +23 -1
- package/src/host/usage-service.ts +9 -4
- package/src/index.ts +59 -55
- package/lib/types/client/UsageSidebarPanel.d.ts +0 -34
- package/lib/types/client/sidebar-entry-core.d.ts +0 -87
- package/lib/types/client/sidebar-entry.d.ts +0 -35
- package/lib/types/client/sidebar-panel-mount.d.ts +0 -40
- package/src/client/UsageSidebarPanel.tsx +0 -196
- package/src/client/sidebar-entry-core.ts +0 -321
- package/src/client/sidebar-entry.ts +0 -101
- package/src/client/sidebar-panel-mount.tsx +0 -113
package/lib/client.js
CHANGED
|
@@ -5,8 +5,8 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
|
-
let react_dom_client = require("react-dom/client");
|
|
9
8
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
+
let react_dom_client = require("react-dom/client");
|
|
10
10
|
//#region \0dsh-store-engine
|
|
11
11
|
const platform = ["@deepseek-ai/dsh-client", "-store"].join("");
|
|
12
12
|
const legacy = ["@deepseek-ai/dsh-client-runtime", "/client"].join("");
|
|
@@ -98,6 +98,7 @@ window.__ModuleLoader__.load({
|
|
|
98
98
|
"usage.config.title": "设置",
|
|
99
99
|
"usage.config.enabled": "启用插件",
|
|
100
100
|
"usage.config.pollIntervalSec": "轮询间隔(秒)",
|
|
101
|
+
"usage.config.saveFailed": "保存失败:设置未被接受。",
|
|
101
102
|
"usage.bank.title": "鲸元券",
|
|
102
103
|
"usage.bank.hint": "官方 API 每消耗 100 万 tokens 铸造 1 鲸元;保存或分享这张票券。",
|
|
103
104
|
"usage.bank.noUsage": "暂无 DeepSeek 官方用量数据(统计自插件启用起)",
|
|
@@ -108,15 +109,11 @@ window.__ModuleLoader__.load({
|
|
|
108
109
|
"usage.bank.save": "保存图片",
|
|
109
110
|
"usage.bank.share": "分享",
|
|
110
111
|
"usage.bank.drawError": "票券生成失败:{error}",
|
|
111
|
-
"usage.
|
|
112
|
-
"usage.
|
|
113
|
-
"usage.
|
|
114
|
-
"usage.
|
|
115
|
-
"usage.
|
|
116
|
-
"usage.sidebar.error": "加载失败:{error}",
|
|
117
|
-
"usage.sidebar.loading": "正在加载用量数据…",
|
|
118
|
-
"usage.sidebar.balanceLeft": "剩余 {balance}",
|
|
119
|
-
"usage.sidebar.todayUsage": "今日 {tokens} tokens"
|
|
112
|
+
"usage.foot.cost": "今日消费",
|
|
113
|
+
"usage.foot.noData": "今日暂无用量",
|
|
114
|
+
"usage.foot.open": "打开使用统计设置",
|
|
115
|
+
"usage.foot.collapse": "收起用量卡片",
|
|
116
|
+
"usage.foot.expand": "展开用量卡片"
|
|
120
117
|
};
|
|
121
118
|
/** English mirror; every zh key present. */
|
|
122
119
|
const en = {
|
|
@@ -159,6 +156,7 @@ window.__ModuleLoader__.load({
|
|
|
159
156
|
"usage.config.title": "Settings",
|
|
160
157
|
"usage.config.enabled": "Enable plugin",
|
|
161
158
|
"usage.config.pollIntervalSec": "Poll interval (seconds)",
|
|
159
|
+
"usage.config.saveFailed": "Save failed: the deployment did not accept the setting.",
|
|
162
160
|
"usage.bank.title": "Whale-yuan voucher",
|
|
163
161
|
"usage.bank.hint": "Every 1,000,000 tokens spent on the official API mint one whale yuan; save or share the note.",
|
|
164
162
|
"usage.bank.noUsage": "No official DeepSeek usage yet (counting starts when the plugin is enabled)",
|
|
@@ -169,15 +167,11 @@ window.__ModuleLoader__.load({
|
|
|
169
167
|
"usage.bank.save": "Save image",
|
|
170
168
|
"usage.bank.share": "Share",
|
|
171
169
|
"usage.bank.drawError": "Failed to render the voucher: {error}",
|
|
172
|
-
"usage.
|
|
173
|
-
"usage.
|
|
174
|
-
"usage.
|
|
175
|
-
"usage.
|
|
176
|
-
"usage.
|
|
177
|
-
"usage.sidebar.error": "Loading failed: {error}",
|
|
178
|
-
"usage.sidebar.loading": "Loading usage data…",
|
|
179
|
-
"usage.sidebar.balanceLeft": "{balance} left",
|
|
180
|
-
"usage.sidebar.todayUsage": "{tokens} tokens today"
|
|
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"
|
|
181
175
|
};
|
|
182
176
|
/**
|
|
183
177
|
* Active dictionary, picked by the document language at call time. The
|
|
@@ -195,7 +189,7 @@ window.__ModuleLoader__.load({
|
|
|
195
189
|
}
|
|
196
190
|
//#endregion
|
|
197
191
|
//#region \0dsh-css:packages/dsh-usage/src/client/usage.module.css.mjs
|
|
198
|
-
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}";
|
|
199
193
|
const tagId = "@linxin666/dsh-usage/packages/dsh-usage/src/client/usage.module.css";
|
|
200
194
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
201
195
|
const tag = document.createElement("style");
|
|
@@ -224,12 +218,20 @@ window.__ModuleLoader__.load({
|
|
|
224
218
|
"chartTokens": "cvtkAW_chartTokens",
|
|
225
219
|
"currentBadge": "cvtkAW_currentBadge",
|
|
226
220
|
"currentProvider": "cvtkAW_currentProvider",
|
|
227
|
-
"entry": "cvtkAW_entry",
|
|
228
|
-
"entryAction": "cvtkAW_entryAction",
|
|
229
|
-
"entryIcon": "cvtkAW_entryIcon",
|
|
230
|
-
"entryLabel": "cvtkAW_entryLabel",
|
|
231
|
-
"entryMain": "cvtkAW_entryMain",
|
|
232
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",
|
|
233
235
|
"header": "cvtkAW_header",
|
|
234
236
|
"muted": "cvtkAW_muted",
|
|
235
237
|
"planCard": "cvtkAW_planCard",
|
|
@@ -244,18 +246,6 @@ window.__ModuleLoader__.load({
|
|
|
244
246
|
"section": "cvtkAW_section",
|
|
245
247
|
"settingItem": "cvtkAW_settingItem",
|
|
246
248
|
"settingsGrid": "cvtkAW_settingsGrid",
|
|
247
|
-
"sidebarBalance": "cvtkAW_sidebarBalance",
|
|
248
|
-
"sidebarBar": "cvtkAW_sidebarBar",
|
|
249
|
-
"sidebarBarFill": "cvtkAW_sidebarBarFill",
|
|
250
|
-
"sidebarMuted": "cvtkAW_sidebarMuted",
|
|
251
|
-
"sidebarPanel": "cvtkAW_sidebarPanel",
|
|
252
|
-
"sidebarProvider": "cvtkAW_sidebarProvider",
|
|
253
|
-
"sidebarProviderHead": "cvtkAW_sidebarProviderHead",
|
|
254
|
-
"sidebarProviderName": "cvtkAW_sidebarProviderName",
|
|
255
|
-
"sidebarSummary": "cvtkAW_sidebarSummary",
|
|
256
|
-
"sidebarSummaryFacts": "cvtkAW_sidebarSummaryFacts",
|
|
257
|
-
"sidebarSummaryName": "cvtkAW_sidebarSummaryName",
|
|
258
|
-
"sidebarWindowRow": "cvtkAW_sidebarWindowRow",
|
|
259
249
|
"stat": "cvtkAW_stat",
|
|
260
250
|
"statLabel": "cvtkAW_statLabel",
|
|
261
251
|
"statRow": "cvtkAW_statRow",
|
|
@@ -264,356 +254,11 @@ window.__ModuleLoader__.load({
|
|
|
264
254
|
"tabActive": "cvtkAW_tabActive",
|
|
265
255
|
"tabs": "cvtkAW_tabs",
|
|
266
256
|
"trendAxis": "cvtkAW_trendAxis",
|
|
267
|
-
"usageEntrySpin": "cvtkAW_usageEntrySpin",
|
|
268
257
|
"voucherPreview": "cvtkAW_voucherPreview",
|
|
269
258
|
"windowLabel": "cvtkAW_windowLabel",
|
|
270
259
|
"windowRow": "cvtkAW_windowRow"
|
|
271
260
|
};
|
|
272
261
|
//#endregion
|
|
273
|
-
//#region src/client/body-mutations.ts
|
|
274
|
-
/** Cross-bundle registry key; `Symbol.for` so every module copy agrees. */
|
|
275
|
-
const HUB_KEY = Symbol.for("dsh-web.body-mutation-hub");
|
|
276
|
-
const INVALIDATION_ONLY = Symbol.for("dsh-web.body-mutation-invalidation");
|
|
277
|
-
function needsRecords(subscribers) {
|
|
278
|
-
for (const listener of subscribers) if (!listener[INVALIDATION_ONLY]) return true;
|
|
279
|
-
return false;
|
|
280
|
-
}
|
|
281
|
-
/**
|
|
282
|
-
* Subscribe to a coalesced DOM re-check without retaining mutation records.
|
|
283
|
-
* The marked wrapper also works with an older hub, which delivers records
|
|
284
|
-
* that it simply ignores until a page reload picks up the updated hub.
|
|
285
|
-
*/
|
|
286
|
-
function subscribeBodyInvalidations(subscriber) {
|
|
287
|
-
const listener = () => {
|
|
288
|
-
subscriber();
|
|
289
|
-
};
|
|
290
|
-
listener[INVALIDATION_ONLY] = true;
|
|
291
|
-
return subscribeBodyMutations(listener);
|
|
292
|
-
}
|
|
293
|
-
/**
|
|
294
|
-
* Subscribe to body-level childList mutations.
|
|
295
|
-
* @param subscriber - called at most once per animation frame with the records
|
|
296
|
-
* collected since the previous flush; must be safe to run repeatedly.
|
|
297
|
-
* @returns the disposer removing this subscriber (and the observer when it was
|
|
298
|
-
* the last one).
|
|
299
|
-
*/
|
|
300
|
-
function subscribeBodyMutations(subscriber) {
|
|
301
|
-
if (typeof globalThis === "undefined" || typeof document === "undefined") return () => {};
|
|
302
|
-
if (typeof MutationObserver !== "function") return () => {};
|
|
303
|
-
const registry = globalThis;
|
|
304
|
-
let hub = registry[HUB_KEY];
|
|
305
|
-
if (hub === void 0) {
|
|
306
|
-
const subscribers = /* @__PURE__ */ new Set();
|
|
307
|
-
const created = {
|
|
308
|
-
observer: void 0,
|
|
309
|
-
subscribers,
|
|
310
|
-
pending: [],
|
|
311
|
-
scheduled: false
|
|
312
|
-
};
|
|
313
|
-
const flush = () => {
|
|
314
|
-
created.frame = void 0;
|
|
315
|
-
created.scheduled = false;
|
|
316
|
-
const batch = created.pending;
|
|
317
|
-
created.pending = [];
|
|
318
|
-
for (const listener of [...subscribers]) {
|
|
319
|
-
if (!subscribers.has(listener)) continue;
|
|
320
|
-
try {
|
|
321
|
-
listener(batch);
|
|
322
|
-
} catch {}
|
|
323
|
-
}
|
|
324
|
-
};
|
|
325
|
-
const schedule = () => {
|
|
326
|
-
if (created.scheduled) return;
|
|
327
|
-
created.scheduled = true;
|
|
328
|
-
if (typeof requestAnimationFrame === "function") created.frame = requestAnimationFrame(flush);
|
|
329
|
-
else flush();
|
|
330
|
-
};
|
|
331
|
-
created.observer = new MutationObserver((records) => {
|
|
332
|
-
if (needsRecords(subscribers)) for (const record of records) created.pending.push(record);
|
|
333
|
-
schedule();
|
|
334
|
-
});
|
|
335
|
-
created.observer.observe(document.body ?? document.documentElement, {
|
|
336
|
-
childList: true,
|
|
337
|
-
subtree: true
|
|
338
|
-
});
|
|
339
|
-
registry[HUB_KEY] = created;
|
|
340
|
-
hub = created;
|
|
341
|
-
}
|
|
342
|
-
const active = hub;
|
|
343
|
-
active.subscribers.add(subscriber);
|
|
344
|
-
let subscribed = true;
|
|
345
|
-
return () => {
|
|
346
|
-
if (!subscribed) return;
|
|
347
|
-
subscribed = false;
|
|
348
|
-
active.subscribers.delete(subscriber);
|
|
349
|
-
if (!needsRecords(active.subscribers)) active.pending = [];
|
|
350
|
-
if (active.subscribers.size === 0 && registry[HUB_KEY] === active) {
|
|
351
|
-
active.observer.disconnect();
|
|
352
|
-
if (active.frame !== void 0 && typeof cancelAnimationFrame === "function") cancelAnimationFrame(active.frame);
|
|
353
|
-
active.frame = void 0;
|
|
354
|
-
active.pending = [];
|
|
355
|
-
active.scheduled = false;
|
|
356
|
-
delete registry[HUB_KEY];
|
|
357
|
-
}
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
//#endregion
|
|
361
|
-
//#region src/client/sidebar-entry-core.ts
|
|
362
|
-
/**
|
|
363
|
-
* Shared sidebar entry injection core.
|
|
364
|
-
*
|
|
365
|
-
* dsh's sidebar shell exposes no slot an external plugin can register into,
|
|
366
|
-
* so the entry row is injected between the shell's New Session button and the
|
|
367
|
-
* workspace browser. The injection self-heals: a MutationObserver watches the
|
|
368
|
-
* sidebar root and re-inserts the row whenever a React re-render displaces it
|
|
369
|
-
* (re-insertion happens in the same frame, before paint, so no flicker).
|
|
370
|
-
*
|
|
371
|
-
* The row is plain DOM (no React tree) so it can never disturb the shell's
|
|
372
|
-
* reconciliation; the view it toggles is a separate root owned by the caller.
|
|
373
|
-
*
|
|
374
|
-
* Packages receive this file as a generated copy via scripts/sync-shared.mjs;
|
|
375
|
-
* edit the shared source and re-run the sync instead of editing a copy.
|
|
376
|
-
*/
|
|
377
|
-
/** Find the sidebar shell root element, or undefined while not yet mounted. */
|
|
378
|
-
function sidebarRoot() {
|
|
379
|
-
const column = document.querySelector("[data-pane=\"sidebar\"], [class*=\"sidebarCol\"]");
|
|
380
|
-
if (column === null) return void 0;
|
|
381
|
-
return column.querySelector("[class*=\"logoRow\"]")?.parentElement ?? column.firstElementChild;
|
|
382
|
-
}
|
|
383
|
-
/** The New Session button: nested in the logo row on current shells, a direct child on legacy shells. */
|
|
384
|
-
function newSessionButton(root) {
|
|
385
|
-
const nested = root.querySelector("button[class*=\"newSession\"]");
|
|
386
|
-
if (nested !== null) return nested;
|
|
387
|
-
for (const child of root.children) if (child.tagName === "BUTTON") return child;
|
|
388
|
-
}
|
|
389
|
-
/** Build the entry row (detached; inserted once the shell is up). */
|
|
390
|
-
function createEntry(options) {
|
|
391
|
-
const actions = options.actions ?? [];
|
|
392
|
-
const composite = actions.length > 0;
|
|
393
|
-
const entry = document.createElement(composite ? "div" : "button");
|
|
394
|
-
if (!composite) entry.type = "button";
|
|
395
|
-
entry.setAttribute(options.rowAttribute, "");
|
|
396
|
-
if (options.plugin !== void 0) {
|
|
397
|
-
entry.setAttribute("data-dsh-plugin", options.plugin);
|
|
398
|
-
entry.setAttribute("data-dsh-part", "sidebar-entry");
|
|
399
|
-
}
|
|
400
|
-
entry.className = options.css["entry"] ?? "";
|
|
401
|
-
const labelSpan = document.createElement("span");
|
|
402
|
-
labelSpan.className = options.css["entryLabel"] ?? "";
|
|
403
|
-
const iconSpan = document.createElement("span");
|
|
404
|
-
iconSpan.className = options.css["entryIcon"] ?? "";
|
|
405
|
-
iconSpan.innerHTML = options.icon;
|
|
406
|
-
const main = composite ? document.createElement("button") : entry;
|
|
407
|
-
if (composite) {
|
|
408
|
-
main.type = "button";
|
|
409
|
-
main.className = options.css["entryMain"] ?? "";
|
|
410
|
-
main.append(iconSpan, labelSpan);
|
|
411
|
-
entry.append(main);
|
|
412
|
-
} else entry.append(iconSpan, labelSpan);
|
|
413
|
-
const actionButtons = [];
|
|
414
|
-
for (const action of actions) {
|
|
415
|
-
const button = document.createElement("button");
|
|
416
|
-
button.type = "button";
|
|
417
|
-
button.className = options.css["entryAction"] ?? "";
|
|
418
|
-
button.setAttribute("data-dsh-entry-action", action.id);
|
|
419
|
-
button.innerHTML = action.icon;
|
|
420
|
-
button.addEventListener("click", () => {
|
|
421
|
-
action.onClick(button);
|
|
422
|
-
});
|
|
423
|
-
entry.append(button);
|
|
424
|
-
actionButtons.push({
|
|
425
|
-
action,
|
|
426
|
-
button
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
let open = false;
|
|
430
|
-
const applyActions = () => {
|
|
431
|
-
for (const { action, button } of actionButtons) {
|
|
432
|
-
button.innerHTML = open || action.inactiveIcon === void 0 ? action.icon : action.inactiveIcon;
|
|
433
|
-
const text = action.label(open);
|
|
434
|
-
button.setAttribute("aria-label", text);
|
|
435
|
-
button.setAttribute("title", text);
|
|
436
|
-
if (action.inactiveIcon !== void 0) button.setAttribute("aria-expanded", String(open));
|
|
437
|
-
}
|
|
438
|
-
};
|
|
439
|
-
const applyLabel = () => {
|
|
440
|
-
main.setAttribute("aria-label", options.label());
|
|
441
|
-
if (options.tooltip !== void 0) main.setAttribute("title", options.tooltip());
|
|
442
|
-
labelSpan.textContent = options.label();
|
|
443
|
-
applyActions();
|
|
444
|
-
};
|
|
445
|
-
applyLabel();
|
|
446
|
-
main.addEventListener("click", options.onToggle);
|
|
447
|
-
if (composite) entry.addEventListener("click", (event) => {
|
|
448
|
-
if (event.target === entry) options.onToggle();
|
|
449
|
-
});
|
|
450
|
-
return {
|
|
451
|
-
entry,
|
|
452
|
-
applyLabel,
|
|
453
|
-
setOpen: (next) => {
|
|
454
|
-
open = next;
|
|
455
|
-
applyActions();
|
|
456
|
-
}
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
/** Re-insert the entry after the New Session row (before the browser region). */
|
|
460
|
-
function placeEntry(root, entry, options) {
|
|
461
|
-
const button = newSessionButton(root);
|
|
462
|
-
if (button === void 0) return false;
|
|
463
|
-
if (entry.parentElement !== root) {
|
|
464
|
-
const row = button.closest("[class*=\"logoRow\"]");
|
|
465
|
-
const base = row !== null && row.parentElement === root ? row : button;
|
|
466
|
-
const family = Array.from(root.children).filter((el) => el instanceof HTMLElement && el.matches(options.familySelectors.join(", ")));
|
|
467
|
-
const anchor = options.position === "before" ? family.length > 0 ? family[0] : base.nextElementSibling : family.length > 0 ? family[family.length - 1].nextElementSibling : base.nextElementSibling;
|
|
468
|
-
root.insertBefore(entry, anchor);
|
|
469
|
-
}
|
|
470
|
-
return true;
|
|
471
|
-
}
|
|
472
|
-
/**
|
|
473
|
-
* Mount the sidebar entry, waiting for the shell to render and self-healing
|
|
474
|
-
* on later React re-renders.
|
|
475
|
-
* @param options - the row's attribute/icon/copy/action/ordering configuration.
|
|
476
|
-
* @returns disposer removing the entry and its observers.
|
|
477
|
-
*/
|
|
478
|
-
function mountSidebarEntry$1(options) {
|
|
479
|
-
if (typeof document !== "undefined" && document.querySelector(options.rowSelector) !== null) return () => {};
|
|
480
|
-
const { entry, applyLabel, setOpen } = createEntry(options);
|
|
481
|
-
let root;
|
|
482
|
-
let placed = false;
|
|
483
|
-
let unsubscribeRefresh;
|
|
484
|
-
if (options.refresh !== void 0) try {
|
|
485
|
-
unsubscribeRefresh = options.refresh.subscribe(applyLabel);
|
|
486
|
-
} catch {}
|
|
487
|
-
const tryPlace = () => {
|
|
488
|
-
if (root !== void 0 && !root.isConnected) {
|
|
489
|
-
rootObserver.disconnect();
|
|
490
|
-
root = void 0;
|
|
491
|
-
placed = false;
|
|
492
|
-
}
|
|
493
|
-
if (placed) {
|
|
494
|
-
if (document.body.contains(entry)) return;
|
|
495
|
-
rootObserver.disconnect();
|
|
496
|
-
root = void 0;
|
|
497
|
-
placed = false;
|
|
498
|
-
}
|
|
499
|
-
root ??= sidebarRoot();
|
|
500
|
-
if (root === void 0) return;
|
|
501
|
-
placed = placeEntry(root, entry, options);
|
|
502
|
-
if (placed) rootObserver.observe(root, {
|
|
503
|
-
childList: true,
|
|
504
|
-
subtree: true
|
|
505
|
-
});
|
|
506
|
-
};
|
|
507
|
-
const unsubscribeBody = subscribeBodyInvalidations(() => {
|
|
508
|
-
tryPlace();
|
|
509
|
-
});
|
|
510
|
-
const rootObserver = new MutationObserver(() => {
|
|
511
|
-
if (root === void 0 || !root.isConnected) {
|
|
512
|
-
placed = false;
|
|
513
|
-
tryPlace();
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
if (!root.contains(entry)) placed = placeEntry(root, entry, options);
|
|
517
|
-
});
|
|
518
|
-
const unsubscribeActive = options.active === void 0 ? void 0 : (() => {
|
|
519
|
-
const syncActive = () => {
|
|
520
|
-
const open = options.active.isOpen();
|
|
521
|
-
if (open) entry.dataset.active = "true";
|
|
522
|
-
else delete entry.dataset.active;
|
|
523
|
-
setOpen(open);
|
|
524
|
-
};
|
|
525
|
-
const unsubscribe = options.active.subscribe(syncActive);
|
|
526
|
-
syncActive();
|
|
527
|
-
return unsubscribe;
|
|
528
|
-
})();
|
|
529
|
-
tryPlace();
|
|
530
|
-
return () => {
|
|
531
|
-
unsubscribeBody();
|
|
532
|
-
rootObserver.disconnect();
|
|
533
|
-
unsubscribeRefresh?.();
|
|
534
|
-
unsubscribeActive?.();
|
|
535
|
-
entry.remove();
|
|
536
|
-
};
|
|
537
|
-
}
|
|
538
|
-
//#endregion
|
|
539
|
-
//#region src/client/sidebar-entry.ts
|
|
540
|
-
/**
|
|
541
|
-
* Sidebar entry injection for the usage panel (issue #1592) — wiring over the
|
|
542
|
-
* shared core. Unlike the panel-takeover family (task board / SSH / skill
|
|
543
|
-
* center) this row does not participate in the family block, so it is placed
|
|
544
|
-
* directly under the New Session row and ordered after those entries. The row
|
|
545
|
-
* seats the panel's controls as trailing actions: a refresh command and a
|
|
546
|
-
* collapse chevron mirroring the open state, so the panel below renders only
|
|
547
|
-
* its content.
|
|
548
|
-
* @module @linxin666/dsh-usage/client/sidebar-entry
|
|
549
|
-
*/
|
|
550
|
-
/** Stable data attribute identifying the injected entry row. */
|
|
551
|
-
const ENTRY_SELECTOR = "[data-dsh-usage-entry]";
|
|
552
|
-
/** Inline gauge glyph normalized to the shell's 18px navigation glyph size. */
|
|
553
|
-
const ICON = "<svg viewBox=\"0 0 16 16\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.3\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M2.5 12.5a7 7 0 0 1 11 -4.3\"/><path d=\"M8 12.5V8.2l2.9-2.1\"/><circle cx=\"8\" cy=\"12.5\" r=\"1\"/></svg>";
|
|
554
|
-
/** Refresh glyph: a clockwise circular arrow, 14px like the sibling action icons. */
|
|
555
|
-
const REFRESH_ICON = "<svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M13.4 8a5.4 5.4 0 1 1-1.58-3.82\"/><path d=\"M13.4 2.6v2.7h-2.7\"/></svg>";
|
|
556
|
-
/** Disclosure chevrons: down while expanded, right while collapsed. */
|
|
557
|
-
const CHEVRON_OPEN_ICON = "<svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m4 6 4 4 4-4\"/></svg>";
|
|
558
|
-
const CHEVRON_CLOSED_ICON = "<svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"m6 4 4 4-4 4\"/></svg>";
|
|
559
|
-
/** Refresh cooldown: one forced probe cycle per click, the icon spins meanwhile. */
|
|
560
|
-
const REFRESH_COOLDOWN_MS = 3e3;
|
|
561
|
-
/** Trailing actions seated at the row's right edge. */
|
|
562
|
-
function entryActions(handlers) {
|
|
563
|
-
return [{
|
|
564
|
-
id: "refresh",
|
|
565
|
-
icon: REFRESH_ICON,
|
|
566
|
-
label: () => t("usage.refresh"),
|
|
567
|
-
onClick: (button) => {
|
|
568
|
-
handlers.onRefresh();
|
|
569
|
-
button.disabled = true;
|
|
570
|
-
window.setTimeout(() => {
|
|
571
|
-
button.disabled = false;
|
|
572
|
-
}, REFRESH_COOLDOWN_MS);
|
|
573
|
-
}
|
|
574
|
-
}, {
|
|
575
|
-
id: "collapse",
|
|
576
|
-
icon: CHEVRON_OPEN_ICON,
|
|
577
|
-
inactiveIcon: CHEVRON_CLOSED_ICON,
|
|
578
|
-
label: (open) => t(open ? "usage.sidebar.toggle.collapse" : "usage.sidebar.toggle.expand"),
|
|
579
|
-
onClick: () => {
|
|
580
|
-
handlers.onToggle();
|
|
581
|
-
}
|
|
582
|
-
}];
|
|
583
|
-
}
|
|
584
|
-
/**
|
|
585
|
-
* Mount the usage sidebar entry.
|
|
586
|
-
* @param handlers - the panel's toggle/refresh callbacks and open-state face.
|
|
587
|
-
* @param locale - locale-change source; when given, re-applies the label on a
|
|
588
|
-
* language switch (the plain-DOM row otherwise keeps the mount-time copy).
|
|
589
|
-
* @returns disposer removing the entry and its observers.
|
|
590
|
-
*/
|
|
591
|
-
function mountSidebarEntry(handlers, locale) {
|
|
592
|
-
return mountSidebarEntry$1({
|
|
593
|
-
rowAttribute: "data-dsh-usage-entry",
|
|
594
|
-
rowSelector: ENTRY_SELECTOR,
|
|
595
|
-
plugin: "usage",
|
|
596
|
-
icon: ICON,
|
|
597
|
-
css: usage_module_css_default,
|
|
598
|
-
label: () => t("usage.sidebar.entry.label"),
|
|
599
|
-
tooltip: () => t("usage.sidebar.entry.tooltip"),
|
|
600
|
-
refresh: locale === void 0 ? void 0 : { subscribe: (listener) => locale.subscribe(listener) },
|
|
601
|
-
onToggle: handlers.onToggle,
|
|
602
|
-
actions: entryActions(handlers),
|
|
603
|
-
position: "after",
|
|
604
|
-
familySelectors: [
|
|
605
|
-
"[data-dsh-taskboard-entry]",
|
|
606
|
-
"[data-dsh-ssh-entry]",
|
|
607
|
-
"[data-dsh-skill-explorer-entry]",
|
|
608
|
-
"[data-dsh-usage-entry]"
|
|
609
|
-
],
|
|
610
|
-
active: {
|
|
611
|
-
subscribe: handlers.subscribeOpen,
|
|
612
|
-
isOpen: handlers.isOpen
|
|
613
|
-
}
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
//#endregion
|
|
617
262
|
//#region src/core/adapters.ts
|
|
618
263
|
/** Parse a string/number into a finite number, else undefined. */
|
|
619
264
|
function toNum(value) {
|
|
@@ -1104,31 +749,6 @@ window.__ModuleLoader__.load({
|
|
|
1104
749
|
};
|
|
1105
750
|
}
|
|
1106
751
|
//#endregion
|
|
1107
|
-
//#region src/core/types.ts
|
|
1108
|
-
/** A zeroed totals bucket. */
|
|
1109
|
-
function emptyTotals() {
|
|
1110
|
-
return {
|
|
1111
|
-
inputTokens: 0,
|
|
1112
|
-
outputTokens: 0,
|
|
1113
|
-
cacheReadTokens: 0,
|
|
1114
|
-
cacheWriteTokens: 0,
|
|
1115
|
-
reasoningTokens: 0,
|
|
1116
|
-
calls: 0,
|
|
1117
|
-
cost: 0
|
|
1118
|
-
};
|
|
1119
|
-
}
|
|
1120
|
-
/** Add `right` into `left` in place. */
|
|
1121
|
-
function addTotals(left, right) {
|
|
1122
|
-
left.inputTokens += right.inputTokens;
|
|
1123
|
-
left.outputTokens += right.outputTokens;
|
|
1124
|
-
left.cacheReadTokens += right.cacheReadTokens;
|
|
1125
|
-
left.cacheWriteTokens += right.cacheWriteTokens;
|
|
1126
|
-
left.reasoningTokens += right.reasoningTokens;
|
|
1127
|
-
left.calls += right.calls;
|
|
1128
|
-
left.cost += right.cost;
|
|
1129
|
-
return left;
|
|
1130
|
-
}
|
|
1131
|
-
//#endregion
|
|
1132
752
|
//#region src/core/ledger.ts
|
|
1133
753
|
/** Total tokens of a bucket (billed input + output; reasoning is inside output). */
|
|
1134
754
|
function totalTokens(totals) {
|
|
@@ -1291,7 +911,7 @@ window.__ModuleLoader__.load({
|
|
|
1291
911
|
return "";
|
|
1292
912
|
}
|
|
1293
913
|
}
|
|
1294
|
-
function formatClock(ms) {
|
|
914
|
+
function formatClock$1(ms) {
|
|
1295
915
|
try {
|
|
1296
916
|
return new Date(ms).toLocaleTimeString([], {
|
|
1297
917
|
hour: "2-digit",
|
|
@@ -1382,7 +1002,7 @@ window.__ModuleLoader__.load({
|
|
|
1382
1002
|
]
|
|
1383
1003
|
});
|
|
1384
1004
|
}
|
|
1385
|
-
function balanceLine(provider) {
|
|
1005
|
+
function balanceLine$1(provider) {
|
|
1386
1006
|
if (provider.balance !== void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1387
1007
|
className: usage_module_css_default.providerBalance,
|
|
1388
1008
|
children: [
|
|
@@ -1418,7 +1038,7 @@ window.__ModuleLoader__.load({
|
|
|
1418
1038
|
})]
|
|
1419
1039
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1420
1040
|
className: usage_module_css_default.providerTokens,
|
|
1421
|
-
children: balanceLine(provider)
|
|
1041
|
+
children: balanceLine$1(provider)
|
|
1422
1042
|
})]
|
|
1423
1043
|
});
|
|
1424
1044
|
}
|
|
@@ -1554,7 +1174,7 @@ window.__ModuleLoader__.load({
|
|
|
1554
1174
|
deepseekVisible && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1555
1175
|
className: usage_module_css_default.muted,
|
|
1556
1176
|
"data-dsh-part": "peak-status",
|
|
1557
|
-
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) })
|
|
1558
1178
|
}),
|
|
1559
1179
|
snapshot.usage.today.totals.calls === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1560
1180
|
className: usage_module_css_default.muted,
|
|
@@ -1936,158 +1556,362 @@ window.__ModuleLoader__.load({
|
|
|
1936
1556
|
]
|
|
1937
1557
|
});
|
|
1938
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
|
+
*/
|
|
1939
1565
|
function SettingsRow(props) {
|
|
1940
1566
|
const { settings, snapshot, value } = props;
|
|
1941
1567
|
const disabled = snapshot === void 0 || !snapshot.writable;
|
|
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
|
+
};
|
|
1942
1584
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1943
1585
|
className: usage_module_css_default.card,
|
|
1944
1586
|
"data-dsh-part": "settings-row",
|
|
1945
|
-
children: [
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
className: usage_module_css_default.
|
|
1952
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
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", {
|
|
1595
|
+
className: usage_module_css_default.settingItem,
|
|
1596
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1597
|
+
type: "checkbox",
|
|
1598
|
+
checked: value.enabled ?? true,
|
|
1599
|
+
disabled,
|
|
1600
|
+
onChange: (event) => {
|
|
1601
|
+
write("enabled", event.target.checked);
|
|
1602
|
+
}
|
|
1603
|
+
}), t("usage.config.enabled")]
|
|
1604
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1605
|
+
className: usage_module_css_default.settingItem,
|
|
1606
|
+
children: [t("usage.config.pollIntervalSec"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1607
|
+
type: "number",
|
|
1608
|
+
min: 30,
|
|
1609
|
+
max: 3600,
|
|
1610
|
+
value: typeof value.pollIntervalSec === "number" ? value.pollIntervalSec : 60,
|
|
1611
|
+
disabled,
|
|
1612
|
+
onChange: (event) => {
|
|
1613
|
+
const parsed = Number(event.target.value);
|
|
1614
|
+
if (Number.isFinite(parsed) && parsed >= 30 && parsed <= 3600) write("pollIntervalSec", Math.round(parsed));
|
|
1615
|
+
}
|
|
1616
|
+
})]
|
|
1972
1617
|
})]
|
|
1973
|
-
})
|
|
1974
|
-
|
|
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
|
+
]
|
|
1975
1625
|
});
|
|
1976
1626
|
}
|
|
1977
1627
|
//#endregion
|
|
1978
|
-
//#region src/client/
|
|
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
|
+
}
|
|
1979
1636
|
/**
|
|
1980
|
-
*
|
|
1981
|
-
*
|
|
1982
|
-
*
|
|
1983
|
-
* providers show their quota windows and balance-only providers their
|
|
1984
|
-
* remaining amount; collapsed, a one-line strip shows the current session
|
|
1985
|
-
* provider's today usage (tokens, calls, and cost when priced) from the same
|
|
1986
|
-
* /api/dsh-usage/overview document the settings section renders. The open
|
|
1987
|
-
* state lives in the mount (persisted to localStorage) and arrives through
|
|
1988
|
-
* the open face; polling runs at 10s expanded, 30s collapsed, and pauses
|
|
1989
|
-
* while the page is hidden.
|
|
1990
|
-
* @module @linxin666/dsh-usage/client/UsageSidebarPanel
|
|
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.
|
|
1991
1640
|
*/
|
|
1992
|
-
|
|
1993
|
-
|
|
1641
|
+
function subscribeBodyInvalidations(subscriber) {
|
|
1642
|
+
const listener = () => {
|
|
1643
|
+
subscriber();
|
|
1644
|
+
};
|
|
1645
|
+
listener[INVALIDATION_ONLY] = true;
|
|
1646
|
+
return subscribeBodyMutations(listener);
|
|
1647
|
+
}
|
|
1994
1648
|
/**
|
|
1995
|
-
*
|
|
1996
|
-
*
|
|
1997
|
-
*
|
|
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).
|
|
1998
1654
|
*/
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
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
|
+
};
|
|
2005
1714
|
}
|
|
1715
|
+
//#endregion
|
|
1716
|
+
//#region src/client/UsageFootCard.tsx
|
|
2006
1717
|
/**
|
|
2007
|
-
*
|
|
2008
|
-
*
|
|
2009
|
-
*
|
|
2010
|
-
*
|
|
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
|
|
2011
1740
|
*/
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
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;
|
|
2022
1753
|
}
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
"
|
|
2042
|
-
children: [
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
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"
|
|
2047
1780
|
})
|
|
2048
|
-
|
|
2049
|
-
className: usage_module_css_default.sidebarWindowRow,
|
|
2050
|
-
"data-dsh-part": "sidebar-window",
|
|
2051
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: window.name ?? t(`usage.plan.windows.${window.key}`) }), window.percent !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2052
|
-
className: usage_module_css_default.sidebarBar,
|
|
2053
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2054
|
-
className: tone(window.percent),
|
|
2055
|
-
style: { width: `${Math.min(100, Math.max(0, window.percent))}%` }
|
|
2056
|
-
})
|
|
2057
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [window.percent >= 10 ? Math.round(window.percent) : window.percent.toFixed(1), "%"] })] })]
|
|
2058
|
-
}, window.key))]
|
|
1781
|
+
]
|
|
2059
1782
|
});
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
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" })
|
|
2073
1808
|
});
|
|
2074
|
-
|
|
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
|
+
}
|
|
2075
1831
|
}
|
|
2076
1832
|
/**
|
|
2077
|
-
*
|
|
2078
|
-
*
|
|
2079
|
-
*
|
|
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.
|
|
2080
1837
|
*/
|
|
2081
|
-
function
|
|
2082
|
-
|
|
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;
|
|
2083
1897
|
const ui = (0, react.useSyncExternalStore)(store.subscribe, store.getSnapshot);
|
|
2084
|
-
const
|
|
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
|
+
};
|
|
2085
1909
|
(0, react.useEffect)(() => {
|
|
1910
|
+
if (!enabled) return void 0;
|
|
2086
1911
|
poll();
|
|
2087
|
-
const cadence = open ? PANEL_POLL_MS : COLLAPSED_POLL_MS;
|
|
2088
1912
|
let timer;
|
|
2089
1913
|
const start = () => {
|
|
2090
|
-
if (timer === void 0 && document.visibilityState === "visible") timer = window.setInterval(poll,
|
|
1914
|
+
if (timer === void 0 && document.visibilityState === "visible") timer = window.setInterval(poll, FOOT_CARD_POLL_MS);
|
|
2091
1915
|
};
|
|
2092
1916
|
const onVisibility = () => {
|
|
2093
1917
|
if (document.visibilityState === "visible") {
|
|
@@ -2104,128 +1928,194 @@ window.__ModuleLoader__.load({
|
|
|
2104
1928
|
if (timer !== void 0) window.clearInterval(timer);
|
|
2105
1929
|
document.removeEventListener("visibilitychange", onVisibility);
|
|
2106
1930
|
};
|
|
2107
|
-
}, [poll,
|
|
1931
|
+
}, [poll, enabled]);
|
|
1932
|
+
if (!enabled) return null;
|
|
2108
1933
|
const snapshot = ui.snapshot;
|
|
2109
|
-
if (
|
|
2110
|
-
|
|
2111
|
-
|
|
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");
|
|
2112
1946
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2113
|
-
className:
|
|
1947
|
+
className: cardClass(collapsed),
|
|
2114
1948
|
"data-dsh-plugin": "usage",
|
|
2115
|
-
"data-dsh-part": "
|
|
2116
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
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]
|
|
2128
1975
|
});
|
|
2129
1976
|
}
|
|
2130
|
-
const
|
|
2131
|
-
|
|
2132
|
-
|
|
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),
|
|
2133
1981
|
"data-dsh-plugin": "usage",
|
|
2134
|
-
"data-dsh-part": "
|
|
2135
|
-
children:
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
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
|
|
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) })
|
|
2046
|
+
})
|
|
2047
|
+
]
|
|
2048
|
+
}), toggle]
|
|
2145
2049
|
});
|
|
2146
2050
|
}
|
|
2147
|
-
|
|
2148
|
-
|
|
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
|
+
}
|
|
2149
2055
|
/**
|
|
2150
|
-
*
|
|
2151
|
-
*
|
|
2152
|
-
*
|
|
2153
|
-
*
|
|
2154
|
-
*
|
|
2155
|
-
*
|
|
2156
|
-
*
|
|
2157
|
-
* The mount owns the panel's single open state: initialized from localStorage,
|
|
2158
|
-
* flipped by the entry row's toggle controls, and mirrored to the React body
|
|
2159
|
-
* through the subscribe/isOpen face (the same face the entry row's chevron
|
|
2160
|
-
* and highlight read).
|
|
2161
|
-
* @module @linxin666/dsh-usage/client/sidebar-panel-mount
|
|
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.
|
|
2162
2063
|
*/
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
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;
|
|
2171
2077
|
}
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
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);
|
|
2178
2090
|
}
|
|
2179
2091
|
/**
|
|
2180
|
-
* Mount the sidebar
|
|
2181
|
-
* @param props - the store
|
|
2182
|
-
* @returns the
|
|
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.
|
|
2183
2095
|
*/
|
|
2184
|
-
function
|
|
2096
|
+
function mountUsageFootCard(props) {
|
|
2097
|
+
if (typeof document !== "undefined" && document.querySelector("[data-dsh-usage-foot-card]") !== null) return () => {};
|
|
2185
2098
|
const container = document.createElement("div");
|
|
2186
|
-
container.setAttribute("data-dsh-usage-
|
|
2187
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
2188
|
-
let open = !readCollapsed();
|
|
2189
|
-
const emit = () => {
|
|
2190
|
-
for (const listener of [...listeners]) listener();
|
|
2191
|
-
};
|
|
2192
|
-
const openState = {
|
|
2193
|
-
subscribe: (listener) => {
|
|
2194
|
-
listeners.add(listener);
|
|
2195
|
-
return () => {
|
|
2196
|
-
listeners.delete(listener);
|
|
2197
|
-
};
|
|
2198
|
-
},
|
|
2199
|
-
isOpen: () => open
|
|
2200
|
-
};
|
|
2099
|
+
container.setAttribute("data-dsh-usage-foot-card", "");
|
|
2201
2100
|
const root = (0, react_dom_client.createRoot)(container);
|
|
2202
|
-
root.render((0, react.createElement)(
|
|
2203
|
-
|
|
2204
|
-
poll: props.poll,
|
|
2205
|
-
open: openState
|
|
2206
|
-
}));
|
|
2207
|
-
/** Keep the panel as the row directly after the usage entry. */
|
|
2101
|
+
root.render((0, react.createElement)(UsageFootCard, props));
|
|
2102
|
+
/** Keep the container directly above the Settings seat inside the foot area. */
|
|
2208
2103
|
const place = () => {
|
|
2209
|
-
const
|
|
2210
|
-
if (
|
|
2211
|
-
|
|
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);
|
|
2212
2112
|
};
|
|
2213
2113
|
place();
|
|
2214
2114
|
const unsubscribeBody = subscribeBodyInvalidations(place);
|
|
2215
|
-
return {
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
open = !open;
|
|
2220
|
-
writeCollapsed(!open);
|
|
2221
|
-
emit();
|
|
2222
|
-
},
|
|
2223
|
-
dispose: () => {
|
|
2224
|
-
unsubscribeBody();
|
|
2225
|
-
root.unmount();
|
|
2226
|
-
container.remove();
|
|
2227
|
-
listeners.clear();
|
|
2228
|
-
}
|
|
2115
|
+
return () => {
|
|
2116
|
+
unsubscribeBody();
|
|
2117
|
+
root.unmount();
|
|
2118
|
+
container.remove();
|
|
2229
2119
|
};
|
|
2230
2120
|
}
|
|
2231
2121
|
//#endregion
|
|
@@ -2244,7 +2134,7 @@ window.__ModuleLoader__.load({
|
|
|
2244
2134
|
overview: () => usageFetch("/api/dsh-usage/overview", "GET"),
|
|
2245
2135
|
refresh: () => usageFetch("/api/dsh-usage/refresh", "POST")
|
|
2246
2136
|
};
|
|
2247
|
-
/** Settings namespace the section edits (
|
|
2137
|
+
/** Settings namespace the section edits (dsh-web-settings maps it onto this row's profile entry id). */
|
|
2248
2138
|
const USAGE_SETTINGS_NS = "dsh-usage";
|
|
2249
2139
|
/** First-level nav position: directly below the Workshop section (order 150). */
|
|
2250
2140
|
const SECTION_ORDER = 151;
|
|
@@ -2253,7 +2143,7 @@ window.__ModuleLoader__.load({
|
|
|
2253
2143
|
"slots",
|
|
2254
2144
|
"locale",
|
|
2255
2145
|
"connection",
|
|
2256
|
-
"
|
|
2146
|
+
"configForms",
|
|
2257
2147
|
"remote"
|
|
2258
2148
|
];
|
|
2259
2149
|
/**
|
|
@@ -2273,7 +2163,8 @@ window.__ModuleLoader__.load({
|
|
|
2273
2163
|
return () => {};
|
|
2274
2164
|
}
|
|
2275
2165
|
}, "dsh-usage: dictionaries");
|
|
2276
|
-
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);
|
|
2277
2168
|
const store = createUsageStore().create();
|
|
2278
2169
|
let pollSeq = 0;
|
|
2279
2170
|
const poll = () => {
|
|
@@ -2302,28 +2193,22 @@ window.__ModuleLoader__.load({
|
|
|
2302
2193
|
store,
|
|
2303
2194
|
poll,
|
|
2304
2195
|
refresh,
|
|
2305
|
-
settings:
|
|
2196
|
+
settings: settingsForm
|
|
2306
2197
|
});
|
|
2307
|
-
const
|
|
2198
|
+
const disposeFootCard = mountUsageFootCard({
|
|
2308
2199
|
store,
|
|
2309
|
-
poll
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
panel.toggle();
|
|
2314
|
-
},
|
|
2315
|
-
onRefresh: () => {
|
|
2316
|
-
refresh();
|
|
2200
|
+
poll,
|
|
2201
|
+
settings: settingsForm,
|
|
2202
|
+
onOpen: () => {
|
|
2203
|
+
openUsageSettings(() => t("usage.title"));
|
|
2317
2204
|
},
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
}, ctx.locale);
|
|
2205
|
+
locale: ctx.locale
|
|
2206
|
+
});
|
|
2321
2207
|
ctx.effect(() => () => {
|
|
2322
2208
|
try {
|
|
2323
|
-
|
|
2209
|
+
disposeFootCard();
|
|
2324
2210
|
} catch {}
|
|
2325
|
-
|
|
2326
|
-
}, "dsh-usage: sidebar panel");
|
|
2211
|
+
}, "dsh-usage: sidebar foot card");
|
|
2327
2212
|
ctx.slots.inject("settings.section", () => {
|
|
2328
2213
|
try {
|
|
2329
2214
|
const unregister = ctx.slots.register({
|