Bundle
dsh-plugin-followup
划词追问:在助手回答里选中文字后右键追问,问答全部留在右侧面板,并从主对话中隐藏。
- Source
- MasterZ9286
- stars
- 1 stars
- License
- MIT
- Updated
- Updated 3 days ago
Readme
# dsh-plugin-followup(划词追问)
在 DeepSeek Harness Web 界面里,**选中助手回答中的任意文字 → 右键 → 追问**。
追问的问答留在**右侧面板**里,主对话里对应的那一轮会被隐藏。
纯客户端插件(浏览器半区),不需要宿主侧服务。
## 解决什么问题(痛点)
在长回答里追问一个细节,本来是件小事,实际用起来却很别扭:
| 痛点 | 具体表现 | 本插件怎么做 |
| --- | --- | --- |
| **一追问就要往回翻** | 回答很长,追问的问答越堆越多,想再看原来那一段就得往上滚半天,来回对照很累 | 选中的原文被**钉成右侧卡片**,对话滚到哪它都在眼前;同一张卡里继续追问,形成「引用 → 追问/回答 → 追问/回答」的线程 |
| **追问会把主对话冲乱** | 常规做法是把引用贴进输入框再发送,主对话里就多出一轮「引用 + 问题 + 回答」,阅读节奏被打断,上下文也越滚越长 | 追问产生的那一回合在主对话中**被隐藏**(按 `data-chat-turn` 精确到回合),需要对照时用面板底部开关随时放回来 |
| **引用要手工复制粘贴** | 想针对某个代码块、表格里的结论追问,得先选中、复制、粘贴,还要自己补引用格式 | 划词 → 右键 → 追问,`> 原文` 的引用格式自动生成;正文、代码块、宽表格、工具输出都能选 |
| **切工作区之后一片混乱** | 多个工作区/会话连着用,面板和记录混在一起,面板还会盖住主对话 | 面板住在**原生右栏**(宽度与比例由产品自己算,不覆盖对话),卡片按 `sessionId` 归属,**每个工作区各自一份** |
| **上下文不能丢** | 如果追问另起一个"新会话",模型就失忆了,答非所问 | 追问走**当前会话自身**的输入通道(`inputActions.setDraft()` + `submit()`),上下文完整;主对话里的隐藏只是渲染层 |
> ⚠️ 关键区别:**隐藏不是删除**。会话日志原样保留(这正是上下文记忆还在的原因),你随时可以恢复;面板里的「清空」也会把隐藏的回合一并放回主对话。
## 演示
**三步流程:选中 → 右键 → 问答留在右侧**

**追问面板结构**

> 以上是按实际 UI 结构绘制的矢量示意图(`docs/flow.svg`、`docs/panel.svg`),矢量图在任何缩放和深色背景阅读器下都清晰。
> 如果你想换成真实截图:把图片放进 `docs/`,再把上面两行改成对应文件名即可。
## 功能
| 能力 | 说明 |
| --- | --- |
| 划词追问 | 在已完成的回答里选中文字 → 右键 → `追问(右侧面板)`;正文、代码块、表格、工具块都支持 |
| 右侧面板 | 面板住在**原生右栏**(接管 `sidebar.right.tab.guide`),宽度与比例由产品自己计算,不会盖住主对话 |
| 按会话独立 | 追问卡片挂在 `sessionId` 上,每个工作区/会话各自一份 |
| 卡片线程 | 每张卡片是一条线程:`引用 → 追问1/回答1 → 追问2/回答2 …`,底部输入框常驻,可继续追问 |
| 页签式窗口 | 每轮追问是面板顶部的一个**页签**(像浏览器标签):点击切换、`×` 关闭并恢复对应回合;**同一页签里的继续追问留在同一页**,新的划词追问自动新建页签并切过去 |
| 回答全文镜像 | 回答从 `turn-tail` 节点的 `closing.blocks` 读取(应用"复制"按钮同源),**不截断** |
| 内置 Markdown 渲染 | 插件自带极简渲染器(**零外部依赖**):代码块围栏、行内代码、标题、列表、引用、表格、链接都按排版显示,不再露出原始的 ``` 围栏;卡片区另有错误边界,单个卡片渲染异常只退化出一行提示,不会拖黑整块面板 |
| 不打断阅读位置 | 发送追问时**事前拦住应用对会话滚动容器的自动滚动**,主对话连一下都不跳(另有 40ms 兜底校正);你一旦滚动 / 按键 / 点击应用立即解除,绝不抢占控制权 |
| 省空间的展示 | 引用/追问/回答过长时按**高度折叠**(不截断文本,避免把代码块切一半),可一键「展开全文(N 字)」 |
| 主对话隐藏 | 追问产生的那一回合按 `data-chat-turn` 在**渲染层**隐藏;会话日志不变,因此模型上下文完整保留 |
| 常驻入口 | 会话头部 `追问 N` 按钮,随时开关面板 |
| 可恢复 | 面板底部开关可把隐藏的回合放回主对话;「清空」为二次确认,并一并恢复 |
## 安装
插件包结构遵循 harness 的 Web 插件表约定:
```
dsh-plugin-followup/
├── package.json # dsh.client / dsh.bundle.patch 声明
├── cordis.patch.yml # 组合补丁:把插件插进 composition
├── dist/
│ ├── client.cjs # 浏览器半区(自包含,__ModuleLoader__ 契约)
│ └── index.js # 宿主半区占位(本插件不需要宿主逻辑)
├── README.md
└── LICENSE
```
`dist/client.cjs` 的外层契约:
```js
window.__ModuleLoader__.load({
id: `dsh-plugin-followup`,
factory: (require) => {
const React = require(`react`)
const module = { exports: {} }
module.exports.name = `dsh-plugin-followup`
module.exports.inject = [`slots`, `timer`]
module.exports.apply = (ctx) => { /* 注册右键菜单、面板、镜像 */ }
return module.exports
},
})
```
### 安装方式
**桌面版(推荐)**:本插件已提交到桌面端的内置预设列表([PR #544](https://github.com/dsh-tauri-desk/deepseek-harness-desktop/pull/544))。合并后,首次启动向导里会出现 **Selection Follow-up (划词追问)**,勾选即自动安装;之后也可以在「设置 → 插件」里查看。
**CLI / 自定义 profile**:
```bash
dsh plugin --profile <profile> add github:MasterZ9286/dsh-plugin-followup
```
> ⚠️ 桌面端每次启动都会按它自己的清单**重建** profile 的 `package.json`、`dsh.profile.bundles` 与 `cordis.patch.yml`,所以手改这几个文件会被覆盖 —— 请走上面的预设 / `dsh plugin add` 路径。
> ⚠️ 如果你同时在用**动态 Cordis 插件**版本(同一个会话里 `cordis_run` 起来的那份),请只留一个,否则会出现两个追问入口。
## 用法
1. 在任意一条**已说完**的回答里选中文字
2. 右键 → `追问(右侧面板)`
3. 右侧出现该轮追问的**页签**:顶部是引用,中间是线程,底部输入框;再次划词追问会新建页签
4. 写问题 → `Enter` 发送(`Shift+Enter` 换行);在同一个页签里继续追问,问答会接着往下排
5. 回答全文流入卡片;主对话里这一轮被隐藏
6. 想对照原文时,点面板底部 `主对话隐藏追问:开/关`;「清空」会把隐藏的回合一并恢复
## 实现要点(都经过对运行时源码的核对)
- **抢占右键**:应用自带的右键插件注册在 `document` 捕获阶段,本插件注册在 **`window` 捕获**并调用 `stopPropagation()`,因此在捕获路径上先于它执行;它开头的 `if (e.defaultPrevented) return` 会让它自行放弃,两层菜单不会叠加。
- **选区归属**:以框架的 `[data-slot="conversation.session"]` 限定"会话正文",而不是"最近的滚动容器"——后者会把自带滚动条的代码块/表格/工具块误判为区域外。取不到该属性时回退为旧的滚动容器启发式。
- **写回答**:优先读会话快照 `navigation.items()[i].response` 之外的**全文**来源,即 `turn-tail` 节点的 `closing.blocks`(`data-chat-turn` 用于定位回合)。
- **投递**:使用插槽标准 props 的 `inputActions.setDraft()` + `submit()`,因此与主会话共享完整上下文;输入框已有草稿时只追加、不自动发送,避免覆盖用户正在写的内容。
- **自带 Markdown 渲染**:面板**不**复用产品内部的 `MarkdownText`——它会动态加载 shiki / katex,在真实模块加载器里拿不到资源时会在渲染期抛错(v1.1.0 曾因此把面板整体拖黑)。现在改为插件自带的极简渲染器,覆盖回答里最常见的语法,且不引入任何外部依赖。`dsh.client.external` 也相应移除。
- **错误边界**:卡片区包在 `PanelBoundary` 中,任何卡片渲染异常只退化出一行提示,不影响对话与面板其余部分。
- **折叠不改文本**:只给容器加 `max-height` 折叠(不截断源文本),因此 Markdown 结构不会被截断破坏。
- **阅读位置守卫(两层)**:① *事前拦截* —— 发送前在该滚动容器上临时屏蔽 `scrollTop` 写入与 `scrollTo` / `scrollBy` / `scroll`,应用"跟随到底部"的调用直接落空,主对话完全不动;② *事后兜底* —— 以 40ms 间隔校回原位(最长 120 秒),应对滚动锚定等其它路径。补丁只作用于那一个 DOM 元素,解除时按原样还原(含元素上原有的属性描述符)。解除时机:`wheel` / `touchstart` / 翻页按键 / 在应用里打字 / 点击面板以外任意位置;面板自身的操作不解除。发送时若本来就贴在底部,则不介入。
## 已知限制
- **隐藏是渲染层的**:会话日志没有任何改动(这正是上下文记忆保留的原因),所以"主对话干净"是视觉上的干净,不是删除记录。
- 面板接管了原生右栏的 `guide`("开始")标签页正文,插件运行期间该页面被替换;禁用/卸载插件即恢复。
- 首次追问会调用 `ctx.layout.openRightbar(true, false)` 让中栏让出轨道;若窗口太窄导致无法保留轨道,则退回浮层模式(此时会覆盖一部分对话)。
- 依赖几个框架级 DOM 约定:`data-slot="conversation.session"`、`data-chat-turn`、`data-composer-input`。上游结构变化时需要同步更新。
## License
MIT
Install
dsh plugin --profile web add github:MasterZ9286/dsh-plugin-followup
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-plugin-followup from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.