Skip to content

dsh-quote-comment

Verified

dsh-quote-comment · v0.1.1 · MIT · Web UI

选中会话内容即浮出「添加评论」按钮,点击把选中文本作为引用插入聊天输入框。

Install

dsh plugin add dsh-quote-comment

Confirm the layer applied with dsh --profile default --dump-config — see the install guide.

Source

Published to npm without a public repository. Inspect the package contents before installing.

Tags

Creators

Readme

dsh-quote-comment

在 DSH 会话里用鼠标选中任意内容 → 选区旁浮出「添加评论」按钮 → 点击即把选中文本作为一枚原子引用 chip 插入聊天输入框,光标落在 chip 之后,直接接着写你的评论。

chip 上只显示一行摘要(悬停看全文),发送时由本插件的 codec 展开成 Markdown 引用(每行 > )——输入框里干净,模型收到的是完整的被引内容。

这就是「快速引用选中内容与模型交流」:不用手打 > ,也不用复制粘贴。

交互细节

动作 行为
在会话正文里选中文字 选区右下方浮出「添加评论」胶囊按钮(悬停显示选区预览)
点击按钮 选中文本作为引用 chip 插入输入框(chip 显示 40 字摘要),光标落在 chip 之后;按钮消失、选区清空、焦点回到输入框
发送消息 chip 按 codec.serialize 展开成 Markdown 引用块(每行 > ,空行写成 >,末尾一空行)
滚动 / 缩放窗口 按钮跟随选区位置
按 Esc 收起按钮
在输入框内选中文字 不出现按钮(只有会话正文里的选中才算)
插入通道 ① 官方引用通道 actx.bail("slash/input-insert-reference") → ② 官方 inputActions.insertText(text, captureInsertion()) → ③ 合成 paste 事件 → ④ 写入剪贴板并提示「已复制,请粘贴」

实现要点

  • 纯客户端插件:宿主半身 lib/index.js 只打印一行日志,全部行为在 lib/client.js(无构建步骤,直接就是浏览器可加载的 UMD 包装,window.__ModuleLoader__.load({ id: "dsh-quote-comment" }))。
  • 浮层挂在官方 shell.overlay 槽位({ kind: "list", scope: "root" },由 @deepseek-ai/dsh-client-ui-layout 的 AppFrame 渲染,z-index 20 的 pointer-events:none 全屏层,直接子元素恢复 pointer-events:auto),不占用会话排版、不需要会话作用域。
  • 引用 chip 走官方引用通道(与官方 @ 引用同一条路):captureInsertion() 取 span → 在会话作用域上 actx.bail(actx, "slash/input-insert-reference", { reference, span })(会话作用域经 sessions.binding(sessionId).ctx 取,sessionId 来自 uiSession.current.getSnapshot().key)→ ui-conversation 的 SessionInputShell.insertReference 把它落成 Lexical 的 reference-chip 节点(data-composer-chip="quote-comment"),并自动补一个尾随空格。
  • chip 只带 source 名字,序列化靠插件自己注册的属主:ctx.get("inputTriggers").registerSource({ trigger: "#", name: "quote-comment", candidates: () => [], codec: { clipboardText, serialize } }),提交消息时 inputTriggers 按名字找到本插件、调用 codec.serialize(ref) 把 ref(原始选中文本)展开成引用块。没有这个属主,发送会被 no serializer for reference source "quote-comment" 挡住,所以注册失败时(服务缺失/名字被占)自动退回纯文本路径。
  • 兜底写输入框走官方 inputActions(ui-conversation 经 uiSession 服务以 props.inputActions 暴露):insertText(text, captureInsertion())。输入框是 Lexical 编辑器,document.execCommand("insertText") 对它不可靠,所以再兜底用合成 paste 事件。
  • 会话正文判定用官方 DOM 锚点:[data-chat-flow] / [data-chat-flow-key] / [data-chat-anchor-key] / [data-conversation-scroll],并排除 [data-composer-seat](输入区)与插件自己的 [data-quote-comment-root]。
  • 插入后光标必须经 Lexical 复位:只做裸 DOM focus() 会把光标丢到 contenteditable 的开头——官方注释原文 "a bare DOM focus on the contenteditable would land the caret at the start instead"(@deepseek-ai/dsh-client-ui-conversation/lib/client.js:13815-13823),于是光标停在 chip 之前。插件照官方两步法复位:rootElement.focus({ preventScroll: true }) → rootElement.__lexicalEditor.focus()(Lexical 把 editor 实例挂在根元素上,见同 bundle :10026),让模型里的选区(插入后 = chip 之后)重新刷回 DOM。

安装到 desktop profile

profile 目录:%USERPROFILE%\.dsh\profiles\desktop(本机 C:\Users\wangj\.dsh\profiles\desktop)。

$src = 'C:\Users\wangj\Documents\deepseek-harness\default-workspace\dsh-quote-comment'
$profile = "$env:USERPROFILE\.dsh\profiles\desktop"

# 1) 把源码目录链接进 profile 的 node_modules(junction,改动即时生效,不用重新拷贝)
New-Item -ItemType Junction -Path "$profile\node_modules\dsh-quote-comment" -Target $src
  1. 在 $profile\package.json 里登记两处(插件生效必须同时出现):
{
  "dependencies": {
    "dsh-quote-comment": "link:C:/Users/wangj/Documents/deepseek-harness/default-workspace/dsh-quote-comment"
  },
  "dsh": {
    "profile": {
      "bundles": [
        "@deepseek-ai/dsh-base",
        "@deepseek-ai/dsh-web-app",
        "@deepseek-ai/dsh-experimental-agent-team-profile",
        "dshmarket",
        "billion-context",
        "@vectorize-io/hindsight-coding-agents",
        "dsh-cost-meter",
        "dsh-quote-comment"
      ]
    }
  }
}
  1. 重启 DSH(客户端 bundle 在启动时组图,改完要重启;重启后刷新页面)。

卸载:把 bundles 与 dependencies 里的 dsh-quote-comment 去掉,删掉 node_modules\dsh-quote-comment 这个 junction,重启即可。

自测

& "C:\Users\wangj\.dsh\dsh-runtimes\dsh-primary-runtime\dependencies\node\bin\node.exe" test\smoke.mjs

test/smoke.mjs 用迷你 DOM + 迷你 React 在 Node 里驱动 lib/client.js,覆盖 51 项断言:模块契约、选区浮出按钮、定位、官方引用 chip 通道(属主注册、reference 载荷、codec 序列化、摘要截断)、无 chip 服务时回退 inputActions.insertText、多行引用格式、输入区选中不触发、paste 兜底、剪贴板兜底、插入后经 Lexical 复位光标(无 __lexicalEditor 时退化为裸 focus)。

test/install-check.mjs 另做 17 项安装自检(profile 登记、加载器清单契约、bundle 形态、宿主半身)。

重启后验收(30 秒)

  1. 退出并重开 DeepSeek Harness(客户端 bundle 只在启动时组图,rev 由内容哈希推导,改代码后重启即换新 URL,不会吃到旧缓存),刷新页面。
  2. 在任意会话的模型回答里用鼠标选中一段文字 → 选区右下方出现「添加评论」胶囊按钮。
  3. 点它 → 输入框里出现一枚引用 chip(气泡图标 + 选中内容摘要),光标在 chip 之后,接着写你的评论即可;发送后模型看到的是完整的 > 引用块。

想确认插件是否真的加载了,可在 DevTools Console 里跑:

document.querySelectorAll('[data-quote-comment-root]').length            // 1 = 浮层已挂载
document.querySelector('[data-composer-chip="quote-comment"]') !== null  // true = chip 插入成功

若按钮不出现,按顺序排查:插件是否在 profile 的 dependencies + dsh.profile.bundles 两处都登记(跑 node test/install-check.mjs);重启是否真的发生(宿主日志里应有 [dsh-quote-comment] host half loaded);选中的是否在会话正文而非输入框内。

若出现的是 > 选中内容 这样的纯文本而不是 chip,说明官方引用通道没走通(回退链生效):此时功能仍可用,但可以检查 ctx.get("inputTriggers") / ctx.get("sessions") 是否改名。

已知边界

  • 只处理鼠标选区(selectionchange / mouseup);键盘跨消息选中同样覆盖(走 selectionchange)。
  • chip 的 appearance 用官方三种图标里唯一的对话图标 session(另两种是 file / folder);ref 存的是原始选中文本,所以 chip 是「文本引用」而不是「会话引用」,点击 chip 不会打开别的会话(官方引用源才提供 openReference)。
  • 引用块用 Markdown > ,与模型交流时语义明确,也不会触发输入框的 / 命令菜单。
  • 插件注册的属主触发字符是 # 且不提供补全候选:输入 # 不会弹出额外菜单项。若日后与别的插件撞名((trigger,name) 唯一),插件会自动改用纯文本路径而不会报错。
  • 若 DSH 后续改了 shell.overlay 槽位名、inputActions 方法名、slash/input-insert-reference 事件名或输入框锚点,需要同步更新 lib/client.js 顶部的选择器/常量与 resolveInputActions()。

更新记录

  • 0.1.1 — 修复「点添加评论后光标停在 chip 之前」:插入后按官方两步法经 Lexical 复位选区(rootElement.focus() → rootElement.__lexicalEditor.focus()),test/smoke.mjs 增加两条回归断言 + 一条无 Lexical 实例时的退化断言。
  • 0.1.0 — 首个版本:会话正文选中 → 浮出「添加评论」→ 经官方引用通道插入原子 chip(注册 #quote-comment 引用属主负责提交时序列化),四级插入兜底(chip → inputActions.insertText → 合成 paste → 剪贴板)。