dsh-selection-followup
wanruxi/dsh-selection-followup
在对话里选中文字,选区变成输入框上方的一张引用卡片,输入框里只写问题;发送时引用原文自动跟在消息后面,模型能读到完整内容。包名 dsh-quote-tray。
安装
dsh plugin --profile web add github:wanruxi/dsh-selection-followupREADME
dsh-quote-tray · 选中追问
在 DeepSeek Harness 的对话里选中一段文字,旁边会浮出一个小「追问」按钮。 点一下,选中的内容就变成输入框上方的一张引用卡片,输入框里只留你要问的问题。
它解决什么问题
看长回答时想追问某一段,通常得先复制,再切到输入框粘贴,再手动加 > 变成引用,
结果输入框里堆满引文,自己写的问题反而找不到。这个插件把引用挪到输入框外面:
选好、点一下,卡片就贴在输入框上方,你只管写问题。
发送时卡片里的内容会以 Markdown 引用块追加到消息末尾,模型收到的仍是原文。
行为
- 只在非空选区出现。输入框内部、以及按钮自己的选区都会跳过,所以在输入框里打字 不会误触发。
- 按钮跟着指针走:鼠标松开处右下方弹出;键盘选区(
Shift+方向键之类)则落在选区 上方。靠近窗口边缘会翻到另一侧,贴边收拢用的是按钮实测尺寸,中文「追问」和英文 「Ask」宽度不同也不会被切掉。 - 点一下,选区变成一张卡片,贴在输入框上方。卡片有
×,随时能删;也可以一次挂多 条,按添加顺序排列。 - 卡片左右边缘和下面的输入框卡片对齐,不是贴着窗口边。对齐用的是宿主自己的两个
变量(
--dsh-composer-side-clearance和--dsh-composer-card-max-width),所以 拖动分栏宽度、开合侧栏、嵌入式窄面板下都还对齐。 - 卡片里只显示第一行前 80 个字符,鼠标悬停能看到全文;发给模型的始终是完整引用。
- 引用成功后按钮原地变成一句「已引用」回执,停留 0.9 秒。这个回执不接收鼠标事件, 不会挡住你下一次点击。
- 选区滚出可视范围就不再出现按钮——不会把按钮钉在屏幕边缘,让你去引用一段自己 已经看不见的字。
Ctrl+Shift+Q不用鼠标也能引用当前选区。所有平台都只认 Ctrl:Cmd+Shift+Q在 macOS 上是「注销」。Esc收起按钮。- 发送时,每条挂着的引用按添加顺序以
> …引用块追加在问题之后,中间空一行。 - 输入框必须有问题才发得出去:宿主会忽略空草稿的 Enter,卡片原样留着,不会丢。
- 发送失败(网络、被拒)时,卡片会退回原位,不会凭空消失。
- 引用前先规整选区:统一换行、去掉行尾空格、合并多余空行。
- 在别的会话(侧边对话)里选的文字,不会被引到这个会话;卡片也按会话分开存。
- 卡片存在
sessionStorage,刷新页面不会丢。 - 超过 8000 字符会被截断并加省略号,悬停提示会说明,而不是悄悄截断。
- 点击前不让焦点跑掉(
mousedown被 preventDefault),选区不会提前消失。
引用是怎么送出去的
宿主没有「输入框之外的额外上下文」这种公开通道,所以卡片本身不占草稿任何位置, 发送前的最后一公里改的是会话输入壳的三个函数:
| 函数 | 时机 | 作用 |
|---|---|---|
sinkSerialized | 每次提交开始 | 把挂着的卡片取走,记在这条发送上 |
deps.defaultSink | 文本已经序列化好、即将发出 | 把引用块拼到消息末尾 |
settleDetachedFailure | 宿主判定这次发送失败 | 把卡片放回去 |
三个一起打补丁,或者一个都不打:只打一半会让引用要么发两遍、要么失败后丢掉。 每个补丁都可逆,输入框组件卸载时会原样还原;补丁装上后如果还有发送在飞,卡片也会 退回。
这条路的代价:它依赖 SessionInputShell 的内部方法名。宿主大改这几个函数时,
插件会在加载时检测不到接缝并保持沉默——卡片还能挂、能删,但不会再自动追加到消息
里,而不是把发送链路搞坏。
和相近插件的区别
目前有几个 Harness 插件都能把选区引用进对话,路线分三类:
| 本插件 | chip 引用卡片 | 输入框外的注释 | |
|---|---|---|---|
| 引用放在哪 | 输入框上方,独立卡片 | 输入框里的引用节点 | 输入框上方独立列表 |
| 输入框里有什么 | 只有你的问题 | 引文和问题混在一起 | 只有你的问题 |
| 怎么发出去 | 发送时拼到消息末尾 | 由引文源 codec 还原 | 依赖 draftContexts 服务 |
| 对宿主的依赖 | sinkSerialized 等三个内部函数 | 公开的 inputTriggers | 该版本没有此服务 |
chip 那条路最“官方”,但引用会挤在输入框里,这正是要被替换掉的观感;
draftContexts 在实测的宿主版本里并不存在。剩下可选的就是改发送链路——代价写在
上面,换取的是输入框干净、引用照样送到。
测试
纯函数有离线测试,不用开浏览器:
npm test # node --test test/helpers.test.mjs
npm run check # 两半都做语法检查
覆盖引用格式、消息拼装顺序、卡片预览、空白规整、按钮贴边收拢、离屏判定、会话隔离、 卡片增删与发送失败的恢复,以及那条发送补丁:拼装位置、成功消耗、失败与拒绝后的 回填、卸载还原、缺接缝时不动宿主。界面效果本身仍需人工看一遍。
安装
在要启用它的 Harness profile 里:
dsh plugin --profile <profile> add dsh-quote-tray
也可以把本仓库目录直接作为 bundle 装进插件管理器。浏览器半边立即生效,刷新页面即可, 不用重启。
0.5.0 起包名从 dsh-selection-followup 改成 dsh-quote-tray:旧名字在 npm 上和另一个
Harness 插件上都被占了,两者没法装在同一台机器上。装过旧版的话,先从 profile 里移除
dsh-selection-followup 再装这个。
兼容性
dsh.client.platform 是 web。浏览器半边用到 conversation.input.overlay 和
conversation.input.dock 两个槽位,以及 conversation 服务的输入壳。engines.dsh
声明的是实测下限。
宿主若把 sinkSerialized / deps.defaultSink 改名,卡片照常能加能删,只是不再自动
追加到消息里;这种情况下浏览器控制台会有一行 [quote-tray] could not hook this compose box 的警告,而不是静默失效。
conversation 服务是用 ctx.get('conversation') 取的,没有写进 inject。原因是动态
插件拿到的 ctx 是个代理:属性访问只对 inject 里声明过的服务放行,ctx.get() 才是
不过门禁的可选查找。写进 inject 会让插件在缺这个服务的宿主上整体加载失败,而卡片
界面本来还能用。
文件分工
| 文件 | 作用 |
|---|---|
lib/index.js | 宿主半边——空实现,只为让 bundle 有个可加载入口 |
lib/client.js | 浏览器半边——选区监听、浮动按钮、卡片存储与发送补丁 |
test/helpers.test.mjs | 纯函数的离线测试 |
cordis.patch.yml | 把插件行插进插件树的 bundle patch |
icon.svg | 插件图标 |
assets/icon-180.png | 商店展示图 |
许可
MIT。