@iasiv5/dsh-quota-watch 0.1.0 → 0.1.1
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 +7 -8
- package/lib/client.js +82 -197
- package/package.json +1 -1
- package/src/client/prefs.mjs +0 -19
- package/src/client.mjs +94 -192
package/README.md
CHANGED
|
@@ -2,21 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
[English summary](#english-summary)
|
|
4
4
|
|
|
5
|
-
个人版 DSH
|
|
5
|
+
个人版 DSH 额度监控插件,以常驻悬浮胶囊集中显示 GLM Coding Plan 窗口与 GitHub Copilot 的 provider 上报额度快照。它不统计 DSH 会话 token、不保存历史,也不需要在浏览器输入任何 provider 密钥。
|
|
6
6
|
|
|
7
7
|
## 功能
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
- **胶囊**:右键悬浮球可切换为一行式胶囊(`GLM 13% · Copilot 24%`),阈值变色规则相同;右键胶囊切回悬浮球。球 / 胶囊形态跨重启记忆。
|
|
9
|
+
- **悬浮胶囊**(0.1.1 起为唯一浮动形态,圆球已退役):常驻屏幕右下角(距边 16px 起步),可整体拖拽到任意位置,位置跨重启记忆,越界自动钳回视口。胶囊一行显示「GLM 13% · Copilot 24%」,已用超过 80% 转橙、超过 95% 转红,任一 provider 超 95% 时胶囊轻微脉动(尊重系统「减少动态效果」设置,开启时不脉动);颜色使用 DSH 主题语义变量,自动适配浅色 / 深色主题。悬停提示与 aria-label 实时给出摘要。
|
|
11
10
|
- **面板**:点击球或胶囊展开浮动详情面板(半透明毛玻璃底,宽 `min(320px, 100vw−24px)`,靠边自动翻侧,滚动 / 缩放跟随),头部带标题与 ✕,Esc 或点击面板外关闭。概览列两个 provider 行,点击进入详情:GLM 为今日 Tokens / 调用次数双大数字 + 五列对齐窗口网格(MCP(月)→ 5 小时 → 每月 → 每周固定排序,周窗口未下发时显示「每周额度 · ♾️(无限)」占位)+ 分模型用量行;Copilot 为可用额度、本期已用(credits 消耗 + 弱化百分比)、重置时间三行。接口未下发有效 `remaining` / `percent_remaining` 时该行不渲染,`credits_used` 绝不用于推算余额。
|
|
12
|
-
- **侧边栏卡片(渐进增强)**:Web profile 侧栏底部区块存在时,卡片照常显示(摘要行 + 行点击开面板 + 行首 provider
|
|
13
|
-
-
|
|
14
|
-
- 凭据缺失的 provider
|
|
11
|
+
- **侧边栏卡片(渐进增强)**:Web profile 侧栏底部区块存在时,卡片照常显示(摘要行 + 行点击开面板 + 行首 provider 名称点击立即探测),卡片右上角有一个胶囊切换按钮(悬停显形),可在卡片上隐藏 / 唤回悬浮胶囊;侧栏收起(Web 窄栏)或侧栏整体不存在(Desktop 收起态)时卡片自然缺席、无报错,由悬浮胶囊承载入口。
|
|
12
|
+
- **右键菜单**:显示 / 隐藏侧边栏卡片 · 立即刷新 · 隐藏悬浮胶囊。菜单项随上下文增减(无侧栏卡片挂载点时不显示卡片项)。「隐藏悬浮胶囊」为**会话级**——刷新页面即恢复,卡片上的切换按钮可在会话内直接唤回,任何时刻都至少有一条找回路径。
|
|
13
|
+
- 凭据缺失的 provider 不渲染摘要行与胶囊分段(两者皆缺时悬浮胶囊与卡片一并隐藏);探测失败时保留最后一次成功数据并在行上加 ⚠ 过期标记(⚠ 只出现在面板与卡片行内,不上球)。
|
|
15
14
|
- 宿主默认每 60 秒探测(窗口 + 用量 + Copilot);profile patch 可将 `pollIntervalSec` 设置在 30–3600 秒。浏览器页签可见时每 30 秒刷新视图,隐藏时暂停。
|
|
16
15
|
|
|
17
16
|
## 兼容与配置
|
|
18
17
|
|
|
19
|
-
- 要求 DSH `0.1.7-rc.2` 或更高;适配 `web` 与 `desktop` 两种 profile——Desktop
|
|
18
|
+
- 要求 DSH `0.1.7-rc.2` 或更高;适配 `web` 与 `desktop` 两种 profile——Desktop 收起侧栏(整体消失)时由悬浮胶囊承载入口,卡片为 Web 渐进增强。
|
|
20
19
|
- GLM 从 `llm-pi-ai` profile 中配置的 provider `apiKeyEnv` credential reference 解析当前 key。支持 `zai-coding-cn`(国内)与 `zai` / `zai-coding`(国际)路由。每次探测都重新解析引用,用户轮换凭据后下一轮读取新值。
|
|
21
20
|
- Copilot 使用内置 `llm-pi-ai/github-copilot` OAuth grant 的 `payload.refresh` 访问用户配额快照;不使用给模型请求的短期 `payload.access`,不使用 `COPILOT_GITHUB_TOKEN` 环境引用。每轮重新读取 grant,以支持用户重新授权或轮换。
|
|
22
21
|
- 如需修改宿主周期,在 profile 的 `quota-watch` row 配置 `pollIntervalSec`;禁用插件请使用 DSH 插件管理器。
|
|
@@ -48,4 +47,4 @@ dsh plugin --profile web add @iasiv5/dsh-quota-watch
|
|
|
48
47
|
|
|
49
48
|
## English summary
|
|
50
49
|
|
|
51
|
-
DSH Quota Watch is a personal DSH quota monitor built around a persistent floating
|
|
50
|
+
DSH Quota Watch is a personal DSH quota monitor built around a persistent floating capsule: a one-line `GLM x% · Copilot y%` summary that turns amber past 80% and red past 95%, draggable anywhere with position remembered across restarts. Clicking the capsule opens a floating glass panel whose overview reuses the classic 0.0.18 summary rows (label · bar · percent · extra · chevron) and whose detail pages replicate the 0.0.18 big numbers, window grid and per-model list; the panel closes via ✕, Esc or an outside click. A sidebar card remains an opportunistic extra entry on web profiles (with a corner button to hide or restore the capsule) and silently disappears whenever the sidebar does — collapsed web sidebars and the desktop profile are covered by the floating capsule alone. A right-click menu offers card visibility, an immediate provider re-probe and a session-scoped capsule hide. Credentials are resolved on every host-side probe, only normalized values reach the browser, no usage history is kept, and a Copilot balance is never inferred from credits used. Colors ride DSH semantic theme tokens with hardcoded fallbacks; all UI lives inside a shadow root (ADR 0001).
|
package/lib/client.js
CHANGED
|
@@ -83,7 +83,6 @@ function remainingToUsed(percentRemaining) {
|
|
|
83
83
|
|
|
84
84
|
// src/client/prefs.mjs
|
|
85
85
|
var FLOAT_GEOMETRY_KEY = "dsh-quota-watch:float-geometry";
|
|
86
|
-
var FLOAT_MODE_KEY = "dsh-quota-watch:float-mode";
|
|
87
86
|
var SURFACE_FLAGS_KEY = "dsh-quota-watch:surface-flags";
|
|
88
87
|
var MARGIN = 8;
|
|
89
88
|
function clampPoint(point, viewport, size = 0) {
|
|
@@ -115,21 +114,6 @@ function saveFloatGeometry(storage, point) {
|
|
|
115
114
|
} catch {
|
|
116
115
|
}
|
|
117
116
|
}
|
|
118
|
-
function loadFloatMode(storage) {
|
|
119
|
-
try {
|
|
120
|
-
const raw = storage.getItem(FLOAT_MODE_KEY);
|
|
121
|
-
return raw === "capsule" ? "capsule" : "ball";
|
|
122
|
-
} catch {
|
|
123
|
-
return "ball";
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
function saveFloatMode(storage, mode) {
|
|
127
|
-
if (mode !== "ball" && mode !== "capsule") throw new TypeError(`invalid float mode: ${mode}`);
|
|
128
|
-
try {
|
|
129
|
-
storage.setItem(FLOAT_MODE_KEY, mode);
|
|
130
|
-
} catch {
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
117
|
function loadSurfaceFlags(storage) {
|
|
134
118
|
try {
|
|
135
119
|
const raw = storage.getItem(SURFACE_FLAGS_KEY);
|
|
@@ -155,24 +139,19 @@ var name = "quota-watch-client";
|
|
|
155
139
|
var inject = [];
|
|
156
140
|
var CARD_SELECTOR = "[data-dsh-quota-watch-card]";
|
|
157
141
|
var FETCH_TIMEOUT_MS = 15e3;
|
|
158
|
-
var
|
|
159
|
-
var
|
|
160
|
-
var RING_RADIUS = 15;
|
|
161
|
-
var RING_CIRCUMFERENCE = 2 * Math.PI * RING_RADIUS;
|
|
162
|
-
var RING_HALF_GAP = 2;
|
|
142
|
+
var SURFACE_SIZE = 38;
|
|
143
|
+
var SURFACE_MARGIN = 16;
|
|
163
144
|
var COPY = {
|
|
164
145
|
zh: {
|
|
165
146
|
title: "Token\u989D\u5EA6",
|
|
166
147
|
openDetails: "\u67E5\u770B\u8BE6\u60C5",
|
|
167
148
|
close: "\u5173\u95ED",
|
|
168
149
|
menu: {
|
|
169
|
-
toggleToBall: "\u5207\u4E3A\u60AC\u6D6E\u7403",
|
|
170
|
-
toggleToCapsule: "\u5207\u4E3A\u80F6\u56CA",
|
|
171
150
|
showCard: "\u663E\u793A\u4FA7\u8FB9\u680F\u5361\u7247",
|
|
172
151
|
hideCard: "\u9690\u85CF\u4FA7\u8FB9\u680F\u5361\u7247",
|
|
173
152
|
refreshNow: "\u7ACB\u5373\u5237\u65B0",
|
|
174
|
-
hideBall: "\u9690\u85CF\u60AC\u6D6E\
|
|
175
|
-
showBall: "\u663E\u793A\u60AC\u6D6E\
|
|
153
|
+
hideBall: "\u9690\u85CF\u60AC\u6D6E\u80F6\u56CA",
|
|
154
|
+
showBall: "\u663E\u793A\u60AC\u6D6E\u80F6\u56CA"
|
|
176
155
|
},
|
|
177
156
|
backToOverview: "\u8FD4\u56DE\u989D\u5EA6\u6982\u89C8",
|
|
178
157
|
providerNames: { glm: "GLM", copilot: "Copilot" },
|
|
@@ -203,13 +182,11 @@ var COPY = {
|
|
|
203
182
|
openDetails: "View details",
|
|
204
183
|
close: "Close",
|
|
205
184
|
menu: {
|
|
206
|
-
toggleToBall: "Switch to ball",
|
|
207
|
-
toggleToCapsule: "Switch to capsule",
|
|
208
185
|
showCard: "Show sidebar card",
|
|
209
186
|
hideCard: "Hide sidebar card",
|
|
210
187
|
refreshNow: "Refresh now",
|
|
211
|
-
hideBall: "Hide floating
|
|
212
|
-
showBall: "Show floating
|
|
188
|
+
hideBall: "Hide floating capsule",
|
|
189
|
+
showBall: "Show floating capsule"
|
|
213
190
|
},
|
|
214
191
|
backToOverview: "Back to quota overview",
|
|
215
192
|
providerNames: { glm: "GLM", copilot: "Copilot" },
|
|
@@ -264,43 +241,9 @@ var STYLE_TEXT = `
|
|
|
264
241
|
.dqw-ball-toggle[data-ball-hidden="true"] { color: var(--dsw-alias-button-primary-fill, #5b8def); }
|
|
265
242
|
.dqw-ball-toggle:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
266
243
|
.dqw-ball-toggle svg { display: block; width: 14px; height: 14px; }
|
|
267
|
-
.dqw-overview-list { display: flex; flex-direction: column; gap: 2px; }
|
|
268
|
-
.dqw-overview-row { display: flex; align-items: center; width: 100%; gap: 6px; margin: 0; padding: 5px 4px; border: 0; border-radius: 8px; background: transparent; color: inherit; font: inherit; text-align: left; cursor: pointer; }
|
|
269
|
-
.dqw-overview-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
270
|
-
.dqw-overview-row:focus-visible { outline: 1px solid var(--dsw-alias-brand-primary, #5b8def); outline-offset: -1px; }
|
|
271
|
-
.dqw-overview-error { display: flex; align-items: center; gap: 6px; padding: 5px 4px; }
|
|
272
|
-
.dqw-bignums { display: flex; gap: 6px; }
|
|
273
|
-
.dqw-big { flex: 1; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.25)); border-radius: 8px; padding: 5px 8px; }
|
|
274
|
-
.dqw-big .dqw-big-value { font-size: 15px; font-weight: 650; line-height: 19px; font-variant-numeric: tabular-nums; }
|
|
275
|
-
.dqw-big .dqw-big-label { font-size: 9.5px; line-height: 13px; opacity: .65; }
|
|
276
|
-
.dqw-muted { margin: 0; font-size: 10px; line-height: 14px; opacity: .7; }
|
|
277
|
-
.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; }
|
|
278
|
-
.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; }
|
|
279
|
-
.dqw-model-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; opacity: .85; }
|
|
280
|
-
.dqw-model-value { flex: none; font-weight: 600; }
|
|
281
|
-
.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; }
|
|
282
|
-
.dqw-kv-label { flex: none; opacity: .65; }
|
|
283
|
-
.dqw-kv-value { min-width: 0; text-align: right; font-weight: 600; }
|
|
284
|
-
.dqw-kv-sub { font-weight: 400; opacity: .55; }
|
|
285
|
-
.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; }
|
|
286
|
-
.dqw-win-name { opacity: .65; }
|
|
287
|
-
.dqw-win-mid { opacity: .65; white-space: nowrap; }
|
|
288
|
-
.dqw-win-used { opacity: .65; white-space: nowrap; }
|
|
289
|
-
.dqw-win-val { min-width: 0; font-weight: 600; }
|
|
290
|
-
.dqw-error { margin: 0; font-size: 10px; line-height: 14px; color: var(--dsw-alias-label-warning, #b46900); }
|
|
291
244
|
`;
|
|
292
245
|
var FLOAT_STYLE_TEXT = `
|
|
293
246
|
:host { position: fixed; z-index: 2147483000; }
|
|
294
|
-
.dqw-ball { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 38px; height: 38px; margin: 0; padding: 0; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 50%; background: var(--dsw-alias-bg-base, rgba(128,128,128,.08)); background: color-mix(in srgb, var(--dsw-alias-bg-base, #1f1f1f) 86%, transparent); -webkit-backdrop-filter: blur(10px) saturate(1.2); backdrop-filter: blur(10px) saturate(1.2); box-shadow: 0 4px 14px rgba(0,0,0,.22); color: var(--dsw-alias-label-primary, inherit); font: inherit; cursor: pointer; }
|
|
295
|
-
.dqw-ball:hover { border-color: var(--dsw-alias-border-primary, var(--dsw-alias-border-secondary, rgba(128,128,128,.35))); }
|
|
296
|
-
.dqw-ball:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #5b8def); outline-offset: 2px; }
|
|
297
|
-
.dqw-ring { position: absolute; inset: 0; width: 100%; height: 100%; }
|
|
298
|
-
.dqw-ring-track { fill: none; stroke: var(--dsw-alias-bg-tertiary, rgba(128,128,128,.2)); stroke-width: 3.5; }
|
|
299
|
-
.dqw-ring-glm { fill: none; stroke: var(--dsw-alias-button-primary-fill, #5b8def); stroke-width: 3.5; stroke-linecap: round; transition: stroke-dasharray .3s ease; }
|
|
300
|
-
.dqw-ring-copilot { fill: none; stroke: var(--dsw-alias-label-success, #3fb950); stroke-width: 3.5; stroke-linecap: round; transition: stroke-dasharray .3s ease; }
|
|
301
|
-
.dqw-ring-glm.warn, .dqw-ring-copilot.warn { stroke: var(--dsw-alias-label-warning, #d29922); }
|
|
302
|
-
.dqw-ring-glm.danger, .dqw-ring-copilot.danger { stroke: var(--dsw-alias-label-danger, #c93c3c); }
|
|
303
|
-
.dqw-ball > svg:not(.dqw-ring) { position: relative; width: 16px; height: 16px; }
|
|
304
247
|
.dqw-capsule { display: inline-flex; align-items: center; gap: 6px; height: 26px; padding: 0 10px; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 999px; background: var(--dsw-alias-bg-base, rgba(128,128,128,.08)); background: color-mix(in srgb, var(--dsw-alias-bg-base, #1f1f1f) 86%, transparent); -webkit-backdrop-filter: blur(10px) saturate(1.2); backdrop-filter: blur(10px) saturate(1.2); box-shadow: 0 4px 14px rgba(0,0,0,.22); color: var(--dsw-alias-label-primary, inherit); font: inherit; font-size: 11px; line-height: 24px; cursor: pointer; user-select: none; white-space: nowrap; }
|
|
305
248
|
.dqw-capsule:hover { border-color: var(--dsw-alias-border-primary, var(--dsw-alias-border-secondary, rgba(128,128,128,.35))); }
|
|
306
249
|
.dqw-capsule:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #5b8def); outline-offset: 2px; }
|
|
@@ -308,14 +251,14 @@ var FLOAT_STYLE_TEXT = `
|
|
|
308
251
|
.dqw-capsule-glm, .dqw-capsule-copilot { font-variant-numeric: tabular-nums; font-weight: 600; }
|
|
309
252
|
.dqw-capsule-glm.warn, .dqw-capsule-copilot.warn { color: var(--dsw-alias-label-warning, #d29922); }
|
|
310
253
|
.dqw-capsule-glm.danger, .dqw-capsule-copilot.danger { color: var(--dsw-alias-label-danger, #c93c3c); }
|
|
254
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
255
|
+
.dqw-capsule[data-alert] { animation: dqw-pulse 1.6s ease-in-out infinite; }
|
|
256
|
+
}
|
|
257
|
+
@keyframes dqw-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.06); } }
|
|
311
258
|
.dqw-menu { position: fixed; min-width: 150px; padding: 4px; 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) 88%, 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); font: inherit; display: flex; flex-direction: column; }
|
|
312
259
|
.dqw-menu[hidden] { display: none; }
|
|
313
260
|
.dqw-menu-item { display: flex; align-items: center; gap: 6px; margin: 0; padding: 6px 10px; border: 0; border-radius: 6px; background: transparent; color: inherit; font: inherit; font-size: 11px; line-height: 16px; text-align: left; cursor: pointer; }
|
|
314
261
|
.dqw-menu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
315
|
-
@media (prefers-reduced-motion: no-preference) {
|
|
316
|
-
.dqw-ball[data-alert] { animation: dqw-ball-pulse 1.6s ease-in-out infinite; }
|
|
317
|
-
}
|
|
318
|
-
@keyframes dqw-ball-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.08); } }
|
|
319
262
|
.dqw-panel { position: fixed; width: min(320px, calc(100vw - 24px)); max-height: calc(100vh - 24px); overflow-y: auto; 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; }
|
|
320
263
|
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { .dqw-panel { background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); } }
|
|
321
264
|
.dqw-panel[hidden] { display: none; }
|
|
@@ -327,6 +270,43 @@ var FLOAT_STYLE_TEXT = `
|
|
|
327
270
|
.dqw-panel-back:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); }
|
|
328
271
|
.dqw-panel-close { display: inline-flex; flex: none; align-items: center; justify-content: center; width: 24px; height: 24px; margin: 0; padding: 0; border: 0; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, inherit); cursor: pointer; font: inherit; font-size: 12px; line-height: 1; }
|
|
329
272
|
.dqw-panel-close:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); }
|
|
273
|
+
.dqw-overview-list { display: flex; flex-direction: column; gap: 2px; }
|
|
274
|
+
.dqw-row-summary { display: flex; align-items: center; gap: 6px; padding: 3px 6px; margin: 0 -2px; border-radius: 8px; }
|
|
275
|
+
.dqw-row-summary[data-expandable] { cursor: pointer; }
|
|
276
|
+
.dqw-row-summary[data-expandable]:hover { background: var(--dsw-alias-bg-hover, rgba(128,128,128,.12)); }
|
|
277
|
+
.dqw-row-summary:focus-visible { outline: 1px solid var(--dsw-alias-button-primary-fill, #5b8def); outline-offset: -1px; }
|
|
278
|
+
.dqw-row-summary.is-error { cursor: default; }
|
|
279
|
+
.dqw-label { font-size: 11px; font-weight: 600; width: 52px; flex: none; }
|
|
280
|
+
.dqw-label[data-action="refresh"] { cursor: pointer; border-radius: 4px; }
|
|
281
|
+
.dqw-label[data-action="refresh"]:hover { opacity: .75; }
|
|
282
|
+
.dqw-bar { display: block; flex: 1; height: 4px; overflow: hidden; border-radius: 4px; background: var(--dsw-alias-bg-tertiary, rgba(128,128,128,.2)); }
|
|
283
|
+
.dqw-bar-fill { display: block; height: 100%; border-radius: inherit; background: var(--dsw-alias-button-primary-fill, #5b8def); }
|
|
284
|
+
.dqw-bar-fill.warn { background: var(--dsw-alias-label-warning, #d29922); }
|
|
285
|
+
.dqw-bar-fill.danger { background: var(--dsw-alias-label-danger, #c93c3c); }
|
|
286
|
+
.dqw-pct { font-size: 10.5px; line-height: 14px; font-variant-numeric: tabular-nums; width: 30px; text-align: right; flex: none; }
|
|
287
|
+
.dqw-extra { font-size: 10px; line-height: 14px; opacity: .72; font-variant-numeric: tabular-nums; flex: none; min-width: 34px; text-align: right; }
|
|
288
|
+
.dqw-chev { font-size: 9px; line-height: 14px; opacity: .6; flex: none; width: 10px; text-align: center; }
|
|
289
|
+
.dqw-stale-mark { flex: none; font-size: 9px; line-height: 14px; color: var(--dsw-alias-label-warning, #b46900); cursor: help; }
|
|
290
|
+
.dqw-errtext { font-size: 10px; line-height: 14px; opacity: .7; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
291
|
+
.dqw-bignums { display: flex; gap: 6px; }
|
|
292
|
+
.dqw-big { flex: 1; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.25)); border-radius: 8px; padding: 5px 8px; }
|
|
293
|
+
.dqw-big .dqw-big-value { font-size: 15px; font-weight: 650; line-height: 19px; font-variant-numeric: tabular-nums; }
|
|
294
|
+
.dqw-big .dqw-big-label { font-size: 9.5px; line-height: 13px; opacity: .65; }
|
|
295
|
+
.dqw-muted { margin: 0; font-size: 10px; line-height: 14px; opacity: .7; }
|
|
296
|
+
.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; }
|
|
297
|
+
.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; }
|
|
298
|
+
.dqw-model-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; opacity: .85; }
|
|
299
|
+
.dqw-model-value { flex: none; font-weight: 600; }
|
|
300
|
+
.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; }
|
|
301
|
+
.dqw-kv-label { flex: none; opacity: .65; }
|
|
302
|
+
.dqw-kv-value { min-width: 0; text-align: right; font-weight: 600; }
|
|
303
|
+
.dqw-kv-sub { font-weight: 400; opacity: .55; }
|
|
304
|
+
.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; }
|
|
305
|
+
.dqw-win-name { opacity: .65; }
|
|
306
|
+
.dqw-win-mid { opacity: .65; white-space: nowrap; }
|
|
307
|
+
.dqw-win-used { opacity: .65; white-space: nowrap; }
|
|
308
|
+
.dqw-win-val { min-width: 0; font-weight: 600; }
|
|
309
|
+
.dqw-error { margin: 0; font-size: 10px; line-height: 14px; color: var(--dsw-alias-label-warning, #b46900); }
|
|
330
310
|
`;
|
|
331
311
|
function localeFor(win) {
|
|
332
312
|
return String(win?.navigator?.language ?? "zh").toLowerCase().startsWith("zh") ? "zh" : "en";
|
|
@@ -408,35 +388,6 @@ function menuItem(doc, action, label) {
|
|
|
408
388
|
item.append(text(doc, "span", "", label));
|
|
409
389
|
return item;
|
|
410
390
|
}
|
|
411
|
-
function ringSvg(doc) {
|
|
412
|
-
const namespace = "http://www.w3.org/2000/svg";
|
|
413
|
-
const svg = doc.createElementNS(namespace, "svg");
|
|
414
|
-
svg.setAttribute("class", "dqw-ring");
|
|
415
|
-
svg.setAttribute("viewBox", "0 0 38 38");
|
|
416
|
-
svg.setAttribute("aria-hidden", "true");
|
|
417
|
-
svg.setAttribute("focusable", "false");
|
|
418
|
-
const track = doc.createElementNS(namespace, "circle");
|
|
419
|
-
track.setAttribute("class", "dqw-ring-track");
|
|
420
|
-
const glm = doc.createElementNS(namespace, "circle");
|
|
421
|
-
glm.setAttribute("class", "dqw-ring-glm");
|
|
422
|
-
glm.setAttribute("transform", "rotate(-90 19 19)");
|
|
423
|
-
const copilot = doc.createElementNS(namespace, "circle");
|
|
424
|
-
copilot.setAttribute("class", "dqw-ring-copilot");
|
|
425
|
-
copilot.setAttribute("transform", "rotate(90 19 19)");
|
|
426
|
-
track.setAttribute("cx", "19");
|
|
427
|
-
track.setAttribute("cy", "19");
|
|
428
|
-
track.setAttribute("r", String(RING_RADIUS));
|
|
429
|
-
track.setAttribute("fill", "none");
|
|
430
|
-
for (const circle of [glm, copilot]) {
|
|
431
|
-
circle.setAttribute("cx", "19");
|
|
432
|
-
circle.setAttribute("cy", "19");
|
|
433
|
-
circle.setAttribute("r", String(RING_RADIUS));
|
|
434
|
-
circle.setAttribute("fill", "none");
|
|
435
|
-
circle.setAttribute("stroke-dasharray", `0 ${RING_CIRCUMFERENCE}`);
|
|
436
|
-
}
|
|
437
|
-
svg.append(track, glm, copilot);
|
|
438
|
-
return svg;
|
|
439
|
-
}
|
|
440
391
|
function backIcon(doc) {
|
|
441
392
|
const namespace = "http://www.w3.org/2000/svg";
|
|
442
393
|
const svg = doc.createElementNS(namespace, "svg");
|
|
@@ -554,16 +505,6 @@ function mountQuotaCard({
|
|
|
554
505
|
floatHost.dataset.dshQuotaWatchFloat = "";
|
|
555
506
|
const floatRoot = floatHost.attachShadow({ mode: "open" });
|
|
556
507
|
floatRoot.append(text(doc, "style", "", FLOAT_STYLE_TEXT));
|
|
557
|
-
const buildBall = () => {
|
|
558
|
-
const el = doc.createElement("button");
|
|
559
|
-
el.type = "button";
|
|
560
|
-
el.className = "dqw-ball";
|
|
561
|
-
el.dataset.dshQuotaWatchBall = "";
|
|
562
|
-
el.setAttribute("aria-haspopup", "dialog");
|
|
563
|
-
el.setAttribute("aria-expanded", "false");
|
|
564
|
-
el.append(ringSvg(doc), quotaIcon(doc));
|
|
565
|
-
return el;
|
|
566
|
-
};
|
|
567
508
|
const buildCapsule = () => {
|
|
568
509
|
const el = doc.createElement("div");
|
|
569
510
|
el.className = "dqw-capsule";
|
|
@@ -579,7 +520,7 @@ function mountQuotaCard({
|
|
|
579
520
|
);
|
|
580
521
|
return el;
|
|
581
522
|
};
|
|
582
|
-
let surface =
|
|
523
|
+
let surface = buildCapsule();
|
|
583
524
|
floatRoot.append(surface);
|
|
584
525
|
const syncFloatVisibility = () => {
|
|
585
526
|
surface.hidden = ballSessionHidden || dataHidden;
|
|
@@ -595,9 +536,9 @@ function mountQuotaCard({
|
|
|
595
536
|
const viewHeight = win.innerHeight ?? 768;
|
|
596
537
|
const saved = loadFloatGeometry(win.localStorage);
|
|
597
538
|
const point = clampPoint(
|
|
598
|
-
saved ?? { x: viewWidth -
|
|
539
|
+
saved ?? { x: viewWidth - SURFACE_MARGIN - SURFACE_SIZE, y: viewHeight - SURFACE_MARGIN - SURFACE_SIZE },
|
|
599
540
|
{ width: viewWidth, height: viewHeight },
|
|
600
|
-
|
|
541
|
+
SURFACE_SIZE
|
|
601
542
|
);
|
|
602
543
|
floatHost.style.left = `${Math.round(point.x)}px`;
|
|
603
544
|
floatHost.style.top = `${Math.round(point.y)}px`;
|
|
@@ -643,7 +584,7 @@ function mountQuotaCard({
|
|
|
643
584
|
const point = clampPoint(
|
|
644
585
|
{ x: event.clientX, y: event.clientY },
|
|
645
586
|
{ width: viewWidth, height: viewHeight },
|
|
646
|
-
|
|
587
|
+
SURFACE_SIZE
|
|
647
588
|
);
|
|
648
589
|
floatHost.style.left = `${Math.round(point.x)}px`;
|
|
649
590
|
floatHost.style.top = `${Math.round(point.y)}px`;
|
|
@@ -729,44 +670,6 @@ function mountQuotaCard({
|
|
|
729
670
|
row.append(text(doc, "span", "dqw-chev", openKey === key ? "\u25BE" : "\u25B8"));
|
|
730
671
|
return [row];
|
|
731
672
|
};
|
|
732
|
-
const renderOverviewProvider = (key, model) => {
|
|
733
|
-
if (model === null) return null;
|
|
734
|
-
const label = text(doc, "span", "dqw-label", model.label);
|
|
735
|
-
if (model.kind === "error") {
|
|
736
|
-
const row2 = doc.createElement("div");
|
|
737
|
-
row2.className = "dqw-overview-error";
|
|
738
|
-
row2.append(label, text(doc, "span", "dqw-errtext", model.error ? `${copy.failed}: ${model.error}` : copy.failed));
|
|
739
|
-
return row2;
|
|
740
|
-
}
|
|
741
|
-
const row = doc.createElement("button");
|
|
742
|
-
row.type = "button";
|
|
743
|
-
row.className = "dqw-overview-row";
|
|
744
|
-
row.dataset.dshQuotaWatchPanelProvider = key;
|
|
745
|
-
row.setAttribute("aria-expanded", "false");
|
|
746
|
-
row.setAttribute("aria-label", `${model.label} \xB7 ${typeof model.percent === "number" ? formatPercent(model.percent, locale) : "\u2014"} \xB7 ${copy.openDetails}`);
|
|
747
|
-
row.append(label);
|
|
748
|
-
if (typeof model.percent === "number" && Number.isFinite(model.percent)) {
|
|
749
|
-
const bar = doc.createElement("span");
|
|
750
|
-
bar.className = "dqw-bar";
|
|
751
|
-
bar.setAttribute("role", "progressbar");
|
|
752
|
-
bar.setAttribute("aria-valuemin", "0");
|
|
753
|
-
bar.setAttribute("aria-valuemax", "100");
|
|
754
|
-
bar.setAttribute("aria-valuenow", String(Math.max(0, Math.min(100, model.percent))));
|
|
755
|
-
const fill = text(doc, "span", fillClass(model.percent));
|
|
756
|
-
fill.style.width = `${Math.max(0, Math.min(100, model.percent))}%`;
|
|
757
|
-
bar.append(fill);
|
|
758
|
-
row.append(bar);
|
|
759
|
-
}
|
|
760
|
-
if (model.stale) {
|
|
761
|
-
const mark = text(doc, "span", "dqw-stale-mark", "\u26A0");
|
|
762
|
-
mark.dataset.dshQuotaWatchStale = "";
|
|
763
|
-
mark.title = model.error ?? copy.stale;
|
|
764
|
-
row.append(mark);
|
|
765
|
-
}
|
|
766
|
-
row.append(text(doc, "span", "dqw-pct", typeof model.percent === "number" ? formatPercent(model.percent, locale) : "\u2014"));
|
|
767
|
-
row.append(text(doc, "span", "dqw-chev", "\u25B8"));
|
|
768
|
-
return row;
|
|
769
|
-
};
|
|
770
673
|
const panelHeader = ({ title, showBack = false }) => {
|
|
771
674
|
const header = doc.createElement("div");
|
|
772
675
|
header.className = "dqw-panel-header";
|
|
@@ -797,9 +700,9 @@ function mountQuotaCard({
|
|
|
797
700
|
const glm = providers.find((provider) => provider?.key === "glm");
|
|
798
701
|
const copilot = providers.find((provider) => provider?.key === "copilot");
|
|
799
702
|
const rows = [
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
].filter(Boolean);
|
|
703
|
+
renderProvider("glm", glmRowModel(glm)),
|
|
704
|
+
renderProvider("copilot", copilotRowModel(copilot))
|
|
705
|
+
].filter(Boolean).flat();
|
|
803
706
|
if (rows.length === 0) {
|
|
804
707
|
panel.append(text(doc, "p", "dqw-muted", lastUpdated.textContent || copy.loading));
|
|
805
708
|
return;
|
|
@@ -807,6 +710,9 @@ function mountQuotaCard({
|
|
|
807
710
|
const list = doc.createElement("div");
|
|
808
711
|
list.className = "dqw-overview-list";
|
|
809
712
|
list.append(...rows);
|
|
713
|
+
for (const row of list.querySelectorAll("[data-dsh-quota-watch-row]")) {
|
|
714
|
+
row.dataset.dshQuotaWatchPanelProvider = row.dataset.dshQuotaWatchRow;
|
|
715
|
+
}
|
|
810
716
|
panel.append(list);
|
|
811
717
|
};
|
|
812
718
|
function renderDetail(detail, key, provider, fromPanelNav = false) {
|
|
@@ -958,13 +864,6 @@ function mountQuotaCard({
|
|
|
958
864
|
placePanel();
|
|
959
865
|
surface.setAttribute("aria-expanded", "true");
|
|
960
866
|
};
|
|
961
|
-
const applyArc = (circle, model) => {
|
|
962
|
-
const pct = model && typeof model.percent === "number" && Number.isFinite(model.percent) ? Math.max(0, Math.min(100, model.percent)) : null;
|
|
963
|
-
const fill = pct === null ? 0 : pct / 100 * (RING_CIRCUMFERENCE / 2 - RING_HALF_GAP);
|
|
964
|
-
circle.setAttribute("stroke-dasharray", `${fill.toFixed(2)} ${(RING_CIRCUMFERENCE - fill).toFixed(2)}`);
|
|
965
|
-
circle.classList.toggle("warn", pct !== null && pct > 80 && pct <= 95);
|
|
966
|
-
circle.classList.toggle("danger", pct !== null && pct > 95);
|
|
967
|
-
};
|
|
968
867
|
const renderFloatFace = () => {
|
|
969
868
|
const providers = snapshot?.providers ?? [];
|
|
970
869
|
const glm = glmRowModel(providers.find((provider) => provider?.key === "glm"));
|
|
@@ -976,24 +875,19 @@ function mountQuotaCard({
|
|
|
976
875
|
}
|
|
977
876
|
syncFloatVisibility();
|
|
978
877
|
if (floatHost.hidden) return;
|
|
979
|
-
|
|
980
|
-
const
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
applyArc(surface.querySelector(".dqw-ring-copilot"), copilot);
|
|
993
|
-
const alert = [glm, copilot].some((model) => model !== null && typeof model.percent === "number" && model.percent > 95);
|
|
994
|
-
if (alert) surface.setAttribute("data-alert", "true");
|
|
995
|
-
else surface.removeAttribute("data-alert");
|
|
996
|
-
}
|
|
878
|
+
const paint = (span, model) => {
|
|
879
|
+
const visible = model !== null && typeof model.percent === "number" && Number.isFinite(model.percent);
|
|
880
|
+
span.textContent = visible ? `${model.label} ${formatPercent(model.percent, locale)}` : "";
|
|
881
|
+
span.classList.toggle("warn", visible && model.percent > 80 && model.percent <= 95);
|
|
882
|
+
span.classList.toggle("danger", visible && model.percent > 95);
|
|
883
|
+
return visible;
|
|
884
|
+
};
|
|
885
|
+
const glmVisible = paint(surface.querySelector(".dqw-capsule-glm"), glm);
|
|
886
|
+
const copilotVisible = paint(surface.querySelector(".dqw-capsule-copilot"), copilot);
|
|
887
|
+
surface.querySelector(".dqw-capsule-sep").hidden = !glmVisible || !copilotVisible;
|
|
888
|
+
const alert = [glm, copilot].some((model) => model !== null && typeof model.percent === "number" && model.percent > 95);
|
|
889
|
+
if (alert) surface.setAttribute("data-alert", "true");
|
|
890
|
+
else surface.removeAttribute("data-alert");
|
|
997
891
|
const summary = [glm, copilot].filter(Boolean).map((model) => `${model.label} ${formatPercent(model.percent, locale)}`).join(" \xB7 ");
|
|
998
892
|
const label = summary === "" ? copy.title : locale === "zh" ? `${copy.title}\uFF1A${summary}` : `${copy.title}: ${summary}`;
|
|
999
893
|
surface.setAttribute("aria-label", label);
|
|
@@ -1004,7 +898,7 @@ function mountQuotaCard({
|
|
|
1004
898
|
suppressNextClick = false;
|
|
1005
899
|
return;
|
|
1006
900
|
}
|
|
1007
|
-
if (openKey
|
|
901
|
+
if (openKey !== void 0 && !panel.hidden) {
|
|
1008
902
|
openKey = void 0;
|
|
1009
903
|
syncPop();
|
|
1010
904
|
return;
|
|
@@ -1016,25 +910,9 @@ function mountQuotaCard({
|
|
|
1016
910
|
event.preventDefault();
|
|
1017
911
|
onSurfaceClick();
|
|
1018
912
|
};
|
|
1019
|
-
const setFloatMode = (nextMode) => {
|
|
1020
|
-
saveFloatMode(win.localStorage, nextMode);
|
|
1021
|
-
openKey = void 0;
|
|
1022
|
-
menu.hidden = true;
|
|
1023
|
-
syncPop();
|
|
1024
|
-
const previous = surface;
|
|
1025
|
-
surface = nextMode === "capsule" ? buildCapsule() : buildBall();
|
|
1026
|
-
previous.replaceWith(surface);
|
|
1027
|
-
surface.addEventListener("click", onSurfaceClick);
|
|
1028
|
-
surface.addEventListener("keydown", onSurfaceKeydown);
|
|
1029
|
-
surface.addEventListener("contextmenu", onSurfaceContext);
|
|
1030
|
-
disposeDrag();
|
|
1031
|
-
disposeDrag = attachDrag(surface);
|
|
1032
|
-
renderFloatFace();
|
|
1033
|
-
};
|
|
1034
913
|
const hasCardMount = () => Boolean(footArea(doc));
|
|
1035
914
|
const openMenu = () => {
|
|
1036
|
-
const
|
|
1037
|
-
const items = [menuItem(doc, "toggle-mode", isBall ? copy.menu.toggleToCapsule : copy.menu.toggleToBall)];
|
|
915
|
+
const items = [];
|
|
1038
916
|
if (hasCardMount()) {
|
|
1039
917
|
items.push(menuItem(doc, "toggle-card", surfaceFlags.cardHidden ? copy.menu.showCard : copy.menu.hideCard));
|
|
1040
918
|
}
|
|
@@ -1061,9 +939,7 @@ function mountQuotaCard({
|
|
|
1061
939
|
const item = event.target?.closest?.("[data-menu]");
|
|
1062
940
|
if (!item) return;
|
|
1063
941
|
const action = item.dataset.menu;
|
|
1064
|
-
if (action === "toggle-
|
|
1065
|
-
setFloatMode(surface.dataset.dshQuotaWatchBall !== void 0 ? "capsule" : "ball");
|
|
1066
|
-
} else if (action === "toggle-card") {
|
|
942
|
+
if (action === "toggle-card") {
|
|
1067
943
|
setCardHidden(!surfaceFlags.cardHidden);
|
|
1068
944
|
} else if (action === "refresh") {
|
|
1069
945
|
void poll(true);
|
|
@@ -1224,6 +1100,15 @@ function mountQuotaCard({
|
|
|
1224
1100
|
body.addEventListener("click", onBodyClick);
|
|
1225
1101
|
body.addEventListener("keydown", onBodyKeydown);
|
|
1226
1102
|
panel.addEventListener("click", onPanelClick);
|
|
1103
|
+
panel.addEventListener("keydown", (event) => {
|
|
1104
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
1105
|
+
const row = event.target?.closest?.("[data-dsh-quota-watch-panel-provider][data-expandable]");
|
|
1106
|
+
if (!row) return;
|
|
1107
|
+
event.preventDefault();
|
|
1108
|
+
openKey = row.dataset.dshQuotaWatchPanelProvider;
|
|
1109
|
+
panelNav = true;
|
|
1110
|
+
syncPop();
|
|
1111
|
+
});
|
|
1227
1112
|
surface.addEventListener("click", onSurfaceClick);
|
|
1228
1113
|
surface.addEventListener("keydown", onSurfaceKeydown);
|
|
1229
1114
|
surface.addEventListener("contextmenu", onSurfaceContext);
|
package/package.json
CHANGED
package/src/client/prefs.mjs
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// Pure logic: no DOM. `storage` is a localStorage-like object (getItem/setItem).
|
|
3
3
|
|
|
4
4
|
export const FLOAT_GEOMETRY_KEY = 'dsh-quota-watch:float-geometry'
|
|
5
|
-
export const FLOAT_MODE_KEY = 'dsh-quota-watch:float-mode'
|
|
6
5
|
export const SURFACE_FLAGS_KEY = 'dsh-quota-watch:surface-flags'
|
|
7
6
|
|
|
8
7
|
const MARGIN = 8
|
|
@@ -45,24 +44,6 @@ export function saveFloatGeometry(storage, point) {
|
|
|
45
44
|
}
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
export function loadFloatMode(storage) {
|
|
49
|
-
try {
|
|
50
|
-
const raw = storage.getItem(FLOAT_MODE_KEY)
|
|
51
|
-
return raw === 'capsule' ? 'capsule' : 'ball'
|
|
52
|
-
} catch {
|
|
53
|
-
return 'ball'
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function saveFloatMode(storage, mode) {
|
|
58
|
-
if (mode !== 'ball' && mode !== 'capsule') throw new TypeError(`invalid float mode: ${mode}`)
|
|
59
|
-
try {
|
|
60
|
-
storage.setItem(FLOAT_MODE_KEY, mode)
|
|
61
|
-
} catch {
|
|
62
|
-
/* storage unavailable; mode stays session-only */
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
47
|
export function loadSurfaceFlags(storage) {
|
|
67
48
|
try {
|
|
68
49
|
const raw = storage.getItem(SURFACE_FLAGS_KEY)
|
package/src/client.mjs
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import { CLIENT_POLL_INTERVAL_MS, CLIENT_ROUTES } from './shared.mjs'
|
|
2
2
|
import { remainingToUsed } from './core/adapters.mjs'
|
|
3
|
-
import { clampPoint, loadFloatGeometry,
|
|
3
|
+
import { clampPoint, loadFloatGeometry, loadSurfaceFlags, saveFloatGeometry, saveSurfaceFlags } from './client/prefs.mjs'
|
|
4
4
|
|
|
5
5
|
export const name = 'quota-watch-client'
|
|
6
6
|
export const inject = []
|
|
7
7
|
|
|
8
8
|
const CARD_SELECTOR = '[data-dsh-quota-watch-card]'
|
|
9
9
|
const FETCH_TIMEOUT_MS = 15_000
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const RING_RADIUS = 15
|
|
13
|
-
const RING_CIRCUMFERENCE = 2 * Math.PI * RING_RADIUS
|
|
14
|
-
const RING_HALF_GAP = 2
|
|
10
|
+
const SURFACE_SIZE = 38
|
|
11
|
+
const SURFACE_MARGIN = 16
|
|
15
12
|
|
|
16
13
|
const COPY = {
|
|
17
14
|
zh: {
|
|
@@ -19,13 +16,11 @@ const COPY = {
|
|
|
19
16
|
openDetails: '查看详情',
|
|
20
17
|
close: '关闭',
|
|
21
18
|
menu: {
|
|
22
|
-
toggleToBall: '切为悬浮球',
|
|
23
|
-
toggleToCapsule: '切为胶囊',
|
|
24
19
|
showCard: '显示侧边栏卡片',
|
|
25
20
|
hideCard: '隐藏侧边栏卡片',
|
|
26
21
|
refreshNow: '立即刷新',
|
|
27
|
-
hideBall: '
|
|
28
|
-
showBall: '
|
|
22
|
+
hideBall: '隐藏悬浮胶囊',
|
|
23
|
+
showBall: '显示悬浮胶囊',
|
|
29
24
|
},
|
|
30
25
|
backToOverview: '返回额度概览',
|
|
31
26
|
providerNames: { glm: 'GLM', copilot: 'Copilot' },
|
|
@@ -56,13 +51,11 @@ const COPY = {
|
|
|
56
51
|
openDetails: 'View details',
|
|
57
52
|
close: 'Close',
|
|
58
53
|
menu: {
|
|
59
|
-
toggleToBall: 'Switch to ball',
|
|
60
|
-
toggleToCapsule: 'Switch to capsule',
|
|
61
54
|
showCard: 'Show sidebar card',
|
|
62
55
|
hideCard: 'Hide sidebar card',
|
|
63
56
|
refreshNow: 'Refresh now',
|
|
64
|
-
hideBall: 'Hide floating
|
|
65
|
-
showBall: 'Show floating
|
|
57
|
+
hideBall: 'Hide floating capsule',
|
|
58
|
+
showBall: 'Show floating capsule',
|
|
66
59
|
},
|
|
67
60
|
backToOverview: 'Back to quota overview',
|
|
68
61
|
providerNames: { glm: 'GLM', copilot: 'Copilot' },
|
|
@@ -118,47 +111,15 @@ const STYLE_TEXT = `
|
|
|
118
111
|
.dqw-ball-toggle[data-ball-hidden="true"] { color: var(--dsw-alias-button-primary-fill, #5b8def); }
|
|
119
112
|
.dqw-ball-toggle:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
120
113
|
.dqw-ball-toggle svg { display: block; width: 14px; height: 14px; }
|
|
121
|
-
.dqw-overview-list { display: flex; flex-direction: column; gap: 2px; }
|
|
122
|
-
.dqw-overview-row { display: flex; align-items: center; width: 100%; gap: 6px; margin: 0; padding: 5px 4px; border: 0; border-radius: 8px; background: transparent; color: inherit; font: inherit; text-align: left; cursor: pointer; }
|
|
123
|
-
.dqw-overview-row:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
124
|
-
.dqw-overview-row:focus-visible { outline: 1px solid var(--dsw-alias-brand-primary, #5b8def); outline-offset: -1px; }
|
|
125
|
-
.dqw-overview-error { display: flex; align-items: center; gap: 6px; padding: 5px 4px; }
|
|
126
|
-
.dqw-bignums { display: flex; gap: 6px; }
|
|
127
|
-
.dqw-big { flex: 1; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.25)); border-radius: 8px; padding: 5px 8px; }
|
|
128
|
-
.dqw-big .dqw-big-value { font-size: 15px; font-weight: 650; line-height: 19px; font-variant-numeric: tabular-nums; }
|
|
129
|
-
.dqw-big .dqw-big-label { font-size: 9.5px; line-height: 13px; opacity: .65; }
|
|
130
|
-
.dqw-muted { margin: 0; font-size: 10px; line-height: 14px; opacity: .7; }
|
|
131
|
-
.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; }
|
|
132
|
-
.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; }
|
|
133
|
-
.dqw-model-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; opacity: .85; }
|
|
134
|
-
.dqw-model-value { flex: none; font-weight: 600; }
|
|
135
|
-
.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; }
|
|
136
|
-
.dqw-kv-label { flex: none; opacity: .65; }
|
|
137
|
-
.dqw-kv-value { min-width: 0; text-align: right; font-weight: 600; }
|
|
138
|
-
.dqw-kv-sub { font-weight: 400; opacity: .55; }
|
|
139
|
-
.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; }
|
|
140
|
-
.dqw-win-name { opacity: .65; }
|
|
141
|
-
.dqw-win-mid { opacity: .65; white-space: nowrap; }
|
|
142
|
-
.dqw-win-used { opacity: .65; white-space: nowrap; }
|
|
143
|
-
.dqw-win-val { min-width: 0; font-weight: 600; }
|
|
144
|
-
.dqw-error { margin: 0; font-size: 10px; line-height: 14px; color: var(--dsw-alias-label-warning, #b46900); }
|
|
145
114
|
`
|
|
146
115
|
|
|
147
|
-
// Float shell (
|
|
148
|
-
//
|
|
149
|
-
//
|
|
116
|
+
// Float shell (capsule) and the panel live on their own shadow host at the
|
|
117
|
+
// document root — ADR 0001 isolation, and survival without any sidebar DOM
|
|
118
|
+
// (collapsed sidebar, desktop profile). Panel content shares the card's visual
|
|
119
|
+
// language, so the row/detail rules are duplicated into THIS shadow root; the
|
|
120
|
+
// card shadow keeps its own copies for the sidebar rows.
|
|
150
121
|
const FLOAT_STYLE_TEXT = `
|
|
151
122
|
:host { position: fixed; z-index: 2147483000; }
|
|
152
|
-
.dqw-ball { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 38px; height: 38px; margin: 0; padding: 0; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 50%; background: var(--dsw-alias-bg-base, rgba(128,128,128,.08)); background: color-mix(in srgb, var(--dsw-alias-bg-base, #1f1f1f) 86%, transparent); -webkit-backdrop-filter: blur(10px) saturate(1.2); backdrop-filter: blur(10px) saturate(1.2); box-shadow: 0 4px 14px rgba(0,0,0,.22); color: var(--dsw-alias-label-primary, inherit); font: inherit; cursor: pointer; }
|
|
153
|
-
.dqw-ball:hover { border-color: var(--dsw-alias-border-primary, var(--dsw-alias-border-secondary, rgba(128,128,128,.35))); }
|
|
154
|
-
.dqw-ball:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #5b8def); outline-offset: 2px; }
|
|
155
|
-
.dqw-ring { position: absolute; inset: 0; width: 100%; height: 100%; }
|
|
156
|
-
.dqw-ring-track { fill: none; stroke: var(--dsw-alias-bg-tertiary, rgba(128,128,128,.2)); stroke-width: 3.5; }
|
|
157
|
-
.dqw-ring-glm { fill: none; stroke: var(--dsw-alias-button-primary-fill, #5b8def); stroke-width: 3.5; stroke-linecap: round; transition: stroke-dasharray .3s ease; }
|
|
158
|
-
.dqw-ring-copilot { fill: none; stroke: var(--dsw-alias-label-success, #3fb950); stroke-width: 3.5; stroke-linecap: round; transition: stroke-dasharray .3s ease; }
|
|
159
|
-
.dqw-ring-glm.warn, .dqw-ring-copilot.warn { stroke: var(--dsw-alias-label-warning, #d29922); }
|
|
160
|
-
.dqw-ring-glm.danger, .dqw-ring-copilot.danger { stroke: var(--dsw-alias-label-danger, #c93c3c); }
|
|
161
|
-
.dqw-ball > svg:not(.dqw-ring) { position: relative; width: 16px; height: 16px; }
|
|
162
123
|
.dqw-capsule { display: inline-flex; align-items: center; gap: 6px; height: 26px; padding: 0 10px; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.35)); border-radius: 999px; background: var(--dsw-alias-bg-base, rgba(128,128,128,.08)); background: color-mix(in srgb, var(--dsw-alias-bg-base, #1f1f1f) 86%, transparent); -webkit-backdrop-filter: blur(10px) saturate(1.2); backdrop-filter: blur(10px) saturate(1.2); box-shadow: 0 4px 14px rgba(0,0,0,.22); color: var(--dsw-alias-label-primary, inherit); font: inherit; font-size: 11px; line-height: 24px; cursor: pointer; user-select: none; white-space: nowrap; }
|
|
163
124
|
.dqw-capsule:hover { border-color: var(--dsw-alias-border-primary, var(--dsw-alias-border-secondary, rgba(128,128,128,.35))); }
|
|
164
125
|
.dqw-capsule:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #5b8def); outline-offset: 2px; }
|
|
@@ -166,14 +127,14 @@ const FLOAT_STYLE_TEXT = `
|
|
|
166
127
|
.dqw-capsule-glm, .dqw-capsule-copilot { font-variant-numeric: tabular-nums; font-weight: 600; }
|
|
167
128
|
.dqw-capsule-glm.warn, .dqw-capsule-copilot.warn { color: var(--dsw-alias-label-warning, #d29922); }
|
|
168
129
|
.dqw-capsule-glm.danger, .dqw-capsule-copilot.danger { color: var(--dsw-alias-label-danger, #c93c3c); }
|
|
130
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
131
|
+
.dqw-capsule[data-alert] { animation: dqw-pulse 1.6s ease-in-out infinite; }
|
|
132
|
+
}
|
|
133
|
+
@keyframes dqw-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.06); } }
|
|
169
134
|
.dqw-menu { position: fixed; min-width: 150px; padding: 4px; 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) 88%, 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); font: inherit; display: flex; flex-direction: column; }
|
|
170
135
|
.dqw-menu[hidden] { display: none; }
|
|
171
136
|
.dqw-menu-item { display: flex; align-items: center; gap: 6px; margin: 0; padding: 6px 10px; border: 0; border-radius: 6px; background: transparent; color: inherit; font: inherit; font-size: 11px; line-height: 16px; text-align: left; cursor: pointer; }
|
|
172
137
|
.dqw-menu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); }
|
|
173
|
-
@media (prefers-reduced-motion: no-preference) {
|
|
174
|
-
.dqw-ball[data-alert] { animation: dqw-ball-pulse 1.6s ease-in-out infinite; }
|
|
175
|
-
}
|
|
176
|
-
@keyframes dqw-ball-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.08); } }
|
|
177
138
|
.dqw-panel { position: fixed; width: min(320px, calc(100vw - 24px)); max-height: calc(100vh - 24px); overflow-y: auto; 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; }
|
|
178
139
|
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { .dqw-panel { background: var(--dsw-alias-bg-elevated, var(--dsw-alias-bg-base, #1f1f1f)); } }
|
|
179
140
|
.dqw-panel[hidden] { display: none; }
|
|
@@ -185,6 +146,43 @@ const FLOAT_STYLE_TEXT = `
|
|
|
185
146
|
.dqw-panel-back:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); }
|
|
186
147
|
.dqw-panel-close { display: inline-flex; flex: none; align-items: center; justify-content: center; width: 24px; height: 24px; margin: 0; padding: 0; border: 0; border-radius: 6px; background: transparent; color: var(--dsw-alias-label-secondary, inherit); cursor: pointer; font: inherit; font-size: 12px; line-height: 1; }
|
|
187
148
|
.dqw-panel-close:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(128,128,128,.12)); color: var(--dsw-alias-label-primary, inherit); }
|
|
149
|
+
.dqw-overview-list { display: flex; flex-direction: column; gap: 2px; }
|
|
150
|
+
.dqw-row-summary { display: flex; align-items: center; gap: 6px; padding: 3px 6px; margin: 0 -2px; border-radius: 8px; }
|
|
151
|
+
.dqw-row-summary[data-expandable] { cursor: pointer; }
|
|
152
|
+
.dqw-row-summary[data-expandable]:hover { background: var(--dsw-alias-bg-hover, rgba(128,128,128,.12)); }
|
|
153
|
+
.dqw-row-summary:focus-visible { outline: 1px solid var(--dsw-alias-button-primary-fill, #5b8def); outline-offset: -1px; }
|
|
154
|
+
.dqw-row-summary.is-error { cursor: default; }
|
|
155
|
+
.dqw-label { font-size: 11px; font-weight: 600; width: 52px; flex: none; }
|
|
156
|
+
.dqw-label[data-action="refresh"] { cursor: pointer; border-radius: 4px; }
|
|
157
|
+
.dqw-label[data-action="refresh"]:hover { opacity: .75; }
|
|
158
|
+
.dqw-bar { display: block; flex: 1; height: 4px; overflow: hidden; border-radius: 4px; background: var(--dsw-alias-bg-tertiary, rgba(128,128,128,.2)); }
|
|
159
|
+
.dqw-bar-fill { display: block; height: 100%; border-radius: inherit; background: var(--dsw-alias-button-primary-fill, #5b8def); }
|
|
160
|
+
.dqw-bar-fill.warn { background: var(--dsw-alias-label-warning, #d29922); }
|
|
161
|
+
.dqw-bar-fill.danger { background: var(--dsw-alias-label-danger, #c93c3c); }
|
|
162
|
+
.dqw-pct { font-size: 10.5px; line-height: 14px; font-variant-numeric: tabular-nums; width: 30px; text-align: right; flex: none; }
|
|
163
|
+
.dqw-extra { font-size: 10px; line-height: 14px; opacity: .72; font-variant-numeric: tabular-nums; flex: none; min-width: 34px; text-align: right; }
|
|
164
|
+
.dqw-chev { font-size: 9px; line-height: 14px; opacity: .6; flex: none; width: 10px; text-align: center; }
|
|
165
|
+
.dqw-stale-mark { flex: none; font-size: 9px; line-height: 14px; color: var(--dsw-alias-label-warning, #b46900); cursor: help; }
|
|
166
|
+
.dqw-errtext { font-size: 10px; line-height: 14px; opacity: .7; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
167
|
+
.dqw-bignums { display: flex; gap: 6px; }
|
|
168
|
+
.dqw-big { flex: 1; border: 1px solid var(--dsw-alias-border-secondary, rgba(128,128,128,.25)); border-radius: 8px; padding: 5px 8px; }
|
|
169
|
+
.dqw-big .dqw-big-value { font-size: 15px; font-weight: 650; line-height: 19px; font-variant-numeric: tabular-nums; }
|
|
170
|
+
.dqw-big .dqw-big-label { font-size: 9.5px; line-height: 13px; opacity: .65; }
|
|
171
|
+
.dqw-muted { margin: 0; font-size: 10px; line-height: 14px; opacity: .7; }
|
|
172
|
+
.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; }
|
|
173
|
+
.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; }
|
|
174
|
+
.dqw-model-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; opacity: .85; }
|
|
175
|
+
.dqw-model-value { flex: none; font-weight: 600; }
|
|
176
|
+
.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; }
|
|
177
|
+
.dqw-kv-label { flex: none; opacity: .65; }
|
|
178
|
+
.dqw-kv-value { min-width: 0; text-align: right; font-weight: 600; }
|
|
179
|
+
.dqw-kv-sub { font-weight: 400; opacity: .55; }
|
|
180
|
+
.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; }
|
|
181
|
+
.dqw-win-name { opacity: .65; }
|
|
182
|
+
.dqw-win-mid { opacity: .65; white-space: nowrap; }
|
|
183
|
+
.dqw-win-used { opacity: .65; white-space: nowrap; }
|
|
184
|
+
.dqw-win-val { min-width: 0; font-weight: 600; }
|
|
185
|
+
.dqw-error { margin: 0; font-size: 10px; line-height: 14px; color: var(--dsw-alias-label-warning, #b46900); }
|
|
188
186
|
`
|
|
189
187
|
|
|
190
188
|
function localeFor(win) {
|
|
@@ -276,37 +274,7 @@ function menuItem(doc, action, label) {
|
|
|
276
274
|
return item
|
|
277
275
|
}
|
|
278
276
|
|
|
279
|
-
/**
|
|
280
|
-
function ringSvg(doc) {
|
|
281
|
-
const namespace = 'http://www.w3.org/2000/svg'
|
|
282
|
-
const svg = doc.createElementNS(namespace, 'svg')
|
|
283
|
-
svg.setAttribute('class', 'dqw-ring')
|
|
284
|
-
svg.setAttribute('viewBox', '0 0 38 38')
|
|
285
|
-
svg.setAttribute('aria-hidden', 'true')
|
|
286
|
-
svg.setAttribute('focusable', 'false')
|
|
287
|
-
const track = doc.createElementNS(namespace, 'circle')
|
|
288
|
-
track.setAttribute('class', 'dqw-ring-track')
|
|
289
|
-
const glm = doc.createElementNS(namespace, 'circle')
|
|
290
|
-
glm.setAttribute('class', 'dqw-ring-glm')
|
|
291
|
-
glm.setAttribute('transform', 'rotate(-90 19 19)')
|
|
292
|
-
const copilot = doc.createElementNS(namespace, 'circle')
|
|
293
|
-
copilot.setAttribute('class', 'dqw-ring-copilot')
|
|
294
|
-
copilot.setAttribute('transform', 'rotate(90 19 19)')
|
|
295
|
-
track.setAttribute('cx', '19')
|
|
296
|
-
track.setAttribute('cy', '19')
|
|
297
|
-
track.setAttribute('r', String(RING_RADIUS))
|
|
298
|
-
track.setAttribute('fill', 'none')
|
|
299
|
-
for (const circle of [glm, copilot]) {
|
|
300
|
-
circle.setAttribute('cx', '19')
|
|
301
|
-
circle.setAttribute('cy', '19')
|
|
302
|
-
circle.setAttribute('r', String(RING_RADIUS))
|
|
303
|
-
circle.setAttribute('fill', 'none')
|
|
304
|
-
circle.setAttribute('stroke-dasharray', `0 ${RING_CIRCUMFERENCE}`)
|
|
305
|
-
}
|
|
306
|
-
svg.append(track, glm, copilot)
|
|
307
|
-
return svg
|
|
308
|
-
}
|
|
309
|
-
|
|
277
|
+
/** Double-chevron icon for the panel's back affordance. */
|
|
310
278
|
function backIcon(doc) {
|
|
311
279
|
const namespace = 'http://www.w3.org/2000/svg'
|
|
312
280
|
const svg = doc.createElementNS(namespace, 'svg')
|
|
@@ -437,16 +405,6 @@ export function mountQuotaCard({
|
|
|
437
405
|
floatHost.dataset.dshQuotaWatchFloat = ''
|
|
438
406
|
const floatRoot = floatHost.attachShadow({ mode: 'open' })
|
|
439
407
|
floatRoot.append(text(doc, 'style', '', FLOAT_STYLE_TEXT))
|
|
440
|
-
const buildBall = () => {
|
|
441
|
-
const el = doc.createElement('button')
|
|
442
|
-
el.type = 'button'
|
|
443
|
-
el.className = 'dqw-ball'
|
|
444
|
-
el.dataset.dshQuotaWatchBall = ''
|
|
445
|
-
el.setAttribute('aria-haspopup', 'dialog')
|
|
446
|
-
el.setAttribute('aria-expanded', 'false')
|
|
447
|
-
el.append(ringSvg(doc), quotaIcon(doc))
|
|
448
|
-
return el
|
|
449
|
-
}
|
|
450
408
|
const buildCapsule = () => {
|
|
451
409
|
const el = doc.createElement('div')
|
|
452
410
|
el.className = 'dqw-capsule'
|
|
@@ -462,8 +420,8 @@ export function mountQuotaCard({
|
|
|
462
420
|
)
|
|
463
421
|
return el
|
|
464
422
|
}
|
|
465
|
-
//
|
|
466
|
-
let surface =
|
|
423
|
+
// Capsule is the only floating surface (0.1.1: the round ball was retired).
|
|
424
|
+
let surface = buildCapsule()
|
|
467
425
|
floatRoot.append(surface)
|
|
468
426
|
/** Session hide only hides the surface; the host stays alive for the panel. */
|
|
469
427
|
const syncFloatVisibility = () => {
|
|
@@ -480,9 +438,9 @@ export function mountQuotaCard({
|
|
|
480
438
|
const viewHeight = win.innerHeight ?? 768
|
|
481
439
|
const saved = loadFloatGeometry(win.localStorage)
|
|
482
440
|
const point = clampPoint(
|
|
483
|
-
saved ?? { x: viewWidth -
|
|
441
|
+
saved ?? { x: viewWidth - SURFACE_MARGIN - SURFACE_SIZE, y: viewHeight - SURFACE_MARGIN - SURFACE_SIZE },
|
|
484
442
|
{ width: viewWidth, height: viewHeight },
|
|
485
|
-
|
|
443
|
+
SURFACE_SIZE,
|
|
486
444
|
)
|
|
487
445
|
floatHost.style.left = `${Math.round(point.x)}px`
|
|
488
446
|
floatHost.style.top = `${Math.round(point.y)}px`
|
|
@@ -534,7 +492,7 @@ export function mountQuotaCard({
|
|
|
534
492
|
const point = clampPoint(
|
|
535
493
|
{ x: event.clientX, y: event.clientY },
|
|
536
494
|
{ width: viewWidth, height: viewHeight },
|
|
537
|
-
|
|
495
|
+
SURFACE_SIZE,
|
|
538
496
|
)
|
|
539
497
|
floatHost.style.left = `${Math.round(point.x)}px`
|
|
540
498
|
floatHost.style.top = `${Math.round(point.y)}px`
|
|
@@ -627,45 +585,6 @@ export function mountQuotaCard({
|
|
|
627
585
|
return [row]
|
|
628
586
|
}
|
|
629
587
|
|
|
630
|
-
const renderOverviewProvider = (key, model) => {
|
|
631
|
-
if (model === null) return null
|
|
632
|
-
const label = text(doc, 'span', 'dqw-label', model.label)
|
|
633
|
-
if (model.kind === 'error') {
|
|
634
|
-
const row = doc.createElement('div')
|
|
635
|
-
row.className = 'dqw-overview-error'
|
|
636
|
-
row.append(label, text(doc, 'span', 'dqw-errtext', model.error ? `${copy.failed}: ${model.error}` : copy.failed))
|
|
637
|
-
return row
|
|
638
|
-
}
|
|
639
|
-
const row = doc.createElement('button')
|
|
640
|
-
row.type = 'button'
|
|
641
|
-
row.className = 'dqw-overview-row'
|
|
642
|
-
row.dataset.dshQuotaWatchPanelProvider = key
|
|
643
|
-
row.setAttribute('aria-expanded', 'false')
|
|
644
|
-
row.setAttribute('aria-label', `${model.label} · ${typeof model.percent === 'number' ? formatPercent(model.percent, locale) : '—'} · ${copy.openDetails}`)
|
|
645
|
-
row.append(label)
|
|
646
|
-
if (typeof model.percent === 'number' && Number.isFinite(model.percent)) {
|
|
647
|
-
const bar = doc.createElement('span')
|
|
648
|
-
bar.className = 'dqw-bar'
|
|
649
|
-
bar.setAttribute('role', 'progressbar')
|
|
650
|
-
bar.setAttribute('aria-valuemin', '0')
|
|
651
|
-
bar.setAttribute('aria-valuemax', '100')
|
|
652
|
-
bar.setAttribute('aria-valuenow', String(Math.max(0, Math.min(100, model.percent))))
|
|
653
|
-
const fill = text(doc, 'span', fillClass(model.percent))
|
|
654
|
-
fill.style.width = `${Math.max(0, Math.min(100, model.percent))}%`
|
|
655
|
-
bar.append(fill)
|
|
656
|
-
row.append(bar)
|
|
657
|
-
}
|
|
658
|
-
if (model.stale) {
|
|
659
|
-
const mark = text(doc, 'span', 'dqw-stale-mark', '⚠')
|
|
660
|
-
mark.dataset.dshQuotaWatchStale = ''
|
|
661
|
-
mark.title = model.error ?? copy.stale
|
|
662
|
-
row.append(mark)
|
|
663
|
-
}
|
|
664
|
-
row.append(text(doc, 'span', 'dqw-pct', typeof model.percent === 'number' ? formatPercent(model.percent, locale) : '—'))
|
|
665
|
-
row.append(text(doc, 'span', 'dqw-chev', '▸'))
|
|
666
|
-
return row
|
|
667
|
-
}
|
|
668
|
-
|
|
669
588
|
/** Panel chrome: optional back, title, always a close affordance. */
|
|
670
589
|
const panelHeader = ({ title, showBack = false }) => {
|
|
671
590
|
const header = doc.createElement('div')
|
|
@@ -697,10 +616,13 @@ export function mountQuotaCard({
|
|
|
697
616
|
const providers = snapshot?.providers ?? []
|
|
698
617
|
const glm = providers.find((provider) => provider?.key === 'glm')
|
|
699
618
|
const copilot = providers.find((provider) => provider?.key === 'copilot')
|
|
619
|
+
// The overview reuses the card's row-summary renderer verbatim so the
|
|
620
|
+
// panel looks exactly like the 0.0.18 sidebar rows (label · bar · percent
|
|
621
|
+
// · extra · chevron).
|
|
700
622
|
const rows = [
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
].filter(Boolean)
|
|
623
|
+
renderProvider('glm', glmRowModel(glm)),
|
|
624
|
+
renderProvider('copilot', copilotRowModel(copilot)),
|
|
625
|
+
].filter(Boolean).flat()
|
|
704
626
|
if (rows.length === 0) {
|
|
705
627
|
panel.append(text(doc, 'p', 'dqw-muted', lastUpdated.textContent || copy.loading))
|
|
706
628
|
return
|
|
@@ -708,6 +630,10 @@ export function mountQuotaCard({
|
|
|
708
630
|
const list = doc.createElement('div')
|
|
709
631
|
list.className = 'dqw-overview-list'
|
|
710
632
|
list.append(...rows)
|
|
633
|
+
// Tag the reused rows for the panel's own click/keyboard routing.
|
|
634
|
+
for (const row of list.querySelectorAll('[data-dsh-quota-watch-row]')) {
|
|
635
|
+
row.dataset.dshQuotaWatchPanelProvider = row.dataset.dshQuotaWatchRow
|
|
636
|
+
}
|
|
711
637
|
panel.append(list)
|
|
712
638
|
}
|
|
713
639
|
|
|
@@ -869,16 +795,6 @@ export function mountQuotaCard({
|
|
|
869
795
|
surface.setAttribute('aria-expanded', 'true')
|
|
870
796
|
}
|
|
871
797
|
|
|
872
|
-
const applyArc = (circle, model) => {
|
|
873
|
-
const pct = model && typeof model.percent === 'number' && Number.isFinite(model.percent)
|
|
874
|
-
? Math.max(0, Math.min(100, model.percent))
|
|
875
|
-
: null
|
|
876
|
-
const fill = pct === null ? 0 : (pct / 100) * (RING_CIRCUMFERENCE / 2 - RING_HALF_GAP)
|
|
877
|
-
circle.setAttribute('stroke-dasharray', `${fill.toFixed(2)} ${(RING_CIRCUMFERENCE - fill).toFixed(2)}`)
|
|
878
|
-
circle.classList.toggle('warn', pct !== null && pct > 80 && pct <= 95)
|
|
879
|
-
circle.classList.toggle('danger', pct !== null && pct > 95)
|
|
880
|
-
}
|
|
881
|
-
|
|
882
798
|
const renderFloatFace = () => {
|
|
883
799
|
const providers = snapshot?.providers ?? []
|
|
884
800
|
const glm = glmRowModel(providers.find((provider) => provider?.key === 'glm'))
|
|
@@ -890,24 +806,19 @@ export function mountQuotaCard({
|
|
|
890
806
|
}
|
|
891
807
|
syncFloatVisibility()
|
|
892
808
|
if (floatHost.hidden) return
|
|
893
|
-
|
|
894
|
-
const
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
return visible
|
|
900
|
-
}
|
|
901
|
-
const glmVisible = paint(surface.querySelector('.dqw-capsule-glm'), glm)
|
|
902
|
-
const copilotVisible = paint(surface.querySelector('.dqw-capsule-copilot'), copilot)
|
|
903
|
-
surface.querySelector('.dqw-capsule-sep').hidden = !glmVisible || !copilotVisible
|
|
904
|
-
} else {
|
|
905
|
-
applyArc(surface.querySelector('.dqw-ring-glm'), glm)
|
|
906
|
-
applyArc(surface.querySelector('.dqw-ring-copilot'), copilot)
|
|
907
|
-
const alert = [glm, copilot].some((model) => model !== null && typeof model.percent === 'number' && model.percent > 95)
|
|
908
|
-
if (alert) surface.setAttribute('data-alert', 'true')
|
|
909
|
-
else surface.removeAttribute('data-alert')
|
|
809
|
+
const paint = (span, model) => {
|
|
810
|
+
const visible = model !== null && typeof model.percent === 'number' && Number.isFinite(model.percent)
|
|
811
|
+
span.textContent = visible ? `${model.label} ${formatPercent(model.percent, locale)}` : ''
|
|
812
|
+
span.classList.toggle('warn', visible && model.percent > 80 && model.percent <= 95)
|
|
813
|
+
span.classList.toggle('danger', visible && model.percent > 95)
|
|
814
|
+
return visible
|
|
910
815
|
}
|
|
816
|
+
const glmVisible = paint(surface.querySelector('.dqw-capsule-glm'), glm)
|
|
817
|
+
const copilotVisible = paint(surface.querySelector('.dqw-capsule-copilot'), copilot)
|
|
818
|
+
surface.querySelector('.dqw-capsule-sep').hidden = !glmVisible || !copilotVisible
|
|
819
|
+
const alert = [glm, copilot].some((model) => model !== null && typeof model.percent === 'number' && model.percent > 95)
|
|
820
|
+
if (alert) surface.setAttribute('data-alert', 'true')
|
|
821
|
+
else surface.removeAttribute('data-alert')
|
|
911
822
|
const summary = [glm, copilot]
|
|
912
823
|
.filter(Boolean)
|
|
913
824
|
.map((model) => `${model.label} ${formatPercent(model.percent, locale)}`)
|
|
@@ -922,7 +833,8 @@ export function mountQuotaCard({
|
|
|
922
833
|
suppressNextClick = false
|
|
923
834
|
return
|
|
924
835
|
}
|
|
925
|
-
|
|
836
|
+
// Toggle semantics (same as the 0.0.18 rail): any open panel closes first.
|
|
837
|
+
if (openKey !== undefined && !panel.hidden) {
|
|
926
838
|
openKey = undefined
|
|
927
839
|
syncPop()
|
|
928
840
|
return
|
|
@@ -934,26 +846,9 @@ export function mountQuotaCard({
|
|
|
934
846
|
event.preventDefault()
|
|
935
847
|
onSurfaceClick()
|
|
936
848
|
}
|
|
937
|
-
/** Swap ball ↔ capsule: persist, close the panel, rebuild and re-wire the surface. */
|
|
938
|
-
const setFloatMode = (nextMode) => {
|
|
939
|
-
saveFloatMode(win.localStorage, nextMode)
|
|
940
|
-
openKey = undefined
|
|
941
|
-
menu.hidden = true
|
|
942
|
-
syncPop()
|
|
943
|
-
const previous = surface
|
|
944
|
-
surface = nextMode === 'capsule' ? buildCapsule() : buildBall()
|
|
945
|
-
previous.replaceWith(surface)
|
|
946
|
-
surface.addEventListener('click', onSurfaceClick)
|
|
947
|
-
surface.addEventListener('keydown', onSurfaceKeydown)
|
|
948
|
-
surface.addEventListener('contextmenu', onSurfaceContext)
|
|
949
|
-
disposeDrag()
|
|
950
|
-
disposeDrag = attachDrag(surface)
|
|
951
|
-
renderFloatFace()
|
|
952
|
-
}
|
|
953
849
|
const hasCardMount = () => Boolean(footArea(doc))
|
|
954
850
|
const openMenu = () => {
|
|
955
|
-
const
|
|
956
|
-
const items = [menuItem(doc, 'toggle-mode', isBall ? copy.menu.toggleToCapsule : copy.menu.toggleToBall)]
|
|
851
|
+
const items = []
|
|
957
852
|
if (hasCardMount()) {
|
|
958
853
|
items.push(menuItem(doc, 'toggle-card', surfaceFlags.cardHidden ? copy.menu.showCard : copy.menu.hideCard))
|
|
959
854
|
}
|
|
@@ -980,9 +875,7 @@ export function mountQuotaCard({
|
|
|
980
875
|
const item = event.target?.closest?.('[data-menu]')
|
|
981
876
|
if (!item) return
|
|
982
877
|
const action = item.dataset.menu
|
|
983
|
-
if (action === 'toggle-
|
|
984
|
-
setFloatMode(surface.dataset.dshQuotaWatchBall !== undefined ? 'capsule' : 'ball')
|
|
985
|
-
} else if (action === 'toggle-card') {
|
|
878
|
+
if (action === 'toggle-card') {
|
|
986
879
|
setCardHidden(!surfaceFlags.cardHidden)
|
|
987
880
|
} else if (action === 'refresh') {
|
|
988
881
|
void poll(true)
|
|
@@ -1148,6 +1041,15 @@ export function mountQuotaCard({
|
|
|
1148
1041
|
body.addEventListener('click', onBodyClick)
|
|
1149
1042
|
body.addEventListener('keydown', onBodyKeydown)
|
|
1150
1043
|
panel.addEventListener('click', onPanelClick)
|
|
1044
|
+
panel.addEventListener('keydown', (event) => {
|
|
1045
|
+
if (event.key !== 'Enter' && event.key !== ' ') return
|
|
1046
|
+
const row = event.target?.closest?.('[data-dsh-quota-watch-panel-provider][data-expandable]')
|
|
1047
|
+
if (!row) return
|
|
1048
|
+
event.preventDefault()
|
|
1049
|
+
openKey = row.dataset.dshQuotaWatchPanelProvider
|
|
1050
|
+
panelNav = true
|
|
1051
|
+
syncPop()
|
|
1052
|
+
})
|
|
1151
1053
|
surface.addEventListener('click', onSurfaceClick)
|
|
1152
1054
|
surface.addEventListener('keydown', onSurfaceKeydown)
|
|
1153
1055
|
surface.addEventListener('contextmenu', onSurfaceContext)
|