From e0781e1bb097b9637edce9ccc2ec2fa084068ed8 Mon Sep 17 00:00:00 2001 From: icarus Date: Mon, 20 Oct 2025 20:39:24 +0800 Subject: [PATCH] refactor(ocr): restructure ocr types into modular files for better maintainability - Split monolithic ocr.ts into separate files for base types, providers, models, and layers (api, data, business) - Update related imports and references across the codebase - Rename API request/response types to be more consistent (Patch->Update, Put->Replace) - Adjust repository and service implementations to match new type structure --- packages/shared/data/api/apiSchemas.ts | 16 +- .../repositories/OcrProviderRepository.ts | 44 +-- src/main/services/ocr/OcrService.ts | 46 +-- src/renderer/src/types/ocr/api.ts | 52 +++ src/renderer/src/types/ocr/base.ts | 20 + src/renderer/src/types/ocr/business.ts | 31 ++ src/renderer/src/types/ocr/data.ts | 22 ++ src/renderer/src/types/ocr/index.ts | 7 +- src/renderer/src/types/ocr/model.ts | 17 + src/renderer/src/types/ocr/ocr.ts | 373 ------------------ src/renderer/src/types/ocr/provider/base.ts | 118 ++++++ src/renderer/src/types/ocr/provider/index.ts | 5 + src/renderer/src/types/ocr/provider/ov.ts | 29 ++ src/renderer/src/types/ocr/provider/paddle.ts | 29 ++ src/renderer/src/types/ocr/provider/system.ts | 25 ++ .../src/types/ocr/provider/tesseract.ts | 31 ++ tsconfig.node.json | 2 +- 17 files changed, 435 insertions(+), 432 deletions(-) create mode 100644 src/renderer/src/types/ocr/api.ts create mode 100644 src/renderer/src/types/ocr/base.ts create mode 100644 src/renderer/src/types/ocr/business.ts create mode 100644 src/renderer/src/types/ocr/data.ts create mode 100644 src/renderer/src/types/ocr/model.ts delete mode 100644 src/renderer/src/types/ocr/ocr.ts create mode 100644 src/renderer/src/types/ocr/provider/base.ts create mode 100644 src/renderer/src/types/ocr/provider/index.ts create mode 100644 src/renderer/src/types/ocr/provider/ov.ts create mode 100644 src/renderer/src/types/ocr/provider/paddle.ts create mode 100644 src/renderer/src/types/ocr/provider/system.ts create mode 100644 src/renderer/src/types/ocr/provider/tesseract.ts diff --git a/packages/shared/data/api/apiSchemas.ts b/packages/shared/data/api/apiSchemas.ts index e92a5756f8..3aa0941014 100644 --- a/packages/shared/data/api/apiSchemas.ts +++ b/packages/shared/data/api/apiSchemas.ts @@ -7,10 +7,10 @@ import type { ListOcrProvidersQuery, ListOcrProvidersResponse, OcrProviderId, - PatchOcrProviderRequest, - PatchOcrProviderResponse, - PutOcrProviderRequest, - PutOcrProviderResponse + ReplaceOcrProviderRequest, + ReplaceOcrProviderResponse, + UpdateOcrProviderRequest, + UpdateOcrProviderResponse } from '@types' import type { BodyForPath, ConcreteApiPaths, QueryParamsForPath, ResponseForPath } from './apiPaths' @@ -377,13 +377,13 @@ export interface ApiSchemas { } PATCH: { params: { id: OcrProviderId } - body: PatchOcrProviderRequest - response: PatchOcrProviderResponse + body: UpdateOcrProviderRequest + response: UpdateOcrProviderResponse } PUT: { params: { id: OcrProviderId } - body: PutOcrProviderRequest - response: PutOcrProviderResponse + body: ReplaceOcrProviderRequest + response: ReplaceOcrProviderResponse } DELETE: { params: { id: OcrProviderId } diff --git a/src/main/data/repositories/OcrProviderRepository.ts b/src/main/data/repositories/OcrProviderRepository.ts index df1abd515e..45d49e010f 100644 --- a/src/main/data/repositories/OcrProviderRepository.ts +++ b/src/main/data/repositories/OcrProviderRepository.ts @@ -2,16 +2,12 @@ import { dbService } from '@data/db/DbService' import { ocrProviderTable } from '@data/db/schemas/ocr/provider' import { loggerService } from '@logger' import type { - CreateOcrProviderRequest, - CreateOcrProviderResponse, DbOcrProvider, - ListOcrProvidersQuery, - ListOcrProvidersResponse, + DbOcrProviderCreate, + DbOcrProviderReplace, + DbOcrProviderUpdate, OcrProviderId, - PatchOcrProviderRequest, - PatchOcrProviderResponse, - PutOcrProviderRequest, - PutOcrProviderResponse + UpdateOcrProviderResponse } from '@types' import { BuiltinOcrProviderIds, isDbOcrProvider } from '@types' import dayjs from 'dayjs' @@ -28,17 +24,11 @@ export class OcrProviderRepository { /** * Get all OCR providers */ - public async findAll(query?: ListOcrProvidersQuery): Promise { + public async findAll(): Promise { try { const providers = await dbService.getDb().select().from(ocrProviderTable) - if (query?.registered) { - // Filter by registered providers (this would need to be implemented) - // For now, return all providers - return { data: providers } - } - - return { data: providers } + return providers } catch (error) { logger.error('Failed to find all OCR providers', error as Error) throw error @@ -90,16 +80,16 @@ export class OcrProviderRepository { /** * Create new OCR provider */ - public async create(data: CreateOcrProviderRequest): Promise { + public async create(param: DbOcrProviderCreate): Promise { try { // Check if provider already exists - if (await this.exists(data.id)) { - throw new Error(`OCR provider ${data.id} already exists`) + if (await this.exists(param.id)) { + throw new Error(`OCR provider ${param.id} already exists`) } const timestamp = dayjs().valueOf() const newProvider = { - ...data, + ...param, createdAt: timestamp, updatedAt: timestamp } satisfies DbOcrProvider @@ -111,10 +101,10 @@ export class OcrProviderRepository { const [created] = await dbService.getDb().insert(ocrProviderTable).values(newProvider).returning() - logger.info(`Created OCR provider: ${data.id}`) - return { data: created } + logger.info(`Created OCR provider: ${param.id}`) + return created } catch (error) { - logger.error(`Failed to create OCR provider ${data.id}`, error as Error) + logger.error(`Failed to create OCR provider ${param.id}`, error as Error) throw error } } @@ -122,12 +112,12 @@ export class OcrProviderRepository { /** * Update OCR provider (partial update) */ - public async update(id: OcrProviderId, data: Partial): Promise { + public async update(id: OcrProviderId, update: DbOcrProviderUpdate): Promise { try { const existing = await this.findById(id) const newProvider = { - ...merge({}, existing, data), + ...merge({}, existing, update), updatedAt: dayjs().valueOf() } satisfies DbOcrProvider @@ -154,7 +144,7 @@ export class OcrProviderRepository { /** * Replace OCR provider (full update) */ - public async replace(data: PutOcrProviderRequest): Promise { + public async replace(data: DbOcrProviderReplace): Promise { try { // Check if it's a built-in provider if (BuiltinOcrProviderIds.some((pid) => pid === data.id)) { @@ -199,7 +189,7 @@ export class OcrProviderRepository { .returning() logger.info(`Replaced OCR provider: ${data.id}`) - return { data: saved } + return saved } catch (error) { logger.error(`Failed to replace OCR provider ${data.id}`, error as Error) throw error diff --git a/src/main/services/ocr/OcrService.ts b/src/main/services/ocr/OcrService.ts index 00bf4093a5..a2efd500a3 100644 --- a/src/main/services/ocr/OcrService.ts +++ b/src/main/services/ocr/OcrService.ts @@ -4,17 +4,18 @@ import type { CreateOcrProviderRequest, CreateOcrProviderResponse, DbOcrProvider, + GetOcrProviderResponse, ListOcrProvidersQuery, ListOcrProvidersResponse, OcrParams, OcrProvider, - OcrProviderId, + OcrProviderKeyBusiness, OcrResult, - PatchOcrProviderRequest, - PatchOcrProviderResponse, - PutOcrProviderRequest, - PutOcrProviderResponse, - SupportedOcrFile + ReplaceOcrProviderRequest, + ReplaceOcrProviderResponse, + SupportedOcrFile, + UpdateOcrProviderRequest, + UpdateOcrProviderResponse } from '@types' import { BuiltinOcrProviderIdMap } from '@types' @@ -31,7 +32,7 @@ const logger = loggerService.withContext('OcrService') * Handles OCR provider registration, orchestration, and core OCR functionality */ class OcrService { - private registry: Map = new Map() + private registry: Map = new Map() private initialized: boolean = false constructor() { @@ -83,7 +84,7 @@ class OcrService { /** * Register an OCR provider service */ - private register(providerId: OcrProviderId, service: OcrBaseService): void { + private register(providerId: OcrProviderKeyBusiness, service: OcrBaseService): void { if (this.registry.has(providerId)) { logger.warn(`Provider ${providerId} already registered. Overwriting.`) } @@ -104,14 +105,14 @@ class OcrService { /** * Get all registered provider IDs */ - public getRegisteredProviderIds(): OcrProviderId[] { + public getRegisteredProviderIds(): OcrProviderKeyBusiness[] { return Array.from(this.registry.keys()) } /** * Check if a provider is registered */ - public isProviderRegistered(providerId: OcrProviderId): boolean { + public isProviderRegistered(providerId: OcrProviderKeyBusiness): boolean { return this.registry.has(providerId) } @@ -121,16 +122,17 @@ class OcrService { public async listProviders(query?: ListOcrProvidersQuery): Promise { try { await this.ensureInitialized() - const result = await ocrProviderRepository.findAll(query) + const providers = await ocrProviderRepository.findAll() + let result = providers if (query?.registered) { // Filter by registered providers const registeredIds = this.getRegisteredProviderIds() - result.data = result.data.filter((provider) => registeredIds.includes(provider.id)) + result = providers.filter((provider) => registeredIds.includes(provider.id)) } - logger.debug(`Listed ${result.data.length} OCR providers`) - return result + logger.debug(`Listed ${result.length} OCR providers`) + return { data: result } } catch (error) { logger.error('Failed to list OCR providers', error as Error) throw error @@ -140,7 +142,7 @@ class OcrService { /** * Get OCR provider by ID */ - public async getProvider(providerId: OcrProviderId): Promise<{ data: DbOcrProvider }> { + public async getProvider(providerId: OcrProviderKeyBusiness): Promise { try { await this.ensureInitialized() const provider = await ocrProviderRepository.findById(providerId) @@ -160,7 +162,7 @@ class OcrService { await this.ensureInitialized() const result = await ocrProviderRepository.create(data) logger.info(`Created OCR provider: ${data.id}`) - return result + return { data: result } } catch (error) { logger.error(`Failed to create OCR provider ${data.id}`, error as Error) throw error @@ -171,9 +173,9 @@ class OcrService { * Update OCR provider (partial update) */ public async updateProvider( - id: OcrProviderId, - data: Partial - ): Promise { + id: OcrProviderKeyBusiness, + data: UpdateOcrProviderRequest + ): Promise { try { await this.ensureInitialized() const result = await ocrProviderRepository.update(id, data) @@ -188,7 +190,7 @@ class OcrService { /** * Replace OCR provider (full update) */ - public async replaceProvider(data: PutOcrProviderRequest): Promise { + public async replaceProvider(data: ReplaceOcrProviderRequest): Promise { try { await this.ensureInitialized() const result = await ocrProviderRepository.replace(data) @@ -203,7 +205,7 @@ class OcrService { /** * Delete OCR provider */ - public async deleteProvider(id: OcrProviderId): Promise { + public async deleteProvider(id: OcrProviderKeyBusiness): Promise { try { await this.ensureInitialized() await ocrProviderRepository.delete(id) @@ -242,7 +244,7 @@ class OcrService { /** * Check if a provider is available and ready */ - public async isProviderAvailable(providerId: OcrProviderId): Promise { + public async isProviderAvailable(providerId: OcrProviderKeyBusiness): Promise { try { const service = this.registry.get(providerId) if (!service) { diff --git a/src/renderer/src/types/ocr/api.ts b/src/renderer/src/types/ocr/api.ts new file mode 100644 index 0000000000..8c65957cc0 --- /dev/null +++ b/src/renderer/src/types/ocr/api.ts @@ -0,0 +1,52 @@ +import * as z from 'zod' + +import { DbOcrProviderSchema } from './data' +import { OcrProviderSchema } from './provider/base' +import { OcrProviderNameSchema } from './provider/base' +import { OcrProviderIdSchema } from './provider/base' +import { OcrProviderConfigSchema } from './provider/base' + +// ========================================================== +// API layer Types +// ========================================================== +export const TimestampExtendShape = { + createdAt: z.number().nullable(), + updatedAt: z.number().nullable() +} +export type ListOcrProvidersQuery = { registered?: boolean } +export const ListOcrProvidersResponseSchema = z.object({ + data: z.array(DbOcrProviderSchema) +}) +export type ListOcrProvidersResponse = z.infer +export const GetOcrProviderResponseSchema = z.object({ + data: DbOcrProviderSchema +}) +export type GetOcrProviderResponse = z.infer /** + * Request payload for updating an OCR provider. + * Only the following fields are modifiable: + * - `name`: provider display name + * - `config`: provider-specific configuration object (all properties optional) + */ + +export const UpdateOcrProviderRequestSchema = z.object({ + id: OcrProviderIdSchema, + name: OcrProviderNameSchema.optional(), + config: OcrProviderConfigSchema.partial().optional() +}) +export type UpdateOcrProviderRequest = z.infer +export const UpdateOcrProviderResponseSchema = z.object({ + data: DbOcrProviderSchema +}) +export type UpdateOcrProviderResponse = z.infer +export const CreateOcrProviderRequestSchema = OcrProviderSchema +export type CreateOcrProviderRequest = z.infer +export const CreateOcrProviderResponseSchema = z.object({ + data: DbOcrProviderSchema +}) +export type CreateOcrProviderResponse = z.infer +export const ReplaceOcrProviderRequestSchema = OcrProviderSchema +export type ReplaceOcrProviderRequest = z.infer +export const ReplaceOcrProviderResponseSchema = z.object({ + data: DbOcrProviderSchema +}) +export type ReplaceOcrProviderResponse = z.infer diff --git a/src/renderer/src/types/ocr/base.ts b/src/renderer/src/types/ocr/base.ts new file mode 100644 index 0000000000..020d160e7b --- /dev/null +++ b/src/renderer/src/types/ocr/base.ts @@ -0,0 +1,20 @@ +import type { FileMetadata, ImageFileMetadata } from '..' +import { isImageFileMetadata } from '..' + +export type SupportedOcrFile = ImageFileMetadata + +export const isSupportedOcrFile = (file: FileMetadata): file is SupportedOcrFile => { + return isImageFileMetadata(file) +} + +export type OcrParams = { + providerId: string +} + +export type OcrResult = { + text: string +} + +export type OcrHandler = (file: SupportedOcrFile) => Promise + +export type OcrImageHandler = (file: ImageFileMetadata) => Promise diff --git a/src/renderer/src/types/ocr/business.ts b/src/renderer/src/types/ocr/business.ts new file mode 100644 index 0000000000..2e9e168f5c --- /dev/null +++ b/src/renderer/src/types/ocr/business.ts @@ -0,0 +1,31 @@ +import type { DbOcrProviderCreate } from './data' +import type { DbOcrProviderUpdate } from './data' +import type { DbOcrProviderReplace } from './data' +import type { DbOcrProviderKey } from './data' +import type { DbOcrProvider } from './data' + +// ========================================================== +// Business layer Types +// ========================================================== +/** + * Business-level representation of an OCR provider. + * Mirrors the data layer but is intended for use in domain/business logic. + */ + +export type OcrProviderBusiness = DbOcrProvider /** + * Business-level representation of an OCR provider creation payload. + */ + +export type OcrProviderCreateBusiness = DbOcrProviderCreate /** + * Business-level representation of an OCR provider update payload. + */ + +export type OcrProviderUpdateBusiness = DbOcrProviderUpdate /** + * Business-level representation of an OCR provider replacement payload. + */ + +export type OcrProviderReplaceBusiness = DbOcrProviderReplace /** + * Business-level key type for identifying an OCR provider. + */ + +export type OcrProviderKeyBusiness = DbOcrProviderKey diff --git a/src/renderer/src/types/ocr/data.ts b/src/renderer/src/types/ocr/data.ts new file mode 100644 index 0000000000..09d9effe41 --- /dev/null +++ b/src/renderer/src/types/ocr/data.ts @@ -0,0 +1,22 @@ +import type * as z from 'zod' + +import { TimestampExtendShape, type UpdateOcrProviderRequest } from './api' +import { type OcrProvider } from './provider/base' +import { OcrProviderSchema } from './provider/base' + +// ========================================================== +// Data layer Types +// +// NOTE: Timestamp operations are not exposed to outside. +// ========================================================== + +export const DbOcrProviderSchema = OcrProviderSchema.extend(TimestampExtendShape) +export type DbOcrProvider = z.infer +export function isDbOcrProvider(p: unknown): p is DbOcrProvider { + return DbOcrProviderSchema.safeParse(p).success +} + +export type DbOcrProviderCreate = OcrProvider +export type DbOcrProviderUpdate = UpdateOcrProviderRequest +export type DbOcrProviderReplace = OcrProvider +export type DbOcrProviderKey = DbOcrProvider['id'] diff --git a/src/renderer/src/types/ocr/index.ts b/src/renderer/src/types/ocr/index.ts index 2fb43eccb6..7f1f2863d9 100644 --- a/src/renderer/src/types/ocr/index.ts +++ b/src/renderer/src/types/ocr/index.ts @@ -1 +1,6 @@ -export * from './ocr' +export * from './api' +export * from './base' +export * from './business' +export * from './data' +export * from './model' +export * from './provider' diff --git a/src/renderer/src/types/ocr/model.ts b/src/renderer/src/types/ocr/model.ts new file mode 100644 index 0000000000..bd10ce0048 --- /dev/null +++ b/src/renderer/src/types/ocr/model.ts @@ -0,0 +1,17 @@ +import * as z from 'zod' + +import type { OcrProviderCapabilityRecord } from './provider/base' +import { OcrProviderCapabilityRecordSchema } from './provider/base' + +// OCR models and providers share the same type definition. +// A provider can offer capabilities to process multiple file types, +// while a model belonging to that provider may be limited to processing only one specific file type. + +export type OcrModelCapabilityRecord = OcrProviderCapabilityRecord +export const OcrModelSchema = z.object({ + id: z.string(), + name: z.string(), + providerId: z.string(), + capabilities: OcrProviderCapabilityRecordSchema +}) +export type OcrModel = z.infer diff --git a/src/renderer/src/types/ocr/ocr.ts b/src/renderer/src/types/ocr/ocr.ts deleted file mode 100644 index 15cfb67838..0000000000 --- a/src/renderer/src/types/ocr/ocr.ts +++ /dev/null @@ -1,373 +0,0 @@ -import type Tesseract from 'tesseract.js' -import * as z from 'zod' - -import type { FileMetadata, ImageFileMetadata, TranslateLanguageCode } from '..' -import { isImageFileMetadata, objectValues, TranslateLanguageCodeSchema } from '..' - -export const BuiltinOcrProviderIdMap = { - tesseract: 'tesseract', - system: 'system', - paddleocr: 'paddleocr', - ovocr: 'ovocr' -} as const satisfies Record - -export const BuiltinOcrProviderIds = Object.freeze(objectValues(BuiltinOcrProviderIdMap)) - -export const BuiltinOcrProviderIdSchema = z.enum(['tesseract', 'system', 'paddleocr', 'ovocr']) - -export type BuiltinOcrProviderId = z.infer - -export const isBuiltinOcrProviderId = (id: string): id is BuiltinOcrProviderId => { - return BuiltinOcrProviderIdSchema.safeParse(id).success -} - -// extensible -export const OcrProviderCapabilities = { - image: 'image' - // pdf: 'pdf' -} as const satisfies Record - -export const OcrProviderCapabilitySchema = z.enum(['image']) - -export type OcrProviderCapability = z.infer - -export const isOcrProviderCapability = (cap: string): cap is OcrProviderCapability => { - return OcrProviderCapabilitySchema.safeParse(cap).success -} - -export const OcrProviderCapabilityRecordSchema = z.partialRecord(OcrProviderCapabilitySchema, z.boolean()) - -export type OcrProviderCapabilityRecord = z.infer - -// OCR models and providers share the same type definition. -// A provider can offer capabilities to process multiple file types, -// while a model belonging to that provider may be limited to processing only one specific file type. -export type OcrModelCapabilityRecord = OcrProviderCapabilityRecord - -export const OcrModelSchema = z.object({ - id: z.string(), - name: z.string(), - providerId: z.string(), - capabilities: OcrProviderCapabilityRecordSchema -}) - -export type OcrModel = z.infer - -/** - * Extend this type to define provider-specefic config types. - */ -export const OcrProviderApiConfigSchema = z.object({ - apiKey: z.string(), - apiHost: z.string(), - apiVersion: z.string().optional() -}) - -export type OcrProviderApiConfig = z.infer - -export const isOcrProviderApiConfig = (config: unknown): config is OcrProviderApiConfig => { - return OcrProviderApiConfigSchema.safeParse(config).success -} - -/** - * For future. Model based ocr, api based ocr. May different api client. - * - * Extend this type to define provider-specific config types. - */ -export const OcrProviderBaseConfigSchema = z.object({ - enabled: z.boolean().default(false) -}) - -export type OcrProviderBaseConfig = z.infer - -export const OcrProviderConfigSchema = OcrProviderBaseConfigSchema.loose() - -export type OcrProviderConfig = z.infer - -const OcrProviderIdSchema = z.string() - -export type OcrProviderId = z.infer - -const OcrProviderNameSchema = z.string() - -export const OcrProviderSchema = z.object({ - id: OcrProviderIdSchema, - name: OcrProviderNameSchema, - capabilities: OcrProviderCapabilityRecordSchema, - config: OcrProviderConfigSchema -}) - -export type OcrProvider = z.infer - -export const isOcrProvider = (p: unknown): p is OcrProvider => { - return OcrProviderSchema.safeParse(p).success -} - -export const OcrApiProviderConfigSchema = OcrProviderBaseConfigSchema.extend({ - api: OcrProviderApiConfigSchema -}) - -export type OcrApiProviderConfig = z.infer - -export const isOcrApiProviderConfig = (config: unknown): config is OcrApiProviderConfig => { - return OcrApiProviderConfigSchema.safeParse(config).success -} - -export const OcrApiProviderSchema = OcrProviderSchema - -/** Currently, there is no API provider yet, but we've left room for expansion. */ -export type OcrApiProvider = z.infer - -export const isOcrApiProvider = (p: unknown): p is OcrApiProvider => { - return OcrApiProviderSchema.safeParse(p).success -} - -export type BuiltinOcrProvider = OcrProvider & { - id: BuiltinOcrProviderId -} - -export const isBuiltinOcrProvider = (p: OcrProvider): p is BuiltinOcrProvider => { - return isBuiltinOcrProviderId(p.id) -} - -// Not sure compatible api endpoint exists. May not support custom ocr provider -export type CustomOcrProvider = OcrProvider & { - id: Exclude -} - -export type ImageOcrProvider = OcrProvider & { - capabilities: OcrProviderCapabilityRecord & { - [OcrProviderCapabilities.image]: true - } -} - -// export type PdfOcrProvider = OcrProvider & { -// capabilities: OcrProviderCapabilityRecord & { -// [OcrProviderCapabilities.pdf]: true -// } -// } - -export const isImageOcrProvider = (p: OcrProvider): p is ImageOcrProvider => { - return p.capabilities.image === true -} - -export type SupportedOcrFile = ImageFileMetadata - -export const isSupportedOcrFile = (file: FileMetadata): file is SupportedOcrFile => { - return isImageFileMetadata(file) -} - -export type OcrParams = { - providerId: string -} - -export type OcrResult = { - text: string -} - -export type OcrHandler = (file: SupportedOcrFile) => Promise - -export type OcrImageHandler = (file: ImageFileMetadata) => Promise - -// ========================================================== -// Tesseract OCR Types -// ========================================================== -export const OcrTesseractConfigSchema = OcrProviderBaseConfigSchema.extend({ - langs: z.record(TranslateLanguageCodeSchema, z.boolean()).optional() -}) - -export type OcrTesseractConfig = z.infer - -export type OcrTesseractProvider = { - id: 'tesseract' - config: OcrTesseractConfig -} & ImageOcrProvider & - BuiltinOcrProvider - -export const isOcrTesseractProvider = (p: OcrProvider): p is OcrTesseractProvider => { - return p.id === BuiltinOcrProviderIdMap.tesseract -} - -export type TesseractLangCode = Tesseract.LanguageCode - -// ========================================================== -// System OCR Types -// ========================================================== -export interface OcrSystemConfig extends OcrProviderBaseConfig { - langs?: TranslateLanguageCode[] -} - -export type OcrSystemProvider = { - id: 'system' - config: OcrSystemConfig -} & ImageOcrProvider & - // PdfOcrProvider & - BuiltinOcrProvider - -export const isOcrSystemProvider = (p: OcrProvider): p is OcrSystemProvider => { - return p.id === BuiltinOcrProviderIdMap.system -} - -// ========================================================== -// PaddleOCR Types -// ========================================================== -export const OcrPpocrConfigSchema = OcrProviderBaseConfigSchema.extend({ - apiUrl: z.string().optional(), - accessToken: z.string().optional() -}) - -export type OcrPpocrConfig = z.infer - -export const isOcrPpocrConfig = (config: unknown): config is OcrPpocrConfig => { - return OcrPpocrConfigSchema.safeParse(config).success -} - -export type OcrPpocrProvider = { - id: 'paddleocr' - config: OcrPpocrConfig -} & ImageOcrProvider & - // PdfOcrProvider & - BuiltinOcrProvider - -export const isOcrPpocrProvider = (p: OcrProvider): p is OcrPpocrProvider => { - return p.id === BuiltinOcrProviderIdMap.paddleocr -} - -// ========================================================== -// OV OCR Types -// ========================================================== -export const OcrOvConfigSchema = OcrProviderBaseConfigSchema.extend({ - // It's not configurable for now. - // langs: z.array(TranslateLanguageCodeSchema).optional() -}) - -export type OcrOvConfig = z.infer - -export const isOcrOvConfig = (config: unknown): config is OcrOvConfig => { - return OcrOvConfigSchema.safeParse(config).success -} - -export type OcrOvProvider = { - id: 'ovocr' - config: OcrOvConfig -} & ImageOcrProvider & - // PdfOcrProvider & - BuiltinOcrProvider - -export const isOcrOVProvider = (p: OcrProvider): p is OcrOvProvider => { - return p.id === BuiltinOcrProviderIdMap.ovocr -} - -// ========================================================== -// API layer Types -// ========================================================== - -const TimestampExtendShape = { - createdAt: z.number().nullable(), - updatedAt: z.number().nullable() -} - -export const DbOcrProviderSchema = OcrProviderSchema.extend(TimestampExtendShape) - -export type DbOcrProvider = z.infer - -export const isDbOcrProvider = (p: unknown): p is DbOcrProvider => { - return DbOcrProviderSchema.safeParse(p).success -} - -export type ListOcrProvidersQuery = { registered?: boolean } - -export const ListOcrProvidersResponseSchema = z.object({ - data: z.array(DbOcrProviderSchema) -}) - -export type ListOcrProvidersResponse = z.infer - -export const GetOcrProviderResponseSchema = z.object({ - data: DbOcrProviderSchema -}) - -export type GetOcrProviderResponse = z.infer - -/** - * Request payload for updating an OCR provider. - * Only the following fields are modifiable: - * - `name`: provider display name - * - `config`: provider-specific configuration object (all properties optional) - */ -export const UpdateOcrProviderRequestSchema = z.object({ - id: OcrProviderIdSchema, - name: OcrProviderNameSchema.optional(), - config: OcrProviderConfigSchema.partial().optional() -}) - -export type UpdateOcrProviderRequest = z.infer - -export const UpdateOcrProviderResponseSchema = z.object({ - data: DbOcrProviderSchema -}) - -export type UpdateOcrProviderResponse = z.infer - -export const CreateOcrProviderRequestSchema = OcrProviderSchema - -export type CreateOcrProviderRequest = z.infer - -export const CreateOcrProviderResponseSchema = z.object({ - data: DbOcrProviderSchema -}) - -export type CreateOcrProviderResponse = z.infer - -export const ReplaceOcrProviderRequestSchema = OcrProviderSchema - -export type ReplaceOcrProviderRequest = z.infer - -export const ReplaceOcrProviderResponseSchema = z.object({ - data: DbOcrProviderSchema -}) - -export type ReplaceOcrProviderResponse = z.infer - -// ========================================================== -// Business layer Types -// ========================================================== - -/** - * Business-level representation of an OCR provider. - * Mirrors the data layer but is intended for use in domain/business logic. - */ -export type OcrProviderBusiness = DbOcrProvider - -/** - * Business-level representation of an OCR provider creation payload. - */ -export type OcrProviderCreateBusiness = DbOcrProviderCreate - -/** - * Business-level representation of an OCR provider update payload. - */ -export type OcrProviderUpdateBusiness = DbOcrProviderUpdate - -/** - * Business-level representation of an OCR provider replacement payload. - */ -export type OcrProviderReplaceBusiness = DbOcrProviderReplace - -/** - * Business-level key type for identifying an OCR provider. - */ -export type OcrProviderKeyBusiness = DbOcrProviderKey - -// ========================================================== -// Data layer Types -// -// NOTE: Timestamp operations are not exposed to outside. -// ========================================================== - -export type DbOcrProviderCreate = OcrProvider - -export type DbOcrProviderUpdate = UpdateOcrProviderRequest - -export type DbOcrProviderReplace = OcrProvider - -export type DbOcrProviderKey = DbOcrProvider['id'] diff --git a/src/renderer/src/types/ocr/provider/base.ts b/src/renderer/src/types/ocr/provider/base.ts new file mode 100644 index 0000000000..6826b36e68 --- /dev/null +++ b/src/renderer/src/types/ocr/provider/base.ts @@ -0,0 +1,118 @@ +import { objectValues } from '@types' +import * as z from 'zod' + +export const BuiltinOcrProviderIdMap = { + tesseract: 'tesseract', + system: 'system', + paddleocr: 'paddleocr', + ovocr: 'ovocr' +} as const satisfies Record + +export const BuiltinOcrProviderIds = Object.freeze(objectValues(BuiltinOcrProviderIdMap)) + +export const BuiltinOcrProviderIdSchema = z.enum(['tesseract', 'system', 'paddleocr', 'ovocr']) + +export type BuiltinOcrProviderId = z.infer + +export const isBuiltinOcrProviderId = (id: string): id is BuiltinOcrProviderId => { + return BuiltinOcrProviderIdSchema.safeParse(id).success +} // extensible + +export const OcrProviderCapabilities = { + image: 'image' + // pdf: 'pdf' +} as const satisfies Record + +export const OcrProviderCapabilitySchema = z.enum(['image']) + +export type OcrProviderCapability = z.infer + +export const isOcrProviderCapability = (cap: string): cap is OcrProviderCapability => { + return OcrProviderCapabilitySchema.safeParse(cap).success +} + +export const OcrProviderCapabilityRecordSchema = z.partialRecord(OcrProviderCapabilitySchema, z.boolean()) + +export type OcrProviderCapabilityRecord = z.infer + +/** + * Extend this type to define provider-specefic config types. + */ +export const OcrProviderApiConfigSchema = z.object({ + apiKey: z.string(), + apiHost: z.string(), + apiVersion: z.string().optional() +}) +export type OcrProviderApiConfig = z.infer +export const isOcrProviderApiConfig = (config: unknown): config is OcrProviderApiConfig => { + return OcrProviderApiConfigSchema.safeParse(config).success +} /** + * For future. Model based ocr, api based ocr. May different api client. + * + * Extend this type to define provider-specific config types. + */ + +export const OcrProviderBaseConfigSchema = z.object({ + enabled: z.boolean().default(false) +}) + +export type OcrProviderBaseConfig = z.infer + +export const OcrProviderConfigSchema = OcrProviderBaseConfigSchema.loose() + +export type OcrProviderConfig = z.infer + +export const OcrProviderIdSchema = z.string() + +export type OcrProviderId = z.infer + +export const OcrProviderNameSchema = z.string() + +export const OcrProviderSchema = z.object({ + id: OcrProviderIdSchema, + name: OcrProviderNameSchema, + capabilities: OcrProviderCapabilityRecordSchema, + config: OcrProviderConfigSchema +}) + +export type OcrProvider = z.infer + +export const isOcrProvider = (p: unknown): p is OcrProvider => { + return OcrProviderSchema.safeParse(p).success +} + +export const OcrApiProviderConfigSchema = OcrProviderBaseConfigSchema.extend({ + api: OcrProviderApiConfigSchema +}) +export type OcrApiProviderConfig = z.infer +export const isOcrApiProviderConfig = (config: unknown): config is OcrApiProviderConfig => { + return OcrApiProviderConfigSchema.safeParse(config).success +} + +export const OcrApiProviderSchema = OcrProviderSchema +/** Currently, there is no API provider yet, but we've left room for expansion. */ +export type OcrApiProvider = z.infer +export const isOcrApiProvider = (p: unknown): p is OcrApiProvider => { + return OcrApiProviderSchema.safeParse(p).success +} + +export type BuiltinOcrProvider = OcrProvider & { + id: BuiltinOcrProviderId +} +export const isBuiltinOcrProvider = (p: OcrProvider): p is BuiltinOcrProvider => { + return isBuiltinOcrProviderId(p.id) +} + +// Not sure compatible api endpoint exists. May not support custom ocr provider +export type CustomOcrProvider = OcrProvider & { + id: Exclude +} + +export type ImageOcrProvider = OcrProvider & { + capabilities: OcrProviderCapabilityRecord & { + [OcrProviderCapabilities.image]: true + } +} +export const isImageOcrProvider = (p: OcrProvider): p is ImageOcrProvider => { + return p.capabilities.image === true +} diff --git a/src/renderer/src/types/ocr/provider/index.ts b/src/renderer/src/types/ocr/provider/index.ts new file mode 100644 index 0000000000..d310521eb5 --- /dev/null +++ b/src/renderer/src/types/ocr/provider/index.ts @@ -0,0 +1,5 @@ +export * from './base' +export * from './ov' +export * from './paddle' +export * from './system' +export * from './tesseract' diff --git a/src/renderer/src/types/ocr/provider/ov.ts b/src/renderer/src/types/ocr/provider/ov.ts new file mode 100644 index 0000000000..c01cf9b2b4 --- /dev/null +++ b/src/renderer/src/types/ocr/provider/ov.ts @@ -0,0 +1,29 @@ +import type * as z from 'zod' + +import type { ImageOcrProvider } from './base' +import type { BuiltinOcrProvider } from './base' +import type { OcrProvider } from './base' +import { OcrProviderBaseConfigSchema } from './base' +import { BuiltinOcrProviderIdMap } from './base' + +// ========================================================== +// OV OCR Types +// ========================================================== + +export const OcrOvConfigSchema = OcrProviderBaseConfigSchema.extend({ + // It's not configurable for now. + // langs: z.array(TranslateLanguageCodeSchema).optional() +}) +export type OcrOvConfig = z.infer +export const isOcrOvConfig = (config: unknown): config is OcrOvConfig => { + return OcrOvConfigSchema.safeParse(config).success +} +export type OcrOvProvider = { + id: 'ovocr' + config: OcrOvConfig +} & ImageOcrProvider & + // PdfOcrProvider & + BuiltinOcrProvider +export const isOcrOVProvider = (p: OcrProvider): p is OcrOvProvider => { + return p.id === BuiltinOcrProviderIdMap.ovocr +} diff --git a/src/renderer/src/types/ocr/provider/paddle.ts b/src/renderer/src/types/ocr/provider/paddle.ts new file mode 100644 index 0000000000..fd5a7a02a2 --- /dev/null +++ b/src/renderer/src/types/ocr/provider/paddle.ts @@ -0,0 +1,29 @@ +import * as z from 'zod' + +import type { ImageOcrProvider } from './base' +import type { BuiltinOcrProvider } from './base' +import type { OcrProvider } from './base' +import { OcrProviderBaseConfigSchema } from './base' +import { BuiltinOcrProviderIdMap } from './base' + +// ========================================================== +// PaddleOCR Types +// ========================================================== + +export const OcrPpocrConfigSchema = OcrProviderBaseConfigSchema.extend({ + apiUrl: z.string().optional(), + accessToken: z.string().optional() +}) +export type OcrPpocrConfig = z.infer +export const isOcrPpocrConfig = (config: unknown): config is OcrPpocrConfig => { + return OcrPpocrConfigSchema.safeParse(config).success +} +export type OcrPpocrProvider = { + id: 'paddleocr' + config: OcrPpocrConfig +} & ImageOcrProvider & + // PdfOcrProvider & + BuiltinOcrProvider +export const isOcrPpocrProvider = (p: OcrProvider): p is OcrPpocrProvider => { + return p.id === BuiltinOcrProviderIdMap.paddleocr +} diff --git a/src/renderer/src/types/ocr/provider/system.ts b/src/renderer/src/types/ocr/provider/system.ts new file mode 100644 index 0000000000..c764b1d2de --- /dev/null +++ b/src/renderer/src/types/ocr/provider/system.ts @@ -0,0 +1,25 @@ +import type { TranslateLanguageCode } from '@types' + +import type { OcrProvider } from './base' +import { type ImageOcrProvider } from './base' +import { type BuiltinOcrProvider } from './base' +import { type OcrProviderBaseConfig } from './base' +import { BuiltinOcrProviderIdMap } from './base' + +// ========================================================== +// System OCR Types +// ========================================================== + +export interface OcrSystemConfig extends OcrProviderBaseConfig { + langs?: TranslateLanguageCode[] +} +export type OcrSystemProvider = { + id: 'system' + config: OcrSystemConfig +} & ImageOcrProvider & + // PdfOcrProvider & + BuiltinOcrProvider + +export const isOcrSystemProvider = (p: OcrProvider): p is OcrSystemProvider => { + return p.id === BuiltinOcrProviderIdMap.system +} diff --git a/src/renderer/src/types/ocr/provider/tesseract.ts b/src/renderer/src/types/ocr/provider/tesseract.ts new file mode 100644 index 0000000000..a22420d8ee --- /dev/null +++ b/src/renderer/src/types/ocr/provider/tesseract.ts @@ -0,0 +1,31 @@ +import { TranslateLanguageCodeSchema } from '@types' +import type Tesseract from 'tesseract.js' +import * as z from 'zod' + +import type { ImageOcrProvider } from './base' +import type { BuiltinOcrProvider } from './base' +import type { OcrProvider } from './base' +import { OcrProviderBaseConfigSchema } from './base' +import { BuiltinOcrProviderIdMap } from './base' + +// ========================================================== +// Tesseract OCR Types +// ========================================================== + +export const OcrTesseractConfigSchema = OcrProviderBaseConfigSchema.extend({ + langs: z.record(TranslateLanguageCodeSchema, z.boolean()).optional() +}) + +export type OcrTesseractConfig = z.infer + +export type OcrTesseractProvider = { + id: 'tesseract' + config: OcrTesseractConfig +} & ImageOcrProvider & + BuiltinOcrProvider + +export const isOcrTesseractProvider = (p: OcrProvider): p is OcrTesseractProvider => { + return p.id === BuiltinOcrProviderIdMap.tesseract +} + +export type TesseractLangCode = Tesseract.LanguageCode diff --git a/tsconfig.node.json b/tsconfig.node.json index a213ad6330..59ac8dc5af 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -11,7 +11,7 @@ "packages/mcp-trace/**/*", "src/renderer/src/services/traceApi.ts", "tests/__mocks__/**/*" -, "src/renderer/src/types/ocr/ocr.ts" ], +, "src/renderer/src/types/ocr/base.ts" ], "compilerOptions": { "composite": true, "incremental": true,