dsh-agy-link 0.4.70 → 0.4.74

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
@@ -36,7 +36,8 @@ window.__ModuleLoader__.load({
36
36
  move: "M9 5h.01M9 12h.01M9 19h.01M15 5h.01M15 12h.01M15 19h.01",
37
37
  arrowUp: "M12 19V5M5 12l7-7 7 7",
38
38
  arrowDown: "M12 5v14M19 12l-7 7-7-7",
39
- power: "M18.36 6.64a9 9 0 1 1-12.73 0M12 2v10"
39
+ power: "M18.36 6.64a9 9 0 1 1-12.73 0M12 2v10",
40
+ flame: "M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"
40
41
  };
41
42
  //#endregion
42
43
  //#region src/client/locales.ts
@@ -181,7 +182,17 @@ window.__ModuleLoader__.load({
181
182
  "account.disableTitle": "停用此账号(切号与调度时跳过)",
182
183
  "account.enabledToast": "已启用账号:{alias}",
183
184
  "account.disabledToast": "已停用账号:{alias}",
184
- "status.disabled": "已停用"
185
+ "status.disabled": "已停用",
186
+ "header.warmup": "一键预热",
187
+ "header.warmingUp": "预热中",
188
+ "account.warmup": "预热",
189
+ "account.warmingUp": "预热中",
190
+ "account.warmupTitle": "预热该账号(向 Google 发送极小探活请求,提前激活 7 天周配额计时器)",
191
+ "warmup.success": "账号预热成功",
192
+ "warmup.allSuccess": "一键预热任务已完成",
193
+ "warmup.skipped": "当前账号或模型处于冷却中,无需预热",
194
+ "config.autoWarmup": "7天周配额自动预热",
195
+ "config.autoWarmupDesc": "周配额到达重置时间或冷启动时自动唤醒 1 次启动新周期"
185
196
  };
186
197
  const en = {
187
198
  "section.label": "Antigravity",
@@ -321,7 +332,17 @@ window.__ModuleLoader__.load({
321
332
  "account.disableTitle": "Disable this account (skip in rotation)",
322
333
  "account.enabledToast": "Account enabled: {alias}",
323
334
  "account.disabledToast": "Account disabled: {alias}",
324
- "status.disabled": "Disabled"
335
+ "status.disabled": "Disabled",
336
+ "header.warmup": "Auto Warmup",
337
+ "header.warmingUp": "Warming up...",
338
+ "account.warmup": "Warmup",
339
+ "account.warmingUp": "Warming up...",
340
+ "account.warmupTitle": "Warm up this account (trigger 7-day weekly reset timer)",
341
+ "warmup.success": "Account warmed up successfully",
342
+ "warmup.allSuccess": "Warmup completed for all accounts",
343
+ "warmup.skipped": "Account or model is in cooldown, no warmup needed",
344
+ "config.autoWarmup": "7-Day Quota Auto-Warmup",
345
+ "config.autoWarmupDesc": "Automatically trigger a 1-token probe when weekly reset time is reached"
325
346
  };
326
347
  const ptBR = {
327
348
  "section.label": "Antigravity",
@@ -461,7 +482,17 @@ window.__ModuleLoader__.load({
461
482
  "account.disableTitle": "Desativar esta conta (ignorar na rotação)",
462
483
  "account.enabledToast": "Conta ativada: {alias}",
463
484
  "account.disabledToast": "Conta desativada: {alias}",
464
- "status.disabled": "Desativado"
485
+ "status.disabled": "Desativado",
486
+ "header.warmup": "Pré-aquecimento",
487
+ "header.warmingUp": "Aquecendo...",
488
+ "account.warmup": "Aquecer",
489
+ "account.warmingUp": "Aquecendo...",
490
+ "account.warmupTitle": "Aquecer esta conta (ativar cronômetro semanal de 7 dias)",
491
+ "warmup.success": "Conta aquecida com sucesso",
492
+ "warmup.allSuccess": "Pré-aquecimento concluído",
493
+ "warmup.skipped": "Conta em espera, sem necessidade de aquecimento",
494
+ "config.autoWarmup": "Pré-aquecimento automático de 7 dias",
495
+ "config.autoWarmupDesc": "Ativa automaticamente um ciclo quando o tempo de redefinição semanal é atingido"
465
496
  };
466
497
  const es = {
467
498
  "section.label": "Antigravity",
@@ -601,7 +632,17 @@ window.__ModuleLoader__.load({
601
632
  "account.disableTitle": "Deshabilitar esta cuenta (ignorar en rotación)",
602
633
  "account.enabledToast": "Cuenta habilitada: {alias}",
603
634
  "account.disabledToast": "Cuenta deshabilitada: {alias}",
604
- "status.disabled": "Deshabilitado"
635
+ "status.disabled": "Deshabilitado",
636
+ "header.warmup": "Precalentamiento",
637
+ "header.warmingUp": "Calentando...",
638
+ "account.warmup": "Calentar",
639
+ "account.warmingUp": "Calentando...",
640
+ "account.warmupTitle": "Calentar esta cuenta (activar temporizador semanal de 7 días)",
641
+ "warmup.success": "Cuenta calentada con éxito",
642
+ "warmup.allSuccess": "Precalentamiento completado",
643
+ "warmup.skipped": "Cuenta en espera, sin necesidad de calentar",
644
+ "config.autoWarmup": "Precalentamiento automático de 7 días",
645
+ "config.autoWarmupDesc": "Activa automáticamente un ciclo cuando se alcanza el tiempo de reinicio semanal"
605
646
  };
606
647
  //#endregion
607
648
  //#region src/client/scroll-guard.ts
@@ -609,6 +650,7 @@ window.__ModuleLoader__.load({
609
650
  let scrollTimer = null;
610
651
  let listenerAttached = false;
611
652
  const settleListeners = /* @__PURE__ */ new Set();
653
+ let overlaySuspended = false;
612
654
  function onScrollActivity() {
613
655
  isScrolling = true;
614
656
  if (scrollTimer !== null) clearTimeout(scrollTimer);
@@ -620,8 +662,29 @@ window.__ModuleLoader__.load({
620
662
  } catch {}
621
663
  }, 150);
622
664
  }
623
- function isScrollActive() {
624
- return isScrolling;
665
+ /**
666
+ * Suspend every overlay scanner while a modal/overlay owns the screen.
667
+ *
668
+ * Opening the Antigravity console must freeze the conversation behind it: any
669
+ * DOM the scanners inject while the panel composites on top becomes visible
670
+ * flashing, and the modal's own periodic re-renders keep waking the global
671
+ * observers. On resume we fire the settle listeners once so scanners catch up
672
+ * on anything that changed while they were held back.
673
+ */
674
+ function setOverlaySuspended(active) {
675
+ if (overlaySuspended === active) return;
676
+ overlaySuspended = active;
677
+ if (!active) for (const fn of settleListeners) try {
678
+ fn();
679
+ } catch {}
680
+ }
681
+ /**
682
+ * The one predicate every overlay scanner should gate on: may I mutate the DOM
683
+ * right now? False during an active scroll gesture AND while an overlay owns
684
+ * the screen. Scanners must not act while this is true.
685
+ */
686
+ function isInteractionLocked() {
687
+ return isScrolling || overlaySuspended;
625
688
  }
626
689
  /** Register a callback to run once scrolling has settled. Returns an unregister function. */
627
690
  function onScrollSettle(fn) {
@@ -654,6 +717,7 @@ window.__ModuleLoader__.load({
654
717
  }
655
718
  settleListeners.clear();
656
719
  isScrolling = false;
720
+ overlaySuspended = false;
657
721
  }
658
722
  //#endregion
659
723
  //#region src/client/decisions.ts
@@ -810,7 +874,7 @@ window.__ModuleLoader__.load({
810
874
  */
811
875
  function scanAndEnhanceDecisions() {
812
876
  if (typeof document === "undefined") return;
813
- if (isScrollActive()) return;
877
+ if (isInteractionLocked()) return;
814
878
  const markdownContainers = document.querySelectorAll("div[class*=\"_markdown_\"], [data-markdown-variant]");
815
879
  for (let i = 0; i < markdownContainers.length; i++) {
816
880
  const container = markdownContainers[i];
@@ -879,11 +943,11 @@ window.__ModuleLoader__.load({
879
943
  let decisionScanTimer = null;
880
944
  let unbindDecisionSettle = null;
881
945
  function scheduleDecisionScan() {
882
- if (isScrollActive()) return;
946
+ if (isInteractionLocked()) return;
883
947
  if (decisionScanTimer !== null) return;
884
948
  decisionScanTimer = setTimeout(() => {
885
949
  decisionScanTimer = null;
886
- if (isScrollActive()) return;
950
+ if (isInteractionLocked()) return;
887
951
  scanAndEnhanceDecisions();
888
952
  }, 100);
889
953
  }
@@ -1018,8 +1082,10 @@ window.__ModuleLoader__.load({
1018
1082
  position: fixed;
1019
1083
  inset: 0;
1020
1084
  z-index: 999999;
1021
- background: rgba(0, 0, 0, 0.85);
1022
- backdrop-filter: blur(8px);
1085
+ background: rgba(0, 0, 0, 0.9);
1086
+ /* No full-viewport backdrop blur: same flicker class as the console overlay.
1087
+ The image viewer is already near-opaque; a solid alpha is stable. */
1088
+ overscroll-behavior: contain;
1023
1089
  display: flex;
1024
1090
  flex-direction: column;
1025
1091
  user-select: none;
@@ -1288,7 +1354,7 @@ window.__ModuleLoader__.load({
1288
1354
  */
1289
1355
  function scanAndEnhanceImages() {
1290
1356
  if (typeof document === "undefined") return;
1291
- if (isScrollActive()) return;
1357
+ if (isInteractionLocked()) return;
1292
1358
  const origin = getOrigin();
1293
1359
  if (origin && !origin.includes("127.0.0.1") && !origin.includes("localhost")) {
1294
1360
  const images = document.querySelectorAll("img[src*=\"/plugins/agy-link/media\"]");
@@ -1381,11 +1447,11 @@ window.__ModuleLoader__.load({
1381
1447
  let imageScanTimer = null;
1382
1448
  let unbindImageSettle = null;
1383
1449
  function scheduleImageScan() {
1384
- if (isScrollActive()) return;
1450
+ if (isInteractionLocked()) return;
1385
1451
  if (imageScanTimer !== null) return;
1386
1452
  imageScanTimer = setTimeout(() => {
1387
1453
  imageScanTimer = null;
1388
- if (isScrollActive()) return;
1454
+ if (isInteractionLocked()) return;
1389
1455
  scanAndEnhanceImages();
1390
1456
  }, 100);
1391
1457
  }
@@ -3006,6 +3072,12 @@ div[data-agy-mermaid-svg] svg {
3006
3072
  min-width: 280px;
3007
3073
  height: auto;
3008
3074
  }
3075
+ .agy-mermaid-body {
3076
+ display: flex;
3077
+ justify-content: center;
3078
+ width: 100%;
3079
+ overflow-x: auto;
3080
+ }
3009
3081
  .agy-mermaid-toolbar {
3010
3082
  display: flex;
3011
3083
  justify-content: flex-end;
@@ -3261,6 +3333,12 @@ body > [id^="iagy-mmd"] .error-icon {
3261
3333
  return l;
3262
3334
  }).join("\n");
3263
3335
  }
3336
+ function isInvalidOrCollapsedSvg(svg) {
3337
+ if (!svg || svg.trim() === "") return true;
3338
+ if (svg.includes("class=\"error-icon\"") || svg.includes("class=\"error-text\"") || svg.includes("aria-roledescription=\"error\"")) return true;
3339
+ if (svg.includes("viewBox=\"0 0 0 0\"") || svg.includes("viewBox=\"-8 -8 16 16\"") || svg.includes("max-width: 16px") || svg.includes("max-width: 0px")) return true;
3340
+ return false;
3341
+ }
3264
3342
  async function renderInto(block, source, lang = "") {
3265
3343
  block.querySelector("[data-agy-mermaid-svg]")?.remove();
3266
3344
  const mount = document.createElement("div");
@@ -3293,8 +3371,8 @@ body > [id^="iagy-mmd"] .error-icon {
3293
3371
  else throw parseErr;
3294
3372
  }
3295
3373
  if (!parseValidated && typeof api.parse !== "function") targetSource = repairMermaidSource(targetSource);
3296
- const { svg } = await api.render(id, targetSource, mount);
3297
- if (!svg || svg.trim() === "" || svg.includes("class=\"error-icon\"") || svg.includes("class=\"error-text\"") || svg.includes("aria-roledescription=\"error\"")) throw new Error("Mermaid returned an empty or error diagram");
3374
+ const { svg } = await api.render(id, targetSource);
3375
+ if (isInvalidOrCollapsedSvg(svg)) throw new Error("Mermaid returned an empty or invalid diagram");
3298
3376
  mount.innerHTML = "";
3299
3377
  const toolbar = document.createElement("div");
3300
3378
  toolbar.className = "agy-mermaid-toolbar";
@@ -3335,17 +3413,17 @@ body > [id^="iagy-mmd"] .error-icon {
3335
3413
  }
3336
3414
  let scanTimer$1 = null;
3337
3415
  function scheduleScan$1() {
3338
- if (isScrollActive()) return;
3416
+ if (isInteractionLocked()) return;
3339
3417
  if (scanTimer$1 !== null) return;
3340
3418
  scanTimer$1 = setTimeout(() => {
3341
3419
  scanTimer$1 = null;
3342
- if (isScrollActive()) return;
3420
+ if (isInteractionLocked()) return;
3343
3421
  scan();
3344
3422
  }, 150);
3345
3423
  }
3346
3424
  function scan() {
3347
3425
  if (typeof document === "undefined") return;
3348
- if (isScrollActive()) return;
3426
+ if (isInteractionLocked()) return;
3349
3427
  cleanupTempElements();
3350
3428
  const blocks = document.querySelectorAll(".md-code-block");
3351
3429
  for (let i = 0; i < blocks.length; i++) {
@@ -3509,7 +3587,7 @@ div[data-variant="think"] [class*="thinkBody"] {
3509
3587
  passive: true
3510
3588
  });
3511
3589
  }
3512
- if (isScrollActive()) return;
3590
+ if (isInteractionLocked()) return;
3513
3591
  if (body.clientHeight > 8) {
3514
3592
  handledTurns.add(turnKey);
3515
3593
  root.dataset.agyExpanded = "true";
@@ -3523,7 +3601,7 @@ div[data-variant="think"] [class*="thinkBody"] {
3523
3601
  }
3524
3602
  function scanThinkingRows() {
3525
3603
  if (typeof document === "undefined") return;
3526
- if (isScrollActive()) return;
3604
+ if (isInteractionLocked()) return;
3527
3605
  const rows = document.querySelectorAll("div[data-variant=\"think\"]");
3528
3606
  for (let i = 0; i < rows.length; i++) {
3529
3607
  const row = rows[i];
@@ -3532,11 +3610,11 @@ div[data-variant="think"] [class*="thinkBody"] {
3532
3610
  }
3533
3611
  let scanTimer = null;
3534
3612
  function scheduleScan() {
3535
- if (isScrollActive()) return;
3613
+ if (isInteractionLocked()) return;
3536
3614
  if (scanTimer !== null) return;
3537
3615
  scanTimer = setTimeout(() => {
3538
3616
  scanTimer = null;
3539
- if (isScrollActive()) return;
3617
+ if (isInteractionLocked()) return;
3540
3618
  scanThinkingRows();
3541
3619
  }, 150);
3542
3620
  }
@@ -3653,6 +3731,7 @@ div[data-variant="think"] [class*="thinkBody"] {
3653
3731
  }
3654
3732
  const statusListeners = /* @__PURE__ */ new Set();
3655
3733
  let statusPollTimer = null;
3734
+ let statusPollInterval = 3e3;
3656
3735
  let statusFetchSeq = 0;
3657
3736
  function isStatusEqual(a, b) {
3658
3737
  if (a === b) return true;
@@ -3680,16 +3759,25 @@ div[data-variant="think"] [class*="thinkBody"] {
3680
3759
  }
3681
3760
  }
3682
3761
  function ensureStatusPolling() {
3683
- if (statusPollTimer !== null) return;
3762
+ if (statusPollTimer !== null) {
3763
+ if (statusPollInterval !== 3e3) {
3764
+ clearInterval(statusPollTimer);
3765
+ statusPollTimer = null;
3766
+ } else return;
3767
+ }
3768
+ statusPollInterval = 3e3;
3684
3769
  statusPollTick();
3685
3770
  statusPollTimer = setInterval(() => {
3686
3771
  statusPollTick();
3687
3772
  }, 3e3);
3688
3773
  }
3689
3774
  function stopStatusPollingIfIdle() {
3690
- if (statusListeners.size === 0 && statusPollTimer !== null) {
3775
+ if (statusListeners.size === 0 && statusPollTimer !== null && statusPollInterval === 3e3) {
3691
3776
  clearInterval(statusPollTimer);
3692
- statusPollTimer = null;
3777
+ statusPollInterval = 15e3;
3778
+ statusPollTimer = setInterval(() => {
3779
+ statusPollTick();
3780
+ }, 15e3);
3693
3781
  }
3694
3782
  }
3695
3783
  /**
@@ -3710,7 +3798,7 @@ div[data-variant="think"] [class*="thinkBody"] {
3710
3798
  }, []);
3711
3799
  const refresh = async () => {
3712
3800
  const st = await getStatus();
3713
- if (st !== null) notifyStatus(st);
3801
+ if (st !== null && !isStatusEqual(statusCache, st)) notifyStatus(st);
3714
3802
  };
3715
3803
  return {
3716
3804
  status,
@@ -3722,7 +3810,10 @@ div[data-variant="think"] [class*="thinkBody"] {
3722
3810
  open: false,
3723
3811
  listeners: /* @__PURE__ */ new Set(),
3724
3812
  setOpen(v) {
3813
+ if (agyModalStore.open === v) return;
3725
3814
  agyModalStore.open = v;
3815
+ setOverlaySuspended(v);
3816
+ if (typeof document !== "undefined" && document.documentElement) document.documentElement.classList.toggle("agy-modal-open", v);
3726
3817
  agyModalStore.listeners.forEach((fn) => fn());
3727
3818
  },
3728
3819
  subscribe(fn) {
@@ -3734,8 +3825,9 @@ div[data-variant="think"] [class*="thinkBody"] {
3734
3825
  };
3735
3826
  /**
3736
3827
  * Global modal host manager: ensures exactly one mounted session header
3737
- * hosts the modal dialog portal reactively. If the primary header unmounts,
3738
- * the next available header is immediately notified and seamlessly takes over.
3828
+ * hosts the modal dialog portal reactively. The most recently focused or mounted
3829
+ * header is prioritized immediately to prevent host handoff flicker when opened.
3830
+ * If the primary header unmounts, the next available header is seamlessly notified.
3739
3831
  */
3740
3832
  const modalHostManager = {
3741
3833
  listeners: /* @__PURE__ */ new Map(),
@@ -3749,7 +3841,9 @@ div[data-variant="think"] [class*="thinkBody"] {
3749
3841
  if (modalHostManager.activeId === id) {
3750
3842
  const next = modalHostManager.listeners.keys().next().value ?? null;
3751
3843
  modalHostManager.activeId = next;
3752
- if (next) modalHostManager.listeners.get(next)?.(true);
3844
+ if (next) try {
3845
+ modalHostManager.listeners.get(next)?.(true);
3846
+ } catch {}
3753
3847
  }
3754
3848
  };
3755
3849
  },
@@ -3757,8 +3851,12 @@ div[data-variant="think"] [class*="thinkBody"] {
3757
3851
  if (modalHostManager.activeId !== id) {
3758
3852
  const prev = modalHostManager.activeId;
3759
3853
  modalHostManager.activeId = id;
3760
- if (prev) modalHostManager.listeners.get(prev)?.(false);
3761
- modalHostManager.listeners.get(id)?.(true);
3854
+ if (prev && prev !== id) try {
3855
+ modalHostManager.listeners.get(prev)?.(false);
3856
+ } catch {}
3857
+ try {
3858
+ modalHostManager.listeners.get(id)?.(true);
3859
+ } catch {}
3762
3860
  }
3763
3861
  }
3764
3862
  };
@@ -4435,7 +4533,7 @@ body.dark,
4435
4533
  }
4436
4534
 
4437
4535
  .agy-btn {
4438
- transition: all 0.15s ease;
4536
+ transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, filter 0.15s ease, transform 0.15s ease;
4439
4537
  user-select: none;
4440
4538
  }
4441
4539
  .agy-btn:hover:not(:disabled) {
@@ -4455,12 +4553,17 @@ body.dark,
4455
4553
  .agy-shimmer-track {
4456
4554
  position: relative;
4457
4555
  overflow: hidden;
4556
+ contain: paint;
4557
+ transform: translateZ(0);
4558
+ isolation: isolate;
4458
4559
  }
4459
4560
  .agy-shimmer-track::after {
4460
4561
  content: '';
4461
4562
  position: absolute;
4462
4563
  top: 0; left: 0; right: 0; bottom: 0;
4463
4564
  background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.22) 50%, transparent 100%);
4565
+ transform: translateX(-100%) translateZ(0);
4566
+ will-change: transform;
4464
4567
  animation: agy-shimmer 1.8s infinite ease-in-out;
4465
4568
  pointer-events: none;
4466
4569
  }
@@ -4469,13 +4572,24 @@ body.dark,
4469
4572
  inset: 0;
4470
4573
  z-index: 9999;
4471
4574
  background: var(--agy-modal-backdrop);
4472
- backdrop-filter: blur(6px);
4575
+ /* NO full-screen backdrop blur here. Filtering the whole viewport forces
4576
+ Chromium/Electron to re-sample the page every frame while anything behind
4577
+ it moves (scroll, streaming, animations) — the recurring "打开控制台后滚动
4578
+ 闪屏" root cause. The rgba dim below is stable and flicker-free. */
4579
+ overscroll-behavior: contain;
4473
4580
  display: flex;
4474
4581
  align-items: center;
4475
4582
  justify-content: center;
4476
4583
  padding: 16px;
4477
4584
  box-sizing: border-box;
4478
4585
  }
4586
+ /* While the console is open, freeze the conversation scroller behind it so
4587
+ nothing moves/reflows under the fixed overlay. scrollbar-gutter:stable on the
4588
+ host scroller keeps the width from jumping when overflow flips to hidden. */
4589
+ html.agy-modal-open [data-conversation-scroll] {
4590
+ overflow: hidden !important;
4591
+ overscroll-behavior: contain;
4592
+ }
4479
4593
  .agy-modal-panel {
4480
4594
  position: relative;
4481
4595
  width: 100%;
@@ -4490,6 +4604,8 @@ body.dark,
4490
4604
  overflow: hidden;
4491
4605
  color: var(--agy-text-primary);
4492
4606
  font-family: var(--agy-font-family);
4607
+ transform: translateZ(0);
4608
+ contain: content;
4493
4609
  }
4494
4610
  .agy-submodel-row {
4495
4611
  display: flex;
@@ -4747,6 +4863,15 @@ body.dark,
4747
4863
  };
4748
4864
  function apply(ctx) {
4749
4865
  injectGlobalStyles();
4866
+ ctx.effect(() => {
4867
+ ensureStatusPolling();
4868
+ return () => {
4869
+ if (statusPollTimer !== null) {
4870
+ clearInterval(statusPollTimer);
4871
+ statusPollTimer = null;
4872
+ }
4873
+ };
4874
+ }, "agy-link: status polling");
4750
4875
  const locale = ctx.locale;
4751
4876
  ctx.effect(() => {
4752
4877
  const disposePtBR = locale.addLanguage({
@@ -4976,15 +5101,41 @@ body.dark,
4976
5101
  };
4977
5102
  const refreshQuota = async (id) => {
4978
5103
  setLoadingAction(id ? `refresh:${id}` : "refresh:all");
4979
- const res = await postJson("/plugins/agy-link/pool/refresh-quota", { id });
4980
- if (res === null || res?.ok === false) {
5104
+ try {
5105
+ const res = await postJson("/plugins/agy-link/pool/refresh-quota", { id });
5106
+ if (res === null || res?.ok === false) {
5107
+ actionFailed();
5108
+ return;
5109
+ }
5110
+ await refresh();
5111
+ showToast(t("quota.refreshed"), "success");
5112
+ } finally {
5113
+ setLoadingAction(null);
5114
+ }
5115
+ };
5116
+ const warmupAccount = async (id) => {
5117
+ setLoadingAction(id ? `warmup:${id}` : "warmup:all");
5118
+ try {
5119
+ const res = await postJson("/plugins/agy-link/pool/warmup", {
5120
+ id,
5121
+ force: true,
5122
+ ...id ? {} : { all: true }
5123
+ });
5124
+ if (res === null || res?.ok === false) {
5125
+ actionFailed();
5126
+ return;
5127
+ }
5128
+ await refresh();
5129
+ const results = Array.isArray(res.results) ? res.results : [];
5130
+ const skippedCount = results.filter((r) => r.skipped).length;
5131
+ const successCount = results.filter((r) => r.success && !r.skipped).length;
5132
+ const failedCount = results.length - skippedCount - successCount;
5133
+ if (successCount > 0) showToast(id ? t("warmup.success") : t("warmup.allSuccess"), "success");
5134
+ else if (skippedCount > 0 && failedCount === 0) showToast(t("warmup.skipped"), "info");
5135
+ else actionFailed();
5136
+ } finally {
4981
5137
  setLoadingAction(null);
4982
- actionFailed();
4983
- return;
4984
5138
  }
4985
- await refresh();
4986
- setLoadingAction(null);
4987
- showToast(t("quota.refreshed"), "success");
4988
5139
  };
4989
5140
  const saveProxy = async (id) => {
4990
5141
  setLoadingAction(`proxy:${id}`);
@@ -5091,7 +5242,7 @@ body.dark,
5091
5242
  "aria-label": t("aria.closeToast")
5092
5243
  }, uiIcon("x", 12)));
5093
5244
  };
5094
- const renderQuotaBar = (label, familyKey, acc) => renderFamilyQuotaBar(t, label, familyKey, acc, loadingAction === "refresh-quota" || loadingAction === `refresh:${acc.id}`);
5245
+ const renderQuotaBar = (label, familyKey, acc) => renderFamilyQuotaBar(t, label, familyKey, acc, loadingAction === "refresh:all" || loadingAction === `refresh:${acc.id}`);
5095
5246
  const authoritativeActiveId = pool?.activeAccountId ?? pool?.lastActiveAccountId ?? pool?.activeAccountIds?.google ?? pool?.primaryAccountId;
5096
5247
  const activeAccountId = optimisticActiveId ?? authoritativeActiveId;
5097
5248
  const renderedAccountCards = accounts.map((acc) => {
@@ -5311,12 +5462,28 @@ body.dark,
5311
5462
  ...S.btnSm,
5312
5463
  gap: "3px",
5313
5464
  fontSize: "11px",
5314
- padding: "2px 6px"
5465
+ padding: "2px 6px",
5466
+ minWidth: "58px",
5467
+ justifyContent: "center"
5315
5468
  },
5316
5469
  title: t("account.syncTitle"),
5317
5470
  disabled: accountActionBusy(acc, "refresh"),
5318
5471
  onClick: () => void refreshQuota(acc.id)
5319
- }, loadingAction === `refresh:${acc.id}` ? [renderSpinner(), t("account.syncing")] : [uiIcon("refresh", 10), ` ${t("account.sync")}`]), acc.enabled && !isInUse ? h("button", {
5472
+ }, loadingAction === `refresh:${acc.id}` ? [renderSpinner(), t("account.syncing")] : [uiIcon("refresh", 10), ` ${t("account.sync")}`]), acc.enabled && !acc.authRequired ? h("button", {
5473
+ type: "button",
5474
+ className: "agy-btn",
5475
+ style: {
5476
+ ...S.btnSm,
5477
+ gap: "3px",
5478
+ fontSize: "11px",
5479
+ padding: "2px 6px",
5480
+ minWidth: "58px",
5481
+ justifyContent: "center"
5482
+ },
5483
+ title: t("account.warmupTitle"),
5484
+ disabled: accountActionBusy(acc, "warmup"),
5485
+ onClick: () => void warmupAccount(acc.id)
5486
+ }, loadingAction === `warmup:${acc.id}` ? [renderSpinner(), t("account.warmingUp")] : [uiIcon("flame", 10, "#f97316"), ` ${t("account.warmup")}`]) : null, acc.enabled && !isInUse ? h("button", {
5320
5487
  type: "button",
5321
5488
  className: "agy-btn",
5322
5489
  style: {
@@ -5618,6 +5785,15 @@ body.dark,
5618
5785
  },
5619
5786
  onClick: () => setAddingAccount(true)
5620
5787
  }, [uiIcon("plus", 12, "#ffffff"), ` ${t("account.add")}`]) : null, h("button", {
5788
+ type: "button",
5789
+ className: "agy-btn",
5790
+ style: {
5791
+ ...S.btn,
5792
+ gap: "4px"
5793
+ },
5794
+ disabled: isBusy,
5795
+ onClick: () => void warmupAccount()
5796
+ }, loadingAction === "warmup:all" ? [renderSpinner(), t("header.warmingUp")] : [uiIcon("flame", 12, "#f97316"), ` ${t("header.warmup")}`]), h("button", {
5621
5797
  type: "button",
5622
5798
  className: "agy-btn",
5623
5799
  style: {
@@ -5714,6 +5890,22 @@ body.dark,
5714
5890
  style: pool?.mode === "round-robin" ? S.segBtnActive : S.segBtn,
5715
5891
  onClick: () => void setMode("round-robin")
5716
5892
  }, t("pool.roundRobin")))), h("div", { style: {
5893
+ display: "flex",
5894
+ alignItems: "center",
5895
+ justifyContent: "space-between"
5896
+ } }, h("div", { style: {
5897
+ display: "flex",
5898
+ flexDirection: "column"
5899
+ } }, h("span", { style: {
5900
+ color: "var(--agy-text-primary)",
5901
+ fontWeight: 600,
5902
+ fontSize: "12.5px"
5903
+ } }, t("config.autoWarmup")), h("span", { style: S.muted }, t("config.autoWarmupDesc"))), h("button", {
5904
+ type: "button",
5905
+ style: status?.autoWarmup !== false ? S.segBtnActive : S.segBtn,
5906
+ disabled: loadingAction === "config:autoWarmup",
5907
+ onClick: () => void setCfg("autoWarmup", status?.autoWarmup === false)
5908
+ }, status?.autoWarmup !== false ? t("account.disable") : t("account.enable"))), h("div", { style: {
5717
5909
  display: "flex",
5718
5910
  flexDirection: "column",
5719
5911
  gap: "6px"
@@ -5800,6 +5992,7 @@ body.dark,
5800
5992
  onClick: () => agyModalStore.setOpen(false)
5801
5993
  }, uiIcon("x", 14, "var(--agy-text-btn)"))), h("div", { style: {
5802
5994
  overflowY: "auto",
5995
+ overscrollBehavior: "contain",
5803
5996
  padding: "18px",
5804
5997
  flex: "1",
5805
5998
  background: "var(--agy-bg-panel)"
@@ -5813,7 +6006,13 @@ body.dark,
5813
6006
  const hostIdRef = useRef(null);
5814
6007
  if (hostIdRef.current === null) hostIdRef.current = "host_" + Math.random().toString(36).slice(2);
5815
6008
  const hostId = hostIdRef.current;
5816
- const [isPrimaryHost, setIsPrimaryHost] = useState(false);
6009
+ const [isPrimaryHost, setIsPrimaryHost] = useState(() => {
6010
+ if (modalHostManager.activeId === null) {
6011
+ modalHostManager.activeId = hostId;
6012
+ return true;
6013
+ }
6014
+ return modalHostManager.activeId === hostId;
6015
+ });
5817
6016
  useEffect(() => {
5818
6017
  return modalHostManager.register(hostId, setIsPrimaryHost);
5819
6018
  }, [hostId]);
@@ -5866,7 +6065,9 @@ body.dark,
5866
6065
  display: "inline-flex",
5867
6066
  alignItems: "center",
5868
6067
  gap: "6px",
5869
- boxShadow: "var(--agy-shadow-card)"
6068
+ boxShadow: "var(--agy-shadow-card)",
6069
+ flexShrink: 0,
6070
+ whiteSpace: "nowrap"
5870
6071
  }
5871
6072
  }, h("span", { style: {
5872
6073
  display: "inline-block",
@@ -5917,7 +6118,12 @@ body.dark,
5917
6118
  });
5918
6119
  ctx.effect(() => {
5919
6120
  ensureScrollListener();
5920
- return disposeScrollListener;
6121
+ return () => {
6122
+ disposeScrollListener();
6123
+ agyModalStore.open = false;
6124
+ setOverlaySuspended(false);
6125
+ if (typeof document !== "undefined" && document.documentElement) document.documentElement.classList.remove("agy-modal-open");
6126
+ };
5921
6127
  });
5922
6128
  installAgyToolView(ctx);
5923
6129
  ctx.effect(() => installAutoExpandReasoning());