← 返回列表
未验证
DeepSeek Harness Web GUI 的自定义背景插件:图片 / 动图 / 视频 /…
尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/9/13 · 已提供中文文档
DeepSeek Harness Web GUI 的自定义背景插件:图片 / 动图 / 视频 / 内置极光渐变,磨砂玻璃质感,配浮动控制面板
综合分
30.4
GitHub 分
30.4
用户评分
—
★ Stars
1
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add liuliyisui/dsh-background该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
数据截至 2026/9/16(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-settings@deepseek-ai/schemastery用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动
README
dsh-background v3
自定义 DeepSeek Harness Web GUI 背景的客户端插件:在界面背后画一层壁纸(图片 / 动图 / 视频)或内置极光渐变,配合毛玻璃界面与一个浮动控制面板,设置持久化在宿主端($DSH_HOME/settings.yaml 的 background: 段)。
功能
- 壁纸:选择本地图片、GIF/WebP 动图或视频(mp4/webm/ogv/mov),选中即上传即生效;支持填充方式与九宫格位置预览。
- 媒体库:上传的媒体持久化到 $DSH_HOME/skin-aurora-media/,面板里可列出、选用、删除;上传按内容寻址,同一文件重复上传不会产生副本。
- 画面调节:遮蔽(变暗)、淡化(融入主题底色)、模糊、透明度。
- 毛玻璃:输入框与侧边栏高斯模糊,模糊半径独立可调。
- 极光渐变:没有壁纸时的默认背景,深浅两套配色。
- 实时生效:面板里改完立刻应用,无需刷新页面。
- 持久化:通过宿主 settings 服务读写(background 命名空间 + schemastery 校验)。
- 零 React 依赖:控制面板是纯 DOM 实现。
使用
1. 重启 dsh web(插件注册需要重启生效),刷新浏览器。
2. 点击右下角 🎛 打开「背景定制」面板。
3. 勾选「启用自定义背景」,选壁纸、调参数。
4. 「恢复默认」一次性清除该命名空间的所有自定义设置。
架构
| 文件 | 职责 |
|---|---|
| lib/index.js | 宿主半部:注册 background settings 命名空间,把媒体路由挂到 webServer |
| lib/media.js | 宿主半部:媒体库路由(流式上传 / 列表 / Range 服务 / 删除),不依赖任何 @deepseek-ai 包 |
| lib/client.js | 浏览器半部:背景层、主题 token 通道、毛玻璃、浮动控制面板 |
| test/media.test.mjs | 媒体路由测试,跑在纯 Node http server 上,不需要 DSH 运行时 |
| tools/ | 开发工具:CDP 探针(probe-gui.mjs)与 GUI token 发现(web-token.mjs) |
浏览器 bundle 以 module-loader 格式(window.__ModuleLoader__.load({id, factory}))由宿主经 /plugins/dsh-background/client.js 提供。
v3 相对 v2 的关键改动
v3 是一次重写,不是叠加。以下每条都对应一个在线上 DOM 或代码里实测确认的缺陷。
1. 背景层原本根本不可见
不透明的 pI_x6G_frame / pI_x6G_root 消费 --dsw-alias-bg-base,且以 position: relative 在绘制顺序上排在 z-index: -1 的背景层之后。v2 只在「设置了壁纸图片」时才把这个 token 打薄,所以纯渐变状态下极光渐变从来没显示过(实测:token 改成 transparent 后 frame 立即透明,改成 rgba(21,21,23,0.25) 即得到「淡化 0.25」)。
v3 把「打薄 token」作为固定的第一步,并让 fade 直接由它实现。
2. 同一个 token 有两个写入方
v2 同时用 标签(!important)和 theme.overrideTokens() 写 --dsw-alias-bg-base,并且颜色功能与图片功能各写一套。v3 只有 overrideTokens() 一个写入方;颜色功能整体砍掉后,这个冲突从根上消失。
3. 毛玻璃其实只糊了侧边栏
v2 的玻璃 CSS 挂在 [data-phase="active"] 下,而 [class="inputBar"] 在真实 DOM 里匹配 0 个元素(真实输入框是 DIV.uV2eYG_input)。所以「输入框毛玻璃」从未生效。
(更正:早期探测说 [data-phase="active"] 也匹配 0 个元素——那是在空会话 hero 状态下测的;活跃会话里它是存在的。第 14 条记录了真正的原因。)
v3 删掉了 v2 里「把 dialog 内所有后代背景强制透明」那条会削掉按钮底色的规则。
14. 玻璃加在了错误的层上(主人报的「输入框没有毛玻璃」)
修完第 3 条之后,玻璃的目标选成了 [class="composerSeat"] [contenteditable]——里层那个可编辑元素。但真正可见的输入框是它外面的卡片:
[data-composer-card] → background: rgb(44,44,46) 不透明;圆角 22px
└─ [data-composer-input] contenteditable → 玻璃加在这里
backdrop-filter 只能模糊它背后的内容,而背后正是那张不透明卡片自己的颜色——所以玻璃永远看不出来。空会话的 hero 与活跃会话用的是同一套卡片,因此两种状态都没生效。
Now the glass is applied to [data-composer-card], and [data-composer-input] is kept transparent (no longer stacking a second layer of surface). These data-composer- are the app's own stable hooks, far more reliable than class names with build hashes.
tools/verify-v3.mjs pins it down with 5 assertions: the card must have a 26px backdrop-filter, the card background must be semi-transparent, and the inner editable must be fully transparent.
15. The glass's own fill must override the background (the "black band in the middle of the input box")
After changing the card to real glass, the user saw a blurry dark band in the middle of the card. The cause was not a CSS mistake, but the inevitable result of glass + wallpaper: backdrop-filter samples everything behind the card, and when the wallpaper is tiled with cover, the input box happens to sit right on the darkest region at the very bottom of the image—blurred by 14px into a dark blob, showing through the 34% background color.
The reason the input box in the reference image looks clean is that behind it is a smooth gradient. So the fix is to let the card's own fill take the lead: a near-solid base color (dark rgba(20,24,36,0.88)), a 1px thin border, plus a very faint vertical gradient, keeping the blur and saturation to maintain the glass texture.
How to actually test it: tools/probe-composer.mjs uses CDP's Page.captureScreenshot with clip to capture a 2x zoomed close-up of the input box; a full-window screenshot can't show this kind of detail. It also outputs a per-row brightness profile, used to distinguish "a semi-transparent card sampling a non-uniform background" from "something is actually drawn there"—in the early days, guessing between these two by eye wasted many rounds.
16. That gradient you added yourself is the "input box black band"
To achieve the layering of the reference image, item 15 once added a linear-gradient(180deg, rgba(255,255,255,0.05), rgba(0,0,0,0.10)) to the card. On such a dark panel, that -10% black is read by the eye as a horizontal band.
The per-row brightness profile is decisive: before the fix, 44 → 35 monotonically decreasing (max difference 9.0); after removing the gradient, 33 → 33 essentially flat (max difference 4.8, almost entirely contributed by the placeholder text itself). Keep the panel flat; don't add decorative gradients.
17. In glass mode the sidebar is washed into a dull gray board
Glass mode gave sidebarCol the v2-inherited rgba(20,28,50,0.55) dark gradient; it stacked on top of the 30% dimming, crushing the wallpaper and turning the sidebar into a dull gray board, completely at odds with the bright, translucent main text area next to it.
Now in glass mode sidebarCol stays transparent: the dimming is delegated to a dedicated blur layer, and the sidebar and main text share the same dimming layer (item 13).
4. Uploads read the file into memory and silently corrupt the binary
v2 used Buffer.concat on the entire request body (up to 512MB), then lazily matched /Content-Type:...\r\n\r\n([\s\S]?)\r\n--/ in a latin1 string to find the file boundary.
Tested on the same valid 98,332-byte multipart data, v2 added only 10 bytes—any image or video containing \r\n-- internally would be truncated at the first \r\n--.
v3 switches to streaming: Transform parses the part header only once, then falls back to a delimiter length before dispatching, so boundaries split across TCP segments won't be misjudged as data; pipeline provides true backpressure.
5. Every slider move writes to disk
v2's four sliders bound input events directly to scope.set(), so dragging a slider once meant dozens of writes to settings.yaml. v3 switches to optimistic local preview + 200ms debounce, and persists in a single batch via scope.mutate() (one drag = one write).
6. The write storm of "restore defaults"
v2 iterated over 16 fields and wrote them one by one, with default values written in a nested triple loop. v3 uses a single atomic operation to clear the entire section:
scope.mutate([{ op: "unset", path: [] }])
7. Frosted-glass blur was hijacked by the wallpaper blur
v2 used GLASS_CSS(Math.max(8, Number(v.blur) || 14)) to reuse the wallpaper's blur value, so setting the wallpaper to 60 would also blur the UI to 60. v3 adds an independent glassBlur field.
8. Images stuffed into localStorage
v2 stored image data URLs in localStorage (5MB quota, settings not portable), and also unset the image field in settings. v3 directly uses media library URLs for lookup, and all localStorage-related code is removed.
9. Cutting unused features
Light/dark background colors (color / colorDark) and the separate dark image (imageDark) have been removed from the schema—the panel never wrote them anyway. Image position changed from a hand-entered CSS value to a nine-grid preset dropdown.
10. The second item of the native dropdown is unreadable
When is expanded, Chromium uses its own light-colored popup background, while the option text inherits the panel's light label color (--dsw-alias-label-primary). Under a dark theme that means white text on a white background—the second item of every dropdown (e.g. "Video") is unreadable.
Now each option's foreground/background is explicitly pinned, and each select's color-scheme follows the theme (refreshed on theme events in sync()).
11. "Wallpaper URL" manual input box removed
壁纸只可能由上传产生,它的地址没有任何手工输入的场景。该行改为只读的「当前壁纸」显示;「选择本地图片 / 视频」变成 选中即上传、即生效,不再需要单独的上传按钮;重新选择走媒体库,另有「清除壁纸」。schema 里 media 字段保留(它仍是壁纸的存储位置)。
12. 状态行误报「命名空间不可用」
插件启动时 settings 镜像还处于 loading,v3 早期版本把它当成 unavailable,而且那句状态只赋值一次、之后再没更新——于是「settings 命名空间不可用」会一直挂在面板上,而背景其实正常渲染。现在只有真正 unavailable 才报错,loading 期间保留既有视图,等首个 ready 快照再落「就绪」。
13. 侧边栏的淡化程度必须和壁纸一致
毛玻璃关闭时,侧边栏的 --dsw-specific-sidebar-fill 原本被设成 纯 transparent,于是它露出原样壁纸,而正文区显示的是淡化后的那一层。两者不一致的直接后果:侧边栏看起来比正文「更透」,文字压在花哨画面上严重掉对比度。
现在侧边栏拿到的是和画布完全相同的 wash(同一个 fade 算出的同一个值),一个滑块同时管住两处;关掉「侧边栏与壁纸同步淡化」则恢复主题原色、完全不透。
tools/verify-v3.mjs 里有一条断言直接钉住这个不变量:--dsw-specific-sidebar-fill 必须与 --dsw-alias-bg-base 逐字相同。
18. 解药是模糊半径,不是不透明度
第 15 条的收尾走了一段弯路:为了压掉输入框里的暗块,把卡片底色一路加到 0.88——暗块没了,但面板变成一块 实心板,玻璃也彻底看不见了。
两条路都试过之后才看清这是个两难:玻璃之所以「看得见」,就是因为它透出背后的东西;而壁纸在输入框后面正好压着最暗的一块。
真正的解法是加大卡片自身的 backdrop-filter 模糊半径:把背后那张高对比插画糊成一片柔和色场,玻璃透出的是颜色而不是形状——这正是参考图的观感。最终形态是「中等底色 + 模糊下限」:
| 形态 | 逐行亮度极差 |
|---|---|
| 0.34 底色(暗块透出) | 局部成块 |
| 0.88 + 装饰渐变 | 9.0(单调下满 = 一条带) |
| 0.88 平色(实心板) | 4.8 |
| 0.52 底色 + blur 28px | 2.9 |
断言也改成防退化:卡片必须有 blur(≥ 16px),且底色必须落在 0.3~0.85 的通透区间——既不能薄到露出暗块,也不能厚成实心板。
19. 「玻璃要多透」是审美,把它交给滑块
第 15~18 条来回改了四轮(0.34 → 0.72 → 0.88 → 0.52),每一轮都是我在猜主人想要多透。这种审美选择不该由实现者猜,所以现在它是一组参数:
- 毛玻璃模糊(px,0~40):卡片与侧栏共用;卡片有 16px 下限,低于它背后的壁纸会露出硬形状而不是化成色场。
- 玻璃不透明度(0.2 很透 ~ 0.95 近实心):一个旋钮同时驱动输入框和侧栏,两者永远一致——不会出现「输入框透了但侧栏还闷着」。
侧栏的玻璃也随之回来了:玻璃模式下给 sidebarCol 刷的是主题自己的底色按该不透明度的半透明版本。既不是全透明那种发闷的灰板,也不是 v2 那套硬编码的深蓝渐变(rgba(20,28,50,0.55),它会把壁纸压死、让侧栏变成一块外来板)。
默认 glassOpacity: 0.45。tools/verify-v3.mjs 新增两条断言:滑块必须存在,且把它设成 0.8 后卡片与侧栏的 alpha 必须同时变成 0.8。
可读性:主题与壁纸的明暗搭配
这是背景类插件的固有代价:把 --dsw-alias-bg-base 打薄之后,正文直接坐在壁纸上,文字对比度就由壁纸的明暗决定。tools/legibility.mjs 能算出真实数字(重建「壁纸 → 遮罩 → 淡化 wash」的合成背景,再按 WCAG 公式计算对比度)。
实测同一张浅色壁纸(平均亮度 0.763)、fade: 0.25:
| 主题 | 通过 ≥ 4.5:1 | 不合格 | 严重 /client.js&rev= 提供且带 cache-control: immutable,指纹随文件内容变化——这是判断「改动没生效」还是「这个窗口还跑着旧 bundle」的唯一可靠依据。为了看清它,曾经白查了好几轮。
- 不做迁移:settings.yaml 里残留的 color / colorDark / image / imageDark 会被 schema 直接忽略。
- 面板真正写过的字段(enabled / blur / overlay / fade / opacity / size / glass / sidebar / mediaType / muted / gradient)名称与含义保持不变。
安装位置
只有两个真实位置(实测确认):
- 源码:F:\DeepSeek Harness\dsh-background\
- 运行副本(实际被加载的):%DSH_HOME%\profiles\web\node_modules\dsh-background
- profile 依赖登记:%DSH_HOME%\profiles\web\package.json 的 dsh.profile.bundles
注意:%DSH_HOME%\vendor\DeepSeekHarness 是一个指向 F:\DeepSeek Harness 的 Junction,不是独立副本——往它里面复制等于复制到源码自己身上(会把 sharing violation)。profile 里 file: 依赖写的是这条 junction 路径,所以源码即 vendor。
改动生效顺序:改源码 → 复制到运行副本 → 重启 dsh web。
开发与验证
媒体路由测试:纯 Node http server,28 项,不碰正在运行的服务
node test/media.test.mjs
探测线上 GUI:背景层是否可见、玻璃选择器是否命中、面板几何、真实类名
node tools/probe-gui.mjs baseline # 截图写到 tools/shots/
端到端面板验证:驱动真实面板,23 项(上传 / 批量写 / 独立玻璃模糊 / 侧边栏淡化一致 / 一次性 reset)
node tools/verify-v3.mjs --url http://127.0.0.1:3080
可读性量测:重建合成背景,按 WCAG 算每个文字元素的对比度(最差在前)
node tools/legibility.mjs --url http://127.0.0.1:3080
tools/web-token.mjs 从 Electron 的 main.log 取回当前 dsh web 的访问 token(0.1.5-rc.1 起裸地址返回 401),并回源校验一次,所以重启后不用手动更新。tools/cdp.mjs 是零依赖的 CDP 客户端(用 Node 内置 WebSocket,不需要 puppeteer)。
验证时怎么避免影响正在使用的 GUI
dsh web 进程同时承载着正在进行的会话,直接重启会把自己这一轮对话掐断。做法是起一个隔离实例:独立 DSH_HOME(只把 profiles 用 junction 复用),换端口跑,验证完删掉。注意隔离目录里会有一份 .credentials.yaml 副本,清理时必须先删 junction 再递归删除,否则会顺着链接删进真实 profile。
已知部署说明
1. 重启生效:插件行为 bundle 需要重启 dsh web 后才会被 dsh-client-modules 扫描进 window.__DSH_BOOT__。
2. settings 命名空间白名单:若网关(@deepseek-ai/dsh-host-apiproxy)仍只暴露 WEB_SETTINGS_NAMESPACES 中的命名空间,需要把 background 加进该数组;升级 @deepseek-ai/dsh 后若被覆盖需重新加回。
3. 客户端半部改动后刷新浏览器即可;宿主半部(lib/index.js / lib/media.js)改动需要重启服务。
20. 「背景定制」面板本身也要磨砂
主人要的一直是面板自己有毛玻璃(我前几轮误当成消息输入框,白改了好几回)。面板原先是不透明的 var(--dsw-alias-bg-layer-2),现在跟随时钟设置:开启毛玻璃时面板变成半透明 + backdrop-filter,底色用 color-mix 从主题色派生(深浅主题自动跟随)。
面板的浓度固定 78%、🎨 按钮 88%——它们装着大量小字和控件,可读性优先;它们不跟随「玻璃不透明度」滑块(那个管输入框和侧栏)。
断言:面板必须有 blur(),且解析出的 alpha ≥ 0.6。注意 color-mix 序列化成 color(srgb r g b / a) 而不是 rgba()。同作者(liuliyisui)的其他插件
扫码进群