dsh-agy-link 0.3.5 → 0.4.9

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 CHANGED
@@ -4,82 +4,773 @@ window.__ModuleLoader__.load({
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ //#region src/client/brand-icons.ts
8
+ const BRAND_PATHS = {
9
+ gemini: "M11.04 19.32Q12 21.51 12 24q0-2.49.93-4.68.96-2.19 2.58-3.81t3.81-2.55Q21.51 12 24 12q-2.49 0-4.68-.93a12.3 12.3 0 0 1-3.81-2.58 12.3 12.3 0 0 1-2.58-3.81Q12 2.49 12 0q0 2.49-.96 4.68-.93 2.19-2.55 3.81a12.3 12.3 0 0 1-3.81 2.58Q2.49 12 0 12q2.49 0 4.68.96 2.19.93 3.81 2.55t2.55 3.81",
10
+ claude: "m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z",
11
+ openai: "M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"
12
+ };
13
+ const BRAND_COLORS = {
14
+ gemini: "#8E75B2",
15
+ claude: "#D97757",
16
+ openai: "#10a37f"
17
+ };
18
+ const UI_PATHS = {
19
+ trash: "M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M10 11v6M14 11v6",
20
+ plus: "M12 5v14M5 12h14",
21
+ refresh: "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",
22
+ star: "M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z",
23
+ mail: "M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2zM22 6l-10 7L2 6",
24
+ globe: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zM2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z",
25
+ zap: "M13 2L3 14h9l-1 8 10-12h-9l1-8z",
26
+ chevronDown: "M6 9l6 6 6-6",
27
+ chevronUp: "M18 15l-6-6-6 6",
28
+ check: "M20 6L9 17l-5-5",
29
+ x: "M18 6L6 18M6 6l12 12",
30
+ externalLink: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3",
31
+ alert: "M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0zM12 9v4M12 17h.01"
32
+ };
33
+ //#endregion
7
34
  //#region src/client/index.ts
8
- const { createElement: h, useState, useEffect } = require("react");
35
+ const { createElement: h, useState, useEffect, useRef } = require("react");
36
+ const reactDom = require("react-dom");
9
37
  const win = globalThis;
38
+ const bodyEl = win.document?.body ?? null;
39
+ const portalToBody = (node) => {
40
+ if (bodyEl && reactDom && typeof reactDom.createPortal === "function") return reactDom.createPortal(node, bodyEl);
41
+ return node;
42
+ };
10
43
  const name = "dsh-agy-link-client";
11
44
  const inject = ["slots"];
12
45
  const base = win.location?.origin ?? "";
46
+ let statusCache = null;
13
47
  async function getStatus() {
14
48
  try {
15
49
  const res = await win.fetch?.(base + "/plugins/agy-link/status");
16
50
  if (!res || !res.ok) return null;
17
- return await res.json();
51
+ const payload = await res.json();
52
+ statusCache = payload;
53
+ return payload;
18
54
  } catch {
19
55
  return null;
20
56
  }
21
57
  }
22
58
  async function postJson(path, body) {
23
59
  try {
24
- return await win.fetch?.(base + path, {
60
+ const res = await win.fetch?.(base + path, {
25
61
  method: "POST",
26
62
  headers: { "Content-Type": "application/json" },
27
63
  body: JSON.stringify(body)
28
- }) ?? null;
64
+ });
65
+ if (!res) return null;
66
+ try {
67
+ return await res.json();
68
+ } catch {
69
+ return { ok: res.ok };
70
+ }
29
71
  } catch {
30
72
  return null;
31
73
  }
32
74
  }
75
+ /** Global modal state for opening Antigravity console dialog from footer shortcut or header status */
76
+ const agyModalStore = {
77
+ open: false,
78
+ listeners: /* @__PURE__ */ new Set(),
79
+ setOpen(v) {
80
+ agyModalStore.open = v;
81
+ agyModalStore.listeners.forEach((fn) => fn());
82
+ },
83
+ subscribe(fn) {
84
+ agyModalStore.listeners.add(fn);
85
+ return () => {
86
+ agyModalStore.listeners.delete(fn);
87
+ };
88
+ }
89
+ };
90
+ /**
91
+ * Format reset timestamp into readable date/time + relative countdown.
92
+ */
93
+ function formatQuotaWindow(resetTimeStr) {
94
+ if (!resetTimeStr) return { resetText: "" };
95
+ try {
96
+ const d = new Date(resetTimeStr);
97
+ const diffMs = d.getTime() - Date.now();
98
+ if (diffMs <= 0) return { resetText: "" };
99
+ const totalMins = Math.ceil(diffMs / 6e4);
100
+ const days = Math.floor(totalMins / 1440);
101
+ const hours = Math.floor(totalMins % 1440 / 60);
102
+ const mins = totalMins % 60;
103
+ const isWeekly = days >= 1;
104
+ const countdown = days > 0 ? `${days}d${hours}h` : hours > 0 ? `${hours}h${mins}m` : `${mins}m`;
105
+ const hh = d.getHours().toString().padStart(2, "0");
106
+ const mm = d.getMinutes().toString().padStart(2, "0");
107
+ return { resetText: isWeekly ? `${d.getMonth() + 1}/${d.getDate()} ${hh}:${mm} (${countdown})` : `${hh}:${mm} (${countdown})` };
108
+ } catch {
109
+ return { resetText: "" };
110
+ }
111
+ }
112
+ /** Inline brand logo (Gemini / Claude / OpenAI) as a crisp SVG mark. */
113
+ function brandIcon(key, size = 13) {
114
+ return h("svg", {
115
+ viewBox: "0 0 24 24",
116
+ width: size,
117
+ height: size,
118
+ "aria-hidden": true,
119
+ style: {
120
+ display: "inline-block",
121
+ verticalAlign: "-2px",
122
+ flexShrink: 0
123
+ }
124
+ }, h("path", {
125
+ d: BRAND_PATHS[key],
126
+ fill: BRAND_COLORS[key]
127
+ }));
128
+ }
129
+ /** Clean outline/filled SVG icon (Lucide-style), zero emoji dependency. */
130
+ function uiIcon(key, size = 12, color = "currentColor") {
131
+ const p = UI_PATHS[key];
132
+ const isFilled = key === "star";
133
+ return h("svg", {
134
+ viewBox: "0 0 24 24",
135
+ width: size,
136
+ height: size,
137
+ "aria-hidden": true,
138
+ style: {
139
+ display: "inline-block",
140
+ verticalAlign: "-1.5px",
141
+ flexShrink: 0
142
+ }
143
+ }, h("path", {
144
+ d: p,
145
+ fill: isFilled ? color : "none",
146
+ stroke: isFilled ? "none" : color,
147
+ "stroke-width": isFilled ? void 0 : 2,
148
+ "stroke-linecap": isFilled ? void 0 : "round",
149
+ "stroke-linejoin": isFilled ? void 0 : "round"
150
+ }));
151
+ }
152
+ const FAMILY_BRAND = {
153
+ google: "gemini",
154
+ anthropic: "claude",
155
+ openai: "openai"
156
+ };
157
+ let agyIconSeq = 0;
158
+ /** Antigravity mark: gradient orb with an orbit ring (product has no public simple-icon). */
159
+ function agyIcon(size = 14) {
160
+ const gid = `agy-g${++agyIconSeq}`;
161
+ return h("svg", {
162
+ viewBox: "0 0 24 24",
163
+ width: size,
164
+ height: size,
165
+ "aria-hidden": true,
166
+ style: {
167
+ display: "inline-block",
168
+ verticalAlign: "-2px",
169
+ flexShrink: 0
170
+ }
171
+ }, h("defs", null, h("linearGradient", {
172
+ id: gid,
173
+ x1: "0",
174
+ y1: "0",
175
+ x2: "1",
176
+ y2: "1"
177
+ }, h("stop", {
178
+ offset: "0",
179
+ "stop-color": "#4C8DFF"
180
+ }), h("stop", {
181
+ offset: "1",
182
+ "stop-color": "#A96BFF"
183
+ }))), h("circle", {
184
+ cx: 12,
185
+ cy: 12,
186
+ r: 6,
187
+ fill: `url(#${gid})`
188
+ }), h("ellipse", {
189
+ cx: 12,
190
+ cy: 12,
191
+ rx: 10.4,
192
+ ry: 3.9,
193
+ fill: "none",
194
+ stroke: `url(#${gid})`,
195
+ "stroke-width": 1.4,
196
+ transform: "rotate(-18 12 12)"
197
+ }));
198
+ }
199
+ const GLOBAL_CSS = `
200
+ @keyframes agy-spin {
201
+ 0% { transform: rotate(0deg); }
202
+ 100% { transform: rotate(360deg); }
203
+ }
204
+
205
+ :root, body {
206
+ --agy-font-family: var(--dsw-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Microsoft YaHei", sans-serif);
207
+ --agy-bg-panel: var(--dsw-alias-bg-layer-2, #ffffff);
208
+ --agy-bg-card: var(--dsw-alias-bg-layer-2, #ffffff);
209
+ --agy-bg-card-primary: #f0f7ff;
210
+ --agy-bg-header: var(--dsw-alias-bg-layer-1, #f8fafc);
211
+ --agy-bg-box: var(--dsw-alias-bg-layer-1, #f8fafc);
212
+ --agy-bg-subbox: var(--dsw-alias-bg-layer-3, #f1f5f9);
213
+ --agy-bg-input: var(--dsw-alias-bg-layer-1, #ffffff);
214
+ --agy-bg-btn: var(--dsw-alias-bg-layer-3, #f1f5f9);
215
+ --agy-bg-btn-hover: var(--dsw-alias-interactive-bg-hover, #e2e8f0);
216
+ --agy-bg-btn-primary: var(--dsw-alias-state-business-primary, #2563eb);
217
+ --agy-bg-progress-track: #e2e8f0;
218
+
219
+ --agy-border-card: var(--dsw-alias-border-l2, #e2e8f0);
220
+ --agy-border-card-primary: #3b82f6;
221
+ --agy-border-box: var(--dsw-alias-border-l1, #e2e8f0);
222
+ --agy-border-subbox: var(--dsw-alias-border-l2, #cbd5e1);
223
+ --agy-border-input: var(--dsw-alias-border-l2, #cbd5e1);
224
+ --agy-border-input-focus: var(--dsw-alias-state-business-primary, #2563eb);
225
+ --agy-border-btn: var(--dsw-alias-border-l2, #cbd5e1);
226
+ --agy-border-btn-primary: #2563eb;
227
+ --agy-border-divider: var(--dsw-alias-border-l2, #e2e8f0);
228
+
229
+ --agy-text-primary: var(--dsw-alias-label-primary, #0f172a);
230
+ --agy-text-secondary: var(--dsw-alias-label-secondary, #334155);
231
+ --agy-text-tertiary: var(--dsw-alias-label-tertiary, #64748b);
232
+ --agy-text-muted: var(--dsw-alias-label-caption, #94a3b8);
233
+ --agy-text-btn: var(--dsw-alias-label-primary, #0f172a);
234
+ --agy-text-btn-primary: #ffffff;
235
+ --agy-text-window-label: #2563eb;
236
+
237
+ --agy-shadow-card: 0 1px 3px rgba(0, 0, 0, 0.05);
238
+ --agy-shadow-card-hover: 0 4px 12px rgba(0, 0, 0, 0.08);
239
+ --agy-shadow-card-primary: 0 0 0 1px #3b82f6, 0 4px 14px rgba(37, 99, 235, 0.12);
240
+ --agy-shadow-modal: 0 20px 50px rgba(0, 0, 0, 0.18);
241
+ --agy-modal-backdrop: rgba(15, 23, 42, 0.5);
242
+
243
+ /* Badges */
244
+ --agy-badge-primary-bg: #dbeafe;
245
+ --agy-badge-primary-text: #1e40af;
246
+ --agy-badge-primary-border: #60a5fa;
247
+
248
+ --agy-badge-tag-bg: #f1f5f9;
249
+ --agy-badge-tag-text: #334155;
250
+ --agy-badge-tag-border: #cbd5e1;
251
+
252
+ --agy-badge-email-bg: #eff6ff;
253
+ --agy-badge-email-text: #1d4ed8;
254
+ --agy-badge-email-border: #bfdbfe;
255
+
256
+ --agy-badge-proxy-bg: #ecfdf5;
257
+ --agy-badge-proxy-text: #047857;
258
+ --agy-badge-proxy-border: #a7f3d0;
259
+
260
+ --agy-badge-ready-bg: #ecfdf5;
261
+ --agy-badge-ready-text: #047857;
262
+ --agy-badge-ready-border: #a7f3d0;
263
+
264
+ --agy-badge-unready-bg: #eff6ff;
265
+ --agy-badge-unready-text: #1d4ed8;
266
+ --agy-badge-unready-border: #bfdbfe;
267
+
268
+ /* Quota status pills */
269
+ --agy-quota-high-bg: #ecfdf5;
270
+ --agy-quota-high-text: #047857;
271
+ --agy-quota-high-border: #a7f3d0;
272
+ --agy-quota-med-bg: #fffbeb;
273
+ --agy-quota-med-text: #b45309;
274
+ --agy-quota-med-border: #fde68a;
275
+ --agy-quota-low-bg: #fef2f2;
276
+ --agy-quota-low-text: #b91c1c;
277
+ --agy-quota-low-border: #fecaca;
278
+ --agy-quota-none-bg: #f1f5f9;
279
+ --agy-quota-none-text: #64748b;
280
+ --agy-quota-none-border: #cbd5e1;
281
+
282
+ /* Danger button */
283
+ --agy-danger-bg: #fef2f2;
284
+ --agy-danger-text: #b91c1c;
285
+ --agy-danger-border: #fecaca;
286
+ --agy-danger-hover-bg: #fee2e2;
287
+
288
+ /* Warning banner */
289
+ --agy-warn-bg: #fffbeb;
290
+ --agy-warn-text: #92400e;
291
+ --agy-warn-border: #fcd34d;
292
+ --agy-warn-btn-bg: #fef3c7;
293
+ --agy-warn-btn-text: #92400e;
294
+ --agy-warn-btn-border: #f59e0b;
295
+
296
+ /* Toast notices */
297
+ --agy-toast-success-bg: #ecfdf5;
298
+ --agy-toast-success-text: #065f46;
299
+ --agy-toast-success-border: #6ee7b7;
300
+ --agy-toast-info-bg: #eff6ff;
301
+ --agy-toast-info-text: #1e40af;
302
+ --agy-toast-info-border: #93c5fd;
303
+ --agy-toast-warn-bg: #fffbeb;
304
+ --agy-toast-warn-text: #92400e;
305
+ --agy-toast-warn-border: #fcd34d;
306
+ --agy-toast-error-bg: #fef2f2;
307
+ --agy-toast-error-text: #991b1b;
308
+ --agy-toast-error-border: #fca5a5;
309
+
310
+ /* Auth Modal / Box */
311
+ --agy-auth-box-bg: #f0f7ff;
312
+ --agy-auth-box-border: #3b82f6;
313
+
314
+ /* Segment control */
315
+ --agy-seg-group-bg: #e2e8f0;
316
+ --agy-seg-group-border: #cbd5e1;
317
+ --agy-seg-btn-text: #475569;
318
+ --agy-seg-btn-active-bg: var(--dsw-alias-state-business-primary, #2563eb);
319
+ --agy-seg-btn-active-text: #ffffff;
320
+ --agy-seg-danger-bg: #dc2626;
321
+ --agy-seg-danger-text: #ffffff;
322
+ --agy-seg-danger-border: #b91c1c;
323
+ }
324
+
325
+ body[data-ds-dark-theme],
326
+ body.dark,
327
+ [data-theme="dark"] {
328
+ --agy-bg-panel: var(--dsw-alias-bg-layer-2, #0f172a);
329
+ --agy-bg-card: var(--dsw-alias-bg-layer-2, #1e293b);
330
+ --agy-bg-card-primary: #111d33;
331
+ --agy-bg-header: var(--dsw-alias-bg-layer-1, #1e293b);
332
+ --agy-bg-box: var(--dsw-alias-bg-layer-1, #0f172a);
333
+ --agy-bg-subbox: var(--dsw-alias-bg-layer-3, #090d16);
334
+ --agy-bg-input: var(--dsw-alias-bg-layer-3, #090d16);
335
+ --agy-bg-btn: var(--dsw-alias-bg-layer-3, #334155);
336
+ --agy-bg-btn-hover: var(--dsw-alias-interactive-bg-hover, #475569);
337
+ --agy-bg-btn-primary: var(--dsw-alias-state-business-primary, #2563eb);
338
+ --agy-bg-progress-track: #1e293b;
339
+
340
+ --agy-border-card: var(--dsw-alias-border-l2, #334155);
341
+ --agy-border-card-primary: #3b82f6;
342
+ --agy-border-box: var(--dsw-alias-border-l1, #1e293b);
343
+ --agy-border-subbox: var(--dsw-alias-border-l2, #334155);
344
+ --agy-border-input: var(--dsw-alias-border-l2, #334155);
345
+ --agy-border-input-focus: #3b82f6;
346
+ --agy-border-btn: var(--dsw-alias-border-l2, #475569);
347
+ --agy-border-btn-primary: #3b82f6;
348
+ --agy-border-divider: var(--dsw-alias-border-l2, #334155);
349
+
350
+ --agy-text-primary: var(--dsw-alias-label-primary, #f8fafc);
351
+ --agy-text-secondary: var(--dsw-alias-label-secondary, #cbd5e1);
352
+ --agy-text-tertiary: var(--dsw-alias-label-tertiary, #94a3b8);
353
+ --agy-text-muted: var(--dsw-alias-label-caption, #64748b);
354
+ --agy-text-btn: var(--dsw-alias-label-primary, #ffffff);
355
+ --agy-text-btn-primary: #ffffff;
356
+ --agy-text-window-label: #93c5fd;
357
+
358
+ --agy-shadow-card: 0 2px 8px rgba(0, 0, 0, 0.3);
359
+ --agy-shadow-card-hover: 0 6px 16px rgba(0, 0, 0, 0.4);
360
+ --agy-shadow-card-primary: 0 0 16px rgba(59, 130, 246, 0.25);
361
+ --agy-shadow-modal: 0 25px 60px rgba(0, 0, 0, 0.8);
362
+ --agy-modal-backdrop: rgba(0, 0, 0, 0.75);
363
+
364
+ /* Badges */
365
+ --agy-badge-primary-bg: #1e3a8a;
366
+ --agy-badge-primary-text: #93c5fd;
367
+ --agy-badge-primary-border: #3b82f6;
368
+
369
+ --agy-badge-tag-bg: #0f172a;
370
+ --agy-badge-tag-text: #cbd5e1;
371
+ --agy-badge-tag-border: #334155;
372
+
373
+ --agy-badge-email-bg: #1e293b;
374
+ --agy-badge-email-text: #93c5fd;
375
+ --agy-badge-email-border: #3b82f6;
376
+
377
+ --agy-badge-proxy-bg: #064e3b;
378
+ --agy-badge-proxy-text: #6ee7b7;
379
+ --agy-badge-proxy-border: #059669;
380
+
381
+ --agy-badge-ready-bg: rgba(16, 185, 129, 0.15);
382
+ --agy-badge-ready-text: #34d399;
383
+ --agy-badge-ready-border: rgba(16, 185, 129, 0.35);
384
+
385
+ --agy-badge-unready-bg: rgba(59, 130, 246, 0.15);
386
+ --agy-badge-unready-text: #93c5fd;
387
+ --agy-badge-unready-border: rgba(59, 130, 246, 0.35);
388
+
389
+ /* Quota status pills */
390
+ --agy-quota-high-bg: #064e3b;
391
+ --agy-quota-high-text: #6ee7b7;
392
+ --agy-quota-high-border: #059669;
393
+ --agy-quota-med-bg: #451a03;
394
+ --agy-quota-med-text: #fde68a;
395
+ --agy-quota-med-border: #d97706;
396
+ --agy-quota-low-bg: #450a0a;
397
+ --agy-quota-low-text: #fca5a5;
398
+ --agy-quota-low-border: #dc2626;
399
+ --agy-quota-none-bg: #1e293b;
400
+ --agy-quota-none-text: #94a3b8;
401
+ --agy-quota-none-border: #475569;
402
+
403
+ /* Danger button */
404
+ --agy-danger-bg: #450a0a;
405
+ --agy-danger-text: #fca5a5;
406
+ --agy-danger-border: #ef4444;
407
+ --agy-danger-hover-bg: #5f1313;
408
+
409
+ /* Warning banner */
410
+ --agy-warn-bg: #451a03;
411
+ --agy-warn-text: #fde68a;
412
+ --agy-warn-border: #d97706;
413
+ --agy-warn-btn-bg: #78350f;
414
+ --agy-warn-btn-text: #fde68a;
415
+ --agy-warn-btn-border: #d97706;
416
+
417
+ /* Toast notices */
418
+ --agy-toast-success-bg: #064e3b;
419
+ --agy-toast-success-text: #6ee7b7;
420
+ --agy-toast-success-border: #059669;
421
+ --agy-toast-info-bg: #1e3a8a;
422
+ --agy-toast-info-text: #93c5fd;
423
+ --agy-toast-info-border: #3b82f6;
424
+ --agy-toast-warn-bg: #451a03;
425
+ --agy-toast-warn-text: #fde68a;
426
+ --agy-toast-warn-border: #d97706;
427
+ --agy-toast-error-bg: #450a0a;
428
+ --agy-toast-error-text: #fca5a5;
429
+ --agy-toast-error-border: #dc2626;
430
+
431
+ /* Auth Modal / Box */
432
+ --agy-auth-box-bg: #0f223a;
433
+ --agy-auth-box-border: #3b82f6;
434
+
435
+ /* Segment control */
436
+ --agy-seg-group-bg: #090d16;
437
+ --agy-seg-group-border: #334155;
438
+ --agy-seg-btn-text: #94a3b8;
439
+ --agy-seg-btn-active-bg: var(--dsw-alias-state-business-primary, #2563eb);
440
+ --agy-seg-btn-active-text: #ffffff;
441
+ --agy-seg-danger-bg: #7f1d1d;
442
+ --agy-seg-danger-text: #fca5a5;
443
+ --agy-seg-danger-border: #ef4444;
444
+ }
445
+
446
+ .agy-spinner {
447
+ display: inline-block;
448
+ width: 12px;
449
+ height: 12px;
450
+ border: 2px solid currentColor;
451
+ border-top-color: transparent;
452
+ border-radius: 50%;
453
+ animation: agy-spin 0.75s linear infinite;
454
+ vertical-align: -2px;
455
+ margin-right: 5px;
456
+ }
457
+ .agy-pulse-dot {
458
+ display: inline-block;
459
+ width: 8px;
460
+ height: 8px;
461
+ border-radius: 50%;
462
+ }
463
+ .agy-card-hover {
464
+ transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.15s ease;
465
+ }
466
+ .agy-card-hover:hover {
467
+ border-color: var(--agy-border-card-primary, #3b82f6) !important;
468
+ box-shadow: var(--agy-shadow-card-hover) !important;
469
+ }
470
+ .agy-btn {
471
+ transition: all 0.15s ease;
472
+ user-select: none;
473
+ }
474
+ .agy-btn:hover:not(:disabled) {
475
+ filter: brightness(1.08);
476
+ transform: translateY(-0.5px);
477
+ }
478
+ .agy-btn:active:not(:disabled) {
479
+ transform: translateY(0.5px);
480
+ }
481
+ .agy-progress-fill {
482
+ transition: width 0.4s cubic-bezier(0.16, 1, 0.3, 1);
483
+ }
484
+ .agy-modal-backdrop {
485
+ position: fixed;
486
+ inset: 0;
487
+ z-index: 9999;
488
+ background: var(--agy-modal-backdrop);
489
+ backdrop-filter: blur(6px);
490
+ display: flex;
491
+ align-items: center;
492
+ justify-content: center;
493
+ padding: 16px;
494
+ box-sizing: border-box;
495
+ }
496
+ .agy-modal-panel {
497
+ position: relative;
498
+ width: 100%;
499
+ max-width: 640px;
500
+ max-height: min(820px, calc(100vh - 48px));
501
+ background: var(--agy-bg-panel);
502
+ border: 1px solid var(--agy-border-card);
503
+ border-radius: 14px;
504
+ box-shadow: var(--agy-shadow-modal);
505
+ display: flex;
506
+ flex-direction: column;
507
+ overflow: hidden;
508
+ color: var(--agy-text-primary);
509
+ font-family: var(--agy-font-family);
510
+ }
511
+ .agy-submodel-row {
512
+ display: flex;
513
+ align-items: center;
514
+ justify-content: space-between;
515
+ padding: 7px 10px;
516
+ font-size: 11.5px;
517
+ border-radius: 6px;
518
+ background: var(--agy-bg-subbox);
519
+ border: 1px solid var(--agy-border-box);
520
+ margin: 4px 0;
521
+ color: var(--agy-text-primary);
522
+ }
523
+ `;
33
524
  const S = {
34
- row: {
525
+ container: {
526
+ lineHeight: 1.5,
527
+ fontSize: "13px",
528
+ fontFamily: "var(--agy-font-family)",
529
+ color: "var(--agy-text-primary)"
530
+ },
531
+ headerCard: {
35
532
  display: "flex",
36
533
  alignItems: "center",
37
- gap: "8px",
38
- marginBottom: "8px"
534
+ justifyContent: "space-between",
535
+ padding: "12px 16px",
536
+ background: "var(--agy-bg-header)",
537
+ border: "1px solid var(--agy-border-card)",
538
+ borderRadius: "10px",
539
+ marginBottom: "12px",
540
+ color: "var(--agy-text-primary)",
541
+ boxShadow: "var(--agy-shadow-card)"
542
+ },
543
+ badgePrimary: {
544
+ display: "inline-flex",
545
+ alignItems: "center",
546
+ gap: "4px",
547
+ background: "var(--agy-badge-primary-bg)",
548
+ color: "var(--agy-badge-primary-text)",
549
+ border: "1px solid var(--agy-badge-primary-border)",
550
+ padding: "2px 8px",
551
+ borderRadius: "5px",
552
+ fontSize: "11px",
553
+ fontWeight: 700
554
+ },
555
+ badgeTag: {
556
+ display: "inline-flex",
557
+ alignItems: "center",
558
+ gap: "4px",
559
+ background: "var(--agy-badge-tag-bg)",
560
+ color: "var(--agy-badge-tag-text)",
561
+ border: "1px solid var(--agy-badge-tag-border)",
562
+ padding: "2px 7px",
563
+ borderRadius: "5px",
564
+ fontSize: "11px",
565
+ fontWeight: 600
566
+ },
567
+ badgeReady: {
568
+ display: "inline-flex",
569
+ alignItems: "center",
570
+ gap: "4px",
571
+ background: "var(--agy-badge-ready-bg)",
572
+ color: "var(--agy-badge-ready-text)",
573
+ border: "1px solid var(--agy-badge-ready-border)",
574
+ padding: "2px 8px",
575
+ borderRadius: "5px",
576
+ fontSize: "11px",
577
+ fontWeight: 700
578
+ },
579
+ badgeUnready: {
580
+ display: "inline-flex",
581
+ alignItems: "center",
582
+ gap: "4px",
583
+ background: "var(--agy-badge-unready-bg)",
584
+ color: "var(--agy-badge-unready-text)",
585
+ border: "1px solid var(--agy-badge-unready-border)",
586
+ padding: "2px 8px",
587
+ borderRadius: "5px",
588
+ fontSize: "11px",
589
+ fontWeight: 700
590
+ },
591
+ card: {
592
+ background: "var(--agy-bg-card)",
593
+ border: "1px solid var(--agy-border-card)",
594
+ borderRadius: "10px",
595
+ padding: "14px 16px",
596
+ marginBottom: "12px",
597
+ color: "var(--agy-text-primary)",
598
+ boxShadow: "var(--agy-shadow-card)"
599
+ },
600
+ cardPrimary: {
601
+ background: "var(--agy-bg-card-primary)",
602
+ border: "1.5px solid var(--agy-border-card-primary)",
603
+ borderRadius: "10px",
604
+ padding: "14px 16px",
605
+ marginBottom: "12px",
606
+ boxShadow: "var(--agy-shadow-card-primary)",
607
+ color: "var(--agy-text-primary)"
608
+ },
609
+ quotaBox: {
610
+ background: "var(--agy-bg-box)",
611
+ border: "1px solid var(--agy-border-box)",
612
+ borderRadius: "8px",
613
+ padding: "8px 10px",
614
+ marginTop: "10px"
615
+ },
616
+ progressBarBg: {
617
+ flex: "1",
618
+ height: "8px",
619
+ borderRadius: "4px",
620
+ background: "var(--agy-bg-progress-track)",
621
+ border: "1px solid var(--agy-border-box)",
622
+ overflow: "hidden",
623
+ margin: "0 10px"
39
624
  },
40
625
  btn: {
41
- padding: "4px 10px",
626
+ display: "inline-flex",
627
+ alignItems: "center",
628
+ justifyContent: "center",
629
+ padding: "5px 12px",
42
630
  borderRadius: "6px",
43
- border: "1px solid rgba(128,128,128,0.4)",
44
- background: "rgba(128,128,128,0.12)",
45
- color: "inherit",
631
+ border: "1px solid var(--agy-border-btn)",
632
+ background: "var(--agy-bg-btn)",
633
+ color: "var(--agy-text-btn)",
46
634
  cursor: "pointer",
47
- fontSize: "12px"
635
+ fontSize: "12px",
636
+ fontWeight: 600
48
637
  },
49
638
  btnPrimary: {
50
- padding: "6px 12px",
639
+ display: "inline-flex",
640
+ alignItems: "center",
641
+ justifyContent: "center",
642
+ padding: "5px 13px",
643
+ borderRadius: "6px",
644
+ border: "1px solid var(--agy-border-btn-primary)",
645
+ background: "var(--agy-bg-btn-primary)",
646
+ color: "var(--agy-text-btn-primary)",
647
+ cursor: "pointer",
648
+ fontSize: "12px",
649
+ fontWeight: 700,
650
+ boxShadow: "0 2px 6px rgba(37, 99, 235, 0.35)"
651
+ },
652
+ btnDanger: {
653
+ display: "inline-flex",
654
+ alignItems: "center",
655
+ justifyContent: "center",
656
+ padding: "4px 9px",
51
657
  borderRadius: "6px",
52
- border: "1px solid rgba(64,140,255,0.6)",
53
- background: "rgba(64,140,255,0.25)",
54
- color: "inherit",
658
+ border: "1px solid var(--agy-danger-border)",
659
+ background: "var(--agy-danger-bg)",
660
+ color: "var(--agy-danger-text)",
55
661
  cursor: "pointer",
56
- fontSize: "13px"
662
+ fontSize: "11px",
663
+ fontWeight: 600
664
+ },
665
+ btnSm: {
666
+ display: "inline-flex",
667
+ alignItems: "center",
668
+ justifyContent: "center",
669
+ padding: "4px 9px",
670
+ borderRadius: "5px",
671
+ border: "1px solid var(--agy-border-btn)",
672
+ background: "var(--agy-bg-btn)",
673
+ color: "var(--agy-text-btn)",
674
+ cursor: "pointer",
675
+ fontSize: "11.5px",
676
+ fontWeight: 600
677
+ },
678
+ btnSmPrimary: {
679
+ display: "inline-flex",
680
+ alignItems: "center",
681
+ justifyContent: "center",
682
+ padding: "4px 9px",
683
+ borderRadius: "5px",
684
+ border: "1px solid var(--agy-border-btn-primary)",
685
+ background: "var(--agy-bg-btn-primary)",
686
+ color: "var(--agy-text-btn-primary)",
687
+ cursor: "pointer",
688
+ fontSize: "11.5px",
689
+ fontWeight: 700
690
+ },
691
+ segGroup: {
692
+ display: "inline-flex",
693
+ background: "var(--agy-seg-group-bg)",
694
+ borderRadius: "7px",
695
+ padding: "3px",
696
+ border: "1px solid var(--agy-seg-group-border)"
697
+ },
698
+ segBtn: {
699
+ padding: "4px 10px",
700
+ borderRadius: "4px",
701
+ border: "none",
702
+ background: "transparent",
703
+ color: "var(--agy-seg-btn-text)",
704
+ cursor: "pointer",
705
+ fontSize: "11.5px",
706
+ fontWeight: 600
707
+ },
708
+ segBtnActive: {
709
+ padding: "4px 10px",
710
+ borderRadius: "4px",
711
+ border: "none",
712
+ background: "var(--agy-seg-btn-active-bg)",
713
+ color: "var(--agy-seg-btn-active-text)",
714
+ cursor: "pointer",
715
+ fontSize: "11.5px",
716
+ fontWeight: 700,
717
+ boxShadow: "0 2px 4px rgba(37, 99, 235, 0.4)"
57
718
  },
58
719
  input: {
59
720
  flex: "1",
60
- padding: "6px 8px",
721
+ padding: "7px 12px",
61
722
  borderRadius: "6px",
62
- border: "1px solid rgba(128,128,128,0.4)",
63
- background: "transparent",
64
- color: "inherit",
65
- fontSize: "12px"
723
+ border: "1.5px solid var(--agy-border-input)",
724
+ background: "var(--agy-bg-input)",
725
+ color: "var(--agy-text-primary)",
726
+ fontSize: "12.5px",
727
+ outline: "none"
66
728
  },
67
729
  muted: {
68
- color: "#9aa0a6",
69
- fontSize: "12px"
730
+ color: "var(--agy-text-tertiary)",
731
+ fontSize: "11.5px"
732
+ },
733
+ noticeBanner: {
734
+ display: "flex",
735
+ alignItems: "center",
736
+ justifyContent: "space-between",
737
+ padding: "10px 14px",
738
+ borderRadius: "8px",
739
+ marginBottom: "12px",
740
+ fontSize: "12.5px",
741
+ fontWeight: 500
742
+ },
743
+ authModal: {
744
+ background: "var(--agy-auth-box-bg)",
745
+ border: "1.5px solid var(--agy-auth-box-border)",
746
+ borderRadius: "10px",
747
+ padding: "14px 16px",
748
+ marginBottom: "14px",
749
+ color: "var(--agy-text-primary)"
70
750
  }
71
751
  };
72
752
  function apply(ctx) {
73
- const AgySettingsSection = () => {
74
- const [status, setStatus] = useState(null);
75
- const [code, setCode] = useState("");
76
- const [busy, setBusy] = useState(false);
77
- const [notice, setNotice] = useState("");
753
+ const AgySettingsSection = (props) => {
754
+ const [status, setStatus] = useState(statusCache);
755
+ const [aliasInput, setAliasInput] = useState("");
756
+ const [proxyInputs, setProxyInputs] = useState({});
757
+ const [editingProxyId, setEditingProxyId] = useState(null);
758
+ const [addingAccount, setAddingAccount] = useState(false);
759
+ const [authCodeInput, setAuthCodeInput] = useState("");
760
+ const [loadingAction, setLoadingAction] = useState(null);
761
+ const [toast, setToast] = useState(null);
762
+ const [expandedModels, setExpandedModels] = useState({});
78
763
  useEffect(() => {
79
764
  let alive = true;
765
+ let lastJson = "";
80
766
  const tick = async () => {
81
767
  const st = await getStatus();
82
- if (alive) setStatus(st);
768
+ if (!alive || !st) return;
769
+ const json = JSON.stringify(st);
770
+ if (json !== lastJson) {
771
+ lastJson = json;
772
+ setStatus(st);
773
+ }
83
774
  };
84
775
  tick();
85
776
  const timer = setInterval(tick, 3e3);
@@ -88,138 +779,761 @@ window.__ModuleLoader__.load({
88
779
  clearInterval(timer);
89
780
  };
90
781
  }, []);
782
+ const flowStartedRef = useRef(false);
783
+ useEffect(() => {
784
+ if (!addingAccount || !flowStartedRef.current) return;
785
+ const pa = status?.poolAuth;
786
+ if (!pa) return;
787
+ if (pa.phase === "done") {
788
+ flowStartedRef.current = false;
789
+ setAddingAccount(false);
790
+ setAuthCodeInput("");
791
+ setAliasInput("");
792
+ showToast(pa.message || "账号已激活入池", "success");
793
+ } else if (pa.phase === "failed") {
794
+ flowStartedRef.current = false;
795
+ showToast(pa.message || "授权失败", "error");
796
+ }
797
+ }, [
798
+ addingAccount,
799
+ status?.poolAuth?.phase,
800
+ status?.poolAuth?.message
801
+ ]);
802
+ const showToast = (text, type = "info") => {
803
+ setToast({
804
+ text,
805
+ type,
806
+ id: Date.now()
807
+ });
808
+ setTimeout(() => {
809
+ setToast((prev) => prev?.text === text ? null : prev);
810
+ }, 6e3);
811
+ };
91
812
  const refresh = async () => {
92
813
  setStatus(await getStatus());
93
814
  };
94
- const startAuth = async () => {
95
- setBusy(true);
96
- setNotice("");
97
- await postJson("/plugins/agy-link/auth", {});
98
- await refresh();
99
- setBusy(false);
815
+ const handleBeginAddAccount = async () => {
816
+ setLoadingAction("pool:beginAdd");
817
+ const res = await postJson("/plugins/agy-link/pool/begin-add", { alias: aliasInput.trim() || `备用 Google 账号 ${(status?.pool?.accounts?.length ?? 1) + 1}` });
818
+ setLoadingAction(null);
819
+ if (res && res.ok) {
820
+ flowStartedRef.current = true;
821
+ await refresh();
822
+ if (res.browserOpened) showToast("浏览器已打开 Google 授权页,完成授权后将自动同步", "info");
823
+ else showToast("无法自动打开浏览器,请点击下方链接手动完成授权", "warn");
824
+ } else showToast(`启动 Google 授权失败: ${res?.message || "请检查网络或代理配置"}`, "error");
825
+ };
826
+ const handleCompleteAddAccount = async () => {
827
+ if (!authCodeInput.trim()) return;
828
+ setLoadingAction("pool:completeAdd");
829
+ const res = await postJson("/plugins/agy-link/pool/complete-add", { code: authCodeInput.trim() });
830
+ setLoadingAction(null);
831
+ if (res && res.ok) {
832
+ flowStartedRef.current = false;
833
+ setAuthCodeInput("");
834
+ setAliasInput("");
835
+ setAddingAccount(false);
836
+ await refresh();
837
+ showToast(res.message || "成功添加并激活 Google 账号", "success");
838
+ } else showToast(res?.message || res?.error || "授权码验证失败", "error");
100
839
  };
101
- const submitCode = async () => {
102
- if (code.trim() === "") return;
103
- setBusy(true);
104
- await postJson("/plugins/agy-link/auth-code", { code: code.trim() });
105
- setCode("");
106
- const st = await getStatus();
107
- setStatus(st);
108
- setNotice(st?.auth?.message ?? "");
109
- setBusy(false);
840
+ const handleCancelAddAccount = async () => {
841
+ flowStartedRef.current = false;
842
+ await postJson("/plugins/agy-link/pool/cancel-add", {});
843
+ setAuthCodeInput("");
844
+ setAliasInput("");
845
+ setAddingAccount(false);
846
+ await refresh();
110
847
  };
111
848
  const setCfg = async (key, value) => {
112
- setBusy(true);
849
+ setLoadingAction(`config:${key}`);
113
850
  await postJson("/plugins/agy-link/config", {
114
851
  key,
115
852
  value
116
853
  });
117
854
  await refresh();
118
- setBusy(false);
855
+ setLoadingAction(null);
119
856
  };
120
- const reachable = status !== null;
121
- const summary = !reachable ? "Status endpoint unreachable — the plugin host did not answer /plugins/agy-link/status. Restart the DSH web server and check its logs." : status.dormantReason ? "Not ready — " + status.dormantReason : status.auth?.phase === "ok" ? "Ready — agy is connected" : "Needs Google login — start the login flow below, or run /agy auth";
122
- const color = !reachable ? "#e5484d" : status.dormantReason ? "#f5a623" : status.auth?.phase === "ok" ? "#30a46c" : "#f5a623";
123
- const infoBlock = h("div", { style: {
857
+ const setPrimary = async (id) => {
858
+ setLoadingAction(`primary:${id}`);
859
+ await postJson("/plugins/agy-link/pool/primary", { id });
860
+ await refresh();
861
+ setLoadingAction(null);
862
+ showToast("已设为主用账号", "success");
863
+ };
864
+ const removeAccount = async (id, alias) => {
865
+ setLoadingAction(`remove:${id}`);
866
+ await postJson("/plugins/agy-link/pool/remove", { id });
867
+ await refresh();
868
+ setLoadingAction(null);
869
+ showToast(`已移除账号: ${alias}`, "info");
870
+ };
871
+ const refreshQuota = async (id) => {
872
+ setLoadingAction(id ? `refresh:${id}` : "refresh:all");
873
+ await postJson("/plugins/agy-link/pool/refresh-quota", { id });
874
+ await refresh();
875
+ setLoadingAction(null);
876
+ showToast("额度已刷新", "success");
877
+ };
878
+ const saveProxy = async (id) => {
879
+ setLoadingAction(`proxy:${id}`);
880
+ const proxyUrl = proxyInputs[id];
881
+ await postJson("/plugins/agy-link/pool/proxy", {
882
+ id,
883
+ proxyUrl
884
+ });
885
+ setEditingProxyId(null);
886
+ await refresh();
887
+ setLoadingAction(null);
888
+ showToast("代理已保存", "success");
889
+ };
890
+ const setMode = async (mode) => {
891
+ setLoadingAction(`mode:${mode}`);
892
+ await postJson("/plugins/agy-link/pool/mode", { mode });
893
+ await refresh();
894
+ setLoadingAction(null);
895
+ };
896
+ const clearCooldown = async (id) => {
897
+ setLoadingAction(id ? `clearCooldown:${id}` : "clearCooldown:all");
898
+ await postJson("/plugins/agy-link/pool/clear-cooldown", { id });
899
+ await refresh();
900
+ setLoadingAction(null);
901
+ showToast("已清除冷却", "success");
902
+ };
903
+ const toggleExpand = (accId) => {
904
+ setExpandedModels((prev) => ({
905
+ ...prev,
906
+ [accId]: !prev[accId]
907
+ }));
908
+ };
909
+ const authPhase = status?.auth?.phase ?? "unknown";
910
+ const pool = status?.pool;
911
+ const accounts = pool?.accounts ?? [];
912
+ const isAuthed = authPhase === "ok" || accounts.length > 0;
913
+ const isBusy = loadingAction !== null;
914
+ const renderSpinner = () => h("span", { className: "agy-spinner" });
915
+ const renderToastBanner = () => {
916
+ if (!toast) return null;
917
+ const style = {
918
+ success: {
919
+ background: "var(--agy-toast-success-bg)",
920
+ border: "1px solid var(--agy-toast-success-border)",
921
+ color: "var(--agy-toast-success-text)"
922
+ },
923
+ info: {
924
+ background: "var(--agy-toast-info-bg)",
925
+ border: "1px solid var(--agy-toast-info-border)",
926
+ color: "var(--agy-toast-info-text)"
927
+ },
928
+ warn: {
929
+ background: "var(--agy-toast-warn-bg)",
930
+ border: "1px solid var(--agy-toast-warn-border)",
931
+ color: "var(--agy-toast-warn-text)"
932
+ },
933
+ error: {
934
+ background: "var(--agy-toast-error-bg)",
935
+ border: "1px solid var(--agy-toast-error-border)",
936
+ color: "var(--agy-toast-error-text)"
937
+ }
938
+ }[toast.type];
939
+ return h("div", { style: {
940
+ ...S.noticeBanner,
941
+ ...style
942
+ } }, h("div", { style: {
943
+ display: "flex",
944
+ alignItems: "center",
945
+ gap: "8px",
946
+ fontWeight: 600
947
+ } }, uiIcon(toast.type === "success" ? "check" : toast.type === "error" || toast.type === "warn" ? "alert" : "globe", 14, style.color), h("span", null, toast.text)), h("button", {
948
+ type: "button",
949
+ style: {
950
+ background: "transparent",
951
+ border: "none",
952
+ color: "inherit",
953
+ cursor: "pointer",
954
+ padding: "0 4px",
955
+ opacity: .8,
956
+ display: "inline-flex",
957
+ alignItems: "center"
958
+ },
959
+ onClick: () => setToast(null)
960
+ }, uiIcon("x", 12)));
961
+ };
962
+ const renderQuotaBar = (label, familyKey, acc) => {
963
+ const info = acc.quotas[familyKey];
964
+ const cd = acc.cooldowns[familyKey];
965
+ const inCooldown = cd && cd.cooldownUntil > Date.now();
966
+ let pct5h = typeof info?.remainingFraction === "number" && Number.isFinite(info.remainingFraction) ? Math.max(0, Math.min(100, Math.round(info.remainingFraction * 100))) : -1;
967
+ if (inCooldown) pct5h = 0;
968
+ const w5h = formatQuotaWindow(info?.resetTime);
969
+ const pctWeekly = typeof info?.weeklyFraction === "number" && Number.isFinite(info.weeklyFraction) ? Math.max(0, Math.min(100, Math.round(info.weeklyFraction * 100))) : -1;
970
+ const wWeekly = formatQuotaWindow(info?.weeklyResetTime);
971
+ const getColors = (pct) => {
972
+ if (pct < 0) return {
973
+ bar: "var(--agy-quota-none-border)",
974
+ text: "var(--agy-quota-none-text)",
975
+ bg: "var(--agy-quota-none-bg)",
976
+ border: "var(--agy-quota-none-border)"
977
+ };
978
+ if (pct <= 20) return {
979
+ bar: "linear-gradient(90deg, #dc2626, #ef4444)",
980
+ text: "var(--agy-quota-low-text)",
981
+ bg: "var(--agy-quota-low-bg)",
982
+ border: "var(--agy-quota-low-border)"
983
+ };
984
+ if (pct <= 50) return {
985
+ bar: "linear-gradient(90deg, #d97706, #f59e0b)",
986
+ text: "var(--agy-quota-med-text)",
987
+ bg: "var(--agy-quota-med-bg)",
988
+ border: "var(--agy-quota-med-border)"
989
+ };
990
+ return {
991
+ bar: "linear-gradient(90deg, #059669, #10b981)",
992
+ text: "var(--agy-quota-high-text)",
993
+ bg: "var(--agy-quota-high-bg)",
994
+ border: "var(--agy-quota-high-border)"
995
+ };
996
+ };
997
+ const c5h = getColors(pct5h);
998
+ const cWeekly = getColors(pctWeekly);
999
+ const renderLine = (windowName, percent, c, resetStr) => {
1000
+ return h("div", { style: {
1001
+ display: "flex",
1002
+ alignItems: "center",
1003
+ fontSize: "11.5px",
1004
+ margin: "4px 0"
1005
+ } }, h("span", { style: {
1006
+ width: "52px",
1007
+ color: "var(--agy-text-window-label)",
1008
+ fontSize: "11px",
1009
+ fontWeight: 700,
1010
+ flexShrink: 0
1011
+ } }, windowName), h("div", { style: S.progressBarBg }, h("div", {
1012
+ className: "agy-progress-fill",
1013
+ style: {
1014
+ width: `${percent < 0 ? 100 : percent}%`,
1015
+ height: "100%",
1016
+ background: c.bar,
1017
+ borderRadius: "3px"
1018
+ }
1019
+ })), h("div", { style: {
1020
+ display: "inline-flex",
1021
+ alignItems: "center",
1022
+ gap: "8px",
1023
+ minWidth: "140px",
1024
+ justifyContent: "flex-end",
1025
+ flexShrink: 0
1026
+ } }, h("span", { style: {
1027
+ padding: "2px 7px",
1028
+ borderRadius: "4px",
1029
+ background: c.bg,
1030
+ color: c.text,
1031
+ border: `1px solid ${c.border}`,
1032
+ fontWeight: 700,
1033
+ fontSize: "11.5px",
1034
+ lineHeight: 1.2
1035
+ } }, percent < 0 ? "—" : `${percent}%`), resetStr ? h("span", { style: {
1036
+ color: "var(--agy-text-secondary)",
1037
+ fontSize: "11px",
1038
+ fontWeight: 500
1039
+ } }, `↻ ${resetStr}`) : null));
1040
+ };
1041
+ return h("div", { style: {
1042
+ margin: "6px 0",
1043
+ padding: "8px 10px",
1044
+ background: "var(--agy-bg-subbox)",
1045
+ borderRadius: "7px",
1046
+ border: "1px solid var(--agy-border-box)"
1047
+ } }, h("div", { style: {
1048
+ display: "flex",
1049
+ alignItems: "center",
1050
+ gap: "6px",
1051
+ fontWeight: 700,
1052
+ fontSize: "12.5px",
1053
+ marginBottom: "5px",
1054
+ color: "var(--agy-text-primary)"
1055
+ } }, brandIcon(FAMILY_BRAND[familyKey], 14), h("span", null, label)), renderLine("5h 额度", pct5h, c5h, w5h.resetText), renderLine("周额度", pctWeekly, cWeekly, wWeekly.resetText));
1056
+ };
1057
+ const renderedAccountCards = accounts.map((acc) => {
1058
+ const isPrimary = acc.id === pool?.primaryAccountId;
1059
+ const hasCooldown = Object.entries(acc.cooldowns).some(([, cd]) => cd && cd.cooldownUntil > Date.now());
1060
+ const dotColor = !acc.enabled ? "#64748b" : hasCooldown ? "#f59e0b" : "#10b981";
1061
+ const isEditingProxy = editingProxyId === acc.id;
1062
+ const isExpanded = expandedModels[acc.id] ?? false;
1063
+ const cardStyle = isPrimary ? { ...S.cardPrimary } : { ...S.card };
1064
+ const googleModels = acc.quotas.google?.models ?? [];
1065
+ const anthropicModels = acc.quotas.anthropic?.models ?? [];
1066
+ const openaiModels = acc.quotas.openai?.models ?? [];
1067
+ const allChildModels = [
1068
+ ...googleModels.map((m) => ({
1069
+ family: "Google",
1070
+ model: m
1071
+ })),
1072
+ ...anthropicModels.map((m) => ({
1073
+ family: "Anthropic",
1074
+ model: m
1075
+ })),
1076
+ ...openaiModels.map((m) => ({
1077
+ family: "OpenAI",
1078
+ model: m
1079
+ }))
1080
+ ];
1081
+ return h("div", {
1082
+ key: acc.id,
1083
+ className: "agy-card-hover",
1084
+ style: cardStyle
1085
+ }, h("div", { style: {
1086
+ display: "flex",
1087
+ alignItems: "center",
1088
+ justifyContent: "space-between",
1089
+ marginBottom: "8px"
1090
+ } }, h("div", { style: {
1091
+ display: "flex",
1092
+ alignItems: "center",
1093
+ gap: "8px",
1094
+ flexWrap: "wrap"
1095
+ } }, h("span", {
1096
+ className: "agy-pulse-dot",
1097
+ style: {
1098
+ background: dotColor,
1099
+ boxShadow: `0 0 8px ${dotColor}aa`
1100
+ }
1101
+ }), h("span", { style: {
1102
+ fontWeight: 700,
1103
+ fontSize: "13.5px",
1104
+ color: "var(--agy-text-primary)"
1105
+ } }, acc.alias), acc.email ? h("span", { style: {
1106
+ ...S.badgeTag,
1107
+ background: "var(--agy-badge-email-bg)",
1108
+ color: "var(--agy-badge-email-text)",
1109
+ borderColor: "var(--agy-badge-email-border)",
1110
+ gap: "5px"
1111
+ } }, uiIcon("mail", 11, "var(--agy-badge-email-text)"), acc.email) : null, isPrimary ? h("span", { style: {
1112
+ ...S.badgePrimary,
1113
+ gap: "4px"
1114
+ } }, uiIcon("star", 10, "var(--agy-badge-primary-text)"), "主用") : null, acc.proxyUrl ? h("span", { style: {
1115
+ ...S.badgeTag,
1116
+ background: "var(--agy-badge-proxy-bg)",
1117
+ color: "var(--agy-badge-proxy-text)",
1118
+ borderColor: "var(--agy-badge-proxy-border)",
1119
+ gap: "5px"
1120
+ } }, uiIcon("globe", 11, "var(--agy-badge-proxy-text)"), "代理") : null), h("div", { style: {
1121
+ display: "flex",
1122
+ gap: "5px",
1123
+ alignItems: "center"
1124
+ } }, allChildModels.length > 0 ? h("button", {
1125
+ type: "button",
1126
+ className: "agy-btn",
1127
+ style: isExpanded ? {
1128
+ ...S.btnSmPrimary,
1129
+ gap: "3px"
1130
+ } : {
1131
+ ...S.btnSm,
1132
+ gap: "3px"
1133
+ },
1134
+ onClick: () => toggleExpand(acc.id)
1135
+ }, isExpanded ? [uiIcon("chevronUp", 11), " 收起"] : [uiIcon("chevronDown", 11), " 明细"]) : null, !isPrimary ? h("button", {
1136
+ type: "button",
1137
+ className: "agy-btn",
1138
+ style: {
1139
+ ...S.btnSm,
1140
+ gap: "4px"
1141
+ },
1142
+ disabled: isBusy,
1143
+ onClick: () => void setPrimary(acc.id)
1144
+ }, loadingAction === `primary:${acc.id}` ? [renderSpinner(), "设置中"] : [uiIcon("star", 11), " 设为主用"]) : null, h("button", {
1145
+ type: "button",
1146
+ className: "agy-btn",
1147
+ style: isEditingProxy ? {
1148
+ ...S.btnSmPrimary,
1149
+ gap: "4px"
1150
+ } : {
1151
+ ...S.btnSm,
1152
+ gap: "4px"
1153
+ },
1154
+ disabled: isBusy,
1155
+ onClick: () => setEditingProxyId(isEditingProxy ? null : acc.id)
1156
+ }, [uiIcon("globe", 11), " 代理"]), accounts.length > 1 ? h("button", {
1157
+ type: "button",
1158
+ className: "agy-btn",
1159
+ style: {
1160
+ ...S.btnDanger,
1161
+ padding: "3px 7px"
1162
+ },
1163
+ title: "移除此账号",
1164
+ disabled: isBusy,
1165
+ onClick: () => void removeAccount(acc.id, acc.alias)
1166
+ }, loadingAction === `remove:${acc.id}` ? renderSpinner() : uiIcon("trash", 12, "var(--agy-danger-text)")) : null)), h("div", { style: S.quotaBox }, renderQuotaBar("Gemini", "google", acc), renderQuotaBar("Claude", "anthropic", acc), renderQuotaBar("GPT-OSS", "openai", acc), isExpanded && allChildModels.length > 0 ? h("div", { style: {
1167
+ marginTop: "8px",
1168
+ paddingTop: "8px",
1169
+ borderTop: "1px solid var(--agy-border-box)"
1170
+ } }, h("div", { style: {
1171
+ color: "var(--agy-text-secondary)",
1172
+ marginBottom: "6px",
1173
+ fontWeight: 600,
1174
+ fontSize: "11px"
1175
+ } }, "单模型明细:"), allChildModels.map(({ family, model }) => {
1176
+ const frac = model.remainingFraction ?? 1;
1177
+ const pct = Math.round(frac * 100);
1178
+ const w = formatQuotaWindow(model.resetTime);
1179
+ const pColor = pct <= 20 ? "var(--agy-quota-low-text)" : pct <= 50 ? "var(--agy-quota-med-text)" : "var(--agy-quota-high-text)";
1180
+ return h("div", {
1181
+ key: model.modelId,
1182
+ className: "agy-submodel-row"
1183
+ }, h("span", { style: {
1184
+ fontWeight: 500,
1185
+ color: "var(--agy-text-primary)"
1186
+ } }, `[${family}] ${model.displayName || model.modelId}`), h("div", { style: {
1187
+ display: "flex",
1188
+ alignItems: "center",
1189
+ gap: "8px"
1190
+ } }, h("span", { style: {
1191
+ color: pColor,
1192
+ fontWeight: 700
1193
+ } }, `${pct}%`), w.resetText ? h("span", { style: {
1194
+ color: "var(--agy-text-tertiary)",
1195
+ fontSize: "10.5px"
1196
+ } }, `↻ ${w.resetText}`) : null));
1197
+ })) : null), hasCooldown ? h("div", { style: {
1198
+ background: "var(--agy-warn-bg)",
1199
+ border: "1px solid var(--agy-warn-border)",
1200
+ borderRadius: "7px",
1201
+ padding: "6px 10px",
1202
+ color: "var(--agy-warn-text)",
1203
+ fontSize: "11.5px",
1204
+ fontWeight: 600,
1205
+ marginTop: "8px",
1206
+ display: "flex",
1207
+ alignItems: "center",
1208
+ justifyContent: "space-between"
1209
+ } }, h("div", { style: {
1210
+ display: "flex",
1211
+ alignItems: "center",
1212
+ gap: "6px"
1213
+ } }, uiIcon("alert", 13, "var(--agy-warn-text)"), h("span", null, "部分模型限流中,已自动切换账号")), h("button", {
1214
+ type: "button",
1215
+ className: "agy-btn",
1216
+ style: {
1217
+ ...S.btnSm,
1218
+ color: "var(--agy-warn-btn-text)",
1219
+ borderColor: "var(--agy-warn-btn-border)",
1220
+ background: "var(--agy-warn-btn-bg)",
1221
+ gap: "4px"
1222
+ },
1223
+ disabled: isBusy,
1224
+ onClick: () => void clearCooldown(acc.id)
1225
+ }, loadingAction === `clearCooldown:${acc.id}` ? [renderSpinner(), ""] : [uiIcon("zap", 11, "var(--agy-warn-btn-text)"), " 清除冷却"])) : null, isEditingProxy ? h("div", { style: {
1226
+ marginTop: "8px",
1227
+ padding: "10px 12px",
1228
+ background: "var(--agy-bg-subbox)",
1229
+ borderRadius: "7px",
1230
+ border: "1px solid var(--agy-border-subbox)"
1231
+ } }, h("div", { style: {
1232
+ display: "flex",
1233
+ gap: "8px"
1234
+ } }, h("input", {
1235
+ style: S.input,
1236
+ value: proxyInputs[acc.id] !== void 0 ? proxyInputs[acc.id] : acc.proxyUrl ?? "",
1237
+ placeholder: "专属代理 URL (如: http://127.0.0.1:7890,留空则使用全局)",
1238
+ onChange: (e) => setProxyInputs({
1239
+ ...proxyInputs,
1240
+ [acc.id]: e.target.value
1241
+ })
1242
+ }), h("button", {
1243
+ type: "button",
1244
+ className: "agy-btn",
1245
+ style: S.btnPrimary,
1246
+ disabled: isBusy,
1247
+ onClick: () => void saveProxy(acc.id)
1248
+ }, loadingAction === `proxy:${acc.id}` ? [renderSpinner(), "保存"] : "保存"), h("button", {
1249
+ type: "button",
1250
+ className: "agy-btn",
1251
+ style: S.btn,
1252
+ onClick: () => setEditingProxyId(null)
1253
+ }, "取消"))) : null);
1254
+ });
1255
+ const poolAuth = status?.poolAuth;
1256
+ const flowPhase = poolAuth?.phase ?? "idle";
1257
+ const flowActive = flowPhase === "waiting" || flowPhase === "exchanging";
1258
+ const addAccountSection = addingAccount ? h("div", { style: S.authModal }, h("div", { style: {
1259
+ fontWeight: 700,
1260
+ fontSize: "13.5px",
1261
+ marginBottom: "10px",
1262
+ display: "flex",
1263
+ alignItems: "center",
1264
+ gap: "6px",
1265
+ color: "var(--agy-text-primary)"
1266
+ } }, uiIcon("plus", 13, "var(--agy-btn-primary-bg)"), "添加 Google 账号"), !flowActive ? h("div", null, h("div", { style: {
1267
+ display: "flex",
1268
+ gap: "8px"
1269
+ } }, h("input", {
1270
+ style: S.input,
1271
+ value: aliasInput,
1272
+ placeholder: "账号别名 (例如: 备用账号 2)",
1273
+ onChange: (e) => setAliasInput(e.target.value)
1274
+ }), h("button", {
1275
+ type: "button",
1276
+ className: "agy-btn",
1277
+ style: {
1278
+ ...S.btnPrimary,
1279
+ gap: "4px"
1280
+ },
1281
+ disabled: isBusy,
1282
+ onClick: () => void handleBeginAddAccount()
1283
+ }, loadingAction === "pool:beginAdd" ? [renderSpinner(), "正在打开浏览器..."] : [uiIcon("externalLink", 12, "#ffffff"), " 打开浏览器登录"]), h("button", {
1284
+ type: "button",
1285
+ className: "agy-btn",
1286
+ style: S.btn,
1287
+ onClick: () => handleCancelAddAccount()
1288
+ }, "取消")), flowPhase === "failed" && poolAuth?.message ? h("div", { style: {
124
1289
  ...S.muted,
125
- lineHeight: 1.6,
126
- margin: "6px 0 12px"
127
- } }, h("div", null, "agy binary: ", status?.bin ?? "not found", status?.version ? " — v" + status.version : ""), h("div", null, "auth: ", status?.auth?.phase ?? "unknown"), h("div", null, "workspace: ", status?.workspaceRoot ? status.workspaceRoot : "(session cwd / process cwd)"), h("div", null, "models: ", String(status?.catalog?.count ?? 0), " — ", status?.catalog?.source ?? ""), h("div", null, "bindings: ", String(status?.bindings ?? 0)), h("div", null, "last run: ", status?.lastRun ? (status.lastRun.ok ? "ok" : status.lastRun.code) + " — " + status.lastRun.model : "none"));
128
- const modeRow = h("div", { style: {
129
- ...S.row,
130
- marginTop: "8px"
131
- } }, h("span", { style: S.muted }, "mode:"), h("button", {
1290
+ color: "var(--agy-danger-text)",
1291
+ marginTop: "8px",
1292
+ display: "flex",
1293
+ alignItems: "center",
1294
+ gap: "5px",
1295
+ fontWeight: 600
1296
+ } }, uiIcon("alert", 12, "var(--agy-danger-text)"), poolAuth.message) : null) : h("div", null, h("div", { style: {
1297
+ display: "flex",
1298
+ alignItems: "center",
1299
+ color: "var(--agy-text-secondary)",
1300
+ marginBottom: "8px",
1301
+ lineHeight: 1.5,
1302
+ fontSize: "12px"
1303
+ } }, renderSpinner(), flowPhase === "exchanging" ? "正在验证授权并激活账号,请稍候..." : "等待浏览器中完成 Google 授权,成功后将自动激活。"), poolAuth?.url ? h("div", { style: { marginBottom: "8px" } }, h("a", {
1304
+ href: poolAuth.url,
1305
+ target: "_blank",
1306
+ style: {
1307
+ color: "var(--dsw-alias-state-business-primary, #2563eb)",
1308
+ textDecoration: "underline",
1309
+ fontSize: "12px",
1310
+ fontWeight: 600,
1311
+ display: "inline-flex",
1312
+ alignItems: "center",
1313
+ gap: "4px"
1314
+ }
1315
+ }, uiIcon("externalLink", 12, "currentColor"), "若浏览器未打开,请点击此处手动打开 Google 登录页")) : null, h("div", { style: {
1316
+ color: "var(--agy-text-tertiary)",
1317
+ marginBottom: "6px",
1318
+ fontSize: "11px",
1319
+ fontWeight: 500
1320
+ } }, "未完成自动回调时,可粘贴授权码或回调 URL:"), h("div", { style: {
1321
+ display: "flex",
1322
+ gap: "8px"
1323
+ } }, h("input", {
1324
+ style: S.input,
1325
+ value: authCodeInput,
1326
+ placeholder: "授权码 或 http://localhost:51121/oauth-callback?code=... 完整链接",
1327
+ onChange: (e) => setAuthCodeInput(e.target.value)
1328
+ }), h("button", {
1329
+ type: "button",
1330
+ className: "agy-btn",
1331
+ style: {
1332
+ ...S.btnPrimary,
1333
+ gap: "4px"
1334
+ },
1335
+ disabled: isBusy || !authCodeInput.trim(),
1336
+ onClick: () => void handleCompleteAddAccount()
1337
+ }, loadingAction === "pool:completeAdd" ? [renderSpinner(), "验证激活中..."] : [uiIcon("check", 12, "#ffffff"), " 手动激活"]), h("button", {
1338
+ type: "button",
1339
+ className: "agy-btn",
1340
+ style: S.btn,
1341
+ onClick: () => handleCancelAddAccount()
1342
+ }, "取消")))) : null;
1343
+ if (status === null) return h("div", { style: {
1344
+ ...S.container,
1345
+ minHeight: "120px",
1346
+ display: "flex",
1347
+ alignItems: "center",
1348
+ justifyContent: "center"
1349
+ } }, h("style", null, GLOBAL_CSS), h("span", { style: S.muted }, [renderSpinner(), "正在加载 Antigravity 状态..."]));
1350
+ return h("div", { style: S.container }, h("style", null, GLOBAL_CSS), h("div", { style: S.headerCard }, h("div", { style: {
1351
+ display: "flex",
1352
+ alignItems: "center",
1353
+ gap: "8px"
1354
+ } }, agyIcon(16), h("span", { style: {
1355
+ fontWeight: 700,
1356
+ fontSize: "14px",
1357
+ color: "var(--agy-text-primary)"
1358
+ } }, "Antigravity"), h("span", { style: isAuthed ? S.badgeReady : S.badgeUnready }, isAuthed ? "就绪" : "待认证")), h("div", { style: {
1359
+ display: "flex",
1360
+ gap: "6px",
1361
+ alignItems: "center"
1362
+ } }, !addingAccount ? h("button", {
1363
+ type: "button",
1364
+ className: "agy-btn",
1365
+ style: {
1366
+ ...S.btnPrimary,
1367
+ gap: "4px"
1368
+ },
1369
+ onClick: () => setAddingAccount(true)
1370
+ }, [uiIcon("plus", 12, "#ffffff"), " 添加账号"]) : null, h("button", {
1371
+ type: "button",
1372
+ className: "agy-btn",
1373
+ style: {
1374
+ ...S.btn,
1375
+ gap: "4px"
1376
+ },
1377
+ disabled: isBusy,
1378
+ onClick: () => void refreshQuota()
1379
+ }, loadingAction === "refresh:all" ? [renderSpinner(), "刷新中"] : [uiIcon("refresh", 12, "var(--agy-text-btn)"), " 刷新额度"]))), renderToastBanner(), addAccountSection, renderedAccountCards, h("div", { style: {
1380
+ marginTop: "16px",
1381
+ paddingTop: "12px",
1382
+ borderTop: "1px solid var(--agy-border-divider)"
1383
+ } }, h("div", { style: {
1384
+ display: "flex",
1385
+ flexDirection: "column",
1386
+ gap: "10px"
1387
+ } }, h("div", { style: {
1388
+ display: "flex",
1389
+ alignItems: "center",
1390
+ justifyContent: "space-between"
1391
+ } }, h("span", { style: {
1392
+ color: "var(--agy-text-primary)",
1393
+ fontWeight: 600,
1394
+ fontSize: "12.5px"
1395
+ } }, "权限模式:"), h("div", { style: S.segGroup }, h("button", {
132
1396
  type: "button",
133
- style: status?.permissionMode === "plan" ? S.btnPrimary : S.btn,
1397
+ style: status?.permissionMode === "plan" ? S.segBtnActive : S.segBtn,
134
1398
  onClick: () => void setCfg("permissionMode", "plan")
135
- }, "plan"), h("button", {
1399
+ }, "plan (只读)"), h("button", {
136
1400
  type: "button",
137
- style: status?.permissionMode === "accept-edits" ? S.btnPrimary : S.btn,
1401
+ style: status?.permissionMode === "accept-edits" ? S.segBtnActive : S.segBtn,
138
1402
  onClick: () => void setCfg("permissionMode", "accept-edits")
139
- }, "accept-edits"), h("button", {
1403
+ }, "accept-edits (改代码)"), h("button", {
140
1404
  type: "button",
141
1405
  style: status?.permissionMode === "skip" ? {
142
- ...S.btn,
143
- color: "#e5484d",
144
- borderColor: "rgba(229,72,77,0.6)"
145
- } : S.btn,
1406
+ ...S.segBtnActive,
1407
+ color: "var(--agy-seg-danger-text)",
1408
+ background: "var(--agy-seg-danger-bg)",
1409
+ border: "1px solid var(--agy-seg-danger-border)"
1410
+ } : S.segBtn,
146
1411
  onClick: () => void setCfg("permissionMode", "skip")
147
- }, "skip"));
148
- const effortRow = h("div", { style: S.row }, h("span", { style: S.muted }, "effort:"), h("button", {
1412
+ }, "skip (全自动免确认)"))), h("div", { style: {
1413
+ display: "flex",
1414
+ alignItems: "center",
1415
+ justifyContent: "space-between"
1416
+ } }, h("span", { style: {
1417
+ color: "var(--agy-text-primary)",
1418
+ fontWeight: 600,
1419
+ fontSize: "12.5px"
1420
+ } }, "思考强度:"), h("div", { style: S.segGroup }, h("button", {
149
1421
  type: "button",
150
- style: status?.defaultEffort === "low" ? S.btnPrimary : S.btn,
1422
+ style: status?.defaultEffort === "" ? S.segBtnActive : S.segBtn,
1423
+ onClick: () => void setCfg("defaultEffort", "")
1424
+ }, "auto"), h("button", {
1425
+ type: "button",
1426
+ style: status?.defaultEffort === "low" ? S.segBtnActive : S.segBtn,
151
1427
  onClick: () => void setCfg("defaultEffort", "low")
152
1428
  }, "low"), h("button", {
153
1429
  type: "button",
154
- style: status?.defaultEffort === "medium" ? S.btnPrimary : S.btn,
1430
+ style: status?.defaultEffort === "medium" ? S.segBtnActive : S.segBtn,
155
1431
  onClick: () => void setCfg("defaultEffort", "medium")
156
1432
  }, "medium"), h("button", {
157
1433
  type: "button",
158
- style: status?.defaultEffort === "high" ? S.btnPrimary : S.btn,
1434
+ style: status?.defaultEffort === "high" ? S.segBtnActive : S.segBtn,
159
1435
  onClick: () => void setCfg("defaultEffort", "high")
160
- }, "high"), h("button", {
161
- type: "button",
162
- style: status?.defaultEffort === "" ? S.btnPrimary : S.btn,
163
- onClick: () => void setCfg("defaultEffort", "")
164
- }, "auto"));
165
- const skipWarn = h("div", { style: {
166
- ...S.muted,
167
- marginBottom: "8px"
168
- } }, "skip runs agy tools with --dangerously-skip-permissions (no approval prompts). plan is the safe read-only default.");
169
- const authBlock = h("div", null, h("div", { style: {
1436
+ }, "high"))), h("div", { style: {
1437
+ display: "flex",
1438
+ alignItems: "center",
1439
+ justifyContent: "space-between"
1440
+ } }, h("span", { style: {
1441
+ color: "var(--agy-text-primary)",
170
1442
  fontWeight: 600,
171
- marginBottom: "4px"
172
- } }, "Google login required"), h("div", null, "agy is not signed in. Start the login flow, scan the QR or open the URL, then paste the authorization code below."), h("img", {
173
- src: base + "/plugins/agy-link/qr",
174
- alt: "auth QR",
175
- width: 200,
176
- height: 200,
177
- style: {
178
- display: "block",
179
- margin: "8px auto"
180
- }
181
- }), h("div", { style: { wordBreak: "break-all" } }, status?.auth?.url ?? ""), h("div", { style: S.row }, h("input", {
182
- style: S.input,
183
- value: code,
184
- placeholder: "authorization code",
185
- onChange: (e) => setCode(e.target.value)
186
- }), h("button", {
1443
+ fontSize: "12.5px"
1444
+ } }, "号池调度:"), h("div", { style: S.segGroup }, h("button", {
187
1445
  type: "button",
188
- style: S.btnPrimary,
189
- disabled: busy,
190
- onClick: () => void submitCode()
191
- }, "Submit code"), h("button", {
1446
+ style: pool?.mode === "sequential" || !pool?.mode ? S.segBtnActive : S.segBtn,
1447
+ onClick: () => void setMode("sequential")
1448
+ }, "顺次耗尽"), h("button", {
192
1449
  type: "button",
193
- style: S.btn,
194
- disabled: busy,
195
- onClick: () => void startAuth()
196
- }, "Restart login")), notice !== "" ? h("div", null, notice) : null);
197
- const needsAuth = reachable && !status.dormantReason && status.auth !== void 0 && status.auth.phase !== "ok";
198
- const controls = !reachable ? null : status.dormantReason ? h("div", { style: S.muted }, "Bridge dormant: ", status.dormantReason) : needsAuth ? authBlock : h("div", null, infoBlock, modeRow, effortRow, skipWarn);
199
- return h("div", { style: { lineHeight: 1.6 } }, h("div", { style: {
1450
+ style: pool?.mode === "round-robin" ? S.segBtnActive : S.segBtn,
1451
+ onClick: () => void setMode("round-robin")
1452
+ }, "轮询均衡"))))));
1453
+ };
1454
+ /** Modal dialog container rendered into document.body */
1455
+ const AgyModalDialog = () => {
1456
+ const [isOpen, setIsOpen] = useState(agyModalStore.open);
1457
+ useEffect(() => {
1458
+ return agyModalStore.subscribe(() => {
1459
+ setIsOpen(agyModalStore.open);
1460
+ });
1461
+ }, []);
1462
+ useEffect(() => {
1463
+ if (!isOpen) return;
1464
+ const onKey = (e) => {
1465
+ if (e.key === "Escape") {
1466
+ e.stopPropagation();
1467
+ agyModalStore.setOpen(false);
1468
+ }
1469
+ };
1470
+ win.addEventListener?.("keydown", onKey, true);
1471
+ return () => win.removeEventListener?.("keydown", onKey, true);
1472
+ }, [isOpen]);
1473
+ if (!isOpen) return null;
1474
+ return h("div", {
1475
+ className: "agy-modal-backdrop",
1476
+ onClick: (e) => {
1477
+ if (e.target === e.currentTarget) agyModalStore.setOpen(false);
1478
+ }
1479
+ }, h("div", {
1480
+ className: "agy-modal-panel",
1481
+ role: "dialog",
1482
+ "aria-modal": true
1483
+ }, h("div", { style: {
200
1484
  display: "flex",
201
1485
  alignItems: "center",
202
- gap: "8px",
203
- fontWeight: 600,
204
- fontSize: "14px"
205
- } }, h("span", { style: {
206
- display: "inline-block",
207
- width: "10px",
208
- height: "10px",
209
- borderRadius: "50%",
210
- background: color
211
- } }), "Antigravity (agy CLI)"), h("div", { style: {
212
- ...S.muted,
213
- margin: "6px 0 12px"
214
- } }, summary), controls, h("div", { style: S.muted }, "Run `/agy status` for full diagnostics, `/agy doctor` to export a report."));
1486
+ justifyContent: "space-between",
1487
+ padding: "14px 18px",
1488
+ borderBottom: "1px solid var(--agy-border-card)",
1489
+ background: "var(--agy-bg-header)",
1490
+ color: "var(--agy-text-primary)"
1491
+ } }, h("div", { style: {
1492
+ display: "flex",
1493
+ alignItems: "center",
1494
+ gap: "8px"
1495
+ } }, agyIcon(18), h("strong", { style: {
1496
+ fontSize: "14.5px",
1497
+ fontWeight: 700,
1498
+ color: "var(--agy-text-primary)"
1499
+ } }, "Antigravity 管理控制台")), h("button", {
1500
+ type: "button",
1501
+ title: "关闭 (Esc)",
1502
+ style: {
1503
+ background: "var(--agy-bg-btn)",
1504
+ border: "1px solid var(--agy-border-btn)",
1505
+ color: "var(--agy-text-btn)",
1506
+ cursor: "pointer",
1507
+ padding: "4px 8px",
1508
+ borderRadius: "6px",
1509
+ display: "inline-flex",
1510
+ alignItems: "center"
1511
+ },
1512
+ onClick: () => agyModalStore.setOpen(false)
1513
+ }, uiIcon("x", 14, "var(--agy-text-btn)"))), h("div", { style: {
1514
+ overflowY: "auto",
1515
+ padding: "18px",
1516
+ flex: "1",
1517
+ background: "var(--agy-bg-panel)"
1518
+ } }, h(AgySettingsSection, {
1519
+ isModal: true,
1520
+ onClose: () => agyModalStore.setOpen(false)
1521
+ }))));
215
1522
  };
1523
+ /** Session Header badge in chat toolbar */
216
1524
  const AgySessionStatus = () => {
217
- const [status, setStatus] = useState(null);
1525
+ const [status, setStatus] = useState(statusCache);
218
1526
  useEffect(() => {
219
1527
  let alive = true;
1528
+ let lastJson = "";
220
1529
  const tick = async () => {
221
1530
  const st = await getStatus();
222
- if (alive) setStatus(st);
1531
+ if (!alive || !st) return;
1532
+ const json = JSON.stringify(st);
1533
+ if (json !== lastJson) {
1534
+ lastJson = json;
1535
+ setStatus(st);
1536
+ }
223
1537
  };
224
1538
  tick();
225
1539
  const timer = setInterval(tick, 3e3);
@@ -228,31 +1542,38 @@ window.__ModuleLoader__.load({
228
1542
  clearInterval(timer);
229
1543
  };
230
1544
  }, []);
231
- const color = status === null ? "#9aa0a6" : status.dormantReason ? "#f5a623" : status.auth?.phase === "ok" ? "#30a46c" : "#f5a623";
232
- const title = status === null ? "Antigravity (agy) status…" : status.dormantReason ? "Antigravity not ready: " + status.dormantReason : status.auth?.phase === "ok" ? "Antigravity ready — agy is connected" : "Antigravity needs Google login";
233
- return h("button", {
1545
+ const accounts = (status?.pool)?.accounts ?? [];
1546
+ const hasCooldown = accounts.some((a) => Object.entries(a.cooldowns).some(([, cd]) => cd && cd.cooldownUntil > Date.now()));
1547
+ const isAuthed = status?.auth?.phase === "ok" || accounts.length > 0;
1548
+ const color = status === null ? "#64748b" : status.dormantReason ? "#f59e0b" : hasCooldown ? "#f59e0b" : isAuthed ? "#10b981" : "#f59e0b";
1549
+ const badge = h("button", {
234
1550
  type: "button",
235
- title,
1551
+ title: `Antigravity: ${accounts.length} accounts ready · 点击打开控制台`,
1552
+ className: "agy-btn",
1553
+ onClick: () => agyModalStore.setOpen(true),
236
1554
  style: {
237
- background: "transparent",
238
- border: "1px solid rgba(128,128,128,0.3)",
1555
+ background: "var(--agy-bg-header)",
1556
+ border: "1px solid var(--agy-border-card)",
239
1557
  borderRadius: "999px",
240
- cursor: "default",
241
- padding: "2px 8px",
242
- fontSize: "11px",
1558
+ cursor: "pointer",
1559
+ padding: "3px 10px",
1560
+ fontSize: "11.5px",
1561
+ fontWeight: 600,
243
1562
  lineHeight: 1.5,
244
- color: "inherit",
1563
+ color: "var(--agy-text-primary)",
245
1564
  display: "inline-flex",
246
1565
  alignItems: "center",
247
- gap: "5px"
1566
+ gap: "6px",
1567
+ boxShadow: "var(--agy-shadow-card)"
248
1568
  }
249
1569
  }, h("span", { style: {
250
1570
  display: "inline-block",
251
- width: "8px",
252
- height: "8px",
1571
+ width: "7px",
1572
+ height: "7px",
253
1573
  borderRadius: "50%",
254
1574
  background: color
255
- } }), "AGY");
1575
+ } }), `AGY (${accounts.length})`);
1576
+ return h("div", { style: { display: "inline-block" } }, badge, portalToBody(h(AgyModalDialog, null)));
256
1577
  };
257
1578
  ctx.slots.inject("conversation.session.header.actions", () => {
258
1579
  return ctx.slots.register({