414 lines
18 KiB
Markdown
414 lines
18 KiB
Markdown
# 3-25 Local Folder MinerU OCR Sidecar Checklist v1
|
||||
|
|
|
|||
|
|
> 创建时间:2026-05-29
|
|||
|
|
> 状态:`process`
|
|||
|
|
> Owner:03-rust-web / local-folder resource runtime
|
|||
|
|
>
|
|||
|
|
> 2026-06-01 复核:后端 mock 闭环已落地 `local_ocr.rs` 与 OCR route;真实 MinerU HTTP client 已补本地 HTTP mock 成功路径测试;`task526` 已覆盖 mock OCR API、搜索命中、显式插入链接和 OCR sidecar Markdown resource tab 打开。前端 OCR task runtime、active job store、realtime event 和任务栏 UI 尚未落地。本文继续保持 `process`,不得被本轮 Page AI / OnlyOffice / ChatOnly 收口误归档。
|
|||
|
|
|
|||
|
|
## 背景
|
|||
|
|
|
|||
|
|
当前 mnote local-first 主线中,本地 Markdown 和同目录资源是默认数据真相。本地图片 / 附件上传已通过 `/api/local-folder/assets/upload` 写入 Markdown 文件附近,并返回 `sourcePath` / `attachmentRef`。现在需要给图片和图片型 PDF 增加 OCR 能力,识别引擎使用 MinerU API。
|
|||
|
|
|
|||
|
|
OCR 结果不应默认写进用户正文,也不应只存在 `.mnote` 私有缓存里。用户希望 OCR 附件位于当前 Markdown 文件相同目录下,并用单独文件夹承载,因为一个 Markdown 页面可能对应多个 OCR 文件。
|
|||
|
|
|
|||
|
|
## 目标
|
|||
|
|
|
|||
|
|
- 对 local-folder 图片和图片型 PDF 提供手动触发 OCR。
|
|||
|
|
- 使用 MinerU API 识别本地文件,生成 Markdown OCR 结果。
|
|||
|
|
- OCR 结果作为页面旁路资源落盘到当前 Markdown 同目录的 `{pageStem}.ocr/` 文件夹。
|
|||
|
|
- OCR 文本默认作为资源 sidecar 被搜索、资源面板和后续 AI 上下文消费,不自动污染正文。
|
|||
|
|
- 支持用户显式把某个 OCR 结果插入当前正文。
|
|||
|
|
- 提供全局 OCR 任务进展入口,让用户离开当前资源后仍能看到 OCR 是否完成、失败或需要重试。
|
|||
|
|
- 保持 local-first 可迁移性:OCR Markdown 文件本身可读、可备份、可编辑;`.mnote` 索引只作为缓存和状态加速。
|
|||
|
|
|
|||
|
|
## 非目标
|
|||
|
|
|
|||
|
|
- 不做全 workspace 自动 OCR。
|
|||
|
|
- 不默认把 OCR 文本追加到页面正文。
|
|||
|
|
- 不把 MinerU token 打印到日志、前端 payload 或用户可见错误里。
|
|||
|
|
- 不在前端直接调用 MinerU API。
|
|||
|
|
- 不把 OCR 文件当成普通页面加入 Page Tree。
|
|||
|
|
- 不为 cloud / Convex / remote source 设计默认 OCR 主路径;第一阶段只覆盖 local-folder。
|
|||
|
|
- 不承诺精确百分比进度;MinerU API 第一阶段按阶段型状态展示。
|
|||
|
|
- 不把任务进展做成前端定时轮询主链;优先走现有 realtime / WS / SSE 事件。
|
|||
|
|
|
|||
|
|
## 文件布局
|
|||
|
|
|
|||
|
|
假设当前页面为:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
docs/Page.md
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
附件可能为:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
docs/Page.assets/photo.png
|
|||
|
|
docs/Page.assets/spec.pdf
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
OCR 结果目录固定为:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
docs/Page.ocr/
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
OCR 结果文件示例:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
docs/Page.ocr/photo.png.ocr.md
|
|||
|
|
docs/Page.ocr/spec.pdf.ocr.md
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
同一页面下存在同名来源文件时,用来源 root-relative path、文件大小和 mtime 派生短 hash:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
docs/Page.ocr/photo.png-8f3a21.ocr.md
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## OCR Markdown frontmatter
|
|||
|
|
|
|||
|
|
OCR Markdown 是 OCR 正文真相,必须能脱离 `.mnote` 索引独立说明来源。
|
|||
|
|
|
|||
|
|
```markdown
|
|||
|
|
---
|
|||
|
|
mnote_ocr_version: 1
|
|||
|
|
provider: mineru
|
|||
|
|
model_version: vlm
|
|||
|
|
owner_document: ./Page.md
|
|||
|
|
source_path: ./Page.assets/photo.png
|
|||
|
|
source_root_relative_path: docs/Page.assets/photo.png
|
|||
|
|
source_size: 123456
|
|||
|
|
source_mtime_ms: 1760000000000
|
|||
|
|
status: done
|
|||
|
|
created_at: 2026-05-29T12:00:00+08:00
|
|||
|
|
updated_at: 2026-05-29T12:00:00+08:00
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
识别文本...
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
字段口径:
|
|||
|
|
|
|||
|
|
- `owner_document`:相对 OCR 文件所在目录到 owner Markdown 的相对路径。
|
|||
|
|
- `source_path`:相对 owner Markdown 所在目录的来源附件路径,和正文中的附件引用口径保持一致。
|
|||
|
|
- `source_root_relative_path`:相对 workspace root 的来源路径,用于稳定查找和索引。
|
|||
|
|
- `source_size` / `source_mtime_ms`:用于判断 OCR 是否 stale。
|
|||
|
|
- `status`:`done`、`failed` 或 `stale`。`queued/running` 只写入 `.mnote/ocr-index.json`,避免半成品 OCR 文件被误读。
|
|||
|
|
|
|||
|
|
## `.mnote` 索引
|
|||
|
|
|
|||
|
|
`.mnote/ocr-index.json` 是状态缓存,不是 OCR 正文真相。
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"version": 1,
|
|||
|
|
"entries": {
|
|||
|
|
"docs/Page.assets/photo.png": {
|
|||
|
|
"ownerDocumentId": "local-md:docs~2FPage.md",
|
|||
|
|
"ownerDocumentPath": "docs/Page.md",
|
|||
|
|
"sourceRootRelativePath": "docs/Page.assets/photo.png",
|
|||
|
|
"ocrRootRelativePath": "docs/Page.ocr/photo.png.ocr.md",
|
|||
|
|
"provider": "mineru",
|
|||
|
|
"modelVersion": "vlm",
|
|||
|
|
"status": "done",
|
|||
|
|
"sourceSize": 123456,
|
|||
|
|
"sourceMtimeMs": 1760000000000,
|
|||
|
|
"createdAtMs": 1760000000000,
|
|||
|
|
"updatedAtMs": 1760000000000,
|
|||
|
|
"plainTextPreview": "识别文本前 240 字"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
索引用途:
|
|||
|
|
|
|||
|
|
- 资源面板快速展示 OCR 状态。
|
|||
|
|
- 搜索索引快速定位 OCR sidecar。
|
|||
|
|
- 避免重复提交同一个未变化资源。
|
|||
|
|
- 记录失败原因,但错误文本需要脱敏,不包含 token、预签名 URL 或完整外部响应体。
|
|||
|
|
|
|||
|
|
## 资源归属与树投影
|
|||
|
|
|
|||
|
|
- OCR 文件夹 `{pageStem}.ocr/` 应在 File Tree 中作为页面旁路资源文件夹出现。
|
|||
|
|
- `{pageStem}.ocr/*.ocr.md` 不应作为普通 Markdown 页面进入 Page Tree。
|
|||
|
|
- local search 不应把 OCR sidecar 当普通 Markdown 页面索引,否则会出现重复页面和错误导航。
|
|||
|
|
- 搜索 OCR 命中时,应返回 owner page 作为结果,`hasOcr=true`,evidence 指向来源附件和 OCR sidecar。
|
|||
|
|
- File Tree 可展示 OCR 文件,点击默认作为 Markdown resource tab 打开,不切换成页面文档。
|
|||
|
|
|
|||
|
|
## MinerU 调用边界
|
|||
|
|
|
|||
|
|
后端读取 MinerU token,前端不接触 token。
|
|||
|
|
|
|||
|
|
配置优先级:
|
|||
|
|
|
|||
|
|
1. `MNOTE_MINERU_API_TOKEN`
|
|||
|
|
2. `MINERU_API_TOKEN`
|
|||
|
|
3. 开发环境可选读取 `~/.hermes/.env` 中的 `MINERU_API_TOKEN`
|
|||
|
|
|
|||
|
|
第一阶段使用 MinerU `model_version=vlm`。
|
|||
|
|
|
|||
|
|
本地文件流程:
|
|||
|
|
|
|||
|
|
1. `POST https://mineru.net/api/v4/file-urls/batch` 申请上传地址。
|
|||
|
|
2. 使用 `PUT` 把本地文件上传到预签名 URL。
|
|||
|
|
3. 轮询 `GET /extract-results/batch/{batch_id}`。
|
|||
|
|
4. 下载 `full_zip_url`。
|
|||
|
|
5. 解压并提取结果 Markdown。
|
|||
|
|
6. 写入 `{pageStem}.ocr/*.ocr.md`。
|
|||
|
|
7. 更新 `.mnote/ocr-index.json`。
|
|||
|
|
|
|||
|
|
## 后端 API
|
|||
|
|
|
|||
|
|
### 创建或复用 OCR job
|
|||
|
|
|
|||
|
|
`POST /api/local-folder/ocr/jobs`
|
|||
|
|
|
|||
|
|
请求:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"rootUri": "file:///mnt/Data1T/mnote-notes",
|
|||
|
|
"documentId": "local-md:docs~2FPage.md",
|
|||
|
|
"sourcePath": "Page.assets/photo.png",
|
|||
|
|
"sourceRootRelativePath": "docs/Page.assets/photo.png",
|
|||
|
|
"provider": "mineru",
|
|||
|
|
"force": false
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
响应:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"ok": true,
|
|||
|
|
"job": {
|
|||
|
|
"status": "done",
|
|||
|
|
"ownerDocumentId": "local-md:docs~2FPage.md",
|
|||
|
|
"sourceRootRelativePath": "docs/Page.assets/photo.png",
|
|||
|
|
"ocrRootRelativePath": "docs/Page.ocr/photo.png.ocr.md",
|
|||
|
|
"stale": false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
第一阶段可以同步执行并在请求内返回最终状态;如果真实耗时过长,再升级为后台 job。同步版本必须设置合理超时,并让 UI 显示运行中状态。
|
|||
|
|
|
|||
|
|
### 查询 OCR 状态
|
|||
|
|
|
|||
|
|
`GET /api/local-folder/ocr/status?rootUri=...&sourceRootRelativePath=...`
|
|||
|
|
|
|||
|
|
返回 `.mnote/ocr-index.json` 中的状态,并重新对比来源文件 `size/mtime` 判断 `stale`。
|
|||
|
|
|
|||
|
|
### 查询 OCR 任务列表
|
|||
|
|
|
|||
|
|
`GET /api/local-folder/ocr/jobs?rootUri=...`
|
|||
|
|
|
|||
|
|
返回当前 root 下 active jobs 和最近完成 / 失败任务,用于全局任务栏首次渲染和刷新后恢复。
|
|||
|
|
|
|||
|
|
响应:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"ok": true,
|
|||
|
|
"jobs": [
|
|||
|
|
{
|
|||
|
|
"jobId": "ocr_1760000000000_abcd",
|
|||
|
|
"fileName": "spec.pdf",
|
|||
|
|
"ownerDocumentId": "local-md:docs~2FPage.md",
|
|||
|
|
"ownerDocumentPath": "docs/Page.md",
|
|||
|
|
"sourceRootRelativePath": "docs/Page.assets/spec.pdf",
|
|||
|
|
"ocrRootRelativePath": "docs/Page.ocr/spec.pdf.ocr.md",
|
|||
|
|
"status": "mineru_processing",
|
|||
|
|
"stageLabel": "识别中",
|
|||
|
|
"startedAtMs": 1760000000000,
|
|||
|
|
"updatedAtMs": 1760000000000,
|
|||
|
|
"finishedAtMs": null,
|
|||
|
|
"stale": false
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 读取 OCR 文本
|
|||
|
|
|
|||
|
|
`GET /api/local-folder/ocr/read?rootUri=...&ocrRootRelativePath=...`
|
|||
|
|
|
|||
|
|
返回 OCR Markdown 文本、frontmatter 摘要、来源路径和 stale 状态。
|
|||
|
|
|
|||
|
|
### 插入 OCR 到正文
|
|||
|
|
|
|||
|
|
`POST /api/local-folder/ocr/insert`
|
|||
|
|
|
|||
|
|
只在用户显式触发时执行。默认插入为链接块:
|
|||
|
|
|
|||
|
|
```markdown
|
|||
|
|
[OCR:photo.png](./Page.ocr/photo.png.ocr.md)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
可选模式 `inlineSection` 插入正文段落:
|
|||
|
|
|
|||
|
|
```markdown
|
|||
|
|
### OCR:photo.png
|
|||
|
|
|
|||
|
|
<!-- mnote:ocr-source ./Page.assets/photo.png -->
|
|||
|
|
|
|||
|
|
识别文本...
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## UI 行为
|
|||
|
|
|
|||
|
|
- 编辑器图片 toolbar / 附件右键菜单增加 `OCR 识别`。
|
|||
|
|
- File Tree 图片/PDF 资源右键菜单增加 `OCR 识别`。
|
|||
|
|
- PDF preview / image resource tab 显示 OCR 状态和结果入口。
|
|||
|
|
- 全局任务栏 / 任务抽屉显示运行中、完成、失败和 interrupted 的 OCR 任务。
|
|||
|
|
- 状态文案只表达:未识别、排队中、上传中、识别中、写入中、已识别、来源已变化、识别失败、已中断。
|
|||
|
|
- 对 `stale` 结果展示“重新识别”操作,不自动覆盖旧 OCR 文件,除非用户确认或传 `force=true`。
|
|||
|
|
- OCR 结果打开在 resource tab,不切到普通文档页面。
|
|||
|
|
|
|||
|
|
## 搜索与 AI 消费
|
|||
|
|
|
|||
|
|
- `includeOcr=false` 时不搜索 OCR sidecar。
|
|||
|
|
- `includeOcr=true` 时,搜索 OCR 文本并返回 owner page。
|
|||
|
|
- 搜索 evidence 包含:
|
|||
|
|
- `kind: "ocr"`
|
|||
|
|
- `sourceRootRelativePath`
|
|||
|
|
- `ocrRootRelativePath`
|
|||
|
|
- `snippet`
|
|||
|
|
- 后续 `image_read` / Page AI attachment context 可以读取 OCR sidecar,优先返回已存在 OCR 文本,不让 agent 重复调用 OCR。
|
|||
|
|
|
|||
|
|
## 任务进展与全局入口
|
|||
|
|
|
|||
|
|
OCR 是慢任务,提交后必须有全局可见状态,不应只依赖 toast 或当前 resource tab。
|
|||
|
|
|
|||
|
|
第一阶段进度采用阶段型状态:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
queued -> uploading -> mineru_processing -> downloading -> writing_sidecar -> done
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
失败和中断状态:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
failed
|
|||
|
|
interrupted
|
|||
|
|
stale
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
状态含义:
|
|||
|
|
|
|||
|
|
- `queued`:任务已创建,等待执行。
|
|||
|
|
- `uploading`:正在上传原始图片/PDF 到 MinerU 预签名地址。
|
|||
|
|
- `mineru_processing`:MinerU 正在识别。
|
|||
|
|
- `downloading`:正在下载 MinerU 结果包。
|
|||
|
|
- `writing_sidecar`:正在写入 `{pageStem}.ocr/*.ocr.md` 和 `.mnote/ocr-index.json`。
|
|||
|
|
- `done`:OCR sidecar 已写入。
|
|||
|
|
- `failed`:任务失败,可重试。
|
|||
|
|
- `interrupted`:mnote-web 进程或任务 worker 中断,不能确认完成,可重试。
|
|||
|
|
- `stale`:来源文件在 OCR 后发生变化。
|
|||
|
|
|
|||
|
|
后端维护一个运行时 active job store,并把每次状态变更写入 `.mnote/ocr-index.json`。页面刷新后,已完成、失败、stale 和 interrupted 状态可以从磁盘恢复;正在运行中的内存任务如果因进程重启丢失,应在下次读取时标记为 `interrupted`,由用户重试。
|
|||
|
|
|
|||
|
|
状态变化通过现有 realtime / WS / SSE 事件链路广播,不在前端叠加 `setInterval` 轮询主链。事件示例:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"type": "local_ocr.job.updated",
|
|||
|
|
"jobId": "ocr_1760000000000_abcd",
|
|||
|
|
"rootUri": "file:///mnt/Data1T/mnote-notes",
|
|||
|
|
"ownerDocumentId": "local-md:docs~2FPage.md",
|
|||
|
|
"sourceRootRelativePath": "docs/Page.assets/spec.pdf",
|
|||
|
|
"ocrRootRelativePath": "docs/Page.ocr/spec.pdf.ocr.md",
|
|||
|
|
"status": "mineru_processing",
|
|||
|
|
"stageLabel": "识别中",
|
|||
|
|
"updatedAtMs": 1760000000000
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
前端需要两类展示:
|
|||
|
|
|
|||
|
|
- 资源局部状态:图片/PDF resource tab、附件菜单、File Tree 行显示当前 OCR 状态。
|
|||
|
|
- 全局任务栏 / 任务抽屉:底栏或右上角显示 `OCR 2` / `1 个任务进行中`。点开后列出文件名、所属页面、阶段、开始时间、完成/失败状态,以及“打开 OCR”“重试”等操作。
|
|||
|
|
|
|||
|
|
第一阶段不强制支持取消。若 MinerU 任务 API 后续提供可靠取消,再补 `cancel` 动作。
|
|||
|
|
|
|||
|
|
## 隐私与安全
|
|||
|
|
|
|||
|
|
- OCR 必须由用户手动触发。
|
|||
|
|
- UI 首次触发时应明确提示:文件会发送到 MinerU API。
|
|||
|
|
- 后端只允许读取 `rootUri` 授权根内文件,禁止绝对路径和 `..` 越界。
|
|||
|
|
- 日志不记录 token、预签名 URL、完整外部错误响应和 OCR 正文全文。
|
|||
|
|
- 失败状态写入简短错误码和脱敏消息。
|
|||
|
|
|
|||
|
|
## 实施 Checklist
|
|||
|
|
|
|||
|
|
- [x] 后端:新增 MinerU client,支持本地文件上传、轮询、结果 zip 下载和 Markdown 提取。
|
|||
|
|
- [x] 后端:新增 OCR 路由模块,提供 job/status/read/insert API。(当前 insert 只支持 link 模式)
|
|||
|
|
- [ ] 后端:新增 OCR active job store,记录 queued/uploading/mineru_processing/downloading/writing_sidecar/done/failed/interrupted/stale。
|
|||
|
|
- [ ] 后端:OCR 状态变化写入 `.mnote/ocr-index.json` 并广播 `local_ocr.job.updated` 事件。
|
|||
|
|
- [x] 后端:提供 OCR jobs list API,用于全局任务栏刷新后恢复。
|
|||
|
|
- [x] 后端:实现 `{pageStem}.ocr/` 路径规划、文件名冲突处理和 UTF-8 OCR Markdown 写入。
|
|||
|
|
- [x] 后端:实现 `.mnote/ocr-index.json` 读写、stale 检测和脱敏错误记录。
|
|||
|
|
- [x] 后端:扩展 local-folder 文件分类,让 `{pageStem}.ocr/*.ocr.md` 不进入 Page Tree 普通页面。
|
|||
|
|
- [x] 搜索:扩展 local search index,`includeOcr=true` 时索引 OCR sidecar 并返回 owner page。
|
|||
|
|
- [ ] UI:在附件右键菜单、File Tree 资源菜单和资源 tab 增加 OCR 识别入口。
|
|||
|
|
- [ ] UI:展示 OCR 状态、stale、失败和重新识别动作。
|
|||
|
|
- [ ] UI:新增全局 OCR 任务栏 / 任务抽屉,显示任务阶段、所属页面、打开 OCR、重试。
|
|||
|
|
- [~] UI/API:增加“插入 OCR 链接到正文”和可选“插入 OCR 正文段落”动作。(后端 link API 已完成;前端入口和 inlineSection 待做)
|
|||
|
|
- [ ] AI:让 image/PDF 资源上下文优先读取已有 OCR sidecar。
|
|||
|
|
- [x] 测试:Rust 单测覆盖路径规划、frontmatter、索引读写、stale、越界拒绝。
|
|||
|
|
- [x] 测试:Rust route 测试覆盖无 token、真实 MinerU HTTP mock 成功、模拟失败和脱敏错误。
|
|||
|
|
- [x] 测试:local search 测试覆盖 OCR 命中返回 owner page,OCR sidecar 不作为普通页面。
|
|||
|
|
- [~] Smoke:浏览器覆盖图片/PDF 手动 OCR、全局任务栏状态、打开 OCR resource tab、搜索 OCR 命中、插入 OCR 链接。(当前 `task526` 覆盖 mock OCR API、搜索命中和 insert link;真实 UI/任务栏/打开 OCR resource tab 待做)
|
|||
|
|
- [x] 文档:补充 `scripts/TESTING_REFERENCE.md` 中 OCR smoke 基线。
|
|||
|
|
|
|||
|
|
## 验收标准
|
|||
|
|
|
|||
|
|
- 本地图片或图片型 PDF 可被用户手动提交 MinerU OCR。
|
|||
|
|
- OCR 结果落在 owner Markdown 同目录的 `{pageStem}.ocr/` 下,文件为 UTF-8 Markdown。
|
|||
|
|
- `.mnote/ocr-index.json` 删除后,仍能从 OCR Markdown frontmatter 重建核心绑定关系。
|
|||
|
|
- OCR 文件不会污染 Page Tree,也不会作为普通页面搜索结果出现。
|
|||
|
|
- 搜索 OCR 文本时返回 owner page,结果标记 `hasOcr=true`。
|
|||
|
|
- 用户未显式选择插入时,页面正文不发生变化。
|
|||
|
|
- 用户提交 OCR 后,即使离开当前资源,也能在全局 OCR 任务栏看到阶段状态;完成后可打开 OCR,失败或中断后可重试。
|
|||
|
|
- 所有外部 API token 和预签名 URL 均不出现在日志、前端 payload 或错误响应中。
|
|||
|
|
|
|||
|
|
## 2026-06-01 后端 mock 闭环
|
|||
|
|
|
|||
|
|
已落地 `rust/crates/mnote-web/src/routes/local_ocr.rs` 与 route 注册,第一刀只承诺后端 mock 闭环:`provider=mock` 可同步生成 `{pageStem}.ocr/*.ocr.md`,写入 `.mnote/ocr-index.json`,提供 jobs/status/read;当时 `provider=mineru` 尚未执行真实外部调用,缺 token 时返回 `mineru_token_missing`,有 token 时返回 `mineru_runtime_not_enabled`。
|
|||
|
|
|
|||
|
|
同时已把 OCR sidecar 从 Page Tree 普通 Markdown 扫描中排除,并扩展 local search:`includeOcr=false` 不命中 OCR 文本,`includeOcr=true` 返回 owner page,结果带 `hasOcr=true` 与 `ocrEvidence`。
|
|||
|
|
|
|||
|
|
验证:
|
|||
|
|
|
|||
|
|
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr -- --test-threads=1`
|
|||
|
|
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_search_ocr -- --test-threads=1`
|
|||
|
|
|
|||
|
|
2026-06-01 续补:
|
|||
|
|
|
|||
|
|
- `local_ocr` route 测试新增来源路径越界、非图片/PDF 来源和 mock 失败脱敏 response 形态覆盖。
|
|||
|
|
- `local_ocr` route 测试新增缺 MinerU token response 覆盖:`mineru_token_missing`。
|
|||
|
|
- `local_ocr` route 测试新增 token 存在但真实 runtime 未接入 response 覆盖:`mineru_runtime_not_enabled`,避免把 mock OCR 闭环误报为真实 MinerU 能力。该旧口径已在后续真实后端 runtime 补齐后由 HTTP mock 成功路径测试替代。
|
|||
|
|
- 新增 `POST /api/local-folder/ocr/insert`,当前只支持 `mode=link`,显式把 OCR sidecar 链接追加到 owner Markdown;`local_ocr_insert_route_appends_explicit_ocr_link` 已覆盖。
|
|||
|
|
|
|||
|
|
2026-06-01 浏览器 API smoke 续补:
|
|||
|
|
|
|||
|
|
- 新增 `scripts/task526-local-folder-ocr-api-smoke.js`,在真实浏览器登录态页面内通过 `fetch` 串起 `POST /api/local-folder/ocr/jobs`、`status`、`read`、`jobs`、`/api/search/documents includeOcr=true` 和 `POST /api/local-folder/ocr/insert`。
|
|||
|
|
- 该 smoke 固定使用 `provider=mock`,不触碰真实 MinerU token、上传、轮询、zip 下载和 Markdown 提取链路。
|
|||
|
|
- 已验证 mock OCR sidecar 落盘、`.mnote/ocr-index.json` jobs/status/read 可读、`includeOcr=false` 不命中 OCR 文本、`includeOcr=true` 返回 owner page 且带 `hasOcr/ocrEvidence`、显式 insert 才向 owner Markdown 追加 OCR 链接。
|
|||
|
|
- 已通过:
|
|||
|
|
- `node --check scripts/task526-local-folder-ocr-api-smoke.js`
|
|||
|
|
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task526-local-folder-ocr-api-smoke.js`
|
|||
|
|
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr -- --test-threads=1`
|
|||
|
|
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_search_ocr -- --test-threads=1`
|
|||
|
|
|
|||
|
|
2026-06-01 后端真实 MinerU runtime 续补:
|
|||
|
|
|
|||
|
|
- `local_ocr.rs` 已补 `mineru_api_base_url`、`mineru_poll_interval`、`mineru_max_polls` 配置函数。
|
|||
|
|
- `provider=mineru` 后端路径已接入申请上传 URL、PUT 上传、轮询 batch 结果、下载 zip、提取 Markdown。
|
|||
|
|
- 新增 `local_ocr_jobs_route_runs_mineru_runtime_against_http_mock`,用本地 HTTP mock 覆盖真实 client 合同,不访问外网。
|
|||
|
|
- 已通过:
|
|||
|
|
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr -- --test-threads=1`
|