dsh-agy-link 0.4.67 → 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/CHANGELOG.md +248 -0
- package/README.md +4 -0
- package/dist/bridge.mjs +6 -2
- package/dist/client.js +454 -74
- package/dist/index.js +1589 -186
- package/docs/KNOWN-GAPS.md +66 -8
- package/package.json +2 -1
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,9 +632,94 @@ 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
|
|
648
|
+
//#region src/client/scroll-guard.ts
|
|
649
|
+
let isScrolling = false;
|
|
650
|
+
let scrollTimer = null;
|
|
651
|
+
let listenerAttached = false;
|
|
652
|
+
const settleListeners = /* @__PURE__ */ new Set();
|
|
653
|
+
let overlaySuspended = false;
|
|
654
|
+
function onScrollActivity() {
|
|
655
|
+
isScrolling = true;
|
|
656
|
+
if (scrollTimer !== null) clearTimeout(scrollTimer);
|
|
657
|
+
scrollTimer = setTimeout(() => {
|
|
658
|
+
isScrolling = false;
|
|
659
|
+
scrollTimer = null;
|
|
660
|
+
for (const fn of settleListeners) try {
|
|
661
|
+
fn();
|
|
662
|
+
} catch {}
|
|
663
|
+
}, 150);
|
|
664
|
+
}
|
|
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;
|
|
688
|
+
}
|
|
689
|
+
/** Register a callback to run once scrolling has settled. Returns an unregister function. */
|
|
690
|
+
function onScrollSettle(fn) {
|
|
691
|
+
ensureScrollListener();
|
|
692
|
+
settleListeners.add(fn);
|
|
693
|
+
return () => {
|
|
694
|
+
settleListeners.delete(fn);
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
function ensureScrollListener() {
|
|
698
|
+
if (typeof window === "undefined" || typeof window.addEventListener !== "function" || listenerAttached) return;
|
|
699
|
+
listenerAttached = true;
|
|
700
|
+
window.addEventListener("scroll", onScrollActivity, {
|
|
701
|
+
passive: true,
|
|
702
|
+
capture: true
|
|
703
|
+
});
|
|
704
|
+
window.addEventListener("wheel", onScrollActivity, {
|
|
705
|
+
passive: true,
|
|
706
|
+
capture: true
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
function disposeScrollListener() {
|
|
710
|
+
if (typeof window === "undefined" || typeof window.removeEventListener !== "function" || !listenerAttached) return;
|
|
711
|
+
window.removeEventListener("scroll", onScrollActivity, true);
|
|
712
|
+
window.removeEventListener("wheel", onScrollActivity, true);
|
|
713
|
+
listenerAttached = false;
|
|
714
|
+
if (scrollTimer !== null) {
|
|
715
|
+
clearTimeout(scrollTimer);
|
|
716
|
+
scrollTimer = null;
|
|
717
|
+
}
|
|
718
|
+
settleListeners.clear();
|
|
719
|
+
isScrolling = false;
|
|
720
|
+
overlaySuspended = false;
|
|
721
|
+
}
|
|
722
|
+
//#endregion
|
|
607
723
|
//#region src/client/decisions.ts
|
|
608
724
|
const DECISION_CSS = `
|
|
609
725
|
.agy-decision-container {
|
|
@@ -740,14 +856,34 @@ window.__ModuleLoader__.load({
|
|
|
740
856
|
return options;
|
|
741
857
|
}
|
|
742
858
|
/**
|
|
743
|
-
* Scan DOM for decision prompts and mount interactive choice chips
|
|
859
|
+
* Scan DOM for decision prompts and mount interactive choice chips.
|
|
860
|
+
*
|
|
861
|
+
* IDEMPOTENCE (incident 2026-10-23 — "scroll back and forth = insane flashing"):
|
|
862
|
+
* DSH renders these containers inside a TanStack-Virtual list with dynamic
|
|
863
|
+
* measurement (`measureElement` + `anchorTo:"start"`). Injecting a toolbar
|
|
864
|
+
* changes the measured item height, which makes the virtualizer rewrite
|
|
865
|
+
* scrollTop; the resulting re-render rebuilds the markdown/heading nodes
|
|
866
|
+
* (dropping the per-HEADING guard) and this scanner injected the toolbar
|
|
867
|
+
* again — growth on every scroll pass, i.e. a flashing flywheel.
|
|
868
|
+
*
|
|
869
|
+
* Two independent guards now make injection strictly once-per-container:
|
|
870
|
+
* 1. `data-agy-decisions-scanned` on the container, written on EVERY pass
|
|
871
|
+
* (it used to be read here but never assigned anywhere in the codebase),
|
|
872
|
+
* 2. "a toolbar already exists in this container" — survives React
|
|
873
|
+
* rebuilding the heading nodes during a virtualizer remount.
|
|
744
874
|
*/
|
|
745
875
|
function scanAndEnhanceDecisions() {
|
|
746
876
|
if (typeof document === "undefined") return;
|
|
877
|
+
if (isInteractionLocked()) return;
|
|
747
878
|
const markdownContainers = document.querySelectorAll("div[class*=\"_markdown_\"], [data-markdown-variant]");
|
|
748
879
|
for (let i = 0; i < markdownContainers.length; i++) {
|
|
749
880
|
const container = markdownContainers[i];
|
|
750
881
|
if (!container || container.dataset.agyDecisionsScanned === "true") continue;
|
|
882
|
+
if (container.querySelector(".agy-decision-container")) {
|
|
883
|
+
container.dataset.agyDecisionsScanned = "true";
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
container.dataset.agyDecisionsScanned = "true";
|
|
751
887
|
const headings = container.querySelectorAll("h2, h3, h4, p > strong");
|
|
752
888
|
for (let j = 0; j < headings.length; j++) {
|
|
753
889
|
const heading = headings[j];
|
|
@@ -804,12 +940,26 @@ window.__ModuleLoader__.load({
|
|
|
804
940
|
}
|
|
805
941
|
}
|
|
806
942
|
let decisionChipsDispose = null;
|
|
943
|
+
let decisionScanTimer = null;
|
|
944
|
+
let unbindDecisionSettle = null;
|
|
945
|
+
function scheduleDecisionScan() {
|
|
946
|
+
if (isInteractionLocked()) return;
|
|
947
|
+
if (decisionScanTimer !== null) return;
|
|
948
|
+
decisionScanTimer = setTimeout(() => {
|
|
949
|
+
decisionScanTimer = null;
|
|
950
|
+
if (isInteractionLocked()) return;
|
|
951
|
+
scanAndEnhanceDecisions();
|
|
952
|
+
}, 100);
|
|
953
|
+
}
|
|
807
954
|
function installDecisionChips() {
|
|
808
955
|
if (typeof window === "undefined" || typeof document === "undefined") return () => {};
|
|
809
956
|
decisionChipsDispose?.();
|
|
810
957
|
injectStyles$3();
|
|
811
958
|
const observer = new MutationObserver(() => {
|
|
812
|
-
|
|
959
|
+
scheduleDecisionScan();
|
|
960
|
+
});
|
|
961
|
+
unbindDecisionSettle = onScrollSettle(() => {
|
|
962
|
+
scheduleDecisionScan();
|
|
813
963
|
});
|
|
814
964
|
const setup = () => {
|
|
815
965
|
if (document.body) {
|
|
@@ -824,6 +974,14 @@ window.__ModuleLoader__.load({
|
|
|
824
974
|
else setup();
|
|
825
975
|
const dispose = () => {
|
|
826
976
|
observer.disconnect();
|
|
977
|
+
if (unbindDecisionSettle !== null) {
|
|
978
|
+
unbindDecisionSettle();
|
|
979
|
+
unbindDecisionSettle = null;
|
|
980
|
+
}
|
|
981
|
+
if (decisionScanTimer !== null) {
|
|
982
|
+
clearTimeout(decisionScanTimer);
|
|
983
|
+
decisionScanTimer = null;
|
|
984
|
+
}
|
|
827
985
|
window.removeEventListener("DOMContentLoaded", setup);
|
|
828
986
|
if (decisionChipsDispose === dispose) decisionChipsDispose = null;
|
|
829
987
|
};
|
|
@@ -924,8 +1082,10 @@ window.__ModuleLoader__.load({
|
|
|
924
1082
|
position: fixed;
|
|
925
1083
|
inset: 0;
|
|
926
1084
|
z-index: 999999;
|
|
927
|
-
background: rgba(0, 0, 0, 0.
|
|
928
|
-
backdrop
|
|
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;
|
|
929
1089
|
display: flex;
|
|
930
1090
|
flex-direction: column;
|
|
931
1091
|
user-select: none;
|
|
@@ -1194,6 +1354,7 @@ window.__ModuleLoader__.load({
|
|
|
1194
1354
|
*/
|
|
1195
1355
|
function scanAndEnhanceImages() {
|
|
1196
1356
|
if (typeof document === "undefined") return;
|
|
1357
|
+
if (isInteractionLocked()) return;
|
|
1197
1358
|
const origin = getOrigin();
|
|
1198
1359
|
if (origin && !origin.includes("127.0.0.1") && !origin.includes("localhost")) {
|
|
1199
1360
|
const images = document.querySelectorAll("img[src*=\"/plugins/agy-link/media\"]");
|
|
@@ -1283,12 +1444,26 @@ window.__ModuleLoader__.load({
|
|
|
1283
1444
|
}
|
|
1284
1445
|
}
|
|
1285
1446
|
let imageEnhancerDispose = null;
|
|
1447
|
+
let imageScanTimer = null;
|
|
1448
|
+
let unbindImageSettle = null;
|
|
1449
|
+
function scheduleImageScan() {
|
|
1450
|
+
if (isInteractionLocked()) return;
|
|
1451
|
+
if (imageScanTimer !== null) return;
|
|
1452
|
+
imageScanTimer = setTimeout(() => {
|
|
1453
|
+
imageScanTimer = null;
|
|
1454
|
+
if (isInteractionLocked()) return;
|
|
1455
|
+
scanAndEnhanceImages();
|
|
1456
|
+
}, 100);
|
|
1457
|
+
}
|
|
1286
1458
|
function installImageEnhancer() {
|
|
1287
1459
|
if (typeof window === "undefined" || typeof document === "undefined") return () => {};
|
|
1288
1460
|
imageEnhancerDispose?.();
|
|
1289
1461
|
injectStyles$2();
|
|
1290
1462
|
const observer = new MutationObserver(() => {
|
|
1291
|
-
|
|
1463
|
+
scheduleImageScan();
|
|
1464
|
+
});
|
|
1465
|
+
unbindImageSettle = onScrollSettle(() => {
|
|
1466
|
+
scheduleImageScan();
|
|
1292
1467
|
});
|
|
1293
1468
|
const setup = () => {
|
|
1294
1469
|
if (document.body) {
|
|
@@ -1303,6 +1478,14 @@ window.__ModuleLoader__.load({
|
|
|
1303
1478
|
else setup();
|
|
1304
1479
|
const dispose = () => {
|
|
1305
1480
|
observer.disconnect();
|
|
1481
|
+
if (unbindImageSettle !== null) {
|
|
1482
|
+
unbindImageSettle();
|
|
1483
|
+
unbindImageSettle = null;
|
|
1484
|
+
}
|
|
1485
|
+
if (imageScanTimer !== null) {
|
|
1486
|
+
clearTimeout(imageScanTimer);
|
|
1487
|
+
imageScanTimer = null;
|
|
1488
|
+
}
|
|
1306
1489
|
window.removeEventListener("DOMContentLoaded", setup);
|
|
1307
1490
|
if (imageEnhancerDispose === dispose) imageEnhancerDispose = null;
|
|
1308
1491
|
};
|
|
@@ -1316,12 +1499,23 @@ window.__ModuleLoader__.load({
|
|
|
1316
1499
|
if (mod === void 0 || mod === null) throw new Error("dsh-agy-link toolview requires react at render time (browser host)");
|
|
1317
1500
|
return mod;
|
|
1318
1501
|
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
const
|
|
1324
|
-
|
|
1502
|
+
/** Cache user-expanded tool cards across TanStack Virtualizer remount cycles */
|
|
1503
|
+
const expandedToolBlockKeys = /* @__PURE__ */ new Set();
|
|
1504
|
+
function useToggle(initial = false, key) {
|
|
1505
|
+
const R2 = react();
|
|
1506
|
+
const init = key ? expandedToolBlockKeys.has(key) : initial;
|
|
1507
|
+
const [value, setValue] = R2.useState(init);
|
|
1508
|
+
const toggle = () => {
|
|
1509
|
+
setValue((prev) => {
|
|
1510
|
+
const next = !prev;
|
|
1511
|
+
if (key) {
|
|
1512
|
+
if (next) expandedToolBlockKeys.add(key);
|
|
1513
|
+
else expandedToolBlockKeys.delete(key);
|
|
1514
|
+
}
|
|
1515
|
+
return next;
|
|
1516
|
+
});
|
|
1517
|
+
};
|
|
1518
|
+
return [value, toggle];
|
|
1325
1519
|
}
|
|
1326
1520
|
function hx(type, props, ...children) {
|
|
1327
1521
|
return react().createElement(type, props, ...children);
|
|
@@ -2096,12 +2290,12 @@ window.__ModuleLoader__.load({
|
|
|
2096
2290
|
}
|
|
2097
2291
|
}
|
|
2098
2292
|
function AgyMirrorToolView(props) {
|
|
2099
|
-
const [open, setOpen] = useToggle(false);
|
|
2100
2293
|
const h = hx;
|
|
2101
|
-
const { block, cwd, home, openFile, inspect, t } = props;
|
|
2294
|
+
const { block, cwd, home, openFile, inspect, t, callId } = props;
|
|
2102
2295
|
const settle = isSettled(block);
|
|
2103
2296
|
const err = isError(block);
|
|
2104
2297
|
const model = mirrorCardModel(block, cwd, home);
|
|
2298
|
+
const [open, setOpen] = useToggle(false, callId ? `call:${callId}` : `${model.kind}:${model.tool ?? ""}:${model.title}:${model.path ?? model.command ?? ""}`);
|
|
2105
2299
|
const state = !settle ? "run" : err ? "err" : "ok";
|
|
2106
2300
|
const title = displayTitle(model, t);
|
|
2107
2301
|
const summary = displaySummary(model, cwd, home);
|
|
@@ -2217,10 +2411,10 @@ window.__ModuleLoader__.load({
|
|
|
2217
2411
|
* friends) so an ordinary Code Mode call reads exactly like a native tool row.
|
|
2218
2412
|
*/
|
|
2219
2413
|
function AgyCodeToolView(props) {
|
|
2220
|
-
const [open, setOpen] = useToggle(false);
|
|
2221
2414
|
const h = hx;
|
|
2222
|
-
const { block, inspect, t } = props ?? {};
|
|
2415
|
+
const { block, inspect, t, callId } = props ?? {};
|
|
2223
2416
|
const model = runCodeModel(block);
|
|
2417
|
+
const [open, setOpen] = useToggle(false, callId ? `call:${callId}` : `code:${model.title}`);
|
|
2224
2418
|
const settle = block !== void 0 && isSettled(block);
|
|
2225
2419
|
const err = block !== void 0 && isError(block);
|
|
2226
2420
|
const state = !settle ? "run" : err ? "err" : "ok";
|
|
@@ -2878,6 +3072,12 @@ div[data-agy-mermaid-svg] svg {
|
|
|
2878
3072
|
min-width: 280px;
|
|
2879
3073
|
height: auto;
|
|
2880
3074
|
}
|
|
3075
|
+
.agy-mermaid-body {
|
|
3076
|
+
display: flex;
|
|
3077
|
+
justify-content: center;
|
|
3078
|
+
width: 100%;
|
|
3079
|
+
overflow-x: auto;
|
|
3080
|
+
}
|
|
2881
3081
|
.agy-mermaid-toolbar {
|
|
2882
3082
|
display: flex;
|
|
2883
3083
|
justify-content: flex-end;
|
|
@@ -3133,6 +3333,12 @@ body > [id^="iagy-mmd"] .error-icon {
|
|
|
3133
3333
|
return l;
|
|
3134
3334
|
}).join("\n");
|
|
3135
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
|
+
}
|
|
3136
3342
|
async function renderInto(block, source, lang = "") {
|
|
3137
3343
|
block.querySelector("[data-agy-mermaid-svg]")?.remove();
|
|
3138
3344
|
const mount = document.createElement("div");
|
|
@@ -3165,8 +3371,8 @@ body > [id^="iagy-mmd"] .error-icon {
|
|
|
3165
3371
|
else throw parseErr;
|
|
3166
3372
|
}
|
|
3167
3373
|
if (!parseValidated && typeof api.parse !== "function") targetSource = repairMermaidSource(targetSource);
|
|
3168
|
-
const { svg } = await api.render(id, targetSource
|
|
3169
|
-
if (
|
|
3374
|
+
const { svg } = await api.render(id, targetSource);
|
|
3375
|
+
if (isInvalidOrCollapsedSvg(svg)) throw new Error("Mermaid returned an empty or invalid diagram");
|
|
3170
3376
|
mount.innerHTML = "";
|
|
3171
3377
|
const toolbar = document.createElement("div");
|
|
3172
3378
|
toolbar.className = "agy-mermaid-toolbar";
|
|
@@ -3207,14 +3413,17 @@ body > [id^="iagy-mmd"] .error-icon {
|
|
|
3207
3413
|
}
|
|
3208
3414
|
let scanTimer$1 = null;
|
|
3209
3415
|
function scheduleScan$1() {
|
|
3416
|
+
if (isInteractionLocked()) return;
|
|
3210
3417
|
if (scanTimer$1 !== null) return;
|
|
3211
3418
|
scanTimer$1 = setTimeout(() => {
|
|
3212
3419
|
scanTimer$1 = null;
|
|
3420
|
+
if (isInteractionLocked()) return;
|
|
3213
3421
|
scan();
|
|
3214
3422
|
}, 150);
|
|
3215
3423
|
}
|
|
3216
3424
|
function scan() {
|
|
3217
3425
|
if (typeof document === "undefined") return;
|
|
3426
|
+
if (isInteractionLocked()) return;
|
|
3218
3427
|
cleanupTempElements();
|
|
3219
3428
|
const blocks = document.querySelectorAll(".md-code-block");
|
|
3220
3429
|
for (let i = 0; i < blocks.length; i++) {
|
|
@@ -3232,6 +3441,7 @@ body > [id^="iagy-mmd"] .error-icon {
|
|
|
3232
3441
|
}
|
|
3233
3442
|
let activeObserver$1 = null;
|
|
3234
3443
|
let domContentLoadedHandler$1 = null;
|
|
3444
|
+
let unbindMermaidSettle = null;
|
|
3235
3445
|
/**
|
|
3236
3446
|
* Install the mermaid overlay. Returns a disposer that disconnects the
|
|
3237
3447
|
* MutationObserver and cancels pending setup — wrap it in ctx.effect so a
|
|
@@ -3246,6 +3456,9 @@ body > [id^="iagy-mmd"] .error-icon {
|
|
|
3246
3456
|
scheduleScan$1();
|
|
3247
3457
|
});
|
|
3248
3458
|
activeObserver$1 = observer;
|
|
3459
|
+
unbindMermaidSettle = onScrollSettle(() => {
|
|
3460
|
+
scheduleScan$1();
|
|
3461
|
+
});
|
|
3249
3462
|
const setup = () => {
|
|
3250
3463
|
if (document.body) {
|
|
3251
3464
|
cleanupTempElements();
|
|
@@ -3268,6 +3481,10 @@ body > [id^="iagy-mmd"] .error-icon {
|
|
|
3268
3481
|
activeObserver$1.disconnect();
|
|
3269
3482
|
activeObserver$1 = null;
|
|
3270
3483
|
}
|
|
3484
|
+
if (unbindMermaidSettle !== null) {
|
|
3485
|
+
unbindMermaidSettle();
|
|
3486
|
+
unbindMermaidSettle = null;
|
|
3487
|
+
}
|
|
3271
3488
|
if (domContentLoadedHandler$1 !== null) {
|
|
3272
3489
|
window.removeEventListener("DOMContentLoaded", domContentLoadedHandler$1);
|
|
3273
3490
|
domContentLoadedHandler$1 = null;
|
|
@@ -3334,42 +3551,57 @@ div[data-variant="think"] [class*="thinkBody"] {
|
|
|
3334
3551
|
}
|
|
3335
3552
|
}
|
|
3336
3553
|
/**
|
|
3554
|
+
* Persistent cache of handled thinking turns (keyed by turn fingerprint).
|
|
3555
|
+
* Survived across TanStack Virtualizer unmount/remount cycles.
|
|
3556
|
+
*/
|
|
3557
|
+
const handledTurns = /* @__PURE__ */ new Set();
|
|
3558
|
+
function getTurnKey(root, text) {
|
|
3559
|
+
const parent = root.closest("[data-message-id], [data-item-index], [data-index], [data-role=\"message-content\"]");
|
|
3560
|
+
return `${parent?.getAttribute("data-message-id") ?? parent?.getAttribute("data-index") ?? parent?.getAttribute("data-item-index") ?? ""}:${text.slice(0, 100)}`;
|
|
3561
|
+
}
|
|
3562
|
+
/**
|
|
3337
3563
|
* Try to expand a thinking row once when its body contains real prose
|
|
3338
3564
|
* (not a bare [agy thinking turn · N tokens] chip). Uses the host's own
|
|
3339
3565
|
* disclosure control when present — never mutates thinkBody children.
|
|
3340
|
-
*
|
|
3341
|
-
* A row the user manually COLLAPSED is recorded in a WeakSet keyed by the
|
|
3342
|
-
* row element; React keeps the same element identity across its own
|
|
3343
|
-
* re-renders, so the scanner stops re-expanding it (the old data attribute
|
|
3344
|
-
* survived only until React rebuilt the node).
|
|
3345
3566
|
*/
|
|
3346
|
-
const userCollapsed = typeof WeakSet !== "undefined" ? /* @__PURE__ */ new WeakSet() : null;
|
|
3347
3567
|
function tryExpandThinkRow(root) {
|
|
3348
3568
|
if (root.dataset.agyExpanded === "true") return;
|
|
3349
|
-
if (userCollapsed?.has(root) === true) return;
|
|
3350
3569
|
const body = root.querySelector("[class*=\"thinkBody\"]");
|
|
3351
3570
|
if (!body) return;
|
|
3352
3571
|
const text = (body.textContent ?? "").trim();
|
|
3353
3572
|
if (text === "") return;
|
|
3354
3573
|
if (/^\[agy(?: thinking turn)?(?: · \d+ (?:thinking )?tokens)?\]\s*$/.test(text)) return;
|
|
3355
|
-
const
|
|
3356
|
-
if (
|
|
3574
|
+
const turnKey = getTurnKey(root, text);
|
|
3575
|
+
if (handledTurns.has(turnKey)) {
|
|
3357
3576
|
root.dataset.agyExpanded = "true";
|
|
3358
3577
|
return;
|
|
3359
3578
|
}
|
|
3360
3579
|
const toggle = root.querySelector("[data-disclosure-row]") ?? root.querySelector("[class*=\"disclosure\"]") ?? root.querySelector("button");
|
|
3361
|
-
if (toggle)
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
}
|
|
3367
|
-
|
|
3368
|
-
|
|
3580
|
+
if (!toggle) return;
|
|
3581
|
+
if (!toggle.dataset.agyClickBound) {
|
|
3582
|
+
toggle.dataset.agyClickBound = "true";
|
|
3583
|
+
toggle.addEventListener("click", () => {
|
|
3584
|
+
handledTurns.add(turnKey);
|
|
3585
|
+
}, {
|
|
3586
|
+
capture: true,
|
|
3587
|
+
passive: true
|
|
3588
|
+
});
|
|
3369
3589
|
}
|
|
3590
|
+
if (isInteractionLocked()) return;
|
|
3591
|
+
if (body.clientHeight > 8) {
|
|
3592
|
+
handledTurns.add(turnKey);
|
|
3593
|
+
root.dataset.agyExpanded = "true";
|
|
3594
|
+
return;
|
|
3595
|
+
}
|
|
3596
|
+
handledTurns.add(turnKey);
|
|
3597
|
+
root.dataset.agyExpanded = "true";
|
|
3598
|
+
try {
|
|
3599
|
+
toggle.click();
|
|
3600
|
+
} catch {}
|
|
3370
3601
|
}
|
|
3371
3602
|
function scanThinkingRows() {
|
|
3372
3603
|
if (typeof document === "undefined") return;
|
|
3604
|
+
if (isInteractionLocked()) return;
|
|
3373
3605
|
const rows = document.querySelectorAll("div[data-variant=\"think\"]");
|
|
3374
3606
|
for (let i = 0; i < rows.length; i++) {
|
|
3375
3607
|
const row = rows[i];
|
|
@@ -3378,14 +3610,17 @@ div[data-variant="think"] [class*="thinkBody"] {
|
|
|
3378
3610
|
}
|
|
3379
3611
|
let scanTimer = null;
|
|
3380
3612
|
function scheduleScan() {
|
|
3613
|
+
if (isInteractionLocked()) return;
|
|
3381
3614
|
if (scanTimer !== null) return;
|
|
3382
3615
|
scanTimer = setTimeout(() => {
|
|
3383
3616
|
scanTimer = null;
|
|
3617
|
+
if (isInteractionLocked()) return;
|
|
3384
3618
|
scanThinkingRows();
|
|
3385
|
-
},
|
|
3619
|
+
}, 150);
|
|
3386
3620
|
}
|
|
3387
3621
|
let activeObserver = null;
|
|
3388
3622
|
let domContentLoadedHandler = null;
|
|
3623
|
+
let unbindSettle = null;
|
|
3389
3624
|
/**
|
|
3390
3625
|
* Install the auto-expand reasoning overlay. Returns a disposer that
|
|
3391
3626
|
* disconnects the MutationObserver — wrap it in ctx.effect so a plugin
|
|
@@ -3399,6 +3634,9 @@ div[data-variant="think"] [class*="thinkBody"] {
|
|
|
3399
3634
|
scheduleScan();
|
|
3400
3635
|
});
|
|
3401
3636
|
activeObserver = observer;
|
|
3637
|
+
unbindSettle = onScrollSettle(() => {
|
|
3638
|
+
scheduleScan();
|
|
3639
|
+
});
|
|
3402
3640
|
const setupObserver = () => {
|
|
3403
3641
|
if (document.body) {
|
|
3404
3642
|
observer.observe(document.body, {
|
|
@@ -3420,6 +3658,10 @@ div[data-variant="think"] [class*="thinkBody"] {
|
|
|
3420
3658
|
activeObserver.disconnect();
|
|
3421
3659
|
activeObserver = null;
|
|
3422
3660
|
}
|
|
3661
|
+
if (unbindSettle !== null) {
|
|
3662
|
+
unbindSettle();
|
|
3663
|
+
unbindSettle = null;
|
|
3664
|
+
}
|
|
3423
3665
|
if (domContentLoadedHandler !== null) {
|
|
3424
3666
|
window.removeEventListener("DOMContentLoaded", domContentLoadedHandler);
|
|
3425
3667
|
domContentLoadedHandler = null;
|
|
@@ -3489,6 +3731,7 @@ div[data-variant="think"] [class*="thinkBody"] {
|
|
|
3489
3731
|
}
|
|
3490
3732
|
const statusListeners = /* @__PURE__ */ new Set();
|
|
3491
3733
|
let statusPollTimer = null;
|
|
3734
|
+
let statusPollInterval = 3e3;
|
|
3492
3735
|
let statusFetchSeq = 0;
|
|
3493
3736
|
function isStatusEqual(a, b) {
|
|
3494
3737
|
if (a === b) return true;
|
|
@@ -3516,16 +3759,25 @@ div[data-variant="think"] [class*="thinkBody"] {
|
|
|
3516
3759
|
}
|
|
3517
3760
|
}
|
|
3518
3761
|
function ensureStatusPolling() {
|
|
3519
|
-
if (statusPollTimer !== null)
|
|
3762
|
+
if (statusPollTimer !== null) {
|
|
3763
|
+
if (statusPollInterval !== 3e3) {
|
|
3764
|
+
clearInterval(statusPollTimer);
|
|
3765
|
+
statusPollTimer = null;
|
|
3766
|
+
} else return;
|
|
3767
|
+
}
|
|
3768
|
+
statusPollInterval = 3e3;
|
|
3520
3769
|
statusPollTick();
|
|
3521
3770
|
statusPollTimer = setInterval(() => {
|
|
3522
3771
|
statusPollTick();
|
|
3523
3772
|
}, 3e3);
|
|
3524
3773
|
}
|
|
3525
3774
|
function stopStatusPollingIfIdle() {
|
|
3526
|
-
if (statusListeners.size === 0 && statusPollTimer !== null) {
|
|
3775
|
+
if (statusListeners.size === 0 && statusPollTimer !== null && statusPollInterval === 3e3) {
|
|
3527
3776
|
clearInterval(statusPollTimer);
|
|
3528
|
-
|
|
3777
|
+
statusPollInterval = 15e3;
|
|
3778
|
+
statusPollTimer = setInterval(() => {
|
|
3779
|
+
statusPollTick();
|
|
3780
|
+
}, 15e3);
|
|
3529
3781
|
}
|
|
3530
3782
|
}
|
|
3531
3783
|
/**
|
|
@@ -3546,7 +3798,7 @@ div[data-variant="think"] [class*="thinkBody"] {
|
|
|
3546
3798
|
}, []);
|
|
3547
3799
|
const refresh = async () => {
|
|
3548
3800
|
const st = await getStatus();
|
|
3549
|
-
if (st !== null) notifyStatus(st);
|
|
3801
|
+
if (st !== null && !isStatusEqual(statusCache, st)) notifyStatus(st);
|
|
3550
3802
|
};
|
|
3551
3803
|
return {
|
|
3552
3804
|
status,
|
|
@@ -3558,7 +3810,10 @@ div[data-variant="think"] [class*="thinkBody"] {
|
|
|
3558
3810
|
open: false,
|
|
3559
3811
|
listeners: /* @__PURE__ */ new Set(),
|
|
3560
3812
|
setOpen(v) {
|
|
3813
|
+
if (agyModalStore.open === v) return;
|
|
3561
3814
|
agyModalStore.open = v;
|
|
3815
|
+
setOverlaySuspended(v);
|
|
3816
|
+
if (typeof document !== "undefined" && document.documentElement) document.documentElement.classList.toggle("agy-modal-open", v);
|
|
3562
3817
|
agyModalStore.listeners.forEach((fn) => fn());
|
|
3563
3818
|
},
|
|
3564
3819
|
subscribe(fn) {
|
|
@@ -3570,8 +3825,9 @@ div[data-variant="think"] [class*="thinkBody"] {
|
|
|
3570
3825
|
};
|
|
3571
3826
|
/**
|
|
3572
3827
|
* Global modal host manager: ensures exactly one mounted session header
|
|
3573
|
-
* hosts the modal dialog portal reactively.
|
|
3574
|
-
*
|
|
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.
|
|
3575
3831
|
*/
|
|
3576
3832
|
const modalHostManager = {
|
|
3577
3833
|
listeners: /* @__PURE__ */ new Map(),
|
|
@@ -3585,7 +3841,9 @@ div[data-variant="think"] [class*="thinkBody"] {
|
|
|
3585
3841
|
if (modalHostManager.activeId === id) {
|
|
3586
3842
|
const next = modalHostManager.listeners.keys().next().value ?? null;
|
|
3587
3843
|
modalHostManager.activeId = next;
|
|
3588
|
-
if (next)
|
|
3844
|
+
if (next) try {
|
|
3845
|
+
modalHostManager.listeners.get(next)?.(true);
|
|
3846
|
+
} catch {}
|
|
3589
3847
|
}
|
|
3590
3848
|
};
|
|
3591
3849
|
},
|
|
@@ -3593,8 +3851,12 @@ div[data-variant="think"] [class*="thinkBody"] {
|
|
|
3593
3851
|
if (modalHostManager.activeId !== id) {
|
|
3594
3852
|
const prev = modalHostManager.activeId;
|
|
3595
3853
|
modalHostManager.activeId = id;
|
|
3596
|
-
if (prev
|
|
3597
|
-
|
|
3854
|
+
if (prev && prev !== id) try {
|
|
3855
|
+
modalHostManager.listeners.get(prev)?.(false);
|
|
3856
|
+
} catch {}
|
|
3857
|
+
try {
|
|
3858
|
+
modalHostManager.listeners.get(id)?.(true);
|
|
3859
|
+
} catch {}
|
|
3598
3860
|
}
|
|
3599
3861
|
}
|
|
3600
3862
|
};
|
|
@@ -3859,8 +4121,12 @@ div[data-variant="think"] [class*="thinkBody"] {
|
|
|
3859
4121
|
scheduled = false;
|
|
3860
4122
|
if (disposed) return;
|
|
3861
4123
|
const wanted = resolveLabel();
|
|
3862
|
-
for (const row of Array.from(document.querySelectorAll(NAV_ROW_SELECTOR)))
|
|
3863
|
-
|
|
4124
|
+
for (const row of Array.from(document.querySelectorAll(NAV_ROW_SELECTOR))) {
|
|
4125
|
+
const shouldMark = isOwnNavRow(row.textContent, wanted);
|
|
4126
|
+
if (shouldMark === row.hasAttribute(NAV_ICON_MARKER)) continue;
|
|
4127
|
+
if (shouldMark) row.setAttribute(NAV_ICON_MARKER, "");
|
|
4128
|
+
else row.removeAttribute(NAV_ICON_MARKER);
|
|
4129
|
+
}
|
|
3864
4130
|
};
|
|
3865
4131
|
const schedule = () => {
|
|
3866
4132
|
if (scheduled || disposed) return;
|
|
@@ -3872,7 +4138,7 @@ div[data-variant="think"] [class*="thinkBody"] {
|
|
|
3872
4138
|
observer.observe(document.body, {
|
|
3873
4139
|
childList: true,
|
|
3874
4140
|
subtree: true,
|
|
3875
|
-
|
|
4141
|
+
attributeFilter: [NAV_ICON_MARKER]
|
|
3876
4142
|
});
|
|
3877
4143
|
return () => {
|
|
3878
4144
|
disposed = true;
|
|
@@ -4267,7 +4533,7 @@ body.dark,
|
|
|
4267
4533
|
}
|
|
4268
4534
|
|
|
4269
4535
|
.agy-btn {
|
|
4270
|
-
transition:
|
|
4536
|
+
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, filter 0.15s ease, transform 0.15s ease;
|
|
4271
4537
|
user-select: none;
|
|
4272
4538
|
}
|
|
4273
4539
|
.agy-btn:hover:not(:disabled) {
|
|
@@ -4287,12 +4553,17 @@ body.dark,
|
|
|
4287
4553
|
.agy-shimmer-track {
|
|
4288
4554
|
position: relative;
|
|
4289
4555
|
overflow: hidden;
|
|
4556
|
+
contain: paint;
|
|
4557
|
+
transform: translateZ(0);
|
|
4558
|
+
isolation: isolate;
|
|
4290
4559
|
}
|
|
4291
4560
|
.agy-shimmer-track::after {
|
|
4292
4561
|
content: '';
|
|
4293
4562
|
position: absolute;
|
|
4294
4563
|
top: 0; left: 0; right: 0; bottom: 0;
|
|
4295
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;
|
|
4296
4567
|
animation: agy-shimmer 1.8s infinite ease-in-out;
|
|
4297
4568
|
pointer-events: none;
|
|
4298
4569
|
}
|
|
@@ -4301,13 +4572,24 @@ body.dark,
|
|
|
4301
4572
|
inset: 0;
|
|
4302
4573
|
z-index: 9999;
|
|
4303
4574
|
background: var(--agy-modal-backdrop);
|
|
4304
|
-
|
|
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;
|
|
4305
4580
|
display: flex;
|
|
4306
4581
|
align-items: center;
|
|
4307
4582
|
justify-content: center;
|
|
4308
4583
|
padding: 16px;
|
|
4309
4584
|
box-sizing: border-box;
|
|
4310
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
|
+
}
|
|
4311
4593
|
.agy-modal-panel {
|
|
4312
4594
|
position: relative;
|
|
4313
4595
|
width: 100%;
|
|
@@ -4322,6 +4604,8 @@ body.dark,
|
|
|
4322
4604
|
overflow: hidden;
|
|
4323
4605
|
color: var(--agy-text-primary);
|
|
4324
4606
|
font-family: var(--agy-font-family);
|
|
4607
|
+
transform: translateZ(0);
|
|
4608
|
+
contain: content;
|
|
4325
4609
|
}
|
|
4326
4610
|
.agy-submodel-row {
|
|
4327
4611
|
display: flex;
|
|
@@ -4579,6 +4863,15 @@ body.dark,
|
|
|
4579
4863
|
};
|
|
4580
4864
|
function apply(ctx) {
|
|
4581
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");
|
|
4582
4875
|
const locale = ctx.locale;
|
|
4583
4876
|
ctx.effect(() => {
|
|
4584
4877
|
const disposePtBR = locale.addLanguage({
|
|
@@ -4808,15 +5101,41 @@ body.dark,
|
|
|
4808
5101
|
};
|
|
4809
5102
|
const refreshQuota = async (id) => {
|
|
4810
5103
|
setLoadingAction(id ? `refresh:${id}` : "refresh:all");
|
|
4811
|
-
|
|
4812
|
-
|
|
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 {
|
|
4813
5137
|
setLoadingAction(null);
|
|
4814
|
-
actionFailed();
|
|
4815
|
-
return;
|
|
4816
5138
|
}
|
|
4817
|
-
await refresh();
|
|
4818
|
-
setLoadingAction(null);
|
|
4819
|
-
showToast(t("quota.refreshed"), "success");
|
|
4820
5139
|
};
|
|
4821
5140
|
const saveProxy = async (id) => {
|
|
4822
5141
|
setLoadingAction(`proxy:${id}`);
|
|
@@ -4871,6 +5190,9 @@ body.dark,
|
|
|
4871
5190
|
const accounts = pool?.accounts ?? [];
|
|
4872
5191
|
const isAuthed = authPhase === "ok" || accounts.length > 0;
|
|
4873
5192
|
const isBusy = loadingAction !== null;
|
|
5193
|
+
/** Busy for a per-account action; also blocks while a global action runs. */
|
|
5194
|
+
const accountActionBusy = (acc, action) => loadingAction === `${action}:${acc.id}`;
|
|
5195
|
+
isBusy && loadingAction?.includes(":");
|
|
4874
5196
|
const renderSpinner = () => h("span", { className: "agy-spinner" });
|
|
4875
5197
|
const renderToastBanner = () => {
|
|
4876
5198
|
if (!toast) return null;
|
|
@@ -4920,8 +5242,7 @@ body.dark,
|
|
|
4920
5242
|
"aria-label": t("aria.closeToast")
|
|
4921
5243
|
}, uiIcon("x", 12)));
|
|
4922
5244
|
};
|
|
4923
|
-
const
|
|
4924
|
-
const renderQuotaBar = (label, familyKey, acc) => renderFamilyQuotaBar(t, label, familyKey, acc, isRefreshingQuota);
|
|
5245
|
+
const renderQuotaBar = (label, familyKey, acc) => renderFamilyQuotaBar(t, label, familyKey, acc, loadingAction === "refresh:all" || loadingAction === `refresh:${acc.id}`);
|
|
4925
5246
|
const authoritativeActiveId = pool?.activeAccountId ?? pool?.lastActiveAccountId ?? pool?.activeAccountIds?.google ?? pool?.primaryAccountId;
|
|
4926
5247
|
const activeAccountId = optimisticActiveId ?? authoritativeActiveId;
|
|
4927
5248
|
const renderedAccountCards = accounts.map((acc) => {
|
|
@@ -5132,7 +5453,7 @@ body.dark,
|
|
|
5132
5453
|
padding: "2px 6px"
|
|
5133
5454
|
},
|
|
5134
5455
|
title: acc.enabled ? t("account.disableTitle") : t("account.enableTitle"),
|
|
5135
|
-
disabled:
|
|
5456
|
+
disabled: accountActionBusy(acc, "enable"),
|
|
5136
5457
|
onClick: () => void toggleAccountEnabled(acc)
|
|
5137
5458
|
}, loadingAction === `enable:${acc.id}` ? [renderSpinner(), acc.enabled ? t("account.disable") : t("account.enable")] : [uiIcon("power", 10, acc.enabled ? "var(--agy-text-secondary)" : "#ffffff"), ` ${acc.enabled ? t("account.disable") : t("account.enable")}`]), h("button", {
|
|
5138
5459
|
type: "button",
|
|
@@ -5141,12 +5462,28 @@ body.dark,
|
|
|
5141
5462
|
...S.btnSm,
|
|
5142
5463
|
gap: "3px",
|
|
5143
5464
|
fontSize: "11px",
|
|
5144
|
-
padding: "2px 6px"
|
|
5465
|
+
padding: "2px 6px",
|
|
5466
|
+
minWidth: "58px",
|
|
5467
|
+
justifyContent: "center"
|
|
5145
5468
|
},
|
|
5146
5469
|
title: t("account.syncTitle"),
|
|
5147
|
-
disabled:
|
|
5470
|
+
disabled: accountActionBusy(acc, "refresh"),
|
|
5148
5471
|
onClick: () => void refreshQuota(acc.id)
|
|
5149
|
-
}, loadingAction === `refresh:${acc.id}` ? [renderSpinner(), t("account.syncing")] : [uiIcon("refresh", 10), ` ${t("account.sync")}`]), acc.enabled && !
|
|
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", {
|
|
5150
5487
|
type: "button",
|
|
5151
5488
|
className: "agy-btn",
|
|
5152
5489
|
style: {
|
|
@@ -5156,7 +5493,7 @@ body.dark,
|
|
|
5156
5493
|
padding: "2px 6px"
|
|
5157
5494
|
},
|
|
5158
5495
|
title: t("account.setActive"),
|
|
5159
|
-
disabled:
|
|
5496
|
+
disabled: accountActionBusy(acc, "active"),
|
|
5160
5497
|
onClick: () => void setActiveAccount(acc.id)
|
|
5161
5498
|
}, loadingAction === `active:${acc.id}` ? [renderSpinner(), t("account.settingActive")] : [uiIcon("zap", 10), ` ${t("account.setActive")}`]) : null, h("button", {
|
|
5162
5499
|
type: "button",
|
|
@@ -5172,7 +5509,7 @@ body.dark,
|
|
|
5172
5509
|
fontSize: "11px",
|
|
5173
5510
|
padding: "2px 6px"
|
|
5174
5511
|
},
|
|
5175
|
-
disabled:
|
|
5512
|
+
disabled: accountActionBusy(acc, "proxy"),
|
|
5176
5513
|
onClick: () => setEditingProxyId(isEditingProxy ? null : acc.id)
|
|
5177
5514
|
}, [uiIcon("globe", 10), ` ${t("proxy.button")}`]), accounts.length > 1 ? h("button", {
|
|
5178
5515
|
type: "button",
|
|
@@ -5184,7 +5521,7 @@ body.dark,
|
|
|
5184
5521
|
},
|
|
5185
5522
|
title: t("account.removeTitle"),
|
|
5186
5523
|
"aria-label": t("account.removeTitle"),
|
|
5187
|
-
disabled:
|
|
5524
|
+
disabled: accountActionBusy(acc, "remove"),
|
|
5188
5525
|
onClick: () => void removeAccount(acc.id, displayAlias)
|
|
5189
5526
|
}, loadingAction === `remove:${acc.id}` ? renderSpinner() : uiIcon("trash", 11, "var(--agy-danger-text)")) : null)), h("div", {
|
|
5190
5527
|
key: "agy-body",
|
|
@@ -5273,7 +5610,7 @@ body.dark,
|
|
|
5273
5610
|
background: "var(--agy-warn-btn-bg)",
|
|
5274
5611
|
gap: "4px"
|
|
5275
5612
|
},
|
|
5276
|
-
disabled:
|
|
5613
|
+
disabled: accountActionBusy(acc, "clearCooldown"),
|
|
5277
5614
|
onClick: () => void clearCooldown(acc.id)
|
|
5278
5615
|
}, loadingAction === `clearCooldown:${acc.id}` ? [renderSpinner(), ""] : [uiIcon("zap", 11, "var(--agy-warn-btn-text)"), ` ${t("account.clearCooldown")}`])) : null, isEditingProxy ? h("div", {
|
|
5279
5616
|
key: "proxy-body",
|
|
@@ -5300,7 +5637,7 @@ body.dark,
|
|
|
5300
5637
|
type: "button",
|
|
5301
5638
|
className: "agy-btn",
|
|
5302
5639
|
style: S.btnPrimary,
|
|
5303
|
-
disabled:
|
|
5640
|
+
disabled: accountActionBusy(acc, "proxy"),
|
|
5304
5641
|
onClick: () => void saveProxy(acc.id)
|
|
5305
5642
|
}, loadingAction === `proxy:${acc.id}` ? [renderSpinner(), t("proxy.save")] : t("proxy.save")), h("button", {
|
|
5306
5643
|
type: "button",
|
|
@@ -5448,6 +5785,15 @@ body.dark,
|
|
|
5448
5785
|
},
|
|
5449
5786
|
onClick: () => setAddingAccount(true)
|
|
5450
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", {
|
|
5451
5797
|
type: "button",
|
|
5452
5798
|
className: "agy-btn",
|
|
5453
5799
|
style: {
|
|
@@ -5544,6 +5890,22 @@ body.dark,
|
|
|
5544
5890
|
style: pool?.mode === "round-robin" ? S.segBtnActive : S.segBtn,
|
|
5545
5891
|
onClick: () => void setMode("round-robin")
|
|
5546
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: {
|
|
5547
5909
|
display: "flex",
|
|
5548
5910
|
flexDirection: "column",
|
|
5549
5911
|
gap: "6px"
|
|
@@ -5630,6 +5992,7 @@ body.dark,
|
|
|
5630
5992
|
onClick: () => agyModalStore.setOpen(false)
|
|
5631
5993
|
}, uiIcon("x", 14, "var(--agy-text-btn)"))), h("div", { style: {
|
|
5632
5994
|
overflowY: "auto",
|
|
5995
|
+
overscrollBehavior: "contain",
|
|
5633
5996
|
padding: "18px",
|
|
5634
5997
|
flex: "1",
|
|
5635
5998
|
background: "var(--agy-bg-panel)"
|
|
@@ -5643,7 +6006,13 @@ body.dark,
|
|
|
5643
6006
|
const hostIdRef = useRef(null);
|
|
5644
6007
|
if (hostIdRef.current === null) hostIdRef.current = "host_" + Math.random().toString(36).slice(2);
|
|
5645
6008
|
const hostId = hostIdRef.current;
|
|
5646
|
-
const [isPrimaryHost, setIsPrimaryHost] = useState(
|
|
6009
|
+
const [isPrimaryHost, setIsPrimaryHost] = useState(() => {
|
|
6010
|
+
if (modalHostManager.activeId === null) {
|
|
6011
|
+
modalHostManager.activeId = hostId;
|
|
6012
|
+
return true;
|
|
6013
|
+
}
|
|
6014
|
+
return modalHostManager.activeId === hostId;
|
|
6015
|
+
});
|
|
5647
6016
|
useEffect(() => {
|
|
5648
6017
|
return modalHostManager.register(hostId, setIsPrimaryHost);
|
|
5649
6018
|
}, [hostId]);
|
|
@@ -5696,7 +6065,9 @@ body.dark,
|
|
|
5696
6065
|
display: "inline-flex",
|
|
5697
6066
|
alignItems: "center",
|
|
5698
6067
|
gap: "6px",
|
|
5699
|
-
boxShadow: "var(--agy-shadow-card)"
|
|
6068
|
+
boxShadow: "var(--agy-shadow-card)",
|
|
6069
|
+
flexShrink: 0,
|
|
6070
|
+
whiteSpace: "nowrap"
|
|
5700
6071
|
}
|
|
5701
6072
|
}, h("span", { style: {
|
|
5702
6073
|
display: "inline-block",
|
|
@@ -5745,6 +6116,15 @@ body.dark,
|
|
|
5745
6116
|
return h(AgySettingsSection, { isModal: false });
|
|
5746
6117
|
});
|
|
5747
6118
|
});
|
|
6119
|
+
ctx.effect(() => {
|
|
6120
|
+
ensureScrollListener();
|
|
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
|
+
};
|
|
6127
|
+
});
|
|
5748
6128
|
installAgyToolView(ctx);
|
|
5749
6129
|
ctx.effect(() => installAutoExpandReasoning());
|
|
5750
6130
|
ctx.effect(() => installMermaidRendering());
|