dsh-glose
Verified@jjblau2021/dsh-glose · v0.7.0 · MIT · Web UI
Glose: explain selected text in DSH conversations as persistent annotations, with a sidebar library
Install
dsh plugin add @jjblau2021/dsh-glose Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Tags
Creators
Readme
glose
在 DSH(DeepSeek Harness)对话里划词解释,并把解释保存成持久高亮批注。
DSH 插件 · @jjblau2021/dsh-glose
选中一段 Agent 回复或用户消息,浮条会提供 解释 和 引用:
- 解释:在选区旁弹出气泡,流式显示解释。不会写入当前对话,不新增用户消息、命令或轮次。
- 保存批注:解释完成后保存,原文留下持久高亮;刷新页面、重开 App 后点高亮还能打开。
- 重新生成:在气泡底部输入「更简洁」「举个例子」「用英文」,或留空直接重新生成。
- 引用:把选区做成 Markdown 引用块插入输入框,再继续提问。
批注保存在浏览器 IndexedDB 中,按会话隔离。

目录
功能
- 不污染对话:解释走独立模型调用,不写会话日志、不触发标题生成/上下文压缩/下一轮请求。
- 气泡内流式回答:Host 通过 NDJSON 推送,气泡逐段显示,支持粗体、斜体、行内代码、列表、引用、代码围栏和 http(s) 链接。
- 一键保存为批注:IndexedDB 持久化 + CSS Custom Highlight,刷新、重开、切会话后自动重锚。
- 左栏批注库:左侧菜单打开全局管理页,按会话浏览全部批注,识别并清理已消失会话留下的记录。
- 草稿 / 已保存状态灯:标题行直接显示状态;保存成功后保存按钮原位变成删除,删除后回到草稿,解释文字保留。
- 按钮内反馈:复制/保存成功在按钮内显示文字与填充图标;反馈需计时结束且鼠标移开才收起。
- 可继续优化:输入 prompt 重新生成;已保存批注会自动更新,失败时恢复上一版。
- 不改 DSH 内部包:Host 只依赖
node:crypto和本地纯函数;Client 只用react,Markdown 使用内置小渲染器。
安装
从 npm
dsh plugin --profile <profile> add @jjblau2021/dsh-glose
桌面端
- 打开 DSH → 侧栏 插件 → 添加插件。
- 填入 npm 包名
@jjblau2021/dsh-glose;本地开发时选择本目录。 - 安装后立即启用。
本地开发
在本仓库目录执行:
dsh plugin --profile <profile> add "$(pwd)"
本地目录安装会以 link: 形式写进 profile。
升级与生效
- npm 安装:重新执行
dsh plugin --profile <profile> add @jjblau2021/dsh-glose。 - 本地 link 安装,只改了
client.js:刷新页面即可。 - 改了
index.js/lib/prompt.mjs(Host 半):完全退出并重开 DSH,或在插件页禁用/启用 glose。只刷新页面会看到「解释服务未加载(HTTP 404)」。 - 改了
package.json的 bundle 元数据:重新安装。
使用
划词
在 Agent 回复或用户消息里选中文字(鼠标拖选、双击、Shift+方向键都行),选区上方出现 解释 | 引用。
- 解释:调用 Host 的
/api/glose/explain,用当前会话的模型路由做一次性、不落盘的模型调用。 - 引用:通过
InputActions.captureInsertion()/insertText()把选区逐行加上>插入 composer,然后把焦点交回输入框。
气泡状态
气泡标题行固定为「批注」,旁边是状态灯;右侧操作随状态变化:
| 状态 | 标题行 | 右侧操作 |
|---|---|---|
| 草稿 | ● 草稿 |
保存 / 复制 / 关闭 |
| 保存成功 | ● 已保存 |
保存成功反馈 / 复制(暂时禁用)/ 关闭 |
| 已保存 | ● 已保存 |
删除 / 复制 / 关闭 |
点空白处、按 Esc、滚动对话都会收起气泡。
批注库
左侧栏的「批注」页签打开全局列表,按会话展示已保存的划词和解释。会话名旁边的箭头会打开那个会话;每条右下角可以删除。复制仍在对话里的批注气泡上操作。已消失的会话没有跳转箭头。
保存与删除
- 点保存:状态灯立即变「已保存」,保存按钮内显示「保存成功」。约 1.8 秒且鼠标移开后,保存按钮隐藏,删除按钮出现在原位置;复制恢复。
- 保存成功后原文留下高亮。之后点高亮会重新打开保存的解释。
- 点删除:移除高亮和 IndexedDB 记录,但气泡不关闭;状态回到「草稿」,解释文字保留,保存按钮重新出现。
- 删除有 300ms 防误触冷却;保存失败/删除失败会在对应按钮内短暂提示。
复制与引用
- 点复制:图标变成填充复制图标,按钮内显示「已复制」;1.5 秒且鼠标移开后还原。反馈期间再点不会重复复制,也不在下方弹提示。
- 点引用:在当前输入框插入 Markdown 引用块,不发送消息。
用 prompt 重新生成
气泡底部的输入框支持输入要求:
- 输入「更简洁」「举个例子」「用英文」等:带着原文、上下文、上一版解释和你的要求重新生成。
- 留空直接点重新生成:从零生成一版新解释。
- 已保存批注在生成成功后自动更新;失败会恢复上一版文字,不破坏批注。
工作原理
[Client]
划词 / pointerup
└─ 选区在当前 session 的 chat 区
└─ 浮条 [解释] [引用]
├─ 引用:inputActions.captureInsertion / insertText
└─ 解释:POST /api/glose/explain
└─ 读 NDJSON,逐段更新 fixed 气泡
└─ 保存:IndexedDB + CSS Custom Highlight
└─ MutationObserver 防抖重锚
└─ 点击高亮:Range.getClientRects 命中测试
[Host]
ctx.connection.fetch.register('/api/glose/explain')
└─ 模型路由:sessionController.selectionFor(agent)
→ session.requestHeader().config
→ agentDefaultModel.currentSelection()
└─ ctx.llm.stream({ provider, model, messages, system, ... })
└─ {"type":"delta","text"} … {"type":"done"|"error"}
模型路由优先使用实时 agent 的当前选择;没有实时 agent 时用会话最后一次请求头;最后回退到 profile 默认模型。
解释不经过 agent 收件箱:
- 不写
user/message、command/run,不创建 turn; - 不会触发标题生成、上下文压缩或下一轮请求;
- 对话历史完全不变,只有客户端内存里的气泡。
持久批注
保存的是锚点描述符,不是字符偏移:
{
id, sessionId, nodeKey, turn,
quote, // 用于显示
anchorQuote, // 从消息 DOM 文本里截出的原文,保证重锚精确
prefix, suffix, // 前后各 64 字,用于重复内容消歧
context, explanation, createdAt, updatedAt
}
- 存储:IndexedDB
glose-annotations/annotations,按sessionId建索引。 - 高亮:
CSS.highlights+::highlight(glose-annotation),不改 DOM 结构。 - 重锚定:
nodeKey优先 →anchorQuote精确匹配 →prefix/suffix消歧;失败再折叠空白匹配。 - 重算时机:加载、保存/删除、切会话、transcript 的
MutationObserver变化(150ms 防抖);消息未加载时保留记录,节点出现后补高亮。 - 重新生成:请求带
instruction(≤500 字)和previous(≤4000 字);已保存批注生成成功后自动更新explanation/updatedAt。 - 去重:同一
nodeKey + anchorQuote + prefix + suffix再保存会更新旧批注。 - 点击高亮:用 Range 的
getClientRects()命中测试,打开已保存解释;支持复制/删除。 - 跨消息选区:暂不保存高亮(一个 anchor 只能落在一条消息里);后续拆多 anchor。
数据与隐私
- 发送给模型的内容:选区原文(≤8000 字)、选区所在消息正文(≤4000 字)、可选 prompt(≤500 字)和上一版解释(≤4000 字)。不会发送整段对话或批注库;超限会截断并在请求里标记。
- 对话日志:不写
user/message、command/run,不创建 turn。 - 本地存储:批注在浏览器 IndexedDB;清站点数据会删除。桌面 App 本地端口变化会换 origin,旧批注可能读不到。
- 注入防护:quote/context 用 JSON 承载;system 明确声明它们是资料、不是指令,忽略其中的命令/角色/工具要求。
- 无遥测:插件不收集使用数据,不存储 API Key;模型请求由 DSH 的模型路由完成。
- 模型看不到批注:批注是纯浏览器本地数据;需要模型可见时再升级为 SessionEvent。
兼容性
- 已在 DSH 0.2.0-rc.2 验证。
- 需要支持
conversation.input.overlay、ctx.llm.stream和 CSS Custom Highlight 的 DSH Web/桌面版本。 - 目标浏览器为 Chromium(DSH Web/桌面内置);高亮使用
CSS.highlights,不支持时保存仍可用但没有可视高亮。 - 气泡限制在当前主对话区;右栏或子代理的嵌入式对话如果 session 不同,不会弹。
开发
要求 Node.js 22+;浏览器测试需要本机 Chrome。
测试
node --test test/*.test.mjs
42 项单测,覆盖规范化/语言/提示词/注入防护、Host 路由与流式事件、模型路由回退、客户端注册、选区过滤、气泡定位、NDJSON 解析、Markdown 和指令插入。
浏览器测试:
cd test/browser
npm install
npm test
用真实 React + Chrome 验证浮条、气泡流式渲染、复制/保存反馈、鼠标移开条件、删除回草稿、刷新重锚、状态灯和 prompt 重新生成。测试通过临时 HTTP 服务加载 harness,保证 IndexedDB 有稳定 origin。
真实 DSH 页面手动验证(需要临时 DSH_HOME 和 token):
node test/browser/live-bubble.mjs "http://127.0.0.1:<port>/?token=<token>"
node test/browser/live-persist.mjs "http://127.0.0.1:<port>/?token=<token>"
项目结构
| 文件 | 作用 |
|---|---|
index.js |
Host 半:注册 /api/glose/explain,解析模型路由,调用 ctx.llm.stream,输出 NDJSON |
lib/prompt.mjs |
纯函数:规范化、语言、解释请求;Host 与单测共用 |
client.js |
Client 半:浮条、选区读取、气泡、流式读取、引用插入、迷你 Markdown、IndexedDB 批注 |
cordis.patch.yml |
bundle 的 Host 插件行 |
locale/{zh,en}.json |
插件列表标题/描述 |
test/*.test.mjs |
Host / Client / prompt 单测 |
test/browser/ |
真实 React + Chrome 交互测试与 live 脚本 |
实现备注
- Host 精确路由:
ctx.connection.fetch.register({ path: '/api/glose/explain', methods: ['POST'], requestBody: 'buffered', fetch });/api的 Host/Origin + cookie 认证由 connection 处理。 - 流式响应:
/apiFetch 桥逐块写Response.body,可返回new Response(ReadableStream)。 - 一次性模型调用:
ctx.llm.stream({ provider, model, reasoningEffort?, messages, system, maxTokens, sessionId, purpose, signal });处理text-delta和finish.reason.kind(stop | max-tokens | error | aborted | tool-calls)。 - 客户端入口:
conversation.input.overlay是 session 作用域 list slot,组件拿到sessionId、inputActions、t。左栏批注库走sidebar.panellist+main(key: glose)。客户端 injectslots、locale、layout、sessions。 - Markdown:DSH 0.2.0 的
@deepseek-ai/dsh-client-ui-primitives不是第三方 Client 插件可 require 的 baseline external,因此自带迷你渲染。 - 气泡边界:横向宽度使用当前 session 的
[data-composer-card]/[data-conversation-region="chat"]矩形,而不只按视口,避免右侧栏/窄面板截断。
发布
维护者发布走 GitHub Actions + NPM_TOKEN:
- 更新
package.json版本; - 推送并创建 tag
v<version>; - 创建 GitHub Release,工作流会跑单测并执行
npm publish --access public; - 同一版本已存在时自动跳过。
需要仓库 Secret NPM_TOKEN(npm granular/automation token,允许发布 @jjblau2021/dsh-glose 并 bypass 2FA)。
排错
- 气泡显示「解释服务未加载(HTTP 404)」:Host 半还是旧代码;完全退出 DSH 再打开(不是只关窗口),或在插件页禁用/启用 glose。
- 气泡显示「当前模型没有配置 API Key」:在设置里配置模型,或给启动环境设置
DEEPSEEK_API_KEY。 - 点解释没反应 / 没有浮条:确认选区在当前会话的对话消息里;输入框、侧栏和别的会话不会弹。
- 更新后仍是旧行为:npm 安装需重新执行
dsh plugin ... add;本地 link 只改 Client 时刷新页面,改 Host 时完全重启。 - 气泡右侧被截断:0.6.2 起按输入区宽度夹取;如果仍复现,请提供右侧栏状态和截图。
已知限制
- 只覆盖当前主对话;右栏/子代理的嵌入式对话如果 session 不同不会弹。
- 批注只支持单条消息内的选区;跨消息暂不保存高亮。
- 重新生成会替换当前解释,不保留历史版本;每次解释独立,没有追问线程。
- 批注是浏览器本地数据,模型看不到。左栏可浏览全库和已消失会话的批注;未打开过的会话无法现场判断原文是否还在。
- 解释只带选区所在消息,不带整段对话;成本/隐私取舍。
- 保存必须手动点击,不会自动保存每次临时解释。
- 回答上限 800 tokens;达到上限会提示截断。
版本
当前 0.7.0。
最近版本
- 0.7.0 — 左栏批注库:按会话展示已保存批注,可删除,会话名旁的箭头打开对应会话。气泡图标对齐 DSH 细线风格。
- 0.6.3 — README 重构为开源 DSH 插件文档,补充 MIT LICENSE;无运行时变化。
- 0.6.2 — 气泡按当前会话输入区/对话区夹宽度,右侧栏/窄面板不再截断。
- 0.6.1 — README 去除本机绝对路径,补充 npm 安装方式。
- 0.6.0 — 包名改为
@jjblau2021/dsh-glose,仓库改为dsh-glose;内部glose命名空间/CSS/IndexedDB 保持不变。 - 0.5.0 — 草稿/已保存状态灯;保存成功后原位变删除;删除回草稿;复制/保存反馈需计时结束 + 鼠标移开。
完整版本:https://www.npmjs.com/package/@jjblau2021/dsh-glose?activeTab=versions
贡献
欢迎提交 issue / PR。提交前请运行:
node --test test/*.test.mjs
cd test/browser && npm install && npm test
License
MIT © JJblau2021