dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
把 DSH 模型选择器按来源(provider)分组:菜单只列来源与其模型数,悬停来源即在右侧滑出该来源的模型子面板(默认);切换为面板模式后改为单击来源滑出,或改为在列表内就地展开,双击来源标题即可在 悬浮 -> 面板 -> 列表 三者间循环切换;当前使用的模型一眼可见。纯 DOM 增强、零侵入——面板是插件自建 DOM 并挂载到 body,选择通过程序化点击原菜单真实按钮完成,不移动也不删除任何 React 管理的节点,因此不影响原组件渲染,并能与其他 UI 插件共存。配色与排版跟随宿主:复用官方 --dsw-* 主题变量,背景保证不透明、对比度充足,来源清单与子集清单共用同一套尺寸与色值令牌。不联网、无遥测。适配 DSH 内核 2.0.15-next。
模型菜单别再一条道排到底。 给 DeepSeek Harness(DSH)的模型选择菜单加按来源(provider)分组的导航能力。
Stop scrolling one endless model list. Group the DSH model picker by provider — the menu lists sources first, and each source's models open in a slide-in side panel (hover / click) or inline below the title.
| 悬浮模式(默认) | 列表模式 |
|---|---|
![]() | ![]() |
| 鼠标划过来源 → 右侧滑出该来源的模型;移开自动收起 | 点来源 → 在下方就地展开该来源的模型 |
第三种是面板模式(
panel):样子与悬浮模式相同,但需要单击来源才滑出面板(不做悬停触发)。

左下:折叠后的来源列表(
DeepSeek (4)、OpenSquilla TokenRhythm 网关 (23)…) 右侧:悬停/点击来源后从右滑出的模型子面板(DeepSeek-V4.1-Flash、GLM-5.3、MiniMax-M3、Kimi-K3…) 底部:设置条 —— 一键切换模式与字号 Left: the folded source list · Right: the slide-in model panel · Bottom: mode + font-size switcher
接了三五个来源之后,模型菜单能拉出一屏还滚不完——DeepSeek、GLM、MiniMax、Qwen、Kimi 二十几个模型挤在一起,每次选模型都要眯着眼找。
Add three or five providers and the model menu runs off the screen — two dozen models jumbled together, every switch a squint-and-hunt exercise.
当前·模型名(加粗 + 品牌色),子面板内选中项保留 ✓。悬浮 → 列表 → 面板(即 hover → inline → panel);菜单底部设置条也可直接点选,选择即时生效并记住。10 / 11 / 12 / 13 四档字号,点一下即刻生效,全菜单与子面板同步。双击来源标题可切换展开方式(当前:悬浮展开),新用户不必猜。--dsw-* 主题变量,底色不透明、对比度充足;来源清单与子集清单尺寸与配色完全统一。当前·model-name (bold + brand colour), with ✓ on the selection inside the panel.hover → inline → panel, or use the in-menu switcher. Applied instantly and remembered.10 / 11 / 12 / 13; both the menu and the side panel follow.--dsw-* tokens the official menu uses, with an enforced opaque background.| 项目 | 版本 |
|---|---|
| DSH 桌面端(内核,实测) | 2.0.18-next(dsh-desktop-next) |
| 官方模型选择组件(实测) | @deepseek-ai/dsh-client-ui-model-selection 0.2.1-alpha.2 |
| 客户端运行环境 | DSH Web GUI(Electron renderer,dsh-app://app/) |
engines.dsh(本包声明) | >=0.1.7-rc.1 |
上表「实测」两行是本机 2026-10-19 从
app.asar读出的真实版本,只作参考;本包自身只声明engines.dsh: ">=0.1.7-rc.1"与peerDependencies["@deepseek-ai/dsh"]: ">=0.1.7-rc.1", 不硬绑某个具体内核版本。
本插件增强的是 两级下钻菜单(root → model / effort):
div[role="menu"] ← 官方 portal 到 body 的浮层
├─ pane="root" button[role="menuitem"] (「模型」「推理等级」两个 cell)
├─ pane="model" div.groups > section[role="group"][aria-labelledby]
│ ├─ div(分组标题 = 来源名)
│ └─ button[role="menuitemradio"](模型项)
└─ pane="effort" 扁平的 button[role="menuitemradio"](无 section[role="group"])
插件只增强 pane="model";root 与 effort 因不含 section[role="group"] 而完全不介入——这也是「思考等级」面板一直稳定的原因。
若你的 DSH 内核版本不同:官方若改动 section[role="group"] / aria-labelledby / menuitemradio 这套语义结构,插件会因选择器失配而静默不生效(不会报错、不会破坏原菜单)。可在 DSH 里按 Ctrl+Shift+I 打开 DevTools,确认上述结构后再提 issue。
三种模式的交互细节见 docs/modes.md,版本历史见 CHANGELOG.md。
在 DSH profile 中安装(依赖 dsh.bundle 自声明,无需手动改 cordis.patch.yml):
dsh plugin --profile desktop add dsh-model-fold
或安装本地 tgz:
dsh plugin --profile desktop add ./dsh-model-fold-<version>.tgz
或把目录复制到 profile 的 node_modules 并在 profile package.json 的 dsh.profile.bundles 中加入 dsh-model-fold。
Install from npm:
dsh plugin --profile desktop add dsh-model-fold
自 v0.5.0 起为三模式:默认 hover(划过来源标题就地滑出该来源的模型),双击任意来源标题按 悬浮 → 列表 → 面板 循环切换,也可用菜单底部设置条直接点选;选择写入 localStorage 并跨菜单记忆。panel(右侧子面板)是 v0.3.0~v0.4.x 的旧默认值,老用户首次升级会自动迁移到 hover,之后若再次显式选 panel 则尊重你的选择。若要回到 v0.2.0(单 inline 模式)版本,可从 GitHub Releases 获取历史版本。
Since v0.5.0 the plugin ships three modes: hover (default — glide over a source heading and its models slide out), with double-click cycling hover → inline → panel; the in-menu settings bar at the bottom lets you pick one directly. The choice is persisted across menu opens. panel was the default in v0.3.0–v0.4.x; users upgrading from that era are migrated to hover once, and any later explicit panel choice is respected. To return to the v0.2.0 single-mode behaviour, grab a historical release from GitHub Releases.
悬浮 → 列表 → 面板。10 / 11 / 12 / 13),position: sticky; bottom: 0 常驻;按钮的 mousedown / click 都 stopPropagation,不会触发宿主「点外部关闭」。双击来源标题可切换展开方式(当前:悬浮展开)。panel 一次性迁移到 hover,之后显式选 panel 则尊重。section[role="group"] 首个子元素是 span[data-menu-group-start],而旧 CSS 用 > div:first-child 定位标题 → 永不匹配,导致折叠箭头、悬停高亮、「当前·模型名」徽标等 10 条规则全失效。改用内核显式属性钩子 [data-menu-group-heading]。paneSignature() 把插件自建节点算进签名 → 被误判为 pane 切换 → resetView() 清掉自建 DOM。改为签名排除自建节点 + 设置条幂等更新 + 字号点击只刷 data-on。--mf-fs。docs/images/mode-hover.png 与 docs/images/mode-list.png 两张效果图。engines.dsh 与 peerDependencies["@deepseek-ai/dsh"] 均为 >=0.1.7-rc.1;不再硬绑具体内核版本号。onBlur 关闭整个菜单;改为隐藏前先把焦点移回 trigger。document 级委托,不再依赖会被 React 换掉的节点。dispose() 摘净所有监听器与定时器,启动新实例前先拆旧实例,杜绝热重载堆叠。(更早版本见 CHANGELOG.md 与 GitHub Releases。)
目标 DOM 是 @deepseek-ai/dsh-client-ui-model-selection 渲染、portal 到 body 的 div[role="menu"]:其中每个来源是一个 section[role="group"],首子 div 为分组标题。插件用 MutationObserver 捕获菜单出现,在标题元素上追加自定义 data 属性(React 不管理这些内容),用 CSS :has() 依据 data 属性隐藏组内模型项——不移动、不删除任何 React 管理的节点,因此不破坏原组件的渲染与状态。
The target DOM is the div[role="menu"] rendered by @deepseek-ai/dsh-client-ui-model-selection and portalled to body; each provider is a section[role="group"] whose first child div is the group heading. The plugin uses a MutationObserver to catch the menu appearing and attaches custom data attributes to the heading (content React does not manage), then hides the in-group model items via CSS :has() — moving and removing nothing React owns.
这几个坑都是与官方组件行为耦合才出现的,改官方 UI 的插件都可能撞上:
1. 事件必须用 document 级委托,不能挂在 React 管理的节点上
早期版本把 click/mousedown 挂在来源标题 div 上。但标题是 React 管理的节点,pane 切换或任何重渲染都可能换掉它——监听器留在旧节点上,用户点的是新节点,于是点击永不触发(实测日志 onTitleClick 触发 0 次),表现为「点了没反应 / 一闪而过」。
现在统一由 document 上的捕获期委托处理,并按落点反查所属分组,不再持有任何节点引用。
2. 折叠前必须先把焦点从将被隐藏的按钮上移走
折叠靠 CSS display:none 隐藏真实模型按钮,而官方在进入模型列表后会主动把焦点放到「当前选中」的那个按钮上。两者相撞:官方聚焦 → 插件隐藏 → 浏览器强制失焦 → 官方根节点 onBlur 收到 relatedTarget = null → close() → 整个菜单连同刚打开的面板一起消失。
修复:隐藏前若发现焦点落在该分组内,先 focus() 回菜单 trigger(trigger 在官方 rootRef 内,onBlur 会因此放行)。
3. 背景要取官方的「菜单表面色」并合成成不透明色
官方菜单的 background-color 是 transparent,真正的颜色来自 CSS 变量 --dsw-specific-menu,而它的值形如 #43454a73——8 位 hex,结尾 73 是 alpha ≈ 45%。
rgb()/rgba(),会解析失败 → 面板退到祖先底色 → 比菜单暗(颜色不一致);transparent → 面板全透明(文字看不清)。正确做法:解析 8 位 hex,把半透明色按 alpha 合成到祖先底色上,得到「看起来与菜单一致、但完全不透明」的等价色;再把同一套变量写到菜单节点上,让来源清单与子集清单共用同一底色。
apply,dispose() 必须摘净所有监听器与定时器;启动新实例前还要显式拆掉上一个实例,否则多个实例同时处理同一次点击、互相 open/close 面板。tabindex、不主动 focus()——面板挂在 body,不在官方 rootRef/menuRef 内,焦点一旦进入面板就等于「离开菜单」,会触发官方 close()。body:官方菜单是 React portal 子树,任何重渲染都会回收其非 React 子节点;面板必须挂在 body 并用 fixed 定位。/model 命令弹窗里有分组折叠吗? 没有。本插件增强的是 composer 的模型菜单(div[role="menu"] 结构);/model 弹窗是另一套 UI 表面,暂不涉及。MIT