dsh-plugin-whale-girl
项目简介:修改应用内的图标,并调整尺寸大小。
把 DeepSeek Harness 应用内的鲸鱼图标换成鲸鱼娘,「思考中」前面的小图标换成一只蓝底小米饭小碗,并把中文的「深度求索中」改成「努力干饭中」;两个图标的大小都可以在应用内的插件设置里单独调整。
这是一个第三方 DSH bundle + client plugin 双面包:安装进 profile 后,官方 app.asar 一行都不改,因此应用升级后自动保留。
当前版本:V0.3.0(见 更新记录)。
目录
改了什么
| 位置 | 机制 | 说明 |
|---|---|---|
| 侧边栏左上角图标 | slot 遮蔽 | 以 priority: -1 注册 sidebar.brand.mark(越低越优先),官方 dsh-client-ui-brand-official 保持启用 |
| 新会话首页的鱼 | slot 遮蔽 | 注册 conversation.hero.brand.mark(该 slot 原本空着,走官方 HeroFish 回落) |
| 「思考中」左侧的小图标 | 注入 CSS | 靠 [data-chat-running] + [class*="_runningIcon"] 命中,隐藏官方 APNG/静态 SVG 后重画;默认是内置的蓝底小米饭,也可以单独换图、单独放大 |
| 浏览器标签页图标 | 注入 <link rel="icon"> |
追加而不删除官方链接 |
深度求索中 / 深度求索中,用时 {duration} ··· |
改写 chat 命名空间 zh 字典 |
只改 zh;英文界面保持官方 Deep diving |
| 图标大小 | 两个 mark 组件乘以设置里的倍数,并放开侧边栏对图标的裁剪 | 默认 1.8(放大 80%) |
| 思考中图标大小 | 官方那 14px 的盒子乘以设置里的倍数 | 默认 1×(即官方尺寸);与上面的图标互不影响 |
没有动:侧边栏「DeepSeek Harness」字标、英文文案、任务栏/托盘/欢迎页/app.asar 内任何文件。
安装
& "<DeepSeek Harness>\resources\runtime\cli\bin\dsh.cmd" plugin --profile desktop add "<本目录的绝对路径>"
命令会把它装成 link: 依赖、写进 profile 的 dsh.profile.bundles 并启用(改完源码只需重启,不用重装)。
也可以走应用内 侧边栏 →「插件」→「添加插件」,填本目录的绝对路径。
⚠️ 这个包只插入一行,不要再手写第二行
本包自带
cordis.patch.yml,里面就是- insert: [{ id: whale-girl, name: 'dsh-plugin-whale-girl' }]; 把包列进 profile 的dsh.profile.bundles之后,Loader 自己就会把这一行插进去。 如果你(或某个安装脚本)又在profiles/<名称>/cordis.patch.yml里手写一份同样的insert, 组合结果里就会出现两行同 id 的whale-girl,而 DSH 的设置文档从此再也写不进去:Configuration for "whale-girl" is overridden by a home patch or command-line overlay原因见常见问题;V0.3.0 起 node 半边会在启动时把这条重复行报进日志。
安装后重启 DeepSeek Harness 生效。快速自检(不用开 DevTools):
Invoke-WebRequest -Uri 'http://127.0.0.1:19387/plugins/dsh-plugin-whale-girl/client.js' -UseBasicParsing |
Select-Object StatusCode, @{n='len';e={$_.Content.Length}}
返回 200 即表示宿主已经把插件纳入启动图。
(桌面应用把插件模块走自己的 dsh-app:// 通道时,这条 HTTP 路由可能返回 404,此时以应用内是否生效为准。)
插件设置
设置入口有两个,指向同一份配置:
- 侧边栏 →「插件」→ 点本插件 → 点
whale-girl这一行 → 配置(plugins.row.config,key =dsh-plugin-whale-girl#whale-girl); - 侧边栏 →「设置」→ 鲸鱼娘主题(
settings.section)。
五个设置按用途分成两栏(面板够宽时并排成两列,窄了自动堆成一列):
| 栏 | 设置 | 说明 |
|---|---|---|
| 图标 | 图标 | 侧边栏 / 新会话首页 / 标签页的图。「选择图片…」直接挑本地图片。纯色背景会自动抠掉,画面居中裁切并缩放到 256px 后存起来;选完点「保存」生效。「恢复内置」清空覆盖。 |
| 图标 | 图标大小 | 侧边栏与新会话首页图标的放大倍数:滑块,0.2×–3×,每档 0.2×,默认 1.8×。 |
| 思考中 | 思考中图标 | 「努力干饭中」前面的那个小图标,与上面的图标互不影响:自己挑一张图,按 64px 保留原色存起来。默认是内置的蓝底小米饭小碗。 |
| 思考中 | 思考中文案 | 显示在「思考中」图标旁的中文,默认 努力干饭中。留空即用默认值。计时后的长文案会自动带上「,用时 X秒 ···」。 |
| 思考中 | 图标大小 | 「思考中」那个图标本身的放大倍数:同一个滑块量程,默认 1×=官方的 14px。 |
五个字段(icon / runningIcon / runningText / iconScale / runningScale)各存各的:改一个不会动另一个,tools/selftest.mjs 与 tools/card-check.mjs 都守着这条。两个滑块都按 0.2 对齐并夹在 0.2–3 之间,所以读数就是实际生效的倍数。
设置是怎么存下来的
宿主会为声明了 .volatile() 字段的 Config 的每个活动 entry 开放一个设置命名空间,命名空间就是 entry id —— 也就是 whale-girl。所以 lib/index.js 里那份 Config 就是全部机制:它同时决定了「有哪些字段」和「哪些字段可以在表单里改」。宿主的 describe() 用 volatileForm 走一遍活 schema,走不出字段的 entry 会被整个跳过,因此字段漏标 volatile() 会表现为「设置页是空的」而不是报错。
写入通过 ctx.configForms.get("whale-girl") 的共享镜像落到宿主的设置文档(profile 的 cordis.patch.yml),所以一个浏览器里改了,其它打开的页面会跟着变。
图片是以 data URI 存在设置文档里的(256px WebP,通常几十 KB)。选图时浏览器端就直接抠图 + 缩放,不经过宿主,也不写任何临时文件。
内置的「思考中」图标
assets/running.png(128px):一只碗口冒尖的小米饭,两根筷子从右上斜插进饭里。它由原图经 tools/prepare-thinking.py 抠底、裁切、缩放而来——原图是白底(准确说是一张把「透明」画成棋盘格的图),脚本按下面那条规则把底抠掉,碗和米饭的白色原样保留。
它和官方那个小鲸鱼不同:官方用 alpha 遮罩 + currentColor 着色,本插件直接把彩色图片贴上去。自带背景,所以浅色/深色主题共用一张图,不需要 *-dark 变体;代价是它不会跟着文字颜色变。
它的大小也不写死在样式表里:官方那个 width:calc(14px + var(--dsh-content-font-delta,0px)) 被本插件改写成同一个表达式乘以 --wg-running-scale(没有这个变量时按 1 算),设置里的滑块就是往 :root 写这个变量。所以「思考中图标大小」不需要额外规则,跟着官方的字号增量一起缩放。
换内置素材:
python tools\prepare-thinking.py assets\你的图.png --preview ..\_whale-girl-work\preview.png
node tools\build-client.mjs
抠底规则(内置素材与设置里挑的图共用)
两条,客户端 keyFlatBackground() 与 tools/prepare-thinking.py 各实现一遍:
- 只抠从边界连通的底色:四个角颜色一致(差值 ≤ 24)才算有底色,然后从四边洪泛填充,容差 ±30。画面被角色包住的颜色永远不会被碰。
- 只在贴着底色处做边缘羽化:抗锯齿过渡像素按「离底色多远」给部分 alpha,但仅限八邻域里真的有被抠掉的像素的那种。以前是「凡是接近底色的像素一律羽化」,结果画在底色里的图形被挖空——白底上的白碗、白米饭,53k 个像素被羽化,其中只有 1.7k 真的在边上。
另外会清掉小于画面千分之一的碎块(背景是棋盘格、纸纹、噪点时,洪泛填充会在格子缝里留下小碎点)。实测那张素材:主体是 100,790 px 的一整块,其余 145 块全都不到 135 px,分得很干净。
想换回一根线的鲸鱼剪影?不用改代码:在设置里给「思考中图标」挑一张图即可(白色背景会被自动抠掉)。构建时
.svg优先于.png。
图标大小为什么是一条侧边栏专用的规则
官方的侧边栏给品牌图标留的是一个 24px 的盒子,并且把它裁掉超出的部分:
._logoRow{height:60px;padding:8px 0 8px 4px;overflow:hidden} ← 整行也裁
._brandIdentity{height:24px} ← 高度被钉死
._brand{overflow:hidden} ← 真正下刀的地方
于是倍数一旦超过 1×,图标上下就被切掉:1.25× 只剩 80%,1.8× 只剩 56%,2× 只剩一半。这套规则是 CSS Module 的哈希类名,本插件按后缀匹配(和它匹配 _runningIcon 是同一套办法),并用 :has() 锚在「真正装着我们这个图标」的那个盒子上——否则后缀会连桌面引导页里同名的 _brand 一起命中。滑块量程是 0.2×–3×,所以这些规则必须让 24px 的盒子一直长到 72px 都不裁。
构建产物里的三条规则(见 tools/build-client.mjs):
| 规则 | 作用 |
|---|---|
[class*="_brandIdentity"]:has(> [class*="_brandMark"]){height:auto;min-height:24px} |
让这个盒子跟着图标长高,1× 时仍是 24px,布局不变 |
[class*="_logoRow"]:has([class*="_brandIdentity"]){overflow:visible} |
整行不再裁掉探出去的部分 |
[class*="_brand"]:has(> [class*="_brandIdentity"]){overflow-x:clip;overflow-y:visible} |
横向仍然裁(窄侧边栏里字标照旧被截断),纵向放行——官方在 Windows 标题栏布局下给这个盒子加了 translateY(1px),overflow:hidden 会连官方图标一起咬掉底下 1px |
overflow-x:clip 与 overflow-y:visible 必须成对:按规范,另一轴是 hidden 时 visible 会被算成 auto,那就又变成裁剪容器了。
「思考中」那一行没有这个问题:官方的 _running / _runningContent 既不设高度也不裁溢出,所以放大后整行会跟着长高。
换素材
把图片丢进 assets/,然后重新构建:
node tools/build-client.mjs
支持 .svg / .png / .webp。文件名(除第一个外都可选,缺省自动回落):
| 文件名 | 用途 | 缺省回落 |
|---|---|---|
icon.* |
必需:hero 图标,以及所有槽位的兜底 | — |
icon-dark.* |
主图标的深色主题变体 | icon |
icon-small.* |
侧边栏 24px 专用小尺寸素材。256px 才看得清的细节缩到 24px 会糊成一团,换一张「头像特写」或简化图形效果会好很多。当前素材本身就是头部特写,所以没有这个文件,自动回落 | icon |
icon-small-dark.* |
小尺寸素材的深色变体 | icon-small |
hero.* |
新会话首页专用(可给一张更宽比例的图) | icon |
hero-dark.* |
hero 的深色变体 | hero |
running.* |
思考中图标:14px 显示尺寸,保留原色(不是剪影,也不再被当作 alpha 遮罩)。当前是 running.png(由 tools/prepare-thinking.py 生成,128px) |
icon |
favicon.* |
标签页图标;单独放一张 64px 的小图,可以避免把整张大图再内联一份 | icon |
每个不同的文件只内联一次:构建脚本把素材写成 :root 下的 CSS 变量,各条规则用 var() 取用,所以同一张图被多个槽位共用也只占一份 base64。当前产物 lib/client.js 约 240 KB。
白底插画怎么处理
直接把白底 PNG 丢进去是不行的:贴到深色主题上会是一个白色方块。用附带的脚本抠图:
python tools/prepare-artwork.py "路径\原图.png" --erase 1180,1420,1536,1536
它会输出 icon.png(256²) / favicon.png(64²),外加一张 running.png(64² 剪影)。三个关键处理:
- 边界连通洪泛填充去背景,而不是「凡白即删」——否则白色额带、白蕾丝领、鳍上的白须边会被掏空。
- 小碎点愈合——洪泛填充会连同「被角色包住的一小块白」一起保留,而这类白块常常只是抠图伪影:几个像素的白点缩到 256px 后就变成一个浅灰点,在大尺寸、深色主题下看得见。脚本按面积区分:真正的白色细节比伪影大一到两个数量级(实测 2321px vs 32px),所以低于阈值(源面积/12000)的封闭白块会被填成周围角色的颜色。
- 预乘 alpha 缩放——直接缩放 RGBA 会把已删掉的白底混进边缘像素,在深色背景上留下白晕。
--erase x0,y0,x1,y1 用来抹掉角落的生成水印(可重复);脚本会打印「其中有多少像素原本是图形」,从而确认没有误伤主体。--small-fraction 0.58 可选,用来额外生成 icon-small.png;不给就不生成。
深色主题变体
--dark-lift 0.25 会额外生成 icon-dark.png:把画面朝白色做 screen 混合提亮,暗部提得多、亮部几乎不动,色相保持不变。实测(对 #14161c 的 WCAG 对比度):
| 素材 | 平均亮度 | 低于 3:1 的像素占比 | 平均饱和度 |
|---|---|---|---|
| 不提亮 | 123.9 | 39.4% | 0.52 |
| 提亮 15% | 143.6 | 20.2% | 0.36 |
| 提亮 20% | 150.1 | 11.0% | 0.32 |
| 提亮 25%(当前) | 156.4 | 0.2% | 0.29 |
| 提亮 35% | 169.8 | 0.0% | 0.23 |
25% 是拐点:再往上对比度收益几乎为零,饱和度却在持续掉。
深色规则挂在 body[data-ds-dark-theme] 上,不使用媒体查询:DSH 的深色调色板是 dsh-client-ui-theme 通过这个属性标记的,和操作系统的 prefers-color-scheme 是两回事;按媒体查询写深色变体,在「系统浅色 + 应用深色」时就会用错素材。「思考中」的小图标自带蓝底,浅色深色共用一张,不挂 *-dark。
构建产物与自测
构建脚本把素材内联成 base64 写进 lib/client.js——这是浏览器那一半唯一的产物,不要手改。改完 lib/client.js 后重启应用生效(桌面 profile 默认没有开 client 插件 HMR)。
node tools/host-check.mjs # node 半边:设置字段、重复行自检(纯 node,无依赖)
node tools/selftest.mjs # 在 VM 里跑 lib/client.js:slot / 设置 / 文案 / 降级行为
node tools/card-check.mjs # 起本地服务 + 无头 Chromium,把设置卡片渲染成真 DOM 并量它
node tools/render-check.mjs # 生成 tools/render-check.html:样式表渲染验证页
node tools/pipeline-check.mjs # 起本地服务 + 无头 Chromium,端到端跑「选择图片」链路
node tools/sidebar-fit-check.mjs # 起本地服务 + 无头 Chromium,量侧边栏图标有没有被裁
sidebar-fit-check.mjs 需要从装好的应用里读侧边栏样式:它会按顺序找当前用户与全机的标准安装位置
(%LOCALAPPDATA%\Programs\DeepSeek Harness、%ProgramFiles%\DeepSeek Harness、%ProgramFiles(x86)%\…);
装在别处(例如 D:\DeepSeek Harness)时把路径显式传给它:
node tools/sidebar-fit-check.mjs --asar "D:\DeepSeek Harness\resources\app.asar"
host-check.mjs 覆盖「重复行会让设置写不进去」这条自检的全部判定:干净的 profile patch、块式/流式重复行、home patch 里的重复行、文件缺失、ctx.inject 不可用、profileContext 结构异常。它跑在纯 node 下(判定逻辑在无依赖的 lib/rows.js);运行环境能解析 @deepseek-ai/schemastery 时,它还会额外验证 node 半边的五个 volatile 字段,否则打印一行 skip。
selftest.mjs 用桩 window/document/react/primitives 与桩 locale、设置服务驱动 apply(ctx),覆盖:四个 slot 的注册与 priority/key、locale 命名空间、默认与自定义的图标放大、两栏分组的成员、两个滑块的量程/对齐/夹取/默认值、五个字段各自独立暂存、文案改写与长文案、晚注册字典、摘要视图、卸载还原、以及「内部结构变了就静默不动、绝不抛错」。
card-check.mjs 把 react/jsx-runtime 产出的元素对象展开成真 DOM(展开的是插件自己的组件,不是手抄一份标记),套上构建产物里的样式表后用无头 Chromium 量那些只有排版才知道的事:面板宽时两栏并排、窄时自动一栏、每组第一个控件紧贴标题、两个滑块都是 0.2–3 / 每档 0.2、读数分别是 1.8× 与 1.0×,以及「思考中」盒子随 --wg-running-scale 真的从 14px 变到 42px、清掉变量后又回到 14px。它顺带把这一页截成 tools/card-check.png。
sidebar-fit-check.mjs 直接从装好的 app.asar 里读侧边栏那一半(样式表原文 + 它自己生成的哈希类名),把插件样式表套到官方元素结构上,0.5×–2× 逐档量三种位置的图标——展开侧边栏、折叠导轨、以及 Windows 标题栏布局下的展开侧边栏。每档两种量法:按 overflow 链算出可见比例,再在图标框上取九个点做命中测试。判据是:after 一列一档都不能被裁,before 一列至少要有一档仍被裁——看不见 bug 的检查什么也证明不了。
pipeline-check.mjs 是目前唯一能验证图片选择的办法(createImageBitmap、2D canvas、getImageData、toDataURL("image/webp") 只能在真浏览器里跑)。它给两个图片来源各塞一张「白底 + 蓝色圆盘」的合成图,量各自的产物:图标是 256×256 的 WebP、思考中图标是 64×64 的 WebP,两者都保留原色、透明占比都在 30% 左右;再喂一张「白底 + 深蓝圆环」,抠底必须保住环里那片白(这是「白底上的白碗被挖空」那个 bug 的回归测试)。
render-check.mjs 生成一个页面,把构建产物里的真实样式表套到官方 _runningIcon / _runningWhaleAnimated / _runningWhaleStill 结构上,截图即可确认贴上去的是内置小碗、官方的静态回退图形确实被压掉、而没被选中的槽位保持空白。
常见问题
「保存一直失败」,思考中图标大小也不生效
症状:设置卡片能打开、滑块能拖、读数也会变,但点「保存」永远失败(卡片底部出现「本部署没有接受这些值,已保留供你修改。」),因此 --wg-running-scale 从来没被写过,「思考中图标大小」怎么调都不生效。
原因:profile 的 cordis.patch.yml 里多了一行 - insert: [{ id: whale-girl, ... }],而本包自带的 patch 已经插入过同一行,于是组合结果里有两行同 id 的 whale-girl。DSH 的设置写路径在这种情况下的行为是:
ConfigEditor.edit()把新配置写到最后一行带这个 id 的行上;- 写完后的回读校验却用
find()取第一行带这个 id 的行做比较; - 第一行(bundle 自己插的那行)没有 config,比较不相等 → 直接抛错:
Configuration for "whale-girl" is overridden by a home patch or command-line overlay
settings.mutate 把它包装成 settings/rejected,浏览器半边只看得到「失败」,看不到原因——所以表现为「保存一直失败」。卸载 unset 之所以看起来成功,是因为它对一个不存在的字段不会产生任何 config,回读比较自然相等(于是又白写一遍文件)。
处理:删掉 profile patch 里那一行(本包已经插过),然后重启应用:
# 例如 profiles/desktop/cordis.patch.yml 里删掉:
# - insert:
# - id: whale-girl
# name: 'dsh-plugin-whale-girl'
V0.3.0 起,node 半边在启动时会检查 profile patch 与 home patch,发现重复行就写一条日志:
whale-girl: <文件> declares the entry row whale-girl, which this bundle's own
cordis.patch.yml already inserts. ... remove that duplicate insert row and restart the application.
设置页是空的
lib/index.js 里的字段漏标 .volatile() 就会这样:宿主只会把 volatile 字段投影成表单,走不出字段的 entry 整个被跳过。对照 tools/host-check.mjs 的 settings surface 一节。
图标放大了被切掉
侧边栏默认把图标盒子裁到 24px、整行 overflow:hidden。本插件带了三条放行规则(见上文),tools/sidebar-fit-check.mjs 就是这条的回归测试;如果官方类名结构变了,改动会退化成「又被裁」而不是侧边栏坏掉。
卸载 / 回滚
- 应用内 侧边栏 →「插件」→ 关掉「鲸鱼娘主题」;或
- 从
profiles/<名称>/package.json的dsh.profile.bundles里删掉dsh-plugin-whale-girl;或 ... dsh.cmd plugin --profile desktop remove dsh-plugin-whale-girl
三种方式都会让界面回到官方原样。插件被卸载时会把改写的文案还原(ctx.effect 的清理逻辑)。
兼容性设计
- 不用
instanceof Map判断 locale 内部结构,改用鸭子类型(typeof dicts.get === "function"):既能跨 realm 工作,也能容忍未来换成任何保持 Map 契约的注册表。 - 所有内部访问都有守卫:
locale.dicts结构一变,整个改写变成无操作,文案保持官方原文,界面不受影响。 - CSS 选择器只用后缀匹配(
[class*="_runningIcon"])和稳定 data 属性([data-chat-running]),构建哈希前缀变化不影响。 - slot 用
priority: -1遮蔽而不是禁用官方插件:就算本插件整体加载失败,侧边栏仍回落到官方图标 + 字标。 - node 半边只有一份
Config与一个自检,自检跑在ctx.inject(["profileContext"], …)的可选子 fiber 里并全程 try/catch:没有设置文档的部署照常出主题,自检出错也绝不会拖住插件挂载。 - 只依赖冻结的平台模块表(本插件只
require("react/jsx-runtime")),不需要声明dsh.client.external。
更新记录
- V0.3.0
- 修复「思考中图标大小不生效 / 保存一直失败」:设置写入不再被 profile patch 里重复的
whale-girl行挡住;node 半边新增重复行自检与日志(lib/rows.js、tools/host-check.mjs)。 - 项目简介改为「修改应用内的图标,并调整尺寸大小。」,并补上本 README。
- 修复「思考中图标大小不生效 / 保存一直失败」:设置写入不再被 profile patch 里重复的
- 未发布的早期版本:图标 / 思考中图标 / 思考中文案 / 两个大小滑块;图层抠底与深色变体;侧边栏与思考中盒子的尺寸放行规则。
No comments yet. Be the first to write one.