二次元壁纸主题 · dsh-anime-theme
给 DeepSeek Harness(Web 版 / DSH Desktop)换上一张随机二次元壁纸的主题插件。
点一下「换一张」,它就从图源取一张随机图铺成整屏背景,并处理好几件容易做丑的事: 整图完整可见(不裁掉大半张脸)、留白处用同一张图的模糊放大版晕染(不出现白块黑块)、 正片边缘羽化(消除硬缝)、深浅色主题自动适配。右下角常驻一个可拖动的控制坞,随时换图、切分类、调暗幕。
不需要 Pixiv 账号,不需要 Token,不预爬不缓存 —— 每次触发按需直取。

图源是 cnmiw.com(MirlKoi API)的公开随机图接口。本插件只转发它返回的图片地址, 不下载、不留存、不修改图片。
✨ 功能
| 功能 | 说明 |
|---|---|
| 🖼️ 随机壁纸 | 每次「换一张」直连 API 取随机图,不预爬不缓存 |
| 🖼️ 四档铺满方式 | 自适应(默认):整图可见 + 留白用同图模糊放大版晕染;cover 铺满居中裁切;cover-top 铺满但裁切焦点偏上(保住人脸);plain 整图 + 纯色留白 |
| 🪶 留白不泛白 | 铺底幕色刻意比正片轻(浅色 0.135 / 深色 0.5)并 saturate(1.45),让两侧是「这张图自己的颜色晕出去」而不是一层白膜 |
| ✂️ 边缘羽化 | 按图的实际渲染矩形算一条 mask-image 渐隐带(羽化宽度 = 图宽 ×0.22,24~160px),把正片与铺底之间的硬缝抹平 |
| 🎯 裁切焦点可调 | 「铺满·偏上」档的裁切位置 0~100% 可调(0 = 顶部,100 = 底部),默认 25% 保住人脸 |
| 🌓 幕色跟随主题 | auto 模式下深色主题用暗幕、浅色主题用亮幕,避免浅色模式被压黑 |
| 🌚 深色模式加深 | 深色 UI 配亮壁纸时浅色字压不住,所以深色主题额外多压一档(dim + 0.2,可关),浅色主题原样不动 |
| 🌗 暗幕 / 亮幕 | 壁纸上加可调遮罩,保证任何图下文字都可读 |
| 🎚️ 实时调节 | 暗幕强度、留白虚化实时可调,拖动即预览 |
| 🎛️ 悬浮控制坞 | 右下角常驻胶囊:换一张 / 开关 / 设置。按住可拖动,位置持久化;空闲时半透明(0.45),鼠标靠近或展开设置恢复实底,不挡状态栏读数 |
| ⚡ 预加载后上屏 | 先等图片真正解码出来(读到自然尺寸)再换背景,不会先闪一下空背景 |
| 🔁 失败自动重试 | 三段都有兜底:上游取地址失败退避重试 3 次;拿到地址却取不到图(403 防盗链 / 404 / 超时)也会自动换下一张;全过程保留上一张壁纸不清空 |
| ⏱️ 自动换图 | 可设每 5 / 15 / 30 / 60 分钟自动换一张,改完立即重新排程 |
| ♻️ 关闭即还原 | 关掉插件写空样式表 + 移除自建层,一条规则都不残留,原背景完整回归 |
| 🧩 自建壁纸层 | 挂在 <html> 之下(不是 body 里),避开「祖先 transform 抢走 position:fixed 包含块」导致图被放大的坑;同时把内置主题插件 dsh-skin 那层 display:none 让位 |
| 🛡️ 零 CORS 依赖 | 宿主半体代理上游 API,浏览器端不再受 api.cnmiw.com 缺 CORS 头影响 |
| 🖼️ 防盗链中继 | 直连链路(sinaimg.cn)自动补 Referer: https://weibo.com/,并做域名白名单 |
图源相关(可选能力,默认关闭)
| 功能 | 说明 |
|---|---|
| 🔞 色图 / 无色图 | 两套独立分类:无色图走 sortSfw、色图走 sortNsfw,下拉框跟随模式切换,不会串台。默认是无色图,色图需手动开启 |
| 🚧 混池拦截 | random / CDNrandom 是上游的「全站随机」,会混进色图;无色图模式下宿主会降级到 CDNcat 并在控制坞如实提示,绝不照单全收 |
| 🔁 近期去重 | 宿主侧记住最近 50 张,命中自动换一张 |
📦 安装
方法一:桌面端一键安装(最省事)
装了 DeepSeek Harness 桌面端的话,把下面这行粘进浏览器地址栏,客户端会被唤起并弹出安装确认:
dsh://plugin/install?id=dsh-anime-theme&name=%E4%BA%8C%E6%AC%A1%E5%85%83%E5%A3%81%E7%BA%B8%E4%B8%BB%E9%A2%98&version=1.5.3&repo=zxr2115-1/dsh-anime-theme&permissions=%E7%BD%91%E7%BB%9C%E8%AE%BF%E9%97%AE%2C%E6%9C%AC%E5%9C%B0%E5%AD%98%E5%82%A8%2C%E5%AE%A2%E6%88%B7%E7%AB%AF%E6%A0%B7%E5%BC%8F%E6%B3%A8%E5%85%A5
⚠ GitHub 会过滤
dsh:这类自定义协议的链接,所以在 README 里做的按钮点不动 —— 上面是给你复制的。 想要能点的按钮,本地打开test-uri-install.html(自带按钮、参数预览和排查清单)。 插件市场 deepseek.stream 的详情页不受此限制,那里的「⚡ 一键安装」可以直接点。
协议格式(dsh:// 由桌面端注册,各参数需 encodeURIComponent):
dsh://plugin/install?id={id}&name={name}&version={version}&repo={repo}&permissions={permissions}&downloadUrl={downloadUrl}
前端拉起用隐藏 iframe 即可,不跳转、也不会被弹窗拦截:
const params = new URLSearchParams({ id, name, version, repo, permissions });
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = 'dsh://plugin/install?' + params.toString();
document.body.appendChild(iframe);
setTimeout(() => document.body.removeChild(iframe), 2000);
Windows 上如果协议没注册,跑一次 install.ps1 即可(它会兜底写入
HKCU\Software\Classes\dsh)。验证:
reg query "HKCU\Software\Classes\dsh\shell\open\command"
方法二:一键脚本(推荐)
- 右键
install.ps1→ 使用 PowerShell 运行 (被系统拦截时双击install.bat,或在 PowerShell 里执行.\install.ps1) - 完全退出并重启 DeepSeek Harness(Web 版刷新页面 / 桌面版重开应用)
- 新建会话即可看到右下角控制坞
方法三:手动安装
# 1) 复制到插件目录
robocopy . "$env:USERPROFILE\.dsh\plugins\dsh-anime-theme" /E /XD .git
# 2) 在 profile package.json 的 dependencies 里加一行
# "dsh-anime-theme": "file:../../plugins/dsh-anime-theme"
# 注意:第三方插件不要写进 dsh.profile.bundles,否则会重复加载报错
# 3) 在 profile cordis.patch.yml 追加挂载项
# - insert:
# - id: dsh-anime-theme
# name: 'dsh-anime-theme'
# 4) 建软链并安装依赖
cd "$env:USERPROFILE\.dsh\profiles\web"
cmd /c mklink /J "node_modules\dsh-anime-theme" "$env:USERPROFILE\.dsh\plugins\dsh-anime-theme"
pnpm install
# 5) 自检
node "$env:USERPROFILE\.dsh\plugins\dsh-anime-theme\scripts\check-install.js"
平台差异
| 平台 | profile 目录 | 说明 |
|---|---|---|
| Windows | install.bat / install.ps1 |
用 NTFS 目录联接(mklink /J)建软链,普通用户即可,无需 UAC |
| macOS / Linux | install.sh |
原生 ln -sfn 软链;.sh 与 .ps1 逻辑等价 |
两个脚本都会遍历 web / default / desktop 中所有已初始化的 profile 并逐一接线,
支持 DSH_PROFILE=desktop 指定单个 profile,也支持 DSH_ROOT 覆盖根目录。二者都是幂等的。
profile 目录名:Web 版是 web,DSH Desktop 是 desktop(部分版本为 default)。
🎛️ 使用
重启后界面右下角出现控制坞:
● 二次元壁纸 [换一张] [开] [设置]
- 换一张:重新取一张随机图
- 开 / 关:临时开关壁纸(不清除配置)
- 设置:展开面板,可切换无色图/色图、更换分类 sort、调节暗幕/虚化、切铺满方式、开关深色加深、跑诊断、重置位置
- 拖动:按住胶囊任意位置拖到别处,松手即保存;空闲时它会自动变半透明,鼠标移上去恢复实底
⚙️ 配置文件
路径:~/.dsh/anime-theme/config.json(首次加载自动生成,参考 config.example.json)
{
"enabled": true,
"allowNsfw": false,
"sortSfw": "CDNcat",
"sortNsfw": "CDNsetu",
"autoRefreshMinutes": 0,
"recentLimit": 50,
"recent": [],
"dim": 0.3,
"fillBlur": 40,
"fit": "smart",
"veil": "auto",
"darkBoost": true,
"coverFocus": 25,
"isolatePanels": false,
"dockX": 18,
"dockY": 76,
"schemaVersion": 2
}
| 字段 | 默认 | 说明 |
|---|---|---|
enabled |
true |
总开关 |
allowNsfw |
false |
是否允许色图分类。关闭时宿主会二次拦截,客户端传参无效 |
sortSfw |
CDNcat |
无色图分类。仅限于无色图池,可选 CDNcat cat CDNtop CDNpc CDNmp CDNiw233 iw233 daimao |
sortNsfw |
CDNsetu |
色图分类。可选 CDNsetu setu Capoo yuexinmiao Denia CDNrandom random |
autoRefreshMinutes |
0 |
自动换图间隔(分钟),0 = 关闭。控制坞里有 关 / 5 / 15 / 30 / 60 的下拉 |
recentLimit |
50 |
近期去重缓冲长度 |
dim |
0.3 |
暗幕强度 |
fillBlur |
40 |
留白处模糊铺底的虚化半径 px,范围 16~80。下限 16 是硬约束:再低铺底就退化成一张清晰的放大副本,自适应等于没生效 |
schemaVersion |
2 |
配置结构版本。展示层设置改语义时 +1,老版本配置里语义已变的键会被丢弃并回到新默认值 |
fit |
smart |
smart = 自适应(整图 + 模糊铺底)/ cover = 铺满居中裁切 / cover-top = 铺满、裁切焦点偏上 / plain = 整图 + 纯色留白 |
veil |
auto |
暗幕色调:auto 跟随软件明暗主题自动切换 / dark 强制暗幕 / light 强制亮幕 |
coverFocus |
25 |
「铺满·偏上」的裁切焦点,0~100(0 = 顶部,100 = 底部) |
darkBoost |
true |
深色主题额外压暗一档(正片 dim + 0.2、铺底跟随),浅色主题不受影响。关闭后深浅两档同值 |
dockX / dockY |
18 / 76 |
控制坞相对右下角的偏移 px。默认 bottom 76 是为了让开底部的状态栏读数;拖动后自动写回,越界会被钳进视口 |
isolatePanels |
false |
仅兜底模式生效。是否让本插件自行接管面板透明 token。装了 dsh-skin 时留 false,面板透明度用「设置 → 主题」里的滑杆 |
🔌 HTTP 接口
宿主半体把路由挂在 /dsh-anime-theme 前缀下(同源,无需鉴权):
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /dsh-anime-theme/api/health |
健康检查 |
| GET | /dsh-anime-theme/api/config |
读取配置 |
| POST | /dsh-anime-theme/api/config |
合并写入配置(只接受白名单键) |
| GET | /dsh-anime-theme/api/random?num=10&nsfw=0 |
取随机图地址(服务端去重) |
| GET | /dsh-anime-theme/api/img?u=<urlencoded> |
图片中继(域名白名单 + Referer 补齐) |
🗂️ 分类白名单
按 MirlKoi 上游分类页自己的归组切分(全部实测可用):
| 池 | 上游归类 | sort |
|---|---|---|
| 🟢 无色图 | 无色图 | iw233 / CDNiw233、top / CDNtop、pc / CDNpc、mp / CDNmp |
| 🟢 无色图 | 呆猫八条 | daimao、cat / CDNcat、yin、xing |
| 🔴 色图 | 色图 | setu / CDNsetu、Capoo、yuexinmiao、Denia |
| ⚠️ 混池 | 区块级「全站随机」 | random / CDNrandom —— 跨两个池,只出现在色图模式的下拉框里 |
CDN* 前缀走 setu.iw233.top,免 Referer 更稳;无前缀走 sinaimg.cn,需 Referer: https://weibo.com/(插件自动补)。
🔧 上游 API
- 端点:
GET https://api.cnmiw.com/api.php?sort=<KEY>&type=json&num=<N> - 返回:
{"pic": ["url1", "url2", ...]},num上限 100,无需鉴权 - CDN 链路(
CDNsetu/CDNcat):返回setu.iw233.top图片,免 Referer - 直连链路(
setu/cat):返回sinaimg.cn图片,需Referer: https://weibo.com/(本插件已自动补齐) - 其它分类:
top(精选)、pc(横屏)、mp(竖屏)、random/iw233(全部),均有CDN*变体
api.cnmiw.com不返回Access-Control-Allow-Origin,所以浏览器端不能直连。 本插件的宿主半体代劳这一步,这也是必须装宿主半体而不能只做纯前端注入的原因。
🧩 技术实现要点
遵循 DSH 官方插件工程规范(与 deepseek.stream/guide 白皮书一致):
- 双半体结构:
index.js(宿主,ESM,声明name/inject/apply)+client.js(客户端 bundle) - 客户端加载契约:
package.json必须同时有dsh.client.platform = "web"与exports["./client"], 否则dsh-client-modules会以MissingClientBundleError拒绝加载 - 容器装载切片:
cordis.patch.yml+dsh.bundle.patch - 可选服务注入:宿主用
ctx.inject(["webServer"], ...),没有webServer(如 Electron file:// 宿主)时静默降级,不阻塞插件加载 - 零运行时依赖:客户端只
require("react")(静态表提供),不依赖任何 DSH 客户端包,避免模块图解析问题 - 无 BOM 写入:所有 JSON 产物用
UTF8Encoding($false)写,避免JSON.parse报Unexpected token - 软链优先:
mklink /J(目录联接)普通用户即可创建,改代码即时生效;.ps1本身以 UTF-8 带 BOM 保存,避免 GBK 代码页下中文乱码 - 幂等安装 + 事务备份:重复运行不会重复挂载,改动前一律时间戳备份
- 闭环自检:
scripts/check-install.js真实模拟模块解析并校验软链、dsh.bundle、exports["./client"]
渲染策略(自建层挂在 <html> 之下)
先说清一个背景:deepseek-ai-cordis(市场里的「deep桌面主题插件」)自己就有一层全屏背景:
<div class="dt-bg dt-fade" data-dsh-theme-bg="true"> ← position:fixed; inset:0; z-index:-1
<div class="dt-bg-gradient"> ← 内置主题渐变
<div class="dt-bg-media"><img src="..."></div> ← 图片 / 视频皮肤
<div class="dt-bg-mask"></div> ← 暗幕
</div>
本插件的做法是:把它整层 display:none 让位,自己另起一层。
为什么不能把自建层挂在 body 里
position: fixed 的包含块会被祖先抢走 —— 只要任一祖先带 transform / filter /
will-change / contain / zoom,fixed 就退化成 absolute,inset: 0 于是按那个祖先的盒子算。
盒子比视口大时,cover 会再放大一轮、contain 也会溢出被裁,表现就是「图铺得比屏幕大、显示不全」。
历史上 v1.0 / v1.1 两版都走过弯路:先是和 dsh-skin 抢同一个 z-index:-1 被压在后面完全看不见,
改成挂在 body 里又撞上包含块被抢。现在的做法绕开了这两个坑。
现在的结构
document.documentElement
├── #dsh-anime-theme-bg ← 本插件自建,<html> 的第一个子节点
└── <body>
└── <div class="dt-bg" data-dsh-theme-bg="true"> ← 被 display:none 让位
- 挂到
<html>之下:上面只剩<html>,没有祖先能抢走包含块; - 尺寸显式喂进去:
window.innerWidth/innerHeight写进 CSS 变量--dsh-anime-theme-w/h,resize时重算 —— 不再依赖「包含块 +inset:0」推算出来的盒子; - 明暗标记镜像到
<html>:自建层是body的兄弟节点,用不了body[data-ds-dark-theme]后代选择器,所以由 JS 把data-dsh-anime-dark同步到<html>上供 CSS 取用。
三层绘制
/* ① 元素自身:兜底色 + 显式视口尺寸 */
#dsh-anime-theme-bg {
position: fixed; top: 0; left: 0;
width: var(--dsh-anime-theme-w, 100vw);
height: var(--dsh-anime-theme-h, 100vh);
z-index: -1; pointer-events: none; overflow: hidden;
background-color: #f4f6fa; /* 深色主题下为 #0b0d12 */
}
/* ② ::before 同一张图 cover + 大半径虚化 —— 留白处的颜色晕染 */
#dsh-anime-theme-bg::before {
content: ""; position: absolute; inset: 0;
background-image: linear-gradient(铺底幕色, 铺底幕色), url(图);
background-size: cover, cover;
filter: blur(40px) saturate(1.45);
transform: scale(1.15);
}
/* ③ ::after 幕色渐变铺满 + 正片;smart/plain 用 contain 保整图,cover 系直接铺满 */
#dsh-anime-theme-bg::after {
content: ""; position: absolute; inset: 0;
background-image: linear-gradient(幕色, 幕色), url(图);
background-size: cover, contain; /* 逐层尺寸:幕色永远铺满,图保整张 */
background-position: center, center;
background-repeat: no-repeat, no-repeat;
mask-image: linear-gradient(to right, transparent …, #000 …, #000 …, transparent …);
}
::before / ::after 都是绝对定位、按 DOM 顺序层叠,
且被本层自己的 overflow: hidden 裁掉,不会溢出到应用界面之上。
其余要点
- 不碰
--dsw-alias-bg-*token —— 面板透明度归 dsh-skin 的themeAlpha/dialogAlpha管(它走ctx.theme.overrideTokens),再用!important去压会直接踩坏它的透明度滑杆; 只有完全没装 dsh-skin 时才由本插件接管(isolatePanels开关); - 关闭即还原:
buildCss()返回空串 + 自建层移除,写在 dsh-skin 那层上的display:none一并消失, 原背景完整回归,一条规则都不残留; - 边缘羽化:
::after挂一条按图的实际渲染矩形算出来的mask-image渐隐带, 抹平正片与模糊铺底之间的硬缝(羽化宽度 = 图宽 ×0.22,钳在 24~160px); - 控制坞注册进官方预留的浮动层槽位
shell.overlay(kind: "list",可加性席位), 注册失败时回退conversation.input.dock。
副作用:本插件开启期间,宿主主题自己的壁纸设置(内置主题 / 图片 / 视频)会被让位, 点控制坞的「开 / 关」即恢复。面板透明度始终归「设置 → 主题」里的滑杆。
🪟 与宿主主题共存(0.1.5 dsh-skin / 0.1.6 liquid-glass)
DSH 0.1.6 把内置主题引擎换成了 @deepseek-ai/dsh-client-ui-liquid-glass 2.x,
壁纸层的做法完全变了 —— 旧版认的 [data-dsh-theme-bg="true"] 在新版里根本不存在:
| 0.1.5 及以前 | 0.1.6 起 | |
|---|---|---|
| 根标记 | —— | <html data-dsh-liquid-glass="true"> |
| 壁纸容器 | <div data-dsh-theme-bg="true"> |
<div data-dsh-glass-ambient> |
| 壁纸本体 | 容器里的 <img> |
<canvas data-dsh-glass-canvas>(WebGL 着色器现画) |
| 纹理来源 | 就是那个 <img> |
<div data-dsh-glass-video-holder> 里一个 1×1、opacity:.001 的隐藏 <video> |
最后一行是关键:那个 <video> 肉眼完全看不见,却带着 autoplay loop muted 一直在解码,
画布再把它的帧当纹理渲染成壁纸。所以两套壁纸会叠在一起,而且白烧一路解码器和一份显存
—— 这是「用了动态壁纸后桌面端很卡」的主要来源之一。
2026-10-03 实测补充(0.1.6-max):官方在
0.1.6-re2-community这个构建里 把dsh-client-ui-liquid-glass整个移除了 ——runtime/node_modules/@deepseek-ai下 315 个包、各包的lib/client.js产物里,没有任何一个再提到glass。 顶替它的是@deepseek-ai/dsh-client-ui-theme0.1.6-alpha.2,只负责明暗三态、字号与settings.theme那一行,不再有任何壁纸层。好消息是上面这套接管逻辑不需要改:引擎不在时
themeKind()返回"none"、glassActive()恒为false,所有相关路径都以它为闸门自动早退 ——wireGlassWatch()第 501 行直接return(不建 observer、不排定时器),killGlassShader()/pauseGlassVideo()同理,设置里那行「宿主玻璃层」也不渲染。 实测诊断日志自证:themeKind:"none"、glassActive:false、appliedAfterStyle里壁纸正常铺满、bodyHasDarkAttr:true。
顺带一提:GSH 那个社区版标题写的就是「无壁纸插件版」—— 0.1.6-max 起官方确实不带壁纸了, 本插件从「与宿主主题共存」变成了唯一的背景来源。 检测到
liquid-glass在场时,本插件会:
- 藏掉
[data-dsh-glass-ambient](画布 + 隐藏视频容器),让你的壁纸成为唯一背景; - 把那个隐藏
<video>一并pause(),停掉解码; - 不碰
--dsw-alias-bg-*这组 token ——liquid-glass自己用ctx.theme.overrideTokens把它们设成transparent来造玻璃感,再拿!important盖上不透明底色会把它整层压平、两套风格打架。所以isolatePanels在它面前自动让路。
面板的磨砂、描边与折射 CSS 全部保留,只是背景换成了你的图。
控制坞「设置」里因此多了一行 宿主玻璃层:接管 / 不接管:
- 接管(默认)—— 按上面做,最省 GPU;
- 不接管 —— 保留
liquid-glass自己画的壁纸。会和本插件叠在一起, 且那个隐藏视频继续解码,GPU 占用明显更高。
想进一步降 GPU,可以到「设置 → 主题 → 液态玻璃」把它自己的折射 / 色散 / 涟漪等 特效参数调低或关掉 —— 那部分开销归它管,本插件不越权修改别人的设置。
🧪 离线验证与打包
打包上传
node scripts/package.mjs # → dist/dsh-anime-theme-<version>.zip
打包前会跑一轮硬校验(必备文件、plugin.json 规范字段、platform 取值、两处版本号一致),
不过就拒绝打包。产物是「一个顶层同名文件夹 + 21 个文件」,服务端会自动穿透单层包裹定位 plugin.json。
离线验证
scripts/probe-css.mjs 用一个极简 DOM/React 桩把 client.js 跑起来,抓出它生成的样式表并核对,
不需要启动整个 Harness:
node scripts/probe-css.mjs # 用默认配置
node scripts/probe-css.mjs client.js '{"fit":"cover-top"}'
输出形如:
{
"afterBackgroundSize": "cover,contain !important",
"afterBackgroundPosition": "center,center !important",
"afterMask": true,
"beforeDisplay": "block ",
"beforeFillAlpha": "0.135",
"beforeFilter": "blur(40px) saturate(1.45) !important",
"darkFillAlpha": "0.5"
}
四档 fit 各自的 background-size / background-position / 是否有羽化 / 铺底是否关闭,
以及深浅两档的幕色、控制坞下拉框的选项数与配色,都可以在这一步核对完再上机。
改样式时先跑它,能省掉大量重启。
它还会真的把控制坞渲染一次、模拟拖动与点击、点开「设置」,然后逐项核对。 两条断言都是被真实 bug 逼出来的:
① 下拉框选项隐形(<select> 字色写成 color:"inherit"):
<select> 的字色原先写成 color: "inherit",而 Chromium 在 Windows 上的下拉弹层用 UA 默认浅色底,
<option> 继承了深色 UI 的浅色字 → 白底白字,整个列表全隐形。现在 <select> 显式给
color-scheme,<option> 再各自写死底色与字色,双保险。
② 按钮点不动(拖动时在容器上 setPointerCapture,pointerup 被重定向,
浏览器取「按下 / 松开」目标的共同祖先作为 click 目标,于是 click 落到容器、
按钮的 onClick 永远收不到)。修法是:按下时不捕获,位移超过 5px 确认是拖动后才捕获,
且从 button / select / input 上按下的一律不进入拖动。
探针对应输出:
"drag": {
"fromButton": { "captured": false }, // 完全没动
"fromLabel": { "right": "38px", "bottom": "86px", "captured": true }, // 正常拖动
"persisted": { "right": "38px", "bottom": "86px" } // 写回配置
}
$env:PROBE_DARK='1'; node scripts/probe-css.mjs # 跑深色分支
🩺 故障排查
| 现象 | 原因 | 处理 |
|---|---|---|
| 右下角没有控制坞 | 客户端 bundle 没加载 | 检查 exports["./client"] 与 dsh.client.platform;F12 看 [dsh-anime-theme] client runtime error |
| 控制坞出现但壁纸不显示 | 面板仍不透明 / 图片没取到 | 看控制坞底部提示;确认宿主路由 /dsh-anime-theme/api/health 可访问 |
| 控制坞底部提示「宿主路由:不可用」 | 宿主半体没起来(webServer 缺失) |
检查 profile cordis.patch.yml 是否挂载、index.js 是否有报错;此时客户端会退回浏览器直连,但会被 CORS 挡住 |
启动直接崩溃 declares no dsh.bundle |
package.json 缺 dsh.bundle.patch |
补 "dsh": { "bundle": { "patch": "./cordis.patch.yml" } } |
SyntaxError: Unexpected token |
JSON 带了 UTF-8 BOM | 用 UTF8Encoding($false) 重写该 JSON |
| 图片 403 | 直连链路防盗链 | 换回 CDNsetu / CDNcat,或确认走的是宿主中继 |
pnpm install 报 ERR_PNPM_FETCH_404 |
某个本地插件的依赖被写成 "*",pnpm 会去 npm registry 找它 |
本地插件一律写成 "file:../../plugins/<id>",重跑 pnpm install 即可 |
| 界面打不开 | 插件导致前端故障 | DSH Desktop 用 Harness → 以安全模式重启… 屏蔽第三方插件 |
| 改完插件、重启后行为还是旧的 | pnpm 把 file: 依赖复制进 .pnpm store,改源目录不会传播 |
见下方「改了却不生效」 |
改了却不生效?(一条命令解决)
npm run sync # 同步到本机所有副本 + 逐字节校验
npm run sync:dry # 只看会动哪些地方,不写盘
它会自动发现目标(~/.dsh/plugins/<name> 与每个 profile 的 node_modules 入口,
Junction 会先解析到真身),镜像后逐个核对 sha256,不一致就报错退出。
下面这些是它解决的原理,遇到问题时可以对照排查。
原理:pnpm 的 file: 依赖是复制品
~/.dsh/plugins/dsh-anime-theme 更新之后,profile 里那份不会跟着变:
~/.dsh/profiles/<p>/node_modules/dsh-anime-theme
→ junction → node_modules/.pnpm/dsh-anime-theme@file+..+..+plugins+dsh-anime-theme/node_modules/dsh-anime-theme
.pnpm 里那份是复制品。 文件明明改了、package.json 版本号也对了,宿主照样跑旧代码。
最快确认方式(宿主半会报自己的真实版本):
curl http://127.0.0.1:3080/dsh-anime-theme/api/health
# {"ok":true,"name":"dsh-anime-theme","version":"1.5.3"} ← 版本不对就是没传播
三种解法任选其一:
# A. 正道:让 pnpm 重新复制
cd ~/.dsh/profiles/desktop && pnpm install --force
# B. 删掉缓存的 store 条目再装
rm -rf ~/.dsh/profiles/desktop/node_modules/.pnpm/dsh-anime-theme@* && pnpm install
# C. 零网络依赖:直接把新内容镜像进 store(网络不好时最实用)
# 把 plugins/dsh-anime-theme/ 的内容覆盖到上面那个 .pnpm 目录
node scripts/check-install.js现在会比对内容 sha256,副本是旧的会直接报错并给出上面的命令。
📦 发布
市场(deepseek.stream)只收 .zip,一条命令打出可直接上传的包:
node scripts/package.mjs # → dist/dsh-anime-theme-1.5.4.zip
打包脚本会先自检(清单文件齐不齐、plugin.json 字段全不全、两处版本号一致不一致),
任何一项不过就直接拒绝打包,不会让你把一个会被市场打回的包传上去。
然后把 zip 拖到 https://deepseek.stream/upload 即可。
收录规范、dsh:// 一键安装链接、版本更新清单,以及几条实测踩坑记录
(桌面端宿主会剥离 dsh.bundle、profile bundles 不能放第三方插件、本地依赖必须用 file:、
Windows 下 .ps1 要带 BOM 而 .json 不能带等)都写在 PUBLISH.md。
📝 更新日志
v1.5.4
- 修复 0.1.6-max 上「控制坞正常但背景不显示」 —— 宿主移除液态玻璃引擎后,面板底色再没人调透,
AppFrame的background:var(--dsw-alias-bg-base)(且带position:relative)建了个层叠上下文,把本插件z-index:-1的壁纸层整片盖死。 - 现在只要没有宿主主题层(
dsh-skin与liquid-glass都不在)就由本插件接管--dsw-alias-bg-*这组 token,不再要求先手动打开「分隔面板」。 surfaceStrength()改为给出半透明值(isolatePanels关 = 壁纸更清楚 / 开 = 正文更清楚),原先默认的 0.92 近乎不透明,即使覆盖了也等于没透。- 侧栏还得单独补一刀:
._0TJJea_sidebarCol与侧栏根的.AehNHG_root用的是另一个 token(--dsw-specific-sidebar-fill),不一起调透的话正文透了、侧栏还是一条不透明的板子。已一并纳入覆盖列表。 - 顺手排掉一个假警告:
panelAlphaOf()原先无条件去读dsh.ui-liquid-glass.settings。宿主把引擎删了,那份设置却还留在 localStorage 里(本机l1Opacity=0.1),于是它永远报 0.1、永远弹「面板几乎全透」,而提示让你去的「设置 → 主题 → 液态玻璃」这个页面已经不存在了,按提示根本修不了。现在没有宿主层时直接报我们实际生效的值。 themeKind() === "none"这条分支原先在警告里没有对应文案(会落到 dsh-skin 那支,说的还是错的),已补齐三支。- 控制坞里的「毛玻璃面板」改名为「面板底色」,选项文案由「已开启/已关闭」改为「更实 · 正文优先 / 更透 · 壁纸优先」—— 它现在管的是面板压实程度,原名会让人以为还是液玻开关。
- 验证方式:把
dsh-client-ui-layout的AppFrame规则与dsh-client-ui-theme的 token 定义抄进测试页做 A/B 对比 —— 无覆盖时整页纯白(复现故障),有覆盖时壁纸正常透出。
v1.5.3
- 修掉诊断上报整个失效的问题(这是我自己在 1.5.0 引入的):为了让诊断能反映接管状态,
当时把 liquid-glass 的整个设置对象塞进了载荷 —— 而里面
wallpaper是video:data:video/mp4;base64,…形式的内联视频,实测 291,568 字符 / 285 KB, 而宿主MAX_BODY_BYTES只有 64 KB。超限被request.destroy()掐断,对端只看到 「连接被重置」,再被那句catch { /* 静默 */ }一吞,症状就只剩「诊断日志不再更新」。 - 载荷改为
glassSettingsSummary():只报标量,wallpaper 只留类型 / 长度 / 头部。 - 新增体积兜底:超过 48 KB 自动降级成摘要,并如实记下
{"kind":"oversize","bytes":292547},至少让你知道「它曾经太大」。 - 新增
reportFailure():把渲染进程的异常送回宿主落盘 —— 按 where+message 去重、 带预算上限,但绝不静默;并挂上window.error/unhandledrejection。 boot()改为每一步各自兜住:早先的串行写法里,取图那一环一旦出问题,会把 诊断上报和自动换图定时器一起带走(自动换图静默失效是同一个根因)。- 宿主
readJsonBody()超限时先回413并带上实际字节数与上限,不再无声destroy()。 /api/diag每条记录自动打上插件版本与宿主侧时间戳。- 探针新增诊断载荷体积断言(含
diagCaptured前置条件,避免对空集合做every()而假通过)。
v1.5.2
- 真正停掉 liquid-glass 的 WebGL 着色器。上一版以为「藏画布 + pause 视频」就够,实测都不行:
- 它的渲染循环是无条件的(
document.hidden/visibilitychange/IntersectionObserver/offsetParent全零命中),display:none只是不参与合成,gl.drawArrays每帧照跑; - 更要命的是
drawScene()里那行 自愈式播放:if (customVideo.paused) customVideo.play()—— 每帧都会把被暂停的视频重新拉起来。实测 pause 之后videodecode不但没降, 反而从 4.8% 升到 6.7%。
- 它的渲染循环是无条件的(
- 改走它自己的生命周期:
WEBGL_lose_context.loseContext()→ 触发webglcontextlost→ 它自己active.dispose(),而那个 dispose 里正好是cancelAnimationFrame+ 停掉并卸掉视频。着色器开销与视频解码一起归零。 - 视觉零损失:接管模式下画布本来就已经被
display:none藏掉了。 - 切到「不接管」或卸载时会
restoreContext()+ 主题自行重建,不会把它的上下文永久废掉。 - 探针新增
shaderContextLost断言。
v1.5.1
- 修掉视频解码的时序漏洞:liquid-glass 的
<video>是在hydrateWallpaperOnBoot()里异步创建的,而applyCss()跑在插件 apply 阶段 —— 那时它还不存在,pauseGlassVideo()必然扑空。偏偏它的drawScene()每帧把 video 当图像源读, 所以 CSS 的display:none拦不住解码,实测videodecode一直挂在 4~5%。 现在补了两层兜底:视频容器上的MutationObserver(<video>一出现就摁停)- 5 个延迟重来(兜住容器本身还没建出来的情况)。
- 顺带修掉一个自我重排的死循环:最后一个重试定时器复位标记后又调
applyCss(), 会再排一轮、永不停止(探针跑不完就是它)。改用「已摁停就不再排」的标记收口。 scripts/probe-css.mjs改为显式process.exit(0),不再被挂起的定时器拖住。
v1.5.0
- 已在 DSH 0.1.6-max(
0.1.6-re2-community)上实测通过:宿主移除液态玻璃引擎后, 全部液玻相关路径以glassActive()为闸门自动早退,无需改动即可正常工作(详见上文共存章节)。 - 适配 DSH 0.1.6 的新主题引擎
@deepseek-ai/dsh-client-ui-liquid-glass2.x: 旧版只认[data-dsh-theme-bg="true"],在 0.1.6 上必然落空,会让两套壁纸叠在一起。 - 新增「宿主玻璃层:接管 / 不接管」开关;接管时停掉隐藏视频的解码,显著降低 GPU 占用。
- 可读性提示改为读
liquid-glass的l1Opacity(新版面板透明度由它管)。 isolatePanels在liquid-glass在场时自动让路,避免压平玻璃层与 token 打架。scripts/probe-css.mjs增加三场景矩阵(旧主题 / liquid-glass / 无主题), 断言接管行为与 token 闸门都按预期生效。
v1.4.1
author改为字符串(市场端String()会把它渲染成[object Object])。- 预览图改用 JPEG(842 KB → 102 KB)。
🗑️ 卸载
运行 uninstall.ps1:移除软链、dependencies 项、cordis.patch.yml 挂载项与插件目录。
用户配置保留在 ~/.dsh/anime-theme/,可手动删除。
📄 License
MIT。
图源版权归原作者所有,本插件仅转发公开随机图接口返回的图片地址,不在本地留存。
设计参考:麦麦 (MaiBot) 生态的「来点二次元图片」插件 把同一个图源用在 QQ 群聊里。本插件与之没有代码或依赖关系,只是选了同一个公开 API; 两者的形态完全不同 —— 那边是发图机器人,这边是界面主题。
No comments yet. Be the first to write one.