@qzsy/dsh-bridge 3.4.4 → 3.5.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 CHANGED
@@ -1,5 +1,32 @@
1
1
  # 更新日志
2
2
 
3
+ ## [v3.5.1] - 2026-11-02
4
+
5
+ ### 🐛 修复
6
+
7
+ - **选当前模型也能继续**:选中项与当前模型相同时,宿主不一定会关闭模型菜单;现在会先按 Esc / 外部点击收起旧菜单,再打开父级并进入「推理等级」,不会卡在半路。
8
+ - **父菜单点击时序**:等父 popover 进入动画和事件绑定完成后再点「推理等级」,避免点得太早导致推理 sheet 不出现。
9
+ - **过渡期宿主菜单改为透明隐藏**:不再用 `visibility:hidden`,避免宿主按钮虽然可见性隐藏却无法被程序化点击;推理 sheet 出现后立即恢复。
10
+
11
+ ### 🧪 验证
12
+
13
+ - 独立宿主模拟:模型 sheet → 选模型 → 推理 sheet → 选等级关闭,4/4;待安装后跑真实 DSH。
14
+
15
+
16
+ ## [v3.5.0] - 2026-11-02
17
+
18
+ ### ✨ 新增
19
+
20
+ - **模型 → 思考级别连招**:在模型 sheet 里选中任意模型后,不再直接关闭整个菜单,而是自动重新打开设置菜单并进入「推理等级」子菜单,以同一个底部 sheet 呈现 Off / Low / High / Max,选完才关闭。
21
+ - **无闪跳过渡**:选中模型时先把旧 sheet 快照留在原地,宿主菜单在背后切换,推理 sheet 滑入后再移除快照;中间宿主菜单隐藏,不会闪出桌面 popover。
22
+ - 模型选中、推理等级选中仍由宿主完成,插件只负责串联和 sheet 化;桌面端不变。
23
+
24
+ ### 🧪 验证
25
+
26
+ - 独立宿主模拟:模型 sheet → 选模型 → 自动进入推理 sheet(Off/Low/High/Max)→ 选等级关闭并清理,4/4 通过。
27
+ - 运行中的 DSH:待 3.5.0 安装后验证完整链路。
28
+
29
+
3
30
  ## [v3.4.4] - 2026-11-02
4
31
 
5
32
  ### 🐛 修复
package/README.md CHANGED
@@ -49,6 +49,7 @@ dsh plugin --profile <name> add @qzsy/dsh-bridge
49
49
  - 内容区占满屏宽,左上角「菜单」按钮打开会话 / 工作区 / 插件入口;
50
50
  - 支持手势:左缘向右滑打开、抽屉内向左滑关闭,拖动时跟手,松手吸附;
51
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
+ opacity: 0 !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,28 +832,42 @@ 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
- document.body?.classList.remove("qzsy-model-sheet-open", "qzsy-model-sheet-dragging");
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 (modelBackdrop) {
793
- modelBackdrop.remove();
794
- modelBackdrop = null;
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
- clearModelSheet();
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();
@@ -838,12 +906,96 @@ function setupMobileDrawer() {
838
906
  if (!disposed && modelPortal === portal) document.body.classList.add("qzsy-model-sheet-open");
839
907
  });
840
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
+ if (modelPortal && visiblePortal(modelPortal)) {
934
+ try {
935
+ dismissHostMenus();
936
+ } catch {
937
+ }
938
+ window.setTimeout(() => openParentAndReason(), 100);
939
+ return;
940
+ }
941
+ openParentAndReason();
942
+ function openParentAndReason() {
943
+ if (disposed || token !== modelAdvanceToken || !modelAdvancing) return;
944
+ const trigger = document.querySelector(
945
+ '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"]'
946
+ );
947
+ try {
948
+ trigger?.click();
949
+ } catch {
950
+ }
951
+ let attempts = 0;
952
+ const openReasonCell = () => {
953
+ if (disposed || token !== modelAdvanceToken || !modelAdvancing) return;
954
+ const cell = [...document.querySelectorAll('button[role="menuitem"]')].find((b) => /推理等级|Reasoning effort|Reasoning level|推理强度/i.test(b.innerText || ""));
955
+ if (cell) {
956
+ reasoningStep = true;
957
+ try {
958
+ cell.click();
959
+ } catch {
960
+ }
961
+ window.setTimeout(() => {
962
+ if (disposed || token !== modelAdvanceToken) return;
963
+ if (!modelPortal) {
964
+ cancelAdvanceOverlay();
965
+ clearModelSheet();
966
+ }
967
+ }, 1200);
968
+ return;
969
+ }
970
+ attempts += 1;
971
+ if (attempts > 20) {
972
+ cancelAdvanceOverlay();
973
+ clearModelSheet();
974
+ return;
975
+ }
976
+ window.setTimeout(openReasonCell, 60);
977
+ };
978
+ window.setTimeout(openReasonCell, 300);
979
+ }
980
+ }, 140);
981
+ };
841
982
  const syncModelSheet = () => {
842
983
  if (disposed) return;
843
984
  if (!isMobileViewport()) {
844
985
  clearModelSheet();
845
986
  return;
846
987
  }
988
+ if (modelAdvancing) {
989
+ if (reasoningStep) {
990
+ const next = findReasoningPortal();
991
+ if (next) {
992
+ applyModelSheet(next);
993
+ return;
994
+ }
995
+ }
996
+ if (modelPortal && !visiblePortal(modelPortal)) modelPortal = null;
997
+ return;
998
+ }
847
999
  if (modelPortal && !visiblePortal(modelPortal)) clearModelSheet();
848
1000
  const found = findModelPortal();
849
1001
  if (!found) {
@@ -872,6 +1024,10 @@ function setupMobileDrawer() {
872
1024
  window.setTimeout(syncModelSheet, 30);
873
1025
  window.setTimeout(syncModelSheet, 180);
874
1026
  }
1027
+ const modelOption = target.closest('button[role="menuitemradio"], [role="menuitemradio"]');
1028
+ if (modelOption && modelPortal && modelPortal.getAttribute("data-qzsy-model-step") === "model" && modelPortal.contains(modelOption)) {
1029
+ beginReasoningAdvance(modelPortal);
1030
+ }
875
1031
  if (!isOpen()) return;
876
1032
  if (target.closest(`.${BACKDROP_CLASS}`)) {
877
1033
  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
+ opacity: 0 !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,29 +651,43 @@ 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
- document.body?.classList.remove('qzsy-model-sheet-open', 'qzsy-model-sheet-dragging');
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 (modelBackdrop) {
615
- modelBackdrop.remove();
616
- modelBackdrop = null;
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
- clearModelSheet();
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();
@@ -664,12 +729,95 @@ export function setupMobileDrawer() {
664
729
  });
665
730
  };
666
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
+ // 选中的是当前模型时宿主可能不关菜单,先按 Esc/外部点击收掉
760
+ if (modelPortal && visiblePortal(modelPortal)) {
761
+ try { dismissHostMenus(); } catch { /* 忽略 */ }
762
+ window.setTimeout(() => openParentAndReason(), 100);
763
+ return;
764
+ }
765
+ openParentAndReason();
766
+
767
+ function openParentAndReason() {
768
+ if (disposed || token !== modelAdvanceToken || !modelAdvancing) return;
769
+ const trigger = document.querySelector(
770
+ 'button[aria-haspopup="menu"][aria-label*="选择模型"], button[aria-haspopup="menu"][aria-label*="Select model"], button[aria-haspopup="menu"][aria-label*="模型"]',
771
+ );
772
+ try { trigger?.click(); } catch { /* 忽略 */ }
773
+ let attempts = 0;
774
+ const openReasonCell = () => {
775
+ if (disposed || token !== modelAdvanceToken || !modelAdvancing) return;
776
+ const cell = [...document.querySelectorAll('button[role="menuitem"]')].find((b) => /推理等级|Reasoning effort|Reasoning level|推理强度/i.test(b.innerText || ''));
777
+ if (cell) {
778
+ reasoningStep = true;
779
+ try { cell.click(); } catch { /* 忽略 */ }
780
+ window.setTimeout(() => {
781
+ if (disposed || token !== modelAdvanceToken) return;
782
+ if (!modelPortal) {
783
+ // 推理菜单没出现:撤掉 overlay,避免卡在半路
784
+ cancelAdvanceOverlay();
785
+ clearModelSheet();
786
+ }
787
+ }, 1200);
788
+ return;
789
+ }
790
+ attempts += 1;
791
+ if (attempts > 20) {
792
+ cancelAdvanceOverlay();
793
+ clearModelSheet();
794
+ return;
795
+ }
796
+ window.setTimeout(openReasonCell, 60);
797
+ };
798
+ // 等父菜单的进入动画/事件绑定完成再点「推理等级」
799
+ window.setTimeout(openReasonCell, 300);
800
+ }
801
+ }, 140);
802
+ };
803
+
667
804
  const syncModelSheet = () => {
668
805
  if (disposed) return;
669
806
  if (!isMobileViewport()) {
670
807
  clearModelSheet();
671
808
  return;
672
809
  }
810
+ if (modelAdvancing) {
811
+ if (reasoningStep) {
812
+ const next = findReasoningPortal();
813
+ if (next) {
814
+ applyModelSheet(next);
815
+ return;
816
+ }
817
+ }
818
+ if (modelPortal && !visiblePortal(modelPortal)) modelPortal = null;
819
+ return;
820
+ }
673
821
  if (modelPortal && !visiblePortal(modelPortal)) clearModelSheet();
674
822
  const found = findModelPortal();
675
823
  if (!found) {
@@ -703,6 +851,14 @@ export function setupMobileDrawer() {
703
851
  window.setTimeout(syncModelSheet, 180);
704
852
  }
705
853
 
854
+ // 选完模型继续进入推理等级,而不是直接关掉整个菜单
855
+ const modelOption = target.closest('button[role="menuitemradio"], [role="menuitemradio"]');
856
+ if (modelOption && modelPortal
857
+ && modelPortal.getAttribute('data-qzsy-model-step') === 'model'
858
+ && modelPortal.contains(modelOption)) {
859
+ beginReasoningAdvance(modelPortal);
860
+ }
861
+
706
862
  if (!isOpen()) return;
707
863
 
708
864
  if (target.closest(`.${BACKDROP_CLASS}`)) {
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@qzsy/dsh-bridge",
3
- "version": "3.4.4",
3
+ "version": "3.5.1",
4
4
  "description": "DSH 远程隧道云官方客户端插件:一条命令把本机 DeepSeek Harness 接入云端隧道(设备码绑定、短期令牌、本地门禁代理、HTTP/SSE/WebSocket 反向隧道)。",
5
- "releaseNotes": "【v3.4.4】模型 sheet 默认不弹键盘\\n• 用 readonly + inputmode=none 替代 blur:保留焦点、不弹 iOS 键盘,宿主菜单不会因 focusout 关闭\\n• 用户主动点搜索框时恢复输入并聚焦;模型列表默认完整显示\\n• 修复 3.4.3 点「模型」后菜单直接消失的问题",
5
+ "releaseNotes": "【v3.5.1】模型 → 思考级别连招修复\\n• 选中当前模型时先收起旧菜单,再进入推理等级,不再卡住\\n• 等父菜单动画/事件就绪后再点推理等级,避免 sheet 不出现\\n• 过渡期宿主菜单改透明隐藏,程序化点击可靠",
6
6
  "type": "module",
7
7
  "main": "lib/index.js",
8
8
  "exports": {