refactor(ocr): move provider logo logic to component and consolidate hooks

Move OcrProviderLogo implementation from useOcrProviders hook to the component file
Extract common OCR provider logic into a separate useOcrProviders hook
Clean up and reorganize related imports and exports
This commit is contained in:
icarus 2025-10-20 02:06:17 +08:00
parent 7658b1e79f
commit beb44eea61
5 changed files with 101 additions and 93 deletions

View File

@ -1,98 +1,15 @@
import { Avatar } from '@cherrystudio/ui'
import { usePreference } from '@data/hooks/usePreference'
import { loggerService } from '@logger'
import IntelLogo from '@renderer/assets/images/providers/intel.png'
import PaddleocrLogo from '@renderer/assets/images/providers/paddleocr.png'
import TesseractLogo from '@renderer/assets/images/providers/Tesseract.js.png'
import { getBuiltinOcrProviderLabel } from '@renderer/i18n/label'
import { useAppSelector } from '@renderer/store'
import { addOcrProvider, removeOcrProvider, updateOcrProviderConfig } from '@renderer/store/ocr'
import type { OcrProvider, OcrProviderConfig } from '@renderer/types'
import { isBuiltinOcrProvider, isBuiltinOcrProviderId, isImageOcrProvider } from '@renderer/types'
import { updateOcrProviderConfig } from '@renderer/store/ocr'
import type { OcrProviderConfig } from '@renderer/types'
import { isBuiltinOcrProviderId } from '@renderer/types'
import { BUILTIN_OCR_PROVIDERS_MAP } from '@shared/config/ocr'
import { FileQuestionMarkIcon, MonitorIcon } from 'lucide-react'
import { useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux'
import { useOcrProviders } from './useOcrProviders'
const logger = loggerService.withContext('useOcrProvider')
export const useOcrProviders = () => {
// TODO: migrate to useQuery
const providers = useAppSelector((state) => state.ocr.providers)
const imageProviders = providers.filter(isImageOcrProvider)
const [imageProviderId, setImageProviderId] = usePreference('ocr.settings.image_provider_id')
const imageProvider = useMemo(() => {
return imageProviders.find((p) => p.id === imageProviderId)
}, [imageProviderId, imageProviders])
const dispatch = useDispatch()
const { t } = useTranslation()
/**
* OCR服务提供者
* @param provider - OCR提供者对象id和其他配置信息
* @throws {Error} ID的提供者时抛出错误
*/
const addProvider = useCallback(
(provider: OcrProvider) => {
if (providers.some((p) => p.id === provider.id)) {
const msg = `Provider with id ${provider.id} already exists`
logger.error(msg)
window.toast.error(t('ocr.error.provider.existing'))
throw new Error(msg)
}
dispatch(addOcrProvider(provider))
},
[dispatch, providers, t]
)
/**
* OCR服务提供者
* @param id - OCR提供者ID
* @throws {Error}
*/
const removeProvider = (id: string) => {
if (isBuiltinOcrProviderId(id)) {
const msg = `Cannot remove builtin provider ${id}`
logger.error(msg)
window.toast.error(t('ocr.error.provider.cannot_remove_builtin'))
throw new Error(msg)
}
dispatch(removeOcrProvider(id))
}
const getOcrProviderName = (p: OcrProvider) => {
return isBuiltinOcrProvider(p) ? getBuiltinOcrProviderLabel(p.id) : p.name
}
const OcrProviderLogo = ({ provider: p, size = 14 }: { provider: OcrProvider; size?: number }) => {
if (isBuiltinOcrProvider(p)) {
switch (p.id) {
case 'tesseract':
return <Avatar src={TesseractLogo} style={{ width: size, height: size }} />
case 'system':
return <MonitorIcon size={size} />
case 'paddleocr':
return <Avatar src={PaddleocrLogo} style={{ width: size, height: size }} />
case 'ovocr':
return <Avatar src={IntelLogo} style={{ width: size, height: size }} />
}
}
return <FileQuestionMarkIcon size={size} />
}
return {
providers,
imageProvider,
addProvider,
removeProvider,
setImageProviderId,
getOcrProviderName,
OcrProviderLogo
}
}
export const useOcrProvider = (id: string) => {
const { t } = useTranslation()
const dispatch = useDispatch()

View File

@ -0,0 +1,71 @@
import { usePreference } from '@data/hooks/usePreference'
import { loggerService } from '@logger'
import { getBuiltinOcrProviderLabel } from '@renderer/i18n/label'
import { useAppSelector } from '@renderer/store'
import { addOcrProvider, removeOcrProvider } from '@renderer/store/ocr'
import type { OcrProvider } from '@renderer/types'
import { isBuiltinOcrProvider, isBuiltinOcrProviderId, isImageOcrProvider } from '@renderer/types'
import { useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useDispatch } from 'react-redux'
const logger = loggerService.withContext('useOcrProviders')
export const useOcrProviders = () => {
// TODO: migrate to useQuery
const providers = useAppSelector((state) => state.ocr.providers)
const imageProviders = providers.filter(isImageOcrProvider)
const [imageProviderId, setImageProviderId] = usePreference('ocr.settings.image_provider_id')
const imageProvider = useMemo(() => {
return imageProviders.find((p) => p.id === imageProviderId)
}, [imageProviderId, imageProviders])
const dispatch = useDispatch()
const { t } = useTranslation()
/**
* OCR服务提供者
* @param provider - OCR提供者对象id和其他配置信息
* @throws {Error} ID的提供者时抛出错误
*/
const addProvider = useCallback(
(provider: OcrProvider) => {
if (providers.some((p) => p.id === provider.id)) {
const msg = `Provider with id ${provider.id} already exists`
logger.error(msg)
window.toast.error(t('ocr.error.provider.existing'))
throw new Error(msg)
}
dispatch(addOcrProvider(provider))
},
[dispatch, providers, t]
)
/**
* OCR服务提供者
* @param id - OCR提供者ID
* @throws {Error}
*/
const removeProvider = (id: string) => {
if (isBuiltinOcrProviderId(id)) {
const msg = `Cannot remove builtin provider ${id}`
logger.error(msg)
window.toast.error(t('ocr.error.provider.cannot_remove_builtin'))
throw new Error(msg)
}
dispatch(removeOcrProvider(id))
}
const getOcrProviderName = (p: OcrProvider) => {
return isBuiltinOcrProvider(p) ? getBuiltinOcrProviderLabel(p.id) : p.name
}
return {
providers,
imageProvider,
addProvider,
removeProvider,
setImageProviderId,
getOcrProviderName
}
}

View File

@ -2,7 +2,7 @@ import { Alert, Skeleton } from '@heroui/react'
import { loggerService } from '@logger'
import { ErrorTag } from '@renderer/components/Tags/ErrorTag'
import { isMac, isWin } from '@renderer/config/constant'
import { useOcrProviders } from '@renderer/hooks/ocr/useOcrProvider'
import { useOcrProviders } from '@renderer/hooks/ocr/useOcrProviders'
import type { ImageOcrProvider } from '@renderer/types'
import { BuiltinOcrProviderIds, isImageOcrProvider } from '@renderer/types'
import { getErrorMessage } from '@renderer/utils'

View File

@ -1,12 +1,16 @@
// import { loggerService } from '@logger'
import { Flex } from '@cherrystudio/ui'
import { Avatar, Flex } from '@cherrystudio/ui'
import IntelLogo from '@renderer/assets/images/providers/intel.png'
import PaddleocrLogo from '@renderer/assets/images/providers/paddleocr.png'
import TesseractLogo from '@renderer/assets/images/providers/Tesseract.js.png'
import { ErrorBoundary } from '@renderer/components/ErrorBoundary'
import { isMac, isWin } from '@renderer/config/constant'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useOcrProviders } from '@renderer/hooks/ocr/useOcrProvider'
import { useOcrProviders } from '@renderer/hooks/ocr/useOcrProviders'
import type { OcrProvider } from '@renderer/types'
import { isBuiltinOcrProvider, isOcrSystemProvider } from '@renderer/types'
import { Divider } from 'antd'
import { FileQuestionMarkIcon, MonitorIcon } from 'lucide-react'
import styled from 'styled-components'
import { SettingGroup, SettingTitle } from '..'
@ -23,7 +27,7 @@ type Props = {
const OcrProviderSettings = ({ provider }: Props) => {
const { theme: themeMode } = useTheme()
const { OcrProviderLogo, getOcrProviderName } = useOcrProviders()
const { getOcrProviderName } = useOcrProviders()
if (!provider || (!isWin && !isMac && isOcrSystemProvider(provider))) {
return null
@ -69,4 +73,20 @@ const ProviderName = styled.span`
font-weight: 500;
`
const OcrProviderLogo = ({ provider: p, size = 14 }: { provider: OcrProvider; size?: number }) => {
if (isBuiltinOcrProvider(p)) {
switch (p.id) {
case 'tesseract':
return <Avatar src={TesseractLogo} style={{ width: size, height: size }} />
case 'system':
return <MonitorIcon size={size} />
case 'paddleocr':
return <Avatar src={PaddleocrLogo} style={{ width: size, height: size }} />
case 'ovocr':
return <Avatar src={IntelLogo} style={{ width: size, height: size }} />
}
}
return <FileQuestionMarkIcon size={size} />
}
export default OcrProviderSettings

View File

@ -1,6 +1,6 @@
import { ErrorBoundary } from '@renderer/components/ErrorBoundary'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useOcrProviders } from '@renderer/hooks/ocr/useOcrProvider'
import { useOcrProviders } from '@renderer/hooks/ocr/useOcrProviders'
import type { FC } from 'react'
import { useTranslation } from 'react-i18next'