v2-refactor-temp/docs/fileProcessing/file-processing-service.md
这份文档是 src/main/services/fileProcessing 下一轮重构的设计基线。
当前代码已经有一版 Main-side file-processing service,但它不是定稿。后续实现可以围绕本文重新组织接口、job 模型和内部服务边界,不需要维护旧的 split API 作为兼容目标。
本文覆盖:
本文不直接描述 UI 交互,也不要求立刻完成 Renderer 切流。
file-processing 是 Main 进程里的内容提取 / 内容转换能力模块。
当前明确支持两类使用场景:
这两个场景都应该收口到同一套底层能力,但 file-processing 本身不应该理解知识库或翻译业务。
换句话说:
file-processing 负责把输入文件处理成可消费的结果 artifact。KnowledgeService 或其他上层 service 负责决定何时处理、如何展示进度、如何入库、如何切 chunk、如何做 embedding。因此,底层接口不使用 preprocessKnowledgeFile、translateOcr 这类业务命名,而使用 startJob 与通用 Job 能力命名。
| 术语 | 含义 |
|---|---|
| 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。统一对外能力面:
startJob({ feature, fileEntryId, processorId? }): Promise<JobSnapshot>jobs.progress.${jobId} cache。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。
startJob 接收:
feature: image_to_text 或 document_to_markdownfileEntryId: 已登记的 FileManager entry idprocessorId: 可选;未传时按 feature 读取默认 processor preferencestartJob 返回统一 Job snapshot:
type StartFileProcessingJobResult = JobSnapshot
约束:
jobId。processorId,且对应 feature 没有配置默认 processor,直接 fail fast。FileProcessing job 是统一 JobManager job。调用方通过通用 job snapshot / progress 观察状态, 不通过 FileProcessing service 推进 provider polling。
约束:
cancelled。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。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 的描述已不适用。
设计取向:
目标分层:
FileProcessingService
JobManager.enqueue 创建统一 job,不持有 job storetasks/backgroundJobHandler.ts 执行本地 / 同步 capabilitytasks/remotePollJobHandler.ts 执行远程 start / poll capabilityrecovery: 'retry'tesseract 需要 lifecycle runtimeJobManager / SQLite job table 是 job 状态的 source of truth。
FileProcessingService 只是对外入口,不应该重复维护 job 状态或实现 provider 细节。
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/
目录规则:
tesseract、paddleocr、open-mineru。image-to-text、document-to-markdown。image_to_text / document_to_markdown,目录名只是对应的 kebab-case 形式。processors/paddleocr/types.ts、processors/paddleocr/utils.ts。utils/。ocr/、markdown/、runtime/services/ 结构,不保留长期桥接目录。processor handler 通过静态 registry 注册。
推荐 shape:
processorRegistry[processorId].capabilities[feature]
设计约束:
PRESETS_FILE_PROCESSORS 声明的 capability 与 registry handler 一致:
FileProcessingService / job execution helper 解析 processor config 后,通过 registry 找到目标 capability handler。processor module 对 job service 暴露 capability handler,而不是继续暴露 OcrProvider / MarkdownProvider 两套接口。
handler 使用 discriminated execution mode:
mode: 'background'mode: 'remote-poll'handler 方法分层:
prepare(file, config, signal?)
execute(context, executionContext)startRemote(context)pollRemote(remoteContext)设计约束:
prepare 不创建本地 job record;job record 由 JobManager.enqueue 创建。prepare 可以在 startJob 期间 fail fast,例如缺 path、缺 API key、processor option 无效、file type 不匹配。统一 job API 不要求所有 processor 内部都变成同一种执行方式。
Job service 内部允许两类执行模式:
适用于本地 OCR、同步 API 调用、或 processor 自身没有远程 job 查询模型的能力。
典型场景:
tesseract 图片 OCRsystem 图片 OCRovocr 图片 OCRmistral 图片 OCRopen-mineru 这类由 Main 启动并等待的后台执行行为:
startJob 通过 JobManager.enqueue 创建本地 job record 后立即返回。failed。适用于 processor 天然支持“启动远程任务 + 查询远程任务结果”的能力。
典型场景:
minerupaddleocr 的文档解析能力doc2x行为:
startJob 创建本地 jobId。startRemote 返回内部 provider task id 和 query context。jobId 通过统一 Job API 查询。failed;调用方可重新发起 job。即使图片 OCR 通常很快,也必须走统一 FileProcessingJob。
代价:
收益:
File-processing 不维护自己的 job event bus。
观察语义:
jobs.progress.${jobId} cache。FileProcessingService 不广播 Renderer IPC。如果后续需要实时 UI 推送,应复用统一 JobManager progress 机制或建立通用 job bridge,而不是为 file-processing 增加独立事件接口。
file-processing 相关数据按职责分层:
src/shared/data/presets/file-processing.tsfeature.file_processing.default_document_to_markdownfeature.file_processing.default_image_to_textfeature.file_processing.overridesFileManager.createInternalEntry 写入 internal FileEntryfileEntryIdDataApi 边界:
Cache 边界:
file-processing job 使用统一 JobManager 的保留和恢复语义。
默认策略:
recovery: 'retry',重启后从头重试当前 attempt。recovery: 'retry',重启后从 job metadata 恢复 provider task id 和可持久 query state。最终 artifact 文件不会随 job 记录保留周期自动删除。artifact 生命周期由 feature 文件数据目录和上层业务清理策略决定。
FileProcessingService / job service 必须做基础准入校验。
基础校验包括:
feature 必须是 FILE_PROCESSOR_FEATURES 中的值。processorId 如果传入,必须是 FILE_PROCESSOR_IDS 中的值。file 必须符合共享 FileMetadataSchema。file.type 必须匹配 capability inputs,例如:
image_to_text 接收 imagedocument_to_markdown 接收 document不在 facade 层做的校验:
这些细节由 provider 自己负责,并把错误映射为 failed job 或 startJob fail-fast。
file-processing processor 应在 src/main/services/fileProcessing/processors 内闭环。
允许复用:
loadOcrImageapplication.getPath(...)processors/tesseract/runtime/TesseractRuntimeService不应依赖:
src/main/services/ocr facadeProcessor handler 输出不直接返回给 IPC 调用方,而由 job service 统一转换成 artifact。
Processor 内部错误应尽量包含明确上下文,但不要把 secret、API key、token 写入错误或日志。
runtime 不是 provider utils 的新名字。只有 processor 执行时需要长期持有、可复用、需要 lifecycle 清理的资源管理层,才应该建立 processor-owned runtime。
满足以下任意两条时,才考虑 runtime:
onStop / onDestroy 清理。AbortSignal 解决。当前判断:
tesseract
tesseract.js worker、串行队列、language-key worker reuse、idle release 和 lifecycle cleanup。ovocr
processors/ovocr/image-to-text 内。open-mineru
mineru、doc2x、paddleocr、mistral
system
本轮不抽通用 ProcessManagerService 或 ProcessRunner。
原因:
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 执行前的资源保护和输入加载。PQueue concurrency 1Markdown conversion 的文件 artifact 继续由 Main 进程稳定落盘。
落盘规则:
FileManager.createInternalEntry({ source: 'bytes', ext: 'md' }) 写入 internal FileEntry。fileEntryId。application.getPath('feature.file_processing.temp') 作为临时目录。OCR text artifact 不落盘,直接以内联文本返回。
如果未来 text artifact 可能很大,再单独引入 size threshold 或 file artifact fallback;本轮不提前设计这个分支。
服务选择:
FileProcessingService:生命周期 service,因为它注册 IPC handler。processors/tesseract/runtime/TesseractRuntimeService:继续作为生命周期 service,因为它管理长寿命 worker、队列和 idle release。依赖关系:
FileProcessingService 依赖 FileManager 和 JobManager。FileProcessingService.onInit 注册 file-processing JobManager handlers。application.get('TesseractRuntimeService') 获取 runtime。@DependsOn;Preference 等 BeforeReady 初始化顺序由 lifecycle 系统保证。清理要求:
FileProcessingService 停止时由 lifecycle 自动清理 IPC handler。本轮 file-processing 重构不做以下事情:
window.api.ocr。src/main/services/ocr。短期允许并存:
但是新 file-processing API 自身不保留旧接口包装。
后续 PR 应分别处理:
startJob 与通用 Job 观察/取消入口的正式接入。window.api.ocr 切到 file-processing job。当前 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 更具体。实现时必须同步修改:
src/shared/data/preference/preferenceTypes.ts
FILE_PROCESSOR_FEATURES 改成 ['image_to_text', 'document_to_markdown']。src/shared/data/presets/file-processing.ts
feature 字段全部改名。feature.file_processing.default_image_to_text。feature.file_processing.default_document_to_markdown。feature.file_processing.overrides 内 capability override key 使用新 feature 名。v2-refactor-temp/tools/data-classify/data/classification.json
本轮不考虑旧数据兼容性:
text_extraction / markdown_conversion capability key。共享类型 / schema 测试:
startJob payload 校验FileProcessingJobOutput artifact schemaFileProcessingArtifact discriminated unionJob service 测试:
image_to_text job 并返回 text artifact。document_to_markdown job 并返回 markdown file artifact。Registry 测试:
processorRegistry[processorId].capabilities[feature] 可以被 job service 按 processor + feature 找到。Persistence 测试:
fileEntryId artifact。Processor 测试:
processors/tesseract/runtime/__tests__、processors/paddleocr/image-to-text/__tests__。完成实现前必须运行:
pnpm lintpnpm testpnpm format统一 job API 的代价:
extractText -> { text } 更复杂。接受这些代价的原因:
统一 artifact 模型的代价:
artifact.kind 和 artifact.format。接受这些代价的原因:
JobManager-backed job 状态的代价:
接受这些代价的原因:
评审这次重构时,应以本文作为目标契约。
重点关注:
FileProcessingJob API。不应作为 blocker 的事项: