# dsh-model-select-collapse **给 DSH(DeepSeek Harness)网页版的模型选择器加上「按供应商分组折叠」能力,让长长的模型列表不再是一拉到底的一大串。** [English](./README.md) · **简体中文** --- ## 这个插件是做什么的 装好之后,DSH 输入框里的模型选择菜单会多出**可点击的分组标题**: | 装之前 | 装之后 | | --- | --- | | 所有供应商的模型全部平铺,一长串滚动 | 每家供应商是一个分组,标题一行,可以收起来 | | 没办法藏起暂时不用的供应商 | 点一下标题就能收起 / 展开这家供应商 | | 每次关掉菜单,状态就没了 | 折叠状态按供应商分别记住 | 具体来说: - 每个供应商分组都有一行**标题**,显示供应商名称和该组里的模型数量。 - **点标题即可收起或展开**这个分组,旁边的箭头图标会跟着转动,指示当前状态。 - 折叠状态按**供应商 id** 存在浏览器的 `localStorage` 里,键名是 `dsh.modelSelect.collapsed`,所以关掉菜单再打开、甚至刷新页面,状态都还在。 - 没有记录的供应商(比如你后来才导入的)默认是**展开**的,不需要任何配置。 除此之外,模型选择器的一切都保持原样:两级菜单(模型列表 / 推理强度)、键盘操作、加载与报错状态、目录加载失败时的「重试」,以及选择失败时的提示条。 ## 它解决什么问题 DSH 会把所有供应商的模型平铺在一个菜单里。五六个供应商时就已经要滚很久,十几个的时候基本就是一面「模型墙」,你真正常用的那两三个反而被埋在中间。这个插件不改变原来的布局和排序,只是让你能把暂时不用的供应商收起来。 ## 行为细节 | 行为 | 说明 | | --- | --- | | 怎么折叠 | 点击供应商分组标题 | | 默认状态 | 展开;只有你主动收起的那些分组会被记录为已收起 | | 存储位置 | `localStorage` 里的 `dsh.modelSelect.collapsed`(一个 `{ 供应商id: true }` 映射) | | 作用范围 | 仅当前浏览器,本地状态,不会同步到任何地方 | | 新导入的供应商 | 默认展开(没有历史记录) | | 键盘操作 | 标题是真正的 `button`,可以用 Tab 聚焦、用回车 / 空格触发 | | 无障碍 | 每个分组是带标题的 `role="group"` 区块,标题上带有 `aria-expanded` 状态 | ## 对 DSH 0.1.7-alpha.2 的兼容说明 DSH `0.1.7-alpha.2` 把 `@deepseek-ai/dsh-client-ui-primitives` 里的图标导出**统一改了名**: | 0.1.7-alpha.2 之前 | 0.1.7-alpha.2 及以后 | | --- | --- | | `IconChevronDownOutline14` | `IconChevronDownOutlineRegular` | | `IconChevronRightOutline14` | `IconChevronRightOutlineRegular` | | `IconCheckOutline16` | `IconCheckOutlineRegular` | | `IconWarningOutline16` | `IconWarningOutlineRegular` | 旧名字在新版里变成了 `undefined`,而把 `undefined` 当成 React 元素类型用会抛 `Element type is invalid`。DSH 的插槽机制随后会把崩掉的这一项判定为「退役」,官方模型选择器顺势顶上——表现出来就是**折叠按钮凭空消失**,而不是看起来像插件坏了。 所以 `lib/client.js` 在加载时会**同时探测新旧两种命名**,取存在的那个;万一两个都没有,就退化成空组件,宁可少一个图标也不让整块区域崩掉。因此同一个版本在旧版和新版 DSH 上都能跑。`Toast` 在新旧版本里都有且签名兼容,同样按「可选」处理。 ## 安装 ### 从 GitHub 安装(推荐) ```bash dsh plugin --profile web add github:banana770/dsh-model-select-collapse ``` ### 从本地目录安装 ```bash dsh plugin --profile web add /path/to/dsh-model-select-collapse ``` 装完后重载 DSH 网页界面(重启 DSH,或者强制刷新页面),然后从输入框那里打开模型选择器即可。 ### 手动安装 真正起作用的只有四个文件: ```text package.json 清单:声明客户端入口(dsh.client)和 bundle 补丁 cordis.patch.yml 把插件挂进 profile 的 bundle 清单(insert: model-select-collapse) lib/index.js 宿主侧半边:空的空实现,只为让加载器里保留这一条记录 lib/client.js 真正的逻辑:替代官方模型选择器并加上分组折叠 ``` 把整个目录放到 profile 的安装位置,并确认 `cordis.patch.yml` 里那条 `insert`(`id: model-select-collapse`,`name: @dsh-external/dsh-model-select-collapse`)是生效的。没有构建步骤。 ## 卸载 在侧边栏 **插件** 页面的「已安装」列表里移除 `dsh-model-select-collapse`,然后重载界面。如果是手动安装的,把 `cordis.patch.yml` 里对应那条 `insert` 删掉并重启 DSH。 ## 实现方式 - **抢占插槽(seat shadowing)**:DSH 输入框上的模型选择器是 `conversation.input.model` 这个插槽。官方插件用默认优先级 `0` 注册,本插件用 `-1` 注册;单插槽的规则是「优先级最低者渲染」,于是本插件的视图替换掉官方视图。 - **不另起数据层**:所有供应商和模型数据都来自 DSH 自己的 `modelDirectories` 服务(`directoryFor(sessionId)` 返回一个 `SnapshotStore`,这里通过 `useSyncExternalStore` 订阅)——和官方视图同源,本插件只负责重新渲染。 - **样式隔离**:整张样式表只注入一次到 `document.head`,带 `data-plugin-css` 标记;所有类名都加 `mse_` 前缀,不会和官方类名撞车。 - **不注册字典**:官方的 model-selection 插件已经注册了 `model` 这个命名空间的 `zh` / `en` 字典,本插件注册插槽时声明同一命名空间以复用它们——因为重复注册同一命名空间会抛错。 ## 想改点东西 | 你想改什么 | 改哪里 | | --- | --- | | 没有记录的供应商默认收还是展 | `lib/client.js` 里的 `loadCollapsed()` | | 存储键名 | `lib/client.js` 里的 `STORAGE_KEY` | | 收起时箭头的方向 | `css` 块里的 `.mse_groupChevronCollapsed` | | 宽度、间距、配色 | `lib/client.js` 里的 `css` 块(基于 `--dsw-*` 主题变量) | ## 隐私 - **数据不出本机**:插件自己不发任何网络请求,也不连任何第三方服务;它读写的只是官方模型选择器本来就在用的那个 DSH 模型目录服务。 - **没有埋点、没有统计、没有账号**。 - **不含也不需要任何 API 密钥**;供应商的凭证仍然留在 DSH 自己保管它们的地方。 - **只写入一项本地状态**:`localStorage` 里的 `dsh.modelSelect.collapsed`,记录你把哪些供应商分组收了起来。里面只有供应商 id 和布尔值,别的什么都没有;删掉它就等于把所有分组恢复成展开。 - 不读取会话内容,不访问任何文件。 ## 兼容性 - 面向 DSH 的 **web** profile(`platform: "web"`)。 - 已在 DSH `0.1.7-alpha.2` 上验证通过。由于图标查找同时探测新旧两种命名,在仍使用旧命名的版本上也能运行。 - 使用 DSH 自带的 `react`(`peerDependencies: >=18 <19`),不会重复安装第二份。 - 没有构建步骤:`lib/client.js` 就是最终可直接挂载的产物。 ## 许可 [BSD-3-Clause](./LICENSE) —— 可自由使用、修改和再分发。