diff --git a/web/src/assets/svg/llm/mineru-bright.svg b/web/src/assets/svg/llm/mineru-bright.svg new file mode 100644 index 000000000..7b4c3257b --- /dev/null +++ b/web/src/assets/svg/llm/mineru-bright.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/assets/svg/llm/mineru-dark.svg b/web/src/assets/svg/llm/mineru-dark.svg new file mode 100644 index 000000000..755fe0f3c --- /dev/null +++ b/web/src/assets/svg/llm/mineru-dark.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/components/layout-recognize-form-field.tsx b/web/src/components/layout-recognize-form-field.tsx index 2b2dc7eda..efaaba52f 100644 --- a/web/src/components/layout-recognize-form-field.tsx +++ b/web/src/components/layout-recognize-form-field.tsx @@ -17,7 +17,6 @@ import { export const enum ParseDocumentType { DeepDOC = 'DeepDOC', PlainText = 'Plain Text', - MinerU = 'MinerU', Docling = 'Docling', TCADPParser = 'TCADP Parser', } @@ -44,7 +43,6 @@ export function LayoutRecognizeFormField({ : [ ParseDocumentType.DeepDOC, ParseDocumentType.PlainText, - ParseDocumentType.MinerU, ParseDocumentType.Docling, ParseDocumentType.TCADPParser, ].map((x) => ({ @@ -52,7 +50,10 @@ export function LayoutRecognizeFormField({ value: x, })); - const image2TextList = allOptions[LlmModelType.Image2text].map((x) => { + const image2TextList = [ + ...allOptions[LlmModelType.Image2text], + ...allOptions[LlmModelType.Ocr], + ].map((x) => { return { ...x, options: x.options.map((y) => { diff --git a/web/src/components/svg-icon.tsx b/web/src/components/svg-icon.tsx index d21e55ace..756336621 100644 --- a/web/src/components/svg-icon.tsx +++ b/web/src/components/svg-icon.tsx @@ -69,6 +69,7 @@ export const LlmIcon = ({ LLMFactory.TogetherAI, LLMFactory.Meituan, LLMFactory.Longcat, + LLMFactory.MinerU, ]; let icon = useMemo(() => { const icontemp = IconMap[name as keyof typeof IconMap]; @@ -88,6 +89,7 @@ export const LlmIcon = ({ // LLMFactory.MiniMax, LLMFactory.Gemini, LLMFactory.StepFun, + LLMFactory.MinerU, // LLMFactory.DeerAPI, ]; if (svgIcons.includes(name as LLMFactory)) { diff --git a/web/src/constants/knowledge.ts b/web/src/constants/knowledge.ts index 130b7ed91..afd2e218b 100644 --- a/web/src/constants/knowledge.ts +++ b/web/src/constants/knowledge.ts @@ -62,6 +62,7 @@ export enum LlmModelType { Speech2text = 'speech2text', Rerank = 'rerank', TTS = 'tts', + Ocr = 'ocr', } export enum KnowledgeSearchParams { diff --git a/web/src/constants/llm.ts b/web/src/constants/llm.ts index a5f5e4b82..1ff5f5387 100644 --- a/web/src/constants/llm.ts +++ b/web/src/constants/llm.ts @@ -60,6 +60,7 @@ export enum LLMFactory { DeerAPI = 'DeerAPI', JiekouAI = 'Jiekou.AI', Builtin = 'Builtin', + MinerU = 'MinerU', } // Please lowercase the file name @@ -125,6 +126,7 @@ export const IconMap = { [LLMFactory.DeerAPI]: 'deerapi', [LLMFactory.JiekouAI]: 'jiekouai', [LLMFactory.Builtin]: 'builtin', + [LLMFactory.MinerU]: 'mineru', }; export const APIMapUrl = { diff --git a/web/src/hooks/use-llm-request.tsx b/web/src/hooks/use-llm-request.tsx index 3436b7506..cdd46c222 100644 --- a/web/src/hooks/use-llm-request.tsx +++ b/web/src/hooks/use-llm-request.tsx @@ -147,6 +147,7 @@ export const useSelectLlmOptionsByModelType = () => { ), [LlmModelType.Rerank]: groupOptionsByModelType(LlmModelType.Rerank), [LlmModelType.TTS]: groupOptionsByModelType(LlmModelType.TTS), + [LlmModelType.Ocr]: groupOptionsByModelType(LlmModelType.Ocr), }; }; @@ -245,7 +246,7 @@ export const useSelectLlmList = () => { name: key, logo: factoryList.find((x) => x.name === key)?.logo ?? '', ...value, - llm: value.llm.map((x) => ({ ...x, name: x.name })), + llm: value.llm?.map((x) => ({ ...x, name: x.name })), })); }, [myLlmList, factoryList]); diff --git a/web/src/interfaces/request/llm.ts b/web/src/interfaces/request/llm.ts index 05f8f470e..a5ca42fdc 100644 --- a/web/src/interfaces/request/llm.ts +++ b/web/src/interfaces/request/llm.ts @@ -3,7 +3,7 @@ export interface IAddLlmRequestBody { llm_name: string; model_type: string; api_base?: string; // chat|embedding|speech2text|image2text - api_key: string; + api_key: string | Record; max_tokens: number; } diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index 6d1049eda..00b8552ca 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -1064,6 +1064,21 @@ Example: Virtual Hosted Style`, modelsToBeAddedTooltip: 'If your model provider is not listed but claims to be "OpenAI-compatible", select the OpenAI-API-compatible card to add the relevant model(s). ', mcp: 'MCP', + mineru: { + modelNameRequired: 'Model name is required', + apiserver: 'MinerU API Server Configuration', + outputDir: 'MinerU Output Directory Path', + backend: 'MinerU Processing Backend Type', + serverUrl: 'MinerU Server URL Address', + deleteOutput: 'Delete Output Files After Processing', + selectBackend: 'Select processing backend', + backendOptions: { + pipeline: 'Standard Pipeline Processing', + vlmTransformers: 'Vision Language Model with Transformers', + vlmVllmEngine: 'Vision Language Model with vLLM Engine', + vlmHttpClient: 'Vision Language Model via HTTP Client', + }, + }, }, message: { registered: 'Registered!', diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index c06ec2886..b09b6ca21 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -936,6 +936,21 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 modelsToBeAddedTooltip: '如果你的模型供应商在这里没有列出,但是宣称 OpenAI-compatible,可以通过选择卡片 OpenAI-API-compatible 设置相关模型。', mcp: 'MCP', + mineru: { + modelNameRequired: '模型名称为必填项', + apiserver: 'MinerU API服务器配置', + outputDir: 'MinerU输出目录路径', + backend: 'MinerU处理后端类型', + serverUrl: 'MinerU服务器URL地址', + deleteOutput: '处理完成后删除输出文件', + selectBackend: '选择处理后端', + backendOptions: { + pipeline: '标准流水线处理', + vlmTransformers: '基于Transformers的视觉语言模型', + vlmVllmEngine: '基于vLLM引擎的视觉语言模型', + vlmHttpClient: '通过HTTP客户端连接的视觉语言模型', + }, + }, }, message: { registered: '注册成功', diff --git a/web/src/pages/user-setting/setting-model/components/modal-card.tsx b/web/src/pages/user-setting/setting-model/components/modal-card.tsx index 3eac19d92..70a232147 100644 --- a/web/src/pages/user-setting/setting-model/components/modal-card.tsx +++ b/web/src/pages/user-setting/setting-model/components/modal-card.tsx @@ -73,7 +73,7 @@ export const ModelProviderCard: FC = ({ {/* Header */}
- +
{item.name} diff --git a/web/src/pages/user-setting/setting-model/components/used-model.tsx b/web/src/pages/user-setting/setting-model/components/used-model.tsx index 3973f6fb6..91c7bc066 100644 --- a/web/src/pages/user-setting/setting-model/components/used-model.tsx +++ b/web/src/pages/user-setting/setting-model/components/used-model.tsx @@ -9,7 +9,7 @@ export const UsedModel = ({ handleAddModel: (factory: string) => void; handleEditModel: (model: any, factory: LlmItem) => void; }) => { - const { factoryList, myLlmList: llmList, loading } = useSelectLlmList(); + const { myLlmList: llmList } = useSelectLlmList(); return (
diff --git a/web/src/pages/user-setting/setting-model/hooks.tsx b/web/src/pages/user-setting/setting-model/hooks.tsx index 4d0708a42..9fc620d3d 100644 --- a/web/src/pages/user-setting/setting-model/hooks.tsx +++ b/web/src/pages/user-setting/setting-model/hooks.tsx @@ -1,3 +1,4 @@ +import { LLMFactory } from '@/constants/llm'; import { useSetModalState, useShowDeleteConfirm } from '@/hooks/common-hooks'; import { IApiKeySavingParams, @@ -16,6 +17,7 @@ import { getRealModelName } from '@/utils/llm-util'; import { useQueryClient } from '@tanstack/react-query'; import { useCallback, useState } from 'react'; import { ApiKeyPostBody } from '../interface'; +import { MinerUFormValues } from './modal/mineru-modal'; type SavingParamsState = Omit; @@ -459,3 +461,42 @@ export const useHandleDeleteFactory = (llmFactory: string) => { return { handleDeleteFactory, deleteFactory }; }; + +export const useSubmitMinerU = () => { + const { addLlm, loading } = useAddLlm(); + const { + visible: mineruVisible, + hideModal: hideMineruModal, + showModal: showMineruModal, + } = useSetModalState(); + + const onMineruOk = useCallback( + async (payload: MinerUFormValues) => { + const cfg = { + ...payload, + mineru_delete_output: payload.mineru_delete_output ?? true ? '1' : '0', + }; + const req: IAddLlmRequestBody = { + llm_factory: LLMFactory.MinerU, + llm_name: payload.llm_name, + model_type: 'ocr', + api_key: cfg, + api_base: '', + max_tokens: 0, + }; + const ret = await addLlm(req); + if (ret === 0) { + hideMineruModal(); + } + }, + [addLlm, hideMineruModal], + ); + + return { + mineruVisible, + hideMineruModal, + showMineruModal, + onMineruOk, + mineruLoading: loading, + }; +}; diff --git a/web/src/pages/user-setting/setting-model/index.tsx b/web/src/pages/user-setting/setting-model/index.tsx index 1e7086019..af7907bb0 100644 --- a/web/src/pages/user-setting/setting-model/index.tsx +++ b/web/src/pages/user-setting/setting-model/index.tsx @@ -13,6 +13,7 @@ import { useSubmitFishAudio, useSubmitGoogle, useSubmitHunyuan, + useSubmitMinerU, useSubmitOllama, useSubmitSpark, useSubmitSystemModelSetting, @@ -26,6 +27,7 @@ import BedrockModal from './modal/bedrock-modal'; 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 TencentCloudModal from './modal/next-tencent-modal'; import OllamaModal from './modal/ollama-modal'; import SparkModal from './modal/spark-modal'; @@ -128,6 +130,14 @@ const ModelProviders = () => { AzureAddingLoading, } = useSubmitAzure(); + const { + mineruVisible, + hideMineruModal, + showMineruModal, + onMineruOk, + mineruLoading, + } = useSubmitMinerU(); + const ModalMap = useMemo( () => ({ [LLMFactory.Bedrock]: showBedrockAddingModal, @@ -139,17 +149,19 @@ const ModelProviders = () => { [LLMFactory.TencentCloud]: showTencentCloudAddingModal, [LLMFactory.GoogleCloud]: showGoogleAddingModal, [LLMFactory.AzureOpenAI]: showAzureAddingModal, + [LLMFactory.MinerU]: showMineruModal, }), [ showBedrockAddingModal, showVolcAddingModal, showHunyuanAddingModal, - showTencentCloudAddingModal, showSparkAddingModal, showyiyanAddingModal, showFishAudioAddingModal, + showTencentCloudAddingModal, showGoogleAddingModal, showAzureAddingModal, + showMineruModal, ], ); @@ -289,6 +301,12 @@ const ModelProviders = () => { loading={AzureAddingLoading} llmFactory={LLMFactory.AzureOpenAI} > +
); }; diff --git a/web/src/pages/user-setting/setting-model/modal/mineru-modal/index.tsx b/web/src/pages/user-setting/setting-model/modal/mineru-modal/index.tsx new file mode 100644 index 000000000..7833467db --- /dev/null +++ b/web/src/pages/user-setting/setting-model/modal/mineru-modal/index.tsx @@ -0,0 +1,148 @@ +import { RAGFlowFormItem } from '@/components/ragflow-form'; +import { ButtonLoading } from '@/components/ui/button'; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { Form } from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { RAGFlowSelect } from '@/components/ui/select'; +import { Switch } from '@/components/ui/switch'; +import { LLMFactory } from '@/constants/llm'; +import { IModalProps } from '@/interfaces/common'; +import { buildOptions } from '@/utils/form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { t } from 'i18next'; +import { useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; +import { z } from 'zod'; +import { LLMHeader } from '../../components/llm-header'; + +const FormSchema = z.object({ + llm_name: z.string().min(1, { + message: t('setting.mineru.modelNameRequired'), + }), + mineru_apiserver: z.string().optional(), + mineru_output_dir: z.string().optional(), + mineru_backend: z.enum([ + 'pipeline', + 'vlm-transformers', + 'vlm-vllm-engine', + 'vlm-http-client', + ]), + mineru_server_url: z.string().optional(), + mineru_delete_output: z.boolean(), +}); + +export type MinerUFormValues = z.infer; + +const MinerUModal = ({ + visible, + hideModal, + onOk, + loading, +}: IModalProps) => { + const { t } = useTranslation(); + + const backendOptions = buildOptions([ + 'pipeline', + 'vlm-transformers', + 'vlm-vllm-engine', + 'vlm-http-client', + ]); + + const form = useForm({ + resolver: zodResolver(FormSchema), + defaultValues: { + mineru_backend: 'pipeline', + mineru_delete_output: true, + }, + }); + + const handleOk = async (values: MinerUFormValues) => { + const ret = await onOk?.(values as any); + if (ret) { + hideModal?.(); + } + }; + + return ( + + + + + + + +
+ + + + + + + + + + + + {(field) => ( + + )} + + + + + + {(field) => ( + + )} + +
+ + + + {t('common.save', 'Save')} + + +
+
+ ); +}; + +export default MinerUModal;