@qzsy/dsh-bridge 3.4.3 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +2 -1
- package/client/client.js +175 -27
- package/client/mobile.js +176 -23
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# 更新日志
|
|
2
2
|
|
|
3
|
+
## [v3.5.0] - 2026-11-02
|
|
4
|
+
|
|
5
|
+
### ✨ 新增
|
|
6
|
+
|
|
7
|
+
- **模型 → 思考级别连招**:在模型 sheet 里选中任意模型后,不再直接关闭整个菜单,而是自动重新打开设置菜单并进入「推理等级」子菜单,以同一个底部 sheet 呈现 Off / Low / High / Max,选完才关闭。
|
|
8
|
+
- **无闪跳过渡**:选中模型时先把旧 sheet 快照留在原地,宿主菜单在背后切换,推理 sheet 滑入后再移除快照;中间宿主菜单隐藏,不会闪出桌面 popover。
|
|
9
|
+
- 模型选中、推理等级选中仍由宿主完成,插件只负责串联和 sheet 化;桌面端不变。
|
|
10
|
+
|
|
11
|
+
### 🧪 验证
|
|
12
|
+
|
|
13
|
+
- 独立宿主模拟:模型 sheet → 选模型 → 自动进入推理 sheet(Off/Low/High/Max)→ 选等级关闭并清理,4/4 通过。
|
|
14
|
+
- 运行中的 DSH:待 3.5.0 安装后验证完整链路。
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## [v3.4.4] - 2026-11-02
|
|
18
|
+
|
|
19
|
+
### 🐛 修复
|
|
20
|
+
|
|
21
|
+
- **模型 sheet 不再因 blur 关闭**:3.4.3 在 sheet 套用后 `blur` 搜索框,宿主菜单会因 focusout 直接关闭,看起来像「点模型没反应」。现在改为给搜索框加 `readonly` + `inputmode=none`:保留焦点、不弹键盘,宿主菜单稳定打开。
|
|
22
|
+
- **用户主动搜索时恢复输入**:监听搜索框 `pointerdown` / `touchstart`,用户主动点的时候恢复 `readonly` 和 `inputmode` 并聚焦,需要搜索仍可正常输入。
|
|
23
|
+
- 模型列表默认完整显示,不再被 iOS 键盘遮挡。
|
|
24
|
+
|
|
25
|
+
### 🧪 验证
|
|
26
|
+
|
|
27
|
+
- 模拟宿主自动 focus:sheet 保持打开,搜索框 `readOnly=true`、`inputmode=none`;用户 pointerdown 后 `readOnly=false` 且聚焦,4/4 通过。
|
|
28
|
+
- 运行中的 DSH:模型菜单打开后保持存在,sheet/遮罩正常。
|
|
29
|
+
|
|
30
|
+
|
|
3
31
|
## [v3.4.3] - 2026-11-02
|
|
4
32
|
|
|
5
33
|
### 🐛 修复
|
package/README.md
CHANGED
|
@@ -48,7 +48,8 @@ dsh plugin --profile <name> add @qzsy/dsh-bridge
|
|
|
48
48
|
|
|
49
49
|
- 内容区占满屏宽,左上角「菜单」按钮打开会话 / 工作区 / 插件入口;
|
|
50
50
|
- 支持手势:左缘向右滑打开、抽屉内向左滑关闭,拖动时跟手,松手吸附;
|
|
51
|
-
- 模型切换器在手机上改为底部 sheet
|
|
51
|
+
- 模型切换器在手机上改为底部 sheet:全宽、抓手、遮罩,支持下拉/点遮罩关闭;默认不弹键盘(搜索框 `readonly`),需要搜索时再点;
|
|
52
|
+
- 选完模型自动继续进入「推理等级」sheet,Off / Low / High / Max 选完才关闭,手机上和桌面菜单一样连贯;
|
|
52
53
|
- 全面屏自动避开安全区:刘海 / 状态栏 / 底部 home indicator 不遮挡内容;
|
|
53
54
|
- 点遮罩、点会话行、按 Esc 或转回桌面宽度都会自动收起;
|
|
54
55
|
- 桌面端保持 DSH 原生布局,不受影响。
|
package/client/client.js
CHANGED
|
@@ -463,6 +463,15 @@ var MOBILE_DRAWER_CSS = `
|
|
|
463
463
|
transition: transform 220ms ease, opacity 220ms ease !important;
|
|
464
464
|
animation: none !important;
|
|
465
465
|
}
|
|
466
|
+
/* \u9009\u4E2D\u6A21\u578B\u540E\u8FDB\u5165\u300C\u63A8\u7406\u7B49\u7EA7\u300D\u7684\u8FC7\u6E21\uFF1A\u65E7 sheet \u7684\u5FEB\u7167\u7559\u5728\u539F\u5730\uFF0C\u5BBF\u4E3B\u83DC\u5355\u5148\u9690\u85CF */
|
|
467
|
+
[data-qzsy-model-sheet-clone] {
|
|
468
|
+
z-index: 1199 !important;
|
|
469
|
+
pointer-events: none !important;
|
|
470
|
+
}
|
|
471
|
+
body.qzsy-model-advancing div[id$="-menu"]:not([data-qzsy-model-sheet]):not([data-qzsy-model-sheet-clone]) {
|
|
472
|
+
visibility: hidden !important;
|
|
473
|
+
pointer-events: none !important;
|
|
474
|
+
}
|
|
466
475
|
body.qzsy-model-sheet-dragging [data-qzsy-model-sheet] {
|
|
467
476
|
transform: translateY(var(--qzsy-model-sheet-y, 0px)) !important;
|
|
468
477
|
transition: none !important;
|
|
@@ -516,6 +525,10 @@ function setupMobileDrawer() {
|
|
|
516
525
|
let modelChainUntil = 0;
|
|
517
526
|
let modelDrag = null;
|
|
518
527
|
let modelDragHandlers = null;
|
|
528
|
+
let modelClone = null;
|
|
529
|
+
let modelAdvancing = false;
|
|
530
|
+
let reasoningStep = false;
|
|
531
|
+
let modelAdvanceToken = 0;
|
|
519
532
|
const preventDrawerScroll = (event) => {
|
|
520
533
|
if (!gesture || !event.cancelable) return;
|
|
521
534
|
const touch = event.touches?.[0];
|
|
@@ -633,7 +646,7 @@ function setupMobileDrawer() {
|
|
|
633
646
|
if (search) {
|
|
634
647
|
const menu = search.closest('[role="menu"]') || search.closest('[class*="_menu"]');
|
|
635
648
|
const portal = bodyPortalOf(menu || search);
|
|
636
|
-
if (portal && visiblePortal(portal)) {
|
|
649
|
+
if (portal && visiblePortal(portal) && !portal.hasAttribute("data-qzsy-model-sheet-clone")) {
|
|
637
650
|
const s = getComputedStyle(portal);
|
|
638
651
|
if ((s.position === "fixed" || s.position === "absolute") && portal.getBoundingClientRect().width > 120) return portal;
|
|
639
652
|
}
|
|
@@ -641,7 +654,8 @@ function setupMobileDrawer() {
|
|
|
641
654
|
const candidates = [];
|
|
642
655
|
for (const el of document.body.children) {
|
|
643
656
|
if (!(el instanceof HTMLElement)) continue;
|
|
644
|
-
if (el === modelBackdrop || el.classList.contains(HEADER_CLASS) || el.classList.contains(BACKDROP_CLASS)) continue;
|
|
657
|
+
if (el === modelBackdrop || el === modelClone || el.classList.contains(HEADER_CLASS) || el.classList.contains(BACKDROP_CLASS)) continue;
|
|
658
|
+
if (el.hasAttribute("data-qzsy-model-sheet-clone")) continue;
|
|
645
659
|
if (!visiblePortal(el)) continue;
|
|
646
660
|
const s = getComputedStyle(el);
|
|
647
661
|
if (s.position !== "fixed" && s.position !== "absolute") continue;
|
|
@@ -661,6 +675,26 @@ function setupMobileDrawer() {
|
|
|
661
675
|
candidates.sort((a, b) => Number(b.hasSearch) - Number(a.hasSearch) || Number(b.hasModelMarker) - Number(a.hasModelMarker) || b.rows - a.rows);
|
|
662
676
|
return candidates[0]?.el || null;
|
|
663
677
|
};
|
|
678
|
+
const findReasoningPortal = () => {
|
|
679
|
+
if (!isMobileViewport()) return null;
|
|
680
|
+
const candidates = [];
|
|
681
|
+
for (const el of document.body.children) {
|
|
682
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
683
|
+
if (el === modelBackdrop || el === modelClone || el.classList.contains(HEADER_CLASS) || el.classList.contains(BACKDROP_CLASS)) continue;
|
|
684
|
+
if (el.hasAttribute("data-qzsy-model-sheet") || el.hasAttribute("data-qzsy-model-sheet-clone")) continue;
|
|
685
|
+
if (!visiblePortal(el)) continue;
|
|
686
|
+
const s = getComputedStyle(el);
|
|
687
|
+
if (s.position !== "fixed" && s.position !== "absolute") continue;
|
|
688
|
+
const menu = el.querySelector('[role="menu"]');
|
|
689
|
+
if (!menu) continue;
|
|
690
|
+
const radios = menu.querySelectorAll('button[role="menuitemradio"], [role="menuitemradio"], input[type="radio"]').length;
|
|
691
|
+
if (radios < 1) continue;
|
|
692
|
+
const text = menu.innerText || "";
|
|
693
|
+
if (/搜索模型|Search model|DeepSeek|claude|模型与推理等级/i.test(text)) continue;
|
|
694
|
+
candidates.push(el);
|
|
695
|
+
}
|
|
696
|
+
return candidates[candidates.length - 1] || null;
|
|
697
|
+
};
|
|
664
698
|
const hostBackdropPresent = () => {
|
|
665
699
|
for (const el of document.body.children) {
|
|
666
700
|
if (!(el instanceof HTMLElement)) continue;
|
|
@@ -700,7 +734,23 @@ function setupMobileDrawer() {
|
|
|
700
734
|
}, 450);
|
|
701
735
|
}, 120);
|
|
702
736
|
};
|
|
737
|
+
const dismissHostMenus = () => {
|
|
738
|
+
try {
|
|
739
|
+
document.body.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true, cancelable: true }));
|
|
740
|
+
} catch {
|
|
741
|
+
}
|
|
742
|
+
try {
|
|
743
|
+
document.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true, cancelable: true }));
|
|
744
|
+
} catch {
|
|
745
|
+
}
|
|
746
|
+
};
|
|
703
747
|
const closeModelSheetNow = () => {
|
|
748
|
+
if (modelAdvancing) {
|
|
749
|
+
cancelAdvanceOverlay();
|
|
750
|
+
clearModelSheet();
|
|
751
|
+
dismissHostMenus();
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
704
754
|
const portal = modelPortal;
|
|
705
755
|
if (!portal) return;
|
|
706
756
|
document.body.classList.remove("qzsy-model-sheet-open");
|
|
@@ -709,6 +759,10 @@ function setupMobileDrawer() {
|
|
|
709
759
|
requestHostModelClose(portal);
|
|
710
760
|
};
|
|
711
761
|
const closeModelSheetAnimated = () => {
|
|
762
|
+
if (modelAdvancing) {
|
|
763
|
+
closeModelSheetNow();
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
712
766
|
const portal = modelPortal;
|
|
713
767
|
if (!portal) return;
|
|
714
768
|
portal.classList.add("qzsy-model-sheet-closing");
|
|
@@ -778,68 +832,158 @@ function setupMobileDrawer() {
|
|
|
778
832
|
portal.addEventListener("touchcancel", onEnd, { passive: true });
|
|
779
833
|
modelDragHandlers = { onStart, onMove, onEnd };
|
|
780
834
|
};
|
|
781
|
-
const clearModelSheet = () => {
|
|
835
|
+
const clearModelSheet = ({ keepChrome = false } = {}) => {
|
|
782
836
|
if (modelPortal) {
|
|
783
837
|
detachModelDrag();
|
|
784
838
|
modelPortal.removeAttribute("data-qzsy-model-sheet");
|
|
839
|
+
modelPortal.removeAttribute("data-qzsy-model-step");
|
|
785
840
|
modelPortal.classList.remove("qzsy-model-sheet", "qzsy-model-sheet-closing");
|
|
786
841
|
}
|
|
787
842
|
modelPortal = null;
|
|
788
843
|
modelDrag = null;
|
|
789
|
-
|
|
844
|
+
modelAdvanceToken += 1;
|
|
845
|
+
modelAdvancing = false;
|
|
846
|
+
reasoningStep = false;
|
|
847
|
+
if (modelClone) {
|
|
848
|
+
modelClone.remove();
|
|
849
|
+
modelClone = null;
|
|
850
|
+
}
|
|
851
|
+
document.body?.classList.remove("qzsy-model-advancing", "qzsy-model-sheet-dragging");
|
|
790
852
|
document.body?.style.removeProperty("--qzsy-model-sheet-y");
|
|
791
853
|
document.body?.style.removeProperty("--qzsy-model-sheet-progress");
|
|
792
|
-
if (
|
|
793
|
-
|
|
794
|
-
modelBackdrop
|
|
854
|
+
if (!keepChrome) {
|
|
855
|
+
document.body?.classList.remove("qzsy-model-sheet-open");
|
|
856
|
+
if (modelBackdrop) {
|
|
857
|
+
modelBackdrop.remove();
|
|
858
|
+
modelBackdrop = null;
|
|
859
|
+
}
|
|
795
860
|
}
|
|
796
861
|
};
|
|
797
862
|
const applyModelSheet = (portal) => {
|
|
798
863
|
if (modelPortal === portal) return;
|
|
799
|
-
|
|
864
|
+
const step = reasoningStep ? "reasoning" : "model";
|
|
865
|
+
const keepChrome = modelAdvancing && Boolean(modelClone);
|
|
866
|
+
clearModelSheet({ keepChrome });
|
|
800
867
|
modelPortal = portal;
|
|
801
868
|
modelChainUntil = 0;
|
|
802
869
|
portal.setAttribute("data-qzsy-model-sheet", "");
|
|
870
|
+
portal.setAttribute("data-qzsy-model-step", step);
|
|
803
871
|
portal.classList.add("qzsy-model-sheet");
|
|
804
872
|
attachModelDrag(portal);
|
|
805
873
|
ensureModelBackdrop();
|
|
806
874
|
const input = portal.querySelector('input[placeholder*="\u641C\u7D22\u6A21\u578B"], input[placeholder*="Search model"], input[type="search"], input');
|
|
807
875
|
if (input) {
|
|
808
876
|
let userIntent = false;
|
|
809
|
-
const
|
|
877
|
+
const originalReadOnly = input.readOnly;
|
|
878
|
+
const originalInputMode = input.getAttribute("inputmode");
|
|
879
|
+
const enableSearch = () => {
|
|
880
|
+
if (userIntent) return;
|
|
810
881
|
userIntent = true;
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
try {
|
|
818
|
-
input.blur();
|
|
819
|
-
} catch {
|
|
820
|
-
}
|
|
882
|
+
input.readOnly = originalReadOnly;
|
|
883
|
+
if (originalInputMode === null) input.removeAttribute("inputmode");
|
|
884
|
+
else input.setAttribute("inputmode", originalInputMode);
|
|
885
|
+
try {
|
|
886
|
+
input.focus({ preventScroll: true });
|
|
887
|
+
} catch {
|
|
821
888
|
}
|
|
822
889
|
};
|
|
823
|
-
|
|
824
|
-
|
|
890
|
+
input.addEventListener("pointerdown", enableSearch);
|
|
891
|
+
input.addEventListener("touchstart", enableSearch, { passive: true });
|
|
892
|
+
const enforceNoKeyboard = () => {
|
|
893
|
+
if (!input.isConnected || userIntent) return;
|
|
894
|
+
if (!input.readOnly) input.readOnly = true;
|
|
895
|
+
if (!input.hasAttribute("readonly")) input.setAttribute("readonly", "");
|
|
896
|
+
if (input.getAttribute("inputmode") !== "none") input.setAttribute("inputmode", "none");
|
|
825
897
|
};
|
|
826
|
-
input.addEventListener("focus",
|
|
827
|
-
|
|
828
|
-
window.
|
|
829
|
-
window.setTimeout(
|
|
830
|
-
window.setTimeout(
|
|
831
|
-
window.setTimeout(() => input.removeEventListener("focus",
|
|
898
|
+
input.addEventListener("focus", enforceNoKeyboard);
|
|
899
|
+
enforceNoKeyboard();
|
|
900
|
+
window.setTimeout(enforceNoKeyboard, 0);
|
|
901
|
+
window.setTimeout(enforceNoKeyboard, 80);
|
|
902
|
+
window.setTimeout(enforceNoKeyboard, 260);
|
|
903
|
+
window.setTimeout(() => input.removeEventListener("focus", enforceNoKeyboard), 1e3);
|
|
832
904
|
}
|
|
833
905
|
window.requestAnimationFrame(() => {
|
|
834
906
|
if (!disposed && modelPortal === portal) document.body.classList.add("qzsy-model-sheet-open");
|
|
835
907
|
});
|
|
836
908
|
};
|
|
909
|
+
const cancelAdvanceOverlay = () => {
|
|
910
|
+
modelAdvanceToken += 1;
|
|
911
|
+
modelAdvancing = false;
|
|
912
|
+
reasoningStep = false;
|
|
913
|
+
document.body?.classList.remove("qzsy-model-advancing");
|
|
914
|
+
if (modelClone) {
|
|
915
|
+
modelClone.remove();
|
|
916
|
+
modelClone = null;
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
const beginReasoningAdvance = (portal) => {
|
|
920
|
+
if (disposed || modelAdvancing) return;
|
|
921
|
+
if (portal.getAttribute("data-qzsy-model-step") !== "model") return;
|
|
922
|
+
if (!portal.querySelector('button[role="menuitemradio"], [role="menuitemradio"]')) return;
|
|
923
|
+
modelAdvancing = true;
|
|
924
|
+
const token = ++modelAdvanceToken;
|
|
925
|
+
document.body.classList.add("qzsy-model-advancing");
|
|
926
|
+
modelClone = portal.cloneNode(true);
|
|
927
|
+
modelClone.setAttribute("data-qzsy-model-sheet-clone", "");
|
|
928
|
+
modelClone.setAttribute("aria-hidden", "true");
|
|
929
|
+
for (const el of modelClone.querySelectorAll("[id]")) el.removeAttribute("id");
|
|
930
|
+
document.body.appendChild(modelClone);
|
|
931
|
+
window.setTimeout(() => {
|
|
932
|
+
if (disposed || token !== modelAdvanceToken || !modelAdvancing) return;
|
|
933
|
+
const trigger = document.querySelector(
|
|
934
|
+
'button[aria-haspopup="menu"][aria-label*="\u9009\u62E9\u6A21\u578B"], button[aria-haspopup="menu"][aria-label*="Select model"], button[aria-haspopup="menu"][aria-label*="\u6A21\u578B"]'
|
|
935
|
+
);
|
|
936
|
+
try {
|
|
937
|
+
trigger?.click();
|
|
938
|
+
} catch {
|
|
939
|
+
}
|
|
940
|
+
let attempts = 0;
|
|
941
|
+
const openReasonCell = () => {
|
|
942
|
+
if (disposed || token !== modelAdvanceToken || !modelAdvancing) return;
|
|
943
|
+
const cell = [...document.querySelectorAll('button[role="menuitem"]')].find((b) => /推理等级|Reasoning effort|Reasoning level|推理强度/i.test(b.innerText || ""));
|
|
944
|
+
if (cell) {
|
|
945
|
+
reasoningStep = true;
|
|
946
|
+
try {
|
|
947
|
+
cell.click();
|
|
948
|
+
} catch {
|
|
949
|
+
}
|
|
950
|
+
window.setTimeout(() => {
|
|
951
|
+
if (disposed || token !== modelAdvanceToken) return;
|
|
952
|
+
if (!modelPortal) {
|
|
953
|
+
cancelAdvanceOverlay();
|
|
954
|
+
clearModelSheet();
|
|
955
|
+
}
|
|
956
|
+
}, 900);
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
attempts += 1;
|
|
960
|
+
if (attempts > 16) {
|
|
961
|
+
cancelAdvanceOverlay();
|
|
962
|
+
clearModelSheet();
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
window.setTimeout(openReasonCell, 60);
|
|
966
|
+
};
|
|
967
|
+
window.setTimeout(openReasonCell, 120);
|
|
968
|
+
}, 140);
|
|
969
|
+
};
|
|
837
970
|
const syncModelSheet = () => {
|
|
838
971
|
if (disposed) return;
|
|
839
972
|
if (!isMobileViewport()) {
|
|
840
973
|
clearModelSheet();
|
|
841
974
|
return;
|
|
842
975
|
}
|
|
976
|
+
if (modelAdvancing) {
|
|
977
|
+
if (reasoningStep) {
|
|
978
|
+
const next = findReasoningPortal();
|
|
979
|
+
if (next) {
|
|
980
|
+
applyModelSheet(next);
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
if (modelPortal && !visiblePortal(modelPortal)) modelPortal = null;
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
843
987
|
if (modelPortal && !visiblePortal(modelPortal)) clearModelSheet();
|
|
844
988
|
const found = findModelPortal();
|
|
845
989
|
if (!found) {
|
|
@@ -868,6 +1012,10 @@ function setupMobileDrawer() {
|
|
|
868
1012
|
window.setTimeout(syncModelSheet, 30);
|
|
869
1013
|
window.setTimeout(syncModelSheet, 180);
|
|
870
1014
|
}
|
|
1015
|
+
const modelOption = target.closest('button[role="menuitemradio"], [role="menuitemradio"]');
|
|
1016
|
+
if (modelOption && modelPortal && modelPortal.getAttribute("data-qzsy-model-step") === "model" && modelPortal.contains(modelOption)) {
|
|
1017
|
+
beginReasoningAdvance(modelPortal);
|
|
1018
|
+
}
|
|
871
1019
|
if (!isOpen()) return;
|
|
872
1020
|
if (target.closest(`.${BACKDROP_CLASS}`)) {
|
|
873
1021
|
closeDrawer();
|
package/client/mobile.js
CHANGED
|
@@ -260,6 +260,15 @@ export const MOBILE_DRAWER_CSS = `
|
|
|
260
260
|
transition: transform 220ms ease, opacity 220ms ease !important;
|
|
261
261
|
animation: none !important;
|
|
262
262
|
}
|
|
263
|
+
/* 选中模型后进入「推理等级」的过渡:旧 sheet 的快照留在原地,宿主菜单先隐藏 */
|
|
264
|
+
[data-qzsy-model-sheet-clone] {
|
|
265
|
+
z-index: 1199 !important;
|
|
266
|
+
pointer-events: none !important;
|
|
267
|
+
}
|
|
268
|
+
body.qzsy-model-advancing div[id$="-menu"]:not([data-qzsy-model-sheet]):not([data-qzsy-model-sheet-clone]) {
|
|
269
|
+
visibility: hidden !important;
|
|
270
|
+
pointer-events: none !important;
|
|
271
|
+
}
|
|
263
272
|
body.qzsy-model-sheet-dragging [data-qzsy-model-sheet] {
|
|
264
273
|
transform: translateY(var(--qzsy-model-sheet-y, 0px)) !important;
|
|
265
274
|
transition: none !important;
|
|
@@ -322,6 +331,10 @@ export function setupMobileDrawer() {
|
|
|
322
331
|
let modelChainUntil = 0;
|
|
323
332
|
let modelDrag = null;
|
|
324
333
|
let modelDragHandlers = null;
|
|
334
|
+
let modelClone = null;
|
|
335
|
+
let modelAdvancing = false;
|
|
336
|
+
let reasoningStep = false;
|
|
337
|
+
let modelAdvanceToken = 0;
|
|
325
338
|
|
|
326
339
|
const preventDrawerScroll = (event) => {
|
|
327
340
|
if (!gesture || !event.cancelable) return;
|
|
@@ -451,7 +464,7 @@ export function setupMobileDrawer() {
|
|
|
451
464
|
if (search) {
|
|
452
465
|
const menu = search.closest('[role="menu"]') || search.closest('[class*="_menu"]');
|
|
453
466
|
const portal = bodyPortalOf(menu || search);
|
|
454
|
-
if (portal && visiblePortal(portal)) {
|
|
467
|
+
if (portal && visiblePortal(portal) && !portal.hasAttribute('data-qzsy-model-sheet-clone')) {
|
|
455
468
|
const s = getComputedStyle(portal);
|
|
456
469
|
if ((s.position === 'fixed' || s.position === 'absolute') && portal.getBoundingClientRect().width > 120) return portal;
|
|
457
470
|
}
|
|
@@ -460,7 +473,8 @@ export function setupMobileDrawer() {
|
|
|
460
473
|
const candidates = [];
|
|
461
474
|
for (const el of document.body.children) {
|
|
462
475
|
if (!(el instanceof HTMLElement)) continue;
|
|
463
|
-
if (el === modelBackdrop || el.classList.contains(HEADER_CLASS) || el.classList.contains(BACKDROP_CLASS)) continue;
|
|
476
|
+
if (el === modelBackdrop || el === modelClone || el.classList.contains(HEADER_CLASS) || el.classList.contains(BACKDROP_CLASS)) continue;
|
|
477
|
+
if (el.hasAttribute('data-qzsy-model-sheet-clone')) continue;
|
|
464
478
|
if (!visiblePortal(el)) continue;
|
|
465
479
|
const s = getComputedStyle(el);
|
|
466
480
|
if (s.position !== 'fixed' && s.position !== 'absolute') continue;
|
|
@@ -485,6 +499,28 @@ export function setupMobileDrawer() {
|
|
|
485
499
|
return candidates[0]?.el || null;
|
|
486
500
|
};
|
|
487
501
|
|
|
502
|
+
const findReasoningPortal = () => {
|
|
503
|
+
if (!isMobileViewport()) return null;
|
|
504
|
+
const candidates = [];
|
|
505
|
+
for (const el of document.body.children) {
|
|
506
|
+
if (!(el instanceof HTMLElement)) continue;
|
|
507
|
+
if (el === modelBackdrop || el === modelClone || el.classList.contains(HEADER_CLASS) || el.classList.contains(BACKDROP_CLASS)) continue;
|
|
508
|
+
if (el.hasAttribute('data-qzsy-model-sheet') || el.hasAttribute('data-qzsy-model-sheet-clone')) continue;
|
|
509
|
+
if (!visiblePortal(el)) continue;
|
|
510
|
+
const s = getComputedStyle(el);
|
|
511
|
+
if (s.position !== 'fixed' && s.position !== 'absolute') continue;
|
|
512
|
+
const menu = el.querySelector('[role="menu"]');
|
|
513
|
+
if (!menu) continue;
|
|
514
|
+
const radios = menu.querySelectorAll('button[role="menuitemradio"], [role="menuitemradio"], input[type="radio"]').length;
|
|
515
|
+
if (radios < 1) continue;
|
|
516
|
+
const text = menu.innerText || '';
|
|
517
|
+
// 推理等级菜单只含 Off / Low / High / Max 等档位,不含模型搜索与模型分组
|
|
518
|
+
if (/搜索模型|Search model|DeepSeek|claude|模型与推理等级/i.test(text)) continue;
|
|
519
|
+
candidates.push(el);
|
|
520
|
+
}
|
|
521
|
+
return candidates[candidates.length - 1] || null;
|
|
522
|
+
};
|
|
523
|
+
|
|
488
524
|
const hostBackdropPresent = () => {
|
|
489
525
|
for (const el of document.body.children) {
|
|
490
526
|
if (!(el instanceof HTMLElement)) continue;
|
|
@@ -517,7 +553,18 @@ export function setupMobileDrawer() {
|
|
|
517
553
|
}, 120);
|
|
518
554
|
};
|
|
519
555
|
|
|
556
|
+
const dismissHostMenus = () => {
|
|
557
|
+
try { document.body.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, cancelable: true })); } catch { /* 忽略 */ }
|
|
558
|
+
try { document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true })); } catch { /* 忽略 */ }
|
|
559
|
+
};
|
|
560
|
+
|
|
520
561
|
const closeModelSheetNow = () => {
|
|
562
|
+
if (modelAdvancing) {
|
|
563
|
+
cancelAdvanceOverlay();
|
|
564
|
+
clearModelSheet();
|
|
565
|
+
dismissHostMenus();
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
521
568
|
const portal = modelPortal;
|
|
522
569
|
if (!portal) return;
|
|
523
570
|
document.body.classList.remove('qzsy-model-sheet-open');
|
|
@@ -527,6 +574,10 @@ export function setupMobileDrawer() {
|
|
|
527
574
|
};
|
|
528
575
|
|
|
529
576
|
const closeModelSheetAnimated = () => {
|
|
577
|
+
if (modelAdvancing) {
|
|
578
|
+
closeModelSheetNow();
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
530
581
|
const portal = modelPortal;
|
|
531
582
|
if (!portal) return;
|
|
532
583
|
portal.classList.add('qzsy-model-sheet-closing');
|
|
@@ -600,54 +651,77 @@ export function setupMobileDrawer() {
|
|
|
600
651
|
modelDragHandlers = { onStart, onMove, onEnd };
|
|
601
652
|
};
|
|
602
653
|
|
|
603
|
-
const clearModelSheet = () => {
|
|
654
|
+
const clearModelSheet = ({ keepChrome = false } = {}) => {
|
|
604
655
|
if (modelPortal) {
|
|
605
656
|
detachModelDrag();
|
|
606
657
|
modelPortal.removeAttribute('data-qzsy-model-sheet');
|
|
658
|
+
modelPortal.removeAttribute('data-qzsy-model-step');
|
|
607
659
|
modelPortal.classList.remove('qzsy-model-sheet', 'qzsy-model-sheet-closing');
|
|
608
660
|
}
|
|
609
661
|
modelPortal = null;
|
|
610
662
|
modelDrag = null;
|
|
611
|
-
|
|
663
|
+
modelAdvanceToken += 1;
|
|
664
|
+
modelAdvancing = false;
|
|
665
|
+
reasoningStep = false;
|
|
666
|
+
if (modelClone) {
|
|
667
|
+
modelClone.remove();
|
|
668
|
+
modelClone = null;
|
|
669
|
+
}
|
|
670
|
+
document.body?.classList.remove('qzsy-model-advancing', 'qzsy-model-sheet-dragging');
|
|
612
671
|
document.body?.style.removeProperty('--qzsy-model-sheet-y');
|
|
613
672
|
document.body?.style.removeProperty('--qzsy-model-sheet-progress');
|
|
614
|
-
if (
|
|
615
|
-
|
|
616
|
-
modelBackdrop
|
|
673
|
+
if (!keepChrome) {
|
|
674
|
+
document.body?.classList.remove('qzsy-model-sheet-open');
|
|
675
|
+
if (modelBackdrop) {
|
|
676
|
+
modelBackdrop.remove();
|
|
677
|
+
modelBackdrop = null;
|
|
678
|
+
}
|
|
617
679
|
}
|
|
618
680
|
};
|
|
619
681
|
|
|
620
682
|
const applyModelSheet = (portal) => {
|
|
621
683
|
if (modelPortal === portal) return;
|
|
622
|
-
|
|
684
|
+
const step = reasoningStep ? 'reasoning' : 'model';
|
|
685
|
+
const keepChrome = modelAdvancing && Boolean(modelClone);
|
|
686
|
+
clearModelSheet({ keepChrome });
|
|
623
687
|
modelPortal = portal;
|
|
624
688
|
modelChainUntil = 0;
|
|
625
689
|
portal.setAttribute('data-qzsy-model-sheet', '');
|
|
690
|
+
portal.setAttribute('data-qzsy-model-step', step);
|
|
626
691
|
portal.classList.add('qzsy-model-sheet');
|
|
627
692
|
attachModelDrag(portal);
|
|
628
693
|
ensureModelBackdrop();
|
|
629
694
|
|
|
630
695
|
// 宿主菜单默认会聚焦「搜索模型」,手机上会直接弹起键盘把 sheet 顶掉一半。
|
|
631
|
-
//
|
|
696
|
+
// 移动端模型数量通常很少:先把搜索框设为 readonly + inputmode=none,阻止键盘;
|
|
697
|
+
// 保留焦点,避免宿主因 focusout 关闭菜单。用户主动点搜索框时再恢复可输入。
|
|
632
698
|
const input = portal.querySelector('input[placeholder*="搜索模型"], input[placeholder*="Search model"], input[type="search"], input');
|
|
633
699
|
if (input) {
|
|
634
700
|
let userIntent = false;
|
|
635
|
-
const
|
|
636
|
-
input.
|
|
637
|
-
|
|
638
|
-
|
|
701
|
+
const originalReadOnly = input.readOnly;
|
|
702
|
+
const originalInputMode = input.getAttribute('inputmode');
|
|
703
|
+
const enableSearch = () => {
|
|
704
|
+
if (userIntent) return;
|
|
705
|
+
userIntent = true;
|
|
706
|
+
input.readOnly = originalReadOnly;
|
|
707
|
+
if (originalInputMode === null) input.removeAttribute('inputmode');
|
|
708
|
+
else input.setAttribute('inputmode', originalInputMode);
|
|
709
|
+
try { input.focus({ preventScroll: true }); } catch { /* 忽略 */ }
|
|
710
|
+
};
|
|
711
|
+
input.addEventListener('pointerdown', enableSearch);
|
|
712
|
+
input.addEventListener('touchstart', enableSearch, { passive: true });
|
|
713
|
+
const enforceNoKeyboard = () => {
|
|
639
714
|
if (!input.isConnected || userIntent) return;
|
|
640
|
-
if (
|
|
641
|
-
|
|
642
|
-
|
|
715
|
+
if (!input.readOnly) input.readOnly = true;
|
|
716
|
+
if (!input.hasAttribute('readonly')) input.setAttribute('readonly', '');
|
|
717
|
+
if (input.getAttribute('inputmode') !== 'none') input.setAttribute('inputmode', 'none');
|
|
643
718
|
};
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
window.
|
|
648
|
-
window.setTimeout(
|
|
649
|
-
window.setTimeout(
|
|
650
|
-
window.setTimeout(() => input.removeEventListener('focus', onFocus), 900);
|
|
719
|
+
input.addEventListener('focus', enforceNoKeyboard);
|
|
720
|
+
enforceNoKeyboard();
|
|
721
|
+
window.setTimeout(enforceNoKeyboard, 0);
|
|
722
|
+
window.setTimeout(enforceNoKeyboard, 80);
|
|
723
|
+
window.setTimeout(enforceNoKeyboard, 260);
|
|
724
|
+
window.setTimeout(() => input.removeEventListener('focus', enforceNoKeyboard), 1000);
|
|
651
725
|
}
|
|
652
726
|
|
|
653
727
|
window.requestAnimationFrame(() => {
|
|
@@ -655,12 +729,83 @@ export function setupMobileDrawer() {
|
|
|
655
729
|
});
|
|
656
730
|
};
|
|
657
731
|
|
|
732
|
+
const cancelAdvanceOverlay = () => {
|
|
733
|
+
modelAdvanceToken += 1;
|
|
734
|
+
modelAdvancing = false;
|
|
735
|
+
reasoningStep = false;
|
|
736
|
+
document.body?.classList.remove('qzsy-model-advancing');
|
|
737
|
+
if (modelClone) {
|
|
738
|
+
modelClone.remove();
|
|
739
|
+
modelClone = null;
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
|
|
743
|
+
// 选完模型后自动进入推理等级:保留旧 sheet 快照,宿主菜单在背后切换,再滑入推理 sheet
|
|
744
|
+
const beginReasoningAdvance = (portal) => {
|
|
745
|
+
if (disposed || modelAdvancing) return;
|
|
746
|
+
if (portal.getAttribute('data-qzsy-model-step') !== 'model') return;
|
|
747
|
+
if (!portal.querySelector('button[role="menuitemradio"], [role="menuitemradio"]')) return;
|
|
748
|
+
modelAdvancing = true;
|
|
749
|
+
const token = ++modelAdvanceToken;
|
|
750
|
+
document.body.classList.add('qzsy-model-advancing');
|
|
751
|
+
modelClone = portal.cloneNode(true);
|
|
752
|
+
modelClone.setAttribute('data-qzsy-model-sheet-clone', '');
|
|
753
|
+
modelClone.setAttribute('aria-hidden', 'true');
|
|
754
|
+
for (const el of modelClone.querySelectorAll('[id]')) el.removeAttribute('id');
|
|
755
|
+
document.body.appendChild(modelClone);
|
|
756
|
+
|
|
757
|
+
window.setTimeout(() => {
|
|
758
|
+
if (disposed || token !== modelAdvanceToken || !modelAdvancing) return;
|
|
759
|
+
const trigger = document.querySelector(
|
|
760
|
+
'button[aria-haspopup="menu"][aria-label*="选择模型"], button[aria-haspopup="menu"][aria-label*="Select model"], button[aria-haspopup="menu"][aria-label*="模型"]',
|
|
761
|
+
);
|
|
762
|
+
try { trigger?.click(); } catch { /* 忽略 */ }
|
|
763
|
+
let attempts = 0;
|
|
764
|
+
const openReasonCell = () => {
|
|
765
|
+
if (disposed || token !== modelAdvanceToken || !modelAdvancing) return;
|
|
766
|
+
const cell = [...document.querySelectorAll('button[role="menuitem"]')].find((b) => /推理等级|Reasoning effort|Reasoning level|推理强度/i.test(b.innerText || ''));
|
|
767
|
+
if (cell) {
|
|
768
|
+
reasoningStep = true;
|
|
769
|
+
try { cell.click(); } catch { /* 忽略 */ }
|
|
770
|
+
window.setTimeout(() => {
|
|
771
|
+
if (disposed || token !== modelAdvanceToken) return;
|
|
772
|
+
if (!modelPortal) {
|
|
773
|
+
// 推理菜单没出现:撤掉 overlay,避免卡在半路
|
|
774
|
+
cancelAdvanceOverlay();
|
|
775
|
+
clearModelSheet();
|
|
776
|
+
}
|
|
777
|
+
}, 900);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
attempts += 1;
|
|
781
|
+
if (attempts > 16) {
|
|
782
|
+
cancelAdvanceOverlay();
|
|
783
|
+
clearModelSheet();
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
window.setTimeout(openReasonCell, 60);
|
|
787
|
+
};
|
|
788
|
+
window.setTimeout(openReasonCell, 120);
|
|
789
|
+
}, 140);
|
|
790
|
+
};
|
|
791
|
+
|
|
658
792
|
const syncModelSheet = () => {
|
|
659
793
|
if (disposed) return;
|
|
660
794
|
if (!isMobileViewport()) {
|
|
661
795
|
clearModelSheet();
|
|
662
796
|
return;
|
|
663
797
|
}
|
|
798
|
+
if (modelAdvancing) {
|
|
799
|
+
if (reasoningStep) {
|
|
800
|
+
const next = findReasoningPortal();
|
|
801
|
+
if (next) {
|
|
802
|
+
applyModelSheet(next);
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
if (modelPortal && !visiblePortal(modelPortal)) modelPortal = null;
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
664
809
|
if (modelPortal && !visiblePortal(modelPortal)) clearModelSheet();
|
|
665
810
|
const found = findModelPortal();
|
|
666
811
|
if (!found) {
|
|
@@ -694,6 +839,14 @@ export function setupMobileDrawer() {
|
|
|
694
839
|
window.setTimeout(syncModelSheet, 180);
|
|
695
840
|
}
|
|
696
841
|
|
|
842
|
+
// 选完模型继续进入推理等级,而不是直接关掉整个菜单
|
|
843
|
+
const modelOption = target.closest('button[role="menuitemradio"], [role="menuitemradio"]');
|
|
844
|
+
if (modelOption && modelPortal
|
|
845
|
+
&& modelPortal.getAttribute('data-qzsy-model-step') === 'model'
|
|
846
|
+
&& modelPortal.contains(modelOption)) {
|
|
847
|
+
beginReasoningAdvance(modelPortal);
|
|
848
|
+
}
|
|
849
|
+
|
|
697
850
|
if (!isOpen()) return;
|
|
698
851
|
|
|
699
852
|
if (target.closest(`.${BACKDROP_CLASS}`)) {
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qzsy/dsh-bridge",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"description": "DSH 远程隧道云官方客户端插件:一条命令把本机 DeepSeek Harness 接入云端隧道(设备码绑定、短期令牌、本地门禁代理、HTTP/SSE/WebSocket 反向隧道)。",
|
|
5
|
-
"releaseNotes": "【v3.
|
|
5
|
+
"releaseNotes": "【v3.5.0】模型 → 思考级别连招\\n• 选完模型自动进入推理等级 sheet(Off / Low / High / Max),选完才关闭\\n• 旧 sheet 快照过渡,宿主菜单在背后切换,不闪桌面 popover\\n• 模型和推理等级仍由宿主选择;桌面端不变",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "lib/index.js",
|
|
8
8
|
"exports": {
|