Skip to content

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

npm version license DSH

选中一段 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

桌面端

  1. 打开 DSH → 侧栏 插件 → 添加插件。
  2. 填入 npm 包名 @jjblau2021/dsh-glose;本地开发时选择本目录。
  3. 安装后立即启用。

本地开发

在本仓库目录执行:

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 处理。
  • 流式响应:/api Fetch 桥逐块写 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)。客户端 inject slots、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:

  1. 更新 package.json 版本;
  2. 推送并创建 tag v<version>;
  3. 创建 GitHub Release,工作流会跑单测并执行 npm publish --access public;
  4. 同一版本已存在时自动跳过。

需要仓库 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