dsh-agy 0.4.2 → 0.4.3
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 -1
- package/lib/{accounts-D605JE2i.mjs → accounts-CB4blXpP.mjs} +5 -5
- package/lib/{accounts-D605JE2i.mjs.map → accounts-CB4blXpP.mjs.map} +1 -1
- package/lib/{catalog-BgdDVJEB.mjs → catalog-BRHHLLd5.mjs} +13 -3
- package/lib/{catalog-BgdDVJEB.mjs.map → catalog-BRHHLLd5.mjs.map} +1 -1
- package/lib/cli/index.mjs +2 -2
- package/lib/client.js +1457 -151
- package/lib/index.mjs +2 -2
- package/lib/{models-B6bD7zPi.mjs → models-CjB0rXtI.mjs} +2 -2
- package/lib/{models-B6bD7zPi.mjs.map → models-CjB0rXtI.mjs.map} +1 -1
- package/lib/parse-38V1zT1B.mjs +270 -0
- package/lib/parse-38V1zT1B.mjs.map +1 -0
- package/lib/{plugin-common-kSBwIaFi.mjs → plugin-common-D2NWMsX6.mjs} +8 -7
- package/lib/plugin-common-D2NWMsX6.mjs.map +1 -0
- package/lib/{translate-YhGDUuVa.mjs → translate-B9ZOauqw.mjs} +2 -2
- package/lib/{translate-YhGDUuVa.mjs.map → translate-B9ZOauqw.mjs.map} +1 -1
- package/lib/web/plugin.mjs +98 -4
- package/lib/web/plugin.mjs.map +1 -1
- package/package.json +4 -2
- package/lib/parse-Bs4rPkHA.mjs +0 -190
- package/lib/parse-Bs4rPkHA.mjs.map +0 -1
- package/lib/plugin-common-kSBwIaFi.mjs.map +0 -1
package/lib/client.js
CHANGED
|
@@ -31,35 +31,70 @@ window.__ModuleLoader__.load({
|
|
|
31
31
|
* host has no primitive for.
|
|
32
32
|
*/
|
|
33
33
|
const STYLE_ID = "dsh-agy-styles";
|
|
34
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Single source of truth for `--dsw-alias-*` fallbacks: the colour an alias
|
|
36
|
+
* renders with when the host theme has not defined it. The host owns the real
|
|
37
|
+
* values; this table only pins the degraded-mode answer, and every usage must
|
|
38
|
+
* go through `aliasVar()` so one alias cannot carry two answers — without the
|
|
39
|
+
* table the file grew three values for `border-l2` and a renamed-forever
|
|
40
|
+
* `--dsw-alias-brand-primary-new-colorprimary-new-color` that no host defines.
|
|
41
|
+
* Normalisation picks (degraded-mode only, enforced by the alias scans in
|
|
42
|
+
* tests/client.test.ts): `state-success` took `#10b981` over the duplicated
|
|
43
|
+
* `#22c55e` because the 60%-alpha glow variant derives from the same base, and
|
|
44
|
+
* `state-business` took the brand blue over the popover pill's emerald —
|
|
45
|
+
* active/cooling/disabled reads as blue/amber/grey, not success-green.
|
|
46
|
+
*/
|
|
47
|
+
const ALIAS_FALLBACKS = {
|
|
48
|
+
"bg-layer-1": "#fff",
|
|
49
|
+
"bg-layer-2": "#f4f5f7",
|
|
50
|
+
"bg-layer-3": "#fff",
|
|
51
|
+
"bg-overlay": "#fff",
|
|
52
|
+
"border-l1": "rgba(0,0,0,.04)",
|
|
53
|
+
"border-l2": "#e5e6eb",
|
|
54
|
+
"border-l3": "rgba(0,0,0,.15)",
|
|
55
|
+
"brand-primary": "#4176e6",
|
|
56
|
+
"interactive-bg-hover": "#f4f5f7",
|
|
57
|
+
"label-primary": "#1f2329",
|
|
58
|
+
"label-secondary": "#61666b",
|
|
59
|
+
"label-tertiary": "#8f959e",
|
|
60
|
+
"state-business-primary": "#4176e6",
|
|
61
|
+
"state-error-primary": "#ec1313",
|
|
62
|
+
"state-error-secondary": "#f59e0b",
|
|
63
|
+
"state-success-primary": "#10b981",
|
|
64
|
+
"state-warn-primary": "#f59e0b"
|
|
65
|
+
};
|
|
66
|
+
function aliasVar(alias) {
|
|
67
|
+
return `var(--dsw-alias-${alias}, ${ALIAS_FALLBACKS[alias]})`;
|
|
68
|
+
}
|
|
69
|
+
const AGY_STYLES_CSS = `
|
|
35
70
|
.agy-root { display: flex; flex-direction: column; gap: 12px; font: var(--dsw-font-xs-13); }
|
|
36
71
|
.agy-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
|
|
37
|
-
.agy-title { font: var(--dsw-font-base-strong-16); color:
|
|
38
|
-
.agy-sub { margin-top: 3px; font: var(--dsw-font-xxs-12); color:
|
|
72
|
+
.agy-title { font: var(--dsw-font-base-strong-16); color: ${aliasVar("label-primary")}; }
|
|
73
|
+
.agy-sub { margin-top: 3px; font: var(--dsw-font-xxs-12); color: ${aliasVar("label-tertiary")}; }
|
|
39
74
|
|
|
40
75
|
.agy-tabs {
|
|
41
76
|
display: flex; align-items: flex-end; gap: 22px; margin-top: 2px;
|
|
42
|
-
border-bottom: 0.5px solid
|
|
77
|
+
border-bottom: 0.5px solid ${aliasVar("border-l2")};
|
|
43
78
|
}
|
|
44
79
|
.agy-tab {
|
|
45
80
|
position: relative; border: 0; padding: 7px 1px 9px; background: transparent;
|
|
46
|
-
color:
|
|
81
|
+
color: ${aliasVar("label-tertiary")};
|
|
47
82
|
font: var(--dsw-font-xs-13); cursor: pointer;
|
|
48
83
|
}
|
|
49
|
-
.agy-tab:hover, .agy-tab[data-active="true"] { color:
|
|
84
|
+
.agy-tab:hover, .agy-tab[data-active="true"] { color: ${aliasVar("label-primary")}; }
|
|
50
85
|
/* Active tab is an underline rule, matching the Plugins settings section's own
|
|
51
86
|
tab bar rather than introducing a second, boxed tab idiom. */
|
|
52
87
|
.agy-tab[data-active="true"]::after {
|
|
53
88
|
position: absolute; right: 0; bottom: -1px; left: 0; height: 2px;
|
|
54
|
-
border-radius: 2px 2px 0 0; background:
|
|
89
|
+
border-radius: 2px 2px 0 0; background: ${aliasVar("label-primary")};
|
|
55
90
|
content: '';
|
|
56
91
|
}
|
|
57
92
|
.agy-tab:focus-visible {
|
|
58
|
-
outline: 2px solid
|
|
93
|
+
outline: 2px solid ${aliasVar("state-business-primary")};
|
|
59
94
|
outline-offset: 2px;
|
|
60
95
|
border-radius: 4px;
|
|
61
96
|
}
|
|
62
|
-
.agy-tab .agy-count { margin-left: 5px; font: var(--dsw-font-xxxs-11); color:
|
|
97
|
+
.agy-tab .agy-count { margin-left: 5px; font: var(--dsw-font-xxxs-11); color: ${aliasVar("label-tertiary")}; }
|
|
63
98
|
|
|
64
99
|
/* A failed action states WHAT failed and then WHY, on two lines. The default
|
|
65
100
|
white-space (normal) folds that newline into a space and runs the verdict
|
|
@@ -67,35 +102,35 @@ window.__ModuleLoader__.load({
|
|
|
67
102
|
.agy-notice already does for its own multi-line form. */
|
|
68
103
|
.agy-error { padding: 9px 12px; border-radius: 8px; font: var(--dsw-font-xxs-12);
|
|
69
104
|
white-space: pre-wrap; overflow-wrap: anywhere;
|
|
70
|
-
color:
|
|
71
|
-
background: color-mix(in srgb,
|
|
105
|
+
color: ${aliasVar("state-error-primary")};
|
|
106
|
+
background: color-mix(in srgb, ${aliasVar("state-error-primary")} 10%, transparent); }
|
|
72
107
|
/* A non-fatal outcome (a partial import). Neutral, not alarming, and it keeps
|
|
73
108
|
newlines so a list of per-source failures stays readable. */
|
|
74
109
|
.agy-notice { padding: 9px 12px; border-radius: 8px; font: var(--dsw-font-xxs-12);
|
|
75
110
|
white-space: pre-wrap; overflow-wrap: anywhere;
|
|
76
|
-
color:
|
|
77
|
-
background:
|
|
78
|
-
.agy-empty { padding: 24px 12px; text-align: center; font: var(--dsw-font-xs-13); color:
|
|
79
|
-
.agy-hint { margin: 0; font: var(--dsw-font-xxs-12); color:
|
|
80
|
-
.agy-aside { font: var(--dsw-font-xxxs-11); color:
|
|
111
|
+
color: ${aliasVar("label-secondary")};
|
|
112
|
+
background: ${aliasVar("bg-layer-2")}; }
|
|
113
|
+
.agy-empty { padding: 24px 12px; text-align: center; font: var(--dsw-font-xs-13); color: ${aliasVar("label-tertiary")}; }
|
|
114
|
+
.agy-hint { margin: 0; font: var(--dsw-font-xxs-12); color: ${aliasVar("label-tertiary")}; }
|
|
115
|
+
.agy-aside { font: var(--dsw-font-xxxs-11); color: ${aliasVar("label-tertiary")}; }
|
|
81
116
|
.agy-grow { flex: 1; }
|
|
82
117
|
|
|
83
118
|
/* ── Grouping surface ───────────────────────────────────────────────────────
|
|
84
119
|
DSH groups with spacing first and a light container second. A settings page
|
|
85
120
|
built only from tables reads as a spreadsheet, so each block gets a card. */
|
|
86
121
|
.agy-card {
|
|
87
|
-
border: 0.5px solid
|
|
122
|
+
border: 0.5px solid ${aliasVar("border-l2")};
|
|
88
123
|
border-radius: 12px;
|
|
89
|
-
background:
|
|
124
|
+
background: ${aliasVar("bg-layer-3")};
|
|
90
125
|
overflow: hidden;
|
|
91
126
|
}
|
|
92
127
|
.agy-card-head {
|
|
93
128
|
display: flex; align-items: center; justify-content: space-between; gap: 10px;
|
|
94
129
|
padding: 10px 12px;
|
|
95
|
-
border-bottom: 0.5px solid
|
|
96
|
-
background:
|
|
130
|
+
border-bottom: 0.5px solid ${aliasVar("border-l1")};
|
|
131
|
+
background: ${aliasVar("bg-layer-2")};
|
|
97
132
|
}
|
|
98
|
-
.agy-card-title { font: var(--dsw-font-xs-strong-13); color:
|
|
133
|
+
.agy-card-title { font: var(--dsw-font-xs-strong-13); color: ${aliasVar("label-primary")}; }
|
|
99
134
|
.agy-card-body { padding: 6px 12px 8px; }
|
|
100
135
|
|
|
101
136
|
/* ── Rows inside a card ────────────────────────────────────────────────────
|
|
@@ -111,8 +146,8 @@ window.__ModuleLoader__.load({
|
|
|
111
146
|
.agy-live {
|
|
112
147
|
display: flex; align-items: center; gap: 7px;
|
|
113
148
|
margin: 2px 2px 6px; padding: 7px 8px; border-radius: 10px;
|
|
114
|
-
font: var(--dsw-font-xxs-12); color:
|
|
115
|
-
background:
|
|
149
|
+
font: var(--dsw-font-xxs-12); color: ${aliasVar("label-secondary")};
|
|
150
|
+
background: ${aliasVar("bg-layer-2")};
|
|
116
151
|
}
|
|
117
152
|
/* Master/detail: the account list beside the selected account's detail, so a
|
|
118
153
|
* row and the panel it opens stay in view together — but only when the
|
|
@@ -157,23 +192,23 @@ window.__ModuleLoader__.load({
|
|
|
157
192
|
}
|
|
158
193
|
.agy-rowitem[data-clickable="true"] { cursor: pointer; }
|
|
159
194
|
.agy-rowitem[data-clickable="true"]:hover {
|
|
160
|
-
background:
|
|
195
|
+
background: ${aliasVar("interactive-bg-hover")};
|
|
161
196
|
}
|
|
162
197
|
.agy-rowitem[data-selected="true"] {
|
|
163
|
-
background:
|
|
198
|
+
background: ${aliasVar("interactive-bg-hover")};
|
|
164
199
|
}
|
|
165
200
|
.agy-rowitem:focus-visible {
|
|
166
|
-
outline: 2px solid
|
|
201
|
+
outline: 2px solid ${aliasVar("label-primary")};
|
|
167
202
|
outline-offset: -2px;
|
|
168
203
|
}
|
|
169
204
|
.agy-rowmain { min-width: 0; flex: 1 1 160px; display: flex; flex-direction: column; gap: 3px; }
|
|
170
205
|
.agy-rowtitle { display: flex; align-items: center; gap: 7px; min-width: 0; }
|
|
171
206
|
.agy-rowname {
|
|
172
|
-
font: var(--dsw-font-xs-strong-13); color:
|
|
207
|
+
font: var(--dsw-font-xs-strong-13); color: ${aliasVar("label-primary")};
|
|
173
208
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
174
209
|
}
|
|
175
210
|
.agy-rowmeta {
|
|
176
|
-
font: var(--dsw-font-xxxs-11); color:
|
|
211
|
+
font: var(--dsw-font-xxxs-11); color: ${aliasVar("label-tertiary")};
|
|
177
212
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
178
213
|
}
|
|
179
214
|
/* margin-left: auto right-aligns the cluster while it shares a line with the
|
|
@@ -191,15 +226,15 @@ window.__ModuleLoader__.load({
|
|
|
191
226
|
the gap and their own left alignment, and the dividers turned a metric strip
|
|
192
227
|
into a spreadsheet grid. */
|
|
193
228
|
.agy-metric { padding: 12px 0; }
|
|
194
|
-
.agy-metric-k { font: var(--dsw-font-xxxs-11); color:
|
|
229
|
+
.agy-metric-k { font: var(--dsw-font-xxxs-11); color: ${aliasVar("label-tertiary")}; }
|
|
195
230
|
/* The theme's own role carries family + size + line-height + weight; the
|
|
196
231
|
metric only tightens the tracking. */
|
|
197
232
|
.agy-metric-v { margin-top: 4px; font: var(--dsw-font-base-strong-16); letter-spacing: -.015em;
|
|
198
|
-
font-variant-numeric: tabular-nums; color:
|
|
233
|
+
font-variant-numeric: tabular-nums; color: ${aliasVar("label-primary")}; }
|
|
199
234
|
.agy-metric-v small { margin-left: 2px; font: var(--dsw-font-xxs-strong-12);
|
|
200
|
-
color:
|
|
235
|
+
color: ${aliasVar("label-tertiary")}; }
|
|
201
236
|
.agy-metric-d { margin-top: 3px; font: var(--dsw-font-xxxs-11);
|
|
202
|
-
color:
|
|
237
|
+
color: ${aliasVar("label-tertiary")}; }
|
|
203
238
|
|
|
204
239
|
/* ── Token composition ─────────────────────────────────────────────────────
|
|
205
240
|
A share bar per bucket, under the headline metrics. This exists to answer
|
|
@@ -212,16 +247,16 @@ window.__ModuleLoader__.load({
|
|
|
212
247
|
display: grid; grid-template-columns: 64px minmax(0,1fr) 56px 44px;
|
|
213
248
|
align-items: center; gap: 10px;
|
|
214
249
|
}
|
|
215
|
-
.agy-compose-k { font: var(--dsw-font-xxxs-11); color:
|
|
250
|
+
.agy-compose-k { font: var(--dsw-font-xxxs-11); color: ${aliasVar("label-tertiary")}; }
|
|
216
251
|
.agy-compose-track {
|
|
217
252
|
height: 6px; border-radius: 3px; overflow: hidden;
|
|
218
|
-
background:
|
|
253
|
+
background: ${aliasVar("border-l2")};
|
|
219
254
|
}
|
|
220
255
|
.agy-compose-track i { display: block; height: 100%; border-radius: 3px; }
|
|
221
256
|
.agy-compose-v { text-align: right; font: var(--dsw-font-xxs-12);
|
|
222
|
-
font-variant-numeric: tabular-nums; color:
|
|
257
|
+
font-variant-numeric: tabular-nums; color: ${aliasVar("label-secondary")}; }
|
|
223
258
|
.agy-compose-p { text-align: right; font: var(--dsw-font-xxxs-11);
|
|
224
|
-
font-variant-numeric: tabular-nums; color:
|
|
259
|
+
font-variant-numeric: tabular-nums; color: ${aliasVar("label-tertiary")}; }
|
|
225
260
|
/* A table's own footnote: states a column's scope that its header cannot. */
|
|
226
261
|
.agy-table-note { padding: 6px 8px 2px; }
|
|
227
262
|
/* The 65535 row is a CONFIGURATION of High, not a sibling tier: indenting it
|
|
@@ -231,25 +266,25 @@ window.__ModuleLoader__.load({
|
|
|
231
266
|
/* ── Definition rows (label / value pairs) ───────────────────────────────── */
|
|
232
267
|
.agy-defs { display: grid; grid-template-columns: 92px minmax(0,1fr); margin: 0; }
|
|
233
268
|
.agy-defs dt {
|
|
234
|
-
padding: 7px 0; font: var(--dsw-font-xxs-12); color:
|
|
235
|
-
border-bottom: 0.5px solid
|
|
269
|
+
padding: 7px 0; font: var(--dsw-font-xxs-12); color: ${aliasVar("label-tertiary")};
|
|
270
|
+
border-bottom: 0.5px solid ${aliasVar("border-l1")};
|
|
236
271
|
}
|
|
237
272
|
.agy-defs dd {
|
|
238
|
-
margin: 0; padding: 7px 0; font: var(--dsw-font-xxs-12); color:
|
|
239
|
-
border-bottom: 0.5px solid
|
|
273
|
+
margin: 0; padding: 7px 0; font: var(--dsw-font-xxs-12); color: ${aliasVar("label-secondary")};
|
|
274
|
+
border-bottom: 0.5px solid ${aliasVar("border-l1")};
|
|
240
275
|
overflow-wrap: anywhere;
|
|
241
276
|
}
|
|
242
277
|
.agy-defs dt:last-of-type, .agy-defs dd:last-of-type { border-bottom: 0; }
|
|
243
278
|
|
|
244
279
|
/* ── Disclosure (the collapsible model-quota block) ──────────────────────── */
|
|
245
|
-
.agy-disclosure { border-top: 0.5px solid
|
|
280
|
+
.agy-disclosure { border-top: 0.5px solid ${aliasVar("border-l1")}; }
|
|
246
281
|
.agy-disclosure-toggle {
|
|
247
282
|
display: flex; align-items: center; gap: 7px; width: 100%;
|
|
248
283
|
padding: 9px 0; border: 0; cursor: pointer;
|
|
249
284
|
font: var(--dsw-font-xxs-strong-12); text-align: left;
|
|
250
|
-
color:
|
|
285
|
+
color: ${aliasVar("label-secondary")}; background: none;
|
|
251
286
|
}
|
|
252
|
-
.agy-disclosure-toggle:hover { color:
|
|
287
|
+
.agy-disclosure-toggle:hover { color: ${aliasVar("label-primary")}; }
|
|
253
288
|
.agy-caret {
|
|
254
289
|
flex: none; width: 0; height: 0; border-left: 4px solid currentColor;
|
|
255
290
|
border-top: 3.5px solid transparent; border-bottom: 3.5px solid transparent;
|
|
@@ -267,7 +302,7 @@ window.__ModuleLoader__.load({
|
|
|
267
302
|
.agy-thinking-group { display: flex; flex-direction: column; }
|
|
268
303
|
.agy-thinking-group + .agy-thinking-group { margin-top: 16px; }
|
|
269
304
|
.agy-thinking-group-name {
|
|
270
|
-
font: var(--dsw-font-xxs-strong-12); color:
|
|
305
|
+
font: var(--dsw-font-xxs-strong-12); color: ${aliasVar("label-secondary")};
|
|
271
306
|
margin-bottom: 2px;
|
|
272
307
|
}
|
|
273
308
|
.agy-thinking-group .agy-hint { margin: 0 0 8px; }
|
|
@@ -278,38 +313,38 @@ window.__ModuleLoader__.load({
|
|
|
278
313
|
last one is why that group has no reference table. */
|
|
279
314
|
.agy-thinking-notes { margin: 0 0 8px; padding-left: 18px; }
|
|
280
315
|
.agy-thinking-notes li {
|
|
281
|
-
font: var(--dsw-font-xxxs-11); color:
|
|
316
|
+
font: var(--dsw-font-xxxs-11); color: ${aliasVar("label-tertiary")};
|
|
282
317
|
line-height: 1.6;
|
|
283
318
|
}
|
|
284
319
|
.agy-thinking-row {
|
|
285
320
|
display: grid; grid-template-columns: 72px minmax(0, 180px) auto;
|
|
286
321
|
align-items: center; gap: 14px; padding: 8px 0;
|
|
287
322
|
}
|
|
288
|
-
.agy-thinking-k { font: var(--dsw-font-xxs-12); color:
|
|
323
|
+
.agy-thinking-k { font: var(--dsw-font-xxs-12); color: ${aliasVar("label-secondary")}; }
|
|
289
324
|
/* Shortcut chips, not a second control: they fill the field beside them. */
|
|
290
325
|
.agy-thinking-chips { display: flex; gap: 6px; }
|
|
291
326
|
.agy-thinking-chip {
|
|
292
|
-
border: 0.5px solid
|
|
327
|
+
border: 0.5px solid ${aliasVar("border-l3")};
|
|
293
328
|
background: transparent; cursor: pointer; border-radius: 10px;
|
|
294
329
|
padding: 2px 8px; font: var(--dsw-font-xxxs-11);
|
|
295
|
-
color:
|
|
330
|
+
color: ${aliasVar("label-secondary")};
|
|
296
331
|
}
|
|
297
|
-
.agy-thinking-chip:hover { background:
|
|
332
|
+
.agy-thinking-chip:hover { background: ${aliasVar("interactive-bg-hover")}; }
|
|
298
333
|
.agy-thinking-chip:focus-visible {
|
|
299
|
-
outline: 2px solid
|
|
334
|
+
outline: 2px solid ${aliasVar("brand-primary")}; outline-offset: 1px;
|
|
300
335
|
}
|
|
301
336
|
.agy-disclosure-meta { margin-left: auto; font: var(--dsw-font-xxxs-11);
|
|
302
|
-
color:
|
|
337
|
+
color: ${aliasVar("label-tertiary")}; font-variant-numeric: tabular-nums; }
|
|
303
338
|
|
|
304
339
|
/* ── 5h / weekly limits ────────────────────────────────────────────────────
|
|
305
340
|
One group per upstream group (Gemini, Claude+GPT), each with its windows.
|
|
306
341
|
The rows are a fixed 4-column grid so the bars and the percentages line up
|
|
307
342
|
across groups: label / bar / percentage / reset countdown. */
|
|
308
343
|
.agy-limits { display: flex; flex-direction: column; gap: 10px; padding: 4px 0; }
|
|
309
|
-
.agy-limit-age { font: var(--dsw-font-xxxs-11); color:
|
|
344
|
+
.agy-limit-age { font: var(--dsw-font-xxxs-11); color: ${aliasVar("label-tertiary")}; }
|
|
310
345
|
.agy-limit-group { display: flex; flex-direction: column; gap: 2px; }
|
|
311
346
|
.agy-limit-group-name {
|
|
312
|
-
font: var(--dsw-font-xxs-strong-12); color:
|
|
347
|
+
font: var(--dsw-font-xxs-strong-12); color: ${aliasVar("label-secondary")};
|
|
313
348
|
padding-bottom: 2px;
|
|
314
349
|
}
|
|
315
350
|
.agy-limit-row {
|
|
@@ -317,33 +352,33 @@ window.__ModuleLoader__.load({
|
|
|
317
352
|
align-items: center; gap: 10px; padding: 4px 0;
|
|
318
353
|
font: var(--dsw-font-xxs-12);
|
|
319
354
|
}
|
|
320
|
-
.agy-limit-k { color:
|
|
355
|
+
.agy-limit-k { color: ${aliasVar("label-secondary")}; }
|
|
321
356
|
.agy-limit-track { height: 6px; border-radius: 3px; overflow: hidden;
|
|
322
|
-
background:
|
|
357
|
+
background: ${aliasVar("border-l2")}; }
|
|
323
358
|
.agy-limit-track i { display: block; height: 100%; border-radius: 3px; }
|
|
324
359
|
.agy-limit-p { text-align: right; font-variant-numeric: tabular-nums;
|
|
325
|
-
color:
|
|
360
|
+
color: ${aliasVar("label-primary")}; }
|
|
326
361
|
.agy-limit-reset { text-align: right; font: var(--dsw-font-xxxs-11);
|
|
327
|
-
color:
|
|
362
|
+
color: ${aliasVar("label-tertiary")}; }
|
|
328
363
|
/* The burn projection: indented to align with the bar (58px label + 10px gap),
|
|
329
364
|
warn-tinted because "this window runs dry before it resets" is the one
|
|
330
365
|
projection that asks the reader to act. */
|
|
331
366
|
.agy-limit-burn { padding: 0 0 4px 68px;
|
|
332
367
|
font: var(--dsw-font-xxxs-11);
|
|
333
|
-
color:
|
|
368
|
+
color: ${aliasVar("state-warn-primary")}; }
|
|
334
369
|
|
|
335
370
|
/* ── Dense breakdown tables (Usage tab only) ─────────────────────────────── */
|
|
336
371
|
.agy-table-wrap { padding: 6px 0 2px; }
|
|
337
372
|
.agy-table { width: 100%; border-collapse: collapse; font: var(--dsw-font-xs-13); table-layout: fixed; }
|
|
338
373
|
.agy-table th {
|
|
339
374
|
text-align: left; padding: 0 8px 7px; font: var(--dsw-font-xxxs-strong-11);
|
|
340
|
-
color:
|
|
341
|
-
border-bottom: 0.5px solid
|
|
375
|
+
color: ${aliasVar("label-tertiary")};
|
|
376
|
+
border-bottom: 0.5px solid ${aliasVar("border-l2")}; white-space: nowrap;
|
|
342
377
|
}
|
|
343
|
-
.agy-table td { padding: 8px; vertical-align: middle; color:
|
|
344
|
-
border-bottom: 0.5px solid
|
|
378
|
+
.agy-table td { padding: 8px; vertical-align: middle; color: ${aliasVar("label-secondary")};
|
|
379
|
+
border-bottom: 0.5px solid ${aliasVar("border-l1")}; }
|
|
345
380
|
.agy-table tr:last-child td { border-bottom: 0; }
|
|
346
|
-
.agy-table tbody tr:hover td { background:
|
|
381
|
+
.agy-table tbody tr:hover td { background: ${aliasVar("bg-layer-2")}; }
|
|
347
382
|
.agy-num { text-align: right; font-variant-numeric: tabular-nums; }
|
|
348
383
|
/* Numeric HEADERS must right-align too, and .agy-num alone cannot do it: the
|
|
349
384
|
.agy-table th rule above is specificity 0-1-1 and outranks the bare .agy-num
|
|
@@ -354,20 +389,20 @@ window.__ModuleLoader__.load({
|
|
|
354
389
|
escalating with !important. */
|
|
355
390
|
.agy-table th.agy-num { text-align: right; }
|
|
356
391
|
/* Inline emphasis on a table cell: a strong role, not a heavier size. */
|
|
357
|
-
.agy-strong { color:
|
|
392
|
+
.agy-strong { color: ${aliasVar("label-primary")}; font-weight: 500; }
|
|
358
393
|
.agy-mail { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
359
394
|
.agy-mono { font-family: var(--ds-font-family-code); }
|
|
360
395
|
/* The one external link in the section (a verification appeal URL). Colored and
|
|
361
396
|
underlined with theme tokens rather than left to the browser default, which
|
|
362
397
|
ignores both the light/dark theme and the host's brand color. */
|
|
363
|
-
.agy-link { color:
|
|
398
|
+
.agy-link { color: ${aliasVar("brand-primary")}; text-decoration: underline; }
|
|
364
399
|
.agy-link:hover { opacity: 0.8; }
|
|
365
400
|
|
|
366
401
|
.agy-bar { display: inline-flex; align-items: center; gap: 8px; justify-content: flex-end; }
|
|
367
402
|
.agy-bar .agy-track { width: 56px; height: 4px; border-radius: 2px; overflow: hidden;
|
|
368
|
-
background:
|
|
403
|
+
background: ${aliasVar("border-l2")}; }
|
|
369
404
|
.agy-bar .agy-track i { display: block; height: 100%; border-radius: 2px;
|
|
370
|
-
background:
|
|
405
|
+
background: ${aliasVar("brand-primary")}; }
|
|
371
406
|
|
|
372
407
|
/* ── Range picker container ────────────────────────────────────────────────
|
|
373
408
|
The pills themselves are the host Pill primitive (its own fill pair and
|
|
@@ -375,16 +410,16 @@ window.__ModuleLoader__.load({
|
|
|
375
410
|
.agy-chips { display: flex; gap: 6px; }
|
|
376
411
|
|
|
377
412
|
/* Danger has no primitive variant; keep the ghost skin and tint the label. */
|
|
378
|
-
.agy-btn-danger { color:
|
|
413
|
+
.agy-btn-danger { color: ${aliasVar("state-error-primary")} !important; }
|
|
379
414
|
|
|
380
415
|
/* ── Recent activity ring ──────────────────────────────────────────────────
|
|
381
416
|
The "what just happened" list. Only the result cell carries color — ok
|
|
382
417
|
inherits the table's neutral, and a wall of tinted rows would read as an
|
|
383
418
|
alarm rather than a log. */
|
|
384
419
|
.agy-recent-state { font: var(--dsw-font-xxs-12); }
|
|
385
|
-
.agy-recent-state[data-kind="fail"] { color:
|
|
386
|
-
.agy-recent-state[data-kind="limited"] { color:
|
|
387
|
-
.agy-recent-state[data-kind="rotation"] { color:
|
|
420
|
+
.agy-recent-state[data-kind="fail"] { color: ${aliasVar("state-error-primary")}; }
|
|
421
|
+
.agy-recent-state[data-kind="limited"] { color: ${aliasVar("state-warn-primary")}; }
|
|
422
|
+
.agy-recent-state[data-kind="rotation"] { color: ${aliasVar("brand-primary")}; }
|
|
388
423
|
/* The recent list is a standalone disclosure on the tab root, not one block
|
|
389
424
|
inside a card body — the separator border-top the disclosure idiom uses
|
|
390
425
|
between sibling blocks would draw a stray line across nothing here. */
|
|
@@ -393,9 +428,471 @@ window.__ModuleLoader__.load({
|
|
|
393
428
|
.agy-toolbar { display: flex; align-items: center; gap: 8px; }
|
|
394
429
|
.agy-textarea { width: 100%; min-height: 88px; resize: vertical; outline: none;
|
|
395
430
|
padding: 9px 10px; font: var(--dsw-font-xxs-12); font-family: var(--ds-font-family-code);
|
|
396
|
-
color:
|
|
397
|
-
border: 0.5px solid
|
|
398
|
-
.agy-textarea:focus { border-color:
|
|
431
|
+
color: ${aliasVar("label-primary")}; background: ${aliasVar("bg-layer-1")};
|
|
432
|
+
border: 0.5px solid ${aliasVar("border-l2")}; border-radius: 8px; }
|
|
433
|
+
.agy-textarea:focus { border-color: ${aliasVar("brand-primary")}; }
|
|
434
|
+
|
|
435
|
+
/* ── Preferences card ───────────────────────────────────────────────────── */
|
|
436
|
+
.agy-pref-row {
|
|
437
|
+
display: flex;
|
|
438
|
+
align-items: center;
|
|
439
|
+
justify-content: space-between;
|
|
440
|
+
gap: 16px;
|
|
441
|
+
padding: 4px 0;
|
|
442
|
+
}
|
|
443
|
+
.agy-pref-name {
|
|
444
|
+
font: var(--dsw-font-xs-strong-13);
|
|
445
|
+
color: ${aliasVar("label-primary")};
|
|
446
|
+
}
|
|
447
|
+
.agy-pref-desc {
|
|
448
|
+
font: var(--dsw-font-xxs-12);
|
|
449
|
+
color: ${aliasVar("label-tertiary")};
|
|
450
|
+
margin-top: 2px;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/* ── Conversation-header quota badge & popover ─────────────────────────── */
|
|
454
|
+
.agy-ui-badge {
|
|
455
|
+
display: inline-flex;
|
|
456
|
+
align-items: center;
|
|
457
|
+
gap: 6px;
|
|
458
|
+
background: ${aliasVar("bg-layer-2")};
|
|
459
|
+
border: 0.5px solid ${aliasVar("border-l2")};
|
|
460
|
+
border-radius: 9999px;
|
|
461
|
+
padding: 3px 10px;
|
|
462
|
+
font: var(--dsw-font-xxs-12);
|
|
463
|
+
color: ${aliasVar("label-primary")};
|
|
464
|
+
cursor: pointer;
|
|
465
|
+
user-select: none;
|
|
466
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
|
467
|
+
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
468
|
+
font-family: inherit;
|
|
469
|
+
height: auto;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
.agy-ui-badge:hover,
|
|
473
|
+
.agy-ui-badge.pinned {
|
|
474
|
+
background: ${aliasVar("bg-layer-3")};
|
|
475
|
+
border-color: ${aliasVar("border-l1")};
|
|
476
|
+
transform: translateY(-1px);
|
|
477
|
+
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.12);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
.agy-ui-badge.pinned {
|
|
481
|
+
border-color: ${aliasVar("brand-primary")};
|
|
482
|
+
box-shadow: 0 0 0 1px ${aliasVar("brand-primary")}, 0 3px 10px rgba(0, 0, 0, 0.12);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.agy-ui-dot {
|
|
486
|
+
display: inline-block;
|
|
487
|
+
width: 7px;
|
|
488
|
+
height: 7px;
|
|
489
|
+
border-radius: 50%;
|
|
490
|
+
flex-shrink: 0;
|
|
491
|
+
transition: background-color 0.3s;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.agy-ui-dot[data-state="done"],
|
|
495
|
+
.agy-ui-dot.active {
|
|
496
|
+
background-color: ${aliasVar("state-success-primary")};
|
|
497
|
+
box-shadow: 0 0 6px ${aliasVar("state-success-primary")};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
.agy-ui-dot[data-state="warning"],
|
|
501
|
+
.agy-ui-dot.cooling {
|
|
502
|
+
background-color: ${aliasVar("state-warn-primary")};
|
|
503
|
+
box-shadow: 0 0 6px ${aliasVar("state-warn-primary")};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.agy-ui-dot[data-state="idle"],
|
|
507
|
+
.agy-ui-dot.disabled {
|
|
508
|
+
background-color: ${aliasVar("label-tertiary")};
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.agy-ui-dot.updating {
|
|
512
|
+
animation: agy-ui-pulse 1.2s ease-in-out infinite;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.agy-ui-dot.active.updating,
|
|
516
|
+
.agy-ui-dot[data-state="done"].updating {
|
|
517
|
+
box-shadow: 0 0 10px ${aliasVar("state-success-primary")};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
.agy-ui-dot.cooling.updating,
|
|
521
|
+
.agy-ui-dot[data-state="warning"].updating {
|
|
522
|
+
box-shadow: 0 0 10px ${aliasVar("state-warn-primary")};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
@keyframes agy-ui-pulse {
|
|
526
|
+
0%, 100% {
|
|
527
|
+
opacity: 1;
|
|
528
|
+
transform: scale(1);
|
|
529
|
+
}
|
|
530
|
+
50% {
|
|
531
|
+
opacity: 0.35;
|
|
532
|
+
transform: scale(0.7);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
.agy-ui-sparkle {
|
|
537
|
+
color: ${aliasVar("brand-primary")};
|
|
538
|
+
font: var(--dsw-font-xs-13);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
.agy-ui-popover-container.desktop {
|
|
542
|
+
position: static;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
.agy-ui-popover-container.mobile {
|
|
546
|
+
position: fixed;
|
|
547
|
+
inset: 0;
|
|
548
|
+
z-index: 1000;
|
|
549
|
+
background: rgba(0, 0, 0, 0.4);
|
|
550
|
+
backdrop-filter: blur(6px);
|
|
551
|
+
-webkit-backdrop-filter: blur(6px);
|
|
552
|
+
display: flex;
|
|
553
|
+
align-items: flex-end;
|
|
554
|
+
justify-content: center;
|
|
555
|
+
animation: agy-ui-fade-in 0.2s ease-out;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
.agy-ui-popover {
|
|
559
|
+
background: ${aliasVar("bg-overlay")};
|
|
560
|
+
backdrop-filter: blur(20px);
|
|
561
|
+
-webkit-backdrop-filter: blur(20px);
|
|
562
|
+
border: 0.5px solid ${aliasVar("border-l2")};
|
|
563
|
+
box-shadow: 0 20px 45px rgba(0, 0, 0, 0.15), 0 0 0 0.5px ${aliasVar("border-l1")};
|
|
564
|
+
color: ${aliasVar("label-primary")};
|
|
565
|
+
overflow: hidden;
|
|
566
|
+
display: flex;
|
|
567
|
+
flex-direction: column;
|
|
568
|
+
box-sizing: border-box;
|
|
569
|
+
z-index: 1000;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
.agy-ui-popover.desktop {
|
|
573
|
+
width: 380px;
|
|
574
|
+
max-width: calc(100vw - 24px);
|
|
575
|
+
border-radius: 14px;
|
|
576
|
+
animation: agy-ui-popover-in 0.18s cubic-bezier(0.16, 1, 0.3, 1);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
.agy-ui-popover.mobile {
|
|
580
|
+
width: 100%;
|
|
581
|
+
max-height: 82vh;
|
|
582
|
+
border-radius: 20px 20px 0 0;
|
|
583
|
+
border-bottom: none;
|
|
584
|
+
animation: agy-ui-bottom-sheet-in 0.25s cubic-bezier(0.16, 1, 0.3, 1);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
.agy-ui-mobile-handle {
|
|
588
|
+
width: 36px;
|
|
589
|
+
height: 4px;
|
|
590
|
+
background: ${aliasVar("border-l2")};
|
|
591
|
+
border-radius: 9999px;
|
|
592
|
+
margin: 8px auto 2px auto;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
@keyframes agy-ui-popover-in {
|
|
596
|
+
from {
|
|
597
|
+
opacity: 0;
|
|
598
|
+
transform: translateY(4px) scale(0.98);
|
|
599
|
+
}
|
|
600
|
+
to {
|
|
601
|
+
opacity: 1;
|
|
602
|
+
transform: translateY(0) scale(1);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
@keyframes agy-ui-bottom-sheet-in {
|
|
607
|
+
from {
|
|
608
|
+
transform: translateY(100%);
|
|
609
|
+
}
|
|
610
|
+
to {
|
|
611
|
+
transform: translateY(0);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
@keyframes agy-ui-fade-in {
|
|
616
|
+
from { opacity: 0; }
|
|
617
|
+
to { opacity: 1; }
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
.agy-ui-modal-header {
|
|
621
|
+
padding: 10px 14px;
|
|
622
|
+
border-bottom: 0.5px solid ${aliasVar("border-l2")};
|
|
623
|
+
display: flex;
|
|
624
|
+
align-items: center;
|
|
625
|
+
justify-content: space-between;
|
|
626
|
+
background: ${aliasVar("bg-layer-1")};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
.agy-ui-modal-title {
|
|
630
|
+
display: flex;
|
|
631
|
+
align-items: center;
|
|
632
|
+
gap: 7px;
|
|
633
|
+
font: var(--dsw-font-xs-strong-13);
|
|
634
|
+
color: ${aliasVar("label-primary")};
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
.agy-ui-pinned-tag {
|
|
638
|
+
font: var(--dsw-font-xxxs-11);
|
|
639
|
+
margin-left: 2px;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
.agy-ui-header-actions {
|
|
643
|
+
display: flex;
|
|
644
|
+
align-items: center;
|
|
645
|
+
gap: 4px;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
.agy-ui-icon-btn {
|
|
649
|
+
color: ${aliasVar("label-secondary")};
|
|
650
|
+
padding: 4px;
|
|
651
|
+
min-width: 28px;
|
|
652
|
+
height: 28px;
|
|
653
|
+
display: flex;
|
|
654
|
+
align-items: center;
|
|
655
|
+
justify-content: center;
|
|
656
|
+
background: transparent;
|
|
657
|
+
border: none;
|
|
658
|
+
cursor: pointer;
|
|
659
|
+
border-radius: 6px;
|
|
660
|
+
transition: all 0.15s;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
.agy-ui-icon-btn:hover {
|
|
664
|
+
color: ${aliasVar("label-primary")};
|
|
665
|
+
background: ${aliasVar("bg-layer-2")};
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
.agy-ui-icon-btn.active {
|
|
669
|
+
color: ${aliasVar("brand-primary")};
|
|
670
|
+
background: color-mix(in srgb, ${aliasVar("brand-primary")} 18%, transparent);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
.agy-ui-icon-btn:disabled {
|
|
674
|
+
opacity: 0.5;
|
|
675
|
+
cursor: not-allowed;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
.agy-ui-spinning {
|
|
679
|
+
animation: agy-ui-spin 1s linear infinite;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
@keyframes agy-ui-spin {
|
|
683
|
+
from { transform: rotate(0deg); }
|
|
684
|
+
to { transform: rotate(360deg); }
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
.agy-ui-modal-body {
|
|
688
|
+
padding: 14px 16px;
|
|
689
|
+
overflow-y: auto;
|
|
690
|
+
display: flex;
|
|
691
|
+
flex-direction: column;
|
|
692
|
+
gap: 12px;
|
|
693
|
+
max-height: 480px;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
.agy-ui-account-card {
|
|
697
|
+
background: ${aliasVar("bg-layer-2")};
|
|
698
|
+
border: 0.5px solid ${aliasVar("border-l1")};
|
|
699
|
+
border-radius: 9px;
|
|
700
|
+
padding: 8px 12px;
|
|
701
|
+
display: flex;
|
|
702
|
+
align-items: center;
|
|
703
|
+
justify-content: space-between;
|
|
704
|
+
gap: 8px;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
.agy-ui-account-email {
|
|
708
|
+
font: var(--dsw-font-xxs-12);
|
|
709
|
+
color: ${aliasVar("label-primary")};
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
.agy-ui-account-project {
|
|
713
|
+
font: var(--dsw-font-xxxs-11);
|
|
714
|
+
color: ${aliasVar("label-tertiary")};
|
|
715
|
+
margin-top: 2px;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
.agy-ui-state-pill {
|
|
719
|
+
font: var(--dsw-font-xxxs-11);
|
|
720
|
+
padding: 2px 7px;
|
|
721
|
+
border-radius: 9999px;
|
|
722
|
+
text-transform: capitalize;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
.agy-ui-state-pill.active {
|
|
726
|
+
background: color-mix(in srgb, ${aliasVar("state-business-primary")} 15%, transparent);
|
|
727
|
+
color: ${aliasVar("state-business-primary")};
|
|
728
|
+
border: 0.5px solid color-mix(in srgb, ${aliasVar("state-business-primary")} 30%, transparent);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
.agy-ui-state-pill.cooling {
|
|
732
|
+
background: color-mix(in srgb, ${aliasVar("state-error-secondary")} 15%, transparent);
|
|
733
|
+
color: ${aliasVar("state-error-secondary")};
|
|
734
|
+
border: 0.5px solid color-mix(in srgb, ${aliasVar("state-error-secondary")} 30%, transparent);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
.agy-ui-state-pill.verification-required {
|
|
738
|
+
background: color-mix(in srgb, ${aliasVar("brand-primary")} 15%, transparent);
|
|
739
|
+
color: ${aliasVar("brand-primary")};
|
|
740
|
+
border: 0.5px solid color-mix(in srgb, ${aliasVar("brand-primary")} 30%, transparent);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
.agy-ui-state-pill.disabled {
|
|
744
|
+
background: color-mix(in srgb, ${aliasVar("label-tertiary")} 15%, transparent);
|
|
745
|
+
color: ${aliasVar("label-secondary")};
|
|
746
|
+
border: 0.5px solid color-mix(in srgb, ${aliasVar("label-tertiary")} 30%, transparent);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
.agy-ui-section-label {
|
|
750
|
+
font: var(--dsw-font-xxxs-11);
|
|
751
|
+
color: ${aliasVar("label-tertiary")};
|
|
752
|
+
text-transform: uppercase;
|
|
753
|
+
letter-spacing: 0.05em;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
.agy-ui-quota-card {
|
|
757
|
+
background: ${aliasVar("bg-layer-2")};
|
|
758
|
+
border: 0.5px solid ${aliasVar("border-l1")};
|
|
759
|
+
border-radius: 10px;
|
|
760
|
+
padding: 10px 12px;
|
|
761
|
+
display: flex;
|
|
762
|
+
flex-direction: column;
|
|
763
|
+
gap: 6px;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
.agy-ui-quota-header {
|
|
767
|
+
display: flex;
|
|
768
|
+
align-items: center;
|
|
769
|
+
justify-content: space-between;
|
|
770
|
+
margin-bottom: 2px;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
.agy-ui-model-name {
|
|
774
|
+
font: var(--dsw-font-xxs-12);
|
|
775
|
+
color: ${aliasVar("label-primary")};
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
.agy-ui-limit-row {
|
|
779
|
+
display: flex;
|
|
780
|
+
flex-direction: column;
|
|
781
|
+
gap: 3px;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
.agy-ui-limit-row + .agy-ui-limit-row {
|
|
785
|
+
margin-top: 5px;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
.agy-ui-limit-header {
|
|
789
|
+
display: flex;
|
|
790
|
+
align-items: center;
|
|
791
|
+
justify-content: space-between;
|
|
792
|
+
font: var(--dsw-font-xxxs-11);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
.agy-ui-limit-title {
|
|
796
|
+
color: ${aliasVar("label-secondary")};
|
|
797
|
+
font: var(--dsw-font-xxxs-11);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
.agy-ui-limit-percent {
|
|
801
|
+
font: var(--dsw-font-xxs-12);
|
|
802
|
+
font-variant-numeric: tabular-nums;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
.agy-ui-progress-track {
|
|
806
|
+
width: 100%;
|
|
807
|
+
height: 5px;
|
|
808
|
+
background: color-mix(in srgb, ${aliasVar("label-primary")} 8%, transparent);
|
|
809
|
+
border-radius: 9999px;
|
|
810
|
+
overflow: hidden;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
.agy-ui-progress-fill {
|
|
814
|
+
height: 100%;
|
|
815
|
+
border-radius: 9999px;
|
|
816
|
+
transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
.agy-ui-quota-footer {
|
|
820
|
+
display: flex;
|
|
821
|
+
justify-content: flex-end;
|
|
822
|
+
font: var(--dsw-font-xxxs-11);
|
|
823
|
+
color: ${aliasVar("label-tertiary")};
|
|
824
|
+
font-variant-numeric: tabular-nums;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
.agy-ui-modal-footer {
|
|
828
|
+
padding: 8px 14px;
|
|
829
|
+
border-top: 0.5px solid ${aliasVar("border-l2")};
|
|
830
|
+
background: ${aliasVar("bg-layer-1")};
|
|
831
|
+
display: flex;
|
|
832
|
+
align-items: center;
|
|
833
|
+
justify-content: space-between;
|
|
834
|
+
gap: 8px;
|
|
835
|
+
flex-wrap: wrap;
|
|
836
|
+
font: var(--dsw-font-xxxs-11);
|
|
837
|
+
color: ${aliasVar("label-tertiary")};
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
.agy-ui-window-note {
|
|
841
|
+
font: var(--dsw-font-xxxs-11);
|
|
842
|
+
color: ${aliasVar("label-tertiary")};
|
|
843
|
+
margin-top: 3px;
|
|
844
|
+
padding: 8px 0;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
.agy-ui-limit-age {
|
|
848
|
+
font: var(--dsw-font-xxxs-11);
|
|
849
|
+
color: ${aliasVar("label-tertiary")};
|
|
850
|
+
text-align: right;
|
|
851
|
+
font-variant-numeric: tabular-nums;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
.agy-ui-verify-note {
|
|
855
|
+
display: flex;
|
|
856
|
+
align-items: center;
|
|
857
|
+
justify-content: space-between;
|
|
858
|
+
gap: 8px;
|
|
859
|
+
font: var(--dsw-font-xxs-12);
|
|
860
|
+
color: ${aliasVar("brand-primary")};
|
|
861
|
+
background: color-mix(in srgb, ${aliasVar("brand-primary")} 10%, transparent);
|
|
862
|
+
border: 0.5px solid color-mix(in srgb, ${aliasVar("brand-primary")} 30%, transparent);
|
|
863
|
+
border-radius: 9999px;
|
|
864
|
+
padding: 6px 12px;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
.agy-ui-link-btn {
|
|
868
|
+
color: ${aliasVar("brand-primary")};
|
|
869
|
+
text-decoration: none;
|
|
870
|
+
font: var(--dsw-font-xxs-12);
|
|
871
|
+
display: inline-flex;
|
|
872
|
+
align-items: center;
|
|
873
|
+
gap: 4px;
|
|
874
|
+
cursor: pointer;
|
|
875
|
+
transition: color 0.15s;
|
|
876
|
+
background: none;
|
|
877
|
+
border: none;
|
|
878
|
+
padding: 0;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
.agy-ui-link-btn:hover {
|
|
882
|
+
text-decoration: underline;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
@media (max-width: 640px) {
|
|
886
|
+
.agy-ui-badge {
|
|
887
|
+
padding: 2px 7px;
|
|
888
|
+
font: var(--dsw-font-xxxs-11);
|
|
889
|
+
gap: 4px;
|
|
890
|
+
}
|
|
891
|
+
.agy-ui-modal-body {
|
|
892
|
+
padding: 12px 14px;
|
|
893
|
+
gap: 10px;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
399
896
|
`;
|
|
400
897
|
/** Install the stylesheet once (idempotent across plugin reloads): a second
|
|
401
898
|
* install never appends a duplicate, and one whose content has gone stale
|
|
@@ -407,12 +904,12 @@ window.__ModuleLoader__.load({
|
|
|
407
904
|
if (typeof document === "undefined") return () => {};
|
|
408
905
|
const existing = document.getElementById(STYLE_ID);
|
|
409
906
|
if (existing !== null) {
|
|
410
|
-
if (existing.textContent !==
|
|
907
|
+
if (existing.textContent !== AGY_STYLES_CSS) existing.textContent = AGY_STYLES_CSS;
|
|
411
908
|
return () => {};
|
|
412
909
|
}
|
|
413
910
|
const style = document.createElement("style");
|
|
414
911
|
style.id = STYLE_ID;
|
|
415
|
-
style.textContent =
|
|
912
|
+
style.textContent = AGY_STYLES_CSS;
|
|
416
913
|
document.head.appendChild(style);
|
|
417
914
|
return () => {};
|
|
418
915
|
}
|
|
@@ -476,6 +973,37 @@ window.__ModuleLoader__.load({
|
|
|
476
973
|
limitBurnWarn: "按此速度{value}后耗尽",
|
|
477
974
|
quotaWindow5h: "5 小时",
|
|
478
975
|
quotaWindowWeekly: "每周",
|
|
976
|
+
quotaWindowDaily: "每日",
|
|
977
|
+
quotaWindowMonthly: "每月",
|
|
978
|
+
quotaResetPassed: "已重置",
|
|
979
|
+
quotaStaleNote: "该窗口已重置,等待下次刷新",
|
|
980
|
+
quotaUnmeasuredNote: "上游未返回该窗口的剩余额度",
|
|
981
|
+
quotaGroupFallback: "配额分组",
|
|
982
|
+
badgeTitle: "Antigravity 配额状态",
|
|
983
|
+
badgeAria: "Antigravity 配额",
|
|
984
|
+
badgeHint: "固定弹窗",
|
|
985
|
+
badgeUnpinHint: "取消固定",
|
|
986
|
+
badgeRefreshHint: "刷新配额状态",
|
|
987
|
+
badgePinned: "已固定",
|
|
988
|
+
badgeClose: "关闭",
|
|
989
|
+
badgeReading: "{window}剩余 {percent}%",
|
|
990
|
+
badgeUnmeasured: "尚未测量",
|
|
991
|
+
badgeNoAccount: "未检测到活跃账号,请前往设置页登录",
|
|
992
|
+
badgeSectionMonitor: "额度监控 (5小时 / 日 / 周 / 月)",
|
|
993
|
+
badgeVerifyRequired: "该账号被要求验证身份",
|
|
994
|
+
badgeAppealLink: "打开申诉链接 ↗",
|
|
995
|
+
badgeNoQuotaData: "暂无额度数据,点击右上角刷新重新测量",
|
|
996
|
+
badgeMeasuredAt: "额度测量于 {time}",
|
|
997
|
+
badgeTooltip: "Antigravity: {count} 个账号 · {reading} · 悬停或点击查看配额详情",
|
|
998
|
+
quotaSourceCaption: "数据来源: dsh-agy 分组额度",
|
|
999
|
+
quotaManageHint: "管理入口: 设置 → Antigravity",
|
|
1000
|
+
preferencesTitle: "偏好设置",
|
|
1001
|
+
prefConversationBadge: "会话顶栏配额胶囊",
|
|
1002
|
+
prefConversationBadgeDesc: "在聊天会话顶栏显示当前账号配额与水位状态浮层",
|
|
1003
|
+
prefSaveFailed: "保存失败:{message}",
|
|
1004
|
+
badgeQuotaFormat: "AGY · {percent}%",
|
|
1005
|
+
badgeWindowQuotaFormat: "AGY · {window}{percent}%",
|
|
1006
|
+
badgeCountFormat: "AGY ✦ {count}",
|
|
479
1007
|
fieldProject: "项目",
|
|
480
1008
|
fieldProxy: "代理",
|
|
481
1009
|
fieldFingerprint: "指纹",
|
|
@@ -658,6 +1186,37 @@ window.__ModuleLoader__.load({
|
|
|
658
1186
|
limitBurnWarn: "at this rate, empty in {value}",
|
|
659
1187
|
quotaWindow5h: "5 hours",
|
|
660
1188
|
quotaWindowWeekly: "Weekly",
|
|
1189
|
+
quotaWindowDaily: "Daily",
|
|
1190
|
+
quotaWindowMonthly: "Monthly",
|
|
1191
|
+
quotaResetPassed: "already reset",
|
|
1192
|
+
quotaStaleNote: "This window has already reset — waiting for the next refresh",
|
|
1193
|
+
quotaUnmeasuredNote: "Upstream reported no remaining fraction for this window",
|
|
1194
|
+
quotaGroupFallback: "Quota group",
|
|
1195
|
+
badgeTitle: "Antigravity quota status",
|
|
1196
|
+
badgeAria: "Antigravity quota",
|
|
1197
|
+
badgeHint: "Pin modal",
|
|
1198
|
+
badgeUnpinHint: "Unpin modal",
|
|
1199
|
+
badgeRefreshHint: "Refresh quota status",
|
|
1200
|
+
badgePinned: "Pinned",
|
|
1201
|
+
badgeClose: "Close",
|
|
1202
|
+
badgeReading: "{window} {percent}% left",
|
|
1203
|
+
badgeUnmeasured: "not measured yet",
|
|
1204
|
+
badgeNoAccount: "No active account, please login in Settings",
|
|
1205
|
+
badgeSectionMonitor: "Quota monitor (5h / Daily / Weekly / Monthly)",
|
|
1206
|
+
badgeVerifyRequired: "Account verification required",
|
|
1207
|
+
badgeAppealLink: "Open appeal link ↗",
|
|
1208
|
+
badgeNoQuotaData: "No quota data yet, click refresh in top right to measure",
|
|
1209
|
+
badgeMeasuredAt: "Limits measured {time}",
|
|
1210
|
+
badgeTooltip: "Antigravity: {count} accounts · {reading} · Hover or click to view quota details",
|
|
1211
|
+
quotaSourceCaption: "Source: dsh-agy grouped limits",
|
|
1212
|
+
quotaManageHint: "Manage: Settings → Antigravity",
|
|
1213
|
+
preferencesTitle: "Preferences",
|
|
1214
|
+
prefConversationBadge: "Conversation header quota badge",
|
|
1215
|
+
prefConversationBadgeDesc: "Display active account quota badge and popover in conversation session header",
|
|
1216
|
+
prefSaveFailed: "Save failed: {message}",
|
|
1217
|
+
badgeQuotaFormat: "AGY · {percent}%",
|
|
1218
|
+
badgeWindowQuotaFormat: "AGY · {window}{percent}%",
|
|
1219
|
+
badgeCountFormat: "AGY ✦ {count}",
|
|
661
1220
|
fieldProject: "Project",
|
|
662
1221
|
fieldProxy: "Proxy",
|
|
663
1222
|
fieldFingerprint: "Fingerprint",
|
|
@@ -791,6 +1350,732 @@ window.__ModuleLoader__.load({
|
|
|
791
1350
|
exportAll: "Export all"
|
|
792
1351
|
};
|
|
793
1352
|
//#endregion
|
|
1353
|
+
//#region src/client/element.ts
|
|
1354
|
+
/**
|
|
1355
|
+
* The element shorthand the browser half is written in.
|
|
1356
|
+
*
|
|
1357
|
+
* `h(tag, props, ...children)` keeps an element tree flat and readable where
|
|
1358
|
+
* nested `createElement` calls become a parenthesis maze — and this bundle has
|
|
1359
|
+
* no JSX transform, so there is no third option.
|
|
1360
|
+
*
|
|
1361
|
+
* A shared module rather than a copy per file: the Settings section and the
|
|
1362
|
+
* conversation-header badge build their trees the same way.
|
|
1363
|
+
*/
|
|
1364
|
+
function h(tag, props, ...children) {
|
|
1365
|
+
return (0, react.createElement)(tag, props ?? null, ...children);
|
|
1366
|
+
}
|
|
1367
|
+
//#endregion
|
|
1368
|
+
//#region src/types.ts
|
|
1369
|
+
/**
|
|
1370
|
+
* The upstream `window` tokens we understand: their ORDER by duration, and which
|
|
1371
|
+
* of them `CachedQuota` has a field for.
|
|
1372
|
+
*
|
|
1373
|
+
* ONE table for a vocabulary that used to live in two places giving two different
|
|
1374
|
+
* answers: `adapter/quota-summary.ts` sorted by its own token table while
|
|
1375
|
+
* `runtime/quota.ts` classified windows with `includes()` checks. The
|
|
1376
|
+
* disagreement was not academic — `daily` existed in one and not the other, so a
|
|
1377
|
+
* daily group parsed and display-sorted correctly, then contributed an EMPTY
|
|
1378
|
+
* record that replaced a real measurement.
|
|
1379
|
+
*
|
|
1380
|
+
* Keeping both facts in one row is deliberate: a token cannot be rankable in one
|
|
1381
|
+
* consumer and unknown to the other, because there is only one place to add it.
|
|
1382
|
+
* A missing `kind` means the cache has nowhere to put that reading, which is what
|
|
1383
|
+
* stops the empty-record overwrite.
|
|
1384
|
+
*
|
|
1385
|
+
* Declared in this leaf rather than in either consumer because BOTH need it:
|
|
1386
|
+
* `runtime/` must not reach into `adapter/`, and the persisted `QuotaWindow` is
|
|
1387
|
+
* already defined here.
|
|
1388
|
+
*
|
|
1389
|
+
* `rank` is explicit rather than inferred from token length so it states the
|
|
1390
|
+
* intended duration ordering: `daily` (5 chars) would otherwise sort before
|
|
1391
|
+
* `weekly` (6) for the wrong reason.
|
|
1392
|
+
*/
|
|
1393
|
+
const QUOTA_WINDOWS = {
|
|
1394
|
+
"5h": {
|
|
1395
|
+
rank: 0,
|
|
1396
|
+
kind: "rolling"
|
|
1397
|
+
},
|
|
1398
|
+
daily: { rank: 1 },
|
|
1399
|
+
weekly: {
|
|
1400
|
+
rank: 2,
|
|
1401
|
+
kind: "weekly"
|
|
1402
|
+
},
|
|
1403
|
+
monthly: { rank: 3 }
|
|
1404
|
+
};
|
|
1405
|
+
//#endregion
|
|
1406
|
+
//#region src/runtime/rotation.ts
|
|
1407
|
+
/** Below this remaining fraction the account is treated as soft-quota-exhausted. */
|
|
1408
|
+
const SOFT_QUOTA_THRESHOLD = .15;
|
|
1409
|
+
/**
|
|
1410
|
+
* The WEEKLY window's exhaustion threshold, and deliberately NOT
|
|
1411
|
+
* `SOFT_QUOTA_THRESHOLD`.
|
|
1412
|
+
*
|
|
1413
|
+
* The two numbers answer the same question — "how much work is left in this
|
|
1414
|
+
* window?" — about windows of very different lengths, so they only look
|
|
1415
|
+
* inconsistent when read side by side:
|
|
1416
|
+
*
|
|
1417
|
+
* 15% of a 5-hour window is ~45 minutes of runway.
|
|
1418
|
+
* 1% of a 7-day window is ~1.7 hours of runway.
|
|
1419
|
+
*
|
|
1420
|
+
* The weekly bar is the stricter one where it matters, because being wrong is
|
|
1421
|
+
* asymmetric: a drained 5-hour window refills within five hours, while a drained
|
|
1422
|
+
* weekly window parks the account for DAYS. Treating an account as drained a
|
|
1423
|
+
* little early costs one rotation; treating it as usable too late costs the user
|
|
1424
|
+
* a failed request with no healthy fallback.
|
|
1425
|
+
*
|
|
1426
|
+
* Do not "unify" these two constants without re-deriving both runways above.
|
|
1427
|
+
*/
|
|
1428
|
+
const WEEKLY_QUOTA_THRESHOLD = .01;
|
|
1429
|
+
/** Absolute server-reported reset in ms when it lies in the future, else undefined. */
|
|
1430
|
+
function parseFutureResetMs(resetTime, now = Date.now()) {
|
|
1431
|
+
if (!resetTime) return void 0;
|
|
1432
|
+
const reset = Date.parse(resetTime);
|
|
1433
|
+
if (Number.isNaN(reset) || reset <= now) return void 0;
|
|
1434
|
+
return reset;
|
|
1435
|
+
}
|
|
1436
|
+
//#endregion
|
|
1437
|
+
//#region src/client/quota-view.ts
|
|
1438
|
+
/**
|
|
1439
|
+
* Pure quota view layer, shared by the conversation-header badge
|
|
1440
|
+
* (`quota-badge.ts`) and the Settings section's own limits card.
|
|
1441
|
+
*
|
|
1442
|
+
* No React, no fetch, no DOM: every figure and every word is computed here from
|
|
1443
|
+
* data handed in, with `now` always injected, so `tests/quota-view.test.ts` can
|
|
1444
|
+
* pin the badge's arithmetic without a browser.
|
|
1445
|
+
*
|
|
1446
|
+
* Nothing in this module restates an upstream fact:
|
|
1447
|
+
*
|
|
1448
|
+
* - the window vocabulary and its order come from `QUOTA_WINDOWS`
|
|
1449
|
+
* (`../types.ts`), the one table the adapter and the scheduler also read;
|
|
1450
|
+
* - the drain thresholds are IMPORTED from `../runtime/rotation.ts`, so the
|
|
1451
|
+
* badge ranks windows by the same numbers the pool rotates on;
|
|
1452
|
+
* - "the reset has passed" is `parseFutureResetMs`, the predicate the pool
|
|
1453
|
+
* itself applies (`isFamilyDrained`, `rankPoolCandidates`);
|
|
1454
|
+
* - every visible word comes from the `agy` dictionary (`locales.ts`).
|
|
1455
|
+
*
|
|
1456
|
+
* A window with no reported fraction is "unknown" and renders as an em dash —
|
|
1457
|
+
* never as 0%, because unknown headroom and no headroom are opposite facts.
|
|
1458
|
+
*/
|
|
1459
|
+
const MINUTE_MS = 6e4;
|
|
1460
|
+
const HOUR_MS = 60 * MINUTE_MS;
|
|
1461
|
+
const DAY_MS = 24 * HOUR_MS;
|
|
1462
|
+
/**
|
|
1463
|
+
* Localized label for an upstream quota window token.
|
|
1464
|
+
*
|
|
1465
|
+
* The tokens are mapped rather than printed so the surface follows the UI
|
|
1466
|
+
* language, and an UNRECOGNIZED token falls back to its raw value: upstream may
|
|
1467
|
+
* add a window, and showing `30d` is better than a blank or a wrong label.
|
|
1468
|
+
*/
|
|
1469
|
+
function windowLabel(window, t) {
|
|
1470
|
+
switch (window) {
|
|
1471
|
+
case "5h": return t("quotaWindow5h");
|
|
1472
|
+
case "daily": return t("quotaWindowDaily");
|
|
1473
|
+
case "weekly": return t("quotaWindowWeekly");
|
|
1474
|
+
case "monthly": return t("quotaWindowMonthly");
|
|
1475
|
+
default: return window;
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
/** Localized account-state label. */
|
|
1479
|
+
function stateLabel(state, t) {
|
|
1480
|
+
switch (state) {
|
|
1481
|
+
case "active": return t("stateActive");
|
|
1482
|
+
case "cooling": return t("stateCooling");
|
|
1483
|
+
case "verification-required": return t("stateVerificationRequired");
|
|
1484
|
+
case "disabled": return t("stateDisabled");
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* Time until a future moment, as localized copy.
|
|
1489
|
+
*
|
|
1490
|
+
* A quota reset wall is often more than 24h out, so a bare `HH:mm` cannot say
|
|
1491
|
+
* whether it means today or tomorrow. Bucket boundaries mirror the host's
|
|
1492
|
+
* `relativeTime`; the words stay in this plugin's own dictionary, which is
|
|
1493
|
+
* exactly the split that API intends.
|
|
1494
|
+
*/
|
|
1495
|
+
function untilText(iso, t, now) {
|
|
1496
|
+
if (iso === null) return "—";
|
|
1497
|
+
const at = new Date(iso).getTime();
|
|
1498
|
+
if (Number.isNaN(at)) return "—";
|
|
1499
|
+
const diff = at - now;
|
|
1500
|
+
if (diff <= 0) return t("relNow");
|
|
1501
|
+
return t("quotaResetIn", { value: diff < 6e4 ? t("relNow") : diff < 36e5 ? t("relMinutes", { n: Math.floor(diff / MINUTE_MS) }) : diff < 864e5 ? t("relHours", { n: Math.floor(diff / HOUR_MS) }) : diff < 2592e6 ? t("relDays", { n: Math.floor(diff / DAY_MS) }) : diff < 31536e6 ? t("relMonths", { n: Math.floor(diff / (30 * DAY_MS)) }) : t("relYears", { n: Math.floor(diff / (365 * DAY_MS)) }) });
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* How long ago a past moment was (the mirror of `untilText`).
|
|
1505
|
+
*
|
|
1506
|
+
* Reuses the same `rel*` magnitudes so the two read consistently, but adds a
|
|
1507
|
+
* direction suffix: a bare magnitude beside a cooldown could equally mean when
|
|
1508
|
+
* it started or when it ends.
|
|
1509
|
+
*/
|
|
1510
|
+
function agoText(iso, t, now) {
|
|
1511
|
+
if (iso === null) return "—";
|
|
1512
|
+
const at = new Date(iso).getTime();
|
|
1513
|
+
if (Number.isNaN(at)) return "—";
|
|
1514
|
+
const diff = now - at;
|
|
1515
|
+
if (diff < 6e4) return t("relJustNow");
|
|
1516
|
+
return t("relAgo", { value: diff < 36e5 ? t("relMinutes", { n: Math.floor(diff / MINUTE_MS) }) : diff < 864e5 ? t("relHours", { n: Math.floor(diff / HOUR_MS) }) : diff < 2592e6 ? t("relDays", { n: Math.floor(diff / DAY_MS) }) : diff < 31536e6 ? t("relMonths", { n: Math.floor(diff / (30 * DAY_MS)) }) : t("relYears", { n: Math.floor(diff / (365 * DAY_MS)) }) });
|
|
1517
|
+
}
|
|
1518
|
+
/**
|
|
1519
|
+
* The reset moment of one window.
|
|
1520
|
+
*
|
|
1521
|
+
* A wall that has already passed is its own state rather than a zero-distance
|
|
1522
|
+
* countdown: `untilText` answers "shortly" for `diff <= 0`, which is precisely
|
|
1523
|
+
* the promise the old badge made about a window that had already refilled.
|
|
1524
|
+
*/
|
|
1525
|
+
function resetText(resetTime, t, now) {
|
|
1526
|
+
if (resetTime === null) return null;
|
|
1527
|
+
const at = Date.parse(resetTime);
|
|
1528
|
+
if (Number.isNaN(at)) return null;
|
|
1529
|
+
if (at <= now) return t("quotaResetPassed");
|
|
1530
|
+
return untilText(resetTime, t, now);
|
|
1531
|
+
}
|
|
1532
|
+
/** Sort rank for a window token; an unknown token ranks last. */
|
|
1533
|
+
function windowRank(window) {
|
|
1534
|
+
return QUOTA_WINDOWS[window]?.rank ?? Number.MAX_SAFE_INTEGER;
|
|
1535
|
+
}
|
|
1536
|
+
/** Order windows shortest-first; an unknown token last, tie-broken alphabetically. */
|
|
1537
|
+
function sortWindows(windows) {
|
|
1538
|
+
return [...windows].sort((a, b) => windowRank(a.window) - windowRank(b.window) || a.window.localeCompare(b.window));
|
|
1539
|
+
}
|
|
1540
|
+
/** 0..100 for a usable fraction, or null when it is absent or not finite. */
|
|
1541
|
+
function toPercent(fraction) {
|
|
1542
|
+
if (typeof fraction !== "number" || !Number.isFinite(fraction)) return null;
|
|
1543
|
+
return Math.round(Math.max(0, Math.min(1, fraction)) * 100);
|
|
1544
|
+
}
|
|
1545
|
+
/**
|
|
1546
|
+
* Quota tint by remaining fraction: healthy / low / critical.
|
|
1547
|
+
*
|
|
1548
|
+
* The thresholds are the Settings section's own, so the badge's bars read the
|
|
1549
|
+
* same colour as the limits card's beside them. Built from `--dsw-alias-*`
|
|
1550
|
+
* tokens (with the literal as a pre-theme fallback) rather than fixed hex: a
|
|
1551
|
+
* private palette does not follow the host's light/dark switch.
|
|
1552
|
+
*/
|
|
1553
|
+
function quotaColor(fraction) {
|
|
1554
|
+
if (fraction > .7) return aliasVar("state-success-primary");
|
|
1555
|
+
if (fraction >= .3) return aliasVar("state-warn-primary");
|
|
1556
|
+
return aliasVar("state-error-primary");
|
|
1557
|
+
}
|
|
1558
|
+
/**
|
|
1559
|
+
* The drain threshold that governs one window token, or undefined when the pool
|
|
1560
|
+
* does not block on that window.
|
|
1561
|
+
*
|
|
1562
|
+
* The two numbers are IMPORTED from `runtime/rotation.ts` because they answer
|
|
1563
|
+
* the same question there ("how much work is left" over windows of different
|
|
1564
|
+
* lengths), which is why they are deliberately asymmetric. `daily`/`monthly`
|
|
1565
|
+
* report but never block, so they have no threshold and never drive the badge.
|
|
1566
|
+
*/
|
|
1567
|
+
function drainThresholdFor(window) {
|
|
1568
|
+
if (window === "5h") return SOFT_QUOTA_THRESHOLD;
|
|
1569
|
+
if (window === "weekly") return WEEKLY_QUOTA_THRESHOLD;
|
|
1570
|
+
}
|
|
1571
|
+
/** The windows the pool actually blocks on. */
|
|
1572
|
+
function rotationWindows(group) {
|
|
1573
|
+
return group.windows.filter((window) => drainThresholdFor(window.window) !== void 0);
|
|
1574
|
+
}
|
|
1575
|
+
/**
|
|
1576
|
+
* Whether a window's reset moment has already passed.
|
|
1577
|
+
*
|
|
1578
|
+
* The pool treats such a window as unmeasured (`isFamilyDrained`,
|
|
1579
|
+
* `rankPoolCandidates`): the cached fraction describes a period that has ENDED,
|
|
1580
|
+
* so it is not evidence about the one we are in. This view layer reads the same
|
|
1581
|
+
* records, so it has to apply the same rule — otherwise a spent five-hour window
|
|
1582
|
+
* that has already rolled over keeps the badge red long after the pool resumed
|
|
1583
|
+
* serving that account.
|
|
1584
|
+
*
|
|
1585
|
+
* A reset moment that cannot be parsed counts as passed: it cannot be shown to
|
|
1586
|
+
* be in the future, and the reading it dates is not trustworthy either way.
|
|
1587
|
+
* A window that reported no reset moment at all is NOT stale — the pool keeps
|
|
1588
|
+
* using its fraction, and so does this.
|
|
1589
|
+
*/
|
|
1590
|
+
function resetInPast(resetTime, now) {
|
|
1591
|
+
if (resetTime === null) return false;
|
|
1592
|
+
return parseFutureResetMs(resetTime, now) === void 0;
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Whether a window's fraction is a leftover from a period that has ended.
|
|
1596
|
+
*
|
|
1597
|
+
* Only a window that DID report a fraction can be stale: one that never reported
|
|
1598
|
+
* stays "unknown", which the row already renders as its own case.
|
|
1599
|
+
*/
|
|
1600
|
+
function windowIsStale(window, now) {
|
|
1601
|
+
return window.remainingFraction !== null && resetInPast(window.resetTime, now);
|
|
1602
|
+
}
|
|
1603
|
+
/**
|
|
1604
|
+
* How close a window is to its own drain point, as a threshold multiple.
|
|
1605
|
+
*
|
|
1606
|
+
* Comparing raw `remainingFraction` values would rank a comfortable 44% week
|
|
1607
|
+
* above a comfortable 82% five-hour window and hand the badge to the week, which
|
|
1608
|
+
* is not what stops a request. Dividing by each window's own drain threshold asks
|
|
1609
|
+
* the question the pool itself asks — how much runway is left before this window
|
|
1610
|
+
* stops serving — so the five-hour window stays in charge until the week is
|
|
1611
|
+
* genuinely nearly spent.
|
|
1612
|
+
*/
|
|
1613
|
+
function windowPressure(window, now) {
|
|
1614
|
+
if (window.remainingFraction === null) return null;
|
|
1615
|
+
if (resetInPast(window.resetTime, now)) return null;
|
|
1616
|
+
const threshold = drainThresholdFor(window.window);
|
|
1617
|
+
if (threshold === void 0 || threshold <= 0) return null;
|
|
1618
|
+
return Math.max(window.remainingFraction, 0) / threshold;
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* Pick the window that constrains the account first, for the header badge.
|
|
1622
|
+
*
|
|
1623
|
+
* Groups are read in upstream's own order — it lists the Gemini budget first —
|
|
1624
|
+
* and the FIRST group reporting a blocking window decides on its own: mixing
|
|
1625
|
+
* groups would blend one budget with another. Within that group the window with
|
|
1626
|
+
* the smallest threshold multiple wins.
|
|
1627
|
+
*
|
|
1628
|
+
* Deliberately no family inference here (the badge used to match group names
|
|
1629
|
+
* against `/gemini/`): upstream's grouping is a payload detail, and the pool's
|
|
1630
|
+
* own family mapping lives in `runtime/quota.ts`, which the browser bundle does
|
|
1631
|
+
* not carry.
|
|
1632
|
+
*/
|
|
1633
|
+
function pickBadgeWindow(limits, now) {
|
|
1634
|
+
if (!limits || limits.length === 0) return null;
|
|
1635
|
+
for (const group of limits) {
|
|
1636
|
+
const candidates = rotationWindows(group);
|
|
1637
|
+
if (candidates.length === 0) continue;
|
|
1638
|
+
let best = null;
|
|
1639
|
+
let bestPressure = Number.POSITIVE_INFINITY;
|
|
1640
|
+
for (const window of candidates) {
|
|
1641
|
+
const pressure = windowPressure(window, now);
|
|
1642
|
+
if (pressure === null) continue;
|
|
1643
|
+
if (pressure < bestPressure) {
|
|
1644
|
+
best = window;
|
|
1645
|
+
bestPressure = pressure;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
if (best !== null) return best;
|
|
1649
|
+
return null;
|
|
1650
|
+
}
|
|
1651
|
+
for (const group of limits) {
|
|
1652
|
+
const first = sortWindows(group.windows).filter((window) => !windowIsStale(window, now))[0];
|
|
1653
|
+
if (first !== void 0) return first;
|
|
1654
|
+
}
|
|
1655
|
+
return null;
|
|
1656
|
+
}
|
|
1657
|
+
/**
|
|
1658
|
+
* The badge's reading: the most constrained tracked window of the ACTIVE account.
|
|
1659
|
+
*
|
|
1660
|
+
* Reported `daily`/`monthly` windows never drive the badge: the pool does not
|
|
1661
|
+
* block on them, so a low monthly figure is not a reason to alarm the header.
|
|
1662
|
+
* Returns null when nothing usable is known — the caller renders a dash instead
|
|
1663
|
+
* of inventing 0%.
|
|
1664
|
+
*/
|
|
1665
|
+
function pickBadgeQuota(accounts, now = Date.now()) {
|
|
1666
|
+
const window = pickBadgeWindow((accounts.find((account) => account.active) ?? accounts[0])?.limits, now);
|
|
1667
|
+
if (window === null) return null;
|
|
1668
|
+
const percent = toPercent(window.remainingFraction);
|
|
1669
|
+
if (percent === null) return null;
|
|
1670
|
+
return {
|
|
1671
|
+
percent,
|
|
1672
|
+
window: window.window
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
/** Build the window rows of one group card. */
|
|
1676
|
+
function windowRows(windows, t, now) {
|
|
1677
|
+
return sortWindows(windows).map((window) => {
|
|
1678
|
+
const stale = windowIsStale(window, now);
|
|
1679
|
+
return {
|
|
1680
|
+
bucketId: window.bucketId,
|
|
1681
|
+
label: windowLabel(window.window, t),
|
|
1682
|
+
percent: stale ? null : toPercent(window.remainingFraction),
|
|
1683
|
+
reset: resetText(window.resetTime, t, now),
|
|
1684
|
+
stale
|
|
1685
|
+
};
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Build the quota cards for one account.
|
|
1690
|
+
*
|
|
1691
|
+
* One card per upstream group, exactly as the group arrived: the grouped
|
|
1692
|
+
* windows are the only quota channel this plugin has now, and upstream's own
|
|
1693
|
+
* split ("Gemini Models" / "Claude and GPT models") cannot be re-derived from
|
|
1694
|
+
* model-id prefixes — `3p-*` covers Claude AND GPT.
|
|
1695
|
+
*
|
|
1696
|
+
* A group whose windows all turned out to be unusable would render as an empty
|
|
1697
|
+
* card that pushes a real one off screen, so those are dropped.
|
|
1698
|
+
*/
|
|
1699
|
+
function buildQuotaCards(account, t, now) {
|
|
1700
|
+
const groups = account?.limits;
|
|
1701
|
+
if (!groups || groups.length === 0) return [];
|
|
1702
|
+
const cards = [];
|
|
1703
|
+
for (const group of groups) {
|
|
1704
|
+
const rows = windowRows(group.windows, t, now);
|
|
1705
|
+
if (rows.length === 0) continue;
|
|
1706
|
+
cards.push({
|
|
1707
|
+
key: group.name || `group-${cards.length}`,
|
|
1708
|
+
title: group.name || t("quotaGroupFallback"),
|
|
1709
|
+
windows: rows
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
return cards;
|
|
1713
|
+
}
|
|
1714
|
+
/**
|
|
1715
|
+
* Which StateDot semantic the badge's health light shows.
|
|
1716
|
+
*
|
|
1717
|
+
* The pool is usable when some account is serving; anything else (a cooling
|
|
1718
|
+
* account, one parked behind verification, a disabled one) is user attention
|
|
1719
|
+
* rather than a hard failure, and an empty pool is idle rather than broken.
|
|
1720
|
+
*/
|
|
1721
|
+
function dotStateFor(accounts) {
|
|
1722
|
+
if (accounts.length === 0) return "idle";
|
|
1723
|
+
if (!accounts.some((account) => account.active || account.state === "active")) return "warning";
|
|
1724
|
+
return accounts.some((account) => account.state !== "active") ? "warning" : "done";
|
|
1725
|
+
}
|
|
1726
|
+
/**
|
|
1727
|
+
* Mask an account email for the header surface.
|
|
1728
|
+
*
|
|
1729
|
+
* Deliberately NOT what the Settings section does: that panel shows the address
|
|
1730
|
+
* in full, on a page the user opened on purpose. The conversation header sits on
|
|
1731
|
+
* screen for the whole session — including during a share or a recording — so the
|
|
1732
|
+
* local part is shortened and the domain kept, which is enough to tell two
|
|
1733
|
+
* accounts apart without publishing the address.
|
|
1734
|
+
*/
|
|
1735
|
+
function desensitizeEmail(email) {
|
|
1736
|
+
if (!email || !email.includes("@")) return email || "—";
|
|
1737
|
+
const [name, domain] = email.split("@");
|
|
1738
|
+
if (name === void 0 || domain === void 0) return email;
|
|
1739
|
+
if (name.length <= 3) return `${name.slice(0, 1)}***@${domain}`;
|
|
1740
|
+
if (name.length <= 6) return `${name.slice(0, 2)}***@${domain}`;
|
|
1741
|
+
return `${name.slice(0, 5)}***@${domain}`;
|
|
1742
|
+
}
|
|
1743
|
+
/**
|
|
1744
|
+
* Which probe a refresh should run.
|
|
1745
|
+
*
|
|
1746
|
+
* `force` re-probes inside the host's window (the user asked); `off` leaves the
|
|
1747
|
+
* window refresh out entirely for this tick; `auto` lets the host's TTL decide.
|
|
1748
|
+
* Ported from the standalone badge, which learned that a failed probe run has to
|
|
1749
|
+
* back itself off.
|
|
1750
|
+
*/
|
|
1751
|
+
function shouldProbeLimits(input) {
|
|
1752
|
+
if (input.force) return "force";
|
|
1753
|
+
if (input.failedAt !== null && input.now - input.failedAt < 6e5) return "off";
|
|
1754
|
+
return "auto";
|
|
1755
|
+
}
|
|
1756
|
+
//#endregion
|
|
1757
|
+
//#region src/client/quota-badge.ts
|
|
1758
|
+
/**
|
|
1759
|
+
* Conversation-header quota badge & popover.
|
|
1760
|
+
*
|
|
1761
|
+
* Shows the tightest of the active account's tracked windows, and opens the
|
|
1762
|
+
* window breakdown floating card on hover or click.
|
|
1763
|
+
*
|
|
1764
|
+
* Built with DSH native UI primitives (Button, Tag, StateDot, useAnchoredPosition,
|
|
1765
|
+
* useDismissOnOutsidePointer) and semantic design tokens (--dsw-*).
|
|
1766
|
+
*/
|
|
1767
|
+
/**
|
|
1768
|
+
* Render-tick cadence for the countdown and staleness figures. Pure state
|
|
1769
|
+
* recompute from already-fetched data — deliberately ZERO RPC: the fetch paths
|
|
1770
|
+
* are mount, window wake (throttled above) and the popover's refresh button,
|
|
1771
|
+
* and without this tick the countdown text would freeze between fetches.
|
|
1772
|
+
*/
|
|
1773
|
+
const RENDER_TICK_MS = 6e4;
|
|
1774
|
+
/**
|
|
1775
|
+
* Grace period before hover close (250ms).
|
|
1776
|
+
*/
|
|
1777
|
+
const HOVER_CLOSE_DELAY_MS = 250;
|
|
1778
|
+
/**
|
|
1779
|
+
* Quota bar and percentage color for a window.
|
|
1780
|
+
* Strictly unified with upstream Settings panel via quotaColor(percent / 100).
|
|
1781
|
+
* Thresholds: >70% success green, 30%-70% warning amber, <30% error red.
|
|
1782
|
+
*/
|
|
1783
|
+
function quotaColorForWindow(percent) {
|
|
1784
|
+
if (percent === null) return aliasVar("label-tertiary");
|
|
1785
|
+
return quotaColor(percent / 100);
|
|
1786
|
+
}
|
|
1787
|
+
/** Render one window row of a quota card. */
|
|
1788
|
+
function renderWindow(window, index, t) {
|
|
1789
|
+
return h("div", {
|
|
1790
|
+
key: window.bucketId,
|
|
1791
|
+
className: "agy-ui-limit-row"
|
|
1792
|
+
}, h("div", { className: "agy-ui-limit-header" }, h("span", { className: "agy-ui-limit-title" }, window.label), h("span", {
|
|
1793
|
+
className: "agy-ui-limit-percent",
|
|
1794
|
+
style: { color: window.color }
|
|
1795
|
+
}, window.percent === null ? "—" : `${window.percent}%`)), h("div", { className: "agy-ui-progress-track" }, h("div", {
|
|
1796
|
+
className: "agy-ui-progress-fill",
|
|
1797
|
+
style: {
|
|
1798
|
+
width: `${window.percent ?? 0}%`,
|
|
1799
|
+
backgroundColor: window.color
|
|
1800
|
+
}
|
|
1801
|
+
})), window.reset ? h("div", { className: "agy-ui-quota-footer" }, h("span", null, window.reset)) : null, window.percent === null ? h("div", { className: "agy-ui-window-note" }, window.stale ? t("quotaStaleNote") : t("quotaUnmeasuredNote")) : null);
|
|
1802
|
+
}
|
|
1803
|
+
/**
|
|
1804
|
+
* The badge and its popover modal.
|
|
1805
|
+
*/
|
|
1806
|
+
function AgyQuotaBadge({ rpc, t }) {
|
|
1807
|
+
const [accounts, setAccounts] = (0, react.useState)([]);
|
|
1808
|
+
const [loading, setLoading] = (0, react.useState)(false);
|
|
1809
|
+
const [isUpdating, setIsUpdating] = (0, react.useState)(false);
|
|
1810
|
+
const [isHovered, setIsHovered] = (0, react.useState)(false);
|
|
1811
|
+
const [isPinned, setIsPinned] = (0, react.useState)(false);
|
|
1812
|
+
const [isMobile, setIsMobile] = (0, react.useState)(false);
|
|
1813
|
+
const [now, setNow] = (0, react.useState)(() => Date.now());
|
|
1814
|
+
const [error, setError] = (0, react.useState)(null);
|
|
1815
|
+
const rootRef = (0, react.useRef)(null);
|
|
1816
|
+
const panelRef = (0, react.useRef)(null);
|
|
1817
|
+
const leaveTimerRef = (0, react.useRef)(null);
|
|
1818
|
+
const lastFetchTimeRef = (0, react.useRef)(0);
|
|
1819
|
+
const failedAtRef = (0, react.useRef)(null);
|
|
1820
|
+
(0, react.useEffect)(() => {
|
|
1821
|
+
installAgyStyles();
|
|
1822
|
+
const checkMobile = () => {
|
|
1823
|
+
setIsMobile(typeof window !== "undefined" && window.innerWidth <= 640);
|
|
1824
|
+
};
|
|
1825
|
+
checkMobile();
|
|
1826
|
+
window.addEventListener("resize", checkMobile);
|
|
1827
|
+
return () => window.removeEventListener("resize", checkMobile);
|
|
1828
|
+
}, []);
|
|
1829
|
+
const load = (0, react.useCallback)(async (isManual = false) => {
|
|
1830
|
+
const currentNow = Date.now();
|
|
1831
|
+
if (!isManual && lastFetchTimeRef.current > 0 && currentNow - lastFetchTimeRef.current < 119e3) return;
|
|
1832
|
+
lastFetchTimeRef.current = currentNow;
|
|
1833
|
+
setIsUpdating(true);
|
|
1834
|
+
setLoading(true);
|
|
1835
|
+
try {
|
|
1836
|
+
const listed = await rpc.call("account.list", {});
|
|
1837
|
+
setAccounts(listed.accounts);
|
|
1838
|
+
setError(null);
|
|
1839
|
+
const probe = shouldProbeLimits({
|
|
1840
|
+
force: isManual,
|
|
1841
|
+
failedAt: failedAtRef.current,
|
|
1842
|
+
now: Date.now()
|
|
1843
|
+
});
|
|
1844
|
+
if (probe !== "off") {
|
|
1845
|
+
const limits = await rpc.call("account.limits", probe === "force" ? { force: true } : {});
|
|
1846
|
+
const byIndex = new Map(limits.limits.map((entry) => [entry.index, entry]));
|
|
1847
|
+
setAccounts((current) => current.map((account) => {
|
|
1848
|
+
const entry = byIndex.get(account.index);
|
|
1849
|
+
return entry === void 0 ? account : {
|
|
1850
|
+
...account,
|
|
1851
|
+
limits: entry.groups,
|
|
1852
|
+
limitsUpdatedAt: entry.updatedAt
|
|
1853
|
+
};
|
|
1854
|
+
}));
|
|
1855
|
+
failedAtRef.current = limits.measured === 0 && limits.failed > 0 ? Date.now() : null;
|
|
1856
|
+
}
|
|
1857
|
+
} catch (err) {
|
|
1858
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
1859
|
+
} finally {
|
|
1860
|
+
setLoading(false);
|
|
1861
|
+
setNow(Date.now());
|
|
1862
|
+
setTimeout(() => {
|
|
1863
|
+
setIsUpdating(false);
|
|
1864
|
+
}, 1200);
|
|
1865
|
+
}
|
|
1866
|
+
}, [rpc]);
|
|
1867
|
+
(0, react.useEffect)(() => {
|
|
1868
|
+
load(false);
|
|
1869
|
+
const tick = window.setInterval(() => setNow(Date.now()), RENDER_TICK_MS);
|
|
1870
|
+
const onWake = () => {
|
|
1871
|
+
if (document.visibilityState !== "hidden") load(false);
|
|
1872
|
+
};
|
|
1873
|
+
window.addEventListener("focus", onWake);
|
|
1874
|
+
document.addEventListener("visibilitychange", onWake);
|
|
1875
|
+
return () => {
|
|
1876
|
+
window.clearInterval(tick);
|
|
1877
|
+
window.removeEventListener("focus", onWake);
|
|
1878
|
+
document.removeEventListener("visibilitychange", onWake);
|
|
1879
|
+
if (leaveTimerRef.current !== null) window.clearTimeout(leaveTimerRef.current);
|
|
1880
|
+
};
|
|
1881
|
+
}, [load]);
|
|
1882
|
+
const isOpen = isPinned || isHovered;
|
|
1883
|
+
(0, _deepseek_ai_dsh_client_ui_primitives.useDismissOnOutsidePointer)(rootRef, isPinned, setIsPinned, panelRef);
|
|
1884
|
+
const anchored = (0, _deepseek_ai_dsh_client_ui_primitives.useAnchoredPosition)({
|
|
1885
|
+
open: isOpen && !isMobile,
|
|
1886
|
+
anchorRef: rootRef,
|
|
1887
|
+
panelRef,
|
|
1888
|
+
side: "bottom",
|
|
1889
|
+
gap: 6,
|
|
1890
|
+
margin: 12
|
|
1891
|
+
});
|
|
1892
|
+
const handleMouseEnterBadge = () => {
|
|
1893
|
+
if (leaveTimerRef.current !== null) {
|
|
1894
|
+
window.clearTimeout(leaveTimerRef.current);
|
|
1895
|
+
leaveTimerRef.current = null;
|
|
1896
|
+
}
|
|
1897
|
+
setIsHovered(true);
|
|
1898
|
+
};
|
|
1899
|
+
const handleMouseLeaveBadge = () => {
|
|
1900
|
+
if (leaveTimerRef.current !== null) window.clearTimeout(leaveTimerRef.current);
|
|
1901
|
+
leaveTimerRef.current = window.setTimeout(() => {
|
|
1902
|
+
setIsHovered(false);
|
|
1903
|
+
}, HOVER_CLOSE_DELAY_MS);
|
|
1904
|
+
};
|
|
1905
|
+
const handleMouseEnterPopover = () => {
|
|
1906
|
+
if (leaveTimerRef.current !== null) {
|
|
1907
|
+
window.clearTimeout(leaveTimerRef.current);
|
|
1908
|
+
leaveTimerRef.current = null;
|
|
1909
|
+
}
|
|
1910
|
+
};
|
|
1911
|
+
const handleMouseLeavePopover = () => {
|
|
1912
|
+
if (leaveTimerRef.current !== null) window.clearTimeout(leaveTimerRef.current);
|
|
1913
|
+
leaveTimerRef.current = window.setTimeout(() => {
|
|
1914
|
+
setIsHovered(false);
|
|
1915
|
+
}, HOVER_CLOSE_DELAY_MS);
|
|
1916
|
+
};
|
|
1917
|
+
const handleTogglePin = (e) => {
|
|
1918
|
+
e?.stopPropagation();
|
|
1919
|
+
setIsPinned((prev) => !prev);
|
|
1920
|
+
setIsHovered(true);
|
|
1921
|
+
};
|
|
1922
|
+
const handleClose = () => {
|
|
1923
|
+
if (leaveTimerRef.current !== null) {
|
|
1924
|
+
window.clearTimeout(leaveTimerRef.current);
|
|
1925
|
+
leaveTimerRef.current = null;
|
|
1926
|
+
}
|
|
1927
|
+
setIsPinned(false);
|
|
1928
|
+
setIsHovered(false);
|
|
1929
|
+
};
|
|
1930
|
+
const handleRefreshClick = async (e) => {
|
|
1931
|
+
e.stopPropagation();
|
|
1932
|
+
if (loading) return;
|
|
1933
|
+
await load(true);
|
|
1934
|
+
};
|
|
1935
|
+
const activeAccount = accounts.find((a) => a.active) ?? accounts[0];
|
|
1936
|
+
const accountCount = accounts.length;
|
|
1937
|
+
const rawDot = dotStateFor(accounts);
|
|
1938
|
+
const dotState = rawDot === "done" ? "active" : rawDot === "warning" ? "cooling" : "disabled";
|
|
1939
|
+
const badgeQuota = pickBadgeQuota(accounts, now);
|
|
1940
|
+
const badgeWindow = badgeQuota?.window && badgeQuota.window !== "5h" ? `${windowLabel(badgeQuota.window, t)} ` : "";
|
|
1941
|
+
const displayText = badgeQuota !== null ? badgeWindow !== "" ? t("badgeWindowQuotaFormat", {
|
|
1942
|
+
window: badgeWindow,
|
|
1943
|
+
percent: badgeQuota.percent
|
|
1944
|
+
}) : t("badgeQuotaFormat", { percent: badgeQuota.percent }) : t("badgeCountFormat", { count: accountCount });
|
|
1945
|
+
const cards = buildQuotaCards(activeAccount, t, now).map((rc) => ({
|
|
1946
|
+
key: rc.key,
|
|
1947
|
+
title: rc.title,
|
|
1948
|
+
windows: rc.windows.map((w) => ({
|
|
1949
|
+
bucketId: w.bucketId,
|
|
1950
|
+
label: w.label,
|
|
1951
|
+
percent: w.percent,
|
|
1952
|
+
color: quotaColorForWindow(w.percent),
|
|
1953
|
+
reset: w.reset,
|
|
1954
|
+
stale: w.stale
|
|
1955
|
+
}))
|
|
1956
|
+
}));
|
|
1957
|
+
const limitsAge = activeAccount?.limitsUpdatedAt ? agoText(new Date(activeAccount.limitsUpdatedAt).toISOString(), t, now) : null;
|
|
1958
|
+
const badgeTooltip = t("badgeTooltip", {
|
|
1959
|
+
count: accountCount,
|
|
1960
|
+
reading: badgeQuota !== null ? t("badgeReading", {
|
|
1961
|
+
window: badgeWindow,
|
|
1962
|
+
percent: badgeQuota.percent
|
|
1963
|
+
}) : t("badgeUnmeasured")
|
|
1964
|
+
});
|
|
1965
|
+
const popoverContent = h("div", {
|
|
1966
|
+
ref: panelRef,
|
|
1967
|
+
className: `agy-ui-popover ${isMobile ? "mobile" : "desktop"}`,
|
|
1968
|
+
style: !isMobile ? anchored === null ? { visibility: "hidden" } : {
|
|
1969
|
+
position: "fixed",
|
|
1970
|
+
top: `${anchored.top}px`,
|
|
1971
|
+
left: `${anchored.left}px`,
|
|
1972
|
+
visibility: "visible"
|
|
1973
|
+
} : void 0,
|
|
1974
|
+
onMouseEnter: handleMouseEnterPopover,
|
|
1975
|
+
onMouseLeave: handleMouseLeavePopover,
|
|
1976
|
+
onClick: (e) => {
|
|
1977
|
+
e.stopPropagation();
|
|
1978
|
+
}
|
|
1979
|
+
}, isMobile ? h("div", { className: "agy-ui-mobile-handle" }) : null, h("div", { className: "agy-ui-modal-header" }, h("div", { className: "agy-ui-modal-title" }, h("span", { className: "agy-ui-sparkle" }, "✦"), h("span", null, t("badgeTitle")), isPinned && !isMobile ? h(_deepseek_ai_dsh_client_ui_primitives.Tag, {
|
|
1980
|
+
tone: "solid",
|
|
1981
|
+
className: "agy-ui-pinned-tag"
|
|
1982
|
+
}, t("badgePinned")) : null), h("div", { className: "agy-ui-header-actions" }, !isMobile ? h(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1983
|
+
variant: "ghost",
|
|
1984
|
+
size: "sm",
|
|
1985
|
+
className: `agy-ui-icon-btn ${isPinned ? "active" : ""}`,
|
|
1986
|
+
title: isPinned ? t("badgeUnpinHint") : t("badgeHint"),
|
|
1987
|
+
onClick: handleTogglePin
|
|
1988
|
+
}, h("svg", {
|
|
1989
|
+
width: "13",
|
|
1990
|
+
height: "13",
|
|
1991
|
+
viewBox: "0 0 24 24",
|
|
1992
|
+
fill: isPinned ? "currentColor" : "none",
|
|
1993
|
+
stroke: "currentColor",
|
|
1994
|
+
strokeWidth: "2"
|
|
1995
|
+
}, h("path", { d: "M12 2v8m0 0l3-3m-3 3L9 7M5 10h14a2 2 0 0 1 2 2v1a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-1a2 2 0 0 1 2-2zM12 15v7" }))) : null, h(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
1996
|
+
variant: "ghost",
|
|
1997
|
+
size: "sm",
|
|
1998
|
+
className: "agy-ui-icon-btn",
|
|
1999
|
+
title: t("badgeRefreshHint"),
|
|
2000
|
+
onClick: handleRefreshClick,
|
|
2001
|
+
disabled: loading
|
|
2002
|
+
}, h("svg", {
|
|
2003
|
+
className: loading || isUpdating ? "agy-ui-spinning" : "",
|
|
2004
|
+
width: "13",
|
|
2005
|
+
height: "13",
|
|
2006
|
+
viewBox: "0 0 24 24",
|
|
2007
|
+
fill: "none",
|
|
2008
|
+
stroke: "currentColor",
|
|
2009
|
+
strokeWidth: "2",
|
|
2010
|
+
strokeLinecap: "round",
|
|
2011
|
+
strokeLinejoin: "round"
|
|
2012
|
+
}, h("path", { d: "M21.5 2v6h-6M2.5 22v-6h6M2.5 11.5a10 10 0 0 1 17.5-4.5l1.5 2M21.5 12.5a10 10 0 0 1-17.5 4.5l-1.5-2" }))), h(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2013
|
+
variant: "ghost",
|
|
2014
|
+
size: "sm",
|
|
2015
|
+
className: "agy-ui-icon-btn",
|
|
2016
|
+
title: t("badgeClose"),
|
|
2017
|
+
onClick: handleClose
|
|
2018
|
+
}, h("svg", {
|
|
2019
|
+
width: "13",
|
|
2020
|
+
height: "13",
|
|
2021
|
+
viewBox: "0 0 24 24",
|
|
2022
|
+
fill: "none",
|
|
2023
|
+
stroke: "currentColor",
|
|
2024
|
+
strokeWidth: "2",
|
|
2025
|
+
strokeLinecap: "round",
|
|
2026
|
+
strokeLinejoin: "round"
|
|
2027
|
+
}, h("line", {
|
|
2028
|
+
x1: "18",
|
|
2029
|
+
y1: "6",
|
|
2030
|
+
x2: "6",
|
|
2031
|
+
y2: "18"
|
|
2032
|
+
}), h("line", {
|
|
2033
|
+
x1: "6",
|
|
2034
|
+
y1: "6",
|
|
2035
|
+
x2: "18",
|
|
2036
|
+
y2: "18"
|
|
2037
|
+
}))))), h("div", { className: "agy-ui-modal-body" }, activeAccount ? h("div", { className: "agy-ui-account-card" }, h("div", null, h("div", { className: "agy-ui-account-email" }, desensitizeEmail(activeAccount.email)), h("div", { className: "agy-ui-account-project" }, `${t("fieldProject")}: ${activeAccount.projectId || t("thinkingDefaultAll")}`)), h(_deepseek_ai_dsh_client_ui_primitives.Tag, {
|
|
2038
|
+
tone: activeAccount.state === "active" ? "success" : activeAccount.state === "cooling" ? "warning" : "danger",
|
|
2039
|
+
className: `agy-ui-state-pill ${activeAccount.state || "active"}`
|
|
2040
|
+
}, stateLabel(activeAccount.state || "active", t))) : h("div", { className: "agy-ui-account-card" }, h("div", {
|
|
2041
|
+
className: "agy-ui-account-email",
|
|
2042
|
+
style: { color: aliasVar("label-tertiary") }
|
|
2043
|
+
}, error ?? t("badgeNoAccount"))), activeAccount?.verificationRequired && activeAccount?.verificationUrl ? h("div", { className: "agy-ui-verify-note" }, h("span", null, t("badgeVerifyRequired")), h("a", {
|
|
2044
|
+
className: "agy-ui-link-btn",
|
|
2045
|
+
href: activeAccount.verificationUrl,
|
|
2046
|
+
target: "_blank",
|
|
2047
|
+
rel: "noopener noreferrer"
|
|
2048
|
+
}, t("badgeAppealLink"))) : null, h("div", { className: "agy-ui-section-label" }, t("badgeSectionMonitor")), ...cards.map((card) => h("div", {
|
|
2049
|
+
key: card.key,
|
|
2050
|
+
className: "agy-ui-quota-card"
|
|
2051
|
+
}, h("div", { className: "agy-ui-quota-header" }, h("span", { className: "agy-ui-model-name" }, card.title)), ...card.windows.map((w, idx) => renderWindow(w, idx, t)))), cards.length === 0 ? h("div", { className: "agy-ui-window-note" }, t("badgeNoQuotaData")) : null, limitsAge ? h("div", { className: "agy-ui-limit-age" }, t("badgeMeasuredAt", { time: limitsAge })) : null), h("div", { className: "agy-ui-modal-footer" }, h("span", null, t("quotaSourceCaption")), h("span", null, t("quotaManageHint"))));
|
|
2052
|
+
const popoverNode = isOpen ? isMobile ? h("div", {
|
|
2053
|
+
className: "agy-ui-popover-container mobile",
|
|
2054
|
+
onClick: handleClose
|
|
2055
|
+
}, popoverContent) : popoverContent : null;
|
|
2056
|
+
return h("span", {
|
|
2057
|
+
ref: rootRef,
|
|
2058
|
+
style: {
|
|
2059
|
+
position: "relative",
|
|
2060
|
+
display: "inline-flex",
|
|
2061
|
+
alignItems: "center"
|
|
2062
|
+
}
|
|
2063
|
+
}, h(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
2064
|
+
variant: "ghost",
|
|
2065
|
+
size: "sm",
|
|
2066
|
+
className: `agy-ui-badge ${isPinned ? "pinned" : ""}`,
|
|
2067
|
+
title: badgeTooltip,
|
|
2068
|
+
"aria-label": t("badgeAria"),
|
|
2069
|
+
onClick: handleTogglePin,
|
|
2070
|
+
onMouseEnter: handleMouseEnterBadge,
|
|
2071
|
+
onMouseLeave: handleMouseLeaveBadge
|
|
2072
|
+
}, h(_deepseek_ai_dsh_client_ui_primitives.StateDot, {
|
|
2073
|
+
state: rawDot,
|
|
2074
|
+
size: 7,
|
|
2075
|
+
className: `agy-ui-dot ${dotState}${isUpdating ? " updating" : ""}`
|
|
2076
|
+
}), h("span", null, displayText)), popoverNode);
|
|
2077
|
+
}
|
|
2078
|
+
//#endregion
|
|
794
2079
|
//#region src/thinking-types.ts
|
|
795
2080
|
/**
|
|
796
2081
|
* Thinking-budget vocabulary shared by the host store, the RPC wire contract,
|
|
@@ -869,9 +2154,6 @@ window.__ModuleLoader__.load({
|
|
|
869
2154
|
* without a visible request cost.
|
|
870
2155
|
*/
|
|
871
2156
|
const POOL_POLL_INTERVAL_MS = 3e3;
|
|
872
|
-
function h(tag, props, ...children) {
|
|
873
|
-
return (0, react.createElement)(tag, props ?? null, ...children);
|
|
874
|
-
}
|
|
875
2157
|
/** Call one management method and unwrap the Connection RPC envelope. */
|
|
876
2158
|
function createRpc(connection) {
|
|
877
2159
|
return { async call(method, payload, signal) {
|
|
@@ -982,21 +2264,6 @@ window.__ModuleLoader__.load({
|
|
|
982
2264
|
function promptTokens(counters) {
|
|
983
2265
|
return counters.input + counters.cacheRead + counters.cacheWrite;
|
|
984
2266
|
}
|
|
985
|
-
/** Quota tint by remaining fraction: healthy / low / critical. */
|
|
986
|
-
function quotaColor(fraction) {
|
|
987
|
-
if (fraction > .7) return "var(--dsw-alias-state-success-primary, #22c55e)";
|
|
988
|
-
if (fraction >= .3) return "var(--dsw-alias-state-warn-primary, #f59e0b)";
|
|
989
|
-
return "var(--dsw-alias-state-error-primary, #ec1313)";
|
|
990
|
-
}
|
|
991
|
-
/** Localized account-state label. */
|
|
992
|
-
function stateLabel(state, t) {
|
|
993
|
-
switch (state) {
|
|
994
|
-
case "active": return t("stateActive");
|
|
995
|
-
case "cooling": return t("stateCooling");
|
|
996
|
-
case "verification-required": return t("stateVerificationRequired");
|
|
997
|
-
case "disabled": return t("stateDisabled");
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
2267
|
/**
|
|
1001
2268
|
* Localized label for a reasoning level.
|
|
1002
2269
|
*
|
|
@@ -1012,21 +2279,6 @@ window.__ModuleLoader__.load({
|
|
|
1012
2279
|
}
|
|
1013
2280
|
}
|
|
1014
2281
|
/**
|
|
1015
|
-
* Localized label for an upstream quota window token.
|
|
1016
|
-
*
|
|
1017
|
-
* Upstream sends `5h` / `weekly` today. The tokens are mapped rather than
|
|
1018
|
-
* printed so the panel follows the UI language, and an UNRECOGNIZED token falls
|
|
1019
|
-
* back to its raw value: upstream may add a window, and showing `30d` is better
|
|
1020
|
-
* than a blank or a wrong localized label.
|
|
1021
|
-
*/
|
|
1022
|
-
function windowLabel(window, t) {
|
|
1023
|
-
switch (window) {
|
|
1024
|
-
case "5h": return t("quotaWindow5h");
|
|
1025
|
-
case "weekly": return t("quotaWindowWeekly");
|
|
1026
|
-
default: return window;
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
/**
|
|
1030
2282
|
* Humanize a burn horizon (hours until a window runs dry at the sampled rate).
|
|
1031
2283
|
*
|
|
1032
2284
|
* Reuses the shared `rel*` magnitudes so the phrasing matches every other
|
|
@@ -1079,27 +2331,6 @@ window.__ModuleLoader__.load({
|
|
|
1079
2331
|
day: "numeric"
|
|
1080
2332
|
});
|
|
1081
2333
|
}
|
|
1082
|
-
const MINUTE_MS = 6e4;
|
|
1083
|
-
const HOUR_MS = 60 * MINUTE_MS;
|
|
1084
|
-
const DAY_MS = 24 * HOUR_MS;
|
|
1085
|
-
/**
|
|
1086
|
-
* Time until a future moment, as localized copy.
|
|
1087
|
-
*
|
|
1088
|
-
* A quota reset wall is often more than 24h out, so a bare `HH:mm` cannot say
|
|
1089
|
-
* whether it means today or tomorrow — the failure this replaces. Bucket
|
|
1090
|
-
* boundaries mirror the host's `relativeTime` (which is defined for past-dated
|
|
1091
|
-
* rows and would need its arguments reversed to serve a future one, so the
|
|
1092
|
-
* comparison is written out here instead); the words stay in this plugin's own
|
|
1093
|
-
* dictionary, which is exactly the split that API intends.
|
|
1094
|
-
*/
|
|
1095
|
-
function untilText(iso, t, now) {
|
|
1096
|
-
if (iso === null) return "—";
|
|
1097
|
-
const at = new Date(iso).getTime();
|
|
1098
|
-
if (Number.isNaN(at)) return "—";
|
|
1099
|
-
const diff = at - now;
|
|
1100
|
-
if (diff <= 0) return t("relNow");
|
|
1101
|
-
return t("quotaResetIn", { value: diff < MINUTE_MS ? t("relNow") : diff < HOUR_MS ? t("relMinutes", { n: Math.floor(diff / MINUTE_MS) }) : diff < DAY_MS ? t("relHours", { n: Math.floor(diff / HOUR_MS) }) : diff < 30 * DAY_MS ? t("relDays", { n: Math.floor(diff / DAY_MS) }) : diff < 365 * DAY_MS ? t("relMonths", { n: Math.floor(diff / (30 * DAY_MS)) }) : t("relYears", { n: Math.floor(diff / (365 * DAY_MS)) }) });
|
|
1102
|
-
}
|
|
1103
2334
|
/**
|
|
1104
2335
|
* Localized label for a cooldown reason.
|
|
1105
2336
|
*
|
|
@@ -1116,22 +2347,6 @@ window.__ModuleLoader__.load({
|
|
|
1116
2347
|
}
|
|
1117
2348
|
}
|
|
1118
2349
|
/**
|
|
1119
|
-
* How long ago a past moment was (the mirror of `untilText`).
|
|
1120
|
-
*
|
|
1121
|
-
* Reuses the same `rel*` magnitudes so the two read consistently, but adds a
|
|
1122
|
-
* direction suffix: a bare magnitude beside a cooldown could equally mean when
|
|
1123
|
-
* it started or when it ends. The suffix and every magnitude come from the
|
|
1124
|
-
* dictionary, so nothing here is language-specific.
|
|
1125
|
-
*/
|
|
1126
|
-
function agoText(iso, t, now) {
|
|
1127
|
-
if (iso === null) return "—";
|
|
1128
|
-
const at = new Date(iso).getTime();
|
|
1129
|
-
if (Number.isNaN(at)) return "—";
|
|
1130
|
-
const diff = now - at;
|
|
1131
|
-
if (diff < MINUTE_MS) return t("relJustNow");
|
|
1132
|
-
return t("relAgo", { value: diff < HOUR_MS ? t("relMinutes", { n: Math.floor(diff / MINUTE_MS) }) : diff < DAY_MS ? t("relHours", { n: Math.floor(diff / HOUR_MS) }) : diff < 30 * DAY_MS ? t("relDays", { n: Math.floor(diff / DAY_MS) }) : diff < 365 * DAY_MS ? t("relMonths", { n: Math.floor(diff / (30 * DAY_MS)) }) : t("relYears", { n: Math.floor(diff / (365 * DAY_MS)) }) });
|
|
1133
|
-
}
|
|
1134
|
-
/**
|
|
1135
2350
|
* Ago label with seconds resolution, for the recent list. `agoText` collapses
|
|
1136
2351
|
* the whole first minute into "just now", which is too coarse when the reader
|
|
1137
2352
|
* is watching live activity — here the seconds carry the information.
|
|
@@ -1139,7 +2354,7 @@ window.__ModuleLoader__.load({
|
|
|
1139
2354
|
function recentAgo(at, now, t) {
|
|
1140
2355
|
const diff = now - at;
|
|
1141
2356
|
if (diff < 1e4) return t("relJustNow");
|
|
1142
|
-
if (diff <
|
|
2357
|
+
if (diff < 6e4) return t("relSeconds", { n: Math.floor(diff / 1e3) });
|
|
1143
2358
|
return agoText(new Date(at).toISOString(), t, now);
|
|
1144
2359
|
}
|
|
1145
2360
|
/**
|
|
@@ -1356,6 +2571,59 @@ window.__ModuleLoader__.load({
|
|
|
1356
2571
|
const activePos = accounts.findIndex((a) => a.active);
|
|
1357
2572
|
return activePos >= 0 ? activePos : 0;
|
|
1358
2573
|
}
|
|
2574
|
+
function PreferencesCard(props) {
|
|
2575
|
+
const { rpc, t, onBadgePrefChange } = props;
|
|
2576
|
+
const [badgeEnabled, setBadgeEnabled] = (0, react.useState)(false);
|
|
2577
|
+
const [loading, setLoading] = (0, react.useState)(true);
|
|
2578
|
+
const [saving, setSaving] = (0, react.useState)(false);
|
|
2579
|
+
const [saveError, setSaveError] = (0, react.useState)(null);
|
|
2580
|
+
const errorTimer = (0, react.useRef)(void 0);
|
|
2581
|
+
(0, react.useEffect)(() => () => {
|
|
2582
|
+
if (errorTimer.current !== void 0) clearTimeout(errorTimer.current);
|
|
2583
|
+
}, []);
|
|
2584
|
+
(0, react.useEffect)(() => {
|
|
2585
|
+
let active = true;
|
|
2586
|
+
rpc.call("ui.prefs.get", {}).then((prefs) => {
|
|
2587
|
+
if (active) {
|
|
2588
|
+
setBadgeEnabled(prefs?.conversationBadge === true);
|
|
2589
|
+
setLoading(false);
|
|
2590
|
+
}
|
|
2591
|
+
}).catch(() => {
|
|
2592
|
+
if (active) setLoading(false);
|
|
2593
|
+
});
|
|
2594
|
+
return () => {
|
|
2595
|
+
active = false;
|
|
2596
|
+
};
|
|
2597
|
+
}, [rpc]);
|
|
2598
|
+
const toggleBadge = (0, react.useCallback)(async (checked) => {
|
|
2599
|
+
setSaving(true);
|
|
2600
|
+
setBadgeEnabled(checked);
|
|
2601
|
+
if (errorTimer.current !== void 0) clearTimeout(errorTimer.current);
|
|
2602
|
+
try {
|
|
2603
|
+
const res = await rpc.call("ui.prefs.set", { conversationBadge: checked });
|
|
2604
|
+
setBadgeEnabled(res.conversationBadge);
|
|
2605
|
+
setSaveError(null);
|
|
2606
|
+
onBadgePrefChange?.(res.conversationBadge);
|
|
2607
|
+
} catch (err) {
|
|
2608
|
+
setBadgeEnabled(!checked);
|
|
2609
|
+
setSaveError(t("prefSaveFailed", { message: err instanceof Error ? err.message : String(err) }));
|
|
2610
|
+
errorTimer.current = setTimeout(() => setSaveError(null), ACTION_MESSAGE_TTL_MS);
|
|
2611
|
+
} finally {
|
|
2612
|
+
setSaving(false);
|
|
2613
|
+
}
|
|
2614
|
+
}, [
|
|
2615
|
+
rpc,
|
|
2616
|
+
t,
|
|
2617
|
+
onBadgePrefChange
|
|
2618
|
+
]);
|
|
2619
|
+
const row = h("div", { className: "agy-pref-row" }, h("div", { className: "agy-pref-info" }, h("div", { className: "agy-pref-name" }, t("prefConversationBadge")), h("div", { className: "agy-pref-desc" }, t("prefConversationBadgeDesc"))), h(_deepseek_ai_dsh_client_ui_primitives.Switch, {
|
|
2620
|
+
label: t("prefConversationBadge"),
|
|
2621
|
+
checked: badgeEnabled,
|
|
2622
|
+
disabled: loading || saving,
|
|
2623
|
+
onChange: (checked) => void toggleBadge(checked)
|
|
2624
|
+
}));
|
|
2625
|
+
return card(t("preferencesTitle"), saveError === null ? row : [row, h("div", { className: "agy-error" }, saveError)]);
|
|
2626
|
+
}
|
|
1359
2627
|
function AccountsTab(props) {
|
|
1360
2628
|
const { accounts, busy, handlers, t } = props;
|
|
1361
2629
|
const [selected, setSelected] = (0, react.useState)(null);
|
|
@@ -1434,6 +2702,10 @@ window.__ModuleLoader__.load({
|
|
|
1434
2702
|
})), h(RecentCard, {
|
|
1435
2703
|
rpc: props.rpc,
|
|
1436
2704
|
t
|
|
2705
|
+
}), h(PreferencesCard, {
|
|
2706
|
+
rpc: props.rpc,
|
|
2707
|
+
t,
|
|
2708
|
+
onBadgePrefChange: props.onBadgePrefChange
|
|
1437
2709
|
})));
|
|
1438
2710
|
}
|
|
1439
2711
|
/**
|
|
@@ -1966,7 +3238,7 @@ window.__ModuleLoader__.load({
|
|
|
1966
3238
|
id: "cacheRead",
|
|
1967
3239
|
labelKey: "kpiCacheRead",
|
|
1968
3240
|
value: counters.cacheRead,
|
|
1969
|
-
tone: "
|
|
3241
|
+
tone: aliasVar("brand-primary")
|
|
1970
3242
|
},
|
|
1971
3243
|
{
|
|
1972
3244
|
id: "missed",
|
|
@@ -1978,7 +3250,7 @@ window.__ModuleLoader__.load({
|
|
|
1978
3250
|
id: "output",
|
|
1979
3251
|
labelKey: "kpiOutput",
|
|
1980
3252
|
value: counters.output,
|
|
1981
|
-
tone: "
|
|
3253
|
+
tone: aliasVar("state-success-primary")
|
|
1982
3254
|
}
|
|
1983
3255
|
].map((row) => {
|
|
1984
3256
|
const share = total > 0 ? row.value / total * 100 : 0;
|
|
@@ -2049,6 +3321,9 @@ window.__ModuleLoader__.load({
|
|
|
2049
3321
|
/** The Settings section body. */
|
|
2050
3322
|
function AgySettings(props) {
|
|
2051
3323
|
const { rpc, t } = props;
|
|
3324
|
+
(0, react.useEffect)(() => {
|
|
3325
|
+
installAgyStyles();
|
|
3326
|
+
}, []);
|
|
2052
3327
|
const [tab, setTab] = (0, react.useState)("accounts");
|
|
2053
3328
|
const [accounts, setAccounts] = (0, react.useState)([]);
|
|
2054
3329
|
const [models, setModels] = (0, react.useState)([]);
|
|
@@ -2421,7 +3696,8 @@ window.__ModuleLoader__.load({
|
|
|
2421
3696
|
handlers,
|
|
2422
3697
|
lang: props.lang,
|
|
2423
3698
|
rpc,
|
|
2424
|
-
t
|
|
3699
|
+
t,
|
|
3700
|
+
onBadgePrefChange: props.onBadgePrefChange
|
|
2425
3701
|
}) : tab === "models" ? modelError === void 0 ? h(ModelsTab, {
|
|
2426
3702
|
models,
|
|
2427
3703
|
account: modelAccount,
|
|
@@ -2552,6 +3828,32 @@ window.__ModuleLoader__.load({
|
|
|
2552
3828
|
const rpc = createRpc(connection);
|
|
2553
3829
|
const t = ctx.locale.bind(NS);
|
|
2554
3830
|
const lang = ctx.locale.getLocale?.().active;
|
|
3831
|
+
let badgeDisposer = null;
|
|
3832
|
+
const registerBadge = () => {
|
|
3833
|
+
if (badgeDisposer !== null) return;
|
|
3834
|
+
badgeDisposer = ctx.slots.inject("conversation.session.header.actions", () => ctx.slots.register({
|
|
3835
|
+
name: "conversation.session.header.actions",
|
|
3836
|
+
id: "agy-quota-badge",
|
|
3837
|
+
order: 8,
|
|
3838
|
+
label: () => t("title")
|
|
3839
|
+
}, () => h(AgyQuotaBadge, {
|
|
3840
|
+
rpc,
|
|
3841
|
+
t
|
|
3842
|
+
})));
|
|
3843
|
+
};
|
|
3844
|
+
const unregisterBadge = () => {
|
|
3845
|
+
if (badgeDisposer !== null) {
|
|
3846
|
+
badgeDisposer();
|
|
3847
|
+
badgeDisposer = null;
|
|
3848
|
+
}
|
|
3849
|
+
};
|
|
3850
|
+
rpc.call("ui.prefs.get", {}).then((prefs) => {
|
|
3851
|
+
if (prefs?.conversationBadge) registerBadge();
|
|
3852
|
+
}).catch(() => {});
|
|
3853
|
+
const onBadgePrefChange = (enabled) => {
|
|
3854
|
+
if (enabled) registerBadge();
|
|
3855
|
+
else unregisterBadge();
|
|
3856
|
+
};
|
|
2555
3857
|
ctx.effect(() => ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
2556
3858
|
name: "settings.section",
|
|
2557
3859
|
id: "agy",
|
|
@@ -2561,8 +3863,12 @@ window.__ModuleLoader__.load({
|
|
|
2561
3863
|
}, () => h(AgySettings, {
|
|
2562
3864
|
rpc,
|
|
2563
3865
|
t,
|
|
2564
|
-
lang
|
|
3866
|
+
lang,
|
|
3867
|
+
onBadgePrefChange
|
|
2565
3868
|
}))), "dsh-agy: Settings section");
|
|
3869
|
+
ctx.effect(() => () => {
|
|
3870
|
+
unregisterBadge();
|
|
3871
|
+
}, "dsh-agy: quota badge cleanup");
|
|
2566
3872
|
}
|
|
2567
3873
|
//#endregion
|
|
2568
3874
|
exports.AgySettings = AgySettings;
|