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, CreateOcrProviderRequest,
CreateOcrProviderResponse, CreateOcrProviderResponse,
GetOcrProviderResponse, GetOcrProviderResponse,
ListOcrProvidersQuery,
ListOcrProvidersResponse, ListOcrProvidersResponse,
OcrProviderId, OcrProviderId,
PatchOcrProviderRequest, PatchOcrProviderRequest,
@ -360,7 +361,7 @@ export interface ApiSchemas {
'/ocr/providers': { '/ocr/providers': {
GET: { GET: {
query: { registered?: boolean } query: ListOcrProvidersQuery
response: ListOcrProvidersResponse response: ListOcrProvidersResponse
} }
POST: { POST: {

View File

@ -7,10 +7,10 @@ import { useOcrProviders } from './useOcrProviders'
export const useOcrImageProvider = () => { export const useOcrImageProvider = () => {
const { providers, loading, error } = useOcrProviders() 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 [imageProviderId, setImageProviderId] = usePreference('ocr.settings.image_provider_id')
const imageProvider = useMemo(() => { const imageProvider = useMemo(() => {
return imageProviders.find((p) => p.id === imageProviderId) return imageProviders?.find((p) => p.id === imageProviderId)
}, [imageProviderId, imageProviders]) }, [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 { useQuery } from '@data/hooks/useDataApi'
import { getBuiltinOcrProviderLabel } from '@renderer/i18n/label' 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 { isBuiltinOcrProvider } from '@renderer/types'
import { BUILTIN_OCR_PROVIDERS } from '@shared/config/ocr'
import { useMemo } from 'react'
export const useOcrProviders = () => { export const useOcrProviders = (query?: ListOcrProvidersQuery) => {
const { data: validProviderIds, loading, error } = useQuery('/ocr/providers') const { data, loading, error } = useQuery('/ocr/providers', { query })
const providers = useMemo(
() => BUILTIN_OCR_PROVIDERS.filter((p) => validProviderIds?.includes(p.id)),
[validProviderIds]
)
const getOcrProviderName = (p: OcrProvider) => { const getOcrProviderName = (p: OcrProvider) => {
return isBuiltinOcrProvider(p) ? getBuiltinOcrProviderLabel(p.id) : p.name return isBuiltinOcrProvider(p) ? getBuiltinOcrProviderLabel(p.id) : p.name
} }
return { return {
providers, providers: data?.data,
loading, loading,
error, error,
getOcrProviderName getOcrProviderName

View File

@ -17,10 +17,10 @@ const logger = loggerService.withContext('OcrImageSettings')
const OcrImageSettings = () => { const OcrImageSettings = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { providers, loading, error, getOcrProviderName } = useOcrProviders() const { providers, loading, error, getOcrProviderName } = useOcrProviders({ registered: true })
const { imageProvider, setImageProviderId } = useOcrImageProvider() 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 setImageProvider = (id: string) => {
const provider = imageProviders.find((p) => p.id === id) const provider = imageProviders.find((p) => p.id === id)
@ -43,29 +43,48 @@ const OcrImageSettings = () => {
const isSystem = imageProvider?.id === BuiltinOcrProviderIdMap.system const isSystem = imageProvider?.id === BuiltinOcrProviderIdMap.system
if (!imageProvider) { const content = useMemo(() => {
return <Alert color="danger" title={t('ocr.error.provider.not_found')} /> 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 ( return (
<> <>
<SettingRow> <SettingRow>
<SettingRowTitle>{t('settings.tool.ocr.image_provider')}</SettingRowTitle> <SettingRowTitle>{t('settings.tool.ocr.image_provider')}</SettingRowTitle>
<div className="flex items-center gap-2 self-stretch"> <div className="flex items-center gap-2 self-stretch">{content}</div>
{!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>
</SettingRow> </SettingRow>
</> </>
) )

View File

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