dsh-effort-dial
English | 中文
为 DeepSeek Harness 输入框做的思考强度滑块。 它接管输入框的模型座位,把一个带动画的思考强度控件放进去,并把选中的档位写回会话的模型选择 —— 与宿主自带座位使用的是同一套契约。

上面每一行都是渲染器自己的输出,由 scripts/render-burst.mjs 在 mock canvas 上绘制 —— 不是浏览器截图。
依次是:中档、最高档静止态,以及落到最高档之后 +200 ms、+700 ms、+1400 ms、+2200 ms。
你会得到什么
- 一个强度滑块 —— 当前模型提供的档位成为轨道上的站点。滑块是一个纯白圆形,它背后的一切都画在 canvas 上。
- 档位就是场的语言 —— 低档是缓慢稀疏的漂移;最高档密集、快速,并带有持续的冲击波环、弧光裂纹和膨胀的核心。
- 落点爆发 —— 到达最高档时亮度与速度同时冲顶(峰值约为静止态的 8 倍),随后用约两秒柔和回落到静止观感。 触发时有一层光从滑块位置向整条轨道扫出去。
- 磁吸锚点 —— 经过档位时滑块会被轻微拖住、档位之间再释放,所以"停住"是有手感的。 拖拽量有界:滑块以手指的 0.5x–1.5x 速度行进,既不会卡死也不会窜出去。
- 缓动位移 —— 点击、按压和键盘步进都沿一条三次贝塞尔曲线滑向目标;拖拽则直接跟手, 残余的滑动偏移会在手指底下自行溶解。
- 模型列表保持宿主原样 —— 弹窗里的选择列表只用宿主设计令牌朴素渲染。这个插件只负责滑块,不负责改列表的长相。
安装
直接从本仓库安装:
dsh plugin --profile web add github:WONGIII/dsh-effort-dial
构建产物 lib/ 已提交,且本包没有声明 prepare 脚本,所以安装时没有任何东西需要编译,
pnpm 也没有构建脚本需要放行。若不想从 git 装,dsh plugin --profile web add ./dsh-effort-dial
从本地检出安装效果相同。
然后重启 DSH(或刷新页面)。
一个顺序上的注意事项
conversation.input.model 是一个 single 槽位:那里只渲染一个条目,且最后注册的插件获胜。
宿主自己也往这个座位注册了一个 ModelSelect。
作为 bundle 安装时,本插件在 profile 自己的 cordis.patch.yml 之前组合,所以宿主座位可能比它注册得更晚。
滑块对此有自己的处理:它会查看槽位的 entries() 列表并重新注册,直到座位归自己,重试限定在启动后的最初几秒内。
只挂一次 —— 就是作为 bundle。 dsh plugin add 会同时写入依赖和 dsh.profile.bundles 里的条目,
这就够了。不要再往 profile 的 cordis.patch.yml 里加一条 insert:
# ~/.dsh/profiles/web/cordis.patch.yml —— 不要加这段
- insert:
- id: effort-dial
name: dsh-effort-dial
同一个 id 出现两行,会组合成同一条目的两个候选行,插件页随后就拒绝定位该行 ——
dsh-plugin-manager 用 candidates.length > 1 判定可寻址性,
并报出:
readOnlyReason: "unaddressable"
这会锁死该行的开关,且开关状态永远不会写入磁盘。表现出来的现象就是:这个插件在界面上根本关不掉, 开关弹回去、什么也没写。
场是怎么工作的
这个效果是一列被发射出去的波前,不是一层滚动的纹理。
滑块持续发射波前。一个波前是一个带自己的位置、宽度和强度的行进脉冲:在滑块处诞生、向外飞行、 跑出轨道尽头后消亡。列在波前到达时亮起、在波前经过后淡去,所以没有任何一条亮纹是"恰好在滚动的驻波" —— 每一条亮纹都是更早发射、此刻已经跑得更远的那个波前。这正是让场看起来是从源头被推出去的原因。
波前的剖面是不对称的:到达时上升很快,身后拖尾很长。陡峭的前沿给出行进感, 而长拖尾填满波前之间的空隙,让轨道保持覆盖而不是变成一条条条纹。
写给其他插件作者
下面三件事都真实地消耗了大量时间。写在这里,是因为它们都不会报出真正的原因。
把你回调会碰到的每一个服务都声明出来。 服务访问由插件的 inject 列表白名单放行,
而且间接读取同样受管。一个座位的 inject() 回调如果调用了模型目录,就需要声明 remote.session,
因为解析会话地址会读它。缺了声明,回调只在渲染时抛错 —— 插件看起来已经挂载,
座位却在槽位的错误边界里死掉:
Error: cannot get property "remote.session" without inject
slot entry crashed in 'conversation.input.model'
single 槽位会忽略你传的 priority。 对任何非 chain 的槽位,运行时都会用自己的递减分配
覆盖 options.priority,所以 priority: -100 是无效的,唯一判据是注册顺序。
slots.entries(key)[0] 才是真正渲染的那个条目。
验证脚本绝不能重写它正在检查的产物。 任何对已服务 bundle 的写入都会触发 client-HMR 监听,
让正在运行的页面重载并重新挂载插件。即使重建出的文件字节完全相同,照样会干扰被检查的对象 ——
而那看起来就和"插件随机消失"一模一样。build.mjs --check 改为与内存中的构建结果比对。
开发
node scripts/build.mjs # src/ -> lib/client.js
node scripts/build.mjs --check # 确认 bundle 是最新的,且不写文件
npm run verify # 全部检查
lib/client.js 是生成物。改 src/ 后重新构建,不要直接改 bundle。
| 路径 | 作用 |
|---|---|
src/client.js |
Cordis 插件:座位、弹窗、朴素模型列表 |
src/fx.js |
FieldRenderer —— 全部动画,每个滑块一个实例 |
src/styles.css |
滑块、弹窗与列表样式 |
cordis.patch.yml |
bundle 层:一行 insert |
lib/index.js |
宿主半边:可挂载的入口,没有任何宿主行为 |
lib/client.js |
构建出的浏览器半边 |
scripts/mock-canvas.mjs |
检查所依赖的预乘 alpha 2D canvas 模拟 |
scripts/verify-*.mjs |
加载、几何、扫掠、运动、光照检查 |
scripts/check-original.mjs |
净室检查(见下) |
scripts/render-*.mjs |
在浏览器之外把场渲染成 PNG |
验证
这个效果是靠测量而不是靠眼睛验证的,运行在了一个如实模拟预乘 alpha 合成的 mock canvas 上:
- 加载 —— 构建出的 bundle 能注册模块行,并能对着桩成功挂载。
- 几何 —— 滑块是正圆、比轨道略大、内缩量是推导而非手写,且光晕的可见边缘落在圆盘之外。
- 扫掠 —— 落点瞬间白光不覆盖任何地方,半程时只覆盖靠近滑块的一半,结束时覆盖整条轨道,且从不后退。
- 场 —— 在五种轨道高度下底部边缘都被绘制(包含非格宽整数倍的高度),且轨道的每个区带都持续在动。
- 光照 —— 档位梯度单调,且单帧耗时保持在预算内。
scripts/check-original.mjs 是一个净室检查。这里的场模型是在读过一份参考实现之后写的,
而早期草稿曾经过于接近那份参考;这个脚本会在 src/fx.js 里搜索那份参考的标志性表达式,
一旦有任何一条残留就失败。它是可选检查 —— 参考实现没安装时它会报告已跳过。
环境要求
- DeepSeek Harness,且使用 web profile(本插件针对
conversation.input.model)。 - 从源码构建需要 Node 18 或更高;从 git 安装则不需要构建。
许可证
MIT —— 见 LICENSE。
No comments yet. Be the first to write one.