安装
在 DeepSeek Harness 里通过 dsh-market 安装
dsh plugin --profile web add dshmarket
或使用命令行
dsh plugin --profile web add github:ttmouse/dsh-model-picker
装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络。请先审阅源码,并尽量锁定 commit(github:owner/repo#sha)。
截图
README

dsh-model-picker
面向 dsh Web GUI 的增强版模型选择器,接管输入框里的模型座位
(conversation.input.model)。
设计
双区触发按钮把「选模型」和「选推理档位」拆成两个独立弹窗:
| 区域 | 点击 | 弹窗内容 |
|---|---|---|
| 左侧(模型名) | 打开模型选择器 | 顶部搜索框(自动聚焦)、左栏供应商、右栏模型列表 |
| 右侧(档位) | 打开档位选择器 | 可选的推理档位列表 |
鼠标悬停任一区域会说明它将要做什么。模型区显示 供应商 · 模型名:同一个模型 id
常常由多个供应商提供,光看名字说不清模型跑在哪里。这里用的是展示用的供应商名,
所以在折叠过的 (modlens vision) 路由上选中时,显示的仍是它的基供应商——也就是你
刚才在左栏点的那个名字。
供应商徽标
供应商的首个字符紧跟在模型名前面——DeepSeek 是 D,智谱 GLM 是 智——关闭状态的
触发器上和列表的每一行都会显示,所以还没打开选择器就能看出模型来自哪里。
- 触发器的
max-width会随徽标的占位(16px + 间距)一起加宽,因此戴上徽标不会让模型 名少显示任何字符。 - 徽标取的是展示用供应商,所以由折叠的
(modlens vision)路由服务的行,戴的是 基供应商的徽标,而不是它自己的。 - 每个供应商还有一个由其路由 id 推导出的固定底色。没有它的话,
17an-db、17an-anbot、17an-mumiao都会只显示一个1,看起来像同一个供应商。只有底色 带色相,字形本身不受影响,因此在明暗两种主题下都清晰。
供应商折叠
若某个供应商的名字等于另一个供应商的名字加上一个括号包裹的能力后缀,它就折叠进
基供应商那一行。modlens 会把视觉能力注册成独立的路由
(DeepSeek (modlens vision)),不折叠的话左栏每个供应商都会多出一行近似重复项。
- 折叠只影响展示:每一行始终保留「模型 + 真正服务它的路由」的配对,所以选中折 叠行里的模型,提交的仍是该模型自己的供应商 id。
- 后缀保留在模型名上;折叠模型的路由若与其所在行不同,会额外标注该路由,两者不会 混淆。
- 带后缀的供应商若没有基供应商,就保留自己独立的一行。
模型信息(上下文长度 / 图片输入)
座位读到的浏览器端目录(ModelCatalogModel)只有
id / name / description / reasoning——没有上下文长度,也没有输入模态。
两者都在 Host 的 llm 服务上,因此由 host 半段(lib/host.js)通过一条回环路由
重新发布:
GET /api/model-picker/models
→ { ok: true, models: [{ provider, id, contextWindow?, input? }] }
context: { contextWindow }的唯一来源是resolveModel();inputModalities则来自开销更低的listModels()。- 每个「供应商/模型」组合互相隔离:抛错的供应商或模型会被丢弃,两项信息都没有的行 直接省略。
- 快照缓存 30 秒——这条路由绝不会按每次按键去扇出请求。
- 弹窗给每行标注上下文长度胶囊(
1M、384K)和图片图标,按provider/modelId关联。 - 视觉能力。 实心图标表示模型自己就能读像素。半透明图标表示这条路由之所以接受
图片,是因为 modlens 做了桥接:模型其实看不见——modlens 先把图片转写成文字。
(modlens vision)后缀就是区分两者的标志。 - modlens 后缀不等于通用的视觉能力标志:若模型 id 本身已带视觉标记、其目录已声 明图片输入、或不在 modlens 配置的家族内,modlens 都不会包装它。因此没有后缀的行 也可能原生支持视觉;判断依据是图标,不是名字。modlens 关闭时,折叠和桥接图标都 处于休眠状态——不会有任何后缀。
- 在 host 半段重启之前,这条路由返回 404,因此上下文胶囊和原生视觉图标暂时缺失。
分组列表(电梯)
右栏始终是同一个分组列表:收藏(置顶的镜像副本),然后是每个供应商的分组。 左栏是这个列表的目录。
- 滚动右栏时,左栏会高亮「钉在列表顶边」的那个分组(滚动联动 / 电梯)。分组标题是
position: sticky的,所以左栏高亮始终和你看得见的标题一致。 - 点击左栏的供应商会直接跳到对应分组——是落点,不是一段动画:电梯是目录,路上花 掉的时间没人要。搜索状态下,点击供应商改为缩小结果范围(即搜索分面)。
收藏
每个模型行在「名称与信息条之间」都有一个 ☆ / ★ 切换。整行本身是一个
<button>,所以星标会拦截自己的点击事件,不让它冒泡到行上——收藏一个模型绝不能
顺带选中它——同时星标自己处理 Enter / Space。
- 被收藏的模型在列表里出现两次:一次在它所属供应商的分组里,一次在置顶的 「收藏」分组里。收藏行始终标注服务它的供应商。
- 收藏以路由(
provider/modelId)为键,而不是单纯的模型 id:同一个模型 id 常 常由多个已配置的供应商提供,收藏其中一条路由并不代表其他几条也被收藏。 - 数据存在
localStorage的dsh.modelPicker.favorites下——和官方对话插件存放 自己视图与宽度偏好的地方一致。写入被拒(隐私模式、配额用满)会被吞掉:内存中的 集合仍然跟随当前页面。
搜索语义
查询是同时作用于两栏的分面,而不只是模型列表:
- 供应商按显示名、路由 id、或「至少有一个模型匹配」来命中。左栏收窄到匹配的供应商, 每个徽章显示该供应商的命中数。
- 未选中任何供应商时,模型列表横跨所有匹配的供应商,并给每行标注其供应商。
- 搜索时点击供应商会把列表收窄到该供应商,且不清空查询。输入新查询会清掉上一个供 应商分面。
模型与档位数据走的是和 /model 弹窗同一个按会话的 ModelDirectory,所以在任一面
切换,另一面接下来显示的就是同一个结果。
键盘操作
这个选择器是搜索优先的,焦点始终留在搜索框里:整个弹窗不需要 Tab 穿过每一行就能全 程用键盘操作。
- 弹窗打开时光标已经落在第一行上——空的收藏分组会被跳过,而不是停在那里。光标不
等于当前模型:对勾表示正在使用的模型,光标表示下一次
Enter会落在哪里。 ↑/↓移动一行,PageUp/PageDown移动十行,Home/End跳到两端。光标 在两端停住,不会绕回。Enter提交光标所在行并关闭弹窗,把焦点交还给触发器。输入新查询会把光标放回第一个 匹配项。- 光标的样子是悬浮底色加上行左边缘的品牌色竖条;列表会把它滚进视野,
scroll-margin-top保证吸顶的分组标题不会盖住刚滚到的这一行。 - 搜索框用
aria-activedescendant指向光标所在行,所以即使 DOM 焦点从不移动,读屏软 件也能播报当前位置。 - 收藏依然可达:
Tab到某行的星标,按Enter/Space切换收藏,而不会选中模型。
测试
npm test 跑两半:
scripts/interaction.mjs在 jsdom 里通过真实的 loader 入口驱动浏览器半段:打开模 型弹窗、输入供应商名与模型名、断言两栏都被过滤、供应商分面保留查询、弹窗打开时光标 就在第一行并能用↑/↓/PageUp/PageDown/Home/End走动直到Enter提交, 以及信息徽章落在信息路由所描述的那些行上。假 context 会强制 cordis 的without inject规则,因此漏声明服务会让测试失败,而不是在真实座位上才崩。它还断 言每个图标都是 SVG,绝不是文字 emoji 或符号字符——在关闭状态的触发器以及两个弹 窗里都是。scripts/host-route.mjs用桩llm驱动真实的路由处理器:断言行结构、抛错的供应 商/模型被隔离,以及 30 秒缓存命中时不会重新列举。
图标对齐。 行内 <svg> 坐在文字基线上,基线下方那段字体降部空间会把图标顶到它
自身行框中心之上——这正是让折叠箭头或对勾偏离文字水平线的原因。
.dsh-mp2-root svg{display:block} 消除了这段空间,每个图标容器再各自居中,于是图标
中心就落在文字的中心线上。
node scripts/probe.mjs 检查真实环境:打开运行中的 GUI(DSH_COOKIE_JAR,默认
/tmp/dsh-cj.txt,里面存着已认证的会话 cookie),断言组件已挂进座位、打开弹窗、测量
查询收窄时面板尺寸不变,并写出 live-model-picker.png。
node scripts/screenshot.mjs 重新生成 README 配图。它会先收藏两个来自不同供应商的
模型——收藏分组是一张静态图必须展示的功能,而全新的浏览器配置下它是空的——并且拒绝
写出一张收藏分组为空的图。可用 SHOT_OUT 覆盖输出路径。
槽位
注入 conversation.input.model(由
@deepseek-ai/dsh-client-ui-conversation 声明)。
注入契约
该座位是单占用槽位,因此本条目会遮蔽官方的 ModelSelect——注册时崩溃会让输入框
完全失去模型控件。cordis 会把服务代理的 this.ctx 重绑到调用方,所以
ctx.modelDirectories 的每个面(directoryFor、load、select)都是通过本插件
的 inject 集去读 remote.session。因此声明是:
const inject = ['slots', 'sessions', 'modelDirectories', 'remote', 'remote.session']
去掉 remote / remote.session 会在座位的 inject 工厂里抛出
cannot get property "remote.session" without inject;条目随即退位,增强选择器永远
不会出现。
评论
评论存放在 GitHub Discussions。用 GitHub 账号登录后可发表评论或点表情。