← Back to home@hello-heyongping

dsh-helloai-works

DeepSeek Harness 卡片资料库:标签分类的图文卡片,可在对话里用 @ 引用单张卡片或整个标签分组

Stars
0
Language
TypeScript
Created
Oct 7, 2026
Updated
Oct 7, 2026

Introduction

HelloAI Works · DeepSeek Harness 卡片资料库

License: MIT version platform

以标签分类,用卡片记录文字与图片,在对话里 @ 就能把资料喂给模型。

面板在左侧栏「插件」的正下方:左列是标签,中间是卡片列表,点任意卡片在右侧栏打开编辑——和 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)——刷新后看角标就知道页面是不是最新构建。

快速上手

  1. 点左侧栏的「卡片资料」打开面板;
  2. 底部输入框新建一个标签(比如 人物),回车;
  3. 点右上角 新建 建一张卡片,标题 + 正文块 / 图片块随你加;
  4. 想引用时,在对话框输入框里打 @ → 选「卡片资料」或「标签分组」;
  5. 数据都在 <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。

怎么恢复:

  1. 重装 DSH 和本插件 → 同一个 DSH_HOME,文件夹还在,打开就是原样;
  2. 换了电脑或数据被删 → 用之前下载出去的 works-full-*.json,在备份抽屉里「导入备份」(导入前会自动先存一份快照);
  3. 只要 helloai-works 文件夹还在(哪怕一份备份文件都没有)→ 直接拷回 DSH_HOME 即可,插件不需要任何登记。

五条习惯:

  1. 定期「生成完整备份」再点「下载」,把文件存到另一个盘 / U 盘 / 网盘——只留在 backups/ 里,磁盘坏了会一起没;
  2. 整个 helloai-works 文件夹用网盘同步,等于随身带一份副本;
  3. 别把「清理未引用图片」当日常操作:它会让旧快照缺图,清理前先做一次完整备份;
  4. 自动快照只保留最近 30 份、且只含文字,重要的时间点自己导一份完整备份;
  5. 卸载、重装或换电脑之前,先导一次完整备份。

以上内容也原样写在插件的备份抽屉里(「数据说明」,默认展开),中英文各一份。

在对话里 @ 引用卡片

写小说要一直带着人物设定、世界观、章节大纲——把它们做成卡片,然后在输入框里 @ 进来:

  1. 打 @,菜单里和自带的「文件 / 会话」并列多出一栏 卡片资料;
  2. 继续打字就是搜卡片(标题、来源、正文、图注、标签名都能搜,用的是和左侧面板同一个检索索引);
  3. 选中后插入的是一个引用标签(草稿里显示卡片标题,不会把正文糊你一屏);
  4. 点标签可以在右侧栏打开这张卡;发送时,标签才会展开成卡片全文交给模型。

那栏里除了单张卡片,还有标签分组:选中一个标签 = 把这个标签下的卡片整组带进上下文。写小说时 @人物 一次带齐所有人物设定,比一张张点省事——这就是"用 @ 调用资料库数据"的完整形态:单片调用(一张卡)+ 成组调用(一个标签)。

模型实际收到的是这段文本(截取上限 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首次加载解析每次保存序列化建索引切换标签/排序每次按键搜索旧实现
5000.41 MB1.4 ms1.5 ms1.0 ms0.1 ms0.1 ms0.6 ms
2,0001.65 MB5.4 ms6.2 ms3.7 ms0.4 ms0.3 ms2.5 ms
5,0004.13 MB14 ms16 ms8.9 ms1.0 ms1.1 ms6.3 ms
20,00016.62 MB58 ms61 ms37 ms4.6 ms4.9 ms23 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/ 并可下载
导入用备份文件整体替换(替换前自动存一份快照)

一句话:整库一个文件,整份原子覆盖,带乐观锁。优点是极简、可读、可拷贝、导出即备份;代价见下节。

冲突期间自动保存会停在原地,不反复重试、更不会覆盖对方。未决冲突的提示条没有关闭按钮,避免把未保存的改动丢掉;这条提示也会跟到右侧栏的卡片页里,因为冲突是一个必须做的决定,不是一条过眼消息。

已知取舍

  1. 写放大(唯一剩下的结构性问题):真正的编辑仍会整份重写文档。实测 5,000 张约 4.1 MB / 16 ms,20,000 张约 16.6 MB / 61 ms(npm run bench)。内容一致时已经跳过写入。
  2. UI 线程序列化:保存前 JSON.stringify 整份文档,20,000 张约有 60 ms 的卡顿。
  3. 没有单卡历史:快照是整库级的,撤销不了「某张卡片的上一次编辑」。
  4. 冲突靠用户裁决:不做字段级自动合并,覆盖或放弃是二选一。对个人资料库这是刻意的——猜错的自动合并比让用户选一次更糟。

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,分四段跑,最后再补一项生命周期检查:

  1. Host —— 保存 / 图片往返 / 乐观锁 / 导出 / 导入 / 恢复 / 清理 / 路径穿越拒绝;
  2. 客户端 —— 把构建产物塞进一个假的 window.__ModuleLoader__,用 react-dom/server 渲染面板、卡片页、chip 和备份抽屉,检查「数据说明」七问七答都在且中英双语都写全了、插图策略(>1500 px 等比缩小、PNG 缩完仍是 PNG、预算内的图不重新编码、GIF/SVG 不动)正确、apply 只往触发器管线里注册一个 @ 来源,并检查没有任何颜色属性写死字面量(只能经由 token 兜底);
  3. 冲突 —— 直接驱动客户端 store 打真实接口,验证「另一个窗口先写 → 本窗口保存 → 409 → 保留本地改动 → 覆盖 / 放弃」两条路径;
  4. 增删改 / 检索 / @ 引用 —— 同样走真实接口验证标签与卡片的增删改;然后用同一份文档验证检索(标题 / 来源 / 正文 / 图注 / 标签名各字段都能搜到、大小写无关、跨字段不会误命中、标签筛选与新旧排序),并直接驱动 @ 来源:候选列表、按正文和标签名过滤、选中的落点(引用标签指向哪张卡)、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):
    1. 样式表改成注入 document.head(id="hxw-collect-style",幂等、不重复):模块求值时注入一次,CollectAction、CollectLayer 每次挂载/开合再断言一次,HMR 重载后自动刷新同一个节点。文档头是「不管哪个 React 树挂着都还在」的地方。
    2. 关键几何改成内联样式兜底:按钮尺寸/无边框、遮罩 position:fixed;inset:0;display:grid;place-items:center、对话框宽度/最大高度/圆角/底色,以及标题、内容、标签输入框和底部按钮的外观,都与样式表里的值一致(内联优先,所以有样式表时观感一模一样)。万一样式表真的丢了,对话框仍然是一个居中、可读、能用的模态框,不再堆到左上角。
    3. 图标补上 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。