给 DSH Web GUI 换上 20 款 base16 配色

Clevebitr Lv3

DeepSeek Harness(下面简称 dsh)的 Web GUI 能像换主题一样整站换配色。这个能力不是 dsh 核心自带的,而是社区插件 dsh-web 全家桶(@linxin666/dsh-web-all)里的「皮肤中心」提供的——皮肤依赖这个插件才能被加载。而皮肤中心自带方案不多,社区皮肤也大多是一套一套手搓的,想要”更多配色”就只能重复劳动。

于是我做了这个项目 dsh-base16-skins(仓库地址),把 base16 这个标准化配色体系里的 20 款方案,一次性喂给皮肤中心。

Github

前置依赖:换肤能力来自 dsh-web 插件

先把依赖关系说清楚,免得装完发现设置里根本没有入口。

  • 换肤功能的提供者是插件 @linxin666/dsh-web-all(DSH Web UI 全家桶聚合插件),皮肤中心本体是它依赖的子包 @linxin666/dsh-client-ui-skin-center。
  • 本仓库只产出皮肤资产,不含任何换肤代码。皮肤中心对自己的定位写得很明确:皮肤是纯资产目录(内置的 + $DSH_HOME/skins),只由皮肤中心加载和渲染。
  • 所以顺序是:先装 dsh-web(皮肤中心)→ 再把皮肤放进去。没装插件的话,install.sh 拷进去的皮肤没有任何东西会去读它,设置里也不会有「皮肤中心」这个入口。

安装插件(profile 侧,pnpm 由 dsh plugin 转发):

1
2
3
dsh plugin --profile web add @linxin666/dsh-web-all@latest
dsh web --dump-config # 预检:bundle 层是否登记
dsh web --no-open --port 3085 # 真机验收:另起实例,不动 3080

装完打开 设置 → 皮肤中心 就能看到入口。全家桶里还有 task-board、git-graph、pet、usage、remote-web-ui 等一堆插件,皮肤中心只是其中之一。

如果你在 Android / Termux 上装这个插件,还有一批额外的坑要处理(pnpm 11 只认 allowBuilds 布尔映射、node-pty 必须与 DSH 核心同版本、cloudflared 在 bionic 上不可用等)。这些我整理在另一篇里:让 DeepSeek Harness 在 Android / Termux 上跑起来。

为什么是 base16

base16 是一套已经约定好语义的 16 色体系,槽位含义固定:

槽位含义槽位含义
base00背景base08红(错误、变量)
base01背景层次 1base09橙(常量、数字)
base02背景层次 2base0A黄(警告、类)
base03注释、暗线base0B绿(字符串、成功)
base04暗前景base0C青(转义、链接)
base05默认前景base0D蓝(函数、强调)
base06亮前景base0E紫(关键字)
base07最亮前景base0F棕(弃用、内嵌)

语义固定带来一个很舒服的结果:只要写一套映射公式,喂任何调色板都能自动得到正确的明暗极性,不需要为每个方案单独手调。上游 tinted-theming/schemes 已经积累了 300+ 款方案,加新配色就是加一个 YAML 文件的事。

项目做了什么

一句话:把 base16 的 16 个颜色,重映射到 dsh 的全部设计 token,并接管 Shiki 语法高亮。

每款皮肤输出一个目录,每个 skin.css 里有 259 条 CSS 自定义属性:

1
2
3
4
5
6
skins/<id>/
├── skin.json # skin-center v2 清单(名称/作者/出处/许可/排序/预览图)
├── skin.css # 259 条 token 映射 + 滚动条/选中态
└── preview/
├── light.jpg # 预览图(两种模式同一套配色)
└── dark.jpg

覆盖的 token 家族:

  • --dsw-alias-*:官方设计 token 主体(表面/边框/品牌/按钮/交互态/文本/Markdown/滚动条/状态/浮层)
  • --dsw-specific-*:侧栏、气泡、输入框、菜单、选择器
  • --shiki-*:代码块语法高亮
  • --aion-*:dsh-web 插件生态在用的另一套变量家族
  • --dsh-scrollbar-*:滚动条

映射规则(generate.mjs 里的 tokens()):

1
2
3
4
5
6
7
8
9
const accent   = p.base0D                       // 强调色统一取 base0D
const toward = (c, t) => mix(c, p.base00, t) // "淡化"一律朝背景混合

'--dsw-alias-bg-base': p.base00, // 表面
'--dsw-alias-bg-layer-1': p.base01,
'--dsw-alias-label-primary': p.base05, // 正文
'--dsw-alias-state-error-primary': p.base08, // 状态色
'--dsw-alias-state-success-primary': p.base0B,
'--dsw-alias-state-warn-primary': p.base0A,

注意 toward() 这个细节:所有”变淡/变暗”的派生色都朝背景混合,而不是朝黑或朝白混合。这样同一套公式在深色底和浅色底上,对比度的变化方向都是对的。

四个值得说的设计决策

1. 恒深 / 恒浅,而不是跟随模式

dsh 官方把深色的 token 声明在 body[data-ds-dark-theme] 上,而不是 :root。所以只写 :root 会被它压住。

同时 Shiki 的语法高亮颜色是按模式切换的——如果只覆盖一半,深色皮肤在浅色模式下就会出现”深底深字”,直接不可读。

于是每款皮肤在 :root 和 body[data-ds-dark-theme] 下写同一组值,也就是”恒深/恒浅”:

1
2
3
4
5
6
7
8
9
10
11
:root {
color-scheme: dark;
color: #abb2bf;
background-color: #282c34;
}

body[data-ds-dark-theme] { /* 同层覆盖,否则被官方声明压住 */
color-scheme: dark;
color: #abb2bf;
background-color: #282c34;
}

代价是:皮肤激活期间,界面的浅色/深色开关不再改变外观。这是刻意的取舍——换来的是”任何配色在任何模式下都不会糊成一团”。

2. 语法高亮同源

代码块是配色最容易露馅的地方。官方把 Shiki 的颜色放在 --shiki-token-* 变量里并按模式切换,本项目一并接管,用 base16 的经典映射:

Shiki tokenbase16Shiki tokenbase16
constantbase09functionbase0D
stringbase0Bstring-expressionbase0C
commentbase03linkbase0C
keywordbase0Epunctuationbase04
parameterbase08foregroundbase05

于是代码块和界面是同一套配色,而不是”界面换了皮、代码块还是原来那个”。

3. 用皮肤中心自己的校验器做离线校验

verify.mjs 不自己写校验逻辑,而是直接 import 皮肤中心安装好的实现:

1
const { validateSkinManifestV2, transformSkinCss, loadSkinCatalog } = lib

逐个皮肤跑三件事:

  1. validateSkinManifestV2 —— 清单是否符合 v2 契约(且 skin.json 的 id 必须等于目录名)
  2. transformSkinCss —— 皮肤中心自己的 CSS 安全管线能不能吃下去、有没有 warning
  3. loadSkinCatalog —— 目录册能否真的收录这 20 个用户来源皮肤

这样校验标准和运行时是同一个,不会出现”我的校验过了但皮肤中心不认”。

4. 对抗浏览器强制深色

Chrome / Edge 的「网站深色主题 / 强制深色」(Auto Dark Theme)会把浅色页面整体反转,看起来就像浅色皮肤失效了——面板发灰、背景不变亮。这跟皮肤本身无关,任何浅色网页都会中招。

皮肤按 MDN 记载的官方豁免写法声明”只支持单一配色”:

1
2
color-scheme: light;        /* 旧版浏览器回退 */
color-scheme: only light; /* 禁止 UA 覆盖:关闭 Chrome Auto Dark Theme */

深色皮肤对应 only dark。

皮肤清单

暗色(恒深)亮色(恒浅)
One Dark · Gruvbox Dark · Nord · Tokyo NightOne Light · Gruvbox Light · Ayu Light · Tokyo Night Light
Catppuccin Mocha · Dracula · Solarized DarkCatppuccin Latte · Solarized Light · Rosé Pine Dawn
Rosé Pine · Everforest Dark · GitHub DarkEverforest Light · GitHub Light · Flexoki Light

共 20 款,暗色 10 + 亮色 10。极性不是手写的,而是读上游调色板的 variant 字段自动判定。

预览图也不是截图,而是 generate.mjs 逐像素画出来的 UI 示意(640×400,sharp 编码成 JPEG)——不依赖系统字体,所以在任何机器上生成的结果都一致。

安装与使用

前提:先把插件装好(@linxin666/dsh-web-all,皮肤中心随它一起进来),见上文「前置依赖」。

1
2
3
4
5
6
git clone https://github.com/clevebitr/dsh-base16-skins.git
cd dsh-base16-skins

node generate.mjs # palettes/ + schemes.json → skins/(含预览图)
node verify.mjs # 用皮肤中心自己的校验器 / CSS 管线 / 目录册校验
bash install.sh # 同步到 $DSH_HOME/skins

装完后打开 设置 → 皮肤中心,刷新页面即可看到新增的 20 款,先「试穿」再「应用」。安装新皮肤不需要重启,皮肤中心重新扫一遍 $DSH_HOME/skins 就行(刷新页面或重开卡片)。

install.sh 支持 --prune,用来清掉皮肤目录里已经不在本仓库的 base16-* 皮肤:

1
bash install.sh --prune

加一款新配色

上游还有 300+ 方案没搬(gruvbox-material-*、kanagawa、horizon-*、selenized-*、zenburn、danqing …):

1
2
3
4
curl -fsSL -o palettes/<slug>.yaml \
https://raw.githubusercontent.com/tinted-theming/schemes/spec-0.11/base16/<slug>.yaml
# 在 schemes.json 里加一条 { palette, id, name, tags, order }
node generate.mjs && node verify.mjs && bash install.sh

只要 YAML 里的 16 个槽位齐全,映射、极性判定、预览图、语法高亮就都是自动的。

已知限制

  • 必须先装 dsh-web 插件(@linxin666/dsh-web-all)。dsh 核心没有皮肤系统,换肤完全是这个插件的能力;本仓库只是往皮肤中心里补资产,卸载插件这些皮肤就一起失效。

  • 皮肤激活期间,界面自带的浅色/深色开关不再改变外观。这是”恒深/恒浅”设计的必然代价,原因见上文。

  • 如果你的浏览器版本仍会强制反转浅色页面,color-scheme: only light 也不生效,请在浏览器设置里关掉:

    • Edge for Android:设置 → 外观 → 深色模式 → 关闭(或「将深色模式应用于网站」设为从不)
    • Chrome for Android:设置 → 主题 → 关闭「网站深色主题」

    自查方法:切到「官方默认」皮肤并把界面切到浅色模式——若背景仍是深色,就说明是浏览器强制深色,而不是皮肤问题。

来源与许可

palettes/*.yaml 是 tinted-theming/schemes(MIT,分支 spec-0.11)的原样拷贝;每款方案的作者与出处都写在各皮肤的 skin.json(author / sourceUrl / attribution / license)。本仓库只做「base16 → dsh token」的映射与打包,脚本与文档同为 MIT。

  • 标题: 给 DSH Web GUI 换上 20 款 base16 配色
  • 作者: Clevebitr
  • 创建于 : 2026-09-14 11:20:00
  • 更新于 : 2026-09-14 11:25:50
  • 链接: https://blog.clevebitr.dpdns.org/2026/09/14/给DSH-Web-GUI换上20款base16配色/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。