dsh-helloai-works
DeepSeek Harness 卡片资料库:标签分类的图文卡片,可在对话里用 @ 引用单张卡片或整个标签分组
- Stars
- 0
- Language
- TypeScript
- Created
- Oct 7, 2026
- Updated
- Oct 7, 2026
Introduction
HelloAI Works · DeepSeek Harness 卡片资料库
以标签分类,用卡片记录文字与图片,在对话里 @ 就能把资料喂给模型。
面板在左侧栏「插件」的正下方:左列是标签,中间是卡片列表,点任意卡片在右侧栏打开编辑——和 DSH 自带的文件预览、终端同一个位置,可拖动、可分栏、可并存。

左列标签,中间卡片列表,点任意一张卡片就在右侧栏打开编辑——和 DSH 自带的文件预览、终端同一个位置,可拖动、可分栏、可并存。
点击看面板单独的样子(深色 / 浅色)

它能做什么
- 标签分类:新建标签、重命名、删除;重命名成已存在的名字会自动合并,这也是把打错的标签并回正轨的唯一方式。删标签只删标签本身,卡片保留、变成未分类。
- 文字 + 图片卡片:正文按块排列(文字块 / 图片块),支持搜索(标题 / 正文 / 图片说明 / 来源 / 标签名)、最新或最早排序。
- 对话里
@引用:输入框打@,菜单里多出「卡片资料」一栏。草稿里只显示卡片标题,发送时才展开成卡片全文——写小说时@人物设定当上下文,正是这个用法。 - 还能整组调用:
@菜单里除单张卡片外还有标签分组,选中一个标签 = 把这个标签下的卡片整组带进上下文。 - 图片按内容寻址:图片以 SHA-1 存成
assets/<sha1>.<ext>,同一张图插多少次都只占一份;宽度 > 1500 px 才等比缩小,缩小时 PNG 仍是 PNG(透明通道保留),GIF / SVG 永不重新编码。 - 一个文件夹就是一个资料库:
<DSH_HOME>/helloai-works/,不用本插件也能读、能拷、能用网盘同步、能丢进 Git。卸载 DSH 后数据照样在。 - 不丢东西:编辑防抖 800 ms 自动保存;保存带乐观锁版本号,冲突返回 409 并原样保留你的改动,覆盖还是放弃由你选;每 10 分钟一份文档级快照(留最近 30 份),随时可导出图文全在一个文件里的完整备份。
安装
dsh plugin --profile desktop add github:hello-heyongping/dsh-helloai-works
或者 clone 到本地再装(想改代码就用这个):
git clone https://github.com/hello-heyongping/dsh-helloai-works.git
cd dsh-helloai-works
dsh plugin --profile desktop add .
把
desktop换成你自己的 profile 名。仓库里已经带上构建好的lib/,不需要先npm install。
装完刷新页面,左侧栏「插件」正下方就是「卡片资料」。面板头部会显示版本角标(例如 V0.2.6)——刷新后看角标就知道页面是不是最新构建。
快速上手
- 点左侧栏的「卡片资料」打开面板;
- 底部输入框新建一个标签(比如
人物),回车; - 点右上角 新建 建一张卡片,标题 + 正文块 / 图片块随你加;
- 想引用时,在对话框输入框里打
@→ 选「卡片资料」或「标签分组」; - 数据都在
<DSH_HOME>/helloai-works/下,直接拷走就是备份。
粘贴 / 拖入 / 选择图片都可以;单张上限 32 MB(base64 后)。
常见问题
卸载 DeepSeek Harness 后我的资料还在吗?
在。数据在 <DSH_HOME>/helloai-works/(默认 C:\Users\<用户名>\.dsh\helloai-works),属于用户目录,卸载只删程序本身。
怎么恢复?
三种都行:① 重装 DSH 和插件,同一个 DSH_HOME,打开就是原样;② 用之前下载的 works-full-*.json 在备份抽屉里「导入备份」;③ 只要 helloai-works 文件夹还在,直接拷回 DSH_HOME 即可,插件不需要任何登记。
@ 菜单里没有「卡片资料」?
那说明这个 DSH 版本不带触发器服务,面板头部会写明「未接入」。此时只有 @ 引用这一项不生效,插件其余部分照常。
「清理未引用图片」能当日常操作吗? 不建议。它会让旧快照缺图,清理前先做一次完整备份。
改了代码要重启 DSH 吗?
客户端改动 npm run build 后刷新页面即可。Host 改动需要完整重启——webServer.register({kind:"prefix"}) 注册的前缀路由在插件 dispose 时不会释放,运行中禁用再启用会报 duplicate prefix route,改动不生效但也不会把插件弄坏。
下面是完整的实现说明、性能实测与更新记录,写给想改代码、或想搞清"为什么这么设计"的人。
DeepSeek Harness 的卡片资料库:以标签分类,用卡片记录文字与图片,卡片内容在右侧栏里查看和编辑。
- 入口在左侧栏「插件」的正下方(面板
order排在插件0与日程10之间)。 - 左侧面板 = 标签栏 + 卡片列表(标题 / 正文摘要 / 收集时间 / 图片数)。
- 点任意卡片 → 在右侧栏打开一个「卡片」标签页,与 DSH 自带的文件预览、终端同一个位置,可拖动、可分栏、可并存。
- 没有会话(右侧栏不可用)时,同一条卡片界面会退回面板内显示,功能不丢。
- 全部配色走
--dsw-*主题变量,换主题即换风格;图片统一 14px 圆角,图 + 文按块排列。 - 头部沿用 HelloAI 家族的样式,固定两行,靠字号与深浅区分层级:第一行
HELLOAI | CARD RECORD品牌字(14px、主文本色)+V0.2.6版本角标;第二行是一句淡色副标题以标签分类,卡片记录图文,对话可@卡片和标签分组(13px、三级文本色--dsw-alias-label-tertiary,不再用分隔符拼两段)。与「技能管理」「备份与恢复」两个插件同一套处理(各自用独立类名前缀,避免全局样式互相踩)。版本角标在构建时取自package.json,所以升级只改那一个数字——刷新后看角标就知道页面是不是最新构建。第二行里的@也是本会话的实况:接入时这句本身就是提示,未接入时才在句尾追加一行主题警告色的说明(@ 引用未接入(触发器服务不可用)),平时不占位置。 - 头部右上角是
新建 / 备份 / 保存三个按钮:图标是内联 SVG(带加号的窗口 / 层叠三片 / 软盘),不是+ ⛁ 💾这类文本字符,所以字形不随系统的 emoji 字体变化;三条路径一律fill="currentColor",跟着按钮的配色走。三个按钮样式统一——只有边框、没有填充,谁也不是"主按钮",鼠标悬停时才亮起底色与主色描边。 - 支持搜索(标题 / 正文 / 图片说明 / 来源 / 标签名)、最新或最早排序、标签增删改。
- 卡片页正文只留标签 + 内容块:
来源是可选字段,收成「收集于」日期旁的链接图标,点一下才展开输入框,平时不占位置。 - 备份抽屉里带一份**「数据说明」**(默认展开,中英双语):数据存在哪、图片怎么存怎么压、完整备份和快照各含什么、卸载 DSH 后数据还在不在、怎么恢复,以及保护数据的五条习惯。
- 在对话里
@引用卡片:输入框打@,菜单里多出「卡片资料」一栏,选中即插入一个引用标签;草稿里只显示卡片标题,发送时自动展开成卡片全文——写小说时把「人物设定」@ 进来当上下文,正是这个用法。
标签与卡片的增删改
标签(左侧栏)
| 操作 | 怎么用 |
|---|---|
| 新建 | 底部输入框回车;名字已存在时直接复用,不会建重复的 |
| 重命名 | 悬停标签行点 ✎,或双击标签名 |
| 删除 | 悬停点 ✕。只删标签本身,卡片保留、变成未分类 |
| 合并 | 重命名成已存在的名字即自动合并:卡片转到那个标签,旧标签消失。这也是把打错的标签并回正轨的唯一方式 |
| 空名字 | 拒绝并提示,标签保持原样 |
卡片(中间列表)
| 操作 | 怎么用 |
|---|---|
| 打开 / 编辑 | 点整行 → 在右侧栏打开 |
| 复制 | 悬停点 ⧉:副本插在原卡片后面,标题加「(副本)」,块 ID 全部重新生成(两张卡不会共用同一段) |
| 删除 | 悬停点 🗑,需确认 |
| 改标题 / 标签 / 文字块 / 图片块 | 右侧栏卡片页 |
| 改收集时间 | 卡片页顶部「收集于」是日期选择器,只改日期、保留原来的时分秒——这是卡片上唯一一个别处改不了的字段 |
| 改来源 | 就在「收集于」日期旁边的链接图标:来源是可选项,平时只占一个图标(有来源时图标变主色,悬停显示内容),点一下才在标题下展开输入框,回车 / 失焦收起 |
标签行和卡片行的操作按钮平时是半可见的(opacity: .4),悬停或键盘聚焦时变实。纯 hover 才出现的控件用起来就像功能没做——这一版就是为此改的。
磁盘结构
整个资料库就是 DSH_HOME 下的一个文件夹,不用本插件也能读、能拷、能同步:
<DSH_HOME>/helloai-works/
works.json 文档:标签 + 卡片(不含图片字节)
assets/<sha1>.<ext> 收集到的图片,按内容寻址,同一张图只存一份
backups/ 轮转快照(只含文档)+ 自包含完整导出(图文全在)
图片不进文档:文档始终只有几 KB 到几百 KB,浏览器按普通图片缓存字节,加一张图不需要重写整个库。
同一个文件夹可以直接用网盘同步、整体拷贝、丢进 Git;里面的图片就是普通图片文件,不装本插件也能打开看。
图片:存放与压缩
粘贴 / 拖入 / 选择进来的图片,先在浏览器里按下面的规则处理,再以 base64 上传,Host 端按 SHA-1 存成 assets/<sha1>.<ext>:
| 情况 | 处理 |
|---|---|
| 宽度 > 1500 px | 等比缩小到宽 1500 px(高度按同一比例),再编码 |
| 宽度 ≤ 1500 px | 一个字节都不动:不重新编码、不掉画质 |
| 需要缩小时的格式 | PNG 仍是 PNG(透明通道保留),WebP 仍是 WebP;JPEG / BMP / AVIF → JPEG(质量 0.88) |
| GIF / SVG | 原样保留,永不重新编码(动图与矢量过不了 canvas) |
| 单张上限 | 32 MB(base64 后),Host 端兜底;超出会明确报错 |
内容寻址带来两个直接好处:同一张图插入多少次都只占一份;图片文件与卡片是两份独立拷贝——删掉电脑上的原图不影响卡片,反过来在插件里删卡片或删图片块也不会立刻删文件,只有点「清理未引用图片」才会删掉没有任何卡片引用的图片。
数据保护:怎么不丢东西
数据在哪:DSH_HOME(默认 C:\Users\<用户名>\.dsh)下的 helloai-works 文件夹。它属于用户目录,与 DeepSeek Harness 的程序安装目录无关。
卸载 DeepSeek Harness 后数据还在吗:在。卸载只删程序本身,helloai-works 文件夹原封不动——除非你手动删除,或用清理工具删掉了整个 .dsh。
怎么恢复:
- 重装 DSH 和本插件 → 同一个
DSH_HOME,文件夹还在,打开就是原样; - 换了电脑或数据被删 → 用之前下载出去的
works-full-*.json,在备份抽屉里「导入备份」(导入前会自动先存一份快照); - 只要
helloai-works文件夹还在(哪怕一份备份文件都没有)→ 直接拷回DSH_HOME即可,插件不需要任何登记。
五条习惯:
- 定期「生成完整备份」再点「下载」,把文件存到另一个盘 / U 盘 / 网盘——只留在
backups/里,磁盘坏了会一起没; - 整个
helloai-works文件夹用网盘同步,等于随身带一份副本; - 别把「清理未引用图片」当日常操作:它会让旧快照缺图,清理前先做一次完整备份;
- 自动快照只保留最近 30 份、且只含文字,重要的时间点自己导一份完整备份;
- 卸载、重装或换电脑之前,先导一次完整备份。
以上内容也原样写在插件的备份抽屉里(「数据说明」,默认展开),中英文各一份。
在对话里 @ 引用卡片
写小说要一直带着人物设定、世界观、章节大纲——把它们做成卡片,然后在输入框里 @ 进来:
- 打
@,菜单里和自带的「文件 / 会话」并列多出一栏 卡片资料; - 继续打字就是搜卡片(标题、来源、正文、图注、标签名都能搜,用的是和左侧面板同一个检索索引);
- 选中后插入的是一个引用标签(草稿里显示卡片标题,不会把正文糊你一屏);
- 点标签可以在右侧栏打开这张卡;发送时,标签才会展开成卡片全文交给模型。
那栏里除了单张卡片,还有标签分组:选中一个标签 = 把这个标签下的卡片整组带进上下文。写小说时 @人物 一次带齐所有人物设定,比一张张点省事——这就是"用 @ 调用资料库数据"的完整形态:单片调用(一张卡)+ 成组调用(一个标签)。
模型实际收到的是这段文本(截取上限 8000 字,超出会注明被截断):
【卡片资料:人物设定】
标签:设定、主角
来源:https://example.com/notes/12
收集于:2026-10-03
林见微,28 岁,旧城修复师。左手腕有一道旧伤……
[图片:人物立绘 v2]
几个实现上的取舍,写在这里免得以后忘了为什么:
- 走的是 DSH 官方的触发器管线
ctx.inputTriggers.registerSource()(@是共享触发器,一个字符一个菜单、一个来源一栏),没有替换任何自带 UI,也没有改动 DSH 自身文件——本插件只往这个公开服务里注册一个来源。菜单按来源的order排序,本插件用-1排到自带来源前面。 - 拿到这个服务有三条路(服务已在 / cordis 注入 / 服务注册事件),哪条先到用哪条;都没有(某个 DSH 版本不带触发器服务)时只有这一项功能不生效,面板头部会写明「未接入」。
- 草稿里是标签、发给模型是全文,靠的是这条管线里来源自己的
codec.serialize()——提交时由它把引用序列化成提示词;序列化失败会让整次发送卡住,所以卡片中途被删时这里返回的是一句说明而不是抛错。 - 只引用你选中的卡片(或标签):没点的卡片不进上下文,模型也不知道你的库里还有什么。
- 图片只带图注、不带字节(模型看不到
assets/里的文件);卡片太长会被截断(单张 8000 字),这是刻意的——引用会在每次提交时展开,不限长的卡片是给自己挖坑。 - 整组引用另外设了总预算(一个标签最多 20000 字),装不下的卡片会在文本末尾注明"还有 N 张未展开",而不是悄悄少给——缺了却没说的上下文,比说明白的更糟。
- 提示词里的每一句(卡片标题 / 标签 / 来源 / 日期 / 图注 / 截断说明 / 已删除的降级话术)都走 i18n,中英各一套,跟着界面语言走。
- 服务由兄弟客户端包提供,本插件用
ctx.get("inputTriggers")可选获取:万一某个 DSH 版本没有这个服务,只有「@ 引用卡片」这一项不生效,插件其余部分照常。
规模与性能(实测)
先分清两件事:备份文件和日常数据。
backups/works-full-*.json只在「生成完整备份 / 下载 / 导入」时被读写,查看和搜索根本不碰它——所以备份用 JSON 不会拖慢日常使用,它只占磁盘、并且生成那一下比较重(见下)。- 日常速度只取决于
works.json:整个文档在页面加载时读进内存,搜索、筛选、排序全在内存里做,保存时整份写回。
npm run bench 会现场量一遍(每张卡片按 2 段文字约 300 字 + 1 张图片引用计;图片字节不在 works.json 里,不计入):
| 卡片数 | works.json | 首次加载解析 | 每次保存序列化 | 建索引 | 切换标签/排序 | 每次按键搜索 | 旧实现 |
|---|---|---|---|---|---|---|---|
| 500 | 0.41 MB | 1.4 ms | 1.5 ms | 1.0 ms | 0.1 ms | 0.1 ms | 0.6 ms |
| 2,000 | 1.65 MB | 5.4 ms | 6.2 ms | 3.7 ms | 0.4 ms | 0.3 ms | 2.5 ms |
| 5,000 | 4.13 MB | 14 ms | 16 ms | 8.9 ms | 1.0 ms | 1.1 ms | 6.3 ms |
| 20,000 | 16.62 MB | 58 ms | 61 ms | 37 ms | 4.6 ms | 4.9 ms | 23 ms |
Windows 11 / Node 24 本机实测,取中位数;搜索一列用「哪张卡片都不含的词」这一最坏情况(不给任何短路机会)。
据此:
- 搜索不会因为备份而变慢:一次按键就是一次子串扫描,5,000 张约 1 ms。
- 小写化只做一次:
buildSearchIndex()在文档变化时给每张卡片建一条全小写的检索串(标题 + 来源 + 每个块的文字/图注 + 标签名),按键时只做子串比较,比「每次按键重新小写整张卡片」快 5–6 倍。 - 排序只在切标签 / 切排序时发生:输入关键词不会重排列表。
- 真正的天花板是列表 DOM:面板目前把可见卡片全部渲染成行;这一项没在 Node 里量过,按经验 1–2 千行仍然流畅,再多就该上窗口化(见路线图)。
- 完整备份会很大:图片按 base64 内联(体积约 +37%),按压缩后每张 300 KB 估算,500 张图 ≈ 200 MB、2,000 张 ≈ 800 MB。它只在生成/下载/导入那一刻出现,但生成时 Host 要在内存里拼出整个文件——图片特别多时,更稳的做法是直接备份
assets/文件夹本身(见上节)。
保存模型(当前实现)
| 环节 | 做法 |
|---|---|
| 触发 | 编辑后防抖 800 ms 自动保存;保存 按钮、页面隐藏时也会立刻提交 |
| 请求 | POST /save { doc, baseRevision } —— 提交整份文档 JSON,并带上乐观锁版本号 |
| 冲突 | baseRevision 与磁盘不一致 → 409 + 当前文档;本地改动原样保留,弹出两个选择:用我的改动覆盖 / 放弃我的改动 |
| 空写 | 内容与磁盘一致时不写盘、不加版本号(只有时间戳漂移的重复保存会被吃掉) |
| 落盘 | 写临时文件 → fsync → 原子 rename 覆盖 works.json |
| 快照 | 每 10 分钟最多一次,文档级副本进 backups/(只含卡片文字,不含图片字节),保留最近 30 份 |
| 完整备份 | 生成完整备份 把被引用的图片内联成一个自包含 JSON(图文都在同一个文件里),写进 backups/ 并可下载 |
| 导入 | 用备份文件整体替换(替换前自动存一份快照) |
一句话:整库一个文件,整份原子覆盖,带乐观锁。优点是极简、可读、可拷贝、导出即备份;代价见下节。
冲突期间自动保存会停在原地,不反复重试、更不会覆盖对方。未决冲突的提示条没有关闭按钮,避免把未保存的改动丢掉;这条提示也会跟到右侧栏的卡片页里,因为冲突是一个必须做的决定,不是一条过眼消息。
已知取舍
- 写放大(唯一剩下的结构性问题):真正的编辑仍会整份重写文档。实测 5,000 张约 4.1 MB / 16 ms,20,000 张约 16.6 MB / 61 ms(
npm run bench)。内容一致时已经跳过写入。 - UI 线程序列化:保存前
JSON.stringify整份文档,20,000 张约有 60 ms 的卡顿。 - 没有单卡历史:快照是整库级的,撤销不了「某张卡片的上一次编辑」。
- 冲突靠用户裁决:不做字段级自动合并,覆盖或放弃是二选一。对个人资料库这是刻意的——猜错的自动合并比让用户选一次更糟。
HTTP 接口
前缀 /api/dsh-helloai-works,只接受本地或受信来源。
| 方法 | 路径 | 作用 |
|---|---|---|
| GET | /state | 文档 + 已有图片 id + 存储路径 |
| POST | /save | 覆盖保存整份文档;带 baseRevision 时做乐观锁,版本不符返回 409 + 当前文档 |
| POST | /asset | 上传 base64 图片,按 sha1 去重,返回 id |
| GET | /asset/<id> | 图片字节,immutable 长缓存 |
| GET | /backups | 备份文件列表 |
| POST | /backups/restore | 用某份备份覆盖当前资料 |
| POST | /backups/delete | 删除某份备份 |
| POST | /export | 生成自包含完整备份文件 |
| GET | /download?name=&offset=&length= | 分块下载备份(桌面端自定义协议扛不住大响应体) |
| POST | /import | 用备份内容整体替换 |
| POST | /collect | 清理没有被任何卡片引用的图片文件 |
开发
npm install
npm run typecheck # tsc --noEmit,strict
npm run build # esbuild:Host → lib/index.js,客户端 → lib/client.js
node scripts/selftest.mjs # 195 项检查
npm run trigger:check # 单独复现/验证 `@` 来源的 Promise 契约(见 v0.2.5)
npm run bench # 规模实测:文档体积 / 解析 / 保存 / 搜索耗时
scripts/selftest.mjs 用临时 DSH_HOME 起一个真 socket,分四段跑,最后再补一项生命周期检查:
- Host —— 保存 / 图片往返 / 乐观锁 / 导出 / 导入 / 恢复 / 清理 / 路径穿越拒绝;
- 客户端 —— 把构建产物塞进一个假的
window.__ModuleLoader__,用react-dom/server渲染面板、卡片页、chip 和备份抽屉,检查「数据说明」七问七答都在且中英双语都写全了、插图策略(>1500 px 等比缩小、PNG 缩完仍是 PNG、预算内的图不重新编码、GIF/SVG 不动)正确、apply只往触发器管线里注册一个@来源,并检查没有任何颜色属性写死字面量(只能经由 token 兜底); - 冲突 —— 直接驱动客户端 store 打真实接口,验证「另一个窗口先写 → 本窗口保存 → 409 → 保留本地改动 → 覆盖 / 放弃」两条路径;
- 增删改 / 检索 /
@引用 —— 同样走真实接口验证标签与卡片的增删改;然后用同一份文档验证检索(标题 / 来源 / 正文 / 图注 / 标签名各字段都能搜到、大小写无关、跨字段不会误命中、标签筛选与新旧排序),并直接驱动@来源:候选列表、按正文和标签名过滤、选中的落点(引用标签指向哪张卡)、codec.serialize展开出的提示词(含标签 / 来源 / 日期 / 正文)、卡片被删后的降级文本、超长卡片截断,以及点标签开卡。
scripts/bench.mjs 不判定成败,只打印上表那组数字;它是测量而不是测试,卡片数可以自己传(node scripts/bench.mjs 100000)。
想对照 DSH 自带实现的源码
DSH 装完是打包在 resources/app.asar 里的,自带插件(@deepseek-ai/dsh-client-ui-input-trigger、…-ui-reference、…-ui-commands)的源码可以直接从里面读出来——v0.2.5 那个 @ 卡骨架的问题就是这么定位的(自带来源都是 async,本插件当时是同步返回数组):
# 列出包里某个插件的文件
python .cache/tools/asar.py "D:/DeepSeek Harness/resources/app.asar" list ui-input-trigger
# 导出到 .cache/dsh-asar 下细读
python .cache/tools/asar.py "D:/DeepSeek Harness/resources/app.asar" dump .cache/dsh-asar ui-input-trigger
.cache/tools/asar.py 就这两个子命令(list / dump),只读、不改动安装目录。
改 Host 代码要重启 DeepSeek Harness
客户端产物是每次请求现读磁盘的:改完 npm run build,刷新页面就是新版——本插件的功能升级(面板、卡片页、备份抽屉、@ 引用、插图压缩)全在客户端,所以升级插件不需要重启 DeepSeek Harness。@ 引用要用的触发器服务由兄弟客户端包提供,本插件用 cordis 的 ctx.inject(["inputTriggers"], …) 注入,服务无论先注册还是后注册都会接上,不依赖加载顺序。
Host 不是:webServer.register({kind:"prefix"}) 注册的前缀路由在插件 dispose 时不会释放。所以在运行中的进程里禁用再启用本插件,会看到
webserver: duplicate prefix route "/api/dsh-helloai-works"
新实例起不来,旧实例继续服务——即改动不会生效,但也不会把插件弄坏。改 Host 代码后请完整重启一次 DeepSeek Harness。排查时若 POST /save 传一个过期的 baseRevision 仍返回 200,就说明跑的还是旧构建。
安装
dsh plugin --profile <profile> add "D:/DeepSeek Harness plugin/dsh-helloai-works"
或手工在 profile 的 package.json 里加:
"dsh": { "profile": { "bundles": [ "...", "@hello-heyongping/dsh-helloai-works" ] } },
"dependencies": { "@hello-heyongping/dsh-helloai-works": "link:D:/DeepSeek Harness plugin/dsh-helloai-works" }
更新记录
v0.2.6 · 修掉「点『加入到卡片资料』后界面错乱」
- 症状:在会话里点助手消息那一行的 ⊕(加入到卡片资料),弹出来的不是一个居中的对话框,而是一堆浏览器原生控件堆在窗口左上角——标题/内容/标签/取消/保存全都裸着,盖住了侧栏、标题栏和 logo,看着就是「界面错乱」。同一个按钮平时也显示成一块灰色实心方块,而不是方框加号。
- 原因:这个功能的两处皮肤(消息行按钮 + 草稿对话框)都只靠
CollectLayer内部渲染的一个<style>节点供样式。那个节点在shell.overlay插槽里,一旦它不在文档里(插槽占用者被换掉、模块被 HMR 重载、该节点被别的东西清掉),按钮就退回成原生<button>(深色主题下就是那块灰方块),对话框也退回成无样式的块级元素——position:fixed的遮罩和居中都没了,于是整坨东西落在文档左上角。换句话说:样式表的送达方式太脆,而不是对话框写错了。 - 修法(
src/collect.tsx):- 样式表改成注入
document.head(id="hxw-collect-style",幂等、不重复):模块求值时注入一次,CollectAction、CollectLayer每次挂载/开合再断言一次,HMR 重载后自动刷新同一个节点。文档头是「不管哪个 React 树挂着都还在」的地方。 - 关键几何改成内联样式兜底:按钮尺寸/无边框、遮罩
position:fixed;inset:0;display:grid;place-items:center、对话框宽度/最大高度/圆角/底色,以及标题、内容、标签输入框和底部按钮的外观,都与样式表里的值一致(内联优先,所以有样式表时观感一模一样)。万一样式表真的丢了,对话框仍然是一个居中、可读、能用的模态框,不再堆到左上角。 - 图标补上
fill-rule="evenodd":方框和加号是同一路径里的三个子路径,靠缠绕方向挖洞不够稳,显式声明填充规则后「方框 + 加号」是确定的。
- 样式表改成注入
- 自检:
npm run typecheck && npm run build && node scripts/selftest.mjs && node scripts/check-trigger-contract.mjs。 - 顺手把头部第二行收敛成一句淡色文案:
以标签分类,卡片记录图文,对话可@卡片和标签分组(13px、三级文本色,去掉分隔符)。@接入时这句话本身就是提示,只有未接入时才在句尾追加主题警告色的一行,平时不占位置。版本角标仍是V0.2.6。 - 升级后请看版本角标:面板头部应显示
V0.2.6。若还是旧角标,说明这个页面仍在用浏览器缓存的旧 bundle——刷新(或在应用里重开一次窗口)即可;旧 bundle 正是这个错乱的来源。
v0.2.5 · 修掉「@ 卡在骨架态」
- 症状:输入框打
@,菜单弹出来但一直是两行灰色骨架,一张卡片都不显示(/指令菜单、@文件也一起不出)。 - 原因:DSH 的触发器管线是用 Promise 收尾的——
source.candidates(...).then(...)。本插件的candidates()是同步函数、直接 return 数组,数组没有.then,于是这一句在菜单的取数循环里抛TypeError;循环就此中断,source-settled再也不会派发,排在后面的来源也一并烂在pending,而pending且没有旧行的分组渲染出来就是那两行骨架。自带来源都是async,所以只有装了本插件的环境会看到这个现象。 - 修法:
candidates()改成async(src/mention.ts),这样所有分支(包括提前return [])返回的都是 Promise;同时顺手尊重管线给的中止信号。 - 防回归:
node scripts/check-trigger-contract.mjs用管线那一句的真实写法跑两个来源——返回裸数组的会当场抛错并把后面的来源一起拖成pending(复现原症状),本插件的来源则正常 settled 出 2 行;自检也加了两项在断言这个 Promise 契约(186 → 195 项)。 - 顺手修掉自检本身的一个偶发红灯:客户端这一段让 store 的预加载请求打到了进程自带的
fetch上(相对路径解析不了),失败重试要好几秒,那条「读取失败」的状态更新会落在第 4 段的编辑中间,把刚写的标签/卡片冲掉,于是随机报错。现在这一段有了自己的 documents 服务替身,并断言没有任何请求漏到网络上——连跑 6 次全绿。 - 若哪个 DSH 版本把
candidates当同步函数调用,async的返回值仍是「一个 thenable 的东西」,不影响兼容。
v0.2.2 · @ 排到菜单最前 + 接线状态可见
@菜单里「卡片资料 / 标签分组」排在最上面:DSH 的触发器菜单按来源的order排序(自带的文件 / 会话来源是 0),本插件原来排在它们之后,容易一眼错过;现在order: -1,打开@第一眼就是卡片。- 插件激活时预加载资料:即使这个会话从没打开过左侧面板,第一次
@也已经有数据。 - 面板头部多一行状态:
对话里输入 @ 即可引用卡片和标签分组;万一某个 DSH 版本没有触发器服务,这里会明确写「未接入」,而不是静默失效。 - 接入方式改成三条路取先到者(服务已在 / cordis 注入 / 服务注册事件),任一条成功即注册,且只注册一次。
- 自检 183 → 186 项。
v0.2.1 · @ 也能整组调用
@菜单里除「卡片资料」(单张卡片)外,多出「标签分组」一栏:选中一个标签 = 把这个标签下的卡片整组带进上下文。@人物一次带齐所有人物设定。- 提示词措辞全部走 i18n(标题 / 标签 / 来源 / 日期 / 图注 / 截断 / 已删除的降级话术),中英各一套。
- 整组引用有独立总预算(20000 字),装不下的卡片会注明未展开。
- 自检 176 → 183 项。
v0.2.0 · 插件功能升级
@引用卡片:输入框打@,菜单里和「文件 / 会话」并列多一栏「卡片资料」;草稿里只放一个引用标签,发送时才展开成卡片全文。实现在新增的src/mention.ts。- 插图压缩:宽 > 1500 px 等比缩小;PNG 缩完仍是 PNG(原来一律转 JPEG,透明背景会被压成黑色,这是修掉的一个真问题);宽度合规的图一个字节都不动。
- 备份抽屉里的「数据说明」:数据在哪、图片怎么存怎么压、完整备份和快照各含什么、卸载 DSH 后还在不在、怎么恢复、五条备份习惯——中英双语,默认展开。
- 卡片页的「来源」收成日期旁的链接图标:可选字段不再占正文的位置,正文只留标签和内容块。
- 检索提速:小写检索串只建一次、排序只在切标签/切排序时发生——5,000 张卡片的一次按键搜索从约 6 ms 降到约 1 ms(
npm run bench可复现)。 - 面板名从「资料卡片」改为「卡片资料」;自检从 106 项加到 176 项(v0.2.1 到 183 项,v0.2.2 到 186 项)。
升级方式:npm run build 后刷新页面即可——客户端产物每次请求现读磁盘,插件功能升级不需要重启 DeepSeek Harness。
路线图
v0.2 冲突安全(已完成):保存带上baseRevision,服务端不匹配返回 409 + 当前文档,本地改动原样保留,覆盖或放弃由用户选。- v0.3 索引化:拆成
index.json(标签 + 卡片元信息)+cards/<id>.json(正文块)。改一张卡只写约 2 KB,网盘同步也变成增量,顺手解决写放大。 - v0.4 检索:把全文检索做成可重建的派生索引(SQLite FTS5 或落盘的倒排表),纯文件仍是唯一事实来源,导出/备份的故事不变。(浏览器内那一步已经做完:见「规模与性能」的
buildSearchIndex。) - v0.5 卡片直达:给每张卡片一个稳定地址,让会话里的引用、搜索日志和反向链接都能指向具体一张卡。
- v0.6 列表窗口化:卡片列表目前渲染全部可见行,上千行以后改成窗口化(虚拟滚动),这是眼下唯一还会随数据量变慢的界面部分。
- v0.7 备份瘦身:图片上千张时,完整备份改成分卷或「文档 + 图片目录」两种模式,避免生成一个几 GB 的 JSON。