refactor(ocr): improve ocr provider handling and error states

- Add ListOcrProvidersQuery type for better type safety
- Update useOcrProviders hook to accept query params and handle undefined data
- Improve error handling and loading states in OcrImageSettings component
- Memoize filtered image providers for better performance
This commit is contained in:
icarus 2025-10-20 08:07:32 +08:00
parent 389dfc08f6
commit a2e628d7e9
5 changed files with 51 additions and 35 deletions

View File

@ -4,6 +4,7 @@ import type {
CreateOcrProviderRequest,
CreateOcrProviderResponse,
GetOcrProviderResponse,
ListOcrProvidersQuery,
ListOcrProvidersResponse,
OcrProviderId,
PatchOcrProviderRequest,
@ -360,7 +361,7 @@ export interface ApiSchemas {
'/ocr/providers': {
GET: {
query: { registered?: boolean }
query: ListOcrProvidersQuery
response: ListOcrProvidersResponse
}
POST: {

View File

@ -7,10 +7,10 @@ import { useOcrProviders } from './useOcrProviders'
export const useOcrImageProvider = () => {
const { providers, loading, error } = useOcrProviders()
const imageProviders: ImageOcrProvider[] = providers.filter((p) => isImageOcrProvider(p))
const imageProviders: ImageOcrProvider[] | undefined = providers?.filter((p) => isImageOcrProvider(p))
const [imageProviderId, setImageProviderId] = usePreference('ocr.settings.image_provider_id')
const imageProvider = useMemo(() => {
return imageProviders.find((p) => p.id === imageProviderId)
return imageProviders?.find((p) => p.id === imageProviderId)
}, [imageProviderId, imageProviders])
return { imageProvider, loading, error, setImageProviderId }
return { imageProvider, loading, error, imageProviderId, setImageProviderId }
}

View File

@ -1,23 +1,17 @@
import { useQuery } from '@data/hooks/useDataApi'
import { getBuiltinOcrProviderLabel } from '@renderer/i18n/label'
import type { OcrProvider } from '@renderer/types'
import type { ListOcrProvidersQuery, OcrProvider } from '@renderer/types'
import { isBuiltinOcrProvider } from '@renderer/types'
import { BUILTIN_OCR_PROVIDERS } from '@shared/config/ocr'
import { useMemo } from 'react'
export const useOcrProviders = () => {
const { data: validProviderIds, loading, error } = useQuery('/ocr/providers')
const providers = useMemo(
() => BUILTIN_OCR_PROVIDERS.filter((p) => validProviderIds?.includes(p.id)),
[validProviderIds]
)
export const useOcrProviders = (query?: ListOcrProvidersQuery) => {
const { data, loading, error } = useQuery('/ocr/providers', { query })
const getOcrProviderName = (p: OcrProvider) => {
return isBuiltinOcrProvider(p) ? getBuiltinOcrProviderLabel(p.id) : p.name
}
return {
providers,
providers: data?.data,
loading,
error,
getOcrProviderName

View File

@ -17,10 +17,10 @@ const logger = loggerService.withContext('OcrImageSettings')
const OcrImageSettings = () => {
const { t } = useTranslation()
const { providers, loading, error, getOcrProviderName } = useOcrProviders()
const { imageProvider, setImageProviderId } = useOcrImageProvider()
const { providers, loading, error, getOcrProviderName } = useOcrProviders({ registered: true })
const { imageProvider, setImageProviderId, imageProviderId } = useOcrImageProvider()
const imageProviders = providers.filter((p) => isImageOcrProvider(p))
const imageProviders = useMemo(() => providers?.filter((p) => isImageOcrProvider(p)) ?? [], [providers])
const setImageProvider = (id: string) => {
const provider = imageProviders.find((p) => p.id === id)
@ -43,29 +43,48 @@ const OcrImageSettings = () => {
const isSystem = imageProvider?.id === BuiltinOcrProviderIdMap.system
if (!imageProvider) {
return <Alert color="danger" title={t('ocr.error.provider.not_found')} />
}
const content = useMemo(() => {
if (loading) {
return <Skeleton className="h-full w-50" />
}
if (error) {
return (
<Alert
color="danger"
title={t('ocr.provider.get.error.failed', { provider: imageProviderId })}
description={getErrorMessage(error)}
/>
)
}
if (!imageProvider) {
return <Alert color="danger" title={t('ocr.error.provider.not_found')} />
}
return (
<>
{!platformSupport && isSystem && <ErrorTag message={t('settings.tool.ocr.error.not_system')} />}
{!loading && !error && (
<Select
value={imageProvider.id}
className="w-50"
onChange={(id: string) => setImageProvider(id)}
options={options}
/>
)}
{!loading && error && (
<Alert color="danger" title={t('ocr.error.provider.get_providers')} description={getErrorMessage(error)} />
)}
</>
)
}, [])
return (
<>
<SettingRow>
<SettingRowTitle>{t('settings.tool.ocr.image_provider')}</SettingRowTitle>
<div className="flex items-center gap-2 self-stretch">
{!platformSupport && isSystem && <ErrorTag message={t('settings.tool.ocr.error.not_system')} />}
{loading && <Skeleton className="h-full w-50" />}
{!loading && !error && (
<Select
value={imageProvider.id}
className="w-50"
onChange={(id: string) => setImageProvider(id)}
options={options}
/>
)}
{!loading && error && (
<Alert color="danger" title={t('ocr.error.provider.get_providers')} description={getErrorMessage(error)} />
)}
</div>
<div className="flex items-center gap-2 self-stretch">{content}</div>
</SettingRow>
</>
)

View File

@ -268,6 +268,8 @@ const TimestampExtendShape = {
const DbOcrProviderSchema = OcrProviderSchema.extend(TimestampExtendShape)
export type ListOcrProvidersQuery = { registered?: boolean }
export const ListOcrProvidersResponseSchema = z.object({
data: z.array(DbOcrProviderSchema)
})