dsh-model-doctor 0.5.0 → 0.5.2

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.
@@ -80,7 +80,7 @@ window.__ModuleLoader__.load({
80
80
  .dmd-chip.is-warn{color:#d08700;background:color-mix(in srgb,#d08700 16%,transparent)}
81
81
  .dmd-allbar{display:flex;align-items:center;gap:8px;padding:6px 12px;margin:2px 6px;font-size:11px;border-radius:8px;background:color-mix(in srgb,currentColor 6%,transparent)}
82
82
  .dmd-allbar button{font:inherit;font-size:11px;line-height:16px;padding:2px 9px;border-radius:6px;border:1px solid color-mix(in srgb,currentColor 25%,transparent);background:transparent;color:inherit;cursor:pointer}
83
- .dmd-allbar button:disabled{opacity:.45;cursor:default}
83
+ .dmd-allbar button:disabled,.dmd-allbar button.is-busy{opacity:.45;cursor:default}
84
84
  .dmd-allbar .dmd-status{opacity:.65}
85
85
  .dmd-allbar .dmd-balance{color:#2fa96e}
86
86
  `
@@ -560,8 +560,17 @@ window.__ModuleLoader__.load({
560
560
  if (result && result.ok === true) {
561
561
  deletedKeys.add(keyOf(provider, model))
562
562
  const section = row.closest('section')
563
+ // 焦点若在被删行上,移除后焦点会落到 body → 官方 onBlur 会关闭菜单,
564
+ // 所以先把焦点转移到相邻的模型行再移除。
565
+ const hadFocus = row === document.activeElement
566
+ const neighbour = row.nextElementSibling ?? row.previousElementSibling
563
567
  row.remove()
564
568
  if (section !== null && section.querySelectorAll(ROW_SEL).length === 0) section.remove()
569
+ if (hadFocus) {
570
+ const target = (neighbour !== null && neighbour.isConnected ? neighbour : null)
571
+ ?? document.querySelector('[role="menuitemradio"]')
572
+ if (target !== null && typeof target.focus === 'function') target.focus()
573
+ }
565
574
  fetchDirectory(true)
566
575
  } else {
567
576
  const message = result && typeof result.error === 'string' ? result.error : '删除失败'
@@ -683,11 +692,21 @@ window.__ModuleLoader__.load({
683
692
  status.className = 'dmd-status'
684
693
  const balance = document.createElement('span')
685
694
  balance.className = 'dmd-balance'
695
+ // 忙碌用标记而不是 disabled:被禁用的聚焦元素会把焦点丢给 body,
696
+ // 而官方菜单在根节点上监听 onBlur(portal 的事件仍走 React 树冒泡),
697
+ // 焦点一离开菜单就会 setOpen(false) —— 表现就是「点全部检测菜单被关掉」。
698
+ let busy = false
699
+ const setBusy = (value) => {
700
+ busy = value
701
+ button.classList.toggle('is-busy', value)
702
+ button.setAttribute('aria-busy', value ? 'true' : 'false')
703
+ button.style.pointerEvents = value ? 'none' : ''
704
+ }
686
705
  button.addEventListener('click', (event) => {
687
706
  event.stopPropagation()
688
707
  event.preventDefault()
689
- if (doctor === null || button.disabled) return
690
- button.disabled = true
708
+ if (doctor === null || busy) return
709
+ setBusy(true)
691
710
  barBusy = true
692
711
  status.textContent = '探测中…'
693
712
  balance.textContent = ''
@@ -696,7 +715,7 @@ window.__ModuleLoader__.load({
696
715
  })
697
716
  doctor.checkAll().then((result) => {
698
717
  if (disposed) return
699
- button.disabled = false
718
+ setBusy(false)
700
719
  barBusy = false
701
720
  if (!result || result.ok === false || !isRecord(result.results)) {
702
721
  status.textContent = `失败:${result && typeof result.error === 'string' ? result.error : '通道异常'}`
@@ -726,7 +745,12 @@ window.__ModuleLoader__.load({
726
745
  }
727
746
  })
728
747
  })
729
- button.addEventListener('mousedown', (event) => event.stopPropagation())
748
+ // preventDefault 阻止焦点转移到本按钮:焦点一旦离开菜单,官方 onBlur 就会关闭菜单
749
+ button.addEventListener('mousedown', (event) => {
750
+ event.preventDefault()
751
+ event.stopPropagation()
752
+ })
753
+ button.addEventListener('pointerdown', (event) => event.stopPropagation())
730
754
  bar.appendChild(button)
731
755
  bar.appendChild(status)
732
756
  bar.appendChild(balance)
@@ -746,6 +770,8 @@ window.__ModuleLoader__.load({
746
770
 
747
771
  let reportSignature = ''
748
772
  let reportAt = 0
773
+ /** 最近一次写入的报告是否包含「菜单已打开」的现场。 */
774
+ let lastReportHadMenu = false
749
775
 
750
776
  function describe(el) {
751
777
  if (el === null || el === undefined) return null
@@ -754,16 +780,19 @@ window.__ModuleLoader__.load({
754
780
  return { tag: el.tagName, role: el.getAttribute('role') ?? '', cls, text }
755
781
  }
756
782
 
757
- function reportDiagnostics(state, force = false) {
783
+ function reportDiagnostics(state, menuVisible = false) {
758
784
  if (doctor === null) return
759
785
  const { at, ...rest } = state
760
786
  const signature = JSON.stringify(rest)
761
787
  const now = Date.now()
762
- // 状态没变时:菜单开着也最多 5 秒写一次,避免刷屏
763
- if (signature === reportSignature && now - reportAt < (force ? 5000 : 1200)) return
788
+ const isError = typeof state.error === 'string'
789
+ // 报告里已经留下「菜单打开」的现场时,不要被随后「菜单关闭」的空扫描覆盖(错误除外)
790
+ if (!menuVisible && !isError && lastReportHadMenu) return
791
+ if (!isError && signature === reportSignature && now - reportAt < (menuVisible ? 5000 : 1200)) return
764
792
  if (now - reportAt < 400) return
765
793
  reportSignature = signature
766
794
  reportAt = now
795
+ lastReportHadMenu = menuVisible
767
796
  void doctor.clientReport(state)
768
797
  }
769
798
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-model-doctor",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "模型医生:在对话输入区的模型选择菜单里,为每个模型选项追加「连通检测」与「删除」按钮——直接适配 DSH 官方模型菜单(按官方 DOM 契约精确定位,不依赖任何菜单渲染插件),检测直连各网关探测连通性/欠费/额度,删除直接从 llm-pi-ai 配置移除模型(自动备份配置文档)。",
5
5
  "keywords": [
6
6
  "deepseek",