mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-09 23:10:20 +08:00
refactor(ocr): restructure ocr provider settings and hooks
- Simplify useOcrImageProvider by directly using useOcrProvider - Make useOcrProvider handle null provider IDs - Update provider settings components to use passed props - Remove styled-components in favor of tailwind classes
This commit is contained in:
parent
741bb94c8b
commit
d47c3b1d63
@ -1,16 +1,9 @@
|
|||||||
import { usePreference } from '@data/hooks/usePreference'
|
import { usePreference } from '@data/hooks/usePreference'
|
||||||
import type { ImageOcrProvider } from '@renderer/types'
|
|
||||||
import { isImageOcrProvider } from '@renderer/types'
|
|
||||||
import { useMemo } from 'react'
|
|
||||||
|
|
||||||
import { useOcrProviders } from './useOcrProviders'
|
import { useOcrProvider } from './useOcrProvider'
|
||||||
|
|
||||||
export const useOcrImageProvider = () => {
|
export const useOcrImageProvider = () => {
|
||||||
const { providers, loading, error } = useOcrProviders()
|
|
||||||
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 { provider: imageProvider, mutating, loading, error, updateConfig } = useOcrProvider(imageProviderId)
|
||||||
return imageProviders?.find((p) => p.id === imageProviderId)
|
return { imageProvider, loading, mutating, error, updateConfig, imageProviderId, setImageProviderId }
|
||||||
}, [imageProviderId, imageProviders])
|
|
||||||
return { imageProvider, loading, error, imageProviderId, setImageProviderId }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,15 +7,16 @@ import { useTranslation } from 'react-i18next'
|
|||||||
|
|
||||||
// const logger = loggerService.withContext('useOcrProvider')
|
// const logger = loggerService.withContext('useOcrProvider')
|
||||||
|
|
||||||
export const useOcrProvider = (id: string) => {
|
export const useOcrProvider = (id: string | null) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const path: ConcreteApiPaths = `/ocr/providers/${id}`
|
const path: ConcreteApiPaths = `/ocr/providers/${id}`
|
||||||
const { data, loading, error } = useQuery(path, undefined)
|
const { data, loading, error } = useQuery(path)
|
||||||
const { mutate, loading: mutating } = useMutation('PATCH', path)
|
const { mutate, loading: mutating } = useMutation('PATCH', path)
|
||||||
|
|
||||||
const updateConfig = useCallback(
|
const updateConfig = useCallback(
|
||||||
async (update: Partial<OcrProviderConfig>) => {
|
async (update: Partial<OcrProviderConfig>) => {
|
||||||
|
if (!id) return
|
||||||
try {
|
try {
|
||||||
await mutate({ body: { id, config: update } })
|
await mutate({ body: { id, config: update } })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -26,7 +27,8 @@ export const useOcrProvider = (id: string) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
provider: data?.data,
|
/** undefined: loading; null: invalid, id is null */
|
||||||
|
provider: id ? data?.data : null,
|
||||||
loading,
|
loading,
|
||||||
mutating,
|
mutating,
|
||||||
error,
|
error,
|
||||||
|
|||||||
@ -7,11 +7,17 @@ import { ErrorBoundary } from '@renderer/components/ErrorBoundary'
|
|||||||
import { isMac, isWin } from '@renderer/config/constant'
|
import { isMac, isWin } from '@renderer/config/constant'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import { useOcrProviders } from '@renderer/hooks/ocr/useOcrProviders'
|
import { useOcrProviders } from '@renderer/hooks/ocr/useOcrProviders'
|
||||||
import type { OcrProvider } from '@renderer/types'
|
import type { OcrProvider, OcrProviderConfig } from '@renderer/types'
|
||||||
import { isBuiltinOcrProvider, isOcrSystemProvider } from '@renderer/types'
|
import {
|
||||||
|
isBuiltinOcrProvider,
|
||||||
|
isOcrOVProvider,
|
||||||
|
isOcrPpocrProvider,
|
||||||
|
isOcrSystemProvider,
|
||||||
|
isOcrTesseractProvider
|
||||||
|
} from '@renderer/types'
|
||||||
import { Divider } from 'antd'
|
import { Divider } from 'antd'
|
||||||
import { FileQuestionMarkIcon, MonitorIcon } from 'lucide-react'
|
import { FileQuestionMarkIcon, MonitorIcon } from 'lucide-react'
|
||||||
import styled from 'styled-components'
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
import { SettingGroup, SettingTitle } from '..'
|
import { SettingGroup, SettingTitle } from '..'
|
||||||
import { OcrOVSettings } from './OcrOVSettings'
|
import { OcrOVSettings } from './OcrOVSettings'
|
||||||
@ -22,34 +28,37 @@ import { OcrTesseractSettings } from './OcrTesseractSettings'
|
|||||||
// const logger = loggerService.withContext('OcrTesseractSettings')
|
// const logger = loggerService.withContext('OcrTesseractSettings')
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
provider: OcrProvider | undefined
|
provider: OcrProvider | undefined | null
|
||||||
|
updateConfig: (config: Partial<OcrProviderConfig>) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
const OcrProviderSettings = ({ provider }: Props) => {
|
const OcrProviderSettings = ({ provider, updateConfig }: Props) => {
|
||||||
const { theme: themeMode } = useTheme()
|
const { theme: themeMode } = useTheme()
|
||||||
const { getOcrProviderName } = useOcrProviders()
|
const { getOcrProviderName } = useOcrProviders()
|
||||||
|
|
||||||
if (!provider || (!isWin && !isMac && isOcrSystemProvider(provider))) {
|
const settings = useMemo(() => {
|
||||||
return null
|
if (!provider) return null
|
||||||
}
|
|
||||||
|
|
||||||
const ProviderSettings = () => {
|
|
||||||
if (isBuiltinOcrProvider(provider)) {
|
if (isBuiltinOcrProvider(provider)) {
|
||||||
switch (provider.id) {
|
if (isOcrTesseractProvider(provider)) {
|
||||||
case 'tesseract':
|
return <OcrTesseractSettings provider={provider} updateConfig={updateConfig} />
|
||||||
return <OcrTesseractSettings />
|
|
||||||
case 'system':
|
|
||||||
return <OcrSystemSettings />
|
|
||||||
case 'paddleocr':
|
|
||||||
return <OcrPpocrSettings />
|
|
||||||
case 'ovocr':
|
|
||||||
return <OcrOVSettings />
|
|
||||||
default:
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
if (isOcrSystemProvider(provider)) {
|
||||||
|
return <OcrSystemSettings />
|
||||||
|
}
|
||||||
|
if (isOcrPpocrProvider(provider)) {
|
||||||
|
return <OcrPpocrSettings />
|
||||||
|
}
|
||||||
|
if (isOcrOVProvider(provider)) {
|
||||||
|
return <OcrOVSettings />
|
||||||
|
}
|
||||||
|
return null
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Not supported OCR provider')
|
throw new Error('Not supported OCR provider')
|
||||||
}
|
}
|
||||||
|
}, [provider, updateConfig])
|
||||||
|
|
||||||
|
if (!provider || (!isWin && !isMac && isOcrSystemProvider(provider))) {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -57,22 +66,15 @@ const OcrProviderSettings = ({ provider }: Props) => {
|
|||||||
<SettingTitle>
|
<SettingTitle>
|
||||||
<Flex className="items-center gap-2">
|
<Flex className="items-center gap-2">
|
||||||
<OcrProviderLogo provider={provider} />
|
<OcrProviderLogo provider={provider} />
|
||||||
<ProviderName> {getOcrProviderName(provider)}</ProviderName>
|
<span className="font-semibold text-sm"> {getOcrProviderName(provider)}</span>
|
||||||
</Flex>
|
</Flex>
|
||||||
</SettingTitle>
|
</SettingTitle>
|
||||||
<Divider style={{ width: '100%', margin: '10px 0' }} />
|
<Divider style={{ width: '100%', margin: '10px 0' }} />
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>{settings}</ErrorBoundary>
|
||||||
<ProviderSettings />
|
|
||||||
</ErrorBoundary>
|
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProviderName = styled.span`
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
`
|
|
||||||
|
|
||||||
const OcrProviderLogo = ({ provider: p, size = 14 }: { provider: OcrProvider; size?: number }) => {
|
const OcrProviderLogo = ({ provider: p, size = 14 }: { provider: OcrProvider; size?: number }) => {
|
||||||
if (isBuiltinOcrProvider(p)) {
|
if (isBuiltinOcrProvider(p)) {
|
||||||
switch (p.id) {
|
switch (p.id) {
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import OcrProviderSettings from './OcrProviderSettings'
|
|||||||
const OcrSettings: FC = () => {
|
const OcrSettings: FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { theme: themeMode } = useTheme()
|
const { theme: themeMode } = useTheme()
|
||||||
const { imageProvider: provider } = useOcrImageProvider()
|
const { imageProvider: provider, updateConfig } = useOcrImageProvider()
|
||||||
// const [activeTab, setActiveTab] = useState<Tab>('image')
|
// const [activeTab, setActiveTab] = useState<Tab>('image')
|
||||||
// const provider = useMemo(() => {
|
// const provider = useMemo(() => {
|
||||||
// switch (activeTab) {
|
// switch (activeTab) {
|
||||||
@ -82,7 +82,7 @@ const OcrSettings: FC = () => {
|
|||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
|
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<OcrProviderSettings provider={provider} />
|
<OcrProviderSettings provider={provider} updateConfig={updateConfig} />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -2,10 +2,9 @@
|
|||||||
import { Flex } from '@cherrystudio/ui'
|
import { Flex } from '@cherrystudio/ui'
|
||||||
import { InfoTooltip } from '@cherrystudio/ui'
|
import { InfoTooltip } from '@cherrystudio/ui'
|
||||||
import CustomTag from '@renderer/components/Tags/CustomTag'
|
import CustomTag from '@renderer/components/Tags/CustomTag'
|
||||||
import { useOcrProvider } from '@renderer/hooks/ocr/useOcrProvider'
|
|
||||||
import useTranslate from '@renderer/hooks/useTranslate'
|
import useTranslate from '@renderer/hooks/useTranslate'
|
||||||
import type { TesseractLangCode } from '@renderer/types'
|
import type { OcrProviderConfig, OcrTesseractConfig, OcrTesseractProvider, TesseractLangCode } from '@renderer/types'
|
||||||
import { BuiltinOcrProviderIdMap, isOcrTesseractProvider } from '@renderer/types'
|
import { objectEntries } from '@renderer/types'
|
||||||
import { TESSERACT_LANG_MAP } from '@shared/config/ocr'
|
import { TESSERACT_LANG_MAP } from '@shared/config/ocr'
|
||||||
import { Select } from 'antd'
|
import { Select } from 'antd'
|
||||||
import { useCallback, useMemo, useState } from 'react'
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
@ -15,15 +14,16 @@ import { SettingRow, SettingRowTitle } from '..'
|
|||||||
|
|
||||||
// const logger = loggerService.withContext('OcrTesseractSettings')
|
// const logger = loggerService.withContext('OcrTesseractSettings')
|
||||||
|
|
||||||
export const OcrTesseractSettings = () => {
|
export const OcrTesseractSettings = ({
|
||||||
|
provider,
|
||||||
|
updateConfig
|
||||||
|
}: {
|
||||||
|
provider: OcrTesseractProvider
|
||||||
|
updateConfig: (config: Partial<OcrProviderConfig>) => Promise<void>
|
||||||
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { provider, config, updateConfig } = useOcrProvider(BuiltinOcrProviderIdMap.tesseract)
|
|
||||||
|
|
||||||
if (!isOcrTesseractProvider(provider)) {
|
const [langs, setLangs] = useState<OcrTesseractConfig['langs'] | undefined>(provider?.config.langs)
|
||||||
throw new Error('Not tesseract provider.')
|
|
||||||
}
|
|
||||||
|
|
||||||
const [langs, setLangs] = useState<Partial<Record<TesseractLangCode, boolean>>>(config?.langs ?? {})
|
|
||||||
const { translateLanguages } = useTranslate()
|
const { translateLanguages } = useTranslate()
|
||||||
|
|
||||||
const options = useMemo(
|
const options = useMemo(
|
||||||
@ -37,14 +37,12 @@ export const OcrTesseractSettings = () => {
|
|||||||
[translateLanguages]
|
[translateLanguages]
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: type safe objectKeys
|
const selectedLangs = useMemo(() => {
|
||||||
const value = useMemo(
|
if (!langs) return
|
||||||
() =>
|
return objectEntries(langs)
|
||||||
Object.entries(langs)
|
.filter(([, enabled]) => enabled)
|
||||||
.filter(([, enabled]) => enabled)
|
.map(([lang]) => lang) as TesseractLangCode[]
|
||||||
.map(([lang]) => lang) as TesseractLangCode[],
|
}, [langs])
|
||||||
[langs]
|
|
||||||
)
|
|
||||||
|
|
||||||
const onChange = useCallback((values: TesseractLangCode[]) => {
|
const onChange = useCallback((values: TesseractLangCode[]) => {
|
||||||
setLangs(() => {
|
setLangs(() => {
|
||||||
@ -69,11 +67,11 @@ export const OcrTesseractSettings = () => {
|
|||||||
<InfoTooltip content={t('settings.tool.ocr.tesseract.langs_tooltip')} />
|
<InfoTooltip content={t('settings.tool.ocr.tesseract.langs_tooltip')} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</SettingRowTitle>
|
</SettingRowTitle>
|
||||||
<div style={{ display: 'flex', gap: '8px' }}>
|
<div className="flex gap-2">
|
||||||
<Select
|
<Select
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
style={{ minWidth: 200 }}
|
style={{ minWidth: 200 }}
|
||||||
value={value}
|
value={selectedLangs}
|
||||||
options={options}
|
options={options}
|
||||||
maxTagCount={1}
|
maxTagCount={1}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user