主题
文档图像识别系统
本文引用的源码文件
本文引用的文件
- README.zh-CN.md
- package.json
- agent/package.json
- src/side_panel/doc-images.js
- agent/src/documents/image-extractor.js
- agent/src/documents/zip-reader.js
- agent/src/documents/routes.js
- test/unit/tool-read-document-image.unit.test.js
- test/unit/doc-images-extract.unit.test.js
- docs/superpowers/specs/2026-10-22-document-image-recognition-design.md
- docs/superpowers/plans/2026-10-22-document-image-recognition.md
目录
引言
本系统为 AI Helper 扩展的“文档图像识别”能力,目标是在用户上传 Word、PDF、Excel 等文档后,把文档内嵌图片转述为文本送入主模型对话;同时提供 read_document_image(现泛化为 read_image)工具,让 Agent 按需深读某张图。其关键设计是:主模型始终消费纯文本,视觉模型仅在插件端调用;Agent 端只负责 docx 抽图落盘和按需提供下载,不解析 PDF,也不接触任何模型调用。
该功能由规划与设计文档确认,并在扩展侧与代理侧分别实现:
- 扩展侧:浏览器端抽取 docx/pdf/xlsx 图片、压缩、视觉转述、清单块生成、session 级快照中转。
- 代理侧:基于 Node ESM 的最小 ZIP 读取器 + docx 图片提取 +
/api/documents/extract-images接口。 - 工具层:background 执行
read_image,从_docImageSnapshot取图并调用视觉 API。
章节来源
- README.zh-CN.md#L39-L86
- docs/superpowers/specs/2026-10-22-document-image-recognition-design.md#L1-L25
项目结构定位
文档图像识别横跨两个进程:
- Chrome 扩展端(Manifest V3,src/ 目录),版本以仓库根 package.json 为准。
- 独立本地代理端 Agent(Node.js,agent/src/server.js 入口),npm 包名 ai-helper-agent,版本以 agent/package.json 为准。
graph TB A["Chrome 扩展<br/>src/side_panel/doc-images.js"] --> B["background 工具执行器<br/>read_image 工具"] B --> C["chrome.storage.local<br/>_docImageSnapshot"] B --> D["视觉模型 API<br/>imageApiBase / imageApiKey / imageModelName"] A --> E["浏览器 pdf.js / jszip"] B --> F["Agent 文件服务<br/>local-agent-client.downloadFile"] F --> G["Agent 文档处理<br/>/api/documents/extract-images"] G --> H["agent/src/documents/*"]
图表来源
- src/side_panel/doc-images.js#L44-L51
- src/side_panel/doc-images.js#L563-L621
- agent/src/documents/routes.js#L10-L72
- test/unit/tool-read-document-image.unit.test.js#L109-L183
章节来源
- package.json#L1-L10
- agent/package.json#L1-L10
- docs/superpowers/specs/2026-10-22-document-image-recognition-design.md#L7-L25
核心组件
本节聚焦四个关键模块及其职责边界。
| 组件 | 位置 | 主要职责 | 输入 | 输出 |
|---|---|---|---|---|
| 浏览器端文档抠图 | src/side_panel/doc-images.js | 从 docx/pdf/xlsx 中抽取图片、压缩、视觉转述、构建清单块、写入 session 快照 | File 对象、配置、可选依赖注入 | images 数组、truncated/skipped、dataUrl、预览文案 |
| 代理端 ZIP 读取器 | agent/src/documents/zip-reader.js | 零依赖解析 ZIP 中央目录,支持 stored/deflate | Uint8Array Buffer | 条目名列表、指定条目原始字节 |
| 代理端 docx 图片提取 | agent/src/documents/image-extractor.js | 按 document.xml r:embed 顺序抽取 PNG/JPEG/GIF/BMP/WebP | docx 字节缓冲 | images、truncated、skipped |
| 代理端文档接口 | agent/src/documents/routes.js | 校验路径、限制大小、解析 zip、落盘 _attachments/<词干>/、写 manifest | { path, maxImages? } | { success, dir, images, truncated, skipped } |
章节来源
- src/side_panel/doc-images.js#L87-L138
- src/side_panel/doc-images.js#L152-L252
- src/side_panel/doc-images.js#L320-L405
- agent/src/documents/zip-reader.js#L1-L83
- agent/src/documents/image-extractor.js#L1-L89
- agent/src/documents/routes.js#L1-L73
架构总览
文档图像识别的整体数据流分为三个阶段:附件上传阶段、会话快照阶段、工具深读阶段。
sequenceDiagram
participant User as "用户"
participant Panel as "侧边栏附件处理"
participant DocImg as "doc-images.js"
participant Storage as "chrome.storage.local"
participant BG as "background 工具执行器"
participant Vision as "视觉模型 API"
participant Agent as "Agent 文件服务"
User->>Panel : "上传 docx/pdf/xlsx"
Panel->>DocImg : "extractDocxImagesFromFile / extractPdfImagesFromFile / extractXlsxImagesFromFile"
DocImg-->>Panel : "{ images, truncated, skipped }"
Panel->>DocImg : "describeImages(前 N 张)"
DocImg->>Vision : "chat/completions(图片+OCR提示)"
Vision-->>DocImg : "描述文本或空"
Panel->>Storage : "saveDocImageSnapshot(sessionId, attachedFiles)"
Note over Panel,Storage : "写入 _docImageSnapshot,含 dataUrl 或 path"
BG->>BG : "executeTool(read_image)"
BG->>Storage : "读取 _docImageSnapshot[sessionId]"
alt "本地来源 dataUrl 存在"
BG->>Vision : "压缩后发送图片深读请求"
Vision-->>BG : "返回分析文本"
else "Agent 来源 path 存在"
BG->>Agent : "downloadFile(path)"
Agent-->>BG : "{ content(base64), mimeType }"
BG->>Vision : "安全校验后发送深读请求"
Vision-->>BG : "返回分析文本"
end
BG-->>User : "工具结果:成功文本或错误文案"
图表来源
- src/side_panel/doc-images.js#L448-L501
- src/side_panel/doc-images.js#L577-L612
- test/unit/tool-read-document-image.unit.test.js#L109-L183
章节来源
- docs/superpowers/specs/2026-10-22-document-image-recognition-design.md#L14-L25
- docs/superpowers/specs/2026-10-22-document-image-recognition-design.md#L102-L116
详细组件分析
浏览器端文档图片抽取与视觉转述
src/side_panel/doc-images.js 是扩展侧的核心模块,承担三类文档的图片抽取与视觉链路。
设计要点
- 开关统一复用
state.enableImageInput,不新增配置项。 - 尺寸解析算法与 Agent 端
image-extractor.js同步,避免两端编号不一致。 - docx 使用 JSZip 解析
word/media/*,按document.xml中r:embed出现顺序编号。 - xlsx 解析 workbook→rels→sheet→drawing→media,记录 sheet 名与单元格坐标。
- pdf 在浏览器端用 pdfjs-dist,优先抓位图,无位图且文本稀少时整页渲染作为扫描件兜底。
- 单图压缩采用 5 轮降级:先降 quality,再缩尺寸,仍超则放弃实体但保留清单元信息。
- 视觉配置走
imageApiBase/imageApiKey/imageModelName,留空回落基础模型配置。
flowchart TD
Start(["开始"]) --> Type{"文档类型"}
Type --> |docx| Docx["JSZip 解析 word/document.xml 与 rels"]
Type --> |xlsx| Xlsx["workbook→rels→sheet→drawing→media"]
Type --> |pdf| Pdf["pdfjs-dist 抓取 paintImageXObject"]
Docx --> Extract["构造 images 数组<br/>index/page/mimeType/bytes/width/height/dataUrl/kind"]
Xlsx --> Extract
Pdf --> ScanCheck{"是否扫描页候选?"}
ScanCheck --> |是| RenderPage["OffscreenCanvas 整页渲染 JPEG"]
ScanCheck --> |否| SkipRender["跳过整页渲染"]
RenderPage --> Extract
SkipRender --> Extract
Extract --> Compress["compressForVision 五轮降级"]
Compress --> Describe["describeImages 并发转述前 N 张"]
Describe --> Snapshot["saveDocImageSnapshot 写入 _docImageSnapshot"]
Snapshot --> End(["结束"])
图表来源
- src/side_panel/doc-images.js#L93-L138
- src/side_panel/doc-images.js#L161-L252
- src/side_panel/doc-images.js#L326-L405
- src/side_panel/doc-images.js#L423-L436
- src/side_panel/doc-images.js#L455-L501
- src/side_panel/doc-images.js#L577-L612
关键函数说明
extractDocxImagesFromFile(file, opts):浏览器端 docx 抠图,返回 images、truncated、skipped。extractXlsxImagesFromFile(file, opts):浏览器端 xlsx 浮动图片抠图,带 sheet 与 cell 定位。extractPdfImagesFromFile(file, opts, deps):浏览器端 pdf 抠图,支持位图去重与扫描页渲染。compressForVision(dataUrl, deps):将 dataUrl 压缩到视觉上限,失败返回 null。describeImages(images, count, onEach):并发调用视觉模型,返回 index→description 映射。buildImageBlock(fileEntry, fileName):拼装消息内联图片清单块。saveDocImageSnapshot(sessionId, attachedFiles):写入 session 级图片快照,按预算淘汰旧会话。
章节来源
- src/side_panel/doc-images.js#L44-L51
- src/side_panel/doc-images.js#L87-L138
- src/side_panel/doc-images.js#L152-L252
- src/side_panel/doc-images.js#L320-L405
- src/side_panel/doc-images.js#L418-L446
- src/side_panel/doc-images.js#L448-L501
- src/side_panel/doc-images.js#L508-L525
- src/side_panel/doc-images.js#L577-L621
代理端 ZIP 读取器与 docx 图片提取
代理端不引入 jszip,而是通过 node:zlib 实现最小 ZIP 读取器,仅支持 stored 与 deflate。
ZIP 读取流程
flowchart TD
Input["Uint8Array Buffer"] --> EOCD["反向扫描 EOCD 签名"]
EOCD --> Central["遍历中央目录条目"]
Central --> Match{"名称匹配?"}
Match --> |否| Next["继续下一个条目"]
Match --> |是| LocalHeader["解析本地头 nameLen/extraLen"]
LocalHeader --> Method{"压缩方式"}
Method --> |stored| Raw["直接截取 compSize"]
Method --> |deflate| Inflate["inflateRawSync 解压"]
Raw --> Output["返回 Uint8Array"]
Inflate --> Output
Next --> Central
图表来源
docx 提取流程
- 读取
word/document.xml与word/_rels/document.xml.rels。 - 建立 rId→media 路径映射。
- 按
r:embed出现顺序收集唯一 media 路径。 - 对 PNG/JPEG/GIF/BMP/WebP 进行头部尺寸解析,超过单图字节上限跳过。
- 返回 images、truncated、skipped。
章节来源
- agent/src/documents/zip-reader.js#L1-L83
- agent/src/documents/image-extractor.js#L14-L31
- agent/src/documents/image-extractor.js#L39-L88
代理端文档接口 /api/documents/extract-images
该接口用于 Agent 模式下接收 docx 文件并落盘图片。
flowchart TD
Req["POST /api/documents/extract-images"] --> Validate["校验 path 与扩展名 .docx"]
Validate --> Security["checkPath 安全检查"]
Security --> Exists{"文件存在且为文件?"}
Exists --> |否| NotFound["返回 notFound"]
Exists --> Size{"文件大小 ≤ 50MB?"}
Size --> |否| TooLarge["返回 fileTooLarge"]
Size --> Read["读取文件字节"]
Read --> ZipCheck["listEntryNames 预校验 zip 完整性"]
ZipCheck --> Extract["extractDocxImages(buf, opts)"]
Extract --> WriteDir["删除并重建 _attachments/<词干>/"]
WriteDir --> WriteFiles["写入 img-{index}.{ext}"]
WriteFiles --> Manifest["写入 manifest.json"]
Manifest --> Success["返回 { success, dir, images, truncated, skipped }"]
图表来源
章节来源
内置工具 read_image(原 read_document_image)
工具定义与执行逻辑位于 background 工具体系,测试用例明确其行为契约。
工具行为
- 参数二选一:
{ file, image }或{ path }。 - 本地来源:从
_docImageSnapshot中读取 dataUrl。 - Agent 来源:通过
local-agent-client.downloadFile(path)拉取 base64。 - 安全校验:magic bytes 与单图体积上限必须通过。
- 视觉调用:复用现有视觉配置链路与压缩逻辑。
- 错误文案:包含文件名、可用附件、序号越界、缓存失效、Agent 离线、视觉未配置等。
sequenceDiagram
participant Tool as "tool-executor.executeTool"
participant Snap as "_docImageSnapshot"
participant DL as "local-agent-client.downloadFile"
participant Safe as "安全校验"
participant Vision as "视觉模型 API"
Tool->>Snap : "查找 sessionId 下 files"
alt "file+image 模式"
Tool->>Snap : "定位 {name, images[index]}"
alt "dataUrl 存在"
Tool->>Safe : "校验 dataUrl 大小与类型"
else "path 存在"
Tool->>DL : "downloadFile(path)"
DL-->>Tool : "{ success, content, mimeType }"
Tool->>Safe : "校验 base64 内容"
end
else "path 模式"
Tool->>DL : "downloadFile(path)"
DL-->>Tool : "{ success, content, mimeType }"
Tool->>Safe : "校验 base64 内容"
end
Safe-->>Tool : "通过 → 压缩/直传"
Tool->>Vision : "chat/completions(图片+问题)"
Vision-->>Tool : "分析文本"
Tool-->>Tool : "返回 { success, content }"
图表来源
- test/unit/tool-read-document-image.unit.test.js#L109-L183
- test/unit/tool-read-document-image.unit.test.js#L185-L259
章节来源
- test/unit/tool-read-document-image.unit.test.js#L1-L260
- docs/superpowers/specs/2026-10-22-document-image-recognition-design.md#L102-L116
依赖关系分析
文档图像识别涉及扩展侧与代理侧的多层依赖。
graph LR DPkg["扩展 package.json"] --> JSZip["jszip"] DPkg --> PDFJS["pdfjs-dist"] DPkg --> Mammoth["mammoth"] APkg["agent package.json"] --> Zlib["node:zlib"] APkg --> WS["ws"] APkg --> Archiver["archiver"] APkg --> Zod["zod"] SidePanel["src/side_panel/doc-images.js"] --> DPkg AgentRoutes["agent/src/documents/routes.js"] --> ImageExt["agent/src/documents/image-extractor.js"] ImageExt --> ZipReader["agent/src/documents/zip-reader.js"]
图表来源
- package.json#L43-L50
- agent/package.json#L26-L43
- src/side_panel/doc-images.js#L95-L96
- agent/src/documents/routes.js#L5-L6
章节来源
- package.json#L43-L50
- agent/package.json#L26-L43
- src/side_panel/doc-images.js#L95-L96
- agent/src/documents/routes.js#L5-L6
性能与资源控制
系统在多个环节设置了阈值与降级策略,防止大文档与大图拖垮浏览器或外部视觉服务。
| 控制点 | 阈值或策略 | 目的 |
|---|---|---|
| 单文档图片数量 | MAX_IMAGES = 20 | 限制单文档抠图上限,超出记 truncated |
| 单图原始字节 | MAX_RAW_BYTES = 5MB | 跳过过大图片,避免内存爆炸 |
| 单图视觉上限 | MAX_IMAGE_BYTES = 200KB | 压缩后送视觉模型,降低带宽与 Token |
| 视觉转述并发 | 前 DESCRIBE_LIMIT = 2 张并发 | 缩短最坏等待时间 |
| 视觉请求超时 | 60s AbortController | 防止外部 API 挂起 |
| 快照预算 | SNAPSHOT_BUDGET = 4MB | 淘汰旧会话 dataUrl,保当前会话 |
| Agent 文件上限 | EXTRACT_MAX_FILE_BYTES = 50MB | 拒绝超大 docx |
| PDF 扫描页阈值 | SCANNED_PAGE_TEXT_THRESHOLD = 50 | 文本稀少才整页渲染,减少无效渲染 |
章节来源
- src/side_panel/doc-images.js#L44-L51
- src/side_panel/doc-images.js#L423-L436
- src/side_panel/doc-images.js#L469-L499
- src/side_panel/doc-images.js#L577-L612
- agent/src/documents/routes.js#L8
故障排查指南
以下是文档图像识别常见问题的定位思路与对应实现依据。
| 现象 | 可能原因 | 排查建议 | 相关实现 |
|---|---|---|---|
| 上传 docx 后无图片 | ZIP 损坏或无 word/document.xml | 检查代理端 parseFailed 错误 | agent/src/documents/routes.js#L34-L43 |
| PDF 图片为空 | 位图不可解析或渲染失败 | 查看 logger.warn 日志 | src/side_panel/doc-images.js#L373-L400 |
| 视觉转述失败 | 未配置 imageApiBase/imageApiKey | 检查存储配置 | src/side_panel/doc-images.js#L439-L446 |
| 深读工具报“缓存已失效” | 跨会话或快照被清理 | 重新附加文件 | test/unit/tool-read-document-image.unit.test.js#L162-L166 |
| Agent 模式无法取图 | Agent 离线或 downloadFile 失败 | 检查 Agent 连接状态 | test/unit/tool-read-document-image.unit.test.js#L127-L133 |
| 图片过大被拒绝 | 超过单图上限或压缩失败 | 检查 magic bytes 与体积闸门 | test/unit/tool-read-document-image.unit.test.js#L137-L147 |
| xlsx 图片顺序异常 | drawing anchor 顺序或 rels 绝对路径 | 检查测试 fixture | test/unit/doc-images-extract.unit.test.js#L84-L167 |
章节来源
- agent/src/documents/routes.js#L34-L43
- src/side_panel/doc-images.js#L373-L400
- src/side_panel/doc-images.js#L439-L446
- test/unit/tool-read-document-image.unit.test.js#L127-L166
- test/unit/doc-images-extract.unit.test.js#L84-L167
结论
文档图像识别系统以“浏览器端抽取 + Agent 端落盘 + background 工具深读”的分层架构实现,既满足主模型纯文本消费约束,又覆盖 docx、xlsx、pdf 三类常见办公文档。其关键保障包括:
- 双端同步的 docx 编号算法与 PNG/JPEG 尺寸解析。
- 浏览器端 pdf.js 统一处理,避免 Agent 端 canvas 依赖问题。
- 多层阈值控制:图片数量、原始大小、视觉大小、快照预算、Agent 文件大小。
- 工具层严格的安全校验:magic bytes、体积上限、参数二选一、视觉配置前置。
- 完善的测试覆盖:浏览器端抽取单测与工具行为单测共同保证回归稳定性。
对于维护者而言,修改 docx 编号或尺寸解析需同时更新扩展侧与代理侧;修改视觉配置或工具参数需同步更新测试用例;Agent 端源码变更后必须重启进程,新接口才生效。