dsh-greeting
给 DSH 的主页 / 新会话界面加一行按时段变化的问候语(像 ZCode 那句「夜深啦,别忘了照顾好自己哦」), 位置在 hero 区「探索未至之境」标题正下方、工作区选择器上方。
- 纯插件实现:不碰 DSH 本体、不改 React 节点、不动任何官方 CSS 类名之外的样式。
- 台词按本地时间分 7 个时段随机抽一句,跨时段自动换(每 30s 检查一次,无需刷新)。
- 只在 hero(空白新会话)出现;一旦进入真正的会话就消失。
- 停用插件 / 删掉目录即彻底消失,没有任何残留。
- 桌面版窗口只认
dsh.client客户端模块(tapIndex注入进不了 asar 里的静态首页), 详见下面「两条下发通道」。
安装(推荐)
插件市场:在 DSH 的插件市场里搜索 dsh-greeting,一键安装。
手动按 tarball 安装:
dsh plugin --profile desktop add "https://github.com/wangtheme-coder/dsh-greeting/releases/download/v0.2.0/dsh-greeting-0.2.0.tgz"
(把 desktop 换成你在用的 profile 名,web 版用 web。)
装好后重启一次 DSH(宿主进程启动时才会扫描 dsh.client 声明并写进模块图),
问候语就会出现在主页;之后改配置、改台词都不用再重启。
本地开发安装
源码就在 $DSH_HOME/plugins/dsh-greeting,profile 侧只需三样东西:
profiles/<profile>/package.json→dependencies里加"dsh-greeting": "link:C:/Users/ASUS/.dsh/plugins/dsh-greeting";- 同一个
package.json→dsh.profile.bundles里加"dsh-greeting"; profiles/<profile>/node_modules/dsh-greeting建 junction 指向源码目录 (New-Item -ItemType Junction -Path <link> -Target <src>)。
装好之后要重启一次(只有一次):宿主进程启动时才扫描 dsh.client 声明并把它写进
模块图,而模块图(pkgMeta 判定 + index.js 的 ESM 缓存)在进程内不会重扫 —— 热挂载
只会把宿主侧半边挂上去,客户端半边必须等宿主换进程。重启后 GET /dsh-greeting/config.json
返回 200、模块图里出现 dsh-greeting 行,问候语就出来了。
此后改代码不用重启:assets/greeting.js、client.js 都是每次请求现读文件;
client.js 还挂在 dsh-client-hmr 的 500ms 轮询上,改完自动热替换正在开着的页面。
改 greeting.json 更是立刻生效。
两条下发通道(重要)
| 通道 | 到哪儿 | 桌面版窗口 | 浏览器打开 127.0.0.1:19387 |
|---|---|---|---|
dsh.client 模块(client.js,走 /plugins 组合路由 + 模块图) |
所有 web 前端 | ✅ 唯一可达 | ✅ |
webServer.tapIndex 注入 + /dsh-greeting/greeting.js |
只注入宿主 HTTP 首页 | ❌ 到不了 | ✅ |
原因:桌面版窗口的文档不是宿主首页,而是 Electron 私有协议 dsh-app://app/ 直接从
app.asar 里读的静态 dist/index.html(lib/main.js 的 protocol.handle:/、
/index.html、/assets/*、favicon 走 serveWebDocument(<asar>/.../dsh-web-frontend/dist)),
所以 tapIndex 注入的 <script> 永远进不了用户真正看的那个窗口。两条通道共用同一套
DOM 标记(data-dsh-greeting)、同一个 data-slot 记账和同一份配置,谁先到谁写,
另一份看到「当前文本仍在本期台词池里」就自动让位,不会抢也不会闪。
配置
配置文件:$DSH_HOME/greeting.json(首次运行自动生成,改完刷新页面即可生效,不用重启)。
{
"enabled": true,
"fontSize": 14,
"reloadShortcut": true,
"phrases": {
"lateNight": ["这句会替换掉内置的凌晨时段台词"],
"evening": ["晚上好。今天也算辛苦了…只有一点点。", "第二句也可以"]
}
}
| 字段 | 说明 |
|---|---|
enabled |
false = 前端不显示问候语(插件本身仍在,可随时改回来) |
fontSize |
字号(px),默认 14 |
reloadShortcut |
默认 true:把 Ctrl+R / F5 接回页面刷新(见下节);设 false 关掉 |
phrases |
按时段覆盖台词数组;没写的时段用内置默认 |
关于「刷新页面」:这个应用本来刷不了
DSH 桌面版(Windows 打包版)的应用菜单只挂了两个 visible:false 的 toggleDevTools
(其中一条带 accelerator: "F12"),生产环境没有 role: "reload" 菜单项,
lib/main.js 里连 webContents.reload 都没有 —— 所以 Ctrl+R、
F5 全都没人处理,页面刷新不了。
- 第一次让脚本生效(或客户端还没起来的时候):
F12 打开开发者工具 → 在 Console 里敲
location.reload()回车 (DevTools 面板是焦点时 Ctrl+R 也能重载页面);或者退出应用重开。 - 装好本插件之后:页面里的 keydown 监听会把 Ctrl+R /
F5 接回来调
location.reload(),从此肌肉记忆可用(reloadShortcut:false可关)。 它不干扰输入框;Ctrl+Shift+R 也一并接成普通刷新 (页面全no-store,强刷本来就没区别)。实测(隔离宿主 + 无头 Chrome,把greeting.js兜底脚本整个掐掉、只用client.js):Ctrl+R / F5 都真的换了文档(哨兵变量随之消失), 刷新后问候语自己回来;reloadShortcut:false时按键原样放行。 注意 Ctrl+R 只重载页面,宿主侧代码(新路由、新dsh.client声明)仍然要重启应用才生效。
时段划分(本地时间,改边界要同时改 client.js 与 assets/greeting.js 的 SLOTS)
| key | 时间 | 内置台词示例 |
|---|---|---|
lateNight |
00:00–04:59 | 夜深啦,别忘了照顾好自己哦——才、才不是学别人说话。 |
dawn |
05:00–06:59 | 天刚亮就来了?…我才没有在等你。 |
morning |
07:00–11:59 | 上午好。今天的活,我勉强帮你分担一点。 |
noon |
12:00–13:59 | 中午了,去吃饭。饿着的脑袋我可懒得替你转。 |
afternoon |
14:00–17:59 | 下午了,别发呆。…要发呆也得先把手上的事做完。 |
evening |
18:00–22:59 | 天黑啦。今天也算辛苦了…只有一点点。 |
night |
23:00–23:59 | 快十二点了,收个尾就去睡。…我只是嫌你明天没精神。 |
关掉 / 卸载
# profiles/<profile>/cordis.patch.yml —— 临时关掉(可逆,需重启)
- id: dsh-greeting
disabled: true
彻底卸载:从 profile 的 package.json 移除依赖与 dsh.profile.bundles 里的 dsh-greeting,
再删掉 node_modules/dsh-greeting 链接与 $DSH_HOME/plugins/dsh-greeting 目录。
实现要点(给未来的自己)
- 客户端半边
client.js(主通道):package.json里dsh.client = {platform:"web"}+exports["./client"],宿主@deepseek-ai/dsh-client-modules扫描后把它作为模块图里的一行 (plugins/??dsh-greeting/client.js&rev=…),前端window.__ModuleLoader__.load({id, factory})注册;id必须等于 loader entry 名(dsh-greeting),否则报 loaded without registering。 声明与文件必须同时存在:声明了dsh.client却没有exports["./client"]会在组合期 直接抛ClientPackageCompositionError(fiber FAILED),所以两个改动永远一起做。 - 宿主半边
index.js:注册GET /dsh-greeting/config.json(JSON,no-store,每次请求现读greeting.json)与GET /dsh-greeting/greeting.js(prelude 拼window.__DSH_GREETING__+ 兜底脚本), 再用webServer.tapIndex往宿主首页注入<script defer>。台词默认值(PHRASES)只存在于这里。 - 页面侧锚点:
[data-conversation-region="chat"][data-content-phase="hero"]里的[class*="_headline"](CSS Module 只换前缀哈希,_headline后缀跨构建稳定)。找不到锚点就安静地 不显示,绝不报错、绝不猜别的元素。 - 只往 headline 的父容器里
insertBefore一个自己的 div(data-dsh-greeting), 用 MutationObserver + 30s 定时器兜住 React 的重建;hero 消失时自行移除。 - 颜色用主题变量
--dsw-alias-label-secondary,深浅色主题都自动适配。 - 两个坑(都已在隔离宿主里复现并修掉):①
picked用const声明又整体赋值 → 静默抛错、 一句都不显示;②data-slot相同就早退 → 配置后到时新台词顶不上来,只剩内置兜底句。 判定改成「当前文本仍在本期台词池里才让位」。CSS 也不要写animation-fill-mode: both: 动画被节流时元素会永远停在opacity: 0。
No comments yet. Be the first to write one.