feat: add paddleocr parser (#12513)

### What problem does this PR solve?

Add PaddleOCR as a new PDF parser.

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
Lin Manhui
2026-01-09 17:48:45 +08:00
committed by GitHub
parent 6abf55c048
commit 2e09db02f3
34 changed files with 1510 additions and 453 deletions

View File

@ -504,3 +504,43 @@ export const useSubmitMinerU = () => {
mineruLoading: loading,
};
};
export const useSubmitPaddleOCR = () => {
const { addLlm, loading } = useAddLlm();
const {
visible: paddleocrVisible,
hideModal: hidePaddleOCRModal,
showModal: showPaddleOCRModal,
} = useSetModalState();
const onPaddleOCROk = useCallback(
async (payload: any) => {
const cfg: any = {
...payload,
};
const req: IAddLlmRequestBody = {
llm_factory: LLMFactory.PaddleOCR,
llm_name: payload.llm_name,
model_type: 'ocr',
api_key: cfg,
api_base: '',
max_tokens: 0,
};
const ret = await addLlm(req);
if (ret === 0) {
hidePaddleOCRModal();
return true;
}
return false;
},
[addLlm, hidePaddleOCRModal],
);
return {
paddleocrVisible,
hidePaddleOCRModal,
showPaddleOCRModal,
onPaddleOCROk,
paddleocrLoading: loading,
};
};

View File

@ -15,6 +15,7 @@ import {
useSubmitHunyuan,
useSubmitMinerU,
useSubmitOllama,
useSubmitPaddleOCR,
useSubmitSpark,
useSubmitSystemModelSetting,
useSubmitTencentCloud,
@ -28,6 +29,7 @@ import FishAudioModal from './modal/fish-audio-modal';
import GoogleModal from './modal/google-modal';
import HunyuanModal from './modal/hunyuan-modal';
import MinerUModal from './modal/mineru-modal';
import PaddleOCRModal from './modal/paddleocr-modal';
import TencentCloudModal from './modal/next-tencent-modal';
import OllamaModal from './modal/ollama-modal';
import SparkModal from './modal/spark-modal';
@ -138,6 +140,14 @@ const ModelProviders = () => {
mineruLoading,
} = useSubmitMinerU();
const {
paddleocrVisible,
hidePaddleOCRModal,
showPaddleOCRModal,
onPaddleOCROk,
paddleocrLoading,
} = useSubmitPaddleOCR();
const ModalMap = useMemo(
() => ({
[LLMFactory.Bedrock]: showBedrockAddingModal,
@ -150,6 +160,7 @@ const ModelProviders = () => {
[LLMFactory.GoogleCloud]: showGoogleAddingModal,
[LLMFactory.AzureOpenAI]: showAzureAddingModal,
[LLMFactory.MinerU]: showMineruModal,
[LLMFactory.PaddleOCR]: showPaddleOCRModal,
}),
[
showBedrockAddingModal,
@ -162,6 +173,7 @@ const ModelProviders = () => {
showGoogleAddingModal,
showAzureAddingModal,
showMineruModal,
showPaddleOCRModal,
],
);
@ -309,6 +321,12 @@ const ModelProviders = () => {
onOk={onMineruOk}
loading={mineruLoading}
></MinerUModal>
<PaddleOCRModal
visible={paddleocrVisible}
hideModal={hidePaddleOCRModal}
onOk={onPaddleOCROk}
loading={paddleocrLoading}
></PaddleOCRModal>
</div>
);
};

View File

@ -0,0 +1,135 @@
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import { t } from 'i18next';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { RAGFlowSelect, RAGFlowSelectOptionType } from '@/components/ui/select';
import { Input } from '@/components/ui/input';
import { Form } from '@/components/ui/form';
import { LLMHeader } from '../../components/llm-header';
import { LLMFactory } from '@/constants/llm';
const FormSchema = z.object({
llm_name: z.string().min(1, {
message: t('setting.paddleocr.modelNameRequired'),
}),
paddleocr_api_url: z.string().min(1, {
message: t('setting.paddleocr.apiUrlRequired'),
}),
paddleocr_access_token: z.string().optional(),
paddleocr_algorithm: z.string().default('PaddleOCR-VL'),
});
export type PaddleOCRFormValues = z.infer<typeof FormSchema>;
export interface IModalProps<T> {
visible: boolean;
hideModal: () => void;
onOk?: (data: T) => Promise<boolean>;
loading?: boolean;
}
const algorithmOptions: RAGFlowSelectOptionType[] = [
{ label: 'PaddleOCR-VL', value: 'PaddleOCR-VL' },
];
const PaddleOCRModal = ({
visible,
hideModal,
onOk,
loading,
}: IModalProps<PaddleOCRFormValues>) => {
const { t } = useTranslation();
const form = useForm<PaddleOCRFormValues>({
resolver: zodResolver(FormSchema),
defaultValues: {
paddleocr_algorithm: 'PaddleOCR-VL',
},
});
const handleOk = async (values: PaddleOCRFormValues) => {
const ret = await onOk?.(values as any);
if (ret) {
hideModal?.();
}
};
return (
<Dialog open={visible} onOpenChange={hideModal}>
<DialogContent>
<DialogHeader>
<DialogTitle>
<LLMHeader name={LLMFactory.PaddleOCR} />
</DialogTitle>
</DialogHeader>
<Form {...form}>
<form
onSubmit={form.handleSubmit(handleOk)}
className="space-y-6"
id="paddleocr-form"
>
<RAGFlowFormItem
name="llm_name"
label={t('setting.modelName')}
required
>
<Input placeholder={t('setting.paddleocr.modelNamePlaceholder')} />
</RAGFlowFormItem>
<RAGFlowFormItem
name="paddleocr_api_url"
label={t('setting.paddleocr.apiUrl')}
required
>
<Input placeholder={t('setting.paddleocr.apiUrlPlaceholder')} />
</RAGFlowFormItem>
<RAGFlowFormItem
name="paddleocr_access_token"
label={t('setting.paddleocr.accessToken')}
>
<Input placeholder={t('setting.paddleocr.accessTokenPlaceholder')} />
</RAGFlowFormItem>
<RAGFlowFormItem
name="paddleocr_algorithm"
label={t('setting.paddleocr.algorithm')}
>
{(field) => (
<RAGFlowSelect
value={field.value}
onChange={field.onChange}
options={algorithmOptions}
placeholder={t('setting.paddleocr.selectAlgorithm')}
/>
)}
</RAGFlowFormItem>
<div className="flex justify-end space-x-2">
<button
type="button"
onClick={hideModal}
className="btn btn-secondary"
>
{t('common.cancel')}
</button>
<button
type="submit"
disabled={loading}
className="btn btn-primary"
>
{loading ? t('common.adding') : t('common.add')}
</button>
</div>
</form>
</Form>
</DialogContent>
</Dialog>
);
};
export default PaddleOCRModal;