Chuyển đến nội dung chính

dsh-quote-comment

Đã xác minh

dsh-quote-comment · v0.1.1 · MIT · Giao diện web

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

Cài đặt

dsh plugin add dsh-quote-comment

Xác nhận layer đã áp bằng dsh --profile default --dump-config — xem hướng dẫn cài plugin.

Mã nguồn

Phát hành lên npm mà không có repository công khai. Hãy kiểm tra nội dung package trước khi cài.

Thẻ

Tác giả

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 → 剪贴板)。