### What this PR does Before this PR: - Knowledge embeddings and reranking ran through the legacy embedjs-based knowledgeV1 stack with their own provider clients, independent of the app's AI service. - File-processing intake accepted several heterogeneous input shapes, and knowledge file items were tracked by FileEntry ids, coupling file content to the file-manager entry/cache. After this PR: - Embeddings and reranking are routed through the unified `AiService` (with cherryin rerank support) and guarded by strict embedding-dimension validation that rejects stale/mismatched vectors. - File-processing intake is collapsed to a single path-based model; knowledge file items are stored by base-relative path under the knowledge-base directory, and v1 uploads are copied into the v2 base dir during migration so migrated items stay reindexable/restorable. - Legacy `knowledgeV1` is removed; the orchestration services were renamed to `KnowledgeService` / `FileProcessingService`. - Chat -> knowledge attach is temporarily disconnected (tracked TODO) while the v2 file-manager bridge is rebuilt. Fixes #N/A (no linked issue) ### Why we need it and why it was done in this way Routing embeddings/rerank through `AiService` unifies provider handling and credentials and removes the parallel embedjs client stack and its v1 coupling. Storing knowledge files by base-relative path (instead of FileEntry ids) makes each knowledge base self-contained and portable. The following tradeoffs were made: - A large, coordinated refactor plus a migration step that physically copies v1 uploads into the v2 base dir, in exchange for removing the parallel client stack and making bases self-contained. - Base-relative path storage required a fail-fast/dedup strategy for same-named files and a guard for blank legacy filenames. The following alternatives were considered: - Keeping the embedjs stack behind an adapter — rejected; perpetuates the parallel client and v1 coupling. - Keeping FileEntry-id storage — rejected; couples knowledge files to the file-manager cache and blocks portability. ### Breaking changes - `knowledgeV1` is removed. Legacy v1 knowledge data reaches v2 only through the v2 migrators; there is no v1 fallback. - The v2 knowledge HTTP API (API gateway) now returns v2-native per-entry fields (`embeddingModelId`, `createdAt` on base entries; `chunkId`, `scoreKind`, `rank` on search results). The response envelope (`knowledge_bases`, `searched_bases`, `total`) is unchanged. See `v2-refactor-temp/docs/breaking-changes/2026-06-05-knowledge-api-v2.md`. ### Special notes for your reviewer - This branch went through several rounds of multi-agent code review. The most recent 6 commits address review findings: directory-import path collisions, migrated-file source copying + blank `relativePath` guard, addItems rollback error preservation, eager `document_to_markdown` output-target validation, a `CompletedKnowledgeBase` type guard, and breaking-changes doc corrections. - Chat -> knowledge attach is intentionally disconnected for now (tracked in `v2-refactor-temp/docs/knowledge/knowledge-todo.md`). - Local full `pnpm lint`/`pnpm test` was not run per the project's review conventions; please rely on CI / `pnpm build:check`. ### Checklist - [x] Branch: This PR targets the correct branch — `main` for active development, `v1` for v1 maintenance fixes - [x] PR: The PR description is expressive enough and will help future contributors - [x] Code: Write code that humans can understand and Keep it simple - [x] Refactor: You have left the code cleaner than you found it (Boy Scout Rule) - [x] Upgrade: Impact of this change on upgrade flows was considered and addressed if required - [ ] Documentation: A user-guide update was considered and is present (link) or not required. - [x] Self-review: I have reviewed my own code before requesting review from others ### Release note ```release-note NONE ``` --------- Signed-off-by: eeee0717 <chentao020717Work@outlook.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
31 KiB
File Processing Unified Job Refactor
1. 文档目的
这份文档是 src/main/services/fileProcessing 下一轮重构的设计基线。
当前代码已经有一版 Main-side file-processing service,但它不是定稿。后续实现可以围绕本文重新组织接口、job 模型和内部服务边界,不需要维护旧的 split API 作为兼容目标。
本文覆盖:
- file-processing 的模块边界
- 统一 job API
- artifact 结果模型
- job 状态、取消、事件和落盘语义
- processor 与配置边界
- 本轮重构明确不做的内容
本文不直接描述 UI 交互,也不要求立刻完成 Renderer 切流。
2. 设计定位
file-processing 是 Main 进程里的内容提取 / 内容转换能力模块。
当前明确支持两类使用场景:
- 知识库上传 PDF / Word 等文档前,先把文档转换成 Markdown。
- 翻译等上层功能上传图片后,把图片 OCR 成文字。
这两个场景都应该收口到同一套底层能力,但 file-processing 本身不应该理解知识库或翻译业务。
换句话说:
file-processing负责把输入文件处理成可消费的结果 artifact。KnowledgeService或其他上层 service 负责决定何时处理、如何展示进度、如何入库、如何切 chunk、如何做 embedding。- 翻译页面或翻译业务负责把 OCR 文本插入输入框、发起翻译或展示错误。
因此,底层接口不使用 preprocessKnowledgeFile、translateOcr 这类业务命名,而使用 startJob 与通用 Job 能力命名。
3. Canonical Terms
| 术语 | 含义 |
|---|---|
| File Processing | 文件内容提取 / 转换能力集合,不代表某个具体业务流程 |
| Processor | 一个可执行文件处理能力的处理器,例如 tesseract、paddleocr、mineru、doc2x |
| Feature | Processor 暴露的能力类型,当前只有 image_to_text 和 document_to_markdown |
| Capability | Processor 对某个 Feature 的支持声明,包括输入类型、输出类型和默认 API 配置 |
| FileProcessingJob | 一次 processor execution,由统一 JobManager 生成 jobId 跟踪 |
| Artifact | job 完成后产出的结果项,例如内联 text 或落盘 markdown file |
| Provider task | 第三方 provider 自己的任务句柄,例如远程 OCR / Markdown 服务返回的 job id;只属于 Main 内部实现细节 |
| Runtime state | handler 执行期的 abort controller、远程 query context、in-flight query 等 Main 进程内存态协调数据;持久 job 状态属于 JobManager |
需要避免的命名:
- 不把底层
image_to_text命名成translate_ocr。 - 不把底层
document_to_markdown命名成knowledge_preprocess。 - 不在对外契约里暴露
providerTaskId或 provider-specific query context。
4. Public Main-side Contract
统一对外能力面:
startJob({ feature, fileEntryId, processorId? }): Promise<JobSnapshot>- job 查询 / 进度观察走统一 Job DataApi 与
jobs.progress.${jobId}cache。 - cancel 走统一 JobManager/job API;FileProcessing 不再单独包装
getJob/cancelJob。
推荐 IPC channel:
file-processing:start-jobfile-processing:list-available-processors
旧 file-processing IPC 不保留兼容包装:
file-processing:extract-textfile-processing:start-markdown-conversion-taskfile-processing:get-markdown-conversion-task-result
这些旧接口应在实现重构时被替换,而不是继续作为新 API 的 facade。
4.1 startJob
startJob 接收:
feature:image_to_text或document_to_markdownfileEntryId: 已登记的 FileManager entry idprocessorId: 可选;未传时按 feature 读取默认 processor preference
startJob 返回统一 Job snapshot:
type StartFileProcessingJobResult = JobSnapshot
约束:
- Main 进程必须生成统一
jobId。 - 调用方不直接持有 provider task id。
- 如果没有显式
processorId,且对应 feature 没有配置默认 processor,直接 fail fast。 - 如果指定 processor 不支持该 feature,直接 fail fast。
- 如果 FileManager metadata 推导出的 file type 不符合 capability 的输入类型,直接 fail fast。
4.2 Job observation and cancellation
FileProcessing job 是统一 JobManager job。调用方通过通用 job snapshot / progress 观察状态, 不通过 FileProcessing service 推进 provider polling。
约束:
- JobManager dispatcher 推进 background / remote-poll handler。
- completed / failed / cancelled 是终态,重复查询返回同一终态快照,保留周期由统一 JobManager 规则决定。
- pending / delayed / running job cancel 后进入
cancelled。 - 本地 background execution 必须 abort。
- remote-poll handler 必须停止本地轮询。
- 第三方远程平台上的 provider task 只做 best effort,不承诺真正远程取消。
5. Job State Model
job 状态统一为:
pendingprocessingcompletedfailedcancelled
基础字段:
type FileProcessingJobBase = {
jobId: string
feature: FileProcessorFeature
processorId: FileProcessorId
status: FileProcessingJobStatus
progress: number
}
终态字段:
type FileProcessingJobCompletedResult = FileProcessingJobBase & {
status: 'completed'
progress: 100
artifact: FileProcessingArtifact
}
type FileProcessingJobFailedResult = FileProcessingJobBase & {
status: 'failed'
error: string
}
type FileProcessingJobCancelledResult = FileProcessingJobBase & {
status: 'cancelled'
reason?: string
}
实现要求:
progress统一 clamp 到 0-100 的整数。completed必须有 artifact。failed必须有非空 error。cancelled不应伪装成 failed。- provider-specific status 必须映射到以上统一状态。
6. Artifact Model
job 结果统一通过 artifact 表达,而不是为每个 feature 增加专用字段。
当前最小 artifact 类型(当前实现):
type FileProcessingArtifact =
| {
kind: 'text'
format: 'plain'
text: string
}
| {
kind: 'file'
format: 'markdown'
path: FilePath
}
当前 feature 到 artifact 的映射:
| Feature | Artifact |
|---|---|
image_to_text |
{ kind: 'text', format: 'plain', text } |
document_to_markdown |
{ kind: 'file', format: 'markdown', path } |
当前实现(supersedes 旧 FileEntry 落盘描述):file-processing 不再产出 managed / FileEntry artifact。 产出只有两种:caller 指定路径的 markdown(
output: { kind: 'path', path },写到 caller 给的库内路径)或 inline text(OCR)。 callerstartJob传file: FileHandle({ kind: 'path' }或{ kind: 'entry' })+ 可选output;产 markdown 的 feature 必须给 path output,产 text 的 feature 忽略 output。 不要为了"有 FileEntry 库"就把 markdown 再塞回 internal FileEntry——下游(知识库 / agent tool)要的是独立 path 产物。下文 §落盘语义里关于FileManager.createInternalEntry写 internal FileEntry 的描述已不适用。
设计取向:
- OCR 文本以内联 text artifact 返回,避免翻译场景还要额外读文件。
- Markdown 文档以 path artifact 返回,写到 caller 指定路径,由 caller 拥有该产物的生命周期。
- artifact 是统一结果容器,不等于所有结果都用同一种存储方式。
- 未来如果需要结构化 OCR、表格、图片资源或多文件输出,应扩展 artifact union,而不是把 provider-specific 字段塞进 job 顶层。
7. Service 分层
目标分层:
FileProcessingService- 生命周期 service
- 注册 IPC handler 和 JobManager handler
- 做 payload Zod 校验
- 解析 processor config、校验输入 file metadata
- 通过
JobManager.enqueue创建统一 job,不持有 job store
- JobManager file-processing handlers
tasks/backgroundJobHandler.ts执行本地 / 同步 capabilitytasks/remotePollJobHandler.ts执行远程 start / poll capability- handler 使用
recovery: 'retry' - remote-poll handler 通过 job metadata 持久化可恢复的 provider task state
- 产出统一 artifact
- Processor 层
- 以 processor 为第一层组织单元
- 按 capability feature 暴露 handler
- 不暴露 provider task id 给调用方
- 不依赖旧 knowledge preprocess service 或旧 OCR facade
- Processor-owned runtime 层
- 只在某个 processor 真的拥有生命周期资源时出现
- 承载 worker、队列、池、锁、idle release、stop / destroy cleanup 等 processor-owned runtime state
- 当前只有
tesseract需要 lifecycle runtime
JobManager / SQLite job table 是 job 状态的 source of truth。
FileProcessingService 只是对外入口,不应该重复维护 job 状态或实现 provider 细节。
7.1 Processor-first 目录结构
fileProcessing 内部目录应以 processor 为第一层组织轴心,而不是以 ocr / markdown feature 分类。
目标结构:
src/main/services/fileProcessing/
config/
persistence/
processors/
registry.ts
types.ts
tesseract/
index.ts
types.ts
image-to-text/
handler.ts
prepare.ts
__tests__/
runtime/
TesseractRuntimeService.ts
types.ts
__tests__/
paddleocr/
index.ts
types.ts
utils.ts
image-to-text/
handler.ts
document-to-markdown/
handler.ts
mineru/
document-to-markdown/
handler.ts
doc2x/
document-to-markdown/
handler.ts
mistral/
image-to-text/
handler.ts
system/
image-to-text/
handler.ts
ovocr/
image-to-text/
handler.ts
open-mineru/
document-to-markdown/
handler.ts
tasks/
utils/
目录规则:
- processor 目录名使用 processor id,例如
tesseract、paddleocr、open-mineru。 - feature 子目录使用 kebab-case,例如
image-to-text、document-to-markdown。 - shared feature enum 使用
image_to_text/document_to_markdown,目录名只是对应的 kebab-case 形式。 - 同一个 processor 的跨 feature 共享代码放在 processor 根目录,例如
processors/paddleocr/types.ts、processors/paddleocr/utils.ts。 - 只有跨多个 processor 都适用的 helper 才放在 file-processing 顶层
utils/。 - 重构时应一次性移除旧的顶层
ocr/、markdown/、runtime/services/结构,不保留长期桥接目录。
7.2 Processor Registry
processor handler 通过静态 registry 注册。
推荐 shape:
processorRegistry[processorId].capabilities[feature]
设计约束:
- registry 以 processor 为第一层 map,和目录结构一致。
- 不做目录自动扫描,避免 Electron / Vite 打包和类型推断变复杂。
- 不维护 processor map 和 feature map 两套 source of truth。
- 测试必须校验
PRESETS_FILE_PROCESSORS声明的 capability 与 registry handler 一致:- preset 有 capability,registry 必须有 handler
- registry 不应声明 preset 不支持的 capability
FileProcessingService/ job execution helper 解析 processor config 后,通过 registry 找到目标 capability handler。
7.3 Capability Handler Contract
processor module 对 job service 暴露 capability handler,而不是继续暴露 OcrProvider / MarkdownProvider 两套接口。
handler 使用 discriminated execution mode:
mode: 'background'mode: 'remote-poll'
handler 方法分层:
prepare(file, config, signal?)- 做 provider-specific fail-fast 校验
- 解析 processor options / capability config
- 返回后续执行需要的 prepared context
- background handler
execute(context, executionContext)- 用于本地 OCR、同步 API 或没有远程 job 查询模型的 processor
- remote-poll handler
startRemote(context)pollRemote(remoteContext)- 用于天然支持远程 start / query 的 processor
设计约束:
prepare不创建本地 job record;job record 由JobManager.enqueue创建。prepare可以在startJob期间 fail fast,例如缺 path、缺 API key、processor option 无效、file type 不匹配。- provider task id、query context、remote context 都只保存在 Main 进程内部 job record。
- handler 输出不直接作为 IPC result;job service 负责统一映射成 artifact。
- capability handler 不应持有跨 job 可变全局状态;需要生命周期状态时,交给 processor-owned runtime service。
8. Execution Model
统一 job API 不要求所有 processor 内部都变成同一种执行方式。
Job service 内部允许两类执行模式:
- background execution
- remote poll
8.1 background execution
适用于本地 OCR、同步 API 调用、或 processor 自身没有远程 job 查询模型的能力。
典型场景:
tesseract图片 OCRsystem图片 OCRovocr图片 OCRmistral图片 OCRopen-mineru这类由 Main 启动并等待的后台执行
行为:
startJob通过JobManager.enqueue创建本地 job record 后立即返回。- JobManager dispatcher 在后台执行 capability handler。
- handler 成功后由 file-processing task helper 转成 artifact。
- handler 抛错后 job 进入
failed。 - caller cancel 或 service stop 时 abort。
8.2 remote poll
适用于 processor 天然支持“启动远程任务 + 查询远程任务结果”的能力。
典型场景:
minerupaddleocr的文档解析能力doc2x
行为:
startJob创建本地jobId。- handler
startRemote返回内部 provider task id 和 query context。 - remote-poll handler 把 provider task id 的可恢复部分写入 job metadata。
- 调用方后续只用本地
jobId通过统一 Job API 查询。 - JobManager dispatcher 负责推进远程轮询并更新 job record / progress。
- 如果远程处理已完成但 artifact 下载或落盘失败,job 进入
failed;调用方可重新发起 job。
8.3 OCR job 化
即使图片 OCR 通常很快,也必须走统一 FileProcessingJob。
代价:
- 翻译 OCR 场景从直接 await 文本变成 start/query。
- Renderer 需要适配 job polling。
收益:
- OCR 和 Markdown 使用同一套状态、失败、取消、进度模型。
- 上层服务可以用同一种方式编排文件处理。
- 未来加入更慢的 OCR provider 时不需要再改对外契约。
9. Progress Observation
File-processing 不维护自己的 job event bus。
观察语义:
- job snapshot 由统一 Job API 查询。
- job progress 由 JobManager 写入
jobs.progress.${jobId}cache。 FileProcessingService不广播 Renderer IPC。- 本轮不设计 Renderer 订阅协议、多窗口广播或 UI job center。
如果后续需要实时 UI 推送,应复用统一 JobManager progress 机制或建立通用 job bridge,而不是为 file-processing 增加独立事件接口。
10. Data Ownership
file-processing 相关数据按职责分层:
- Processor preset
- 位于
src/shared/data/presets/file-processing.ts - 属于内建 shared metadata
- 不属于 DataApi / Cache / Preference 记录
- 位于
- 用户默认 processor 与 override
- 位于 Preference
- 当前键位继续使用:
feature.file_processing.default_document_to_markdownfeature.file_processing.default_image_to_textfeature.file_processing.overrides
- job 运行时状态
- job record 位于 JobManager / SQLite job table
- remote-poll 的可恢复 provider task state 位于 job metadata
- API key、token、abort controller、in-flight query、background execution 等只保留在 handler 执行期内存
- 不新增 file-processing DataApi endpoint,不镜像到 Cache / SharedCache
- 最终 file artifact
- 只保留最终 markdown 文件
- 通过
FileManager.createInternalEntry写入 internal FileEntry - 由 completed job artifact 返回
fileEntryId
DataApi 边界:
- file-processing job 使用统一 JobManager job table,不新增业务表。
- job state 是 runtime coordination state,不是 DataApi-backed business data。
- 因此不新增 file-processing DataApi endpoint。
Cache 边界:
- 不新增 shared cache job mirror。
- 不把 job progress 当跨窗口共享状态存 Cache。
- 如果上层业务需要聚合进度,应由上层业务维护自己的状态。
11. Job Recovery And Retention
file-processing job 使用统一 JobManager 的保留和恢复语义。
默认策略:
- completed / failed / cancelled 终态 job 按 JobManager 规则保留和查询。
- background handler 使用
recovery: 'retry',重启后从头重试当前 attempt。 - remote-poll handler 使用
recovery: 'retry',重启后从 job metadata 恢复 provider task id 和可持久 query state。 - API key、token、abort controller、in-flight query、background execution 等不写入 metadata。
最终 artifact 文件不会随 job 记录保留周期自动删除。artifact 生命周期由 feature 文件数据目录和上层业务清理策略决定。
12. Input Validation
FileProcessingService / job service 必须做基础准入校验。
基础校验包括:
- IPC payload 使用 Zod schema 校验。
feature必须是FILE_PROCESSOR_FEATURES中的值。processorId如果传入,必须是FILE_PROCESSOR_IDS中的值。file必须符合共享FileMetadataSchema。- processor 必须支持请求的 feature。
file.type必须匹配 capabilityinputs,例如:image_to_text接收imagedocument_to_markdown接收document
不在 facade 层做的校验:
- PDF、DOCX、PNG、JPG 等细分扩展名白名单。
- provider 特定模型限制。
- provider 特定 API key / api host / path 可用性。
- 远程服务是否真的支持某个文档格式。
这些细节由 provider 自己负责,并把错误映射为 failed job 或 startJob fail-fast。
13. Processor Boundary
file-processing processor 应在 src/main/services/fileProcessing/processors 内闭环。
允许复用:
- 通用底层工具,例如
loadOcrImage - 第三方 SDK / 原生库
- shared preset / preference 类型
application.getPath(...)- processor-owned lifecycle runtime service,例如
processors/tesseract/runtime/TesseractRuntimeService
不应依赖:
- 旧 knowledge preprocess service
- 旧
src/main/services/ocrfacade - Renderer store / Redux / Dexie / ElectronStore
- processor-specific 全局单例状态,除非有 lifecycle runtime service 管理
Processor handler 输出不直接返回给 IPC 调用方,而由 job service 统一转换成 artifact。
Processor 内部错误应尽量包含明确上下文,但不要把 secret、API key、token 写入错误或日志。
13.1 Runtime Ownership Criteria
runtime 不是 provider utils 的新名字。只有 processor 执行时需要长期持有、可复用、需要 lifecycle 清理的资源管理层,才应该建立 processor-owned runtime。
满足以下任意两条时,才考虑 runtime:
- 持有长寿命 worker、process、pool、connection 或模型加载状态。
- 需要 lifecycle
onStop/onDestroy清理。 - 有队列、池、锁或 idle release。
- 初始化成本高,需要跨 job 复用。
- job 取消不能只靠单个请求的
AbortSignal解决。
当前判断:
tesseract- 需要 runtime service。
- 原因是它持有
tesseract.jsworker、串行队列、language-key worker reuse、idle release 和 lifecycle cleanup。
ovocr- 暂时不建 runtime service。
- 它当前是一次性 child process execution,临时目录、脚本执行和结果解析留在
processors/ovocr/image-to-text内。 - 未来如果多个 processor 都需要外部进程生命周期管理,再迁入统一 process management。
open-mineru- 暂时不建 runtime service。
- 当前只调用已经运行的本地 HTTP service;除非未来由 Cherry 负责启动 / 停止 OpenMinerU 服务本身。
mineru、doc2x、paddleocr、mistral- 暂时不建 runtime service。
- 它们是远程 API processor。
system- 暂时不建 runtime service。
- 当前只调用系统 OCR API,不持有长寿命资源。
本轮不抽通用 ProcessManagerService 或 ProcessRunner。
原因:
- Tesseract worker、OV OCR script、OpenMinerU HTTP call 不是同一种 runtime。
- 当前没有足够重复的外部进程需求来支撑通用进程平台。
- 过早抽象会把 file-processing 重构扩大成基础设施工程。
- 如果未来出现多个外部二进制 / utility process processor,再把 process lifecycle 从 processor 内迁到统一 ProcessManager。
13.2 Tesseract Runtime Boundary
TesseractRuntimeService 应移动到 processors/tesseract/runtime/,并只暴露 runtime-level API。
推荐 public input:
type TesseractRuntimeInput = {
file: ImageFileMetadata
langs: LanguageCode[]
signal?: AbortSignal
}
边界:
processors/tesseract/image-to-text/prepare.ts负责从FileProcessorMerged解析 langs 和 options。TesseractRuntimeService不接收FileProcessorMerged,也不 import image-to-text handler 的 private types。TesseractRuntimeService可以保留图片大小校验和loadOcrImage,因为它们属于 worker 执行前的资源保护和输入加载。- runtime 继续保持当前行为:
- 单 shared worker
- 按 langs key 复用
PQueueconcurrency 1- idle release
- stop / destroy 时 abort pending work 并 terminate worker
- 不在本轮引入 language worker pool 或 per-task worker。
14. Result Persistence
Markdown conversion 的文件 artifact 继续由 Main 进程稳定落盘。
落盘规则:
- 最终只保留 markdown 文件,不保留 zip 内图片/附件目录。
- markdown 内容通过
FileManager.createInternalEntry({ source: 'bytes', ext: 'md' })写入 internal FileEntry。 - job output 只返回 processed artifact 的
fileEntryId。 - zip 结果只读取第一个 markdown entry,仍必须做 entry path 规范化和安全校验,防止 zip slip。
- 下载 zip 时只使用
application.getPath('feature.file_processing.temp')作为临时目录。
OCR text artifact 不落盘,直接以内联文本返回。
如果未来 text artifact 可能很大,再单独引入 size threshold 或 file artifact fallback;本轮不提前设计这个分支。
15. Lifecycle
服务选择:
FileProcessingService:生命周期 service,因为它注册 IPC handler。processors/tesseract/runtime/TesseractRuntimeService:继续作为生命周期 service,因为它管理长寿命 worker、队列和 idle release。- file-processing task handlers:普通 JobManager handler,不是 lifecycle service。
- processor helper / pure utility:保持普通函数或 direct-import singleton,不引入无意义 lifecycle 层。
依赖关系:
FileProcessingService依赖FileManager和JobManager。FileProcessingService.onInit注册 file-processing JobManager handlers。- Tesseract image-to-text handler 在执行时通过
application.get('TesseractRuntimeService')获取 runtime。 - 不需要声明对 BeforeReady 服务的 cross-phase
@DependsOn;Preference 等 BeforeReady 初始化顺序由 lifecycle 系统保证。
清理要求:
FileProcessingService停止时由 lifecycle 自动清理 IPC handler。- Job cancel / retry / timeout 由 JobManager 驱动。
- 长寿命 processor runtime 在自己的 lifecycle service 中清理资源。
16. Legacy And Scope
本轮 file-processing 重构不做以下事情:
- 不完成 Renderer 全量切流。
- 不删除旧
window.api.ocr。 - 不删除旧
src/main/services/ocr。 - 不把旧 OCR IPC 桥接到新 job API。
- 不建立统一 UI job center。
- 不新增 file-processing DataApi job table。
短期允许并存:
- 新 file-processing job API
- 旧 OCR renderer/main 调用链
- 旧 preprocess provider 残留代码
但是新 file-processing API 自身不保留旧接口包装。
后续 PR 应分别处理:
- Renderer / preload 对
startJob与通用 Job 观察/取消入口的正式接入。 - 翻译 OCR 从旧
window.api.ocr切到 file-processing job。 - 删除旧 OCR service 与旧 preprocess provider。
- 清理旧 i18n、设置页和 migration 中不再需要的兼容逻辑。
17. Feature Rename Implementation Notes
当前 feature 名应从旧的行为描述改成 I/O 描述:
| Old | New | Handler name | Directory |
|---|---|---|---|
text_extraction |
image_to_text |
imageToText |
image-to-text/ |
markdown_conversion |
document_to_markdown |
documentToMarkdown |
document-to-markdown/ |
命名理由:
text_extraction太宽,容易和 PDF 原生文本提取、Word 解析、任意文档读文本混淆。image_to_text明确表达输入是 image、输出是 text,不把 OCR 这个实现方式写进 feature 名。document_to_markdown明确表达输入是 document、输出是 markdown,比泛化的 conversion 更具体。- 两个 feature 都以 I/O 命名,不携带知识库或翻译业务语义。
实现时必须同步修改:
src/shared/data/preference/preferenceTypes.tsFILE_PROCESSOR_FEATURES改成['image_to_text', 'document_to_markdown']。
src/shared/data/presets/file-processing.ts- capability schema 从旧 literal 改成新 literal。
- preset capability 的
feature字段全部改名。 - capability override schema key 从旧 feature key 改成新 feature key。
- preference schema / default preference keys
- 默认 processor key 改成
feature.file_processing.default_image_to_text。 - 默认 processor key 改成
feature.file_processing.default_document_to_markdown。 feature.file_processing.overrides内 capability override key 使用新 feature 名。
- 默认 processor key 改成
v2-refactor-temp/tools/data-classify/data/classification.json- 更新目标 key,之后通过 data-classify toolchain 重新生成 preference schema 和 mapping。
- v2 migration mappings / tests
- 更新 file-processing override merge 中的 feature 名。
- 更新 default processor mapping 的 target key。
- 更新相关单测断言。
- file-processing service / processor code
- resolver、registry、job payload schema、capability handler、tests 全部使用新 feature 名。
本轮不考虑旧数据兼容性:
- 不保留旧 preference key 到新 preference key 的 runtime fallback。
- 不在 service 里接受旧 feature 名 alias。
- 不在 override 读取时兼容旧
text_extraction/markdown_conversioncapability key。 - migration / data-classify 只需要生成新 schema 和新目标 key,不需要为旧 v2 中间数据做兼容迁移。
- 如果本分支已有旧名写入的开发期数据,可以直接清理或重新迁移;这属于 v2 开发期 schema drift。
18. Testing Baseline
共享类型 / schema 测试:
startJobpayload 校验- 通用 Job snapshot / progress 观察接入
- 通用 job cancel 接入
FileProcessingJobOutputartifact schemaFileProcessingArtifactdiscriminated union
Job service 测试:
- 启动
image_to_textjob 并返回 text artifact。 - 启动
document_to_markdownjob 并返回 markdown file artifact。 - remote-poll job 并发查询 dedupe。
- background job progress 更新。
- provider 抛错后进入 failed。
- cancel pending / processing job 后进入 cancelled。
- cancel completed job 保持 completed。
- 缺默认 processor 时 fail fast。
- processor 不支持 feature 时 fail fast。
- file type 不匹配 capability inputs 时 fail fast。
- background handler 在重试时重新执行 capability。
- remote-poll handler 可以从 metadata 恢复 provider task state。
Registry 测试:
- 每个 preset capability 都有对应 registry handler。
- registry 不声明 preset 不支持的 capability。
processorRegistry[processorId].capabilities[feature]可以被 job service 按 processor + feature 找到。
Persistence 测试:
- markdown content 写入 internal FileEntry 并返回
fileEntryIdartifact。 - zip result 只读取第一个 markdown entry 并写入 internal FileEntry。
- unsafe zip entry 被拒绝。
- zip 下载临时目录完成后清理。
Processor 测试:
- processor-specific schema / request / result parsing 保持单测覆盖。
- processor feature handler 不测试 job store 细节。
- job service 不测试第三方真实网络。
- processor-specific 测试贴近实现目录,例如
processors/tesseract/runtime/__tests__、processors/paddleocr/image-to-text/__tests__。 - Tesseract runtime 测试继续覆盖 lifecycle phase、worker reuse、queued work、stop / destroy cleanup、idle release、stop 后拒绝新 job。
完成实现前必须运行:
pnpm lintpnpm testpnpm format
19. Accepted Trade-offs
统一 job API 的代价:
- 快速 OCR 也需要 start/query。
- 翻译页需要适配轮询或上层 await helper。
- 类型比原来的
extractText -> { text }更复杂。
接受这些代价的原因:
- OCR 和 Markdown 共享进度、失败、取消和终态模型。
- 慢 OCR provider 不需要未来再破坏接口。
- 上层服务可以用统一方式编排文件处理。
统一 artifact 模型的代价:
- 调用方需要 inspect
artifact.kind和artifact.format。 - 文本和文件仍然有不同存储策略。
接受这些代价的原因:
- 未来可以自然扩展多 artifact 输出。
- 不需要在 job result 顶层不断增加 feature-specific 字段。
- OCR 保持内联文本的消费效率,Markdown 保持落盘文件的稳定性。
JobManager-backed job 状态的代价:
- file-processing 必须遵循统一 JobManager 的 retry / retention 语义。
- 多窗口实时进度仍依赖统一 job progress 观察能力。
接受这些代价的原因:
- 当前 job state 是 runtime coordination state,不是 file-processing 自有 business data。
- 结果 artifact 已经稳定落盘。
- 避免引入独立 file-processing job store 或 DataApi / Cache 双 source of truth。
20. Review Baseline
评审这次重构时,应以本文作为目标契约。
重点关注:
- 是否真正形成统一
FileProcessingJobAPI。 - 是否避免把知识库 / 翻译业务语义塞进 file-processing。
- 是否正确隐藏 provider task id 和 query context。
- 是否用 artifact 统一终态结果。
- 是否有清晰取消语义。
- 是否把 job state 收口到统一 JobManager,而不是建立 file-processing 自有 store。
- 是否使用统一 job progress 观察能力,而没有过早设计 Renderer broadcast 协议。
不应作为 blocker 的事项:
- Renderer 尚未切到新 job API。
- 旧 OCR service 尚未删除。
- facade 没有维护具体扩展名白名单。