@qzsy/dsh-bridge 3.3.1 → 3.4.1
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 +25 -0
- package/README.md +1 -0
- package/client/client.js +339 -5
- package/client/mobile.js +353 -5
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# 更新日志
|
|
2
2
|
|
|
3
|
+
## [v3.4.1] - 2026-11-02
|
|
4
|
+
|
|
5
|
+
### 🐛 修复
|
|
6
|
+
|
|
7
|
+
- **模型 sheet 识别收紧**:模型菜单被改造成 sheet 后立刻清空触发链,并优先用 `data-model-id` 等模型标记定位;关闭模型面板后紧接着打开账号等其它菜单,不会误套用底部 sheet。
|
|
8
|
+
- **无搜索框宿主兜底**:触发链窗口从 6 秒收紧到 2.5 秒,只对「模型触发后出现、且行数 > 2」的菜单生效。
|
|
9
|
+
|
|
10
|
+
### 🧪 验证
|
|
11
|
+
|
|
12
|
+
- 实测:第一层「模型 / 推理等级」popover 不改造;二级模型菜单 sheet 化;关闭后再开账号菜单,`data-qzsy-model-sheet` 数量为 0,无残留遮罩。
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## [v3.4.0] - 2026-11-02
|
|
16
|
+
|
|
17
|
+
### ✨ 新增
|
|
18
|
+
|
|
19
|
+
- **移动端模型切换器底部 sheet**:宿主的模型二级菜单从桌面 popover 改成全宽底部 sheet——圆角、抓手、遮罩、滚动区、`safe-area` 底部留白,切换模型更接近原生。
|
|
20
|
+
- **模型 sheet 手势**:点遮罩关闭;在顶部抓手上向下拖关闭(跟手 + 松手吸附);桌面端 popover 完全不变。
|
|
21
|
+
- **兼容有/无搜索框的宿主**:优先用「搜索模型」输入框定位;没有搜索框的移动版本按模型触发链 + 菜单行数兜底,不依赖宿主 CSS Modules 哈希。
|
|
22
|
+
|
|
23
|
+
### 🧪 验证
|
|
24
|
+
|
|
25
|
+
- 真实 DSH + 无头 Chrome:模型二级菜单变成 390px 全宽、`z=1200`、18px 顶部圆角、遮罩 `z=1190`;底部安全区叠加后内边距 48px;点遮罩 / 顶部下拉均能关闭,7/7 通过。
|
|
26
|
+
|
|
27
|
+
|
|
3
28
|
## [v3.3.1] - 2026-11-02
|
|
4
29
|
|
|
5
30
|
### 🐛 修复
|
package/README.md
CHANGED
|
@@ -48,6 +48,7 @@ dsh plugin --profile <name> add @qzsy/dsh-bridge
|
|
|
48
48
|
|
|
49
49
|
- 内容区占满屏宽,左上角「菜单」按钮打开会话 / 工作区 / 插件入口;
|
|
50
50
|
- 支持手势:左缘向右滑打开、抽屉内向左滑关闭,拖动时跟手,松手吸附;
|
|
51
|
+
- 模型切换器在手机上改为底部 sheet:全宽、抓手、遮罩,支持下拉/点遮罩关闭;
|
|
51
52
|
- 全面屏自动避开安全区:刘海 / 状态栏 / 底部 home indicator 不遮挡内容;
|
|
52
53
|
- 点遮罩、点会话行、按 Esc 或转回桌面宽度都会自动收起;
|
|
53
54
|
- 桌面端保持 DSH 原生布局,不受影响。
|
package/client/client.js
CHANGED
|
@@ -236,7 +236,8 @@ var MOBILE_DRAWER_CSS = `
|
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
.qzsy-mobile-header,
|
|
239
|
-
.qzsy-mobile-backdrop
|
|
239
|
+
.qzsy-mobile-backdrop,
|
|
240
|
+
.qzsy-model-sheet-backdrop {
|
|
240
241
|
display: none;
|
|
241
242
|
}
|
|
242
243
|
|
|
@@ -366,6 +367,109 @@ var MOBILE_DRAWER_CSS = `
|
|
|
366
367
|
opacity: 0;
|
|
367
368
|
pointer-events: none;
|
|
368
369
|
}
|
|
370
|
+
|
|
371
|
+
/* 5. \u6A21\u578B\u5207\u6362\u5668\uFF1A\u628A\u5BBF\u4E3B\u684C\u9762 popover \u6539\u6210\u79FB\u52A8\u7AEF\u5E95\u90E8 sheet */
|
|
372
|
+
@keyframes qzsy-model-sheet-in {
|
|
373
|
+
from { transform: translateY(100%); opacity: 0.6; }
|
|
374
|
+
to { transform: translateY(0); opacity: 1; }
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.qzsy-model-sheet-backdrop {
|
|
378
|
+
display: block;
|
|
379
|
+
position: fixed;
|
|
380
|
+
inset: 0;
|
|
381
|
+
z-index: 1190;
|
|
382
|
+
background: rgba(0, 0, 0, 0.4);
|
|
383
|
+
opacity: 0;
|
|
384
|
+
pointer-events: none;
|
|
385
|
+
transition: opacity 240ms ease;
|
|
386
|
+
touch-action: none;
|
|
387
|
+
-webkit-tap-highlight-color: transparent;
|
|
388
|
+
}
|
|
389
|
+
body.qzsy-model-sheet-open .qzsy-model-sheet-backdrop {
|
|
390
|
+
opacity: 1;
|
|
391
|
+
pointer-events: auto;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
[data-qzsy-model-sheet] {
|
|
395
|
+
position: fixed !important;
|
|
396
|
+
top: auto !important;
|
|
397
|
+
left: 0 !important;
|
|
398
|
+
right: 0 !important;
|
|
399
|
+
bottom: 0 !important;
|
|
400
|
+
width: 100vw !important;
|
|
401
|
+
max-width: 100vw !important;
|
|
402
|
+
height: auto !important;
|
|
403
|
+
max-height: 82vh !important;
|
|
404
|
+
max-height: 82dvh !important;
|
|
405
|
+
margin: 0 !important;
|
|
406
|
+
padding: 18px 0 0 !important;
|
|
407
|
+
border: 0 !important;
|
|
408
|
+
border-radius: 18px 18px 0 0 !important;
|
|
409
|
+
background: var(--dsw-alias-bg-layer-1, #ffffff) !important;
|
|
410
|
+
box-shadow: 0 -12px 40px rgba(0, 0, 0, 0.18) !important;
|
|
411
|
+
box-sizing: border-box !important;
|
|
412
|
+
display: flex !important;
|
|
413
|
+
flex-direction: column !important;
|
|
414
|
+
overflow: hidden !important;
|
|
415
|
+
z-index: 1200 !important;
|
|
416
|
+
transform-origin: bottom center !important;
|
|
417
|
+
animation: qzsy-model-sheet-in 260ms cubic-bezier(0.16, 1, 0.3, 1) !important;
|
|
418
|
+
will-change: transform;
|
|
419
|
+
}
|
|
420
|
+
[data-qzsy-model-sheet]::before {
|
|
421
|
+
content: '';
|
|
422
|
+
position: absolute;
|
|
423
|
+
top: 7px;
|
|
424
|
+
left: 50%;
|
|
425
|
+
width: 36px;
|
|
426
|
+
height: 4px;
|
|
427
|
+
margin-left: -18px;
|
|
428
|
+
border-radius: 999px;
|
|
429
|
+
background: rgba(0, 0, 0, 0.18);
|
|
430
|
+
pointer-events: none;
|
|
431
|
+
}
|
|
432
|
+
[data-qzsy-model-sheet] > div,
|
|
433
|
+
[data-qzsy-model-sheet] div[role="menu"] {
|
|
434
|
+
width: 100% !important;
|
|
435
|
+
max-width: 100% !important;
|
|
436
|
+
border: 0 !important;
|
|
437
|
+
border-radius: 0 !important;
|
|
438
|
+
background: transparent !important;
|
|
439
|
+
box-shadow: none !important;
|
|
440
|
+
box-sizing: border-box !important;
|
|
441
|
+
}
|
|
442
|
+
[data-qzsy-model-sheet] div[role="menu"] {
|
|
443
|
+
max-height: calc(82vh - 40px) !important;
|
|
444
|
+
max-height: calc(82dvh - 40px) !important;
|
|
445
|
+
overflow-y: auto !important;
|
|
446
|
+
overscroll-behavior: contain !important;
|
|
447
|
+
padding: 8px 12px calc(14px + var(--qzsy-safe-bottom, 0px)) !important;
|
|
448
|
+
}
|
|
449
|
+
[data-qzsy-model-sheet] input {
|
|
450
|
+
width: 100% !important;
|
|
451
|
+
box-sizing: border-box !important;
|
|
452
|
+
}
|
|
453
|
+
[data-qzsy-model-sheet] button[role="menuitem"],
|
|
454
|
+
[data-qzsy-model-sheet] [role="option"] {
|
|
455
|
+
width: 100% !important;
|
|
456
|
+
box-sizing: border-box !important;
|
|
457
|
+
}
|
|
458
|
+
[data-qzsy-model-sheet].qzsy-model-sheet-closing {
|
|
459
|
+
transform: translateY(100%) !important;
|
|
460
|
+
opacity: 0 !important;
|
|
461
|
+
transition: transform 220ms ease, opacity 220ms ease !important;
|
|
462
|
+
animation: none !important;
|
|
463
|
+
}
|
|
464
|
+
body.qzsy-model-sheet-dragging [data-qzsy-model-sheet] {
|
|
465
|
+
transform: translateY(var(--qzsy-model-sheet-y, 0px)) !important;
|
|
466
|
+
transition: none !important;
|
|
467
|
+
animation: none !important;
|
|
468
|
+
}
|
|
469
|
+
body.qzsy-model-sheet-dragging .qzsy-model-sheet-backdrop {
|
|
470
|
+
opacity: var(--qzsy-model-sheet-progress, 1) !important;
|
|
471
|
+
transition: none !important;
|
|
472
|
+
}
|
|
369
473
|
}
|
|
370
474
|
`;
|
|
371
475
|
function isElement(value) {
|
|
@@ -405,6 +509,11 @@ function setupMobileDrawer() {
|
|
|
405
509
|
let disposed = false;
|
|
406
510
|
let gesture = null;
|
|
407
511
|
let suppressClickUntil = 0;
|
|
512
|
+
let modelPortal = null;
|
|
513
|
+
let modelBackdrop = null;
|
|
514
|
+
let modelChainUntil = 0;
|
|
515
|
+
let modelDrag = null;
|
|
516
|
+
let modelDragHandlers = null;
|
|
408
517
|
const ensureElements = () => {
|
|
409
518
|
if (disposed || !document.body) return;
|
|
410
519
|
injectStyles();
|
|
@@ -484,6 +593,221 @@ function setupMobileDrawer() {
|
|
|
484
593
|
if (!disposed && isOpen()) closeDrawer({ restoreFocus: false });
|
|
485
594
|
}, 60);
|
|
486
595
|
};
|
|
596
|
+
const bodyPortalOf = (node) => {
|
|
597
|
+
let el = node;
|
|
598
|
+
while (el && el.parentElement && el.parentElement !== document.body) el = el.parentElement;
|
|
599
|
+
return el && el.parentElement === document.body ? el : null;
|
|
600
|
+
};
|
|
601
|
+
const visiblePortal = (el) => {
|
|
602
|
+
if (!el || !el.isConnected) return false;
|
|
603
|
+
const s = getComputedStyle(el);
|
|
604
|
+
if (s.display === "none" || s.visibility === "hidden") return false;
|
|
605
|
+
const r = el.getBoundingClientRect();
|
|
606
|
+
return r.width > 0 && r.height > 0;
|
|
607
|
+
};
|
|
608
|
+
const isModelTrigger = (target) => {
|
|
609
|
+
const el = target?.closest?.('button, [role="menuitem"], [role="button"], a');
|
|
610
|
+
if (!el) return false;
|
|
611
|
+
const label = `${el.getAttribute("aria-label") || ""} ${el.getAttribute("title") || ""} ${el.innerText || ""}`;
|
|
612
|
+
return /模型|Model|model/.test(label);
|
|
613
|
+
};
|
|
614
|
+
const findModelPortal = () => {
|
|
615
|
+
if (!isMobileViewport()) return null;
|
|
616
|
+
const search = document.querySelector(
|
|
617
|
+
'input[placeholder*="\u641C\u7D22\u6A21\u578B"], input[placeholder*="Search model"], input[aria-label*="\u641C\u7D22\u6A21\u578B"], input[aria-label*="Search model"]'
|
|
618
|
+
);
|
|
619
|
+
if (search) {
|
|
620
|
+
const menu = search.closest('[role="menu"]') || search.closest('[class*="_menu"]');
|
|
621
|
+
const portal = bodyPortalOf(menu || search);
|
|
622
|
+
if (portal && visiblePortal(portal)) {
|
|
623
|
+
const s = getComputedStyle(portal);
|
|
624
|
+
if ((s.position === "fixed" || s.position === "absolute") && portal.getBoundingClientRect().width > 120) return portal;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
const candidates = [];
|
|
628
|
+
for (const el of document.body.children) {
|
|
629
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
630
|
+
if (el === modelBackdrop || el.classList.contains(HEADER_CLASS) || el.classList.contains(BACKDROP_CLASS)) continue;
|
|
631
|
+
if (!visiblePortal(el)) continue;
|
|
632
|
+
const s = getComputedStyle(el);
|
|
633
|
+
if (s.position !== "fixed" && s.position !== "absolute") continue;
|
|
634
|
+
const menu = el.querySelector('[role="menu"], [role="listbox"]');
|
|
635
|
+
if (!menu) continue;
|
|
636
|
+
const text = menu.innerText || "";
|
|
637
|
+
const rows = menu.querySelectorAll('[role="menuitem"], [role="option"], button, a, [tabindex]').length;
|
|
638
|
+
const hasSearch = Boolean(el.querySelector('input[placeholder*="\u641C\u7D22\u6A21\u578B"], input[placeholder*="Search model"]'));
|
|
639
|
+
const hasModelMarker = Boolean(el.querySelector("[data-model-id], [data-provider-id], [data-model], [data-model-name]"));
|
|
640
|
+
if (/推理等级|Reasoning effort|推理强度/i.test(text) && rows <= 2) continue;
|
|
641
|
+
const rect = el.getBoundingClientRect();
|
|
642
|
+
const alreadySheet = rect.width >= window.innerWidth - 2 && Math.abs(rect.bottom - window.innerHeight) < 2 && rect.height > 120;
|
|
643
|
+
if (alreadySheet && !hasSearch) continue;
|
|
644
|
+
const chained = Date.now() < modelChainUntil && rows > 2;
|
|
645
|
+
if (hasSearch || hasModelMarker || chained) candidates.push({ el, rows, hasSearch, hasModelMarker });
|
|
646
|
+
}
|
|
647
|
+
candidates.sort((a, b) => Number(b.hasSearch) - Number(a.hasSearch) || Number(b.hasModelMarker) - Number(a.hasModelMarker) || b.rows - a.rows);
|
|
648
|
+
return candidates[0]?.el || null;
|
|
649
|
+
};
|
|
650
|
+
const hostBackdropPresent = () => {
|
|
651
|
+
for (const el of document.body.children) {
|
|
652
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
653
|
+
if (el === modelBackdrop || el.classList.contains(HEADER_CLASS) || el.classList.contains(BACKDROP_CLASS)) continue;
|
|
654
|
+
const s = getComputedStyle(el);
|
|
655
|
+
if (s.position !== "fixed") continue;
|
|
656
|
+
const r = el.getBoundingClientRect();
|
|
657
|
+
if (r.width < window.innerWidth - 4 || r.height < window.innerHeight - 4) continue;
|
|
658
|
+
if (Number(s.opacity) < 0.1) continue;
|
|
659
|
+
const bg = s.backgroundColor || "";
|
|
660
|
+
if (!/^rgba?\(/.test(bg) || /rgba\(0,\s*0,\s*0,\s*0\)/.test(bg)) continue;
|
|
661
|
+
if (el.querySelector('[role="menu"]')) continue;
|
|
662
|
+
return true;
|
|
663
|
+
}
|
|
664
|
+
return false;
|
|
665
|
+
};
|
|
666
|
+
const requestHostModelClose = (portal) => {
|
|
667
|
+
if (disposed || !portal || !portal.isConnected) return;
|
|
668
|
+
const PointerCtor = typeof window.PointerEvent === "function" ? window.PointerEvent : window.MouseEvent;
|
|
669
|
+
try {
|
|
670
|
+
document.body.dispatchEvent(new PointerCtor("pointerdown", { bubbles: true, cancelable: true }));
|
|
671
|
+
} catch {
|
|
672
|
+
}
|
|
673
|
+
try {
|
|
674
|
+
document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true, cancelable: true }));
|
|
675
|
+
} catch {
|
|
676
|
+
}
|
|
677
|
+
window.setTimeout(() => {
|
|
678
|
+
if (disposed || !portal.isConnected) return;
|
|
679
|
+
const trigger = document.querySelector('button[aria-haspopup="menu"][aria-expanded="true"][aria-label*="\u9009\u62E9\u6A21\u578B"], button[aria-haspopup="menu"][aria-expanded="true"][aria-label*="Select model"]');
|
|
680
|
+
try {
|
|
681
|
+
trigger?.click();
|
|
682
|
+
} catch {
|
|
683
|
+
}
|
|
684
|
+
window.setTimeout(() => {
|
|
685
|
+
if (!disposed && portal.isConnected && modelPortal === portal) clearModelSheet();
|
|
686
|
+
}, 450);
|
|
687
|
+
}, 120);
|
|
688
|
+
};
|
|
689
|
+
const closeModelSheetNow = () => {
|
|
690
|
+
const portal = modelPortal;
|
|
691
|
+
if (!portal) return;
|
|
692
|
+
document.body.classList.remove("qzsy-model-sheet-open");
|
|
693
|
+
document.body.style.removeProperty("--qzsy-model-sheet-y");
|
|
694
|
+
document.body.style.removeProperty("--qzsy-model-sheet-progress");
|
|
695
|
+
requestHostModelClose(portal);
|
|
696
|
+
};
|
|
697
|
+
const closeModelSheetAnimated = () => {
|
|
698
|
+
const portal = modelPortal;
|
|
699
|
+
if (!portal) return;
|
|
700
|
+
portal.classList.add("qzsy-model-sheet-closing");
|
|
701
|
+
document.body.classList.remove("qzsy-model-sheet-open");
|
|
702
|
+
window.setTimeout(() => requestHostModelClose(portal), 180);
|
|
703
|
+
};
|
|
704
|
+
const ensureModelBackdrop = () => {
|
|
705
|
+
if (modelBackdrop || hostBackdropPresent()) return;
|
|
706
|
+
modelBackdrop = document.createElement("div");
|
|
707
|
+
modelBackdrop.className = "qzsy-model-sheet-backdrop";
|
|
708
|
+
modelBackdrop.setAttribute("aria-hidden", "true");
|
|
709
|
+
modelBackdrop.addEventListener("pointerdown", (event) => event.stopPropagation(), true);
|
|
710
|
+
modelBackdrop.addEventListener("click", (event) => {
|
|
711
|
+
event.preventDefault();
|
|
712
|
+
event.stopPropagation();
|
|
713
|
+
closeModelSheetNow();
|
|
714
|
+
});
|
|
715
|
+
document.body.appendChild(modelBackdrop);
|
|
716
|
+
};
|
|
717
|
+
const detachModelDrag = () => {
|
|
718
|
+
if (!modelPortal || !modelDragHandlers) return;
|
|
719
|
+
modelPortal.removeEventListener("touchstart", modelDragHandlers.onStart);
|
|
720
|
+
modelPortal.removeEventListener("touchmove", modelDragHandlers.onMove);
|
|
721
|
+
modelPortal.removeEventListener("touchend", modelDragHandlers.onEnd);
|
|
722
|
+
modelPortal.removeEventListener("touchcancel", modelDragHandlers.onEnd);
|
|
723
|
+
modelDragHandlers = null;
|
|
724
|
+
};
|
|
725
|
+
const attachModelDrag = (portal) => {
|
|
726
|
+
if (!portal || modelDragHandlers) return;
|
|
727
|
+
const onStart = (event) => {
|
|
728
|
+
if (event.touches?.length !== 1) return;
|
|
729
|
+
const touch = event.touches[0];
|
|
730
|
+
const rect = portal.getBoundingClientRect();
|
|
731
|
+
if (touch.clientY - rect.top > 30) return;
|
|
732
|
+
if (event.target.closest?.('input, textarea, select, button, a, [contenteditable="true"]')) return;
|
|
733
|
+
modelDrag = { startY: touch.clientY, startT: Date.now(), dy: 0, active: false, height: rect.height };
|
|
734
|
+
};
|
|
735
|
+
const onMove = (event) => {
|
|
736
|
+
if (!modelDrag) return;
|
|
737
|
+
const touch = event.touches?.[0];
|
|
738
|
+
if (!touch) return;
|
|
739
|
+
const dy = touch.clientY - modelDrag.startY;
|
|
740
|
+
if (!modelDrag.active) {
|
|
741
|
+
if (dy < 6) return;
|
|
742
|
+
modelDrag.active = true;
|
|
743
|
+
document.body.classList.add("qzsy-model-sheet-dragging");
|
|
744
|
+
}
|
|
745
|
+
modelDrag.dy = Math.max(0, dy);
|
|
746
|
+
document.body.style.setProperty("--qzsy-model-sheet-y", `${modelDrag.dy}px`);
|
|
747
|
+
document.body.style.setProperty("--qzsy-model-sheet-progress", String(Math.max(0, 1 - modelDrag.dy / Math.max(1, modelDrag.height))));
|
|
748
|
+
if (event.cancelable) event.preventDefault();
|
|
749
|
+
};
|
|
750
|
+
const onEnd = () => {
|
|
751
|
+
if (!modelDrag) return;
|
|
752
|
+
const drag = modelDrag;
|
|
753
|
+
modelDrag = null;
|
|
754
|
+
if (!drag.active) return;
|
|
755
|
+
document.body.classList.remove("qzsy-model-sheet-dragging");
|
|
756
|
+
document.body.style.removeProperty("--qzsy-model-sheet-y");
|
|
757
|
+
document.body.style.removeProperty("--qzsy-model-sheet-progress");
|
|
758
|
+
const velocity = drag.dy / Math.max(1, Date.now() - drag.startT);
|
|
759
|
+
if (drag.dy > 110 || velocity > 0.5) closeModelSheetAnimated();
|
|
760
|
+
};
|
|
761
|
+
portal.addEventListener("touchstart", onStart, { passive: true });
|
|
762
|
+
portal.addEventListener("touchmove", onMove, { passive: false });
|
|
763
|
+
portal.addEventListener("touchend", onEnd, { passive: true });
|
|
764
|
+
portal.addEventListener("touchcancel", onEnd, { passive: true });
|
|
765
|
+
modelDragHandlers = { onStart, onMove, onEnd };
|
|
766
|
+
};
|
|
767
|
+
const clearModelSheet = () => {
|
|
768
|
+
if (modelPortal) {
|
|
769
|
+
detachModelDrag();
|
|
770
|
+
modelPortal.removeAttribute("data-qzsy-model-sheet");
|
|
771
|
+
modelPortal.classList.remove("qzsy-model-sheet", "qzsy-model-sheet-closing");
|
|
772
|
+
}
|
|
773
|
+
modelPortal = null;
|
|
774
|
+
modelDrag = null;
|
|
775
|
+
document.body?.classList.remove("qzsy-model-sheet-open", "qzsy-model-sheet-dragging");
|
|
776
|
+
document.body?.style.removeProperty("--qzsy-model-sheet-y");
|
|
777
|
+
document.body?.style.removeProperty("--qzsy-model-sheet-progress");
|
|
778
|
+
if (modelBackdrop) {
|
|
779
|
+
modelBackdrop.remove();
|
|
780
|
+
modelBackdrop = null;
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
const applyModelSheet = (portal) => {
|
|
784
|
+
if (modelPortal === portal) return;
|
|
785
|
+
clearModelSheet();
|
|
786
|
+
modelPortal = portal;
|
|
787
|
+
modelChainUntil = 0;
|
|
788
|
+
portal.setAttribute("data-qzsy-model-sheet", "");
|
|
789
|
+
portal.classList.add("qzsy-model-sheet");
|
|
790
|
+
attachModelDrag(portal);
|
|
791
|
+
ensureModelBackdrop();
|
|
792
|
+
window.requestAnimationFrame(() => {
|
|
793
|
+
if (!disposed && modelPortal === portal) document.body.classList.add("qzsy-model-sheet-open");
|
|
794
|
+
});
|
|
795
|
+
};
|
|
796
|
+
const syncModelSheet = () => {
|
|
797
|
+
if (disposed) return;
|
|
798
|
+
if (!isMobileViewport()) {
|
|
799
|
+
clearModelSheet();
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
if (modelPortal && !visiblePortal(modelPortal)) clearModelSheet();
|
|
803
|
+
const found = findModelPortal();
|
|
804
|
+
if (!found) {
|
|
805
|
+
if (modelPortal?.isConnected) return;
|
|
806
|
+
if (modelPortal) clearModelSheet();
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
if (found !== modelPortal) applyModelSheet(found);
|
|
810
|
+
};
|
|
487
811
|
const onClick = (event) => {
|
|
488
812
|
if (Date.now() < suppressClickUntil) {
|
|
489
813
|
event.preventDefault();
|
|
@@ -498,6 +822,11 @@ function setupMobileDrawer() {
|
|
|
498
822
|
else openDrawer();
|
|
499
823
|
return;
|
|
500
824
|
}
|
|
825
|
+
if (isModelTrigger(target)) {
|
|
826
|
+
modelChainUntil = Date.now() + 2500;
|
|
827
|
+
window.setTimeout(syncModelSheet, 30);
|
|
828
|
+
window.setTimeout(syncModelSheet, 180);
|
|
829
|
+
}
|
|
501
830
|
if (!isOpen()) return;
|
|
502
831
|
if (target.closest(`.${BACKDROP_CLASS}`)) {
|
|
503
832
|
closeDrawer();
|
|
@@ -531,6 +860,7 @@ function setupMobileDrawer() {
|
|
|
531
860
|
clearDrag();
|
|
532
861
|
closeDrawer({ restoreFocus: false });
|
|
533
862
|
}
|
|
863
|
+
syncModelSheet();
|
|
534
864
|
};
|
|
535
865
|
const onVisibilityChange = () => {
|
|
536
866
|
if (document.visibilityState === "hidden" && isOpen()) {
|
|
@@ -538,9 +868,10 @@ function setupMobileDrawer() {
|
|
|
538
868
|
clearDrag();
|
|
539
869
|
closeDrawer({ restoreFocus: false });
|
|
540
870
|
}
|
|
871
|
+
if (document.visibilityState === "visible") syncModelSheet();
|
|
541
872
|
};
|
|
542
873
|
const onTouchStart = (event) => {
|
|
543
|
-
if (disposed || !isMobileViewport() || event.touches?.length !== 1) return;
|
|
874
|
+
if (modelPortal || disposed || !isMobileViewport() || event.touches?.length !== 1) return;
|
|
544
875
|
const touch = event.touches[0];
|
|
545
876
|
const sidebar = document.querySelector('div[class*="_sidebarCol"]');
|
|
546
877
|
if (!sidebar) return;
|
|
@@ -572,7 +903,7 @@ function setupMobileDrawer() {
|
|
|
572
903
|
};
|
|
573
904
|
};
|
|
574
905
|
const onTouchMove = (event) => {
|
|
575
|
-
if (!gesture || disposed) return;
|
|
906
|
+
if (modelPortal || !gesture || disposed) return;
|
|
576
907
|
const touch = event.touches?.[0];
|
|
577
908
|
if (!touch) return;
|
|
578
909
|
const dx = touch.clientX - gesture.startX;
|
|
@@ -620,11 +951,11 @@ function setupMobileDrawer() {
|
|
|
620
951
|
window.requestAnimationFrame(clearDrag);
|
|
621
952
|
};
|
|
622
953
|
const onTouchEnd = (event) => {
|
|
623
|
-
if (!gesture) return;
|
|
954
|
+
if (modelPortal || !gesture) return;
|
|
624
955
|
finishGesture(event.changedTouches?.[0]);
|
|
625
956
|
};
|
|
626
957
|
const onTouchCancel = () => {
|
|
627
|
-
if (!gesture) return;
|
|
958
|
+
if (modelPortal || !gesture) return;
|
|
628
959
|
finishGesture(null);
|
|
629
960
|
};
|
|
630
961
|
injectStyles();
|
|
@@ -645,9 +976,11 @@ function setupMobileDrawer() {
|
|
|
645
976
|
if (!document.querySelector(`.${HEADER_CLASS}`) || !document.querySelector(`.${MENU_CLASS}`) || !document.querySelector(`.${BACKDROP_CLASS}`) || !document.getElementById(MOBILE_DRAWER_CSS_ID)) {
|
|
646
977
|
ensureElements();
|
|
647
978
|
}
|
|
979
|
+
syncModelSheet();
|
|
648
980
|
});
|
|
649
981
|
observer.observe(document.body, { childList: true });
|
|
650
982
|
}
|
|
983
|
+
syncModelSheet();
|
|
651
984
|
syncAria();
|
|
652
985
|
return () => {
|
|
653
986
|
disposed = true;
|
|
@@ -664,6 +997,7 @@ function setupMobileDrawer() {
|
|
|
664
997
|
window.removeEventListener("touchmove", onTouchMove);
|
|
665
998
|
window.removeEventListener("touchend", onTouchEnd);
|
|
666
999
|
window.removeEventListener("touchcancel", onTouchCancel);
|
|
1000
|
+
clearModelSheet();
|
|
667
1001
|
clearDrag();
|
|
668
1002
|
document.body?.classList.remove(OPEN_CLASS);
|
|
669
1003
|
document.body?.classList.remove("qzsy-drawer-ready");
|
package/client/mobile.js
CHANGED
|
@@ -33,7 +33,8 @@ export const MOBILE_DRAWER_CSS = `
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
.qzsy-mobile-header,
|
|
36
|
-
.qzsy-mobile-backdrop
|
|
36
|
+
.qzsy-mobile-backdrop,
|
|
37
|
+
.qzsy-model-sheet-backdrop {
|
|
37
38
|
display: none;
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -163,6 +164,109 @@ export const MOBILE_DRAWER_CSS = `
|
|
|
163
164
|
opacity: 0;
|
|
164
165
|
pointer-events: none;
|
|
165
166
|
}
|
|
167
|
+
|
|
168
|
+
/* 5. 模型切换器:把宿主桌面 popover 改成移动端底部 sheet */
|
|
169
|
+
@keyframes qzsy-model-sheet-in {
|
|
170
|
+
from { transform: translateY(100%); opacity: 0.6; }
|
|
171
|
+
to { transform: translateY(0); opacity: 1; }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.qzsy-model-sheet-backdrop {
|
|
175
|
+
display: block;
|
|
176
|
+
position: fixed;
|
|
177
|
+
inset: 0;
|
|
178
|
+
z-index: 1190;
|
|
179
|
+
background: rgba(0, 0, 0, 0.4);
|
|
180
|
+
opacity: 0;
|
|
181
|
+
pointer-events: none;
|
|
182
|
+
transition: opacity 240ms ease;
|
|
183
|
+
touch-action: none;
|
|
184
|
+
-webkit-tap-highlight-color: transparent;
|
|
185
|
+
}
|
|
186
|
+
body.qzsy-model-sheet-open .qzsy-model-sheet-backdrop {
|
|
187
|
+
opacity: 1;
|
|
188
|
+
pointer-events: auto;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
[data-qzsy-model-sheet] {
|
|
192
|
+
position: fixed !important;
|
|
193
|
+
top: auto !important;
|
|
194
|
+
left: 0 !important;
|
|
195
|
+
right: 0 !important;
|
|
196
|
+
bottom: 0 !important;
|
|
197
|
+
width: 100vw !important;
|
|
198
|
+
max-width: 100vw !important;
|
|
199
|
+
height: auto !important;
|
|
200
|
+
max-height: 82vh !important;
|
|
201
|
+
max-height: 82dvh !important;
|
|
202
|
+
margin: 0 !important;
|
|
203
|
+
padding: 18px 0 0 !important;
|
|
204
|
+
border: 0 !important;
|
|
205
|
+
border-radius: 18px 18px 0 0 !important;
|
|
206
|
+
background: var(--dsw-alias-bg-layer-1, #ffffff) !important;
|
|
207
|
+
box-shadow: 0 -12px 40px rgba(0, 0, 0, 0.18) !important;
|
|
208
|
+
box-sizing: border-box !important;
|
|
209
|
+
display: flex !important;
|
|
210
|
+
flex-direction: column !important;
|
|
211
|
+
overflow: hidden !important;
|
|
212
|
+
z-index: 1200 !important;
|
|
213
|
+
transform-origin: bottom center !important;
|
|
214
|
+
animation: qzsy-model-sheet-in 260ms cubic-bezier(0.16, 1, 0.3, 1) !important;
|
|
215
|
+
will-change: transform;
|
|
216
|
+
}
|
|
217
|
+
[data-qzsy-model-sheet]::before {
|
|
218
|
+
content: '';
|
|
219
|
+
position: absolute;
|
|
220
|
+
top: 7px;
|
|
221
|
+
left: 50%;
|
|
222
|
+
width: 36px;
|
|
223
|
+
height: 4px;
|
|
224
|
+
margin-left: -18px;
|
|
225
|
+
border-radius: 999px;
|
|
226
|
+
background: rgba(0, 0, 0, 0.18);
|
|
227
|
+
pointer-events: none;
|
|
228
|
+
}
|
|
229
|
+
[data-qzsy-model-sheet] > div,
|
|
230
|
+
[data-qzsy-model-sheet] div[role="menu"] {
|
|
231
|
+
width: 100% !important;
|
|
232
|
+
max-width: 100% !important;
|
|
233
|
+
border: 0 !important;
|
|
234
|
+
border-radius: 0 !important;
|
|
235
|
+
background: transparent !important;
|
|
236
|
+
box-shadow: none !important;
|
|
237
|
+
box-sizing: border-box !important;
|
|
238
|
+
}
|
|
239
|
+
[data-qzsy-model-sheet] div[role="menu"] {
|
|
240
|
+
max-height: calc(82vh - 40px) !important;
|
|
241
|
+
max-height: calc(82dvh - 40px) !important;
|
|
242
|
+
overflow-y: auto !important;
|
|
243
|
+
overscroll-behavior: contain !important;
|
|
244
|
+
padding: 8px 12px calc(14px + var(--qzsy-safe-bottom, 0px)) !important;
|
|
245
|
+
}
|
|
246
|
+
[data-qzsy-model-sheet] input {
|
|
247
|
+
width: 100% !important;
|
|
248
|
+
box-sizing: border-box !important;
|
|
249
|
+
}
|
|
250
|
+
[data-qzsy-model-sheet] button[role="menuitem"],
|
|
251
|
+
[data-qzsy-model-sheet] [role="option"] {
|
|
252
|
+
width: 100% !important;
|
|
253
|
+
box-sizing: border-box !important;
|
|
254
|
+
}
|
|
255
|
+
[data-qzsy-model-sheet].qzsy-model-sheet-closing {
|
|
256
|
+
transform: translateY(100%) !important;
|
|
257
|
+
opacity: 0 !important;
|
|
258
|
+
transition: transform 220ms ease, opacity 220ms ease !important;
|
|
259
|
+
animation: none !important;
|
|
260
|
+
}
|
|
261
|
+
body.qzsy-model-sheet-dragging [data-qzsy-model-sheet] {
|
|
262
|
+
transform: translateY(var(--qzsy-model-sheet-y, 0px)) !important;
|
|
263
|
+
transition: none !important;
|
|
264
|
+
animation: none !important;
|
|
265
|
+
}
|
|
266
|
+
body.qzsy-model-sheet-dragging .qzsy-model-sheet-backdrop {
|
|
267
|
+
opacity: var(--qzsy-model-sheet-progress, 1) !important;
|
|
268
|
+
transition: none !important;
|
|
269
|
+
}
|
|
166
270
|
}
|
|
167
271
|
`;
|
|
168
272
|
|
|
@@ -211,6 +315,11 @@ export function setupMobileDrawer() {
|
|
|
211
315
|
let disposed = false;
|
|
212
316
|
let gesture = null;
|
|
213
317
|
let suppressClickUntil = 0;
|
|
318
|
+
let modelPortal = null;
|
|
319
|
+
let modelBackdrop = null;
|
|
320
|
+
let modelChainUntil = 0;
|
|
321
|
+
let modelDrag = null;
|
|
322
|
+
let modelDragHandlers = null;
|
|
214
323
|
|
|
215
324
|
const ensureElements = () => {
|
|
216
325
|
if (disposed || !document.body) return;
|
|
@@ -296,6 +405,232 @@ export function setupMobileDrawer() {
|
|
|
296
405
|
}, 60);
|
|
297
406
|
};
|
|
298
407
|
|
|
408
|
+
// ---- 模型切换器:宿主桌面 popover → 移动端底部 sheet ----
|
|
409
|
+
const bodyPortalOf = (node) => {
|
|
410
|
+
let el = node;
|
|
411
|
+
while (el && el.parentElement && el.parentElement !== document.body) el = el.parentElement;
|
|
412
|
+
return el && el.parentElement === document.body ? el : null;
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
const visiblePortal = (el) => {
|
|
416
|
+
if (!el || !el.isConnected) return false;
|
|
417
|
+
const s = getComputedStyle(el);
|
|
418
|
+
if (s.display === 'none' || s.visibility === 'hidden') return false;
|
|
419
|
+
const r = el.getBoundingClientRect();
|
|
420
|
+
return r.width > 0 && r.height > 0;
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
const isModelTrigger = (target) => {
|
|
424
|
+
const el = target?.closest?.('button, [role="menuitem"], [role="button"], a');
|
|
425
|
+
if (!el) return false;
|
|
426
|
+
const label = `${el.getAttribute('aria-label') || ''} ${el.getAttribute('title') || ''} ${el.innerText || ''}`;
|
|
427
|
+
return /模型|Model|model/.test(label);
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
const findModelPortal = () => {
|
|
431
|
+
if (!isMobileViewport()) return null;
|
|
432
|
+
const search = document.querySelector(
|
|
433
|
+
'input[placeholder*="搜索模型"], input[placeholder*="Search model"], input[aria-label*="搜索模型"], input[aria-label*="Search model"]',
|
|
434
|
+
);
|
|
435
|
+
if (search) {
|
|
436
|
+
const menu = search.closest('[role="menu"]') || search.closest('[class*="_menu"]');
|
|
437
|
+
const portal = bodyPortalOf(menu || search);
|
|
438
|
+
if (portal && visiblePortal(portal)) {
|
|
439
|
+
const s = getComputedStyle(portal);
|
|
440
|
+
if ((s.position === 'fixed' || s.position === 'absolute') && portal.getBoundingClientRect().width > 120) return portal;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
// 无搜索框的宿主(如移动端版本)用「模型标记 / 模型触发链 + 菜单行数」兜底
|
|
444
|
+
const candidates = [];
|
|
445
|
+
for (const el of document.body.children) {
|
|
446
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
447
|
+
if (el === modelBackdrop || el.classList.contains(HEADER_CLASS) || el.classList.contains(BACKDROP_CLASS)) continue;
|
|
448
|
+
if (!visiblePortal(el)) continue;
|
|
449
|
+
const s = getComputedStyle(el);
|
|
450
|
+
if (s.position !== 'fixed' && s.position !== 'absolute') continue;
|
|
451
|
+
const menu = el.querySelector('[role="menu"], [role="listbox"]');
|
|
452
|
+
if (!menu) continue;
|
|
453
|
+
const text = menu.innerText || '';
|
|
454
|
+
const rows = menu.querySelectorAll('[role="menuitem"], [role="option"], button, a, [tabindex]').length;
|
|
455
|
+
const hasSearch = Boolean(el.querySelector('input[placeholder*="搜索模型"], input[placeholder*="Search model"]'));
|
|
456
|
+
const hasModelMarker = Boolean(el.querySelector('[data-model-id], [data-provider-id], [data-model], [data-model-name]'));
|
|
457
|
+
// 排除「模型 / 推理等级」两行的第一层 popover
|
|
458
|
+
if (/推理等级|Reasoning effort|推理强度/i.test(text) && rows <= 2) continue;
|
|
459
|
+
// 已经是全宽底部 sheet 的宿主菜单不再重复改造
|
|
460
|
+
const rect = el.getBoundingClientRect();
|
|
461
|
+
const alreadySheet = rect.width >= window.innerWidth - 2 && Math.abs(rect.bottom - window.innerHeight) < 2 && rect.height > 120;
|
|
462
|
+
if (alreadySheet && !hasSearch) continue;
|
|
463
|
+
const chained = Date.now() < modelChainUntil && rows > 2;
|
|
464
|
+
if (hasSearch || hasModelMarker || chained) candidates.push({ el, rows, hasSearch, hasModelMarker });
|
|
465
|
+
}
|
|
466
|
+
candidates.sort((a, b) => Number(b.hasSearch) - Number(a.hasSearch)
|
|
467
|
+
|| Number(b.hasModelMarker) - Number(a.hasModelMarker)
|
|
468
|
+
|| b.rows - a.rows);
|
|
469
|
+
return candidates[0]?.el || null;
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
const hostBackdropPresent = () => {
|
|
473
|
+
for (const el of document.body.children) {
|
|
474
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
475
|
+
if (el === modelBackdrop || el.classList.contains(HEADER_CLASS) || el.classList.contains(BACKDROP_CLASS)) continue;
|
|
476
|
+
const s = getComputedStyle(el);
|
|
477
|
+
if (s.position !== 'fixed') continue;
|
|
478
|
+
const r = el.getBoundingClientRect();
|
|
479
|
+
if (r.width < window.innerWidth - 4 || r.height < window.innerHeight - 4) continue;
|
|
480
|
+
if (Number(s.opacity) < 0.1) continue;
|
|
481
|
+
const bg = s.backgroundColor || '';
|
|
482
|
+
if (!/^rgba?\(/.test(bg) || /rgba\(0,\s*0,\s*0,\s*0\)/.test(bg)) continue;
|
|
483
|
+
if (el.querySelector('[role="menu"]')) continue;
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
486
|
+
return false;
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
const requestHostModelClose = (portal) => {
|
|
490
|
+
if (disposed || !portal || !portal.isConnected) return;
|
|
491
|
+
const PointerCtor = typeof window.PointerEvent === 'function' ? window.PointerEvent : window.MouseEvent;
|
|
492
|
+
try { document.body.dispatchEvent(new PointerCtor('pointerdown', { bubbles: true, cancelable: true })); } catch { /* 忽略 */ }
|
|
493
|
+
try { document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true })); } catch { /* 忽略 */ }
|
|
494
|
+
window.setTimeout(() => {
|
|
495
|
+
if (disposed || !portal.isConnected) return;
|
|
496
|
+
const trigger = document.querySelector('button[aria-haspopup="menu"][aria-expanded="true"][aria-label*="选择模型"], button[aria-haspopup="menu"][aria-expanded="true"][aria-label*="Select model"]');
|
|
497
|
+
try { trigger?.click(); } catch { /* 忽略 */ }
|
|
498
|
+
window.setTimeout(() => {
|
|
499
|
+
if (!disposed && portal.isConnected && modelPortal === portal) clearModelSheet();
|
|
500
|
+
}, 450);
|
|
501
|
+
}, 120);
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
const closeModelSheetNow = () => {
|
|
505
|
+
const portal = modelPortal;
|
|
506
|
+
if (!portal) return;
|
|
507
|
+
document.body.classList.remove('qzsy-model-sheet-open');
|
|
508
|
+
document.body.style.removeProperty('--qzsy-model-sheet-y');
|
|
509
|
+
document.body.style.removeProperty('--qzsy-model-sheet-progress');
|
|
510
|
+
requestHostModelClose(portal);
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
const closeModelSheetAnimated = () => {
|
|
514
|
+
const portal = modelPortal;
|
|
515
|
+
if (!portal) return;
|
|
516
|
+
portal.classList.add('qzsy-model-sheet-closing');
|
|
517
|
+
document.body.classList.remove('qzsy-model-sheet-open');
|
|
518
|
+
window.setTimeout(() => requestHostModelClose(portal), 180);
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
const ensureModelBackdrop = () => {
|
|
522
|
+
if (modelBackdrop || hostBackdropPresent()) return;
|
|
523
|
+
modelBackdrop = document.createElement('div');
|
|
524
|
+
modelBackdrop.className = 'qzsy-model-sheet-backdrop';
|
|
525
|
+
modelBackdrop.setAttribute('aria-hidden', 'true');
|
|
526
|
+
modelBackdrop.addEventListener('pointerdown', (event) => event.stopPropagation(), true);
|
|
527
|
+
modelBackdrop.addEventListener('click', (event) => {
|
|
528
|
+
event.preventDefault();
|
|
529
|
+
event.stopPropagation();
|
|
530
|
+
closeModelSheetNow();
|
|
531
|
+
});
|
|
532
|
+
document.body.appendChild(modelBackdrop);
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
const detachModelDrag = () => {
|
|
536
|
+
if (!modelPortal || !modelDragHandlers) return;
|
|
537
|
+
modelPortal.removeEventListener('touchstart', modelDragHandlers.onStart);
|
|
538
|
+
modelPortal.removeEventListener('touchmove', modelDragHandlers.onMove);
|
|
539
|
+
modelPortal.removeEventListener('touchend', modelDragHandlers.onEnd);
|
|
540
|
+
modelPortal.removeEventListener('touchcancel', modelDragHandlers.onEnd);
|
|
541
|
+
modelDragHandlers = null;
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
const attachModelDrag = (portal) => {
|
|
545
|
+
if (!portal || modelDragHandlers) return;
|
|
546
|
+
const onStart = (event) => {
|
|
547
|
+
if (event.touches?.length !== 1) return;
|
|
548
|
+
const touch = event.touches[0];
|
|
549
|
+
const rect = portal.getBoundingClientRect();
|
|
550
|
+
if (touch.clientY - rect.top > 30) return;
|
|
551
|
+
if (event.target.closest?.('input, textarea, select, button, a, [contenteditable="true"]')) return;
|
|
552
|
+
modelDrag = { startY: touch.clientY, startT: Date.now(), dy: 0, active: false, height: rect.height };
|
|
553
|
+
};
|
|
554
|
+
const onMove = (event) => {
|
|
555
|
+
if (!modelDrag) return;
|
|
556
|
+
const touch = event.touches?.[0];
|
|
557
|
+
if (!touch) return;
|
|
558
|
+
const dy = touch.clientY - modelDrag.startY;
|
|
559
|
+
if (!modelDrag.active) {
|
|
560
|
+
if (dy < 6) return;
|
|
561
|
+
modelDrag.active = true;
|
|
562
|
+
document.body.classList.add('qzsy-model-sheet-dragging');
|
|
563
|
+
}
|
|
564
|
+
modelDrag.dy = Math.max(0, dy);
|
|
565
|
+
document.body.style.setProperty('--qzsy-model-sheet-y', `${modelDrag.dy}px`);
|
|
566
|
+
document.body.style.setProperty('--qzsy-model-sheet-progress', String(Math.max(0, 1 - modelDrag.dy / Math.max(1, modelDrag.height))));
|
|
567
|
+
if (event.cancelable) event.preventDefault();
|
|
568
|
+
};
|
|
569
|
+
const onEnd = () => {
|
|
570
|
+
if (!modelDrag) return;
|
|
571
|
+
const drag = modelDrag;
|
|
572
|
+
modelDrag = null;
|
|
573
|
+
if (!drag.active) return;
|
|
574
|
+
document.body.classList.remove('qzsy-model-sheet-dragging');
|
|
575
|
+
document.body.style.removeProperty('--qzsy-model-sheet-y');
|
|
576
|
+
document.body.style.removeProperty('--qzsy-model-sheet-progress');
|
|
577
|
+
const velocity = drag.dy / Math.max(1, Date.now() - drag.startT);
|
|
578
|
+
if (drag.dy > 110 || velocity > 0.5) closeModelSheetAnimated();
|
|
579
|
+
};
|
|
580
|
+
portal.addEventListener('touchstart', onStart, { passive: true });
|
|
581
|
+
portal.addEventListener('touchmove', onMove, { passive: false });
|
|
582
|
+
portal.addEventListener('touchend', onEnd, { passive: true });
|
|
583
|
+
portal.addEventListener('touchcancel', onEnd, { passive: true });
|
|
584
|
+
modelDragHandlers = { onStart, onMove, onEnd };
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
const clearModelSheet = () => {
|
|
588
|
+
if (modelPortal) {
|
|
589
|
+
detachModelDrag();
|
|
590
|
+
modelPortal.removeAttribute('data-qzsy-model-sheet');
|
|
591
|
+
modelPortal.classList.remove('qzsy-model-sheet', 'qzsy-model-sheet-closing');
|
|
592
|
+
}
|
|
593
|
+
modelPortal = null;
|
|
594
|
+
modelDrag = null;
|
|
595
|
+
document.body?.classList.remove('qzsy-model-sheet-open', 'qzsy-model-sheet-dragging');
|
|
596
|
+
document.body?.style.removeProperty('--qzsy-model-sheet-y');
|
|
597
|
+
document.body?.style.removeProperty('--qzsy-model-sheet-progress');
|
|
598
|
+
if (modelBackdrop) {
|
|
599
|
+
modelBackdrop.remove();
|
|
600
|
+
modelBackdrop = null;
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
const applyModelSheet = (portal) => {
|
|
605
|
+
if (modelPortal === portal) return;
|
|
606
|
+
clearModelSheet();
|
|
607
|
+
modelPortal = portal;
|
|
608
|
+
modelChainUntil = 0;
|
|
609
|
+
portal.setAttribute('data-qzsy-model-sheet', '');
|
|
610
|
+
portal.classList.add('qzsy-model-sheet');
|
|
611
|
+
attachModelDrag(portal);
|
|
612
|
+
ensureModelBackdrop();
|
|
613
|
+
window.requestAnimationFrame(() => {
|
|
614
|
+
if (!disposed && modelPortal === portal) document.body.classList.add('qzsy-model-sheet-open');
|
|
615
|
+
});
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
const syncModelSheet = () => {
|
|
619
|
+
if (disposed) return;
|
|
620
|
+
if (!isMobileViewport()) {
|
|
621
|
+
clearModelSheet();
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
if (modelPortal && !visiblePortal(modelPortal)) clearModelSheet();
|
|
625
|
+
const found = findModelPortal();
|
|
626
|
+
if (!found) {
|
|
627
|
+
if (modelPortal?.isConnected) return; // 已应用,等待宿主关闭后移除
|
|
628
|
+
if (modelPortal) clearModelSheet();
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
if (found !== modelPortal) applyModelSheet(found);
|
|
632
|
+
};
|
|
633
|
+
|
|
299
634
|
const onClick = (event) => {
|
|
300
635
|
if (Date.now() < suppressClickUntil) {
|
|
301
636
|
event.preventDefault();
|
|
@@ -311,6 +646,14 @@ export function setupMobileDrawer() {
|
|
|
311
646
|
else openDrawer();
|
|
312
647
|
return;
|
|
313
648
|
}
|
|
649
|
+
|
|
650
|
+
// 模型切换器:记录触发链,等宿主把二级菜单 portal 挂到 body 后再改造成 sheet
|
|
651
|
+
if (isModelTrigger(target)) {
|
|
652
|
+
modelChainUntil = Date.now() + 2500;
|
|
653
|
+
window.setTimeout(syncModelSheet, 30);
|
|
654
|
+
window.setTimeout(syncModelSheet, 180);
|
|
655
|
+
}
|
|
656
|
+
|
|
314
657
|
if (!isOpen()) return;
|
|
315
658
|
|
|
316
659
|
if (target.closest(`.${BACKDROP_CLASS}`)) {
|
|
@@ -353,6 +696,7 @@ export function setupMobileDrawer() {
|
|
|
353
696
|
clearDrag();
|
|
354
697
|
closeDrawer({ restoreFocus: false });
|
|
355
698
|
}
|
|
699
|
+
syncModelSheet();
|
|
356
700
|
};
|
|
357
701
|
|
|
358
702
|
const onVisibilityChange = () => {
|
|
@@ -361,11 +705,12 @@ export function setupMobileDrawer() {
|
|
|
361
705
|
clearDrag();
|
|
362
706
|
closeDrawer({ restoreFocus: false });
|
|
363
707
|
}
|
|
708
|
+
if (document.visibilityState === 'visible') syncModelSheet();
|
|
364
709
|
};
|
|
365
710
|
|
|
366
711
|
// ---- 手势:左缘右滑打开;抽屉内左滑关闭;拖动跟手 ----
|
|
367
712
|
const onTouchStart = (event) => {
|
|
368
|
-
if (disposed || !isMobileViewport() || event.touches?.length !== 1) return;
|
|
713
|
+
if (modelPortal || disposed || !isMobileViewport() || event.touches?.length !== 1) return;
|
|
369
714
|
const touch = event.touches[0];
|
|
370
715
|
const sidebar = document.querySelector('div[class*="_sidebarCol"]');
|
|
371
716
|
if (!sidebar) return;
|
|
@@ -401,7 +746,7 @@ export function setupMobileDrawer() {
|
|
|
401
746
|
};
|
|
402
747
|
|
|
403
748
|
const onTouchMove = (event) => {
|
|
404
|
-
if (!gesture || disposed) return;
|
|
749
|
+
if (modelPortal || !gesture || disposed) return;
|
|
405
750
|
const touch = event.touches?.[0];
|
|
406
751
|
if (!touch) return;
|
|
407
752
|
const dx = touch.clientX - gesture.startX;
|
|
@@ -450,12 +795,12 @@ export function setupMobileDrawer() {
|
|
|
450
795
|
};
|
|
451
796
|
|
|
452
797
|
const onTouchEnd = (event) => {
|
|
453
|
-
if (!gesture) return;
|
|
798
|
+
if (modelPortal || !gesture) return;
|
|
454
799
|
finishGesture(event.changedTouches?.[0]);
|
|
455
800
|
};
|
|
456
801
|
|
|
457
802
|
const onTouchCancel = () => {
|
|
458
|
-
if (!gesture) return;
|
|
803
|
+
if (modelPortal || !gesture) return;
|
|
459
804
|
finishGesture(null);
|
|
460
805
|
};
|
|
461
806
|
|
|
@@ -480,10 +825,12 @@ export function setupMobileDrawer() {
|
|
|
480
825
|
if (!document.querySelector(`.${HEADER_CLASS}`) || !document.querySelector(`.${MENU_CLASS}`) || !document.querySelector(`.${BACKDROP_CLASS}`) || !document.getElementById(MOBILE_DRAWER_CSS_ID)) {
|
|
481
826
|
ensureElements();
|
|
482
827
|
}
|
|
828
|
+
syncModelSheet();
|
|
483
829
|
});
|
|
484
830
|
observer.observe(document.body, { childList: true });
|
|
485
831
|
}
|
|
486
832
|
|
|
833
|
+
syncModelSheet();
|
|
487
834
|
syncAria();
|
|
488
835
|
|
|
489
836
|
return () => {
|
|
@@ -498,6 +845,7 @@ export function setupMobileDrawer() {
|
|
|
498
845
|
window.removeEventListener('touchmove', onTouchMove);
|
|
499
846
|
window.removeEventListener('touchend', onTouchEnd);
|
|
500
847
|
window.removeEventListener('touchcancel', onTouchCancel);
|
|
848
|
+
clearModelSheet();
|
|
501
849
|
clearDrag();
|
|
502
850
|
document.body?.classList.remove(OPEN_CLASS);
|
|
503
851
|
document.body?.classList.remove('qzsy-drawer-ready');
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qzsy/dsh-bridge",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.1",
|
|
4
4
|
"description": "DSH 远程隧道云官方客户端插件:一条命令把本机 DeepSeek Harness 接入云端隧道(设备码绑定、短期令牌、本地门禁代理、HTTP/SSE/WebSocket 反向隧道)。",
|
|
5
|
-
"releaseNotes": "【v3.
|
|
5
|
+
"releaseNotes": "【v3.4.1】模型 sheet 识别收紧\\n• 模型菜单改造后立即清空触发链,关闭后再开账号菜单不会误套底部 sheet\\n• 优先按 data-model-id 等模型标记定位;无搜索框宿主的兜底窗口收紧到 2.5 秒\\n• 第一层「模型 / 推理等级」popover 保持不变",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "lib/index.js",
|
|
8
8
|
"exports": {
|