dsh-effort-slider:把模型選擇和思考強度收進一條星光滑軌

前言

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 。

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

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

小夜