refactor(ocr): improve type safety and config handling in useOcrProvider

- Replace dynamic provider lookup with type-safe registry pattern
- Add separate config management for each provider type
- Remove unused imports and simplify provider fallback logic
This commit is contained in:
icarus 2025-10-20 03:06:11 +08:00
parent 94ed39ab27
commit a34426d431
2 changed files with 94 additions and 26 deletions

View File

@ -1,35 +1,106 @@
import { usePreference } from '@data/hooks/usePreference'
import { loggerService } from '@logger' import { loggerService } from '@logger'
import { updateOcrProviderConfig } from '@renderer/store/ocr' import type {
import type { OcrProviderConfig } from '@renderer/types' BuiltinOcrProviderId,
OcrOvConfig,
OcrOvProvider,
OcrPpocrConfig,
OcrPpocrProvider,
OcrSystemConfig,
OcrSystemProvider,
OcrTesseractConfig,
OcrTesseractProvider
} from '@renderer/types'
import { BUILTIN_OCR_PROVIDERS_MAP } from '@shared/config/ocr' import { BUILTIN_OCR_PROVIDERS_MAP } from '@shared/config/ocr'
import { useTranslation } from 'react-i18next' import { merge } from 'lodash'
import { useDispatch } from 'react-redux' import { useCallback, useMemo } from 'react'
import { useOcrProviders } from './useOcrProviders'
const logger = loggerService.withContext('useOcrProvider') const logger = loggerService.withContext('useOcrProvider')
export const useOcrProvider = (id: string) => { const PROVIDER_REGISTRY = {
const { t } = useTranslation() ovocr: null as unknown as OcrOvProvider,
const dispatch = useDispatch() paddleocr: null as unknown as OcrPpocrProvider,
const { providers } = useOcrProviders() system: null as unknown as OcrSystemProvider,
let provider = providers.find((p) => p.id === id) tesseract: null as unknown as OcrTesseractProvider
}
// safely fallback const CONFIG_REGISTRY = {
if (!provider) { ovocr: null as unknown as OcrOvConfig,
logger.error(`Ocr Provider ${id} not found`) paddleocr: null as unknown as OcrPpocrConfig,
logger.warn(`Fallback to tesseract`) system: null as unknown as OcrSystemConfig,
window.toast.error(t('ocr.error.provider.not_found')) tesseract: null as unknown as OcrTesseractConfig
window.toast.warning(t('ocr.warning.provider.fallback', { name: 'Tesseract' })) } as const
provider = BUILTIN_OCR_PROVIDERS_MAP.tesseract
}
const updateConfig = (update: Partial<OcrProviderConfig>) => { type ProviderMap = typeof PROVIDER_REGISTRY
dispatch(updateOcrProviderConfig({ id: provider.id, update }))
} type ConfigMap = typeof CONFIG_REGISTRY
type TProvider<T extends BuiltinOcrProviderId> = ProviderMap[T]
type TConfig<T extends BuiltinOcrProviderId> = ConfigMap[T]
type UseOcrProviderReturn<T extends BuiltinOcrProviderId> = {
provider: TProvider<T>
config: TConfig<T>
updateConfig: (update: Partial<TConfig<T>>) => void
}
export const useOcrProvider = <T extends BuiltinOcrProviderId>(id: T): UseOcrProviderReturn<T> => {
const provider = useMemo(() => {
switch (id) {
case 'ovocr':
return BUILTIN_OCR_PROVIDERS_MAP.ovocr
case 'paddleocr':
return BUILTIN_OCR_PROVIDERS_MAP.paddleocr
case 'system':
return BUILTIN_OCR_PROVIDERS_MAP.system
case 'tesseract':
return BUILTIN_OCR_PROVIDERS_MAP.tesseract
}
}, [id])
const [ovConfig, setOvConfig] = usePreference('ocr.provider.config.ovocr')
const [ppConfig, setPpConfig] = usePreference('ocr.provider.config.paddleocr')
const [sysConfig, setSysConfig] = usePreference('ocr.provider.config.system')
const [tesConfig, setTesConfig] = usePreference('ocr.provider.config.tesseract')
const config = useMemo(() => {
switch (id) {
case 'ovocr':
return ovConfig
case 'paddleocr':
return ppConfig
case 'system':
return sysConfig
case 'tesseract':
return tesConfig
}
}, [id, ovConfig, ppConfig, sysConfig, tesConfig])
const updateConfig = useCallback(
(update: Partial<TConfig<T>>) => {
switch (id) {
case 'ovocr':
setOvConfig(merge({}, ovConfig, update))
break
case 'paddleocr':
setPpConfig(merge({}, ppConfig, update))
break
case 'system':
setSysConfig(merge({}, sysConfig, update))
break
case 'tesseract':
setTesConfig(merge({}, tesConfig, update))
break
default:
logger.warn(`Unsupported OCR provider id: ${id}`)
}
},
[id, ovConfig, ppConfig, setOvConfig, setPpConfig, setSysConfig, setTesConfig, sysConfig, tesConfig]
)
return { return {
provider, provider,
config,
updateConfig updateConfig
} } as UseOcrProviderReturn<T>
} }

View File

@ -1,13 +1,10 @@
import { useQuery } from '@data/hooks/useDataApi' import { useQuery } from '@data/hooks/useDataApi'
import { loggerService } from '@logger'
import { getBuiltinOcrProviderLabel } from '@renderer/i18n/label' import { getBuiltinOcrProviderLabel } from '@renderer/i18n/label'
import type { OcrProvider } from '@renderer/types' import type { OcrProvider } from '@renderer/types'
import { isBuiltinOcrProvider } from '@renderer/types' import { isBuiltinOcrProvider } from '@renderer/types'
import { BUILTIN_OCR_PROVIDERS } from '@shared/config/ocr' import { BUILTIN_OCR_PROVIDERS } from '@shared/config/ocr'
import { useMemo } from 'react' import { useMemo } from 'react'
const logger = loggerService.withContext('useOcrProviders')
export const useOcrProviders = () => { export const useOcrProviders = () => {
const { data: validProviderIds, loading, error } = useQuery('/ocr/providers') const { data: validProviderIds, loading, error } = useQuery('/ocr/providers')
const providers = useMemo( const providers = useMemo(