@iasiv5/dsh-quota-watch 0.0.3 → 0.0.10
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.md +5 -4
- package/lib/client.js +208 -99
- package/package.json +1 -1
- package/src/client.mjs +218 -98
package/README.md
CHANGED
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
## 功能
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
- GLM 摘要行固定显示 5
|
|
11
|
-
- GitHub Copilot 摘要行仅在 GitHub 返回有限、有效的 `remaining` 且带 `percent_remaining`
|
|
9
|
+
- 在侧栏底部区块的最顶部(框架渲染的插件入口与设置行之上)显示一张常显摘要卡(无标题、无刷新按钮):GLM 与 GitHub Copilot 各占一行,统一显示**已用百分比**(Copilot 为 `100 − percent_remaining` 的单值反转),已用超过 80% 进度条转橙、超过 95% 转红;点击行首的 GLM / Copilot 名称立即触发一次宿主探测(等同原刷新按钮),点击行其余区域展开详情。
|
|
10
|
+
- GLM 摘要行固定显示 5 小时窗口已用百分比,行尾等宽小字为当日 token 用量;点击行在侧栏右侧弹出详情面板(半透明毛玻璃底,深浅主题均可读,不改变卡片高度):今日 Tokens / 调用次数双大数字、窗口区为五列对齐网格(名称 | `·` | `已用`/♾️ | 百分比 | 同字号弱色「(重置: …)」,♾️ 与 `已用` 同列左对齐,弱色括注整列左对齐)、虚线分隔的分模型用量行(名称左对齐、token 数右对齐)。窗口按 MCP(月)→ 5 小时 → 每月 → 每周固定排序;接口未下发周窗口时显示「每周额度 · ♾️(无限)」占位,真实周窗口数据一旦下发即自动替换占位。同一时间至多展开一个详情;点击面板外或按 Esc 关闭。
|
|
11
|
+
- GitHub Copilot 摘要行仅在 GitHub 返回有限、有效的 `remaining` 且带 `percent_remaining` 时显示;行尾等宽小字显示本期 credits 消耗的紧凑值。点击在侧栏右侧弹出精简详情(标签左对齐、数值右对齐的三行):可用额度、本期已用(credits 消耗 + 弱化的已用百分比)、重置时间。`credits_used` 绝不用于推算余额,缺失有效百分比时该行不渲染。
|
|
12
|
+
- 卡片顶部不显示全局更新时间;仅在等待首个快照或刷新失败时显示一行状态文字(读取中 / 刷新失败 / 查询失败)。
|
|
12
13
|
- 今日 tokens / 调用次数按宿主本地时区的自然日(`00:00:00 → 当前时刻`)查询;不带滚动窗口语义。
|
|
13
14
|
- 凭据缺失的 provider 不渲染摘要行(两者皆缺时整卡隐藏);探测失败时保留最后一次成功数据并在行上加 ⚠ 过期标记。
|
|
14
|
-
- 宿主默认每 60 秒探测(窗口 + 用量 + Copilot);profile patch 可将 `pollIntervalSec` 设置在 30–3600 秒。浏览器页签可见时每 30
|
|
15
|
+
- 宿主默认每 60 秒探测(窗口 + 用量 + Copilot);profile patch 可将 `pollIntervalSec` 设置在 30–3600 秒。浏览器页签可见时每 30 秒刷新视图,隐藏时暂停;点击行首 provider 名称可立即请求宿主探测。
|
|
15
16
|
|
|
16
17
|
## 兼容与配置
|
|
17
18
|
|
package/lib/client.js
CHANGED
|
@@ -91,80 +91,97 @@ var COPY = {
|
|
|
91
91
|
title: "\u5957\u9910\u76D1\u63A7",
|
|
92
92
|
refresh: "\u5237\u65B0",
|
|
93
93
|
loading: "\u8BFB\u53D6\u4E2D",
|
|
94
|
-
updated: "\u66F4\u65B0",
|
|
95
94
|
failed: "\u67E5\u8BE2\u5931\u8D25",
|
|
96
95
|
stale: "\u6570\u636E\u53EF\u80FD\u5DF2\u8FC7\u671F",
|
|
97
96
|
requestFailed: "\u5237\u65B0\u5931\u8D25\uFF0C\u663E\u793A\u4E0A\u6B21\u6210\u529F\u6570\u636E",
|
|
98
97
|
available: "\u53EF\u7528\u989D\u5EA6",
|
|
99
98
|
used: "\u672C\u671F\u5DF2\u7528",
|
|
100
99
|
reset: "\u91CD\u7F6E",
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
resetTime: "\u91CD\u7F6E\u65F6\u95F4",
|
|
101
|
+
windowUsed: "\u5DF2\u7528",
|
|
102
|
+
todayTokens: "\u4ECA\u65E5 Tokens",
|
|
103
|
+
todayCalls: "\u8C03\u7528\u6B21\u6570",
|
|
104
|
+
weekUnlimited: "\u6BCF\u5468\u989D\u5EA6",
|
|
105
|
+
unlimited: "\u267E\uFE0F",
|
|
106
|
+
unlimitedNote: "\uFF08\u65E0\u9650\uFF09",
|
|
103
107
|
windows: {
|
|
104
|
-
"5h": "5 \u5C0F\u65F6\
|
|
105
|
-
week: "\u6BCF\u5468\
|
|
106
|
-
month: "\u6BCF\u6708\
|
|
107
|
-
mcp: "MCP\uFF08\u6708\uFF09
|
|
108
|
+
"5h": "5 \u5C0F\u65F6\u989D\u5EA6",
|
|
109
|
+
week: "\u6BCF\u5468\u989D\u5EA6",
|
|
110
|
+
month: "\u6BCF\u6708\u989D\u5EA6",
|
|
111
|
+
mcp: "MCP\uFF08\u6708\uFF09"
|
|
108
112
|
}
|
|
109
113
|
},
|
|
110
114
|
en: {
|
|
111
115
|
title: "Quota Watch",
|
|
112
116
|
refresh: "Refresh",
|
|
113
117
|
loading: "Loading",
|
|
114
|
-
updated: "Updated",
|
|
115
118
|
failed: "Query failed",
|
|
116
119
|
stale: "Data may be stale",
|
|
117
120
|
requestFailed: "Refresh failed; showing last successful data",
|
|
118
121
|
available: "Available credits",
|
|
119
122
|
used: "Used this cycle",
|
|
120
123
|
reset: "Resets",
|
|
124
|
+
resetTime: "Reset time",
|
|
125
|
+
windowUsed: "used",
|
|
121
126
|
todayTokens: "Today tokens",
|
|
122
|
-
todayCalls: "Calls
|
|
127
|
+
todayCalls: "Calls",
|
|
128
|
+
weekUnlimited: "Weekly quota",
|
|
129
|
+
unlimited: "\u267E\uFE0F",
|
|
130
|
+
unlimitedNote: " (unlimited)",
|
|
123
131
|
windows: {
|
|
124
|
-
"5h": "5-hour
|
|
125
|
-
week: "Weekly
|
|
126
|
-
month: "Monthly
|
|
127
|
-
mcp: "MCP (month)
|
|
132
|
+
"5h": "5-hour quota",
|
|
133
|
+
week: "Weekly quota",
|
|
134
|
+
month: "Monthly quota",
|
|
135
|
+
mcp: "MCP (month)"
|
|
128
136
|
}
|
|
129
137
|
}
|
|
130
138
|
};
|
|
131
139
|
var STYLE_TEXT = `
|
|
132
140
|
[data-dsh-quota-watch-card] { box-sizing: border-box; width: 100%; margin: 0 0 8px; color: var(--dsw-alias-label-primary, inherit); font: inherit; }
|
|
133
141
|
[data-dsh-quota-watch-card] *, [data-dsh-quota-watch-card] *::before, [data-dsh-quota-watch-card] *::after { box-sizing: border-box; }
|
|
134
|
-
.dqw-card { border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 10px; background: var(--dsw-alias-bg-base, rgba(128,128,128,.08)); padding: 8px; display: flex; flex-direction: column; gap:
|
|
135
|
-
.dqw-head { display: flex; align-items: center; gap: 4px; min-width: 0; }
|
|
136
|
-
.dqw-title { flex: 1; min-width: 0; margin: 0; font-size: 12px; font-weight: 600; line-height: 18px; }
|
|
137
|
-
.dqw-button { border: 0; border-radius: 6px; background: transparent; color: inherit; cursor: pointer; font: inherit; font-size: 11px; line-height: 18px; padding: 1px 4px; }
|
|
138
|
-
.dqw-button:hover { background: var(--dsw-alias-bg-hover, rgba(128,128,128,.16)); }
|
|
139
|
-
.dqw-button:disabled { cursor: wait; opacity: .6; }
|
|
142
|
+
.dqw-card { border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 10px; background: var(--dsw-alias-bg-base, rgba(128,128,128,.08)); padding: 5px 8px; display: flex; flex-direction: column; gap: 2px; }
|
|
140
143
|
.dqw-meta { margin: 0; font-size: 10px; line-height: 14px; opacity: .7; }
|
|
141
144
|
.dqw-body { display: flex; flex-direction: column; }
|
|
142
|
-
.dqw-row-summary { display: flex; align-items: center; gap: 6px; padding:
|
|
145
|
+
.dqw-row-summary { display: flex; align-items: center; gap: 6px; padding: 3px 6px; margin: 0 -2px; border-radius: 8px; }
|
|
143
146
|
.dqw-row-summary[data-expandable] { cursor: pointer; }
|
|
144
147
|
.dqw-row-summary[data-expandable]:hover { background: var(--dsw-alias-bg-hover, rgba(128,128,128,.12)); }
|
|
145
148
|
.dqw-row-summary:focus-visible { outline: 1px solid var(--dsw-alias-button-primary-fill, #5b8def); outline-offset: -1px; }
|
|
146
149
|
.dqw-row-summary.is-error { cursor: default; }
|
|
147
150
|
.dqw-label { font-size: 11px; font-weight: 600; width: 52px; flex: none; }
|
|
151
|
+
.dqw-label[data-action="refresh"] { cursor: pointer; border-radius: 4px; }
|
|
152
|
+
.dqw-label[data-action="refresh"]:hover { opacity: .75; }
|
|
148
153
|
.dqw-bar { display: block; flex: 1; height: 4px; overflow: hidden; border-radius: 4px; background: var(--dsw-alias-bg-tertiary, rgba(128,128,128,.2)); }
|
|
149
154
|
.dqw-bar-fill { display: block; height: 100%; border-radius: inherit; background: var(--dsw-alias-button-primary-fill, #5b8def); }
|
|
150
155
|
.dqw-bar-fill.warn { background: var(--dsw-alias-label-warning, #d29922); }
|
|
151
156
|
.dqw-bar-fill.danger { background: var(--dsw-alias-label-danger, #c93c3c); }
|
|
152
157
|
.dqw-pct { font-size: 10.5px; line-height: 14px; font-variant-numeric: tabular-nums; width: 30px; text-align: right; flex: none; }
|
|
153
|
-
.dqw-extra { font-size: 10px; line-height: 14px; opacity: .72; font-variant-numeric: tabular-nums; flex: none; }
|
|
158
|
+
.dqw-extra { font-size: 10px; line-height: 14px; opacity: .72; font-variant-numeric: tabular-nums; flex: none; min-width: 34px; text-align: right; }
|
|
154
159
|
.dqw-chev { font-size: 9px; line-height: 14px; opacity: .6; flex: none; width: 10px; text-align: center; }
|
|
155
160
|
.dqw-stale-mark { flex: none; font-size: 9px; line-height: 14px; color: var(--dsw-alias-label-warning, #b46900); cursor: help; }
|
|
156
161
|
.dqw-errtext { font-size: 10px; line-height: 14px; opacity: .7; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
157
|
-
.dqw-
|
|
162
|
+
.dqw-pop { position: fixed; z-index: 2147483000; min-width: 232px; max-width: 320px; padding: 10px 12px; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 10px; background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); background: color-mix(in srgb, var(--dsw-alias-bg-base, #1f1f1f) 86%, transparent); -webkit-backdrop-filter: blur(14px) saturate(1.3); backdrop-filter: blur(14px) saturate(1.3); box-shadow: 0 8px 24px rgba(0,0,0,.25); color: var(--dsw-alias-label-primary, inherit); display: flex; flex-direction: column; gap: 8px; font: inherit; }
|
|
163
|
+
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { .dqw-pop { background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); } }
|
|
164
|
+
.dqw-pop[hidden] { display: none; }
|
|
165
|
+
.dqw-pop *, .dqw-pop *::before, .dqw-pop *::after { box-sizing: border-box; }
|
|
158
166
|
.dqw-bignums { display: flex; gap: 6px; }
|
|
159
|
-
.dqw-big { flex: 1; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.25)); border-radius: 8px; padding:
|
|
160
|
-
.dqw-big .dqw-big-value { font-size:
|
|
167
|
+
.dqw-big { flex: 1; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.25)); border-radius: 8px; padding: 5px 8px; }
|
|
168
|
+
.dqw-big .dqw-big-value { font-size: 15px; font-weight: 650; line-height: 19px; font-variant-numeric: tabular-nums; }
|
|
161
169
|
.dqw-big .dqw-big-label { font-size: 9.5px; line-height: 13px; opacity: .65; }
|
|
162
|
-
.dqw-row { display: flex; flex-direction: column; gap: 2px; }
|
|
163
|
-
.dqw-row-head { display: flex; justify-content: space-between; gap: 5px; font-size: 10px; line-height: 14px; }
|
|
164
170
|
.dqw-muted { margin: 0; font-size: 10px; line-height: 14px; opacity: .7; }
|
|
165
|
-
.dqw-models {
|
|
171
|
+
.dqw-models { border-top: 1px dashed var(--dsw-alias-border-secondary, rgba(128,128,128,.25)); padding-top: 6px; display: flex; flex-direction: column; gap: 3px; }
|
|
172
|
+
.dqw-model-row { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; font-size: 10.5px; line-height: 15px; font-variant-numeric: tabular-nums; }
|
|
173
|
+
.dqw-model-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; opacity: .85; }
|
|
174
|
+
.dqw-model-value { flex: none; font-weight: 600; }
|
|
175
|
+
.dqw-kv { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; font-size: 10.5px; line-height: 17px; font-variant-numeric: tabular-nums; }
|
|
176
|
+
.dqw-kv-label { flex: none; opacity: .65; }
|
|
177
|
+
.dqw-kv-value { min-width: 0; text-align: right; font-weight: 600; }
|
|
178
|
+
.dqw-kv-sub { font-weight: 400; opacity: .55; }
|
|
179
|
+
.dqw-wins { display: grid; grid-template-columns: max-content max-content max-content max-content 1fr; column-gap: 6px; row-gap: 3px; align-items: baseline; font-size: 10.5px; line-height: 17px; font-variant-numeric: tabular-nums; }
|
|
180
|
+
.dqw-win-name { opacity: .65; }
|
|
181
|
+
.dqw-win-mid { opacity: .65; white-space: nowrap; }
|
|
182
|
+
.dqw-win-used { opacity: .65; white-space: nowrap; }
|
|
183
|
+
.dqw-win-val { min-width: 0; font-weight: 600; }
|
|
166
184
|
.dqw-error { margin: 0; font-size: 10px; line-height: 14px; color: var(--dsw-alias-label-warning, #b46900); }
|
|
167
|
-
.dqw-credit { margin: 0; font-size: 10px; line-height: 14px; font-variant-numeric: tabular-nums; }
|
|
168
185
|
`;
|
|
169
186
|
function localeFor(win) {
|
|
170
187
|
return String(win?.navigator?.language ?? "zh").toLowerCase().startsWith("zh") ? "zh" : "en";
|
|
@@ -201,23 +218,10 @@ function text(doc, tag, className, value) {
|
|
|
201
218
|
node.textContent = String(value ?? "");
|
|
202
219
|
return node;
|
|
203
220
|
}
|
|
204
|
-
function
|
|
221
|
+
function kvRow(doc, label, value) {
|
|
205
222
|
const row = doc.createElement("div");
|
|
206
|
-
row.className = "dqw-
|
|
207
|
-
|
|
208
|
-
head.className = "dqw-row-head";
|
|
209
|
-
head.append(text(doc, "span", "", label), text(doc, "span", "", formatPercent(percent, locale)));
|
|
210
|
-
const bar = doc.createElement("span");
|
|
211
|
-
bar.className = "dqw-bar";
|
|
212
|
-
bar.setAttribute("role", "progressbar");
|
|
213
|
-
bar.setAttribute("aria-valuemin", "0");
|
|
214
|
-
bar.setAttribute("aria-valuemax", "100");
|
|
215
|
-
bar.setAttribute("aria-valuenow", String(Math.max(0, Math.min(100, percent))));
|
|
216
|
-
const fill = doc.createElement("span");
|
|
217
|
-
fill.className = `dqw-bar-fill${percent > 95 ? " danger" : percent > 80 ? " warn" : ""}`;
|
|
218
|
-
fill.style.width = `${Math.max(0, Math.min(100, percent))}%`;
|
|
219
|
-
bar.append(fill);
|
|
220
|
-
row.append(head, bar);
|
|
223
|
+
row.className = "dqw-kv";
|
|
224
|
+
row.append(text(doc, "span", "dqw-kv-label", label), text(doc, "span", "dqw-kv-value", value));
|
|
221
225
|
return row;
|
|
222
226
|
}
|
|
223
227
|
function glmRowModel(provider) {
|
|
@@ -248,7 +252,8 @@ function copilotRowModel(provider) {
|
|
|
248
252
|
label: "Copilot",
|
|
249
253
|
percent: used,
|
|
250
254
|
stale: provider.status === "stale",
|
|
251
|
-
error: provider.error
|
|
255
|
+
error: provider.error,
|
|
256
|
+
extra: typeof quota.creditsUsed === "number" && quota.creditsUsed >= 0 ? quota.creditsUsed : void 0
|
|
252
257
|
};
|
|
253
258
|
}
|
|
254
259
|
function footArea(doc) {
|
|
@@ -284,22 +289,18 @@ function mountQuotaCard({
|
|
|
284
289
|
const card = doc.createElement("section");
|
|
285
290
|
card.className = "dqw-card";
|
|
286
291
|
card.setAttribute("aria-label", copy.title);
|
|
287
|
-
const header = doc.createElement("header");
|
|
288
|
-
header.className = "dqw-head";
|
|
289
|
-
header.append(text(doc, "h3", "dqw-title", copy.title));
|
|
290
|
-
const refreshButton = text(doc, "button", "dqw-button", "\u21BB");
|
|
291
|
-
refreshButton.type = "button";
|
|
292
|
-
refreshButton.title = copy.refresh;
|
|
293
|
-
refreshButton.setAttribute("aria-label", copy.refresh);
|
|
294
|
-
refreshButton.dataset.action = "refresh";
|
|
295
|
-
header.append(refreshButton);
|
|
296
292
|
const body = doc.createElement("div");
|
|
297
293
|
body.className = "dqw-body";
|
|
298
294
|
const lastUpdated = text(doc, "p", "dqw-meta", copy.loading);
|
|
299
295
|
lastUpdated.dataset.role = "updated";
|
|
300
296
|
body.append(lastUpdated);
|
|
301
|
-
card.append(
|
|
297
|
+
card.append(body);
|
|
302
298
|
container.append(style, card);
|
|
299
|
+
const pop = doc.createElement("div");
|
|
300
|
+
pop.className = "dqw-pop";
|
|
301
|
+
pop.dataset.dshQuotaWatchPop = "";
|
|
302
|
+
pop.hidden = true;
|
|
303
|
+
doc.body.append(pop);
|
|
303
304
|
let snapshot;
|
|
304
305
|
let requestSequence = 0;
|
|
305
306
|
let timer;
|
|
@@ -307,12 +308,15 @@ function mountQuotaCard({
|
|
|
307
308
|
let queuedPlace = false;
|
|
308
309
|
let openKey;
|
|
309
310
|
const fillClass = (percent) => percent > 95 ? "dqw-bar-fill danger" : percent > 80 ? "dqw-bar-fill warn" : "dqw-bar-fill";
|
|
310
|
-
const renderProvider = (key, model
|
|
311
|
+
const renderProvider = (key, model) => {
|
|
311
312
|
if (model === null) return null;
|
|
312
313
|
const row = doc.createElement("div");
|
|
313
314
|
row.className = "dqw-row-summary";
|
|
314
315
|
row.dataset.dshQuotaWatchRow = key;
|
|
315
|
-
|
|
316
|
+
const label = text(doc, "span", "dqw-label", model.label);
|
|
317
|
+
label.dataset.action = "refresh";
|
|
318
|
+
label.title = `${model.label} \xB7 ${copy.refresh}`;
|
|
319
|
+
row.append(label);
|
|
316
320
|
if (model.kind === "error") {
|
|
317
321
|
row.classList.add("is-error");
|
|
318
322
|
row.append(text(doc, "span", "dqw-errtext", model.error ? `${copy.failed}: ${model.error}` : copy.failed));
|
|
@@ -347,11 +351,7 @@ function mountQuotaCard({
|
|
|
347
351
|
row.append(extra);
|
|
348
352
|
}
|
|
349
353
|
row.append(text(doc, "span", "dqw-chev", openKey === key ? "\u25BE" : "\u25B8"));
|
|
350
|
-
|
|
351
|
-
detail.className = "dqw-detail";
|
|
352
|
-
detail.dataset.dshQuotaWatchDetail = key;
|
|
353
|
-
if (openKey === key) renderDetail(detail, key, provider);
|
|
354
|
-
return [row, detail];
|
|
354
|
+
return [row];
|
|
355
355
|
};
|
|
356
356
|
function renderDetail(detail, key, provider) {
|
|
357
357
|
detail.replaceChildren();
|
|
@@ -376,42 +376,124 @@ function mountQuotaCard({
|
|
|
376
376
|
);
|
|
377
377
|
numbers.append(tokensBig, callsBig);
|
|
378
378
|
detail.append(numbers);
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
379
|
+
const raw = Array.isArray(provider?.plan?.windows) ? provider.plan.windows : [];
|
|
380
|
+
const order = { mcp: 0, "5h": 1, month: 2, week: 3 };
|
|
381
|
+
const sorted = [...raw].sort((a, b) => (order[a?.key] ?? 99) - (order[b?.key] ?? 99));
|
|
382
|
+
const items = [];
|
|
383
|
+
let weekPlaced = false;
|
|
384
|
+
for (const window of sorted) {
|
|
385
|
+
if (!weekPlaced && (order[window?.key] ?? 99) > order.week) {
|
|
386
|
+
items.push({ placeholder: true });
|
|
387
|
+
weekPlaced = true;
|
|
388
|
+
}
|
|
389
|
+
if (window?.key === "week") weekPlaced = true;
|
|
390
|
+
items.push({ window });
|
|
391
|
+
}
|
|
392
|
+
if (!weekPlaced) items.push({ placeholder: true });
|
|
393
|
+
const wins = doc.createElement("div");
|
|
394
|
+
wins.className = "dqw-wins";
|
|
395
|
+
for (const item of items) {
|
|
396
|
+
if (item.placeholder) {
|
|
397
|
+
wins.append(
|
|
398
|
+
text(doc, "span", "dqw-win-name", copy.weekUnlimited),
|
|
399
|
+
text(doc, "span", "dqw-win-mid", "\xB7"),
|
|
400
|
+
text(doc, "span", "dqw-win-used", copy.unlimited),
|
|
401
|
+
text(doc, "span", ""),
|
|
402
|
+
text(doc, "span", "dqw-kv-sub", copy.unlimitedNote)
|
|
403
|
+
);
|
|
404
|
+
continue;
|
|
385
405
|
}
|
|
386
|
-
const
|
|
387
|
-
|
|
406
|
+
const window = item.window;
|
|
407
|
+
const percent = typeof window?.percent === "number" && Number.isFinite(window.percent) ? window.percent : void 0;
|
|
408
|
+
const reset = formatTime(window?.resetsAt, locale);
|
|
409
|
+
wins.append(text(doc, "span", "dqw-win-name", copy.windows[window?.key] ?? window?.key));
|
|
410
|
+
wins.append(text(doc, "span", "dqw-win-mid", "\xB7"));
|
|
411
|
+
wins.append(text(doc, "span", "dqw-win-used", copy.windowUsed));
|
|
412
|
+
wins.append(text(doc, "span", "dqw-win-val", percent !== void 0 ? formatPercent(percent, locale) : "\u2014"));
|
|
413
|
+
wins.append(text(doc, "span", "dqw-kv-sub", reset ? `\uFF08${copy.reset}: ${reset}\uFF09` : ""));
|
|
414
|
+
}
|
|
415
|
+
detail.append(wins);
|
|
416
|
+
if (Array.isArray(usage?.models) && usage.models.length > 0) {
|
|
417
|
+
const models = doc.createElement("div");
|
|
418
|
+
models.className = "dqw-models";
|
|
419
|
+
for (const model of usage.models) {
|
|
420
|
+
const row = doc.createElement("div");
|
|
421
|
+
row.className = "dqw-model-row";
|
|
422
|
+
row.append(
|
|
423
|
+
text(doc, "span", "dqw-model-name", model?.name ?? "\u2014"),
|
|
424
|
+
text(doc, "span", "dqw-model-value", formatCompact(model?.tokens, locale))
|
|
425
|
+
);
|
|
426
|
+
models.append(row);
|
|
427
|
+
}
|
|
428
|
+
detail.append(models);
|
|
388
429
|
}
|
|
389
|
-
const models = Array.isArray(usage?.models) && usage.models.length > 0 ? usage.models.map((model) => `${model.name} ${formatCompact(model.tokens, locale)}`).join(" \xB7 ") : void 0;
|
|
390
|
-
if (models) detail.append(text(doc, "p", "dqw-models", models));
|
|
391
|
-
const updated = formatTime(provider?.updatedAt ? new Date(provider.updatedAt).toISOString() : "", locale);
|
|
392
|
-
if (updated) detail.append(text(doc, "p", "dqw-meta", `${copy.updated}: ${updated}`));
|
|
393
430
|
return;
|
|
394
431
|
}
|
|
395
432
|
const quota = provider?.quota;
|
|
396
433
|
if (quota?.balanceAvailable === true) {
|
|
397
|
-
detail.append(
|
|
434
|
+
detail.append(kvRow(doc, copy.available, `${formatNumber(quota.remaining, locale)} / ${formatNumber(quota.entitlement, locale)}`));
|
|
398
435
|
const used = remainingToUsed(quota.percentRemaining);
|
|
399
|
-
if (used !== void 0) detail.append(progress(doc, copy.used, used, locale));
|
|
400
436
|
if (typeof quota.creditsUsed === "number" && quota.creditsUsed >= 0) {
|
|
401
|
-
|
|
437
|
+
const row = doc.createElement("div");
|
|
438
|
+
row.className = "dqw-kv";
|
|
439
|
+
const value = doc.createElement("span");
|
|
440
|
+
value.className = "dqw-kv-value";
|
|
441
|
+
value.append(text(doc, "span", "", formatNumber(quota.creditsUsed, locale)));
|
|
442
|
+
if (used !== void 0) value.append(text(doc, "span", "dqw-kv-sub", ` \xB7 ${formatPercent(used, locale)}`));
|
|
443
|
+
row.append(text(doc, "span", "dqw-kv-label", copy.used), value);
|
|
444
|
+
detail.append(row);
|
|
402
445
|
}
|
|
446
|
+
const reset = formatTime(quota?.resetsAt, locale);
|
|
447
|
+
if (reset) detail.append(kvRow(doc, copy.resetTime, reset));
|
|
403
448
|
} else {
|
|
404
449
|
detail.append(text(doc, "p", "dqw-muted", copy.stale));
|
|
405
450
|
}
|
|
406
|
-
const reset = formatTime(quota?.resetsAt, locale);
|
|
407
|
-
if (reset) detail.append(text(doc, "p", "dqw-muted", `${copy.reset}: ${reset}`));
|
|
408
451
|
}
|
|
452
|
+
const positionPop = () => {
|
|
453
|
+
if (pop.hidden || openKey === void 0) return;
|
|
454
|
+
const row = body.querySelector(`[data-dsh-quota-watch-row="${openKey}"]`);
|
|
455
|
+
if (!row) return;
|
|
456
|
+
const rect = row.getBoundingClientRect();
|
|
457
|
+
const viewWidth = win.innerWidth ?? 1024;
|
|
458
|
+
const viewHeight = win.innerHeight ?? 768;
|
|
459
|
+
const width = pop.offsetWidth || 240;
|
|
460
|
+
const height = pop.offsetHeight || 160;
|
|
461
|
+
let left = rect.right + 8;
|
|
462
|
+
if (left + width > viewWidth - 8) left = rect.left - width - 8;
|
|
463
|
+
if (left + width > viewWidth - 8) left = viewWidth - width - 8;
|
|
464
|
+
left = Math.max(8, left);
|
|
465
|
+
const top = Math.min(Math.max(rect.top, 8), Math.max(8, viewHeight - height - 8));
|
|
466
|
+
pop.style.left = `${Math.round(left)}px`;
|
|
467
|
+
pop.style.top = `${Math.round(top)}px`;
|
|
468
|
+
};
|
|
469
|
+
const syncPop = () => {
|
|
470
|
+
if (openKey === void 0 || disposed) {
|
|
471
|
+
if (!pop.hidden) {
|
|
472
|
+
pop.hidden = true;
|
|
473
|
+
pop.replaceChildren();
|
|
474
|
+
delete pop.dataset.dshQuotaWatchDetail;
|
|
475
|
+
}
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
const provider = (snapshot?.providers ?? []).find((item) => item?.key === openKey);
|
|
479
|
+
const row = body.querySelector(`[data-dsh-quota-watch-row="${openKey}"]`);
|
|
480
|
+
if (!provider || !row || !row.hasAttribute("data-expandable")) {
|
|
481
|
+
openKey = void 0;
|
|
482
|
+
syncPop();
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
renderDetail(pop, openKey, provider);
|
|
486
|
+
pop.dataset.dshQuotaWatchDetail = openKey;
|
|
487
|
+
pop.setAttribute("aria-label", openKey === "glm" ? "GLM" : "Copilot");
|
|
488
|
+
pop.hidden = false;
|
|
489
|
+
positionPop();
|
|
490
|
+
};
|
|
409
491
|
const renderAll = () => {
|
|
410
492
|
const providers = snapshot?.providers ?? [];
|
|
411
493
|
const glm = providers.find((provider) => provider?.key === "glm");
|
|
412
494
|
const copilot = providers.find((provider) => provider?.key === "copilot");
|
|
413
|
-
const glmParts = renderProvider("glm", glmRowModel(glm)
|
|
414
|
-
const copilotParts = renderProvider("copilot", copilotRowModel(copilot)
|
|
495
|
+
const glmParts = renderProvider("glm", glmRowModel(glm));
|
|
496
|
+
const copilotParts = renderProvider("copilot", copilotRowModel(copilot));
|
|
415
497
|
const fragment = doc.createDocumentFragment();
|
|
416
498
|
fragment.append(lastUpdated);
|
|
417
499
|
if (glmParts) fragment.append(...glmParts);
|
|
@@ -419,37 +501,37 @@ function mountQuotaCard({
|
|
|
419
501
|
body.replaceChildren(fragment);
|
|
420
502
|
container.hidden = glmParts === null && copilotParts === null;
|
|
421
503
|
const updated = formatTime(snapshot?.updatedAt ? new Date(snapshot.updatedAt).toISOString() : "", locale);
|
|
422
|
-
|
|
504
|
+
if (updated) {
|
|
505
|
+
lastUpdated.hidden = true;
|
|
506
|
+
} else {
|
|
507
|
+
lastUpdated.hidden = false;
|
|
508
|
+
lastUpdated.textContent = copy.loading;
|
|
509
|
+
}
|
|
510
|
+
syncPop();
|
|
423
511
|
};
|
|
424
512
|
const render = (next) => {
|
|
425
513
|
snapshot = next;
|
|
426
514
|
renderAll();
|
|
427
515
|
};
|
|
428
516
|
const showTransportError = () => {
|
|
429
|
-
|
|
430
|
-
|
|
517
|
+
lastUpdated.hidden = false;
|
|
518
|
+
lastUpdated.textContent = snapshot ? copy.requestFailed : copy.failed;
|
|
431
519
|
};
|
|
432
520
|
const poll = async (force) => {
|
|
433
521
|
if (disposed) return;
|
|
434
522
|
const sequence = ++requestSequence;
|
|
435
|
-
refreshButton.disabled = force === true;
|
|
436
523
|
try {
|
|
437
524
|
const next = await fetchJson(fetchImpl, force ? CLIENT_ROUTES.refresh : CLIENT_ROUTES.overview, force ? "POST" : "GET");
|
|
438
525
|
if (sequence === requestSequence && !disposed) render(next);
|
|
439
526
|
} catch {
|
|
440
527
|
if (sequence === requestSequence && !disposed) showTransportError();
|
|
441
|
-
} finally {
|
|
442
|
-
if (!disposed && sequence === requestSequence) refreshButton.disabled = false;
|
|
443
528
|
}
|
|
444
529
|
};
|
|
445
530
|
const place = () => {
|
|
446
531
|
const foot = footArea(doc);
|
|
447
532
|
if (!foot) return;
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
if (container.parentElement !== foot || container.nextElementSibling !== settings) foot.insertBefore(container, settings);
|
|
451
|
-
} else if (container.parentElement !== foot || foot.lastElementChild !== container) {
|
|
452
|
-
foot.append(container);
|
|
533
|
+
if (container.parentElement !== foot || foot.firstElementChild !== container) {
|
|
534
|
+
foot.insertBefore(container, foot.firstChild);
|
|
453
535
|
}
|
|
454
536
|
};
|
|
455
537
|
const schedulePlace = () => {
|
|
@@ -465,9 +547,9 @@ function mountQuotaCard({
|
|
|
465
547
|
place();
|
|
466
548
|
const startPolling = () => {
|
|
467
549
|
if (timer !== void 0 || doc.visibilityState === "hidden" || disposed) return;
|
|
468
|
-
void poll(
|
|
550
|
+
void poll();
|
|
469
551
|
timer = win.setInterval(() => {
|
|
470
|
-
void poll(
|
|
552
|
+
void poll();
|
|
471
553
|
}, pollIntervalMs);
|
|
472
554
|
};
|
|
473
555
|
const stopPolling = () => {
|
|
@@ -478,14 +560,15 @@ function mountQuotaCard({
|
|
|
478
560
|
if (doc.visibilityState === "hidden") stopPolling();
|
|
479
561
|
else startPolling();
|
|
480
562
|
};
|
|
481
|
-
const onRefresh = () => {
|
|
482
|
-
void poll(true);
|
|
483
|
-
};
|
|
484
563
|
const toggleRow = (key) => {
|
|
485
564
|
openKey = openKey === key ? void 0 : key;
|
|
486
565
|
renderAll();
|
|
487
566
|
};
|
|
488
567
|
const onBodyClick = (event) => {
|
|
568
|
+
if (event.target?.closest?.('[data-action="refresh"]')) {
|
|
569
|
+
void poll(true);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
489
572
|
const row = event.target?.closest?.("[data-dsh-quota-watch-row][data-expandable]");
|
|
490
573
|
if (row) toggleRow(row.dataset.dshQuotaWatchRow);
|
|
491
574
|
};
|
|
@@ -497,20 +580,46 @@ function mountQuotaCard({
|
|
|
497
580
|
toggleRow(row.dataset.dshQuotaWatchRow);
|
|
498
581
|
}
|
|
499
582
|
};
|
|
500
|
-
|
|
583
|
+
const onDocPointerDown = (event) => {
|
|
584
|
+
if (openKey === void 0) return;
|
|
585
|
+
const target = event.target;
|
|
586
|
+
if (pop.contains(target) || target?.closest?.("[data-dsh-quota-watch-row]") || target?.closest?.(CARD_SELECTOR)) return;
|
|
587
|
+
openKey = void 0;
|
|
588
|
+
renderAll();
|
|
589
|
+
};
|
|
590
|
+
const onDocScroll = () => {
|
|
591
|
+
positionPop();
|
|
592
|
+
};
|
|
593
|
+
const onWinResize = () => {
|
|
594
|
+
positionPop();
|
|
595
|
+
};
|
|
596
|
+
const onDocKeydown = (event) => {
|
|
597
|
+
if (event.key === "Escape" && openKey !== void 0) {
|
|
598
|
+
openKey = void 0;
|
|
599
|
+
renderAll();
|
|
600
|
+
}
|
|
601
|
+
};
|
|
501
602
|
body.addEventListener("click", onBodyClick);
|
|
502
603
|
body.addEventListener("keydown", onBodyKeydown);
|
|
503
604
|
doc.addEventListener("visibilitychange", onVisibilityChange);
|
|
605
|
+
doc.addEventListener("pointerdown", onDocPointerDown, true);
|
|
606
|
+
doc.addEventListener("scroll", onDocScroll, true);
|
|
607
|
+
doc.addEventListener("keydown", onDocKeydown);
|
|
608
|
+
win.addEventListener("resize", onWinResize);
|
|
504
609
|
startPolling();
|
|
505
610
|
return () => {
|
|
506
611
|
disposed = true;
|
|
507
612
|
stopPolling();
|
|
508
613
|
observer.disconnect();
|
|
509
614
|
doc.removeEventListener("visibilitychange", onVisibilityChange);
|
|
510
|
-
|
|
615
|
+
doc.removeEventListener("pointerdown", onDocPointerDown, true);
|
|
616
|
+
doc.removeEventListener("scroll", onDocScroll, true);
|
|
617
|
+
doc.removeEventListener("keydown", onDocKeydown);
|
|
618
|
+
win.removeEventListener("resize", onWinResize);
|
|
511
619
|
body.removeEventListener("click", onBodyClick);
|
|
512
620
|
body.removeEventListener("keydown", onBodyKeydown);
|
|
513
621
|
container.remove();
|
|
622
|
+
pop.remove();
|
|
514
623
|
};
|
|
515
624
|
}
|
|
516
625
|
function apply(ctx) {
|
package/package.json
CHANGED
package/src/client.mjs
CHANGED
|
@@ -12,40 +12,48 @@ const COPY = {
|
|
|
12
12
|
title: '套餐监控',
|
|
13
13
|
refresh: '刷新',
|
|
14
14
|
loading: '读取中',
|
|
15
|
-
updated: '更新',
|
|
16
15
|
failed: '查询失败',
|
|
17
16
|
stale: '数据可能已过期',
|
|
18
17
|
requestFailed: '刷新失败,显示上次成功数据',
|
|
19
18
|
available: '可用额度',
|
|
20
19
|
used: '本期已用',
|
|
21
20
|
reset: '重置',
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
resetTime: '重置时间',
|
|
22
|
+
windowUsed: '已用',
|
|
23
|
+
todayTokens: '今日 Tokens',
|
|
24
|
+
todayCalls: '调用次数',
|
|
25
|
+
weekUnlimited: '每周额度',
|
|
26
|
+
unlimited: '♾️',
|
|
27
|
+
unlimitedNote: '(无限)',
|
|
24
28
|
windows: {
|
|
25
|
-
'5h': '5
|
|
26
|
-
week: '
|
|
27
|
-
month: '
|
|
28
|
-
mcp: 'MCP
|
|
29
|
+
'5h': '5 小时额度',
|
|
30
|
+
week: '每周额度',
|
|
31
|
+
month: '每月额度',
|
|
32
|
+
mcp: 'MCP(月)',
|
|
29
33
|
},
|
|
30
34
|
},
|
|
31
35
|
en: {
|
|
32
36
|
title: 'Quota Watch',
|
|
33
37
|
refresh: 'Refresh',
|
|
34
38
|
loading: 'Loading',
|
|
35
|
-
updated: 'Updated',
|
|
36
39
|
failed: 'Query failed',
|
|
37
40
|
stale: 'Data may be stale',
|
|
38
41
|
requestFailed: 'Refresh failed; showing last successful data',
|
|
39
42
|
available: 'Available credits',
|
|
40
43
|
used: 'Used this cycle',
|
|
41
44
|
reset: 'Resets',
|
|
45
|
+
resetTime: 'Reset time',
|
|
46
|
+
windowUsed: 'used',
|
|
42
47
|
todayTokens: 'Today tokens',
|
|
43
|
-
todayCalls: 'Calls
|
|
48
|
+
todayCalls: 'Calls',
|
|
49
|
+
weekUnlimited: 'Weekly quota',
|
|
50
|
+
unlimited: '♾️',
|
|
51
|
+
unlimitedNote: ' (unlimited)',
|
|
44
52
|
windows: {
|
|
45
|
-
'5h': '5-hour
|
|
46
|
-
week: 'Weekly
|
|
47
|
-
month: 'Monthly
|
|
48
|
-
mcp: 'MCP (month)
|
|
53
|
+
'5h': '5-hour quota',
|
|
54
|
+
week: 'Weekly quota',
|
|
55
|
+
month: 'Monthly quota',
|
|
56
|
+
mcp: 'MCP (month)',
|
|
49
57
|
},
|
|
50
58
|
},
|
|
51
59
|
}
|
|
@@ -53,40 +61,49 @@ const COPY = {
|
|
|
53
61
|
const STYLE_TEXT = `
|
|
54
62
|
[data-dsh-quota-watch-card] { box-sizing: border-box; width: 100%; margin: 0 0 8px; color: var(--dsw-alias-label-primary, inherit); font: inherit; }
|
|
55
63
|
[data-dsh-quota-watch-card] *, [data-dsh-quota-watch-card] *::before, [data-dsh-quota-watch-card] *::after { box-sizing: border-box; }
|
|
56
|
-
.dqw-card { border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 10px; background: var(--dsw-alias-bg-base, rgba(128,128,128,.08)); padding: 8px; display: flex; flex-direction: column; gap:
|
|
57
|
-
.dqw-head { display: flex; align-items: center; gap: 4px; min-width: 0; }
|
|
58
|
-
.dqw-title { flex: 1; min-width: 0; margin: 0; font-size: 12px; font-weight: 600; line-height: 18px; }
|
|
59
|
-
.dqw-button { border: 0; border-radius: 6px; background: transparent; color: inherit; cursor: pointer; font: inherit; font-size: 11px; line-height: 18px; padding: 1px 4px; }
|
|
60
|
-
.dqw-button:hover { background: var(--dsw-alias-bg-hover, rgba(128,128,128,.16)); }
|
|
61
|
-
.dqw-button:disabled { cursor: wait; opacity: .6; }
|
|
64
|
+
.dqw-card { border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 10px; background: var(--dsw-alias-bg-base, rgba(128,128,128,.08)); padding: 5px 8px; display: flex; flex-direction: column; gap: 2px; }
|
|
62
65
|
.dqw-meta { margin: 0; font-size: 10px; line-height: 14px; opacity: .7; }
|
|
63
66
|
.dqw-body { display: flex; flex-direction: column; }
|
|
64
|
-
.dqw-row-summary { display: flex; align-items: center; gap: 6px; padding:
|
|
67
|
+
.dqw-row-summary { display: flex; align-items: center; gap: 6px; padding: 3px 6px; margin: 0 -2px; border-radius: 8px; }
|
|
65
68
|
.dqw-row-summary[data-expandable] { cursor: pointer; }
|
|
66
69
|
.dqw-row-summary[data-expandable]:hover { background: var(--dsw-alias-bg-hover, rgba(128,128,128,.12)); }
|
|
67
70
|
.dqw-row-summary:focus-visible { outline: 1px solid var(--dsw-alias-button-primary-fill, #5b8def); outline-offset: -1px; }
|
|
68
71
|
.dqw-row-summary.is-error { cursor: default; }
|
|
69
72
|
.dqw-label { font-size: 11px; font-weight: 600; width: 52px; flex: none; }
|
|
73
|
+
.dqw-label[data-action="refresh"] { cursor: pointer; border-radius: 4px; }
|
|
74
|
+
.dqw-label[data-action="refresh"]:hover { opacity: .75; }
|
|
70
75
|
.dqw-bar { display: block; flex: 1; height: 4px; overflow: hidden; border-radius: 4px; background: var(--dsw-alias-bg-tertiary, rgba(128,128,128,.2)); }
|
|
71
76
|
.dqw-bar-fill { display: block; height: 100%; border-radius: inherit; background: var(--dsw-alias-button-primary-fill, #5b8def); }
|
|
72
77
|
.dqw-bar-fill.warn { background: var(--dsw-alias-label-warning, #d29922); }
|
|
73
78
|
.dqw-bar-fill.danger { background: var(--dsw-alias-label-danger, #c93c3c); }
|
|
74
79
|
.dqw-pct { font-size: 10.5px; line-height: 14px; font-variant-numeric: tabular-nums; width: 30px; text-align: right; flex: none; }
|
|
75
|
-
.dqw-extra { font-size: 10px; line-height: 14px; opacity: .72; font-variant-numeric: tabular-nums; flex: none; }
|
|
80
|
+
.dqw-extra { font-size: 10px; line-height: 14px; opacity: .72; font-variant-numeric: tabular-nums; flex: none; min-width: 34px; text-align: right; }
|
|
76
81
|
.dqw-chev { font-size: 9px; line-height: 14px; opacity: .6; flex: none; width: 10px; text-align: center; }
|
|
77
82
|
.dqw-stale-mark { flex: none; font-size: 9px; line-height: 14px; color: var(--dsw-alias-label-warning, #b46900); cursor: help; }
|
|
78
83
|
.dqw-errtext { font-size: 10px; line-height: 14px; opacity: .7; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
79
|
-
.dqw-
|
|
84
|
+
.dqw-pop { position: fixed; z-index: 2147483000; min-width: 232px; max-width: 320px; padding: 10px 12px; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 10px; background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); background: color-mix(in srgb, var(--dsw-alias-bg-base, #1f1f1f) 86%, transparent); -webkit-backdrop-filter: blur(14px) saturate(1.3); backdrop-filter: blur(14px) saturate(1.3); box-shadow: 0 8px 24px rgba(0,0,0,.25); color: var(--dsw-alias-label-primary, inherit); display: flex; flex-direction: column; gap: 8px; font: inherit; }
|
|
85
|
+
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { .dqw-pop { background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); } }
|
|
86
|
+
.dqw-pop[hidden] { display: none; }
|
|
87
|
+
.dqw-pop *, .dqw-pop *::before, .dqw-pop *::after { box-sizing: border-box; }
|
|
80
88
|
.dqw-bignums { display: flex; gap: 6px; }
|
|
81
|
-
.dqw-big { flex: 1; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.25)); border-radius: 8px; padding:
|
|
82
|
-
.dqw-big .dqw-big-value { font-size:
|
|
89
|
+
.dqw-big { flex: 1; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.25)); border-radius: 8px; padding: 5px 8px; }
|
|
90
|
+
.dqw-big .dqw-big-value { font-size: 15px; font-weight: 650; line-height: 19px; font-variant-numeric: tabular-nums; }
|
|
83
91
|
.dqw-big .dqw-big-label { font-size: 9.5px; line-height: 13px; opacity: .65; }
|
|
84
|
-
.dqw-row { display: flex; flex-direction: column; gap: 2px; }
|
|
85
|
-
.dqw-row-head { display: flex; justify-content: space-between; gap: 5px; font-size: 10px; line-height: 14px; }
|
|
86
92
|
.dqw-muted { margin: 0; font-size: 10px; line-height: 14px; opacity: .7; }
|
|
87
|
-
.dqw-models {
|
|
93
|
+
.dqw-models { border-top: 1px dashed var(--dsw-alias-border-secondary, rgba(128,128,128,.25)); padding-top: 6px; display: flex; flex-direction: column; gap: 3px; }
|
|
94
|
+
.dqw-model-row { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; font-size: 10.5px; line-height: 15px; font-variant-numeric: tabular-nums; }
|
|
95
|
+
.dqw-model-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; opacity: .85; }
|
|
96
|
+
.dqw-model-value { flex: none; font-weight: 600; }
|
|
97
|
+
.dqw-kv { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; font-size: 10.5px; line-height: 17px; font-variant-numeric: tabular-nums; }
|
|
98
|
+
.dqw-kv-label { flex: none; opacity: .65; }
|
|
99
|
+
.dqw-kv-value { min-width: 0; text-align: right; font-weight: 600; }
|
|
100
|
+
.dqw-kv-sub { font-weight: 400; opacity: .55; }
|
|
101
|
+
.dqw-wins { display: grid; grid-template-columns: max-content max-content max-content max-content 1fr; column-gap: 6px; row-gap: 3px; align-items: baseline; font-size: 10.5px; line-height: 17px; font-variant-numeric: tabular-nums; }
|
|
102
|
+
.dqw-win-name { opacity: .65; }
|
|
103
|
+
.dqw-win-mid { opacity: .65; white-space: nowrap; }
|
|
104
|
+
.dqw-win-used { opacity: .65; white-space: nowrap; }
|
|
105
|
+
.dqw-win-val { min-width: 0; font-weight: 600; }
|
|
88
106
|
.dqw-error { margin: 0; font-size: 10px; line-height: 14px; color: var(--dsw-alias-label-warning, #b46900); }
|
|
89
|
-
.dqw-credit { margin: 0; font-size: 10px; line-height: 14px; font-variant-numeric: tabular-nums; }
|
|
90
107
|
`
|
|
91
108
|
|
|
92
109
|
function localeFor(win) {
|
|
@@ -130,23 +147,11 @@ function text(doc, tag, className, value) {
|
|
|
130
147
|
return node
|
|
131
148
|
}
|
|
132
149
|
|
|
133
|
-
|
|
150
|
+
/** Aligned label/value line used by the simplified Copilot detail. */
|
|
151
|
+
function kvRow(doc, label, value) {
|
|
134
152
|
const row = doc.createElement('div')
|
|
135
|
-
row.className = 'dqw-
|
|
136
|
-
|
|
137
|
-
head.className = 'dqw-row-head'
|
|
138
|
-
head.append(text(doc, 'span', '', label), text(doc, 'span', '', formatPercent(percent, locale)))
|
|
139
|
-
const bar = doc.createElement('span')
|
|
140
|
-
bar.className = 'dqw-bar'
|
|
141
|
-
bar.setAttribute('role', 'progressbar')
|
|
142
|
-
bar.setAttribute('aria-valuemin', '0')
|
|
143
|
-
bar.setAttribute('aria-valuemax', '100')
|
|
144
|
-
bar.setAttribute('aria-valuenow', String(Math.max(0, Math.min(100, percent))))
|
|
145
|
-
const fill = doc.createElement('span')
|
|
146
|
-
fill.className = `dqw-bar-fill${percent > 95 ? ' danger' : percent > 80 ? ' warn' : ''}`
|
|
147
|
-
fill.style.width = `${Math.max(0, Math.min(100, percent))}%`
|
|
148
|
-
bar.append(fill)
|
|
149
|
-
row.append(head, bar)
|
|
153
|
+
row.className = 'dqw-kv'
|
|
154
|
+
row.append(text(doc, 'span', 'dqw-kv-label', label), text(doc, 'span', 'dqw-kv-value', value))
|
|
150
155
|
return row
|
|
151
156
|
}
|
|
152
157
|
|
|
@@ -184,6 +189,7 @@ function copilotRowModel(provider) {
|
|
|
184
189
|
percent: used,
|
|
185
190
|
stale: provider.status === 'stale',
|
|
186
191
|
error: provider.error,
|
|
192
|
+
extra: typeof quota.creditsUsed === 'number' && quota.creditsUsed >= 0 ? quota.creditsUsed : undefined,
|
|
187
193
|
}
|
|
188
194
|
}
|
|
189
195
|
|
|
@@ -222,22 +228,20 @@ export function mountQuotaCard({
|
|
|
222
228
|
const card = doc.createElement('section')
|
|
223
229
|
card.className = 'dqw-card'
|
|
224
230
|
card.setAttribute('aria-label', copy.title)
|
|
225
|
-
const header = doc.createElement('header')
|
|
226
|
-
header.className = 'dqw-head'
|
|
227
|
-
header.append(text(doc, 'h3', 'dqw-title', copy.title))
|
|
228
|
-
const refreshButton = text(doc, 'button', 'dqw-button', '↻')
|
|
229
|
-
refreshButton.type = 'button'
|
|
230
|
-
refreshButton.title = copy.refresh
|
|
231
|
-
refreshButton.setAttribute('aria-label', copy.refresh)
|
|
232
|
-
refreshButton.dataset.action = 'refresh'
|
|
233
|
-
header.append(refreshButton)
|
|
234
231
|
const body = doc.createElement('div')
|
|
235
232
|
body.className = 'dqw-body'
|
|
233
|
+
// Status line doubles as the transport-error surface; hidden once data renders.
|
|
236
234
|
const lastUpdated = text(doc, 'p', 'dqw-meta', copy.loading)
|
|
237
235
|
lastUpdated.dataset.role = 'updated'
|
|
238
236
|
body.append(lastUpdated)
|
|
239
|
-
card.append(
|
|
237
|
+
card.append(body)
|
|
240
238
|
container.append(style, card)
|
|
239
|
+
// Details open in a fixed-position panel to the right of the rows.
|
|
240
|
+
const pop = doc.createElement('div')
|
|
241
|
+
pop.className = 'dqw-pop'
|
|
242
|
+
pop.dataset.dshQuotaWatchPop = ''
|
|
243
|
+
pop.hidden = true
|
|
244
|
+
doc.body.append(pop)
|
|
241
245
|
let snapshot
|
|
242
246
|
let requestSequence = 0
|
|
243
247
|
let timer
|
|
@@ -247,13 +251,17 @@ export function mountQuotaCard({
|
|
|
247
251
|
|
|
248
252
|
const fillClass = (percent) => (percent > 95 ? 'dqw-bar-fill danger' : percent > 80 ? 'dqw-bar-fill warn' : 'dqw-bar-fill')
|
|
249
253
|
|
|
250
|
-
/** Build
|
|
251
|
-
const renderProvider = (key, model
|
|
254
|
+
/** Build the summary row for one provider, or null when it must not render. */
|
|
255
|
+
const renderProvider = (key, model) => {
|
|
252
256
|
if (model === null) return null
|
|
253
257
|
const row = doc.createElement('div')
|
|
254
258
|
row.className = 'dqw-row-summary'
|
|
255
259
|
row.dataset.dshQuotaWatchRow = key
|
|
256
|
-
|
|
260
|
+
const label = text(doc, 'span', 'dqw-label', model.label)
|
|
261
|
+
// Clicking the provider name re-probes, standing in for the removed refresh button.
|
|
262
|
+
label.dataset.action = 'refresh'
|
|
263
|
+
label.title = `${model.label} · ${copy.refresh}`
|
|
264
|
+
row.append(label)
|
|
257
265
|
if (model.kind === 'error') {
|
|
258
266
|
row.classList.add('is-error')
|
|
259
267
|
row.append(text(doc, 'span', 'dqw-errtext', model.error ? `${copy.failed}: ${model.error}` : copy.failed))
|
|
@@ -288,11 +296,7 @@ export function mountQuotaCard({
|
|
|
288
296
|
row.append(extra)
|
|
289
297
|
}
|
|
290
298
|
row.append(text(doc, 'span', 'dqw-chev', openKey === key ? '▾' : '▸'))
|
|
291
|
-
|
|
292
|
-
detail.className = 'dqw-detail'
|
|
293
|
-
detail.dataset.dshQuotaWatchDetail = key
|
|
294
|
-
if (openKey === key) renderDetail(detail, key, provider)
|
|
295
|
-
return [row, detail]
|
|
299
|
+
return [row]
|
|
296
300
|
}
|
|
297
301
|
|
|
298
302
|
function renderDetail(detail, key, provider) {
|
|
@@ -318,45 +322,133 @@ export function mountQuotaCard({
|
|
|
318
322
|
)
|
|
319
323
|
numbers.append(tokensBig, callsBig)
|
|
320
324
|
detail.append(numbers)
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
325
|
+
const raw = Array.isArray(provider?.plan?.windows) ? provider.plan.windows : []
|
|
326
|
+
// Fixed display order (MCP, 5h, month, week); the weekly window shows an
|
|
327
|
+
// unlimited placeholder until the provider starts reporting one.
|
|
328
|
+
const order = { mcp: 0, '5h': 1, month: 2, week: 3 }
|
|
329
|
+
const sorted = [...raw].sort((a, b) => (order[a?.key] ?? 99) - (order[b?.key] ?? 99))
|
|
330
|
+
const items = []
|
|
331
|
+
let weekPlaced = false
|
|
332
|
+
for (const window of sorted) {
|
|
333
|
+
if (!weekPlaced && (order[window?.key] ?? 99) > order.week) {
|
|
334
|
+
items.push({ placeholder: true })
|
|
335
|
+
weekPlaced = true
|
|
327
336
|
}
|
|
337
|
+
if (window?.key === 'week') weekPlaced = true
|
|
338
|
+
items.push({ window })
|
|
339
|
+
}
|
|
340
|
+
if (!weekPlaced) items.push({ placeholder: true })
|
|
341
|
+
// One grid keeps the name, ·, used/♾️, percent and weak-note columns aligned
|
|
342
|
+
// across all window rows.
|
|
343
|
+
const wins = doc.createElement('div')
|
|
344
|
+
wins.className = 'dqw-wins'
|
|
345
|
+
for (const item of items) {
|
|
346
|
+
if (item.placeholder) {
|
|
347
|
+
wins.append(
|
|
348
|
+
text(doc, 'span', 'dqw-win-name', copy.weekUnlimited),
|
|
349
|
+
text(doc, 'span', 'dqw-win-mid', '·'),
|
|
350
|
+
text(doc, 'span', 'dqw-win-used', copy.unlimited),
|
|
351
|
+
text(doc, 'span', ''),
|
|
352
|
+
text(doc, 'span', 'dqw-kv-sub', copy.unlimitedNote),
|
|
353
|
+
)
|
|
354
|
+
continue
|
|
355
|
+
}
|
|
356
|
+
const window = item.window
|
|
357
|
+
const percent = typeof window?.percent === 'number' && Number.isFinite(window.percent) ? window.percent : undefined
|
|
328
358
|
const reset = formatTime(window?.resetsAt, locale)
|
|
329
|
-
|
|
359
|
+
wins.append(text(doc, 'span', 'dqw-win-name', copy.windows[window?.key] ?? window?.key))
|
|
360
|
+
wins.append(text(doc, 'span', 'dqw-win-mid', '·'))
|
|
361
|
+
wins.append(text(doc, 'span', 'dqw-win-used', copy.windowUsed))
|
|
362
|
+
wins.append(text(doc, 'span', 'dqw-win-val', percent !== undefined ? formatPercent(percent, locale) : '—'))
|
|
363
|
+
wins.append(text(doc, 'span', 'dqw-kv-sub', reset ? `(${copy.reset}: ${reset})` : ''))
|
|
364
|
+
}
|
|
365
|
+
detail.append(wins)
|
|
366
|
+
if (Array.isArray(usage?.models) && usage.models.length > 0) {
|
|
367
|
+
const models = doc.createElement('div')
|
|
368
|
+
models.className = 'dqw-models'
|
|
369
|
+
for (const model of usage.models) {
|
|
370
|
+
const row = doc.createElement('div')
|
|
371
|
+
row.className = 'dqw-model-row'
|
|
372
|
+
row.append(
|
|
373
|
+
text(doc, 'span', 'dqw-model-name', model?.name ?? '—'),
|
|
374
|
+
text(doc, 'span', 'dqw-model-value', formatCompact(model?.tokens, locale)),
|
|
375
|
+
)
|
|
376
|
+
models.append(row)
|
|
377
|
+
}
|
|
378
|
+
detail.append(models)
|
|
330
379
|
}
|
|
331
|
-
const models = Array.isArray(usage?.models) && usage.models.length > 0
|
|
332
|
-
? usage.models.map((model) => `${model.name} ${formatCompact(model.tokens, locale)}`).join(' · ')
|
|
333
|
-
: undefined
|
|
334
|
-
if (models) detail.append(text(doc, 'p', 'dqw-models', models))
|
|
335
|
-
const updated = formatTime(provider?.updatedAt ? new Date(provider.updatedAt).toISOString() : '', locale)
|
|
336
|
-
if (updated) detail.append(text(doc, 'p', 'dqw-meta', `${copy.updated}: ${updated}`))
|
|
337
380
|
return
|
|
338
381
|
}
|
|
339
382
|
const quota = provider?.quota
|
|
340
383
|
if (quota?.balanceAvailable === true) {
|
|
341
|
-
detail.append(
|
|
384
|
+
detail.append(kvRow(doc, copy.available, `${formatNumber(quota.remaining, locale)} / ${formatNumber(quota.entitlement, locale)}`))
|
|
342
385
|
const used = remainingToUsed(quota.percentRemaining)
|
|
343
|
-
if (used !== undefined) detail.append(progress(doc, copy.used, used, locale))
|
|
344
386
|
if (typeof quota.creditsUsed === 'number' && quota.creditsUsed >= 0) {
|
|
345
|
-
|
|
387
|
+
const row = doc.createElement('div')
|
|
388
|
+
row.className = 'dqw-kv'
|
|
389
|
+
const value = doc.createElement('span')
|
|
390
|
+
value.className = 'dqw-kv-value'
|
|
391
|
+
value.append(text(doc, 'span', '', formatNumber(quota.creditsUsed, locale)))
|
|
392
|
+
// The percent duplicates the summary bar, so render it weakened.
|
|
393
|
+
if (used !== undefined) value.append(text(doc, 'span', 'dqw-kv-sub', ` · ${formatPercent(used, locale)}`))
|
|
394
|
+
row.append(text(doc, 'span', 'dqw-kv-label', copy.used), value)
|
|
395
|
+
detail.append(row)
|
|
346
396
|
}
|
|
397
|
+
const reset = formatTime(quota?.resetsAt, locale)
|
|
398
|
+
if (reset) detail.append(kvRow(doc, copy.resetTime, reset))
|
|
347
399
|
} else {
|
|
348
400
|
detail.append(text(doc, 'p', 'dqw-muted', copy.stale))
|
|
349
401
|
}
|
|
350
|
-
|
|
351
|
-
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/** Keep the detail panel anchored to the expanded row's right edge. */
|
|
405
|
+
const positionPop = () => {
|
|
406
|
+
if (pop.hidden || openKey === undefined) return
|
|
407
|
+
const row = body.querySelector(`[data-dsh-quota-watch-row="${openKey}"]`)
|
|
408
|
+
if (!row) return
|
|
409
|
+
const rect = row.getBoundingClientRect()
|
|
410
|
+
const viewWidth = win.innerWidth ?? 1024
|
|
411
|
+
const viewHeight = win.innerHeight ?? 768
|
|
412
|
+
const width = pop.offsetWidth || 240
|
|
413
|
+
const height = pop.offsetHeight || 160
|
|
414
|
+
let left = rect.right + 8
|
|
415
|
+
if (left + width > viewWidth - 8) left = rect.left - width - 8
|
|
416
|
+
if (left + width > viewWidth - 8) left = viewWidth - width - 8
|
|
417
|
+
left = Math.max(8, left)
|
|
418
|
+
const top = Math.min(Math.max(rect.top, 8), Math.max(8, viewHeight - height - 8))
|
|
419
|
+
pop.style.left = `${Math.round(left)}px`
|
|
420
|
+
pop.style.top = `${Math.round(top)}px`
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const syncPop = () => {
|
|
424
|
+
if (openKey === undefined || disposed) {
|
|
425
|
+
if (!pop.hidden) {
|
|
426
|
+
pop.hidden = true
|
|
427
|
+
pop.replaceChildren()
|
|
428
|
+
delete pop.dataset.dshQuotaWatchDetail
|
|
429
|
+
}
|
|
430
|
+
return
|
|
431
|
+
}
|
|
432
|
+
const provider = (snapshot?.providers ?? []).find((item) => item?.key === openKey)
|
|
433
|
+
const row = body.querySelector(`[data-dsh-quota-watch-row="${openKey}"]`)
|
|
434
|
+
if (!provider || !row || !row.hasAttribute('data-expandable')) {
|
|
435
|
+
openKey = undefined
|
|
436
|
+
syncPop()
|
|
437
|
+
return
|
|
438
|
+
}
|
|
439
|
+
renderDetail(pop, openKey, provider)
|
|
440
|
+
pop.dataset.dshQuotaWatchDetail = openKey
|
|
441
|
+
pop.setAttribute('aria-label', openKey === 'glm' ? 'GLM' : 'Copilot')
|
|
442
|
+
pop.hidden = false
|
|
443
|
+
positionPop()
|
|
352
444
|
}
|
|
353
445
|
|
|
354
446
|
const renderAll = () => {
|
|
355
447
|
const providers = snapshot?.providers ?? []
|
|
356
448
|
const glm = providers.find((provider) => provider?.key === 'glm')
|
|
357
449
|
const copilot = providers.find((provider) => provider?.key === 'copilot')
|
|
358
|
-
const glmParts = renderProvider('glm', glmRowModel(glm)
|
|
359
|
-
const copilotParts = renderProvider('copilot', copilotRowModel(copilot)
|
|
450
|
+
const glmParts = renderProvider('glm', glmRowModel(glm))
|
|
451
|
+
const copilotParts = renderProvider('copilot', copilotRowModel(copilot))
|
|
360
452
|
const fragment = doc.createDocumentFragment()
|
|
361
453
|
fragment.append(lastUpdated)
|
|
362
454
|
if (glmParts) fragment.append(...glmParts)
|
|
@@ -364,7 +456,13 @@ export function mountQuotaCard({
|
|
|
364
456
|
body.replaceChildren(fragment)
|
|
365
457
|
container.hidden = glmParts === null && copilotParts === null
|
|
366
458
|
const updated = formatTime(snapshot?.updatedAt ? new Date(snapshot.updatedAt).toISOString() : '', locale)
|
|
367
|
-
|
|
459
|
+
if (updated) {
|
|
460
|
+
lastUpdated.hidden = true
|
|
461
|
+
} else {
|
|
462
|
+
lastUpdated.hidden = false
|
|
463
|
+
lastUpdated.textContent = copy.loading
|
|
464
|
+
}
|
|
465
|
+
syncPop()
|
|
368
466
|
}
|
|
369
467
|
|
|
370
468
|
const render = (next) => {
|
|
@@ -373,32 +471,29 @@ export function mountQuotaCard({
|
|
|
373
471
|
}
|
|
374
472
|
|
|
375
473
|
const showTransportError = () => {
|
|
376
|
-
|
|
377
|
-
|
|
474
|
+
lastUpdated.hidden = false
|
|
475
|
+
lastUpdated.textContent = snapshot ? copy.requestFailed : copy.failed
|
|
378
476
|
}
|
|
379
477
|
|
|
380
478
|
const poll = async (force) => {
|
|
381
479
|
if (disposed) return
|
|
382
480
|
const sequence = ++requestSequence
|
|
383
|
-
refreshButton.disabled = force === true
|
|
384
481
|
try {
|
|
385
482
|
const next = await fetchJson(fetchImpl, force ? CLIENT_ROUTES.refresh : CLIENT_ROUTES.overview, force ? 'POST' : 'GET')
|
|
386
483
|
if (sequence === requestSequence && !disposed) render(next)
|
|
387
484
|
} catch {
|
|
388
485
|
if (sequence === requestSequence && !disposed) showTransportError()
|
|
389
|
-
} finally {
|
|
390
|
-
if (!disposed && sequence === requestSequence) refreshButton.disabled = false
|
|
391
486
|
}
|
|
392
487
|
}
|
|
393
488
|
|
|
394
489
|
const place = () => {
|
|
395
490
|
const foot = footArea(doc)
|
|
396
491
|
if (!foot) return
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
foot.
|
|
492
|
+
// Claim the first slot of the sidebar footer so the card sits above the
|
|
493
|
+
// framework-rendered plugin entries and the settings row; the mutation
|
|
494
|
+
// observer re-seats it whenever the framework displaces it.
|
|
495
|
+
if (container.parentElement !== foot || foot.firstElementChild !== container) {
|
|
496
|
+
foot.insertBefore(container, foot.firstChild)
|
|
402
497
|
}
|
|
403
498
|
}
|
|
404
499
|
|
|
@@ -417,8 +512,8 @@ export function mountQuotaCard({
|
|
|
417
512
|
|
|
418
513
|
const startPolling = () => {
|
|
419
514
|
if (timer !== undefined || doc.visibilityState === 'hidden' || disposed) return
|
|
420
|
-
void poll(
|
|
421
|
-
timer = win.setInterval(() => { void poll(
|
|
515
|
+
void poll()
|
|
516
|
+
timer = win.setInterval(() => { void poll() }, pollIntervalMs)
|
|
422
517
|
}
|
|
423
518
|
const stopPolling = () => {
|
|
424
519
|
if (timer !== undefined) win.clearInterval(timer)
|
|
@@ -428,12 +523,15 @@ export function mountQuotaCard({
|
|
|
428
523
|
if (doc.visibilityState === 'hidden') stopPolling()
|
|
429
524
|
else startPolling()
|
|
430
525
|
}
|
|
431
|
-
const onRefresh = () => { void poll(true) }
|
|
432
526
|
const toggleRow = (key) => {
|
|
433
527
|
openKey = openKey === key ? undefined : key
|
|
434
528
|
renderAll()
|
|
435
529
|
}
|
|
436
530
|
const onBodyClick = (event) => {
|
|
531
|
+
if (event.target?.closest?.('[data-action="refresh"]')) {
|
|
532
|
+
void poll(true)
|
|
533
|
+
return
|
|
534
|
+
}
|
|
437
535
|
const row = event.target?.closest?.('[data-dsh-quota-watch-row][data-expandable]')
|
|
438
536
|
if (row) toggleRow(row.dataset.dshQuotaWatchRow)
|
|
439
537
|
}
|
|
@@ -445,10 +543,28 @@ export function mountQuotaCard({
|
|
|
445
543
|
toggleRow(row.dataset.dshQuotaWatchRow)
|
|
446
544
|
}
|
|
447
545
|
}
|
|
448
|
-
|
|
546
|
+
const onDocPointerDown = (event) => {
|
|
547
|
+
if (openKey === undefined) return
|
|
548
|
+
const target = event.target
|
|
549
|
+
if (pop.contains(target) || target?.closest?.('[data-dsh-quota-watch-row]') || target?.closest?.(CARD_SELECTOR)) return
|
|
550
|
+
openKey = undefined
|
|
551
|
+
renderAll()
|
|
552
|
+
}
|
|
553
|
+
const onDocScroll = () => { positionPop() }
|
|
554
|
+
const onWinResize = () => { positionPop() }
|
|
555
|
+
const onDocKeydown = (event) => {
|
|
556
|
+
if (event.key === 'Escape' && openKey !== undefined) {
|
|
557
|
+
openKey = undefined
|
|
558
|
+
renderAll()
|
|
559
|
+
}
|
|
560
|
+
}
|
|
449
561
|
body.addEventListener('click', onBodyClick)
|
|
450
562
|
body.addEventListener('keydown', onBodyKeydown)
|
|
451
563
|
doc.addEventListener('visibilitychange', onVisibilityChange)
|
|
564
|
+
doc.addEventListener('pointerdown', onDocPointerDown, true)
|
|
565
|
+
doc.addEventListener('scroll', onDocScroll, true)
|
|
566
|
+
doc.addEventListener('keydown', onDocKeydown)
|
|
567
|
+
win.addEventListener('resize', onWinResize)
|
|
452
568
|
startPolling()
|
|
453
569
|
|
|
454
570
|
return () => {
|
|
@@ -456,10 +572,14 @@ export function mountQuotaCard({
|
|
|
456
572
|
stopPolling()
|
|
457
573
|
observer.disconnect()
|
|
458
574
|
doc.removeEventListener('visibilitychange', onVisibilityChange)
|
|
459
|
-
|
|
575
|
+
doc.removeEventListener('pointerdown', onDocPointerDown, true)
|
|
576
|
+
doc.removeEventListener('scroll', onDocScroll, true)
|
|
577
|
+
doc.removeEventListener('keydown', onDocKeydown)
|
|
578
|
+
win.removeEventListener('resize', onWinResize)
|
|
460
579
|
body.removeEventListener('click', onBodyClick)
|
|
461
580
|
body.removeEventListener('keydown', onBodyKeydown)
|
|
462
581
|
container.remove()
|
|
582
|
+
pop.remove()
|
|
463
583
|
}
|
|
464
584
|
}
|
|
465
585
|
|