dsh-effort-slider: Integrating model selection and thinking intensity into a star-shaped slider track

前言

dsh web GUI 的输入框里自带一个模型选择器。模型多了之后会出现一个具体问题:不同模型声明的 reasoning-effort 档位不一样,有的只有 off / low / high / max,有的多到 low / medium / high / xhigh / max。更根本的约束是,Host 端只校验不归一——@deepseek-ai/dsh-llm 的 resolveCallWithInfo 在档位不属于目标模型 reasoning.efforts 时直接抛 UNSUPPORTED_REASONING_EFFORT,会话远程把这层拒绝包装成 session/model-unavailable,界面上就是一句红色提示 切换失败:provider "…" model "…" does not support reasoning effort "…"。用户得自己猜哪个模型支持哪一档。

下面介绍的插件 suyunxing/dsh-effort-slider 就是针对这个点做的:仍然用官方模型选择器那个槽位,但模型列表与思考强度由一个控件统一管理,切到不支持当前档位的模型时在客户端先做一次映射,把请求发出去之前就落到目标模型支持的档位上。维护者是 suyunxing,分类属于模型推理,许可证为 MIT,npm 上的版本是 0.2.7。

这是什么

一句话定位:替换 dsh web GUI composer 里自带的模型选择器,把”模型列表”和”该模型的 reasoning effort”合并成一条发光的星光滑轨(starfield rail)。

它不是在原生选择器旁边再放一个控件,而是因为 conversation.input.model 是单槽位,插件以 priority: -1(单槽位渲染时最低优先级者优先)接管这个槽位,从而把原生选择器挤掉。插件不新增服务、不新增工具、不新增配置项,只做 UI 与请求构造前的档位映射。

核心功能

1、运行时读取模型目录,不写死列表。模型列表来自 catalog.groups(按 provider 分组),档位就是当前模型自己声明的 reasoning.efforts,典型情况是 off / low / high / max。换一个模型,滑轨上的刻度标签跟着换。

2、显示会话的真实选择,而不是部署默认值。它读取持久化的 modelSelection projection,所以滑轨指针落在的是当前会话真正使用的档位。

3、选择模型或档位时调用会话的 selectModel 远程方法,参数是显式的 { provider, model, reasoningEffort } 三元组。选择会作用于当前会话,并在后台保存为默认值。

4、换模型时先在客户端做档位映射。目标模型声明了当前档位就原样发送;声明了别的档位就把最接近的一档发过去,状态行说明替代关系;完全不支持思考的模型则省略该字段,由 Host 套用该模型自己的默认值。不报错,也不静默丢弃。

5、支持”无档位”模型。这类模型在列表里标为 无档位,选中后滑轨变暗,状态行说明情况,而不是假装切换成功。

6、移动端/手机壳兼容。dsh-pocket 会给文档级点击装上守卫:任何文本看起来像文件路径的 <button>/<a> 都会被 preventDefault() + stopImmediatePropagation() 拦掉并弹 toast。因此所有可见标签都经过 safeLabel 处理。

安装与启用

安装命令来自仓库 README,有两种来源:

dsh plugin add @lolannn/dsh-effort-slider          # from npm
dsh plugin add git+https://github.com/suyunxing/dsh-effort-slider   # from source

装完在 web GUI 里重新加载插件即可生效,无需额外配置项。package.json 里声明了 dsh.client.inject 依赖 @deepseek-ai/dsh-client-connection、@deepseek-ai/dsh-client-ui-renderer、@deepseek-ai/dsh-api-remotes、@deepseek-ai/dsh-api-session-controller,平台是 web,immediately: true 表示客户端随页面立即注入。

典型用法

控件本身即用法,无需命令行操作。安装并加载后:

1、点击 composer 上的触发器(显示为模型 · 档位),弹出星光滑轨面板,面板里包含模型行、分组模型列表、effort 滑轨、档位刻度和状态行。

2、拖动滑轨,或按 ← → ↑ ↓ Home End 切换档位。松手时滑轨以弹性回弹的方式吸附到最近的一档。

3、点击滑轨下方的档位名也能直接跳到该档;标签按滑轨的停止点对齐,点击区域与滑块共用同一套边缘计算。

4、点击模型行展开模型列表,挑选某个模型时会把当前档位带过去;目标模型缺少该档就按上一节规则重新映射,并在状态行给出替代说明。

5、Esc 或点击面板外关闭。焦点在输入框时方向键被忽略,所以在 composer 里打字不会误切档位。

面板通过 react-dom 的 createPortal 渲染到 document.body,因为 composer 那一行可能处在有 transform 或 overflow 的祖先容器里,会裁掉一个 position: fixed 的子元素。

输入卡片本身也会发光:整个输入卡被一圈与滑轨同色相的 halo 包住,亮度、光环透明度和辉光半径都跟随档位变化——off 只有一道浅边,最高档持续呼吸,打开面板时整体提亮;halo 打开时按钮只保留描边。halo 是一个开关而不是硬规则:滑轨下方的外圈光环开关关掉后,辉光回到按钮本身,退成插件出现之前的样式,选择存在 localStorage 的 effort-slider:halo 下,刷新不丢。

最高档还带一点庆祝效果:滑轨上方会展开一个三臂星系的坍缩动画,同时触发冲击波爆发、星光扭曲和光刃闪烁。这些 canvas 动画由 requestAnimationFrame 驱动,卸载时停止;prefers-reduced-motion: reduce 下会关掉爆发、扭曲和震动。canvas 尺寸来自卡片本身(getBoundingClientRect + ResizeObserver),不随视口变化;动画只用 transform、opacity 和 canvas,不触发布局抖动。

适用场景与注意

适合谁:在 dsh web GUI 里频繁切换模型、且不同模型的 reasoning 档位不一致的开发者;或者希望模型选择与思考强度在交互上看得见、点得准的场景。

注意几点:

1、插件以当前 dsh 进程的权限运行。安装前应该先打开源码看过,确认 index.js 与 client.js 的行为符合预期,再决定是否引入。仓库许可证是 MIT。

2、它是 UI 层插件,不改 Host 的校验逻辑。Host 的 resolveCallWithInfo 仍然”不做 clamp 或 alias”,插件只是在构造请求前替用户把档位映射好。这个映射规则是设计上的取舍,值得了解:

  • 档位按一套共享刻度比较,而不是按 provider 各自的列表排序。适配器的档位 id 来自同一词汇表:off · none · minimal · low · medium/med · high · xhigh · max · ultra。
  • 距离相等时偏向更强的一档。理由是少用推理能力比多用更糟,所以 high 距离 xhigh 和 medium 都是一步时会选 xhigh。
  • 不在共享刻度上的档位按它在自己列表里的位置放置;如果携带的 id 本身未知,无法测量距离,就改用目标模型的 defaultEffort,再退到它的最强档。
  • 最后一个真实选择记在 lastEffort 里,所以 max → 无档位模型 → back 回到 max,而不是那个模型的默认值。

3、档位映射发生在客户端,属于 UI 层的便利措施;真正生效的是发出去的 { provider, model, reasoningEffort } 三元组。

结尾

这个插件没有引入新的运行能力,它做减法:把”模型列表”和”思考强度”合并成一条滑轨,把 Host 端不归一导致的切换报错在客户端提前消掉。模型目录、当前会话的真实选择、以及无档位模型的降级都有明确来源,行为可查。目录页在 https://www.skillhub.cn/plugins/suyunxing/dsh-effort-slider ,源码在 https://github.com/suyunxing/dsh-effort-slider 。

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

Xiaoye