refactor(ocr): simplify ocr service interface and params handling

- Replace OcrProvider with OcrParams to simplify interface
- Remove unused OcrApiClientFactory and related code
- Consolidate ocr service calls to use consistent params structure
This commit is contained in:
icarus 2025-10-20 05:07:53 +08:00
parent 68aaf9df4a
commit 49c80620ae
9 changed files with 57 additions and 48 deletions

View File

@ -18,7 +18,7 @@ import type {
AgentPersistedMessage,
FileMetadata,
Notification,
OcrProvider,
OcrParams,
Provider,
Shortcut,
SupportedOcrFile
@ -872,9 +872,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
)
// OCR
ipcMain.handle(IpcChannel.OCR_Ocr, (_, file: SupportedOcrFile, provider: OcrProvider) =>
ocrService.ocr(file, provider)
)
ipcMain.handle(IpcChannel.OCR_Ocr, (_, file: SupportedOcrFile, params: OcrParams) => ocrService.ocr(file, params))
// OVMS
ipcMain.handle(IpcChannel.Ovms_AddModel, (_, modelName: string, modelId: string, modelSource: string, task: string) =>

View File

@ -1,5 +1,5 @@
import { loggerService } from '@logger'
import type { OcrProvider, OcrResult, SupportedOcrFile } from '@types'
import type { OcrParams, OcrResult, SupportedOcrFile } from '@types'
import { BuiltinOcrProviderIds } from '@types'
import type { OcrBaseService } from './builtin/OcrBaseService'
@ -28,12 +28,12 @@ export class OcrService {
return Array.from(this.registry.keys())
}
public async ocr(file: SupportedOcrFile, provider: OcrProvider): Promise<OcrResult> {
const service = this.registry.get(provider.id)
public async ocr(file: SupportedOcrFile, params: OcrParams): Promise<OcrResult> {
const service = this.registry.get(params.providerId)
if (!service) {
throw new Error(`Provider ${provider.id} is not registered`)
throw new Error(`Provider ${params.providerId} is not registered`)
}
return service.ocr(file, provider.config)
return service.ocr(file)
}
}

View File

@ -12,7 +12,7 @@ import type {
} from '@shared/data/preference/preferenceTypes'
import type { UpgradeChannel } from '@shared/data/preference/preferenceTypes'
import { IpcChannel } from '@shared/IpcChannel'
import type { Notification } from '@types'
import type { Notification, OcrParams } from '@types'
import type {
AddMemoryOptions,
AssistantMessage,
@ -27,7 +27,6 @@ import type {
MemoryConfig,
MemoryListOptions,
MemorySearchOptions,
OcrProvider,
OcrResult,
Provider,
RestartApiServerStatusResult,
@ -476,8 +475,8 @@ const api = {
ipcRenderer.invoke(IpcChannel.CodeTools_RemoveCustomTerminalPath, terminalId)
},
ocr: {
ocr: (file: SupportedOcrFile, provider: OcrProvider): Promise<OcrResult> =>
ipcRenderer.invoke(IpcChannel.OCR_Ocr, file, provider)
ocr: (file: SupportedOcrFile, params: OcrParams): Promise<OcrResult> =>
ipcRenderer.invoke(IpcChannel.OCR_Ocr, file, params)
},
cherryai: {
generateSignature: (params: { method: string; path: string; query: string; body: Record<string, any> }) =>

View File

@ -26,8 +26,10 @@ export const useOcr = () => {
const ocrImage = useCallback(
async (image: ImageFileMetadata) => {
if (isProviderAvailable(imageProvider)) {
logger.debug('ocrImage', { config: imageProvider.config })
return OcrService.ocr(image, imageProvider)
logger.debug('ocrImage', { provider: imageProvider })
return OcrService.ocr(image, {
providerId: imageProvider.id
})
} else {
throw new Error(t('ocr.error.provider.'))
}

View File

@ -1,8 +1,5 @@
import { loggerService } from '@logger'
import type { OcrProvider, OcrResult, SupportedOcrFile } from '@renderer/types'
import { isOcrApiProvider } from '@renderer/types'
import { OcrApiClientFactory } from './clients/OcrApiClientFactory'
import type { OcrParams, OcrResult, SupportedOcrFile } from '@renderer/types'
const logger = loggerService.withContext('renderer:OcrService')
@ -13,12 +10,7 @@ const logger = loggerService.withContext('renderer:OcrService')
* @returns ocr result
* @throws {Error}
*/
export const ocr = async (file: SupportedOcrFile, provider: OcrProvider): Promise<OcrResult> => {
export const ocr = async (file: SupportedOcrFile, params: OcrParams): Promise<OcrResult> => {
logger.info(`ocr file ${file.path}`)
if (isOcrApiProvider(provider)) {
const client = OcrApiClientFactory.create(provider)
return client.ocr(file, provider.config)
} else {
return window.api.ocr.ocr(file, provider)
}
return window.api.ocr.ocr(file, params)
}

View File

@ -1,27 +1,29 @@
import { loggerService } from '@logger'
import type { OcrApiProvider } from '@renderer/types'
import type { OcrApiProvider, OcrApiProviderConfig } from '@renderer/types'
import type { OcrBaseApiClient } from './OcrBaseApiClient'
import { OcrExampleApiClient } from './OcrExampleApiClient'
const logger = loggerService.withContext('OcrApiClientFactory')
// Not being used for now.
// TODO: Migrate to main in the future.
export class OcrApiClientFactory {
/**
* Create an ApiClient instance for the given provider
* ApiClient实例
*/
static create(provider: OcrApiProvider): OcrBaseApiClient {
static create(provider: OcrApiProvider, config: OcrApiProviderConfig): OcrBaseApiClient {
logger.debug(`Creating ApiClient for provider:`, {
id: provider.id,
config: provider.config
config
})
let instance: OcrBaseApiClient
// Extend other clients here
// eslint-disable-next-line prefer-const
instance = new OcrExampleApiClient(provider)
instance = new OcrExampleApiClient(provider, config)
return instance
}

View File

@ -1,26 +1,31 @@
import { cacheService } from '@data/CacheService'
import type { OcrApiProvider, OcrHandler } from '@renderer/types'
import type { OcrApiProvider, OcrApiProviderConfig, OcrHandler } from '@renderer/types'
// Not being used for now.
// TODO: Migrate to main in the future.
export abstract class OcrBaseApiClient {
public provider: OcrApiProvider
public config: OcrApiProviderConfig
protected host: string
protected apiKey: string
constructor(provider: OcrApiProvider) {
constructor(provider: OcrApiProvider, config: OcrApiProviderConfig) {
this.provider = provider
this.host = this.getHost()
this.apiKey = this.getApiKey()
this.config = config
}
abstract ocr: OcrHandler
// copy from BaseApiClient
public getHost(): string {
return this.provider.config.api.apiHost
return this.config.api.apiHost
}
// copy from BaseApiClient
public getApiKey() {
const keys = this.provider.config.api.apiKey.split(',').map((key) => key.trim())
const keys = this.config.api.apiKey.split(',').map((key) => key.trim())
const keyName = `ocr_provider:${this.provider.id}:last_used_key`
if (keys.length === 1) {

View File

@ -1,12 +1,14 @@
import type { OcrApiProvider, SupportedOcrFile } from '@renderer/types'
import type { OcrApiProvider, OcrApiProviderConfig, SupportedOcrFile } from '@renderer/types'
import { OcrBaseApiClient } from './OcrBaseApiClient'
export type OcrExampleProvider = OcrApiProvider
// Not being used for now.
// TODO: Migrate to main in the future.
export class OcrExampleApiClient extends OcrBaseApiClient {
constructor(provider: OcrApiProvider) {
super(provider)
constructor(provider: OcrApiProvider, config: OcrApiProviderConfig) {
super(provider, config)
}
public ocr = async (file: SupportedOcrFile) => {

View File

@ -104,18 +104,23 @@ export const isOcrProvider = (p: unknown): p is OcrProvider => {
return OcrProviderSchema.safeParse(p).success
}
export type OcrApiProviderConfig = OcrProviderBaseConfig & {
api: OcrProviderApiConfig
export const OcrApiProviderConfigSchema = OcrProviderBaseConfigSchema.extend({
api: OcrProviderApiConfigSchema
})
export type OcrApiProviderConfig = z.infer<typeof OcrApiProviderConfigSchema>
export const isOcrApiProviderConfig = (config: unknown): config is OcrApiProviderConfig => {
return OcrApiProviderConfigSchema.safeParse(config).success
}
/** This type is not being used. */
export type OcrApiProvider = OcrProvider & {
config: OcrApiProviderConfig
}
export const OcrApiProviderSchema = OcrProviderSchema
/** This function is not being used. */
export const isOcrApiProvider = (p: OcrProvider): p is OcrApiProvider => {
return !!(p.config && p.config.api && isOcrProviderApiConfig(p.config.api))
/** Currently, there is no API provider yet, but we've left room for expansion. */
export type OcrApiProvider = z.infer<typeof OcrApiProviderSchema>
export const isOcrApiProvider = (p: unknown): p is OcrApiProvider => {
return OcrApiProviderSchema.safeParse(p).success
}
export type BuiltinOcrProvider = OcrProvider & {
@ -153,13 +158,17 @@ 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, options?: OcrProviderBaseConfig) => Promise<OcrResult>
export type OcrHandler = (file: SupportedOcrFile) => Promise<OcrResult>
export type OcrImageHandler = (file: ImageFileMetadata, options?: OcrProviderBaseConfig) => Promise<OcrResult>
export type OcrImageHandler = (file: ImageFileMetadata) => Promise<OcrResult>
// Tesseract Types
export type OcrTesseractConfig = OcrProviderBaseConfig & {