dsh-custom-theme
DSH 自定义主题:多套主题可新建与切换,配色、背景图、背景音乐、首屏立绘与多条文案(顺序或随机)都可修改。 · DSH Custom Theme: switchable themes with palettes, wallpaper, background music, hero artwork and multi-line copy. 适合需要个性化界面视觉、背景音乐及特定Agent主题联动的用户。
同名パッケージの別リポジトリ
インストール
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:YOGEMOW/dsh-custom-themeドキュメント
README 全文を読む ↗DSH 自定义主题
包名:dsh-custom-theme(0.3.0 之前叫 dsh-theme-everforest)。
DeepSeek Harness 的自定义主题插件:多套主题可新建 / 切换 / 重命名 / 复制 / 删除,每套主题都能改 配色(浅色 + 深色 + 语义色)、背景图与视觉效果、首屏立绘、以及多条轮换文案。
A custom-theme plugin for DeepSeek Harness: several switchable themes, each with palettes (light + dark + semantic), a wallpaper with effects, hero artwork, and multi-line copy shown in order or at random.
设置页在 设置 → 自定义主题(settings.section,id theme-studio)。
功能
| 分组 | 内容 |
|---|---|
| 主题 | 主题列表:双色迷你预览图标(浅色一半 + 深色一半,取该主题的底色/强调色/前景色生成)、改名字、切换「使用」、删除(至少保留一套)、新建主题、复制当前主题;预设主题:Everforest、德谬歌 |
| Agent 预设联动 | 每套主题可绑定若干 Agent 预设 id;会话使用这些预设时自动切换到该主题(会话的 agentPreset 投影一变就切换) |
| 视觉风格 | 模式:浅色 / 深色 / 跟随系统(写回 DSH 内置偏好) |
| 配色 | 强调色、背景、前景、侧栏背景(可留空自动推导)、对比度 0–100 |
| 深色配色 | 「分别设置深色配色」开关;开启后单独编辑深色的一套;关闭时按浅色色相自动生成 |
| 语义色 | 成功 / 错误 / 警告 / 次要 |
| 背景图 | 选择本地图片或填图片地址、主界面可见度 0–100%、子界面不透明度 0–100%、模糊 0–40px、亮度 30–200%、文字遮罩 0–150%、铺满 / 适应 |
| 背景音乐 | 每套主题一首:选择本地音乐(mp3/wav/ogg/m4a/flac)或填链接、启用开关、音量、循环、最小化时暂停;切换主题自动切歌 |
| 首屏立绘 | 选择本地图片或填地址、显示宽度 16–200px |
| 文案 | 4 个字段(首屏标题、角标、首屏输入框提示、会话输入框提示),每个字段可放多条:顺序轮换或随机显示,可增删/上移/下移/换一条 |
修改即时生效(本地推导 token,不等保存回包),约 160ms 防抖后写入配置目录,页脚显示保存状态。
0.8.0:Agent 预设联动改成从宿主预设列表里多选(显示预设名称 + id,勾选即保存),不再手填 id; 新增宿主路由
GET /dsh-theme-studio/agent-presets。0.7.0:新增与 Agent 预设联动——每套主题可绑定 Agent 预设 id,会话的
agentPreset投影变化时 自动切换到对应主题;设置页显示最近检测到的预设 id。0.6.3:修「添加一条没反应」——以前插入空行会被归一化丢掉,现在插入可编辑的占位文案; 同时把编辑时的重复计算缓存起来(预设设计只在版本升级时重建、token 与壁纸样式按值缓存), 删除/连续编辑不再卡顿。
0.6.2:文案列表高亮当前显示的那一条,预览行标出「第 n 条」,并新增「回到第一条」——之前 列表顺序与「当前显示」对不上,是顺序模式每次加载后游标 +1 造成的,不是排序错误。
0.6.1:文字遮罩变成可调选项(背景图分组里的 0–150%),0 即关闭;最小化/切后台时自动暂停 背景音乐,回来继续(每套主题可单独开关)。
0.6.0:提高壁纸模式下的文字可读性——壁纸层自动叠加一层与主题同色的遮罩(scrim), 主界面底色不再完全透明(最多透出 82%),浅色的次级/段落文字会自动向正文色靠拢; 同时新增预设版本同步:预设更新后,已创建的主题会自动接收新设计(手动改过的字段与壁纸保持不变)。
0.5.1:预设主题行去掉「套用到当前主题」按钮,只保留两个预设按钮。
0.5.0:新增德谬歌预设主题(依据昔涟 / 第十三位泰坦设定)与背景音乐;本地图片/音频改为 上传到宿主
assets目录(不再把大图内联进文档,壁纸也不再压缩)。0.4.2:主界面可见度与子界面不透明度拆成两个开关(默认子界面 100% 不透明,解决设置面板/卡片 透出背景文字重叠);主题列表图标改为按主题配色生成的迷你双色预览;图片地址行重排为 「缩略图 + 输入框 + 操作按钮 + 提示」的整行布局。
0.4.1:修掉文案标题显示成
copy.headline这类原始 key 的问题(补了copy.title.*词典并让 「当前显示」改用独立 key);背景图与立绘都支持直接选本地图片。
德谬歌预设(依据昔涟 / 第十三位泰坦设定)
德谬歌是昔涟的完整形态:第十三位泰坦、权杖 δ-me13 的本源,「十三种光彩」,击败铁幕、闭合因果后 以孩童模样回归最初。预设把设定落成这套观感:
| 部位 | 设计 |
|---|---|
| 配色 | 浅色 粉白(#fff7fc 底 / #e58bb4 强调 / #4a3a57 前景),深色 夜幕紫(#161020 底 / #f0a8cc 强调 / #f4e9f3 前景)——粉白发色、蓝眸与星空记忆的对照 |
| 立绘 | 13 个光点环绕的羽翼光环(十三种光彩,内圈成对羽翼 = 权杖本源),尺寸 96px |
| 文案 | 标题「德谬歌 / 第十三泰坦 / 忆海无垠,故事为舟」,角标「第十三泰坦 / 第十三种光彩」,占位「拾起一段记忆…」「三千万次轮回,皆为你执笔」 |
| 壁纸 | 不内置:添加预设时直接沿用当前主题的壁纸(保留其可见度/模糊/亮度/铺满方式);当前主题没有壁纸时才用内置星空兜底 |
| 音乐 | 默认为空并关闭,等你放入音乐文件 |
入口:主题分组底部「预设主题:+ 德谬歌」。
预设同步
从预设创建的主题会记录 preset / presetVersion / presetBase(上一次预设的设计快照):
- 预设设计随插件版本更新(
PRESET_VERSION提升)时,已创建的主题会自动同步; - 同步按字段比较:只有仍等于旧预设值的字段会被替换,你手动改过的颜色/立绘/文案/音乐保持原样;
- 壁纸从不属于预设,始终保留;
- 旧的、改名恰好为「德谬歌」的主题会被自动认领为德谬歌预设。
与 Agent 预设联动
Agent 预设(Session 的 agentPreset 投影)决定一个会话用哪套工具/提示词。主题可以绑定预设,在
会话使用该预设时自动切换过去:
- 设置页「Agent 预设联动」每套主题一行:点「选择预设」展开勾选列表(多选,勾选即保存),
列表来自宿主
agentPresets.list()(名牌 + id,加载失败的预设会标注); - 客户端占用两个无害的会话槽位(
conversation.input.dock与conversation.hero.options)各挂一个 不渲染的观察组件,通过标准 HookuseProjection("agentPreset")读取当前会话预设,一旦变化就切主题; - 没有任何主题绑定该预设时,保持用户当前主题不动;
- 设置页底部显示「当前会话的 Agent 预设」,方便核对是否命中。
背景音乐
- 按主题保存:每套主题有各自的
music.src / enabled / volume / loop / pauseWhenHidden,切主题即切歌; - 本地文件:点「选择本地音乐」上传到宿主
assets目录(也可直接填 https 链接); - 最小化暂停:窗口最小化或切到后台时自动暂停,回到界面继续(每套主题可关掉这个开关);
- 浏览器自动播放限制:首次加载不会自动响,需要你先与页面交互一次(点击/按键),之后切换主题、 重新启用都会继续播放;关闭开关即暂停。
本地文件怎么工作
本地图片/音频不再内联进主题文档,而是上传到宿主:
- 客户端把选中的文件 POST 到
/dsh-theme-studio/asset; - 宿主按魔数识别类型(png/jpg/webp/gif/svg、mp3/wav/ogg/m4a/flac),写入
/theme-studio/assets/.,返回同源 URL; - 文档里只存 URL;
GET /dsh-theme-studio/asset/负责提供文件(带 Range 支持,音频可拖动进度)。
旧的 data:image/... 内联图仍然兼容(此前版本保存的壁纸不受影响)。单文件上限 40MB。
修复:配色改完又变回原色
0.2.0 的设置页把 React 的 key("light.accent")当成了调色板字段名去读写,于是颜色被写进
state.light["light.accent"] 这种无效字段,保存时被宿主归一化丢弃,回包又把旧颜色刷回来。
0.3.0 的修法有两层:
- 键名分离:调色板行按字段名
accent / surface / ink / sidebar / contrast读写; - token 由客户端本地推导:每次编辑立刻
theme.overrideTokens()重绘,保存回包只作回执, 不再用服务端状态覆盖本地草案 —— 从结构上杜绝"保存后被刷回去"。
架构(为什么以后改主题不用重启 DSH)
- 宿主半
lib/index.js:薄的文档存储 + 首屏提示。GET/POST /dsh-theme-studio/document:读写/theme-studio/theme.json;webserver/index-inject:在页面首帧前按boot提示刷底色和壁纸,UI 挂载后由 token 接管 (html[data-ts-ready])。
- 浏览器半
client/client.js:全部主题逻辑(文档模型、token 推导、文案、壁纸、设置页)。 它把模型导出为__model供测试使用。
因此主题功能迭代只需刷新页面(客户端半由 dsh-client-hmr 轮询热更新);只有传输/宿主逻辑变化
才需要重启一次 DSH。
数据
/theme-studio/theme.json # 默认 ~/.dsh/theme-studio/theme.json
{
"version": 1,
"boot": {
"light": "#fdf6e3",
"dark": "#2d353b",
"wallpaper": { "image": "https://…", "blur": 4, "brightness": 90, "position": "cover" }
},
"state": {
"version": 3,
"activeThemeId": "everforest",
"themes": [
{
"id": "everforest",
"name": "Everforest",
"light": { "accent": "#93b259", "surface": "#fdf6e3", "ink": "#5c6a72", "contrast": 45,
"success": "#8da101", "danger": "#f85552", "warn": "#dfa000", "idle": "#829181",
"sidebar": "" },
"dark": { "accent": "#a7c080", "surface": "#2d353b", "ink": "#d3c6aa", "contrast": 45,
"success": "#8da101", "danger": "#f85552", "warn": "#dfa000", "idle": "#829181",
"sidebar": "" },
"separateDark": true,
"background": { "image": "", "strength": 0, "panelOpacity": 100, "blur": 0, "brightness": 100, "position": "cover" },
"hero": { "image": "", "size": 34 },
"music": { "src": "", "enabled": false, "volume": 45, "loop": true },
"copy": {
"headline": { "mode": "sequence", "items": ["探索未至之境"] },
"preview": { "mode": "sequence", "items": [] },
"placeholderHero": { "mode": "sequence", "items": [] },
"placeholderDefault": { "mode": "sequence", "items": [] }
},
"cursor": { "headline": 1, "preview": 0, "placeholderHero": 0, "placeholderDefault": 0 }
}
]
}
}
v1/v2 的单主题文档会在加载时自动迁移到 themes 数组。
文案的轮换语义
- 顺序:每次页面加载显示当前游标那一条,然后把游标 +1 存回文档 → 下次加载显示下一条,循环;
- 随机:每次页面加载随机挑一条(游标不动);
- 设置页的「换一条」立即把该字段切到下一条/随机一条;「回到第一条」把游标重置为 0,显示列表第 1 条;
- 「添加一条」插入一条可编辑的占位文案(「新文案 / New line」)——空行会被归一化丢弃,所以不能靠 插入空串来新建;
- 列表里高亮那一行就是当前显示的那条,预览行写着「当前显示(第 n 条)」。因为游标保存在主题里, 刷新过几次后可能在第二条——不是顺序错位,点「回到第一条」即可回到开头。
背景图怎么生效
应用表面全部由 --dsw-alias-* token 上色,所以壁纸方案是:
- 客户端注入
body::before固定层,铺图片并按设置做模糊/亮度; - 壁纸层上再叠一层与主题同色的遮罩(浅色用浅色 surface、深色用深色 surface,透明度 = 可见度基础值 × 「文字遮罩」百分比;默认 100% 时随可见度从 0.12 升到 0.52,调 0 即关闭)。 这层在内容下方、壁纸上方,用来压住过亮/过花哨的壁纸,保证文字对比度;
- 主界面(页面底色
bg-base、侧栏specific-sidebar-fill)按主界面可见度变半透明,但 底色最多透出 82%(不会完全消失); - 壁纸开启时,次级/段落文字(
label-secondary/tertiary/caption/dimmed)会自动向正文色靠拢, 解决"浅白色段落看不清"; - 子界面(设置面板、弹窗、卡片、输入框等
bg-layer-1/2、bg-overlay、specific-menu/selector) 由子界面不透明度控制,默认 100% 不透明; - 文字与描边始终不透明;主界面可见度 0 = 完全关闭透出。
首屏由宿主的 boot 提示提前画好底色、壁纸与同一层遮罩,避免首帧白闪。
立绘与文案的落点
- 立绘:注册
conversation.hero.brand.mark(首屏标题前的品牌图形),用 `` 顶替内置小鱼; - 文案:没有文案插槽,且同一 locale 重复注册会抛
already has locale,因此用 CSScontent覆盖宿主实际渲染的节点([class*="_titleGroup"]、[class*="_previewBadge"]、[data-composer-placeholder],用:has([data-phase="hero"])区分首屏/会话内)。宿主改结构时 规则自然失效并回退到内置文案。
安装与生效
仓库不含构建步骤:client/client.js 是可读的加载器产物,lib/index.js 只依赖 Node 内建模块。
# 方式一:从 GitHub 安装(DSH 支持 github: spec)
plugin_manager install_bundle # target: github:YOGEMOW/dsh-custom-theme
# 或:dsh plugin --profile
add github:YOGEMOW/dsh-custom-theme
# 方式二:克隆后按本地目录安装
git clone https://github.com/YOGEMOW/dsh-custom-theme.git
plugin_manager install_bundle # target:
- 0.8.0 新增宿主
agent-presets只读路由:升级需要重启一次 DSH(之后联动配置只需刷新页面); - 0.5.0 新增宿主
assets上传/提供路由:首次升级需要重启一次 DSH(之后主题/音乐迭代只需刷新页面); - 0.4.0 改了包名(
dsh-theme-everforest→dsh-custom-theme),当时需要 remove + install; - 宿主传输是
/dsh-theme-studio/document(文件theme.json)与/dsh-theme-studio/asset/*; - 旧的
~/.dsh/theme-studio/state.json可删除(新文件是theme.json); - 卸载:
plugin_manager remove_bundle。
结构
lib/index.js 宿主半:文档存储、assets 上传/服务、首屏注入
client/client.js 浏览器半:文档模型、预设、token、文案、壁纸、音乐、设置页
tests/state.test.mjs 宿主:文件往返、boot 提示、assets 识别与存储、路由注册
tests/client.test.mjs 客户端:模型、迁移、德谬歌预设、音乐、token、文案、壁纸、挂载
locale/{zh,en}.json 插件页标题与描述
examples/codex-appearance-v1.json 最初参考的 Codex 外观
cordis.patch.yml bundle patch
.github/workflows/test.yml CI:跑两个测试套件
测试
node tests/state.test.mjs
node tests/client.test.mjs
28 项断言覆盖:文档往返与缺失回退、boot 提示校验与注入(含遮罩)、assets 魔数识别/存储/路径校验、 Agent 预设列表路由(id/名称/失败标记)、路由注册、默认主题、v2→v3 迁移、 德谬歌预设内容与版本同步(保留用户字段与壁纸)、 音乐配置归一化(含最小化暂停)、文字遮罩 0/150% 的开关与强度、 空白文案行的丢弃与占位行、壁纸 CSS 缓存的复用与失效、 Agent 预设绑定(多选增删/大小写/显示名)与自动切主题、 主题 id 去重、token 双方案取值、壁纸可见度对底色 alpha 与文字对比度的影响、 文案顺序/随机轮换、文案与壁纸 CSS(含遮罩)、boot hint、以及挂载后修改强调色立即反映到 token。
已知边界
- 立绘是首屏品牌图形(约 34px 座位)的替换,不是整屏背景;整屏背景用「背景图」。
- 文案覆盖依赖宿主 DOM 结构,属唯一"贴近实现"的部分。
- 主界面可见度越高,文字背后的对比度越依赖壁纸内容;已有与主题同色的遮罩兜底,仍建议配合亮度/模糊;子界面不透明度建议保持 100%,否则面板文字会与背后内容重叠。
- 深色语义色由浅色提亮推导,设置页只编辑深色的基础四色。
- 背景音乐受浏览器自动播放策略限制,首次需一次页面交互;音频文件存于 DSH 配置目录,不随主题文档同步到其他机器。