DeepSeek Harness Plugin Hub

Publish and manage complete Harness Profiles. Discover Plugins for your next setup.

Explore

PluginsPresetsDocsNews

Community

Publish a pluginContactReport an issue

Resources

Plugin Hub on GitHubDeepSeek HarnessSystem statusPrivacy notice
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

Independent and unofficial. Not affiliated with, authorized by, or endorsed by DeepSeek.

Think Intensity — DSH Plugin for DeepSeek Harness
← Plugins
T

dsh-think-intensity

Think Intensity

DSH Thinking Intensity Slider: Turn “Reasoning Level” into a sci-fi slider that breathes, sends light dots flying, and displays flowing rainbow colors when maxed out

The plugin will be installed here. Keep web if you are unsure.

npx -y @deepseek-ai/dsh plugin --profile web add github:Cyanthracene/dsh-think-intensity#03554c74c6d1a67bee74ab5caa921a81e633f403
READMECompatibilityVersions

Compatibility and provenance

Think Intensity is published as dsh-think-intensity and currently resolves to version 1.0.0. The Hub verifies its manifest and preserves the exact installation source for reproducible installs.

DSH compatibility
*
Runtime surfaces
web
Release source
github
Registry updated
10/15/2026

Versions

1.0.0stable
10/15/2026

Related plugins

Loading related plugins…

Latest
1.0.0
DSH
*
HMR
Process restart
Tree shaking
Safe tree shaking not declared
Unpacked size
Unavailable
Files
Unavailable
Surface
web
License
MIT
Source
github
GitHub
★ 0
Weekly downloads
0
Last push
10/15/2026
View source ↗
README badge

Click the badge to copy Markdown for your README.

Do you maintain this Plugin?Claim benefit · Priority security scan

Verify the GitHub repository declared in package.json to manage this listing. After you claim it, Hub will prioritize a security scan of the current version and publish the result when it passes.

Claim this Plugin →
Report an issue
DeepSeek Harness Plugin Hub
ProfilesPluginsCategoriesNewsDocsSign inManage Profiles
ProfilesPluginsCategoriesNewsDocsSign in

Related plugins

More verified plugins in ui-customization.

Web App@deepseek-ai/dsh-web-appThe dsh browser-surface bundle: the web patch layer over dsh-base plus the runtime glue plugin (frontend dist serving, web-surface prompt, bash runtime variables, URL line)Experimental Agent Team Web Profile@deepseek-ai/dsh-experimental-agent-team-web-profileExperimental Web profile layer for Agent Teams Remote and UI pluginsAb Chartdsh-ab-chartChart rendering tool with the browser card that draws its calls, in one publishable plugin packageClient Ui Skill Explorer@linxin666/dsh-client-ui-skill-explorerDSH skill center: browse loaded skills by source (bundled / project / user / custom / runtime), enable or disable, create and delete, in a web GUI panel.

README

dsh-think-intensity · 思考强度滑块

把 DSH 的「推理等级(reasoning effort)」变成模型选择器旁边一颗可展开的科幻滑块。

一个 DSH 客户端插件:纯 CSS 动画 + 零运行时依赖, 常驻全屏氛围光、跨档冲击波、四类可独立开关的粒子特效。

快速开始

不需要 git(推荐):

npm install https://codeload.github.com/Cyanthracene/dsh-think-intensity/tar.gz/refs/heads/main

或者 clone 下来改(需要 git):

git clone https://github.com/Cyanthracene/dsh-think-intensity.git
dsh plugin --profile desktop add <克隆目录的绝对路径>

装完完全退出并重启 DSH(不是刷新页面)。详见 安装 / 卸载。

自测(零依赖):node test/smoke.mjs → OK: smoke test passed

许可:MIT

它长什么样

  • 位置:输入框工具行里、官方模型选择器左侧那颗 chip(座位 conversation.input.right)。 不再在输入框上方放整条面板 —— 那个座位(conversation.input.dock)是故意不挂的。
  • chip:强度 high ●●●●○ —— 档位名 + 一条点阵(当前档位发光)。 点它向上弹出一个 320px 宽的紧凑面板,里面才是真正的轨道。
  • 滑块:冷青 → 紫的发光轨道(width: 100%,高 30px、行高 44px —— 短而厚), 光泽沿线扫过,刻度线来自模型自己声明的推理等级;拇指是 30×26 的椭圆把手,厚实好抓。 横向刻意收窄(面板从 460px 收到 320px),纵向加厚,不再"摊"在屏幕上。
  • 光点特效:轨道左侧悬着一片会缓慢上浮、明灭的蓝色光尘;档位越高,亮起来的光点越多、光柱越长。
  • 每跨过一个推理等级:全屏冲击波(见下节)。
  • 拉满(最高档)时的额外流彩(都是"体块"而不是细线):
    • 填充条本身变成流动的彩虹带(4 倍宽渐变,1.4s 一圈);
    • 两束方向相反的横扫光带(ti-overdrive + 它的 ::after,1.8s / 2.4s 反向);
    • 面板外围随呼吸缩放的彩色光晕(ti-grid-glow);
    • 拇指处不断炸开的彩虹光环(ti-flare,1.1s);
    • 文字(标签 / 读数 / MAX 徽标)变成流动的彩虹渐变;
    • 连整个输入框卡片都会获得一圈 2.2px 的旋转彩虹描边 + 三色柔光(ti-card-overlay,仅最高档挂载)。
    • 早先那条"面板底部的极光细线"(ti-aurora)已删除:它看起来像一条杂线。

全屏常驻氛围光(ti-amb)

某个档位被选中时,屏幕上常驻一层氛围光 —— 不看控件也能一眼看出当前思考强度。 它与冲击波是两条不同的设计轴:

冲击波 ti-ion氛围光 ti-amb
性质事件:跨档瞬间状态:一直存在
层级浮在最上层(z-index: 2147482000)压在最后层(z-index: 0,<body> 首个子节点)
位置从指针炸开铺满全屏、在所有面板之后
时间0.4 ~ 2.1s几秒到几十秒一个循环
形态同心冲击环离散形状:微粒 / 光环 / 光带 / 极光幕

每一档解锁一层新特效,逐级叠加

档位解锁光尘光环光带极光幕
0(最低)无0000
1漂浮光尘:微粒自下而上飘、逐颗明灭40———
2+ 扩散光环:错开起跑的圆环向外扩散523——
3+ 流光横扫:细飘带上下起伏地扫过6442—
4+ 极光幕:倾斜的彩色飘带横向流动76533
5全部加强88644

特效开关(面板底部)

两个主键,三态互斥 —— 每个模式只有一个决定组合的来源,互不叠加:

默认特效 → 供给 = 档位配方,副键状态完全忽略(灯只是"该档供给什么"的读数)
自定义   → 供给 = 满档强度,副键决定最终组合
特效关闭 → 全灭
控件角色
默认特效 / 自定义特效 / 特效关闭主键:点击在三个状态间循环
光尘 光环 流光 极光副键:自定义模式下决定组合;默认模式下点击不改变画面,但会预先设置自定义要用的组合
冲击波独立开关:任何模式下都可单独关

为什么默认模式必须完全无视副键

曾经让默认模式把副键与档位供给 AND 起来,结果是:

你在自定义模式里取消过某个勾选 → 它写进 localStorage → 切回默认模式后 那个元素仍然被取消,默认供给被静默削掉,最坏情况整屏全黑。

而灯读的是档位供给,所以面板上一切"正常",屏幕却什么都没有。这正是 on=false lv=3 但主键显示「默认特效」的原因,也是"默认特效又没了"的根因。

现在默认模式原样返回档位配方(连副本都不建),所以这是结构性保证: 任何残留勾选都不可能影响默认模式。自测直接断言 「四个副键全部取消勾选 + 默认模式 → 仍然完整渲染」。

副键在默认模式下保持可点(不再锁定):点击不改变画面,但会预先设置自定义要用的组合。 锁定它们曾经是错的 —— 锁住的键无法"预设",而且还把这个 bug 掩盖成"反正点了也没用"。

副键的灯

档位   光尘  光环  流光  极光
 0     ○    ○    ○    ○
 1     ●    ○    ○    ○     ← low:只亮光尘
 2     ●    ●    ○    ○
 3     ●    ●    ●    ○
 4     ●    ●    ●    ●     ← max:四个全亮
  • 默认模式:读档位供给,与副键状态无关;
  • 自定义 / 关闭:读勾选框状态(关闭时画成空心,表示选择被保留)。

自测断言:默认模式下即使四个勾选全被取消,四个灯仍然全亮(因为档位供给它们); 灯只会随档位递增、绝不倒退;max 档极光灯必须点亮。

自定义模式在任何档位都与 max 档一致(含最低档)

最低档(off)默认模式下本来就什么都不供给,但自定义模式在这里必须照常满载。 三个轴的来源必须全部换掉,缺一个都会出问题:

轴从档位取会怎样自定义模式改为
密度low 的光环/流光/极光本来就是 0,min(0,勾选) 恒为 0 → 那三个键永远点不动固定 88/6/4/4
亮度层不透明度随档位衰减(.9 / 只在解锁后点亮)data-custom 让任何非空池 opacity:1
配色最低档的 alpha: 0 会被写进 --ti-glow,即所有形状的光晕变成全透明取表格顶层的配色

第三行是这次真正的元凶:形状数量一直是对的,但在最低档下每个形状都没有光晕, 于是只剩几乎没有对比度的白点/淡描边 —— 看起来就像"特效被关掉了"。

custom per level : 88/6/4/4  88/6/4/4  88/6/4/4  88/6/4/4  88/6/4/4   ← 密度恒定
low rung custom  : 0/6/4/4 | low default 40/0/0/0                   ← 最低档也满载

为什么密度不能四个元素都拉到 88:光环是 220px 圆 + 22px 光晕、极光幕是 34vh 模糊条, 88 个 = 352 个重度元素,会把合成器压垮(表现为画面紊乱与闪烁,不是"变慢")。 所以每个元素保留各自的量级,靠亮度与配色满载达到"满档"观感。

亮度满载由三件事共同保证,全部有断言:

  1. .ti-amb[data-custom='true'] .ti-amb-layer:not(:empty){opacity:1} —— 自定义模式下 任何非空池都是全不透明,与滑块停在哪一档无关(:not(:empty) 保证取消勾选后仍会变暗);
  2. 档位阶梯里没有任何 .9 之类的衰减,每一档都是 opacity:1;
  3. 逐形状亮度与数量解耦(shapeBrightness):原先 --mo/--ro/--so 取自共享随机流, 于是 6 个和 88 个会采到不同的段落、平均亮度不同 —— 这是"没有满载"最隐蔽的那一层。 现在按索引取固定环值,族内平均亮度与数量无关,同时保留逐形状差异。

三态仍然保留:默认特效 → 自定义特效 → 特效关闭。只有"特效关闭"会全灭, 而且它必须是被主动选中的,绝不会因为滑块停在低档就顺带发生。 自测断言 EFFECT_MODES 仍是三态、off 仍能全灭、且每一档的自定义配色都非零。

  • 主键绝不改动副键:切换模式只改 mode,所以切到自定义再切回来,勾选原样保留。

  • "关掉 == off 档"是结构保证:关闭时配方塌成 0/0/0/0、on: false,与最低档同构。 自测同时断言计划形状与实际 DOM(在真实宿主节点上跑 applyAmbient 后逐个读节点数)。

  • 开关是端到端测过的:弹层主体抽成 IntensityPanel,自测直接渲染它并点每一个键。

  • 档位指示条走同一个过滤器,所以它不可能宣传一个没在屏幕上的特效。

  • 颜色编码:主键边框按状态变色(默认=中性 / 自定义=绿 / 关闭=虚线)。

  • 持久化:写入 localStorage(键 dsh-think-intensity/effects),刷新与重启后保持;

    旧版本存档里的布尔 all 会自动迁移成 all:true → 默认、all:false → 关闭。 存储不可用(隐私模式、配额)时静默退回默认模式。

面板底部的状态行

面板最下面一行是一句紧凑状态,因为宿主是应用的兄弟节点,从控件里看不见:

氛围光:全效(过载) · 档 4 · 已开启 · 模式 默认特效

它回答"当前这一档到底有没有特效、处于哪个模式"。曾经还有第二行"宿主实测" (逐个池的形状数与计算后 opacity),用于定位"特效没生成"还是"被 CSS 关掉", 按用户要求已删除 —— 面板只需要表达状态,不需要暴露实现细节。

调试时的替代手段仍在:

  • 宿主元素的属性本身就是完整状态:data-on / data-lv / data-peak / data-custom / data-painted(形状总数),可直接在 DevTools 里读;

  • dsh-think-intensity:ambient 事件带 detail(rung / key / on / alpha / dust / mounted / layers),可监听;

  • 不写控制台:插件没有理由向宿主应用的 console 输出内容。

  • 递进是"结构量"而不是"亮度":自测断言每一档的每一种形状数量都不减少、总数严格增加, 并且断言第 1 档只有光尘、第 2 档解锁光环、第 3 档解锁光带、第 4 档解锁极光幕 —— 也就是"每一档都必须新增一种以前没有的东西"。

  • 为什么放弃"氛围洗色":一层大范围低 alpha 渐变在深色主题上无论 alpha 调到多高都读不出来, 只会变成盖在壁纸上的一层膜,既不够亮又挡背景。能读出来的是"在动的结构", 所以现在每一层都是离散形状(微粒、圆环、光带、光幕),而不是一层纱。

  • 不挡视线:微粒与圆环都是小尺寸、稀疏分布;光带是细飘带(10~16vh)、极光幕是窄条; 整个宿主 pointer-events:none,屏幕中部不做任何堆积。

  • 模糊是稀释,不是"柔化":同样亮度摊在大面积上,峰值会按半径的平方掉下去。 光带一开始用 26px、极光幕整层用 34px,结果"看着有但读不出形状";现在光带 5px、 每张幕自带 14px,光带中心还加了近白色核心 —— 边缘依然柔和,但主体立得住。

  • 形状要"曲",不要"直":矩形 + 纯水平位移读起来就是一根硬邦邦的横条。所以每条光带:

    • 细:10~16vh(之前是 32vh 的板子);
    • 透镜形:border-radius:50% 12% 50% 12%,两端收成尖的;
    • 倾斜并在行进中摆动:关键帧在 -tilt / -0.45tilt / +tilt 三个角度之间摆动;
    • 上下起伏(水波):水平位移之外再叠一条 Y 轴近似正弦(--samp 为振幅, 每条带 2.5~6.5vh 各不相同),一圈内经过 6 个不同的 Y 停点,所以它像水面一样起伏, 而不是沿导轨平移;倾角与起伏的周期数不同,两者叠加后动作永不显周期;
    • 填充也斜:linear-gradient(100deg, …)。
  • 倾角与振幅必须走变量:transform 归关键帧所有,内联 transform 会被关键帧覆盖(静默失效)。 所以极光幕用 --atilt、光带用 --st0/--stm/--st1 与 --samp,全部在关键帧里参与运算。 自测会从已安装的样式表里解析关键帧,断言 Y 轴确实有 ≥5 个停点、≥4 个不同取值, 并且确实消费了振幅变量 —— 也就是说"起伏有没有真的写进去"是可验证的,不靠肉眼。

三条踩过坑的样式事实(都在自测里钉住了)

  1. z-index: 1,不是 0 也不是负值。 0 会让特效落在 shell 那些不透明面板之下 —— 结果是被盖住,而不是"在后面"; 负值更糟,会落到应用自身背景之后。往上走一格才是"在背景之上、面板之下"。 宿主同时永远是 <body> 的第一个子节点(React portal 会往 body 追加节点,所以每次切档都重新沉底一次)。
  2. 正常合成,不要 mix-blend-mode。 把 screen 叠到近黑表面上,结果会被乘上 1 - dst,也就是约 0.05 —— 再怎么加 alpha 都救不回来。
  3. 强度不能用 rgba(var(--x), calc(var(--y,.12) * k)) 表达。 当 var() 内部含 calc() 且变量在计算值时无效,回退值会替换整个 calc() 表达式, 于是每一档都按那个 0.12 回退值上色 —— 这就是"alpha 从 0.10 调到 0.90 却毫无变化"的真正原因。 现在所有与强度有关的数值都在 JS 里算好、以字面量写进样式,并且可以在测试里读回来断言。
  • data-on='false' → 宿主 opacity:0,最低档等于不存在。
  • 可访问性:prefers-reduced-motion: reduce 时停止位移与循环,但形状保留 (在媒体查询里显式重申各层的透明度与静止缩放)。

实现上与冲击波同构:每个文档一个宿主(模块级缓存,跨渲染与卸载存活), 四个形状池全部预建,切档只写 data-on / data-lv / data-peak; 每个池只在其数量真的变化时才重建,且形状的随机参数用确定性种子,重建结果完全一致。

全屏等级冲击波(ti-ion)

每跨过一个推理等级的临界点(不是每次移动),全屏从指针位置炸开一次冲击:

层作用
ti-ion-flashring白热锋面环:纯白粗边 + 内外双向白光,始终最先冲出画面
ti-ion-ring…4彩色圆环,每环是三层叠出来的:白热前缘(::before)+ 饱和色体 + 受光内腔(::after)
ti-ion-core白热核心:中心 97% 白 + 饱和外晕(blur 6px,带真实光溢出)
ti-ion-beam沿指针方向的射线,白心彩尾
ti-ion-splash全屏色晕 + 指针处的硬高光点(这是"打击感"的来源)

力度是档位的函数(递进感)

一组 keyframes + 每次触发写入的自定义属性,圈数 / 半径 / 亮度 / 时长 / 色晕 / 高光 / 锋面粗细全部随档位递增。 BURST_FORCE 每行就是一个完整配方,实测值(5 档模型):

档位  圈数  时长    扩散倍率  核心亮度  色晕   高光   锋面
 0     1    0.40s     10      0.50    0.16   0.22   3.0px
 1     1    0.52s     16      0.62    0.26   0.36   3.5px
 2     2    0.64s     22      0.72    0.36   0.50   4.0px
 3     3    0.80s     30      0.82    0.48   0.62   4.6px
 4     3    0.96s     38      0.90    0.60   0.74   5.2px
 5     4    1.08s     48      1.00    0.72   0.86   6.0px
  • 圈数:0 档只有 1 圈,最高档 4 圈,而且每圈依次更慢、更远、更细,读起来是"一波接一波"而不是一圈更粗;
  • 扩散:末端倍率 10 → 48,最外圈直径从约 840px 涨到 4000px;
  • 射线:从第 2 档开始解锁,沿指针方向射出一道线;
  • 色晕:0.16 → 0.72,低档是一记亮闪,最高档是整屏染色 + 硬高光;
  • 颜色也随档位:burstColors(level, max) 在调色板上按档位比例取色,冷青 → 洋红。
  • 自测直接断言这些单调性(圈数不减、时长更长、半径更大、核心更亮、色晕更强), 并且断言最高档布防的环数严格多于最低档(1 → 4)。

反复来回拖可以无限释放

  • 只建一次 DOM:一个 fixed 宿主 + 固定的环槽池;触发时只切换 [data-arm]、写几个自定义属性、 把 data-run 摘掉再挂上(强制一次 reflow)—— 动画即重放。没有节点增删、没有布局计算。 环槽数量固定为最宽配方所需的 4 个,所以永远不会有第 5 个节点。
  • fire 必须是稳定引用(踩过坑):如果每次渲染都返回新的 fire,每次调用就会各自建一个宿主, 重放标志落在没人看的节点上。chip 每次换档都会重渲染,而换档正是触发冲击波的时刻 —— 所以用 ref 固定它。
  • 每个层都必须声明"静止态"(踩过坑):所有层都写死了 opacity:0 + 起始 transform, 几何与透明度只出现在 keyframes 里是不够的。曾经的写法把 opacity 只放在动画里, 于是"动画没在跑"的瞬间,一个 280px 高斯模糊核心就以全尺寸、全不透明停在输入框上。 现在:静止态 → 不可见;data-run 在时才播;生命周期到了属性被移除。 自测断言每个层都带 opacity:0,且 data-run 是存在性标志而不是 ='true'。
  • 节点必须活过渲染周期(踩过坑,导致过"完全没有冲击波"):宿主节点缓存在模块级变量里, 卸载时不删节点、不清 ref。曾经的清理函数会 removeChild + hostRef.current = null, 于是下一次触发新建了一个浏览器从未绘制过的节点、在它身上重放动画,而旧节点已经脱离文档 —— 动画确实"启动了",但屏幕上什么都没有。重放动画的前提是节点已经被绘制过。 自测会渲染 → 卸载 → 再渲染,然后断言两次 fire 落在同一个节点上、且它仍然挂在文档里。
  • 生命周期必须长过最长的那条动画:ring4 是 1.75 倍基础时长,所以 burstLifetime = dur * 1750 + 260ms。早先用 dur * 1250 + 160,最高档 1.08s 的配方会算出 1510ms,而 ring4 要跑 1890ms —— 标志位会在最后一圈还没扩完时被摘掉。
  • 速率保护:两次触发之间至少隔 60ms,快速划过不会叠成一坨。
  • 无坐标输入也有效:键盘(←/→/Home/End)与原生 range 的 input 事件没有指针坐标, 此时冲击波以屏幕为中心释放。
  • 可访问性:prefers-reduced-motion: reduce 时整个 ti-ion 直接 display: none。

它做什么

  1. 读取当前会话的「模型目录」(ctx.get('modelDirectories')),拿到当前模型自己声明的推理等级列表(例如 low / medium / high / max,不同 provider 不一样)。
  2. 拖动滑块 → 调用与模型选择器同一个入口 directory.select({ provider, model, reasoningEffort })。
  3. 该入口在宿主侧走 session.selectModel,既把等级装到下一个请求上,也会把整份选择保存为以后新会话的默认值——所以这个滑块同时是「本次会话」和「默认档位」。

拖动位置是连续值(拇指完全跟手,不逐格顿挫),松手才吸附到最近的档位并立即写入; 拖动过程中每 260ms 最多写一次远端,避免一路拖过去打出一串请求。

安装 / 卸载

方式 A:不需要 git(推荐给只想用的人)

包管理器直接抓 GitHub 的 tarball,完全不依赖 git:

npm install https://codeload.github.com/Cyanthracene/dsh-think-intensity/tar.gz/refs/heads/main

也可以让 DSH 用这个地址安装(<地址> 换成上面那条 URL):

dsh plugin --profile desktop add https://codeload.github.com/Cyanthracene/dsh-think-intensity/tar.gz/refs/heads/main

方式 B:先 clone(想改代码、想跟着更新)

git clone https://github.com/Cyanthracene/dsh-think-intensity.git
dsh plugin --profile desktop add <克隆下来的目录绝对路径>

让它以 link: 方式挂进 profile 后,直接改那个目录里的文件即可继续同步,不需要重装。

⚠️ 方式选择的关键:git 是不是必须的

用 git 地址(git+https://... 或 github:用户/仓库)安装时,包管理器需要调用系统里的 git。 Windows 默认不带 git,所以这条会直接失败,报错长这样:

npm error syscall spawn git
npm error path git
npm error enoent This is related to npm not being able to find a file.

遇到这个报错,就用方式 A 的 tarball 地址,或者先装 git(winget install Git.Git)。

装完必须重启

完全退出并重启 DSH(不是刷新页面)。原因:

⚠️ 桌面端的 __DSH_BOOT__ 注入表是宿主启动时收集一次、之后一直用内存里那份的, 所以新装 / 新删 / 新禁用插件后必须重启才会生效。

它在 profile 的 package.json 里体现为:

  • dependencies:"dsh-think-intensity": "link:<该目录>"(方式 B)或版本/tarball 记录(方式 A)
  • dsh.profile.bundles:追加 dsh-think-intensity

(该 profile 用 bundles 列表启用插件,所以 cordis.patch.yml 里不会出现插入行; 本包自带的 cordis.patch.yml 是给"把本包当作 bundle patch 层"的宿主用的。)

卸载

把 dsh-think-intensity 从 dsh.profile.bundles 与 dependencies 中移除,重启 DSH。

开发时的一条省事规则

⚠️ 入口 lib/client.js 由宿主以组合 URL(combo URL)下发且会被持久缓存, 所以改入口必须重启;而 lib/client.view.js 是通过带 ?rev= 的 chunk 通道加载的, 改视图刷新页面即可。这条差异能省掉大量无谓重启。

自测

零依赖,不需要安装任何东西:

node test/smoke.mjs      # 期望输出:OK: smoke test passed

test/smoke.mjs 用一个很小的 React 替身跑真实的入口 + 视图 chunk,共 260 余条断言。 除了入口注册、chunk 通道、初始档位、禁用态这些基础项,它还钉住了几条踩过坑才立下的约定:

  • 档位映射不得把两个档位塌到同一层(曾经的 round(i/max*span) 在五档下得到 0,1,1,0,0);
  • 小数档位 / NaN / undefined 输入不得让控件崩掉(26 组敌意输入);
  • 氛围光的依赖键必须覆盖每一个进入 DOM 的字段(漏掉三个数量会导致"某些特效应关不掉");
  • 自定义模式的密度、亮度、配色三个轴都不随档位变化(最低档也要与 max 档一致);
  • 默认模式原样返回档位配方,残留勾选不得影响它(否则会整屏全黑);
  • 轨道几何(track ≥ 24px、rail ≥ 36px)与"插件不得写控制台"。

指针拖拽路径(level → 去抖 → directory.select → 投影回读)不在自测范围内: 替身的 effect/ref 调度只是对 React 的近似,把它掰成 React 的代价大于收益, 这条路径只在真实浏览器里验证。

兼容与降级

  • 依赖 @deepseek-ai/dsh-client-ui-model-selection 提供的 modelDirectories 服务:该服务不在时,条目会明确报错而不是静默失效。
  • 子代理寻址会话不提供模型选择,此时滑块显示为禁用态。
  • 当前模型没有推理等级时,滑块禁用并显示「当前模型不提供推理等级」。
  • 尊重 prefers-reduced-motion:系统开启减少动效时,全部特效动画关闭。
  • 关键布局同时写在内联样式里:即使样式表完全没生效,也会看到一个带边框的可见控件, 而不是一片空白(另有 mainStylesheetLive() 探针 + 兜底样式表)。

结构

文件作用
lib/index.js宿主半区:只为让本包成为一条可挂载的 Loader 行(无服务、无路由、无状态)
lib/client.js浏览器半区(入口):样式、conversation.input.dock 这一行的注册、以及注入面
lib/client.view.js浏览器半区(视图 chunk):折叠/展开、指针拖拽、光点、最高档流彩
cordis.patch.ymlbundle 补丁:插入 id: think-intensity 这一行
package.jsondsh.bundle.patch + dsh.client(platform: web,exports["./client"])声明
test/smoke.mjs本地自测:用 React 替身跑真实 chunk,断言初始档位/禁用态/锚点/折叠态结构

为什么视图要拆成 chunk

桌面壳把每个客户端插件通过组合 URL(/plugins/??<id>/client.js&rev=…)一次性下发, 而 Chromium 对这个 URL 用的是持久磁盘缓存:只要该包的 client.js 修订号不变, 它的字节永远不会刷新 —— 连完全重启应用也一样。

包内 chunk 走的是 /plugins/<id>/client.view.js?rev=…,带查询串、必然回源。 所以在"重装插件换版本"不方便的前提下,它是唯一能保证改动生效的通道。 因此:改动只放视图 chunk,入口尽量不动。

chunk 通道的两条硬性约定(踩过坑)

  1. 调用点必须用单引号:require.async('./client.view.js')。 写双引号会让引号本身进 URL(...%22client.view.js%22), 而 chunk 通道的校验正则是 /^client\.[A-Za-z0-9][A-Za-z0-9._-]*\.js$/ —— 直接不匹配。
  2. chunk 必须自己注册:它是普通的 bundle 脚本,要调用 window.__ModuleLoader__.load({ id: "<所属包>/<文件名>" }), 即 id: "dsh-think-intensity/client.view.js"。 只写 module.exports 不会进模块表,加载会被判为 loaded without registering … via __ModuleLoader__.load。

座位数据形状(踩过坑)

座位注入面的形状必须和 ui-model-selection 自己的一致(内核源码里就是这么写的):

const directory = models.directoryFor(sessionId);
return {
  store:  directory.store,                       // 快照存储:subscribe / getSnapshot
  select: (s) => directory.select(s),             // 动词在控制器上
  load:   ()     => directory.load(),
};

把 控制器本身当成快照存储传进去(我犯过)→ 在其上找不到 subscribe/getSnapshot, 控件要么崩、要么退化成"模型目录服务不可用"。快照里的字段: current{provider,model,reasoningEffort}、groups[].models[].reasoning.{defaultEffort,efforts[]}。

交互设计

  • 入口:chip 默认收起(只有一个按钮 + 档位点阵);点击向上弹出面板,点面板外或按 Esc 关闭。 面板贴到窗口右缘时会自动回缩(margin-right 收边),不溢出视口。
  • 拖拽:指针事件挂在 .ti-pointer 这层上(pointerdown 取 setPointerCapture, pointermove 连续跟手,pointerup/cancel/lostpointercapture 提交)。 原生 <input type="range"> 只保留键盘(←/→/Home/End)与可访问名(clip-path 隐藏、pointer-events:none), 不参与指针路径 —— 早先"只能点不能拖"就是那层隐形 input 抢走了拖动。
  • 手感:拖动位置是连续值(ratio * maxIndex,浮点),拇指完全跟指针;拖动中 data-dragging 关掉填充/光点/拇指的过渡,只留按下的轻微放大;指针层上下各外扩 8px,更容易抓住。
  • 写入节流(这是"切换会卡一下"的修法):轨道每次移动都会先判断四舍五入后的档位有没有变, 只有变了才排队;一次手势里第一档立即发送(单击也要瞬时),后续档位在 120ms 窗口内合并成一次; 松手时清空队列并把最终档位写出去。这样一次拖动最多几个请求,而不是几十个。
  • 过期响应:拖动可能让多个 select 同时在途,只有最新那个的返回值允许回写本地档位, 否则旧响应会把拇指拽回用户已经离开的位置。