Files
goodbuddy/docs/features/document-processing/chat-attachments-technical-design.md
T

11 KiB
Raw Blame History

聊天附件技术说明

产品规则见 PRD 的聊天附件入口,输入区布局与反馈遵循 UI 设计系统。本文说明本地文件选择、文件粘贴和截图粘贴的实现。

文件导入链路

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 的附件消费实现。

2026-09-19 生产更新

HTTP 来源、文件资产、持久化草稿、显式图片 OCR、选图、历史预览和队列恢复已接入。 当前行为以生产实现说明为准;本文件后续“纯图片现状与规划边界” 及“完整附件流程设计”保留 2026-09-18 的实施前分析,不能作为当前未实现清单。 历史 Electron 记录仍按原日期保留,本轮证据见进度。

纯图片现状与规划边界

当前 ContextManager 将本地图片、粘贴图片和消息通道图片交给 storeImage,通过 encodeBoundedJpeg 转成 JPEG 暂存在内存;enrichRequest 将其放入 images,不调用 DocumentParsingService。serializeForQueue 保存上下文内容,restoreFromQueue 可恢复 队列图片,但这些机制未实现原始图片的独立文件保存。

图片能力由有效模型和 Runtime 决定。例如 ModelRuntime 在未启用图片输入时拒绝图片请求; 图片工具绑定路径还可能只保留工具引用并告知聊天模型无法看图,不能由附件已添加推断模型 已看到图片。现有路径没有统一的“提取图片文字或切换视觉模型”交互,本文不声称已经实现。

已确认的目标规则见 纯图片路由需求,原图与解析 文档分开保存的目录及生命周期见 资源规划。 显式图片 OCR、原图保留、草稿引用保护和长期预览均待实现,以下历史验证不覆盖这些能力。

完整附件流程设计

本节为待实现范围,与下述既有验证记录分开。设置与预览的布局、交互及验收由 UI 设计维护,发送模式、幂等、限制及删除规则见 选图与发送,进程契约和资源提交见 HTTP 技术设计。

需要在现有聊天链路中补齐:

  • App.tsx 的会话草稿附件增加读取持久化资源、逐附件解析状态、预览、重试和取消。 当前 attachmentsByConversation 与内存 contexts 不构成跨重启持久化。
  • 共享附件契约保存资源引用、原始名称、来源和发送方式。图片 OCR 成功只改变该草稿的 发送方式与显示名,原图仍可打开;消息与队列保留接受时的文字/图片选择。
  • 文档预览添加选图与普通导入汇入同一草稿和请求消费路径。选图采用整批提交及来源幂等, 不能直接套用新增文件按剩余位置截取的 UI 逻辑。父文档全文只送一次,图片分别计数。
  • 数量及容量值统一读取已有附件规则;Main 对草稿、入队和实际执行分别核验能力及有效引用, UI 不凭模型名称或图片工具引用推断已支持视觉。
  • 草稿、发送和队列先保存会话资产与 DB 引用,再报告接受;从知识库复制的图片属于会话, 不依赖源库文档继续存在。移除父文档、图片、队列或会话各自只释放对应引用。
  • ContextFileSelectionProgress 当前只有读取/解析阶段,需增加阶段、操作 ID 和取消接线; 首次批次失败仍回滚本批,新结果失败则保留旧附件。重启不自动恢复中断的 HTTP 推理。

新增图片发送路径仍需核对实际 Runtime 消费,包括远端 Agent;前文“本次变更不修改 Agent” 描述的是已完成文件粘贴工作,不是对本节未来实现范围的保证。生产实现需按仓库规则验证 受影响的远端路径,不能用历史 Windows 文件粘贴测试替代。

PPTX 图片识别与 OCR 生命周期

产品策略与限制分别见 PPTX 图片流程、 OCR 空闲资源回收。

DocumentParsingService 在非快速工作流中使用 knowledge/pptx-parser.ts 读取 演示文稿顺序、幻灯片文字、图片关系和内嵌图片。Office ZIP 读取复用 document-parser.ts 的条目数量、单条目及总展开大小检查;不读取工作区中的关联文件。 每张引用图片通过现有 DocumentOcrBroker 的图片请求契约,进入生产 document-ocr-bridge.ts 和 WASM Worker。结果合并时保留原生文字,附上幻灯片页码、 图片定位及 OCR 置信度,诊断中的 OCR 页数按页码去重。

Bridge 用待完成请求计数判断队列是否空闲,正常完成后安排一次空闲计时; 新请求和 terminateWorker 均清除计时。到期通过 Worker.terminate() 释放整个推理环境, 而不是只清空 JavaScript 引用或反复触发垃圾回收。此机制由 PDF 与 PPTX 共用, 不增加 IPC、持久化设置或模型安装状态。

解析与回收发生在 Desktop 的 Main/Renderer,gbagent 不运行此 OCR Worker。 对于 PDF、Office 文档,远程 Runtime 消费的仍是 Desktop 已提取的附件文本,没有单独的 Agent 解析器或 需要同步修改的远程 OCR 生命周期。

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、输入契约和截图/文本回归。

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 项全部通过。本次没有再次完整重跑全仓测试。

2026-09-15 PPTX OCR 与空闲回收验证

在 Windows x64、Electron 43.2.0 上,从本次源码构建独立输出,使用独立用户数据目录和 本机已安装的 PP-OCRv6 Tiny 模型完成真实验证。测试仅替换系统文件选择对话框的返回路径, 保留生产 UI、preload、IPC、ContextManager、解析服务、OCR Broker 与 WASM Worker, 未替换解析或推理实现。

  • 从“添加附件”一次导入 4 份图片型 PPTX,共 40 页;4 个附件均成功显示,连续处理期间 仅创建 1 个 OCR Worker。
  • 队列空闲后观察到 Worker 被终止。每 5 秒读取 Electron app.getAppMetrics(): Renderer 工作集采样高点为 733,492 KiB(约 716 MiB),回收后为 162,716 KiB (约 159 MiB),启动基线约 163 MiB。该结果是本次设备与文件上的测量,不是固定内存承诺。
  • 回收后,通过生产 context.importFiles 再导入一份 9 页 PPTX;新建第 2 个 Worker, 9 页均返回非空文字,共 2,462 字符,附件成功生成。
  • 本次合计 49 次本地图片 OCR 请求,文本模型调用 0 次;没有发送聊天消息,也没有将文件 交给云端模型。未修改源文件。未在 macOS、Linux 或其他 OCR 模型档位上做本次实测。

新增聚焦回归覆盖 PPTX 关系与顺序、各场景路由、原生文字合并、快速模式、页数上限、 OCR 错误和取消;Worker 回归覆盖空闲到期、连续请求复用、队列保护、关闭清理与立即取消。 空闲回收后重新加载也在真实模型路径上验证,不能只用模型配置检查代替。

最终检查:npm test 4,293 项通过、67 项跳过;npm run typecheck、 npm run lint 通过。当前源码的隔离 Electron 生产构建通过;文档的 10 个相对文件链接 均有效。测试应用已关闭,包含附件草稿的独立用户数据目录已删除,原文件和原模型安装未改动。