feat: paste local files into chat attachments

Copied local files previously required the attachment picker. The chat
composer now imports one or more supported files directly from paste,
sharing parsing progress, limits, and batch rollback with file selection.
Text and screenshot paste remain available, and failures preserve drafts.

Validation: prior 166 focused tests, typecheck, and lint passed; 28 targeted
tests passed again before commit. The prior full suite had 4214 passes,
67 skips, and 6 window-recovery failures; window tests passed 10/10 alone.
Native clipboard behavior was verified on Windows; macOS/Linux remain unverified.

Release note: 现在可在文件管理器中复制一个或多个支持的本地文件,直接粘贴到聊天输入框添加附件;可查看读取和解析进度,失败时保留草稿与已有附件。
This commit is contained in:
mesalogo
2026-09-15 08:05:31 +08:00
parent e1fe1cf10a
commit d4f1668c9b
19 changed files with 344 additions and 29 deletions
+3 -1
View File
@@ -7,8 +7,10 @@
| 文档 | 权威职责 |
| --- | --- |
| [文档解析与本地 OCR PRD](./prd.md) | 产品范围、解析策略、OCR 模型管理和验收 |
| [聊天附件技术说明](./chat-attachments-technical-design.md) | 文件选择与粘贴的进程链路、IPC、失败处理和验证方法 |
当前尚无独立 User Stories、功能逻辑、UI、技术设计和进度文档。
当前尚无独立 User Stories、功能逻辑、UI 和进度文档;聊天输入区交互遵循根目录
[UI 设计系统](../../../UI-DESIGN.md)。
Office 文件的浏览器内人工编辑、AI 选区修改、保存和撤销不属于文档解析职责,由
[Office 协同编辑](../office-document-editing/README.md)负责。文档处理仍负责内容提取、OCR、
@@ -0,0 +1,69 @@
# 聊天附件技术说明
产品规则见 [PRD 的聊天附件入口](./prd.md#321-聊天附件入口),输入区布局与反馈遵循
[UI 设计系统](../../../UI-DESIGN.md)。本文说明本地文件选择、文件粘贴和截图粘贴的实现。
## 文件导入链路
`App.tsx` 在 textarea 的 `paste` 事件中同步读取 `clipboardData.files`。没有文件时
保留浏览器默认文本粘贴。有文件时阻止默认插入,并通过 preload 的
`context.getFilePath(file)` 调用 Electron `webUtils.getPathForFile`。
有本地路径的文件调用 `context.importFiles(paths)`,经 `context:import-files` IPC 到
Main。IPC 先验证可信 sender,再使用共享 `contextImportFilesSchema` 校验输入。
没有本地路径的截图沿用 `context.addPastedImage`;无路径的非图片文件和混有无路径项目
的本地文件批次显示可处理的错误,不静默丢弃项目。
`ContextManager.selectFiles` 只负责文件对话框,再调用与粘贴共用的 `importFiles`。
这个方法完成路径规范化、格式及大小检查、文本读取、图片转换、文档解析与上下文暂存。
任一文件失败时删除本批次已经暂存的项目,保留之前的附件。文件系统错误不向 Renderer
暴露绝对路径。
两种文件入口均发送 `context:file-selection-progress`。Renderer 共用正在导入状态、
附件上限处理和附件清理逻辑,解析期间禁用附件按钮并阻止发送。成功结果加入发起操作的
会话草稿;发送时通过已有 `contextIds` 和 `ContextManager.enrichRequest` 生成请求上下文。
本次变更不修改 Agent、远端协议或 Runtime 的附件消费实现。
## Electron 验证
2026-09-15 在 Windows 的 Electron 43.2.0 上,从当前源码构建隔离输出并启动完整应用,
使用独立用户数据目录,保持 `sandbox: true`、`contextIsolation: true`。
已实际验证:
- Windows `Shell.Application` 的文件 `copy` verb 生成系统剪贴板后,原生粘贴事件提供
`File` 和有效路径,TXT 出现在聊天附件区。
- `CF_HDROP` 同时携带 TXT、DOCX 时,两个文件经生产 preload、IPC 和解析器成为附件;
DOCX 发出 `reading`、`parsing` 进度,包含正确文件名及 `2 / 2` 批次序号。
- 普通文本原生粘贴进入草稿,后续文件粘贴保持文字不变。
- ZIP 经生产导入路径报出不支持类型,已添加附件仍在。
- 系统图片剪贴板产生的 `image.png` 没有本地路径,仍通过截图入口生成图片附件。
以上使用原生 `webContents.paste()`,未伪造 DOM 粘贴事件或替换附件实现。Windows Shell
复制测试没有操纵资源管理器窗口中的键盘。产品未增加 Win32 剪贴板读取或兼容代码。
探测程序的原生调用仅用于构造文件剪贴板和保存、恢复测试前的剪贴板对象;最终探测的
`OleSetClipboard`、`OleFlushClipboard` 均返回成功。早期一次探测的恢复调用返回错误,
无法追溯确认该次剪贴板是否完整恢复。
macOS、Linux 文件管理器的实际剪贴板行为尚未在本次环境验证。没有发起模型调用;附件
进入发送请求的 `contextIds` 由 Renderer 回归测试验证,完整文档内容进入请求由生产
`ContextManager` 的真实文件解析测试验证。
## 自动检查
聚焦用例位于 `src/main/context-manager.test.ts`、`src/main/ipc.test.ts`、
`src/preload/context-files.test.ts`、`src/shared/context-contracts.test.ts` 和
`src/renderer/src/App.test.tsx`,覆盖多文件、进度、重复粘贴、发送上下文、错误与草稿保留、
大小及数量限制、批次回滚、可信 sender、输入契约和截图/文本回归。
```text
npx vitest run src/main/context-manager.test.ts src/main/ipc.test.ts src/preload/context-files.test.ts src/shared/context-contracts.test.ts src/renderer/src/App.test.tsx
npm test
npm run typecheck
npm run lint
```
本次最终拆分检查通过 6 项附件相关 Renderer 用例和 160 项 Main、IPC、preload、契约及
沙箱用例,类型检查与 lint 通过。完整 `npm test` 运行结果为 4214 项通过、67 项跳过、
6 项窗口恢复用例失败;这些用例属于并行修改的 `window.test.ts`,随后该文件单独复跑
10 项全部通过。本次没有再次完整重跑全仓测试。
+24 -1
View File
@@ -52,10 +52,33 @@ PDF 不是所有文档唯一的中间格式。解析应同时保留:
| 高保真审阅 | 视觉增强 | 原生解析、页面渲染、OCR 或视觉理解合并 |
PDF、DOCX、PPTX 和 XLSX 的解析结果保留章节定位及全文,不在附件入库时截断为
256 KiB,也不以富化后 Prompt 的 1 MiB 上限阻止发送。本地选择与消息通道附件使用
256 KiB,也不以富化后 Prompt 的 1 MiB 上限阻止发送。本地选择、文件粘贴与消息通道附件使用
同一文本保存路径;原文件大小、解析器和 12 MiB 附件暂存容量仍按现有规则检查,超限时
明确报错,不能用截断后的内容冒充全文。模型上下文窗口仍由所选 Runtime 处理。
#### 3.2.1 聊天附件入口
用户可点击附件按钮选择文件,也可在文件管理器中复制一个或多个文件后,在聊天输入框
按 `Ctrl+V` 添加。macOS 使用系统粘贴快捷键 `Command+V`。粘贴文件不替换草稿文字,
不自动发送;普通文本仍直接插入光标位置,系统截图仍作为图片附件添加。
本地文件选择与粘贴使用相同的格式及容量规则:
| 内容 | 支持范围 | 原文件大小上限 |
| --- | --- | --- |
| 文本、代码和配置 | C/C++、CSS、CSV、Go、HTML、Java、JS/JSX、JSON、日志、Markdown、Python、Rust、SQL、TS/TSX、TXT、XML、YAML | 256 KiB |
| 图片 | JPEG、PNG、WebP | 12 MiB |
| 文档 | PDF、DOCX、PPTX、XLSX | 20 MiB |
每条消息最多 8 个附件,Main 最多暂存 16 个上下文项目,合计不超过 12 MiB。
图片转换和文档解析后的内容计入暂存容量。一次粘贴超过 8 个文件时拒绝本次粘贴并提示;
批次本身未超限、但加上已有附件超过 8 个时,只保留剩余位置可容纳的附件并提示。
文件读取和解析期间显示文件名、处理阶段及批次序号,阻止重复文件导入和发送。
不支持的扩展名、目录、超限文件、读取或解析失败会使本批次回滚;已有附件与草稿保留,
错误显示在输入区。剪贴板中的虚拟文档若没有本地路径,提示先保存到本地后添加;
无本地路径的截图继续使用图片粘贴入口。
### 3.3 本地优先
- 文本层和本地 OCR 均在设备上处理;