dsh-select-quote 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +37 -126
  2. package/lib/client.js +2261 -548
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -1,11 +1,16 @@
1
1
  # dsh-select-quote
2
2
 
3
- [DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/guide/quickstart) 的 Web 客户端插件:在对话里划词,把选中的文本作为**引用**带进下一条消息。
3
+ [DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/guide/quickstart) 的 Web 客户端插件:在对话里**划词加批注**——把选中的原文连同你的评论一起交给模型,并让它在回答里明确指出说的是哪一段。
4
4
 
5
- - **划词工具条**:选中对话区任意文本,浮出「复制 / 添加到任务」
6
- - **引用卡片**:添加到任务后,输入框内出现引用卡片(可多张),**草稿里不会出现 Markdown**
7
- - **随消息发送**:按 Enter 或点原来的发送按钮,引用块按卡片顺序折进消息,卡片消失
8
- - **对话记录**:同一条消息里的引用渲染为卡片,用户气泡里只剩你打的问题
5
+ - **划词工具条**:选中对话区任意文本,浮出「复制 / 添加到任务」;拖拽结束才出现,不挡操作
6
+ - **批注卡片**:添加后输入框内出现一张摘要卡片(可多条),**草稿里不会出现任何协议文本**
7
+ - **正文标记**:被划中的文字在原消息里留下虚线下划线 + 序号角标;点卡片里的条目会滚回那段文字
8
+ - **模型回指**:模型回答时以内联指令标出它处理的每一条批注,插件把这些指令渲染成可点击的 chip
9
+ - **对话记录**:已发送消息里的批注渲染为卡片,用户气泡只剩你打的问题
10
+
11
+ ![screenshot](https://raw.githubusercontent.com/latte03/dsh-select-quote/main/assets/screenshot-1.png)
12
+
13
+ <sub>示意图(按产品真实设计 token 渲染,源文件 `assets/screenshot-1.mock.html`)</sub>
9
14
 
10
15
  ---
11
16
 
@@ -13,149 +18,55 @@
13
18
 
14
19
  | 项 | 值 |
15
20
  |---|---|
16
- | dsh | `0.1.5-alpha.2`(同 minor 的 `0.1.5-alpha.x` 应该可用) |
21
+ | dsh | `0.1.7-rc.1`(2026-09-24 实测:划词、批注卡片、正文标记、对话记录渲染全通) |
17
22
  | profile | web(`dsh web`) |
18
23
  | 运行时 | 只有浏览器半有业务;Node 半仅用于让包被 Loader 挂载 |
19
24
 
20
- 实现依赖若干产品内部约定(见[已知限制](#已知限制)),升级 dsh 后请先跑 `npm run check` 再人工回归。
21
-
22
- ## 快速开始
23
-
24
- ### 方式一:本地开发加载(`--patch`)
25
-
26
- ```bash
27
- git clone <repo> && cd dsh-select-quote
28
- npm install
29
- npm run build
30
-
31
- dsh web --patch "$PWD/dev.patch.yml"
32
- ```
33
-
34
- 打开 `http://127.0.0.1:3080`,在对话消息里划词即可看到工具条。
25
+ 实现依赖若干产品内部约定(槽位名、composer DOM、深色主题属性等),升级 dsh 后请先跑 `npm run check`,再照 [`docs/internals.md`](docs/internals.md) 末尾的复核清单人工回归一遍。
35
26
 
36
- > `dev.patch.yml` 里的插件路径必须是**绝对路径**:checkout 不在 profile 的 `node_modules` 里,Loader 没有包名可解析。仓库内该文件按本机路径写死,换机器/换目录时改这一行;该文件不随 npm 包发布。
27
+ ## 安装
37
28
 
38
- ### 方式二:从 npm 安装(bundle)
29
+ ### 方式一:从 npm 安装(bundle)
39
30
 
40
31
  ```bash
41
- dsh plugin --profile web add dsh-select-quote # 或本地目录的绝对路径
32
+ dsh plugin --profile web add dsh-select-quote
42
33
  dsh --profile web
43
34
  ```
44
35
 
45
- ## 开发
46
-
47
- ```bash
48
- npm run typecheck # tsc --noEmit,应无输出
49
- npm run build # 产出 lib/index.js 与 lib/client.js
50
- npm run watch # 增量重建
51
- npm run verify # 无头执行 lib/client.js,断言 5 项贡献都注册上了
52
- npm run check # typecheck + build + verify(提交前跑这个)
53
- ```
54
-
55
- **为什么有 `verify`**:`tsdown` 不做类型检查,一个语法坏掉的 bundle 也能构建"成功"。最典型的一次事故是 CSS 模板字符串里出现了反引号(`` `.nyYjTG_file` ``),字符串被提前截断,产出的 JS 无法执行,但 build 输出一切正常。`verify` 把 `lib/client.js` 放进假的 `window.__ModuleLoader__` 里跑一遍,用 mock ctx 调 `apply()`,逐项核对注册结果——这是唯一能在浏览器之外发现这类错误的手段。改动 `src/client/index.tsx` 的注册项时,记得同步 `scripts/verify-bundle.mjs` 里的 `EXPECTED`。
56
-
57
- ## 工作原理
58
-
59
- ### 数据契约:消息里的引用块
60
-
61
- 引用在消息里就是一段带标记的 Markdown 引用块,正文在最前面:
62
-
63
- ```
64
- > [选中文本]
65
- > 用户选中的第一段
66
-
67
- > [选中文本]
68
- > 用户选中的第二段
69
-
70
- 用户自己输入的问题
71
- ```
36
+ > ⚠️ npm 上目前只有 `0.1.0`,那是旧版「引用块」行为,不含批注卡片、正文标记与模型回指。要用现在这套,请先用方式二,或从仓库目录安装:`dsh plugin --profile web add <本仓库绝对路径>`。
72
37
 
73
- `> [选中文本]` 这个标记是插件的识别依据:客户端据此把这段从用户气泡里剥掉、改渲染成卡片。**改动这个标记要同时改三处**:`formatQuoteBlock()`(写)、`parseQuoteMessage()`(读)、`displayTextWithoutQuote()`(剥离)。
38
+ ### 方式二:本地 checkout 加载(`--patch`)
74
39
 
75
- ### 客户端贡献
76
-
77
- | 位置 | 类型 | 说明 |
78
- |---|---|---|
79
- | `conversation.input.overlay` | list slot | `select-quote-toolbar`(划词工具条)、`select-quote-card`(引用卡片堆) |
80
- | `conversation.chat.node` | keyed slot | `select-quote`(引用卡片)、`user`(替换用户气泡) |
81
- | Conversation Definition | `uiConversation.events.register` | `select-quote`:把带引用的 `user/message` 变成一个 Chat 节点 |
82
-
83
- ### 关键机制
84
-
85
- **1. 引用不进草稿,只在发送瞬间注入**
86
-
87
- 卡片不是草稿的"可视化",而是唯一的表示。草稿保持用户真正输入的内容,引用块在**发送动作的捕获阶段**才折进去:`document` 上的 `keydown`(无修饰键的 Enter、非 IME 组字)或 `click`(composer 内 DOM 顺序上的最后一个 `button`,且不是停止按钮——停止按钮渲染的是 `svg rect`)。捕获阶段早于 composer 自己的处理器,因此它读到的草稿已经包含引用块。见 `src/client/QuoteCard.tsx`。
88
-
89
- **2. 不可见字符 U+200B 让发送按钮保持可用**
90
-
91
- 草稿为空时 composer 会把发送按钮置灰(`empty = draft.trim() === "" && attachments.length === 0`),而 U+200B 不是 JS 的 WhiteSpace,`trim()` 不会去掉它,于是"只有引用、没打字"也能直接发送。它只在**新增/移除引用卡片**时写入或清除一次,发送前由 `stripDraftMarker()` 剔除,绝不进消息。
92
-
93
- > 这条规则有个坑:**永远不要因为草稿变化就重写编辑器**。`inputActions.setDraft()` 的实现是 `root.clear()` + 逐行重建 + 光标移到末尾。早期版本在 `useEffect` 里依赖 `draft`,一旦发现标记缺失就补——用户按 Backspace 删到标记时会被立刻补回(表现为"删除失效"),输入法组字期间甚至会把正在组字的编辑器内容整段清掉(表现为"打完中文按 Backspace 没反应")。
94
-
95
- **3. 输入框卡片布局**
96
-
97
- `conversation.input.overlay` 的锚点是 `height: 0; position: absolute`,卡片本身也是绝对定位,因此**不会挤开下面的编辑器**,默认会盖住附件栏。做法是给 `[data-composer-card]` 加一个 `.dsq_cardPad` 类,用 `ResizeObserver` 实测卡片堆高度写进 `--dsq-quote-pad`:
98
-
99
- ```css
100
- .dsq_cardPad { padding-top: var(--dsq-quote-pad, 88px) !important; }
40
+ ```bash
41
+ git clone <repo> && cd dsh-select-quote
42
+ npm install && npm run build
43
+ dsh web --patch "$PWD/dev.patch.yml"
101
44
  ```
102
45
 
103
- 绝对定位子元素相对**padding box** 定位,所以卡片仍在顶部,而附件栏与编辑器一起被推到它下方——这也顺带修掉了"传图时图片被卡片盖住"。
104
-
105
- **4. 对话记录:Definition + 节点 + 气泡替换**
46
+ 打开 `http://127.0.0.1:3080`,在对话消息里划词即可看到工具条。`dev.patch.yml` 里的插件路径按本机写死(必须是绝对路径),换机器或换目录时改那一行;该文件不随 npm 包发布。
106
47
 
107
- - Definition(`transcript-node.ts`)匹配 `type === "user/message"` 且文本含引用块的消息,产出一个 `select-quote` 节点;
108
- - `TranscriptQuoteCard.tsx` 渲染该节点(每段引用一张卡片,纵向堆叠、与气泡同侧);
109
- - `UserMessageDisplay.tsx` 以 `priority: -10` 替换内置的 `user` 节点,把引用块从气泡里剥掉,**同时自己渲染消息携带的图片**——只引用、没有正文的消息,气泡文本为空,若不接管图片渲染,整条消息(含图片)会消失。
48
+ ## 用法
110
49
 
111
- **5. 样式与主题**
50
+ 1. 在对话区选中一段文本 → 点「添加到任务」
51
+ 2. 弹出评论框,写一句想说的话(可留空,只划不评)→ `⌘/Ctrl + Enter` 确认
52
+ 3. 输入框上方出现批注卡片,悬停可展开列表:逐条编辑、删除,或一键清空;点条目会跳到正文里对应的那段文字
53
+ 4. 正常打字、按 Enter 发送。批注随消息一起交给模型,卡片随之消失
54
+ 5. 模型回答里指向某条批注的位置,会显示成可点的 chip
112
55
 
113
- 卡片几何对齐产品自带的文件卡片(`ui-deliverables` 的 `.nyYjTG_file`):`.5px` 发丝边框、18px 圆角、64px 固定高、10px 内边距、hover 背景过渡;删除按钮绝对定位右上角,仅 hover / 键盘聚焦时淡入。
56
+ ## 已知限制
114
57
 
115
- - 深色模式用产品自己的 `body[data-ds-dark-theme]`(由 `dsh-client-ui-layout` 挂在 body 上),**不是** `prefers-color-scheme`;
116
- - 颜色一律用会随主题翻转的 `--dsw-alias-*` / `--dsw-static-*` token,只自己定义 `--dsq-card-fill` / `--dsq-card-hover` 两个填充变量;
117
- - 样式通过 `ensureToolbarStyles()` 注入一个 `<style data-plugin-css="dsh-select-quote/toolbar.css">`,**改完 CSS 必须刷新页面**(函数只插入一次)。
58
+ - **待发批注只在内存里**:刷新页面或切换会话后会丢,卡片不持久化(已发送消息里的批注不受影响)。
59
+ - **占用了用户气泡的渲染**:本插件以最低优先级替换内置 `user` 节点。若其它插件也替换同一个键(例如 `dsh-easyrewrite` 用 `-1`),会被本插件屏蔽。
60
+ - **卡片宽度有限**:输入框内每条摘要最宽约为输入框的 1/4,超长标题走省略号。
61
+ - 划词只在对话区生效,不介入编辑器、终端等面板。
118
62
 
119
- ## 目录结构
63
+ ## 开发
120
64
 
121
- ```
122
- src/index.ts # Node 半:占位,让包被 Loader 挂载
123
- src/client/index.tsx # Client 半入口:注册 Slot 与 Conversation Definition
124
- src/client/SelectionToolbar.tsx # 划词浮动条(复制 / 添加到任务)
125
- src/client/selection.ts # 选区快照、工具条定位、复制、聚焦输入框
126
- src/client/QuoteCard.tsx # 输入框内引用卡片堆 + 发送动作拦截
127
- src/client/quote-store.ts # 每会话引用状态、消息块拼装、U+200B 标记
128
- src/client/TranscriptQuoteCard.tsx # 对话记录里的引用卡片
129
- src/client/UserMessageDisplay.tsx # 替换用户气泡(隐藏引用块、保留图片)
130
- src/client/transcript-node.ts # select-quote Conversation Definition
131
- src/client/transcript-parse.ts # 引用块解析 / 气泡文本剥离
132
- src/client/styles.ts # 全部 CSS(模板字符串注入)
133
- src/client/context.d.ts # 客户端 Context 服务的类型补充
134
- scripts/build.mjs # tsdown 构建 + ModuleLoader 包装
135
- scripts/verify-bundle.mjs # 无头 bundle 校验
136
- cordis.patch.yml # bundle 加载层(按包名,随 npm 包发布)
137
- dev.patch.yml # 本地 --patch 开发层(绝对路径,不发布)
138
- lib/ # 构建产物(已提交,运行时直接读它)
65
+ ```bash
66
+ npm run check # typecheck + build + verify,提交前跑这个
139
67
  ```
140
68
 
141
- ## 已知限制
142
-
143
- - **引用状态在内存里**:刷新页面或换会话后待发引用会丢,卡片不会持久化。
144
- - **输入框卡片最宽为输入框的 1/4**(`max-width: calc(25% - 5px)`),超长标题走省略号;同一行可放 4 张,多了换行。正因为窄,输入框内的图标是 36px,而对话记录里是 40px。
145
- - **替换了 `user` 节点**(`priority: -10`,最低优先级胜出)。若其它插件也替换同一个键(例如 `dsh-easyrewrite` 用 `-1`),本插件会把对方屏蔽掉。
146
- - **只注入一个会话一份待发引用**,按 `sessionId` 在内存 Map 里存。
147
- - 依赖的产品内部约定:`[data-composer-card]` 属性、主发送按钮是卡片内 DOM 顺序上最后一个 `button`、停止按钮渲染 `svg rect`、`user/message` 事件的内容在 `data.content`(不是 `data.message.content`)、深色属性 `data-ds-dark-theme`。产品升级后这些都需要复核。
148
-
149
- ## 排错
150
-
151
- | 现象 | 先查 |
152
- |---|---|
153
- | 划词没有工具条 / 卡片不出现 | 跑 `npm run check`;确认页面已刷新;Host 的 boot 图里应包含 `dsh-select-quote`(`/plugins` 路由由 `ctx.clientModules` 提供) |
154
- | 输入框里出现 `> [选中文本]` 原文 | 说明发送前的注入没生效:确认草稿里存在 U+200B(发送按钮应是可用的),以及 composer 结构未变(`[data-composer-card]`) |
155
- | 对话记录里没有卡片 | 该 `user/message` 事件的 `data.content` 是否含引用块;`conversation.chat.node` 的 `select-quote` 单元是否被注册 |
156
- | 带图片的消息在记录里只剩卡片、图片没了 | `UserMessageDisplay` 是否拿到了 `renderMessageImages` |
157
- | 改了 CSS 不生效 | `<style>` 只注入一次,刷新页面 |
158
- | 按 Backspace 删不掉 / 中文输入被打断 | 检查是否有代码在响应草稿变化时调用 `setDraft()`(见[关键机制 2](#关键机制)) |
69
+ 改 CSS 后要刷新页面(样式只注入一次)。协议格式、槽位注册、以及几处非显而易见的坑(U+200B 草稿标记、portal 到 body 的浮层、`--dsq-quote-pad` 高度补偿)记录在 **[docs/internals.md](docs/internals.md)**。
159
70
 
160
71
  ## License
161
72