- Главная
- Плагины
- Улучшения UI
- dsh-thinking-slider
dsh-thinking-slider
lihihe/dsh-thinking-slider
Turns the composer's reasoning-effort menu into a draggable slider and takes over the stock model-and-reasoning-level chip. Dragging follows the pointer continuously; releasing snaps to the nearest level and submits one session update per drag. The track draws its own fill, tick marks and white knob, only the highest levels (xhigh, max, ultra) get a flowing brand-colour gradient, and both ends carry the lowest and highest level names. The same card expands into the official model list. Arrow keys step one level, PageUp and PageDown two, Home and End jump to the ends, and each move submits. A generation counter on the directory object discards late responses, and reduced-motion turns the animations off. All data comes from the stock modelDirectories directory; no new capabilities are added.
Установка
dsh plugin --profile web add github:lihihe/dsh-thinking-sliderREADME
dsh-thinking-slider —— 推理强度滑条
把 DSH Web GUI 输入框右侧的「推理等级」从菜单点选换成可拖拽的滑条: 按住滑块左右拖,拖动中连续跟手、松手自动吸附到最近的档位并提交; 拖动过程实时预览档位,但只在松手时发一次请求。
官方的「选择模型 + 选择推理强度」那枚 DeepSeek-V4.1-Flash · High chip 会被本插件接管:
座位本身接管不了(那是 single 插槽,见下文),所以插件在运行时把它隐掉,
把选模型并进自己这枚 chip 与卡片里 —— 数据和官方菜单同一份(ctx.modelDirectories)。
目标来源:用户原话「加入功能:滑动调整思考强度,与GPT类似」(m00002); 视觉口径来自用户后续要求「修改思考强度滑动插件,完全仿照GPT的样式」(m00001); 接管官方座位来自用户原话「修改:覆盖原版的选择思考强度与选择模型按钮(图中显示 DeepSeekV41flash high 的部分),把选择模型功能集成到滑动条旁边」(m01989)。
它长什么样
输入框右侧只剩这一枚 chip(官方座位已被隐藏),它是「谁 + 多强」——
[⚡ DeepSeek-V4.1-Flash · 高 ⌃]
↓ 点开卡片
┌──────────────────────────────────┐
│ ⚡ 高 › ↻ │ ← 点「高」往上切一档
│ DeepSeek-V4.1-Flash ⌄ │ ← 点模型名展开模型清单
│ ┌────────────────────────────┐ │
│ │ ✓ DeepSeek-V4.1-Flash │ │ ← 清单 = 官方模型菜单的同一份数据
│ │ DeepSeek-V4.1-Pro │ │ (多 provider 时插分组标题,
│ │ OpenAI(登录已过期) │ │ 加载失败的 provider 不可选)
│ └────────────────────────────┘ │
│ ●━━━━┿━━━┿━━━┿━━━┿━━━┿━━━○ │ ← 26px 胶囊轨道 + 6 颗刻度点 + 白圆钮
└──────────────────────────────────┘
模型不支持推理等级时,chip 只剩模型名(它这时就是一个纯模型选择器), 卡片里用一行说明代替滑轨 —— 再也不会整块消失(否则藏掉官方座位后就没法切模型了)。
「完全仿照 GPT」的六条硬约束(照 ChatGPT composer 的推理强度滑条,参考实现见 openclaw PR #155402 里同款控件的 CSS):
⚠️ 这张表是第一轮(2026-10-11 上午)的口径。同日更晚用户拿了素材图要求按素材重做, 于是轨高、圆钮尺寸、刻度点、最高档渐变与星尘全部换了规格 —— 以 手感:连续跟手 + 动效 一节和预览页为准, 下表里「刻度点都没有」「中性 20px 圆钮」这些已经是历史。
| 元素 | GPT 口径 | 本插件实现 |
|---|---|---|
| 触发 chip | 中性文字 chip + 图标,没有彩色圆点 | TRIGGER 无背景、无边框,图标是官方 IconThinkOutlineRegular(中性色) |
| 轨道 | 一根单色胖胶囊(26px 高、满圆角、1px 极淡描边) | RANGE_CSS 的 height: 26px + inset 0 0 0 1px var(--dsh-ts-edge) |
| 已填充段 | 文字色 18% | TRACK_FILL = color-mix(in srgb, var(--dsw-alias-label-primary) 18%, transparent) |
| 未填充段 | 文字色 7% | TRACK_REST = … 7% …(深浅主题自动成立) |
| 圆钮 | 白色实心 + 柔和投影,按住外扩光晕 | 20px,:active 外扩 6px 中性光晕(不用品牌色) |
| 刻度点 / ± 步进 | 都没有 | 无 −/+ 按钮、无刻度点;档位范围靠两端档位名表达 |
| 最高档 | 只有最高档才上品牌色 + 流动 | BOOST_EFFORTS = ["xhigh","max","ultra"]:容器打 data-boost,渐变由 RANGE_CSS 的 [data-boost]::before 画(linear-gradient(105deg, …)、background-size: 220% 100%、dsh-ts-effort-flow 6s 往返,ultra 4s),并把轨道自身置为透明让渐变透出来;进/出最高档是一次 280ms 的淡入淡出,不是硬闪 |
其余细节:
- 拖动:连续跟手 + 松手吸附。
onInput只把填充比例写进 CSS 变量(零重渲染), 松手(onPointerUp)时把落点吸附到最近的档位(Math.round,一半处朝强度更高一侧)并提交一次。 整条滑条就是唯一操作面 —— GPT 样式里没有 ± 步进按钮,也没有刻度点。 - 选模型:卡头那行模型名就是按钮,点开是官方模型菜单的同一份数据(多 provider 插分组标题,
当前项打勾,加载失败的行不可选、hover 显示原因)。清单常驻 DOM、开合是 300ms 过渡:
卡头那行点一下,卡片高度跟着一起长/缩(外层
grid-template-rows: 0fr ↔ 1fr),清单本体淡入 + 轻微下移。 收起态只是被裁掉,所以顺手把每行tabIndex设成-1、给整块加aria-hidden,读屏与 Tab 都不会碰到它。 - 键盘:
Tab聚焦滑条,←/→、Home/End、PageUp/PageDown按整档移动后自动提交(Tab本身不提交)。 - 两端标签:左端是最低档、右端是最高档的名字(目录声明了
defaultEffort时左端就是那个档, 没声明时左端是「默认」)。 - 返回默认:只在「能回到一个默认档」且当前不在默认档时出现;样式是中性文字按钮,不用品牌色。
- 并发保护:用目录对象上的世代计数器丢弃迟到的响应,快速连拖不会出现「A 的结果盖掉 B 的结果」。
- 加载中:目录还在加载且一个具名档位都没有时,滑条锁定为只读(
.rail:has(range:disabled)压到 55% 透明)。 - 降级:
@media (prefers-reduced-motion: reduce)下关掉最高档的流动动画与全部过渡(填充、圆钮、档位名、模型清单开合)。 - 预览:
node preview/build-preview.mjs && node preview/serve.mjs→ http://127.0.0.1:3017/ 用插件自己的真实常量渲染一份离线外观(含浅色/深色切换与可直接拖的交互演示),手机上不用 devtools 也能核对手感。
手感:连续跟手 + 松手吸附 + 动效(2026-10-11)
拖动中「连续跟手」,松手「吸附到最近档位」——两个动作分工明确。每一条改动都有具体的病根:
| 改了什么 | 为什么(原来的病根) |
|---|---|
step: 0.01,value 用连续位置,档位语义交给 Math.round | 原来 step: 1 + 受控整数只有 4~5 个落点,圆钮一格格跳 |
拖动中只写 --dsh-ts-fill(setProperty),不进 React state | 原来每个 input 事件都 setState → 整张卡片重渲染 → 掉帧 |
拖动中临时 transition: none,松手恢复 --dsh-ts-fill 260ms cubic-bezier(0.22,1,0.36,1) | 开着过渡,填充会花 260ms 去「追」手指,手感发黏 |
松手把草稿值换成 Math.round(落点)(吸附),且过渡在写草稿前恢复 | 原来落点保持连续值 → 圆钮有概率停在两档之间,既不在档位中心、也和档位名对不上;换成整数后由 CSS 那条 320ms 回弹过渡滑过去,看起来是「被最近的档位吸过去」,而不是「啪」一跳 |
| 吸附只在松手那一刻做,拖动中一次都不吸 | 拖动中也吸附的话,3~5 档的滑条会一格格跳,跟手感立刻没了(这正是当年从 step: 1 改掉的原因) |
松手时把 lastRaw 记成手指离开处的连续值 | 浏览器松手时会补发一个重复的 input 事件,不带这一条就会把刚吸附好的圆钮拽回两档之间 |
| 权威档采纳后保留吸附落点 | 原来 setDraft(null) 会让圆钮「啪」地跳回档位中心 |
提交中不禁用滑条(busy 时只加 data-busy 把圆钮压到 72% 透明) | 原来 disabled: locked || busy 会让 :has(:disabled){opacity:.55} 在往返期间闪整行 |
渐变改由 [data-boost]::before 画,进/出最高档走 opacity 280ms | 原来渐变是 inline style,进出最高档是硬闪 |
档位名 <p key={levelName}> 重挂 + dsh-ts-level-in 200ms 淡入上浮 | 原来换档是数字硬切,看不出「换了一档」 |
模型清单常驻 DOM,开合交给 data-open + grid-template-rows: 0fr ↔ 1fr(300ms) | 原来「展开才挂载」:收起时内容先在 1 帧内消失、空出来的高度再慢慢收,像被「抽走」;常驻才能让淡出与收缩同步,卡片高度也一起过渡(reveal 夹在 gap: 10px 的内容列中间,收起时用 margin-bottom: -10px 抵掉一条 gap,卡片高度与改动前逐像素一致) |
清单本体 opacity + translateY(-6px) + 内层 clip(overflow: hidden)承担裁切 | overflow: hidden 得有自己的盒子才裁得干净;clip 顺手给清单 margin: 0 -6px 留出横向余量,否则细滚动条会被裁掉半边 |
拖动中若重渲染,位置读 lastRaw(ref)而不是草稿值 | 否则任何一次外部重渲染(目录快照更新等)都会把圆钮从手指底下拽回上一个跨档点 |
| 键盘按整档移动 | step 是 0.01,交给浏览器就变成 0.01 的微调,按 100 次才走一格 |
@media (prefers-reduced-motion: reduce) 关掉动画与过渡 | 无障碍:前庭敏感用户不该被流动渐变打扰 |
语义完全跟随官方模型菜单
推理档位不是自己编的,全部来自官方客户端服务 ctx.modelDirectories,
判档规则与官方「推理等级」菜单(@deepseek-ai/dsh-client-ui-model-selection)逐条对齐:
| 情况 | 官方菜单 | 本控件 |
|---|---|---|
模型没有 reasoning 字段 | 不显示 Effort 行 | 卡片里换成一行说明(card.noEffort),chip 只显示模型名 —— 但座位继续渲染,因为官方那枚 chip 已被它隐藏 |
模型有 reasoning 但 efforts: [] | 只有默认档 | 滑轨锁成单点,不可拖 |
未显式选 effort、目录声明 defaultEffort | 显示该默认档 | 滑块停在该档(effectiveEffort = reasoningEffort ?? defaultEffort) |
目录没有声明 defaultEffort | 多一项「Default」 | 最左多一档「默认」,选中时整字段省略 |
| 提交档位 | selectModel({provider, model, reasoningEffort}) | 同一路径:directory.select(...) |
| 提交模型 | 同上 | 同一路径(sendSelection),切换模型时档位按 transferEffort 迁移 |
| provider 加载失败 | 菜单里一行不可选的失败项 | 清单里同一行不可选,hover 显示原因(failures) |
「默认」档提交时请求体里不带 reasoningEffort 字段(而不是发一个空值)——这是官方的
「provider default」语义。
接管官方「选择模型」座位
官方那枚 chip 注册在插槽 conversation.input.model(@deepseek-ai/dsh-client-ui-model-selection),
而这个插槽是 single 类型:@deepseek-ai/dsh-client-ui-slots/lib/index.js:172 对重复注册直接
throw new Error('single slot "…" already has a registration …')。抢占不进去,也不该去抢
(注册顺序一变就是一颗炸掉整个前端的雷)。
所以走「运行时让位」:
- 官方那一排控件的 DOM 是
div.standardControls的[renderSlot("conversation.input.right"), renderSlot("conversation.input.model")], 官方 chip 是其中最后一个元素子节点。 - 插件从自己座位的父节点往上找(最多 6 层),取「最后一个装了别人
aria-haspopup="menu"按钮的兄弟节点」——那就是官方座位,给它打上data-dsh-thinking-slider-native="hidden", CSS 里display: none !important。 - 切换会话会让官方座位重挂 DOM(session 作用域 entry 带 session generation key),
标记会丢,所以用
MutationObserver(childList)盯着父节点重打标记。 - 官方座位处于锁死状态(
disabled,即locked = removed || inert || !live)时不接管, 保持原样;目录还没就绪(directory === null)时也不接管 —— 失败模式是「两个 chip 都在」, 而不是「一个都没有」。 - 卸载/切会话时移除标记,把官方座位交还回去。整段副作用全在
try {} catch {}里, 任何异常都只降级成「不接管」,不会像裸调ctx.slots.inject那样带走整个前端 boot。 - 跑任务时这一排可能整行消失,此时本插件的 chip 和官方那枚 chip 一起不显示 ——
不是插件坏了。真机上实测到两条独立的隐藏链路(
seat-geometry的killers字段会把 「到底是哪条 CSS 规则干的」直接指出来):- 皮肤(实机证据):装了
@smalltailqwq/dsh-client-ui-skin-maid-atelier时它有一条body[data-dsh-maid-atelier] [data-phase=active] [data-composer-seat][data-maid-composer-capsule] :is([data-input-scroll],[data-composer-card]>[class*=row]){display:none!important}—— 会话phase=active(正在跑)且合成器处于胶囊模式时,整行控件被皮肤收起来。 这是皮肤有意的胶囊行为,不是本插件能也不该去对抗的。 - 官方:
tools与standardControls都带hidden: activity(@deepseek-ai/dsh-client-ui-conversation/lib/client.js:17494、:17530-17531, 配.uV2eYG_tools[hidden],.uV2eYG_standardControls[hidden]{display:none}), 有后台活动回报时官方自己也会把这排藏掉。 两种情况都在会话空闲时自动恢复(接管成立时只剩本插件这一枚 chip)。 要确认视觉效果,只能在空闲时刻截图。
- 皮肤(实机证据):装了
与官方的一处有意不同:换模型时官方 selectionOf 一律回到新模型的 defaultEffort,
本插件走 transferEffort —— 目标模型也声明了同 id 的档位就把它带过去(「高」还是「高」),
没声明才省略该字段(等于用新模型的默认档)。跨 provider 换模型时这个差别最明显。
架构:双面包 + 两条接线纪律
插件是双面包:
lib/index.js(宿主半边):功能上什么都不做,只注册一条排障用的诊断路由POST /dsh-thinking-slider/diag(把浏览器半边回报的状态逐行追加到$DSH_HOME/thinking-slider-diag.log)。它存在的另一个理由是——双面包要在dsh.profile.bundles里有一行且能import成功,浏览器半边./client才会被 web app 组合进window.__DSH_BOOT__。lib/client.js(浏览器半边):全部功能。通过window.__ModuleLoader__.load({id, factory})注册,require只用react/react-dom/@deepseek-ai/dsh-client-ui-primitives三个模块。
没有打任何引擎补丁(对比 dsh-flow-control 需要 tools/patch-engine.mjs):
选中推理等级直接调官方客户端服务 directory.select({provider, model, reasoningEffort}),
与官方模型菜单共享同一份 pending / 竞态 / 错误语义,因此不需要宿主侧状态。
挂载点是官方的 list 插槽 conversation.input.right(order: 45,排在流控按钮 39/40 之后),
不替换官方 conversation.input.model 模型座位。
三条踩过坑的接线纪律(第一版「重启后输入框右侧什么都没有」就是因为第 3 条):
-
factory 顶部必须自己声明
module/exports。真实契约是dsh-client-modules/lib/client.js的materialize():exports: registered.factory(this.makeRequire(ownerId, edges))—— factory 只拿到require一个参数,模块接口就是它的返回值,浏览器里没有module/exports这两个自由变量。直接写exports.apply = apply; return module.exports;会在首次装载时抛ReferenceError: exports is not defined, 表现是插件静静地什么都没注册(没有任何界面提示)。正确开头(官方 bundle 与dsh-flow-control都这么写):window.__ModuleLoader__.load({ id: "dsh-thinking-slider", factory: (require) => { var module = { exports: {} }; var exports = module.exports; /* … */ return module.exports; }, });单测必须同样只传
require(test/helpers.mjs的loadClient已按此收严), 否则这个错误在测试里永远看不见。 -
inject只声明["slots"]。声明了却没到的服务会让插件永远停在 pending, 界面上同样表现为「什么都没有」,也没有报错(引擎日志里只有N entries did not activate这种不含插件名的汇总行)。模型目录服务modelDirectories由官方dsh-client-ui-model-selection按需提供,可能后到, 所以客户端用轮询等待(useDirectory,250ms × 40 ≈ 10 秒)。 -
座位注册不声明
locale命名空间。官方渲染器(dsh-client-ui-renderer)在 「座位声明了命名空间、当下却没有文案面」时会抛SlotAssemblyError,整个座位 直接不显示。文案于是走本插件内置的中文兜底(t === null分支)。 想用官方t就必须先确认文案面已经就绪。
为什么它不会再弄死 DSH(2026-10-11 加固)
这一节的由来:用户报「这个插件这么容易崩,DSH 已经因为这个插件死 3 次了」(m00457)。
机制:一个插件抛错 = 整个界面打不开
DSH 前端 boot 收集所有客户端插件入口,只要有一个不是 active(failed 和
pending (waiting for service…) 都算),就整体抛错:
web boot: 1 entry did not activate
dsh-thinking-slider: failed
外层 catch 走 page.fail(...),页面停在 「Failed to load plugins」,应用永远不
mount —— 用户看到的现象就是「DSH 打不开,只能杀进程重启」。这条错误与它前面的
apply-throw 都写在诊断日志里(当时的证据:02:43–02:56 之间 6 次
did not activate,每次前面都有一条 apply-throw)。
三处加固
| 纪律 | 做法 |
|---|---|
apply() 永不抛 | 每一步都过 quiet(label, step):失败只写一条 diag(apply-error / css-error / locale-error / seat-error / seat-skip),返回值 undefined。最坏降级 = 座位没注册,界面照常。 |
| 服务访问永不裸奔 | ctx.get(...)、ctx.slots、ctx.locale、client.modelDirectories 全部走「try + 属性兜底」;cordis 的未 inject 服务被访问时会抛 cannot get property "X" without inject,那正是历史上把 boot 带走的那条异常。 |
| 加载期也不行 | require("@deepseek-ai/dsh-client-ui-primitives") 包在 loadPrimitives() 里,失败退化成 {};图标缺席时 ThinkIcon === null 只是不画图标,不会让 createElement(undefined) 抛错。 |
| 渲染期兜底 | 座位外面本来就有一层 SliderBoundary(错误边界),渲染异常 → 只丢掉这一个座位 + diag render-error。 |
发布也要原子
node tools/publish.mjs:先跑单测(不通过不发布)→ 重建静态预览 → 写同目录临时
文件再 rename() → 逐字节复核。为什么不能就地 cp:client-hmr 每 500ms 给
bundle 做 stat,一变就让浏览器重新拉这个文件;读到写了一半的 JS(语法错误)会让
入口变成 failed,回到上面那条「界面打不开」。rename() 在同一目录内是原子的,
读者只会看到旧版或新版。
诊断通道
手机 WebView 没有 devtools,控件不显示时无从取证,所以浏览器半边会把
apply / 插槽注册 / 目录就绪 / 座位渲染(含 sessionId、目录是否可用、模型是否
支持推理、档位数)/ 渲染异常都 POST 到宿主路由,落到:
tail -f /data/user/0/app.dsh.mobile/files/dsh-home/thinking-slider-diag.log
判读方式:
| 日志里有什么 | 说明 |
|---|---|
| 连日志文件都没有 | 宿主半边没被激活(profile 没组合进去) |
只有 host-apply(引擎每次启动一行) | 宿主起来了,但浏览器半边没回报 —— bundle 没装载 / apply 没跑 |
有 apply,没有 register | 插槽注入没跑(服务没到 / 插件 pending) |
有 register,没有 seat-render | 座位没有渲染(组合失败或座位被跳过) |
seat-render 里 sessionId: null | 座位没拿到会话(不是 session 作用域) |
seat-render 里 hasDirectory: false | 目录服务还没到(会显示虚线占位点) |
seat-render 里 supported: false | 当前模型没有推理等级 —— 卡片里换成一行说明,chip 只剩模型名(座位仍然渲染) |
render-error | 渲染抛异常,座位已被边界吃掉(控件不显示) |
native-seat {hidden:true} | 官方那枚「选择模型」chip 已被隐藏(接管成功);{hidden:false} 是交还或没找到 |
native-seat-error | 接管官方座位时抛了异常(已被吞掉,行为降级成「两个 chip 都在」) |
seat-geometry(每次挂载一行) | 座位的几何取证。「chip 看不见」先看这个:chain 是自身往上 12 层的 TAG.class|display/WxH(#hidden = 该层带 hidden 属性、#mark = 该层是插件打的接管标记、#inline = 该层内联 display:none);marked 是被标记的官方座位是谁;instances 列出文档里所有 …_standardControls(MINE 前缀 = 自己这枚 chip 所在的那个);killers 用 rowEl.matches(selectorText) 逐个样式表反查,直接列出「是哪条 CSS 规则把这排隐藏了」——实机就是这么抓到皮肤那条 [data-maid-composer-capsule] … [data-composer-card]>[class*=row]{display:none!important} 的。 |
apply-error / css-error / locale-error / seat-error | apply 的某一步失败(已被 quiet 吞掉,所以界面不会死);看 message 字段定位 |
seat-skip | slots 服务还没到,这一轮安静跳过(正常时序,不算错误) |
primitives-missing | 官方组件包 require 失败,插件退化成「无图标」继续跑 |
这条通道只是排障旁路,删掉 apply 里的路由注册不影响控件本身。
安装
DSH_HOME=/data/user/0/app.dsh.mobile/files/dsh-home
cp -r /storage/emulated/0/.A工作区/dsh-thinking-slider "$DSH_HOME/harness/plugins/dsh-thinking-slider"
ln -sfn ../../../harness/plugins/dsh-thinking-slider "$DSH_HOME/profiles/web/node_modules/dsh-thinking-slider"
然后在 $DSH_HOME/profiles/web/package.json 里加两处(cp 备份后手改或让 pnpm 处理都可以):
"dependencies": { "dsh-thinking-slider": "link:/data/user/0/app.dsh.mobile/files/dsh-home/harness/plugins/dsh-thinking-slider" },
"dsh": { "profile": { "bundles": [ /* … 追加 */ "dsh-thinking-slider" ] } }
最后必须重启引擎(GUI 设置 → 重启引擎)——profile 的 bundles 列表只在引擎启动时组合。
卸载:从 dsh.profile.bundles 与 dependencies 里删掉两行,删掉
node_modules/dsh-thinking-slider 软链和 harness/plugins/dsh-thinking-slider 目录,重启引擎。
测试
cd /storage/emulated/0/.A工作区/dsh-thinking-slider
node --test test/*.test.mjs # 57 passed / 0 failed
零依赖:test/helpers.mjs 里自带一个极简 react 桩(createElement / useState / useRef /
useEffect / useSyncExternalStore /createPortal 的最小可用实现,按组件函数用 WeakMap
保存 hook 状态,所以可以跨帧模拟「点开卡片 → 拖动 → 松手」)、primitives 桩(每个组件渲染成
带 data-primitive 的 div)、假的 document 和假的 cordis ctx
(slots.inject 会立刻回调 register,与真实注册表一致)。
为什么不能直接跑真件:
@deepseek-ai/dsh-client-ui-primitives是带 CSS import 的 ESM, Node 里 require 不动;react 也不在引擎的 node_modules 解析路径上。所以桩掉。
覆盖范围:档位判定的四种形态、buildStops / stopIndexOf / resetIndexOf / fillPercent、
槽位注册(order: 45、不声明 locale)、卡片布局(卡头三列 / 等级行 › / 模型行 / 滑轨 / 没有 ± 步进按钮)、
模型选择(modelChoicesOf 铺平 provider 与失败行、分组标题只在多 provider 时插、当前项勾选、
点另一个模型提交 {provider, model} 且档位按 transferEffort 迁移、点当前项/失败行不发请求、
失败时清单不收以便直接改选)、接管官方座位(isNativeModelSeat 的判据、
pickNativeSeat 从后往前挑、nativeSeatOf 最多上溯 6 层、hideNativeSeat/showNativeSeat 幂等、
属性名 data-dsh-thinking-slider-native)、
GPT 视觉契约(轨道颜色不含品牌色、BOOST_EFFORTS 固定为 xhigh/max/ultra、railStyleFor() 只出布局、
railBoostOf/railBoostAttr 决定 data-boost、rangeVarsFor 只在最高档让轨道透明、热重载后 <style> 内容被刷新且标签仍只有一个)、
手感契约(step: 0.01 拖动中连续不吸附、拖动中同档内不改 state、重渲染读 lastRaw 不被拽回跨档点、
松手吸附到最近档位(两个方向 / 正好中点朝强度更高一侧 / 两端越界被夹住 / 本来就是档位则原地不动 /
空操作也要吸回档位中心 / 吸附只改落点不改请求体)、失败后草稿丢弃、
模型清单开合(常驻 DOM、data-open 驱动 grid-template-rows 0fr ↔ 1fr、收起态 aria-hidden + 每行 tabIndex: -1、
三层结构 reveal/clip/models、同一个节点不重挂)、prefers-reduced-motion 关掉动画与过渡)、
「拖动只提交一次」、provider-default 省略字段、同档短路、键盘按整档移动后提交、滑条顶端语义、
「返回默认」两条分支、{ok:false} 与抛异常两条错误路径、迟到响应被丢弃、加载中锁定、
没有 t 时回退中文文案。
兼容性
用了这些官方公共接口(均在 2026-10 的引擎构建里核对过):
- 插槽:
conversation.input.right(kind:"list"、scope:"session"→ 组件收到sessionId)。 座位不声明locale命名空间(声明了却没有文案面会让整个座位被渲染器丢弃), 文案走内置字典 + 中文兜底。 - 服务:
slots、locale、modelDirectories(ctx.provide("locale", …)/super(ctx, "modelDirectories"))。 - 目录:
directory.store.getSnapshot()/subscribe()、directory.load()、directory.select()。 - primitives:
MenuSurface、Toast(onDone)、Tooltip(label)、IconThinkOutlineRegular、IconChevronDownOutlineRegular、IconChevronUpOutlineRegular、IconLoadingOutlineRegular、useAnchoredPosition、useDismissOnOutsidePointer。 - 样式:自己的
<style data-plugin-css="dsh-thinking-slider/range.css">(幂等注入,热重载时按内容 就地刷新,不会把旧 CSS 留在页面上),原生<input type="range">+::-webkit-slider-runnable-track/::-webkit-slider-thumb/::-moz-range-track/::-moz-range-progress;单色胶囊用--dsh-ts-fill-color/--dsh-ts-rest-color/--dsh-ts-edge三个变量画,最高档由.dsh-thinking-slider-rail上的--dsh-ts-track-bg: transparent+ 品牌渐变接管;颜色全部取 DSH 的--dsw-alias-*变量, 跟随浅色/深色主题(深色下自动变成「近白的 18% / 7%」胶囊)。
Похожие плагины
dsh-web (dsh-task-board)
zhu1090093659/dsh-web
dsh-web (dsh-web-all)
zhu1090093659/dsh-web
dsh-web-ui (dsh-task-board)
zhu1090093659/dsh-web-ui
dsh-web-ui (dsh-web-ui-all)
zhu1090093659/dsh-web-ui