dsh-agy-ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js ADDED
@@ -0,0 +1,884 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "dsh-agy-ui",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+
8
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
9
+ //#region \0rolldown/runtime.js
10
+ var __create = Object.create;
11
+ var __defProp = Object.defineProperty;
12
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
13
+ var __getOwnPropNames = Object.getOwnPropertyNames;
14
+ var __getProtoOf = Object.getPrototypeOf;
15
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
18
+ key = keys[i];
19
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
20
+ get: ((k) => from[k]).bind(null, key),
21
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
22
+ });
23
+ }
24
+ return to;
25
+ };
26
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
27
+ value: mod,
28
+ enumerable: true
29
+ }) : target, mod));
30
+ //#endregion
31
+ let react = require("react");
32
+ react = __toESM(react, 1);
33
+ let react_dom = require("react-dom");
34
+ react_dom = __toESM(react_dom, 1);
35
+ //#region src/client/styles.ts
36
+ const CSS_TEXT = `
37
+ .agy-ui-badge {
38
+ display: inline-flex;
39
+ align-items: center;
40
+ gap: 6px;
41
+ background: rgba(30, 41, 59, 0.85);
42
+ border: 1px solid rgba(255, 255, 255, 0.16);
43
+ border-radius: 9999px;
44
+ padding: 3px 10px;
45
+ font-size: 11.5px;
46
+ font-weight: 600;
47
+ line-height: 1.5;
48
+ color: #f1f5f9;
49
+ cursor: pointer;
50
+ user-select: none;
51
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
52
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
53
+ font-family: inherit;
54
+ }
55
+
56
+ .agy-ui-badge:hover,
57
+ .agy-ui-badge.pinned {
58
+ background: rgba(51, 65, 85, 0.95);
59
+ border-color: rgba(255, 255, 255, 0.35);
60
+ transform: translateY(-1px);
61
+ box-shadow: 0 3px 10px rgba(0, 0, 0, 0.35);
62
+ }
63
+
64
+ .agy-ui-badge.pinned {
65
+ border-color: #a855f7;
66
+ box-shadow: 0 0 0 1px #a855f7, 0 3px 10px rgba(0, 0, 0, 0.35);
67
+ }
68
+
69
+ .agy-ui-dot {
70
+ display: inline-block;
71
+ width: 7px;
72
+ height: 7px;
73
+ border-radius: 50%;
74
+ flex-shrink: 0;
75
+ transition: background-color 0.3s;
76
+ }
77
+
78
+ .agy-ui-dot.active {
79
+ background-color: #10b981;
80
+ box-shadow: 0 0 5px rgba(16, 185, 129, 0.4);
81
+ }
82
+
83
+ .agy-ui-dot.cooling {
84
+ background-color: #f59e0b;
85
+ box-shadow: 0 0 5px rgba(245, 158, 11, 0.4);
86
+ }
87
+
88
+ .agy-ui-dot.disabled {
89
+ background-color: #64748b;
90
+ }
91
+
92
+ /* Breathing pulse effect ONLY during updating! Steady/non-pulsing by default */
93
+ .agy-ui-dot.updating {
94
+ animation: agy-ui-pulse 1.2s ease-in-out infinite;
95
+ }
96
+
97
+ .agy-ui-dot.active.updating {
98
+ box-shadow: 0 0 10px #10b981;
99
+ }
100
+
101
+ .agy-ui-dot.cooling.updating {
102
+ box-shadow: 0 0 10px #f59e0b;
103
+ }
104
+
105
+ @keyframes agy-ui-pulse {
106
+ 0%, 100% {
107
+ opacity: 1;
108
+ transform: scale(1);
109
+ }
110
+ 50% {
111
+ opacity: 0.35;
112
+ transform: scale(0.7);
113
+ }
114
+ }
115
+
116
+ .agy-ui-sparkle {
117
+ color: #a855f7;
118
+ font-size: 13px;
119
+ line-height: 1;
120
+ }
121
+
122
+ /* Popover Container (Desktop: transparent layer; Mobile: backdrop overlay) */
123
+ .agy-ui-popover-container.desktop {
124
+ position: static;
125
+ }
126
+
127
+ .agy-ui-popover-container.mobile {
128
+ position: fixed;
129
+ inset: 0;
130
+ z-index: 999999;
131
+ background: rgba(0, 0, 0, 0.6);
132
+ backdrop-filter: blur(6px);
133
+ -webkit-backdrop-filter: blur(6px);
134
+ display: flex;
135
+ align-items: flex-end;
136
+ justify-content: center;
137
+ animation: agy-ui-fade-in 0.2s ease-out;
138
+ }
139
+
140
+ /* Floating Popover Card */
141
+ .agy-ui-popover {
142
+ background: rgba(15, 23, 42, 0.96);
143
+ backdrop-filter: blur(18px);
144
+ -webkit-backdrop-filter: blur(18px);
145
+ border: 1px solid rgba(255, 255, 255, 0.14);
146
+ box-shadow: 0 20px 45px rgba(0, 0, 0, 0.65), 0 0 0 1px rgba(255, 255, 255, 0.05);
147
+ color: #f8fafc;
148
+ overflow: hidden;
149
+ display: flex;
150
+ flex-direction: column;
151
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
152
+ box-sizing: border-box;
153
+ }
154
+
155
+ .agy-ui-popover.desktop {
156
+ border-radius: 14px;
157
+ animation: agy-ui-popover-in 0.18s cubic-bezier(0.16, 1, 0.3, 1);
158
+ }
159
+
160
+ .agy-ui-popover.mobile {
161
+ width: 100%;
162
+ max-height: 82vh;
163
+ border-radius: 20px 20px 0 0;
164
+ border-bottom: none;
165
+ animation: agy-ui-bottom-sheet-in 0.25s cubic-bezier(0.16, 1, 0.3, 1);
166
+ }
167
+
168
+ .agy-ui-mobile-handle {
169
+ width: 36px;
170
+ height: 4px;
171
+ background: rgba(255, 255, 255, 0.25);
172
+ border-radius: 9999px;
173
+ margin: 8px auto 2px auto;
174
+ }
175
+
176
+ @keyframes agy-ui-popover-in {
177
+ from {
178
+ opacity: 0;
179
+ transform: translateY(-6px) scale(0.98);
180
+ }
181
+ to {
182
+ opacity: 1;
183
+ transform: translateY(0) scale(1);
184
+ }
185
+ }
186
+
187
+ @keyframes agy-ui-bottom-sheet-in {
188
+ from {
189
+ transform: translateY(100%);
190
+ }
191
+ to {
192
+ transform: translateY(0);
193
+ }
194
+ }
195
+
196
+ @keyframes agy-ui-fade-in {
197
+ from { opacity: 0; }
198
+ to { opacity: 1; }
199
+ }
200
+
201
+ .agy-ui-modal-header {
202
+ padding: 12px 16px;
203
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
204
+ display: flex;
205
+ align-items: center;
206
+ justify-content: space-between;
207
+ background: rgba(255, 255, 255, 0.02);
208
+ }
209
+
210
+ .agy-ui-modal-title {
211
+ display: flex;
212
+ align-items: center;
213
+ gap: 7px;
214
+ font-size: 13px;
215
+ font-weight: 600;
216
+ color: #f8fafc;
217
+ }
218
+
219
+ .agy-ui-pinned-tag {
220
+ font-size: 10px;
221
+ font-weight: 500;
222
+ color: #c084fc;
223
+ background: rgba(168, 85, 247, 0.15);
224
+ border: 1px solid rgba(168, 85, 247, 0.3);
225
+ padding: 1px 6px;
226
+ border-radius: 9999px;
227
+ margin-left: 2px;
228
+ }
229
+
230
+ .agy-ui-header-actions {
231
+ display: flex;
232
+ align-items: center;
233
+ gap: 6px;
234
+ }
235
+
236
+ .agy-ui-icon-btn {
237
+ background: transparent;
238
+ border: none;
239
+ color: #94a3b8;
240
+ cursor: pointer;
241
+ padding: 4px;
242
+ border-radius: 6px;
243
+ display: flex;
244
+ align-items: center;
245
+ justify-content: center;
246
+ transition: all 0.15s;
247
+ }
248
+
249
+ .agy-ui-icon-btn:hover {
250
+ background: rgba(255, 255, 255, 0.08);
251
+ color: #f8fafc;
252
+ }
253
+
254
+ .agy-ui-icon-btn.active {
255
+ color: #c084fc;
256
+ background: rgba(168, 85, 247, 0.18);
257
+ }
258
+
259
+ .agy-ui-icon-btn:disabled {
260
+ opacity: 0.5;
261
+ cursor: not-allowed;
262
+ }
263
+
264
+ .agy-ui-spinning {
265
+ animation: agy-ui-spin 1s linear infinite;
266
+ }
267
+
268
+ @keyframes agy-ui-spin {
269
+ from { transform: rotate(0deg); }
270
+ to { transform: rotate(360deg); }
271
+ }
272
+
273
+ .agy-ui-modal-body {
274
+ padding: 14px 16px;
275
+ display: flex;
276
+ flex-direction: column;
277
+ gap: 11px;
278
+ max-height: 70vh;
279
+ overflow-y: auto;
280
+ }
281
+
282
+ .agy-ui-account-card {
283
+ background: rgba(30, 41, 59, 0.5);
284
+ border: 1px solid rgba(255, 255, 255, 0.08);
285
+ border-radius: 9px;
286
+ padding: 10px 12px;
287
+ display: flex;
288
+ align-items: center;
289
+ justify-content: space-between;
290
+ }
291
+
292
+ .agy-ui-account-email {
293
+ font-size: 12.5px;
294
+ font-weight: 500;
295
+ color: #f1f5f9;
296
+ }
297
+
298
+ .agy-ui-account-project {
299
+ font-size: 11px;
300
+ color: #94a3b8;
301
+ margin-top: 2px;
302
+ }
303
+
304
+ .agy-ui-state-pill {
305
+ font-size: 10.5px;
306
+ font-weight: 600;
307
+ padding: 2px 7px;
308
+ border-radius: 9999px;
309
+ text-transform: capitalize;
310
+ }
311
+
312
+ .agy-ui-state-pill.active {
313
+ background: rgba(16, 185, 129, 0.15);
314
+ color: #34d399;
315
+ border: 1px solid rgba(16, 185, 129, 0.3);
316
+ }
317
+
318
+ .agy-ui-state-pill.cooling {
319
+ background: rgba(245, 158, 11, 0.15);
320
+ color: #fbbf24;
321
+ border: 1px solid rgba(245, 158, 11, 0.3);
322
+ }
323
+
324
+ .agy-ui-section-label {
325
+ font-size: 11px;
326
+ font-weight: 600;
327
+ color: #94a3b8;
328
+ text-transform: uppercase;
329
+ letter-spacing: 0.05em;
330
+ }
331
+
332
+ .agy-ui-quota-card {
333
+ background: rgba(30, 41, 59, 0.4);
334
+ border: 1px solid rgba(255, 255, 255, 0.07);
335
+ border-radius: 10px;
336
+ padding: 10px 12px;
337
+ display: flex;
338
+ flex-direction: column;
339
+ gap: 6px;
340
+ }
341
+
342
+ .agy-ui-quota-header {
343
+ display: flex;
344
+ align-items: center;
345
+ justify-content: space-between;
346
+ margin-bottom: 2px;
347
+ }
348
+
349
+ .agy-ui-model-name {
350
+ font-size: 12px;
351
+ font-weight: 600;
352
+ color: #e2e8f0;
353
+ }
354
+
355
+ .agy-ui-model-tag {
356
+ font-size: 10px;
357
+ padding: 1px 5px;
358
+ border-radius: 4px;
359
+ background: rgba(255, 255, 255, 0.08);
360
+ color: #94a3b8;
361
+ font-weight: 500;
362
+ }
363
+
364
+ .agy-ui-limit-row {
365
+ display: flex;
366
+ flex-direction: column;
367
+ gap: 3px;
368
+ }
369
+
370
+ .agy-ui-limit-header {
371
+ display: flex;
372
+ align-items: center;
373
+ justify-content: space-between;
374
+ font-size: 11px;
375
+ }
376
+
377
+ .agy-ui-limit-title {
378
+ color: #cbd5e1;
379
+ font-size: 10.5px;
380
+ }
381
+
382
+ .agy-ui-limit-percent {
383
+ font-weight: 600;
384
+ font-size: 11.5px;
385
+ font-variant-numeric: tabular-nums;
386
+ }
387
+
388
+ .agy-ui-progress-track {
389
+ width: 100%;
390
+ height: 5px;
391
+ background: rgba(255, 255, 255, 0.08);
392
+ border-radius: 9999px;
393
+ overflow: hidden;
394
+ }
395
+
396
+ .agy-ui-progress-fill {
397
+ height: 100%;
398
+ border-radius: 9999px;
399
+ transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1);
400
+ }
401
+
402
+ .agy-ui-quota-footer {
403
+ display: flex;
404
+ justify-content: flex-end;
405
+ font-size: 10px;
406
+ color: #94a3b8;
407
+ font-variant-numeric: tabular-nums;
408
+ }
409
+
410
+ .agy-ui-modal-footer {
411
+ padding: 10px 16px;
412
+ border-top: 1px solid rgba(255, 255, 255, 0.08);
413
+ background: rgba(255, 255, 255, 0.02);
414
+ display: flex;
415
+ align-items: center;
416
+ justify-content: space-between;
417
+ }
418
+
419
+ .agy-ui-link-btn {
420
+ color: #38bdf8;
421
+ text-decoration: none;
422
+ font-size: 11.5px;
423
+ font-weight: 500;
424
+ display: inline-flex;
425
+ align-items: center;
426
+ gap: 4px;
427
+ cursor: pointer;
428
+ transition: color 0.15s;
429
+ background: none;
430
+ border: none;
431
+ padding: 0;
432
+ }
433
+
434
+ .agy-ui-link-btn:hover {
435
+ color: #7dd3fc;
436
+ text-decoration: underline;
437
+ }
438
+
439
+ /* Mobile Responsiveness */
440
+ @media (max-width: 640px) {
441
+ .agy-ui-badge {
442
+ padding: 2px 7px;
443
+ font-size: 10.5px;
444
+ gap: 4px;
445
+ }
446
+ .agy-ui-modal-body {
447
+ padding: 12px 14px;
448
+ gap: 10px;
449
+ }
450
+ }
451
+ `;
452
+ function injectAgyUiStyles() {
453
+ if (typeof document === "undefined") return;
454
+ const existing = document.getElementById("agy-ui-styles");
455
+ if (existing) {
456
+ existing.textContent = CSS_TEXT;
457
+ return;
458
+ }
459
+ const style = document.createElement("style");
460
+ style.id = "agy-ui-styles";
461
+ style.textContent = CSS_TEXT;
462
+ document.head.appendChild(style);
463
+ }
464
+ //#endregion
465
+ //#region src/client/popover.tsx
466
+ function desensitizeEmail(email) {
467
+ if (!email || !email.includes("@")) return email || "—";
468
+ const [name, domain] = email.split("@");
469
+ if (name.length <= 3) return `${name.slice(0, 1)}***@${domain}`;
470
+ if (name.length <= 6) return `${name.slice(0, 2)}***@${domain}`;
471
+ return `${name.slice(0, 5)}***@${domain}`;
472
+ }
473
+ function format5hCountdown(resetTimeStr) {
474
+ if (!resetTimeStr) return "";
475
+ try {
476
+ const diffMs = new Date(resetTimeStr).getTime() - Date.now();
477
+ if (diffMs <= 0) return "即将重置";
478
+ const totalMins = Math.ceil(diffMs / 6e4);
479
+ const hours = Math.floor(totalMins / 60);
480
+ const mins = totalMins % 60;
481
+ if (hours > 0) return `${hours}小时${mins}分后重置`;
482
+ return `${mins}分钟后重置`;
483
+ } catch {
484
+ return "";
485
+ }
486
+ }
487
+ function formatWeeklyCountdown(resetTimeStr) {
488
+ if (!resetTimeStr) return "";
489
+ try {
490
+ const target = new Date(resetTimeStr);
491
+ const diffMs = target.getTime() - Date.now();
492
+ if (diffMs <= 0) return "即将重置";
493
+ const totalMins = Math.ceil(diffMs / 6e4);
494
+ const days = Math.floor(totalMins / 1440);
495
+ const hours = Math.floor(totalMins % 1440 / 60);
496
+ return `${target.getMonth() + 1}月${target.getDate()}日 ${target.getHours().toString().padStart(2, "0")}:${target.getMinutes().toString().padStart(2, "0")} (${days > 0 ? `${days}天${hours}h后` : `${hours}h后`})`;
497
+ } catch {
498
+ return "";
499
+ }
500
+ }
501
+ function buildGroupQuotas(account, linkQuotas) {
502
+ const models = account?.quota?.models ?? [];
503
+ const findModel = (ids) => {
504
+ for (const id of ids) {
505
+ const m = models.find((item) => item.id === id);
506
+ if (m) return m;
507
+ }
508
+ return null;
509
+ };
510
+ const g5hModel = findModel([
511
+ "gemini-3.8-flash-tiered",
512
+ "gemini-3.7-flash-tiered",
513
+ "gemini-3.6-flash-tiered",
514
+ "gemini-3.1-pro-low"
515
+ ]);
516
+ const g5h = linkQuotas?.google?.remainingFraction ?? g5hModel?.remainingFraction ?? 1;
517
+ const g5hReset = linkQuotas?.google?.resetTime ?? g5hModel?.resetTime ?? null;
518
+ const gWeekly = linkQuotas?.google?.weeklyFraction;
519
+ const gWeeklyReset = linkQuotas?.google?.weeklyResetTime;
520
+ const c5hModel = findModel(["claude-sonnet-4-6", "claude-opus-4-6-thinking"]);
521
+ const c5h = linkQuotas?.anthropic?.remainingFraction ?? c5hModel?.remainingFraction ?? 1;
522
+ const c5hReset = linkQuotas?.anthropic?.resetTime ?? c5hModel?.resetTime ?? null;
523
+ const cWeekly = linkQuotas?.anthropic?.weeklyFraction;
524
+ const cWeeklyReset = linkQuotas?.anthropic?.weeklyResetTime;
525
+ const o5hModel = findModel(["gpt-oss-120b-medium"]);
526
+ const o5h = linkQuotas?.openai?.remainingFraction ?? o5hModel?.remainingFraction ?? 1;
527
+ const o5hReset = linkQuotas?.openai?.resetTime ?? o5hModel?.resetTime ?? null;
528
+ const oWeekly = linkQuotas?.openai?.weeklyFraction;
529
+ const oWeeklyReset = linkQuotas?.openai?.weeklyResetTime;
530
+ return [
531
+ {
532
+ title: "Gemini Flash & Pro",
533
+ badgeTag: "Google",
534
+ fiveHourFraction: g5h,
535
+ fiveHourReset: g5hReset,
536
+ weeklyFraction: gWeekly,
537
+ weeklyReset: gWeeklyReset
538
+ },
539
+ {
540
+ title: "Claude 4.6 (Sonnet / Opus)",
541
+ badgeTag: "Anthropic",
542
+ fiveHourFraction: c5h,
543
+ fiveHourReset: c5hReset,
544
+ weeklyFraction: cWeekly,
545
+ weeklyReset: cWeeklyReset
546
+ },
547
+ {
548
+ title: "GPT-OSS (120B)",
549
+ badgeTag: "OpenAI",
550
+ fiveHourFraction: o5h,
551
+ fiveHourReset: o5hReset,
552
+ weeklyFraction: oWeekly,
553
+ weeklyReset: oWeeklyReset
554
+ }
555
+ ];
556
+ }
557
+ const QuotaPopover = ({ open, pinned, onTogglePin, onClose, onMouseEnter, onMouseLeave, anchorRect, accounts, linkQuotas, loading, onRefresh }) => {
558
+ const [refreshing, setRefreshing] = (0, react.useState)(false);
559
+ const [isMobile, setIsMobile] = (0, react.useState)(false);
560
+ (0, react.useEffect)(() => {
561
+ const checkMobile = () => {
562
+ setIsMobile(window.innerWidth <= 640);
563
+ };
564
+ checkMobile();
565
+ window.addEventListener("resize", checkMobile);
566
+ return () => window.removeEventListener("resize", checkMobile);
567
+ }, []);
568
+ if (!open) return null;
569
+ const handleRefreshClick = async (e) => {
570
+ e.stopPropagation();
571
+ if (refreshing || loading) return;
572
+ setRefreshing(true);
573
+ try {
574
+ await onRefresh();
575
+ } finally {
576
+ setRefreshing(false);
577
+ }
578
+ };
579
+ const activeAccount = accounts.find((a) => a.active) || accounts[0];
580
+ const groupQuotas = buildGroupQuotas(activeAccount, linkQuotas);
581
+ let stylePos = {};
582
+ if (!isMobile && anchorRect) {
583
+ const popWidth = Math.min(390, window.innerWidth - 24);
584
+ let left = anchorRect.left + anchorRect.width / 2 - popWidth / 2;
585
+ left = Math.max(12, Math.min(left, window.innerWidth - popWidth - 12));
586
+ stylePos = {
587
+ position: "fixed",
588
+ top: `${Math.max(10, Math.min(anchorRect.bottom + 8, window.innerHeight - 200))}px`,
589
+ left: `${left}px`,
590
+ width: `${popWidth}px`,
591
+ zIndex: 999999
592
+ };
593
+ }
594
+ const popoverNode = /* @__PURE__ */ react.default.createElement("div", {
595
+ className: `agy-ui-popover-container ${isMobile ? "mobile" : "desktop"}`,
596
+ onClick: isMobile ? onClose : void 0
597
+ }, /* @__PURE__ */ react.default.createElement("div", {
598
+ className: `agy-ui-popover ${isMobile ? "mobile" : "desktop"}`,
599
+ style: !isMobile ? stylePos : void 0,
600
+ onMouseEnter,
601
+ onMouseLeave,
602
+ onClick: (e) => e.stopPropagation()
603
+ }, isMobile && /* @__PURE__ */ react.default.createElement("div", { className: "agy-ui-mobile-handle" }), /* @__PURE__ */ react.default.createElement("div", { className: "agy-ui-modal-header" }, /* @__PURE__ */ react.default.createElement("div", { className: "agy-ui-modal-title" }, /* @__PURE__ */ react.default.createElement("span", { className: "agy-ui-sparkle" }, "✦"), /* @__PURE__ */ react.default.createElement("span", null, "Antigravity 配额状态"), pinned && !isMobile && /* @__PURE__ */ react.default.createElement("span", { className: "agy-ui-pinned-tag" }, "已固定")), /* @__PURE__ */ react.default.createElement("div", { className: "agy-ui-header-actions" }, !isMobile && /* @__PURE__ */ react.default.createElement("button", {
604
+ type: "button",
605
+ className: `agy-ui-icon-btn ${pinned ? "active" : ""}`,
606
+ title: pinned ? "取消固定" : "固定弹窗",
607
+ onClick: onTogglePin
608
+ }, /* @__PURE__ */ react.default.createElement("svg", {
609
+ width: "13",
610
+ height: "13",
611
+ viewBox: "0 0 24 24",
612
+ fill: pinned ? "currentColor" : "none",
613
+ stroke: "currentColor",
614
+ strokeWidth: "2"
615
+ }, /* @__PURE__ */ react.default.createElement("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" }))), /* @__PURE__ */ react.default.createElement("button", {
616
+ type: "button",
617
+ className: "agy-ui-icon-btn",
618
+ title: "刷新配额状态",
619
+ onClick: handleRefreshClick,
620
+ disabled: refreshing || loading
621
+ }, /* @__PURE__ */ react.default.createElement("svg", {
622
+ className: refreshing || loading ? "agy-ui-spinning" : "",
623
+ width: "13",
624
+ height: "13",
625
+ viewBox: "0 0 24 24",
626
+ fill: "none",
627
+ stroke: "currentColor",
628
+ strokeWidth: "2",
629
+ strokeLinecap: "round",
630
+ strokeLinejoin: "round"
631
+ }, /* @__PURE__ */ react.default.createElement("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" }))), /* @__PURE__ */ react.default.createElement("button", {
632
+ type: "button",
633
+ className: "agy-ui-icon-btn",
634
+ title: "关闭",
635
+ onClick: onClose
636
+ }, /* @__PURE__ */ react.default.createElement("svg", {
637
+ width: "13",
638
+ height: "13",
639
+ viewBox: "0 0 24 24",
640
+ fill: "none",
641
+ stroke: "currentColor",
642
+ strokeWidth: "2",
643
+ strokeLinecap: "round",
644
+ strokeLinejoin: "round"
645
+ }, /* @__PURE__ */ react.default.createElement("line", {
646
+ x1: "18",
647
+ y1: "6",
648
+ x2: "6",
649
+ y2: "18"
650
+ }), /* @__PURE__ */ react.default.createElement("line", {
651
+ x1: "6",
652
+ y1: "6",
653
+ x2: "18",
654
+ y2: "18"
655
+ }))))), /* @__PURE__ */ react.default.createElement("div", { className: "agy-ui-modal-body" }, activeAccount ? /* @__PURE__ */ react.default.createElement("div", { className: "agy-ui-account-card" }, /* @__PURE__ */ react.default.createElement("div", null, /* @__PURE__ */ react.default.createElement("div", { className: "agy-ui-account-email" }, desensitizeEmail(activeAccount.email)), /* @__PURE__ */ react.default.createElement("div", { className: "agy-ui-account-project" }, "项目: ", activeAccount.projectId || "默认")), /* @__PURE__ */ react.default.createElement("div", { className: `agy-ui-state-pill ${activeAccount.state || "active"}` }, activeAccount.state === "active" ? "正常" : activeAccount.state === "cooling" ? "冷却中" : activeAccount.state === "rate-limited" ? "已限流" : activeAccount.state)) : /* @__PURE__ */ react.default.createElement("div", { className: "agy-ui-account-card" }, /* @__PURE__ */ react.default.createElement("div", {
656
+ className: "agy-ui-account-email",
657
+ style: { color: "#94a3b8" }
658
+ }, "未检测到活跃账号,请前往后台登录")), /* @__PURE__ */ react.default.createElement("div", { className: "agy-ui-section-label" }, "主力模型额度监控"), groupQuotas.map((group, idx) => {
659
+ const pct5h = Math.round(group.fiveHourFraction * 100);
660
+ const color5h = pct5h > 30 ? "#10b981" : pct5h > 10 ? "#f59e0b" : "#ef4444";
661
+ const reset5hText = format5hCountdown(group.fiveHourReset);
662
+ const hasWeekly = typeof group.weeklyFraction === "number";
663
+ const pctWeekly = hasWeekly ? Math.round((group.weeklyFraction ?? 1) * 100) : null;
664
+ const colorWeekly = pctWeekly !== null && pctWeekly > 30 ? "#38bdf8" : pctWeekly !== null && pctWeekly > 10 ? "#f59e0b" : "#ef4444";
665
+ const resetWeeklyText = hasWeekly ? formatWeeklyCountdown(group.weeklyReset ?? null) : null;
666
+ return /* @__PURE__ */ react.default.createElement("div", {
667
+ key: idx,
668
+ className: "agy-ui-quota-card"
669
+ }, /* @__PURE__ */ react.default.createElement("div", { className: "agy-ui-quota-header" }, /* @__PURE__ */ react.default.createElement("div", { style: {
670
+ display: "flex",
671
+ alignItems: "center",
672
+ gap: "6px"
673
+ } }, /* @__PURE__ */ react.default.createElement("span", { className: "agy-ui-model-name" }, group.title), /* @__PURE__ */ react.default.createElement("span", { className: "agy-ui-model-tag" }, group.badgeTag))), /* @__PURE__ */ react.default.createElement("div", { className: "agy-ui-limit-row" }, /* @__PURE__ */ react.default.createElement("div", { className: "agy-ui-limit-header" }, /* @__PURE__ */ react.default.createElement("span", { className: "agy-ui-limit-title" }, "5小时周期"), /* @__PURE__ */ react.default.createElement("span", {
674
+ className: "agy-ui-limit-percent",
675
+ style: { color: color5h }
676
+ }, pct5h, "%")), /* @__PURE__ */ react.default.createElement("div", { className: "agy-ui-progress-track" }, /* @__PURE__ */ react.default.createElement("div", {
677
+ className: "agy-ui-progress-fill",
678
+ style: {
679
+ width: `${pct5h}%`,
680
+ backgroundColor: color5h
681
+ }
682
+ })), reset5hText && /* @__PURE__ */ react.default.createElement("div", { className: "agy-ui-quota-footer" }, /* @__PURE__ */ react.default.createElement("span", null, reset5hText))), hasWeekly && pctWeekly !== null && /* @__PURE__ */ react.default.createElement("div", {
683
+ className: "agy-ui-limit-row",
684
+ style: { marginTop: "5px" }
685
+ }, /* @__PURE__ */ react.default.createElement("div", { className: "agy-ui-limit-header" }, /* @__PURE__ */ react.default.createElement("span", { className: "agy-ui-limit-title" }, "周额度 (Weekly)"), /* @__PURE__ */ react.default.createElement("span", {
686
+ className: "agy-ui-limit-percent",
687
+ style: { color: colorWeekly }
688
+ }, pctWeekly, "%")), /* @__PURE__ */ react.default.createElement("div", { className: "agy-ui-progress-track" }, /* @__PURE__ */ react.default.createElement("div", {
689
+ className: "agy-ui-progress-fill",
690
+ style: {
691
+ width: `${pctWeekly}%`,
692
+ backgroundColor: colorWeekly
693
+ }
694
+ })), resetWeeklyText && /* @__PURE__ */ react.default.createElement("div", { className: "agy-ui-quota-footer" }, /* @__PURE__ */ react.default.createElement("span", null, resetWeeklyText))));
695
+ })), /* @__PURE__ */ react.default.createElement("div", { className: "agy-ui-modal-footer" }, /* @__PURE__ */ react.default.createElement("span", { style: {
696
+ fontSize: "11px",
697
+ color: "#64748b"
698
+ } }, linkQuotas ? "数据: 多周期额度池已同步" : "数据: dsh-agy 本地账号池"), /* @__PURE__ */ react.default.createElement("a", {
699
+ className: "agy-ui-link-btn",
700
+ href: "/agy",
701
+ target: "_blank",
702
+ rel: "noopener noreferrer"
703
+ }, "打开完整管理后台 ↗"))));
704
+ if (typeof document !== "undefined" && document.body) return react_dom.default.createPortal(popoverNode, document.body);
705
+ return popoverNode;
706
+ };
707
+ //#endregion
708
+ //#region src/client/badge.tsx
709
+ /**
710
+ * Get specifically Gemini's 5-Hour limit percentage (0-100).
711
+ * Strictly ignores weekly quota to avoid confusion.
712
+ */
713
+ function getGemini5hPercentage(accounts, linkQuotas) {
714
+ if (typeof linkQuotas?.google?.remainingFraction === "number" && Number.isFinite(linkQuotas.google.remainingFraction)) return Math.round(linkQuotas.google.remainingFraction * 100);
715
+ const activeAccount = accounts.find((a) => a.active) || accounts[0];
716
+ if (activeAccount?.quota?.models?.length) for (const id of [
717
+ "gemini-3.8-flash-tiered",
718
+ "gemini-3.7-flash-tiered",
719
+ "gemini-3.6-flash-tiered",
720
+ "gemini-3.1-pro-low",
721
+ "gemini-pro-agent",
722
+ "gemini-2.5-flash",
723
+ "gemini-2.5-pro"
724
+ ]) {
725
+ const m = activeAccount.quota.models.find((item) => item.id === id);
726
+ if (typeof m?.remainingFraction === "number" && Number.isFinite(m.remainingFraction)) return Math.round(m.remainingFraction * 100);
727
+ }
728
+ return null;
729
+ }
730
+ const AgyQuotaBadge = () => {
731
+ const [accounts, setAccounts] = (0, react.useState)([]);
732
+ const [linkQuotas, setLinkQuotas] = (0, react.useState)(null);
733
+ const [loading, setLoading] = (0, react.useState)(false);
734
+ const [isUpdating, setIsUpdating] = (0, react.useState)(false);
735
+ const [isHovered, setIsHovered] = (0, react.useState)(false);
736
+ const [isPinned, setIsPinned] = (0, react.useState)(false);
737
+ const [anchorRect, setAnchorRect] = (0, react.useState)(null);
738
+ const mountedRef = (0, react.useRef)(true);
739
+ const badgeRef = (0, react.useRef)(null);
740
+ const leaveTimerRef = (0, react.useRef)(null);
741
+ const lastFetchTimeRef = (0, react.useRef)(0);
742
+ const fetchAccountsAndQuotas = (0, react.useCallback)(async (isManual = false) => {
743
+ const now = Date.now();
744
+ if (!isManual && lastFetchTimeRef.current > 0 && now - lastFetchTimeRef.current < 119e3) return;
745
+ lastFetchTimeRef.current = now;
746
+ try {
747
+ setIsUpdating(true);
748
+ setLoading(true);
749
+ const [accRes, linkRes] = await Promise.all([fetch("/agy/api/accounts").catch(() => null), fetch("/plugins/agy-link/status").catch(() => null)]);
750
+ if (mountedRef.current && accRes && accRes.ok) {
751
+ const data = await accRes.json();
752
+ if (Array.isArray(data.accounts)) setAccounts(data.accounts);
753
+ }
754
+ if (mountedRef.current && linkRes && linkRes.ok) {
755
+ const quotas = (await linkRes.json()).pool?.accounts?.[0]?.quotas;
756
+ if (quotas) setLinkQuotas(quotas);
757
+ }
758
+ } catch {} finally {
759
+ if (mountedRef.current) {
760
+ setLoading(false);
761
+ setTimeout(() => {
762
+ if (mountedRef.current) setIsUpdating(false);
763
+ }, 1200);
764
+ }
765
+ }
766
+ }, []);
767
+ (0, react.useEffect)(() => {
768
+ mountedRef.current = true;
769
+ fetchAccountsAndQuotas(false);
770
+ const timer = setInterval(() => {
771
+ if (typeof document === "undefined" || document.visibilityState !== "hidden") fetchAccountsAndQuotas(false);
772
+ }, 12e4);
773
+ const onWake = () => {
774
+ if (typeof document === "undefined" || document.visibilityState !== "hidden") fetchAccountsAndQuotas(false);
775
+ };
776
+ window.addEventListener("focus", onWake);
777
+ document.addEventListener("visibilitychange", onWake);
778
+ return () => {
779
+ mountedRef.current = false;
780
+ clearInterval(timer);
781
+ window.removeEventListener("focus", onWake);
782
+ document.removeEventListener("visibilitychange", onWake);
783
+ if (leaveTimerRef.current) clearTimeout(leaveTimerRef.current);
784
+ };
785
+ }, [fetchAccountsAndQuotas]);
786
+ const updateAnchor = () => {
787
+ if (badgeRef.current) setAnchorRect(badgeRef.current.getBoundingClientRect());
788
+ };
789
+ const handleMouseEnterBadge = () => {
790
+ if (leaveTimerRef.current) {
791
+ clearTimeout(leaveTimerRef.current);
792
+ leaveTimerRef.current = null;
793
+ }
794
+ updateAnchor();
795
+ setIsHovered(true);
796
+ };
797
+ const handleMouseLeaveBadge = () => {
798
+ if (leaveTimerRef.current) clearTimeout(leaveTimerRef.current);
799
+ leaveTimerRef.current = setTimeout(() => {
800
+ if (mountedRef.current) setIsHovered(false);
801
+ }, 250);
802
+ };
803
+ const handleMouseEnterPopover = () => {
804
+ if (leaveTimerRef.current) {
805
+ clearTimeout(leaveTimerRef.current);
806
+ leaveTimerRef.current = null;
807
+ }
808
+ };
809
+ const handleMouseLeavePopover = () => {
810
+ if (leaveTimerRef.current) clearTimeout(leaveTimerRef.current);
811
+ leaveTimerRef.current = setTimeout(() => {
812
+ if (mountedRef.current) setIsHovered(false);
813
+ }, 250);
814
+ };
815
+ const handleTogglePin = (e) => {
816
+ e?.stopPropagation();
817
+ updateAnchor();
818
+ setIsPinned((prev) => !prev);
819
+ setIsHovered(true);
820
+ };
821
+ const handleClose = () => {
822
+ if (leaveTimerRef.current) {
823
+ clearTimeout(leaveTimerRef.current);
824
+ leaveTimerRef.current = null;
825
+ }
826
+ setIsPinned(false);
827
+ setIsHovered(false);
828
+ };
829
+ const activeAccounts = accounts.filter((a) => a.active || a.state === "active");
830
+ const hasCooling = accounts.some((a) => a.state === "cooling" || a.state === "rate-limited");
831
+ const activeCount = accounts.length;
832
+ let dotState = "disabled";
833
+ if (activeAccounts.length > 0) dotState = hasCooling ? "cooling" : "active";
834
+ else if (accounts.length > 0) dotState = "cooling";
835
+ const gemini5h = getGemini5hPercentage(accounts, linkQuotas);
836
+ const displayText = gemini5h !== null ? `AGY · ${gemini5h}%` : `AGY ✦ ${activeCount}`;
837
+ const isOpen = isPinned || isHovered;
838
+ return /* @__PURE__ */ react.default.createElement(react.default.Fragment, null, /* @__PURE__ */ react.default.createElement("button", {
839
+ ref: badgeRef,
840
+ type: "button",
841
+ className: `agy-ui-badge ${isPinned ? "pinned" : ""}`,
842
+ title: `Antigravity: ${activeCount} 个账号就绪 · Gemini 5h额度: ${gemini5h !== null ? `${gemini5h}%` : "就绪"} · 悬停或点击查看配额详情`,
843
+ onClick: handleTogglePin,
844
+ onMouseEnter: handleMouseEnterBadge,
845
+ onMouseLeave: handleMouseLeaveBadge
846
+ }, /* @__PURE__ */ react.default.createElement("span", { className: `agy-ui-dot ${dotState}${isUpdating ? " updating" : ""}` }), /* @__PURE__ */ react.default.createElement("span", null, displayText)), /* @__PURE__ */ react.default.createElement(QuotaPopover, {
847
+ open: isOpen,
848
+ pinned: isPinned,
849
+ onTogglePin: handleTogglePin,
850
+ onClose: handleClose,
851
+ onMouseEnter: handleMouseEnterPopover,
852
+ onMouseLeave: handleMouseLeavePopover,
853
+ anchorRect,
854
+ accounts,
855
+ linkQuotas,
856
+ loading,
857
+ onRefresh: () => fetchAccountsAndQuotas(true)
858
+ }));
859
+ };
860
+ //#endregion
861
+ //#region src/client/index.ts
862
+ const name = "dsh-agy-ui-client";
863
+ const inject = ["slots"];
864
+ function apply(ctx) {
865
+ console.log("[dsh-agy-ui-client] Client plugin apply called!");
866
+ injectAgyUiStyles();
867
+ ctx.slots.inject("conversation.session.header.actions", () => {
868
+ return ctx.slots.register({
869
+ name: "conversation.session.header.actions",
870
+ id: "agy-ui-quota-badge",
871
+ order: 8,
872
+ label: "Antigravity Quota"
873
+ }, AgyQuotaBadge);
874
+ });
875
+ }
876
+ //#endregion
877
+ exports.apply = apply;
878
+ exports.inject = inject;
879
+ exports.name = name;
880
+
881
+
882
+ return module.exports;
883
+ }
884
+ });