Fix: Fixed the styling and logic issues on the model provider page #10703 (#10909)

### What problem does this PR solve?

Fix: Fixed the styling and logic issues on the model provider page

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
chanx
2025-10-31 13:42:28 +08:00
committed by GitHub
parent c8a82da722
commit d8a7fb6f2b
24 changed files with 265 additions and 175 deletions

View File

@ -0,0 +1,174 @@
import { IModalManagerChildrenProps } from '@/components/modal-manager';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Modal } from '@/components/ui/modal/modal';
import { LLMFactory } from '@/constants/llm';
import { useTranslate } from '@/hooks/common-hooks';
import { KeyboardEventHandler, useCallback, useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { ApiKeyPostBody } from '../../../interface';
interface IProps extends Omit<IModalManagerChildrenProps, 'showModal'> {
loading: boolean;
initialValue: string;
llmFactory: string;
editMode?: boolean;
onOk: (postBody: ApiKeyPostBody) => void;
showModal?(): void;
}
type FieldType = {
api_key?: string;
base_url?: string;
group_id?: string;
};
const modelsWithBaseUrl = [
LLMFactory.OpenAI,
LLMFactory.AzureOpenAI,
LLMFactory.TongYiQianWen,
];
const ApiKeyModal = ({
visible,
hideModal,
llmFactory,
loading,
initialValue,
editMode = false,
onOk,
}: IProps) => {
const form = useForm<FieldType>();
const { t } = useTranslate('setting');
const handleOk = useCallback(async () => {
await form.handleSubmit((values) => onOk(values))();
}, [form, onOk]);
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = useCallback(
async (e) => {
if (e.key === 'Enter') {
await handleOk();
}
},
[handleOk],
);
useEffect(() => {
if (visible) {
form.setValue('api_key', initialValue);
}
}, [initialValue, form, visible]);
return (
<Modal
title={editMode ? t('editModel') : t('modify')}
open={visible}
onOpenChange={(open) => !open && hideModal()}
onOk={handleOk}
onCancel={hideModal}
confirmLoading={loading}
okText={t('save')}
cancelText={t('cancel')}
>
<Form {...form}>
<div className="space-y-4 py-4">
<FormField
name="api_key"
rules={{ required: t('apiKeyMessage') }}
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium text-text-primary">
{t('apiKey')}
<span className="ml-1 text-destructive">*</span>
</FormLabel>
<FormControl>
<Input
{...field}
onKeyDown={handleKeyDown}
className="w-full"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{modelsWithBaseUrl.some((x) => x === llmFactory) && (
<FormField
name="base_url"
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium text-text-primary">
{t('baseUrl')}
</FormLabel>
<FormControl>
<Input
{...field}
placeholder={
llmFactory === LLMFactory.TongYiQianWen
? t('tongyiBaseUrlPlaceholder')
: 'https://api.openai.com/v1'
}
onKeyDown={handleKeyDown}
className="w-full"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
{llmFactory?.toLowerCase() === 'Anthropic'.toLowerCase() && (
<FormField
name="base_url"
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium text-text-primary">
{t('baseUrl')}
</FormLabel>
<FormControl>
<Input
{...field}
placeholder="https://api.anthropic.com/v1"
onKeyDown={handleKeyDown}
className="w-full"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
{llmFactory?.toLowerCase() === 'Minimax'.toLowerCase() && (
<FormField
name="group_id"
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium text-text-primary">
Group ID
</FormLabel>
<FormControl>
<Input {...field} className="w-full" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
</div>
</Form>
</Modal>
);
};
export default ApiKeyModal;

View File

@ -0,0 +1,169 @@
import { useTranslate } from '@/hooks/common-hooks';
import { IModalProps } from '@/interfaces/common';
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
import { Form, Input, InputNumber, Modal, Select, Switch } from 'antd';
import omit from 'lodash/omit';
type FieldType = IAddLlmRequestBody & {
api_version: string;
vision: boolean;
};
const { Option } = Select;
const AzureOpenAIModal = ({
visible,
hideModal,
onOk,
loading,
llmFactory,
}: IModalProps<IAddLlmRequestBody> & { llmFactory: string }) => {
const [form] = Form.useForm<FieldType>();
const { t } = useTranslate('setting');
const handleOk = async () => {
const values = await form.validateFields();
const modelType =
values.model_type === 'chat' && values.vision
? 'image2text'
: values.model_type;
const data = {
...omit(values, ['vision']),
model_type: modelType,
llm_factory: llmFactory,
max_tokens: values.max_tokens,
};
console.info(data);
onOk?.(data);
};
const optionsMap = {
Default: [
{ value: 'chat', label: 'chat' },
{ value: 'embedding', label: 'embedding' },
{ value: 'image2text', label: 'image2text' },
],
};
const getOptions = () => {
return optionsMap.Default;
};
const handleKeyDown = async (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
await handleOk();
}
};
return (
<Modal
title={t('addLlmTitle', { name: llmFactory })}
open={visible}
onOk={handleOk}
onCancel={hideModal}
okButtonProps={{ loading }}
>
<Form
name="basic"
style={{ maxWidth: 600 }}
autoComplete="off"
layout={'vertical'}
form={form}
>
<Form.Item<FieldType>
label={t('modelType')}
name="model_type"
initialValue={'embedding'}
rules={[{ required: true, message: t('modelTypeMessage') }]}
>
<Select placeholder={t('modelTypeMessage')}>
{getOptions(llmFactory).map((option) => (
<Option key={option.value} value={option.value}>
{option.label}
</Option>
))}
</Select>
</Form.Item>
<Form.Item<FieldType>
label={t('addLlmBaseUrl')}
name="api_base"
rules={[{ required: true, message: t('baseUrlNameMessage') }]}
>
<Input
placeholder={t('baseUrlNameMessage')}
onKeyDown={handleKeyDown}
/>
</Form.Item>
<Form.Item<FieldType>
label={t('apiKey')}
name="api_key"
rules={[{ required: false, message: t('apiKeyMessage') }]}
>
<Input placeholder={t('apiKeyMessage')} onKeyDown={handleKeyDown} />
</Form.Item>
<Form.Item<FieldType>
label={t('modelName')}
name="llm_name"
initialValue="gpt-3.5-turbo"
rules={[{ required: true, message: t('modelNameMessage') }]}
>
<Input
placeholder={t('modelNameMessage')}
onKeyDown={handleKeyDown}
/>
</Form.Item>
<Form.Item<FieldType>
label={t('apiVersion')}
name="api_version"
initialValue="2024-02-01"
rules={[{ required: false, message: t('apiVersionMessage') }]}
>
<Input
placeholder={t('apiVersionMessage')}
onKeyDown={handleKeyDown}
/>
</Form.Item>
<Form.Item<FieldType>
label={t('maxTokens')}
name="max_tokens"
rules={[
{ required: true, message: t('maxTokensMessage') },
{
type: 'number',
message: t('maxTokensInvalidMessage'),
},
({}) => ({
validator(_, value) {
if (value < 0) {
return Promise.reject(new Error(t('maxTokensMinMessage')));
}
return Promise.resolve();
},
}),
]}
>
<InputNumber
placeholder={t('maxTokensTip')}
style={{ width: '100%' }}
/>
</Form.Item>
<Form.Item noStyle dependencies={['model_type']}>
{({ getFieldValue }) =>
getFieldValue('model_type') === 'chat' && (
<Form.Item
label={t('vision')}
valuePropName="checked"
name={'vision'}
>
<Switch />
</Form.Item>
)
}
</Form.Item>
</Form>
</Modal>
);
};
export default AzureOpenAIModal;

View File

@ -0,0 +1,144 @@
import { useTranslate } from '@/hooks/common-hooks';
import { IModalProps } from '@/interfaces/common';
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
import { Flex, Form, Input, InputNumber, Modal, Select, Space } from 'antd';
import { useMemo } from 'react';
import { BedrockRegionList } from '../../constant';
type FieldType = IAddLlmRequestBody & {
bedrock_ak: string;
bedrock_sk: string;
bedrock_region: string;
};
const { Option } = Select;
const BedrockModal = ({
visible,
hideModal,
onOk,
loading,
llmFactory,
}: IModalProps<IAddLlmRequestBody> & { llmFactory: string }) => {
const [form] = Form.useForm<FieldType>();
const { t } = useTranslate('setting');
const options = useMemo(
() => BedrockRegionList.map((x) => ({ value: x, label: t(x) })),
[t],
);
const handleOk = async () => {
const values = await form.validateFields();
const data = {
...values,
llm_factory: llmFactory,
max_tokens: values.max_tokens,
};
onOk?.(data);
};
return (
<Modal
title={t('addLlmTitle', { name: llmFactory })}
open={visible}
onOk={handleOk}
onCancel={hideModal}
okButtonProps={{ loading }}
footer={(originNode: React.ReactNode) => {
return (
<Flex justify={'space-between'}>
<a
href="https://console.aws.amazon.com/"
target="_blank"
rel="noreferrer"
>
{t('ollamaLink', { name: llmFactory })}
</a>
<Space>{originNode}</Space>
</Flex>
);
}}
>
<Form
name="basic"
style={{ maxWidth: 600 }}
autoComplete="off"
layout={'vertical'}
form={form}
>
<Form.Item<FieldType>
label={t('modelType')}
name="model_type"
initialValue={'chat'}
rules={[{ required: true, message: t('modelTypeMessage') }]}
>
<Select placeholder={t('modelTypeMessage')}>
<Option value="chat">chat</Option>
<Option value="embedding">embedding</Option>
</Select>
</Form.Item>
<Form.Item<FieldType>
label={t('modelName')}
name="llm_name"
rules={[{ required: true, message: t('bedrockModelNameMessage') }]}
>
<Input placeholder={t('bedrockModelNameMessage')} />
</Form.Item>
<Form.Item<FieldType>
label={t('addBedrockEngineAK')}
name="bedrock_ak"
rules={[{ required: true, message: t('bedrockAKMessage') }]}
>
<Input placeholder={t('bedrockAKMessage')} />
</Form.Item>
<Form.Item<FieldType>
label={t('addBedrockSK')}
name="bedrock_sk"
rules={[{ required: true, message: t('bedrockSKMessage') }]}
>
<Input placeholder={t('bedrockSKMessage')} />
</Form.Item>
<Form.Item<FieldType>
label={t('bedrockRegion')}
name="bedrock_region"
rules={[{ required: true, message: t('bedrockRegionMessage') }]}
>
<Select
placeholder={t('bedrockRegionMessage')}
options={options}
allowClear
></Select>
</Form.Item>
<Form.Item<FieldType>
label={t('maxTokens')}
name="max_tokens"
rules={[
{ required: true, message: t('maxTokensMessage') },
{
type: 'number',
message: t('maxTokensInvalidMessage'),
},
({}) => ({
validator(_, value) {
if (value < 0) {
return Promise.reject(new Error(t('maxTokensMinMessage')));
}
return Promise.resolve();
},
}),
]}
>
<InputNumber
placeholder={t('maxTokensTip')}
style={{ width: '100%' }}
/>
</Form.Item>
</Form>
</Modal>
);
};
export default BedrockModal;

View File

@ -0,0 +1,126 @@
import { useTranslate } from '@/hooks/common-hooks';
import { IModalProps } from '@/interfaces/common';
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
import { Flex, Form, Input, InputNumber, Modal, Select, Space } from 'antd';
import omit from 'lodash/omit';
type FieldType = IAddLlmRequestBody & {
fish_audio_ak: string;
fish_audio_refid: string;
};
const { Option } = Select;
const FishAudioModal = ({
visible,
hideModal,
onOk,
loading,
llmFactory,
}: IModalProps<IAddLlmRequestBody> & { llmFactory: string }) => {
const [form] = Form.useForm<FieldType>();
const { t } = useTranslate('setting');
const handleOk = async () => {
const values = await form.validateFields();
const modelType = values.model_type;
const data = {
...omit(values),
model_type: modelType,
llm_factory: llmFactory,
max_tokens: values.max_tokens,
};
console.info(data);
onOk?.(data);
};
return (
<Modal
title={t('addLlmTitle', { name: llmFactory })}
open={visible}
onOk={handleOk}
onCancel={hideModal}
okButtonProps={{ loading }}
footer={(originNode: React.ReactNode) => {
return (
<Flex justify={'space-between'}>
<a href={`https://fish.audio`} target="_blank" rel="noreferrer">
{t('FishAudioLink')}
</a>
<Space>{originNode}</Space>
</Flex>
);
}}
confirmLoading={loading}
>
<Form
name="basic"
style={{ maxWidth: 600 }}
autoComplete="off"
layout={'vertical'}
form={form}
>
<Form.Item<FieldType>
label={t('modelType')}
name="model_type"
initialValue={'tts'}
rules={[{ required: true, message: t('modelTypeMessage') }]}
>
<Select placeholder={t('modelTypeMessage')}>
<Option value="tts">tts</Option>
</Select>
</Form.Item>
<Form.Item<FieldType>
label={t('modelName')}
name="llm_name"
rules={[{ required: true, message: t('FishAudioModelNameMessage') }]}
>
<Input placeholder={t('FishAudioModelNameMessage')} />
</Form.Item>
<Form.Item<FieldType>
label={t('addFishAudioAK')}
name="fish_audio_ak"
rules={[{ required: true, message: t('FishAudioAKMessage') }]}
>
<Input placeholder={t('FishAudioAKMessage')} />
</Form.Item>
<Form.Item<FieldType>
label={t('addFishAudioRefID')}
name="fish_audio_refid"
rules={[{ required: true, message: t('FishAudioRefIDMessage') }]}
>
<Input placeholder={t('FishAudioRefIDMessage')} />
</Form.Item>
<Form.Item<FieldType>
label={t('maxTokens')}
name="max_tokens"
rules={[
{ required: true, message: t('maxTokensMessage') },
{
type: 'number',
message: t('maxTokensInvalidMessage'),
},
({}) => ({
validator(_, value) {
if (value < 0) {
return Promise.reject(new Error(t('maxTokensMinMessage')));
}
return Promise.resolve();
},
}),
]}
>
<InputNumber
placeholder={t('maxTokensTip')}
style={{ width: '100%' }}
/>
</Form.Item>
</Form>
</Modal>
);
};
export default FishAudioModal;

View File

@ -0,0 +1,133 @@
import { useTranslate } from '@/hooks/common-hooks';
import { IModalProps } from '@/interfaces/common';
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
import { Form, Input, InputNumber, Modal, Select } from 'antd';
type FieldType = IAddLlmRequestBody & {
google_project_id: string;
google_region: string;
google_service_account_key: string;
};
const { Option } = Select;
const GoogleModal = ({
visible,
hideModal,
onOk,
loading,
llmFactory,
}: IModalProps<IAddLlmRequestBody> & { llmFactory: string }) => {
const [form] = Form.useForm<FieldType>();
const { t } = useTranslate('setting');
const handleOk = async () => {
const values = await form.validateFields();
const data = {
...values,
llm_factory: llmFactory,
max_tokens: values.max_tokens,
};
onOk?.(data);
};
const handleKeyDown = async (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
await handleOk();
}
};
return (
<Modal
title={t('addLlmTitle', { name: llmFactory })}
open={visible}
onOk={handleOk}
onCancel={hideModal}
okButtonProps={{ loading }}
>
<Form form={form}>
<Form.Item<FieldType>
label={t('modelType')}
name="model_type"
initialValue={'chat'}
rules={[{ required: true, message: t('modelTypeMessage') }]}
>
<Select placeholder={t('modelTypeMessage')}>
<Option value="chat">chat</Option>
<Option value="image2text">image2text</Option>
</Select>
</Form.Item>
<Form.Item<FieldType>
label={t('modelID')}
name="llm_name"
rules={[{ required: true, message: t('GoogleModelIDMessage') }]}
>
<Input
placeholder={t('GoogleModelIDMessage')}
onKeyDown={handleKeyDown}
/>
</Form.Item>
<Form.Item<FieldType>
label={t('addGoogleProjectID')}
name="google_project_id"
rules={[{ required: true, message: t('GoogleProjectIDMessage') }]}
>
<Input
placeholder={t('GoogleProjectIDMessage')}
onKeyDown={handleKeyDown}
/>
</Form.Item>
<Form.Item<FieldType>
label={t('addGoogleRegion')}
name="google_region"
rules={[{ required: true, message: t('GoogleRegionMessage') }]}
>
<Input
placeholder={t('GoogleRegionMessage')}
onKeyDown={handleKeyDown}
/>
</Form.Item>
<Form.Item<FieldType>
label={t('addGoogleServiceAccountKey')}
name="google_service_account_key"
rules={[
{ required: true, message: t('GoogleServiceAccountKeyMessage') },
]}
>
<Input
placeholder={t('GoogleServiceAccountKeyMessage')}
onKeyDown={handleKeyDown}
/>
</Form.Item>
<Form.Item<FieldType>
label={t('maxTokens')}
name="max_tokens"
rules={[
{ required: true, message: t('maxTokensMessage') },
{
type: 'number',
message: t('maxTokensInvalidMessage'),
},
({}) => ({
validator(_, value) {
if (value < 0) {
return Promise.reject(new Error(t('maxTokensMinMessage')));
}
return Promise.resolve();
},
}),
]}
>
<InputNumber
placeholder={t('maxTokensTip')}
style={{ width: '100%' }}
/>
</Form.Item>
</Form>
</Modal>
);
};
export default GoogleModal;

View File

@ -0,0 +1,88 @@
import { useTranslate } from '@/hooks/common-hooks';
import { IModalProps } from '@/interfaces/common';
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
import { Form, Input, Modal } from 'antd';
import omit from 'lodash/omit';
type FieldType = IAddLlmRequestBody & {
vision: boolean;
hunyuan_sid: string;
hunyuan_sk: string;
};
const HunyuanModal = ({
visible,
hideModal,
onOk,
loading,
llmFactory,
}: IModalProps<IAddLlmRequestBody> & { llmFactory: string }) => {
const [form] = Form.useForm<FieldType>();
const { t } = useTranslate('setting');
const handleOk = async () => {
const values = await form.validateFields();
const modelType =
values.model_type === 'chat' && values.vision
? 'image2text'
: values.model_type;
const data = {
...omit(values, ['vision']),
model_type: modelType,
llm_factory: llmFactory,
};
console.info(data);
onOk?.(data);
};
const handleKeyDown = async (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
await handleOk();
}
};
return (
<Modal
title={t('addLlmTitle', { name: llmFactory })}
open={visible}
onOk={handleOk}
onCancel={hideModal}
okButtonProps={{ loading }}
confirmLoading={loading}
>
<Form
name="basic"
style={{ maxWidth: 600 }}
autoComplete="off"
layout={'vertical'}
form={form}
>
<Form.Item<FieldType>
label={t('addHunyuanSID')}
name="hunyuan_sid"
rules={[{ required: true, message: t('HunyuanSIDMessage') }]}
>
<Input
placeholder={t('HunyuanSIDMessage')}
onKeyDown={handleKeyDown}
/>
</Form.Item>
<Form.Item<FieldType>
label={t('addHunyuanSK')}
name="hunyuan_sk"
rules={[{ required: true, message: t('HunyuanSKMessage') }]}
>
<Input
placeholder={t('HunyuanSKMessage')}
onKeyDown={handleKeyDown}
/>
</Form.Item>
</Form>
</Modal>
);
};
export default HunyuanModal;

View File

@ -0,0 +1,136 @@
import { useTranslate } from '@/hooks/common-hooks';
import { IModalProps } from '@/interfaces/common';
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
import { Flex, Form, Input, Modal, Select, Space } from 'antd';
import omit from 'lodash/omit';
type FieldType = IAddLlmRequestBody & {
TencentCloud_sid: string;
TencentCloud_sk: string;
};
const { Option } = Select;
const TencentCloudModal = ({
visible,
hideModal,
onOk,
loading,
llmFactory,
}: IModalProps<IAddLlmRequestBody> & { llmFactory: string }) => {
const [form] = Form.useForm<FieldType>();
const { t } = useTranslate('setting');
const handleOk = async () => {
const values = await form.validateFields();
const modelType = values.model_type;
const data = {
...omit(values),
model_type: modelType,
llm_factory: llmFactory,
max_tokens: 16000,
};
console.info(data);
onOk?.(data);
};
const handleKeyDown = async (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
await handleOk();
}
};
return (
<Modal
title={t('addLlmTitle', { name: llmFactory })}
open={visible}
onOk={handleOk}
onCancel={hideModal}
okButtonProps={{ loading }}
footer={(originNode: React.ReactNode) => {
return (
<Flex justify={'space-between'}>
<a
href={`https://cloud.tencent.com/document/api/1093/37823`}
target="_blank"
rel="noreferrer"
>
{t('TencentCloudLink')}
</a>
<Space>{originNode}</Space>
</Flex>
);
}}
confirmLoading={loading}
>
<Form>
<Form.Item<FieldType>
label={t('modelType')}
name="model_type"
initialValue={'speech2text'}
rules={[{ required: true, message: t('modelTypeMessage') }]}
>
<Select placeholder={t('modelTypeMessage')}>
<Option value="speech2text">speech2text</Option>
</Select>
</Form.Item>
<Form.Item<FieldType>
label={t('modelName')}
name="llm_name"
initialValue={'16k_zh'}
rules={[{ required: true, message: t('SparkModelNameMessage') }]}
>
<Select placeholder={t('modelTypeMessage')}>
<Option value="16k_zh">16k_zh</Option>
<Option value="16k_zh_large">16k_zh_large</Option>
<Option value="16k_multi_lang">16k_multi_lang</Option>
<Option value="16k_zh_dialect">16k_zh_dialect</Option>
<Option value="16k_en">16k_en</Option>
<Option value="16k_yue">16k_yue</Option>
<Option value="16k_zh-PY">16k_zh-PY</Option>
<Option value="16k_ja">16k_ja</Option>
<Option value="16k_ko">16k_ko</Option>
<Option value="16k_vi">16k_vi</Option>
<Option value="16k_ms">16k_ms</Option>
<Option value="16k_id">16k_id</Option>
<Option value="16k_fil">16k_fil</Option>
<Option value="16k_th">16k_th</Option>
<Option value="16k_pt">16k_pt</Option>
<Option value="16k_tr">16k_tr</Option>
<Option value="16k_ar">16k_ar</Option>
<Option value="16k_es">16k_es</Option>
<Option value="16k_hi">16k_hi</Option>
<Option value="16k_fr">16k_fr</Option>
<Option value="16k_zh_medical">16k_zh_medical</Option>
<Option value="16k_de">16k_de</Option>
</Select>
</Form.Item>
<Form.Item<FieldType>
label={t('addTencentCloudSID')}
name="TencentCloud_sid"
rules={[{ required: true, message: t('TencentCloudSIDMessage') }]}
>
<Input
placeholder={t('TencentCloudSIDMessage')}
onKeyDown={handleKeyDown}
/>
</Form.Item>
<Form.Item<FieldType>
label={t('addTencentCloudSK')}
name="TencentCloud_sk"
rules={[{ required: true, message: t('TencentCloudSKMessage') }]}
>
<Input
placeholder={t('TencentCloudSKMessage')}
onKeyDown={handleKeyDown}
/>
</Form.Item>
</Form>
</Modal>
);
};
export default TencentCloudModal;

View File

@ -0,0 +1,272 @@
import { LLMFactory } from '@/constants/llm';
import { useTranslate } from '@/hooks/common-hooks';
import { IModalProps } from '@/interfaces/common';
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
import {
Flex,
Form,
Input,
InputNumber,
Modal,
Select,
Space,
Switch,
} from 'antd';
import omit from 'lodash/omit';
import { useEffect } from 'react';
type FieldType = IAddLlmRequestBody & {
vision: boolean;
provider_order?: string;
};
const { Option } = Select;
const llmFactoryToUrlMap = {
[LLMFactory.Ollama]:
'https://github.com/infiniflow/ragflow/blob/main/docs/guides/models/deploy_local_llm.mdx',
[LLMFactory.Xinference]:
'https://inference.readthedocs.io/en/latest/user_guide',
[LLMFactory.ModelScope]:
'https://www.modelscope.cn/docs/model-service/API-Inference/intro',
[LLMFactory.LocalAI]: 'https://localai.io/docs/getting-started/models/',
[LLMFactory.LMStudio]: 'https://lmstudio.ai/docs/basics',
[LLMFactory.OpenAiAPICompatible]:
'https://platform.openai.com/docs/models/gpt-4',
[LLMFactory.TogetherAI]: 'https://docs.together.ai/docs/deployment-options',
[LLMFactory.Replicate]: 'https://replicate.com/docs/topics/deployments',
[LLMFactory.OpenRouter]: 'https://openrouter.ai/docs',
[LLMFactory.HuggingFace]:
'https://huggingface.co/docs/text-embeddings-inference/quick_tour',
[LLMFactory.GPUStack]: 'https://docs.gpustack.ai/latest/quickstart',
[LLMFactory.VLLM]: 'https://docs.vllm.ai/en/latest/',
[LLMFactory.TokenPony]: 'https://docs.tokenpony.cn/#/',
};
type LlmFactory = keyof typeof llmFactoryToUrlMap;
const OllamaModal = ({
visible,
hideModal,
onOk,
loading,
llmFactory,
editMode = false,
initialValues,
}: IModalProps<IAddLlmRequestBody> & {
llmFactory: string;
editMode?: boolean;
initialValues?: Partial<IAddLlmRequestBody>;
}) => {
const [form] = Form.useForm<FieldType>();
const { t } = useTranslate('setting');
const handleOk = async () => {
const values = await form.validateFields();
const modelType =
values.model_type === 'chat' && values.vision
? 'image2text'
: values.model_type;
const data = {
...omit(values, ['vision']),
model_type: modelType,
llm_factory: llmFactory,
max_tokens: values.max_tokens,
};
console.info(data);
onOk?.(data);
};
const handleKeyDown = async (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
await handleOk();
}
};
useEffect(() => {
if (visible && editMode && initialValues) {
const formValues = {
llm_name: initialValues.llm_name,
model_type: initialValues.model_type,
api_base: initialValues.api_base,
max_tokens: initialValues.max_tokens || 8192,
api_key: '',
...initialValues,
};
form.setFieldsValue(formValues);
} else if (visible && !editMode) {
form.resetFields();
}
}, [visible, editMode, initialValues, form]);
const url =
llmFactoryToUrlMap[llmFactory as LlmFactory] ||
'https://github.com/infiniflow/ragflow/blob/main/docs/guides/models/deploy_local_llm.mdx';
const optionsMap = {
[LLMFactory.HuggingFace]: [
{ value: 'embedding', label: 'embedding' },
{ value: 'chat', label: 'chat' },
{ value: 'rerank', label: 'rerank' },
],
[LLMFactory.LMStudio]: [
{ value: 'chat', label: 'chat' },
{ value: 'embedding', label: 'embedding' },
{ value: 'image2text', label: 'image2text' },
],
[LLMFactory.Xinference]: [
{ value: 'chat', label: 'chat' },
{ value: 'embedding', label: 'embedding' },
{ value: 'rerank', label: 'rerank' },
{ value: 'image2text', label: 'image2text' },
{ value: 'speech2text', label: 'sequence2text' },
{ value: 'tts', label: 'tts' },
],
[LLMFactory.ModelScope]: [{ value: 'chat', label: 'chat' }],
[LLMFactory.GPUStack]: [
{ value: 'chat', label: 'chat' },
{ value: 'embedding', label: 'embedding' },
{ value: 'rerank', label: 'rerank' },
{ value: 'speech2text', label: 'sequence2text' },
{ value: 'tts', label: 'tts' },
],
[LLMFactory.OpenRouter]: [
{ value: 'chat', label: 'chat' },
{ value: 'image2text', label: 'image2text' },
],
Default: [
{ value: 'chat', label: 'chat' },
{ value: 'embedding', label: 'embedding' },
{ value: 'rerank', label: 'rerank' },
{ value: 'image2text', label: 'image2text' },
],
};
const getOptions = (factory: string) => {
return optionsMap[factory as keyof typeof optionsMap] || optionsMap.Default;
};
return (
<Modal
title={
editMode
? t('editLlmTitle', { name: llmFactory })
: t('addLlmTitle', { name: llmFactory })
}
open={visible}
onOk={handleOk}
onCancel={hideModal}
okButtonProps={{ loading }}
footer={(originNode: React.ReactNode) => {
return (
<Flex justify={'space-between'}>
<a href={url} target="_blank" rel="noreferrer">
{t('ollamaLink', { name: llmFactory })}
</a>
<Space>{originNode}</Space>
</Flex>
);
}}
>
<Form
name="basic"
style={{ maxWidth: 600 }}
autoComplete="off"
layout={'vertical'}
form={form}
>
<Form.Item<FieldType>
label={t('modelType')}
name="model_type"
initialValue={'embedding'}
rules={[{ required: true, message: t('modelTypeMessage') }]}
>
<Select placeholder={t('modelTypeMessage')}>
{getOptions(llmFactory).map((option) => (
<Option key={option.value} value={option.value}>
{option.label}
</Option>
))}
</Select>
</Form.Item>
<Form.Item<FieldType>
label={t(llmFactory === 'Xinference' ? 'modelUid' : 'modelName')}
name="llm_name"
rules={[{ required: true, message: t('modelNameMessage') }]}
>
<Input
placeholder={t('modelNameMessage')}
onKeyDown={handleKeyDown}
/>
</Form.Item>
<Form.Item<FieldType>
label={t('addLlmBaseUrl')}
name="api_base"
rules={[{ required: true, message: t('baseUrlNameMessage') }]}
>
<Input
placeholder={t('baseUrlNameMessage')}
onKeyDown={handleKeyDown}
/>
</Form.Item>
<Form.Item<FieldType>
label={t('apiKey')}
name="api_key"
rules={[{ required: false, message: t('apiKeyMessage') }]}
>
<Input placeholder={t('apiKeyMessage')} onKeyDown={handleKeyDown} />
</Form.Item>
<Form.Item<FieldType>
label={t('maxTokens')}
name="max_tokens"
rules={[
{ required: true, message: t('maxTokensMessage') },
{
type: 'number',
message: t('maxTokensInvalidMessage'),
},
({}) => ({
validator(_, value) {
if (value < 0) {
return Promise.reject(new Error(t('maxTokensMinMessage')));
}
return Promise.resolve();
},
}),
]}
>
<InputNumber
placeholder={t('maxTokensTip')}
style={{ width: '100%' }}
onKeyDown={handleKeyDown}
/>
</Form.Item>
{llmFactory === LLMFactory.OpenRouter && (
<Form.Item<FieldType>
label="Provider Order"
name="provider_order"
tooltip="Comma-separated provider list, e.g. Groq,Fireworks"
rules={[]}
>
<Input placeholder="Groq,Fireworks" onKeyDown={handleKeyDown} />
</Form.Item>
)}
<Form.Item noStyle dependencies={['model_type']}>
{({ getFieldValue }) =>
getFieldValue('model_type') === 'chat' && (
<Form.Item
label={t('vision')}
valuePropName="checked"
name={'vision'}
>
<Switch />
</Form.Item>
)
}
</Form.Item>
</Form>
</Modal>
);
};
export default OllamaModal;

View File

@ -0,0 +1,163 @@
import { useTranslate } from '@/hooks/common-hooks';
import { IModalProps } from '@/interfaces/common';
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
import { Form, Input, InputNumber, Modal, Select } from 'antd';
import omit from 'lodash/omit';
type FieldType = IAddLlmRequestBody & {
vision: boolean;
spark_api_password: string;
spark_app_id: string;
spark_api_secret: string;
spark_api_key: string;
};
const { Option } = Select;
const SparkModal = ({
visible,
hideModal,
onOk,
loading,
llmFactory,
}: IModalProps<IAddLlmRequestBody> & { llmFactory: string }) => {
const [form] = Form.useForm<FieldType>();
const { t } = useTranslate('setting');
const handleOk = async () => {
const values = await form.validateFields();
const modelType =
values.model_type === 'chat' && values.vision
? 'image2text'
: values.model_type;
const data = {
...omit(values, ['vision']),
model_type: modelType,
llm_factory: llmFactory,
max_tokens: values.max_tokens,
};
console.info(data);
onOk?.(data);
};
const handleKeyDown = async (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
await handleOk();
}
};
return (
<Modal
title={t('addLlmTitle', { name: llmFactory })}
open={visible}
onOk={handleOk}
onCancel={hideModal}
okButtonProps={{ loading }}
confirmLoading={loading}
>
<Form>
<Form.Item<FieldType>
label={t('modelType')}
name="model_type"
initialValue={'chat'}
rules={[{ required: true, message: t('modelTypeMessage') }]}
>
<Select placeholder={t('modelTypeMessage')}>
<Option value="chat">chat</Option>
<Option value="tts">tts</Option>
</Select>
</Form.Item>
<Form.Item<FieldType>
label={t('modelName')}
name="llm_name"
rules={[{ required: true, message: t('SparkModelNameMessage') }]}
>
<Input
placeholder={t('modelNameMessage')}
onKeyDown={handleKeyDown}
/>
</Form.Item>
<Form.Item<FieldType>
label={t('addSparkAPIPassword')}
name="spark_api_password"
rules={[{ required: true, message: t('SparkAPIPasswordMessage') }]}
>
<Input
placeholder={t('SparkAPIPasswordMessage')}
onKeyDown={handleKeyDown}
/>
</Form.Item>
<Form.Item noStyle dependencies={['model_type']}>
{({ getFieldValue }) =>
getFieldValue('model_type') === 'tts' && (
<Form.Item<FieldType>
label={t('addSparkAPPID')}
name="spark_app_id"
rules={[{ required: true, message: t('SparkAPPIDMessage') }]}
>
<Input placeholder={t('SparkAPPIDMessage')} />
</Form.Item>
)
}
</Form.Item>
<Form.Item noStyle dependencies={['model_type']}>
{({ getFieldValue }) =>
getFieldValue('model_type') === 'tts' && (
<Form.Item<FieldType>
label={t('addSparkAPISecret')}
name="spark_api_secret"
rules={[
{ required: true, message: t('SparkAPISecretMessage') },
]}
>
<Input placeholder={t('SparkAPISecretMessage')} />
</Form.Item>
)
}
</Form.Item>
<Form.Item noStyle dependencies={['model_type']}>
{({ getFieldValue }) =>
getFieldValue('model_type') === 'tts' && (
<Form.Item<FieldType>
label={t('addSparkAPIKey')}
name="spark_api_key"
rules={[{ required: true, message: t('SparkAPIKeyMessage') }]}
>
<Input placeholder={t('SparkAPIKeyMessage')} />
</Form.Item>
)
}
</Form.Item>
<Form.Item<FieldType>
label={t('maxTokens')}
name="max_tokens"
rules={[
{ required: true, message: t('maxTokensMessage') },
{
type: 'number',
message: t('maxTokensInvalidMessage'),
},
({}) => ({
validator(_, value) {
if (value < 0) {
return Promise.reject(new Error(t('maxTokensMinMessage')));
}
return Promise.resolve();
},
}),
]}
>
<InputNumber
placeholder={t('maxTokensTip')}
style={{ width: '100%' }}
/>
</Form.Item>
</Form>
</Modal>
);
};
export default SparkModal;

View File

@ -0,0 +1,132 @@
import { IModalManagerChildrenProps } from '@/components/modal-manager';
import { LlmModelType } from '@/constants/knowledge';
import { useTranslate } from '@/hooks/common-hooks';
import {
ISystemModelSettingSavingParams,
useComposeLlmOptionsByModelTypes,
} from '@/hooks/llm-hooks';
import { Form, Modal, Select } from 'antd';
import { useEffect } from 'react';
import { useFetchSystemModelSettingOnMount } from '../../hooks';
interface IProps extends Omit<IModalManagerChildrenProps, 'showModal'> {
loading: boolean;
onOk: (
payload: Omit<ISystemModelSettingSavingParams, 'tenant_id' | 'name'>,
) => void;
}
const SystemModelSettingModal = ({
visible,
hideModal,
onOk,
loading,
}: IProps) => {
const [form] = Form.useForm();
const { systemSetting: initialValues, allOptions } =
useFetchSystemModelSettingOnMount();
const { t } = useTranslate('setting');
const handleOk = async () => {
const values = await form.validateFields();
onOk({
...values,
asr_id: values.asr_id ?? '',
embd_id: values.embd_id ?? '',
img2txt_id: values.img2txt_id ?? '',
llm_id: values.llm_id ?? '',
});
};
useEffect(() => {
if (visible) {
form.setFieldsValue(initialValues);
}
}, [form, initialValues, visible]);
const onFormLayoutChange = () => {};
const modelOptions = useComposeLlmOptionsByModelTypes([
LlmModelType.Chat,
LlmModelType.Image2text,
]);
return (
<Modal
title={t('systemModelSettings')}
open={visible}
onOk={handleOk}
onCancel={hideModal}
okButtonProps={{ loading }}
confirmLoading={loading}
>
<Form form={form} onValuesChange={onFormLayoutChange} layout={'vertical'}>
<Form.Item
label={t('chatModel')}
name="llm_id"
tooltip={t('chatModelTip')}
>
<Select options={modelOptions} allowClear showSearch />
</Form.Item>
<Form.Item
label={t('embeddingModel')}
name="embd_id"
tooltip={t('embeddingModelTip')}
>
<Select
options={allOptions[LlmModelType.Embedding]}
allowClear
showSearch
/>
</Form.Item>
<Form.Item
label={t('img2txtModel')}
name="img2txt_id"
tooltip={t('img2txtModelTip')}
>
<Select
options={allOptions[LlmModelType.Image2text]}
allowClear
showSearch
/>
</Form.Item>
<Form.Item
label={t('sequence2txtModel')}
name="asr_id"
tooltip={t('sequence2txtModelTip')}
>
<Select
options={allOptions[LlmModelType.Speech2text]}
allowClear
showSearch
/>
</Form.Item>
<Form.Item
label={t('rerankModel')}
name="rerank_id"
tooltip={t('rerankModelTip')}
>
<Select
options={allOptions[LlmModelType.Rerank]}
allowClear
showSearch
/>
</Form.Item>
<Form.Item
label={t('ttsModel')}
name="tts_id"
tooltip={t('ttsModelTip')}
>
<Select
options={allOptions[LlmModelType.TTS]}
allowClear
showSearch
/>
</Form.Item>
</Form>
</Modal>
);
};
export default SystemModelSettingModal;

View File

@ -0,0 +1,137 @@
import { useTranslate } from '@/hooks/common-hooks';
import { IModalProps } from '@/interfaces/common';
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
import { Flex, Form, Input, InputNumber, Modal, Select, Space } from 'antd';
import omit from 'lodash/omit';
type FieldType = IAddLlmRequestBody & {
vision: boolean;
volc_ak: string;
volc_sk: string;
endpoint_id: string;
ark_api_key: string;
};
const { Option } = Select;
const VolcEngineModal = ({
visible,
hideModal,
onOk,
loading,
llmFactory,
}: IModalProps<IAddLlmRequestBody> & { llmFactory: string }) => {
const [form] = Form.useForm<FieldType>();
const { t } = useTranslate('setting');
const handleOk = async () => {
const values = await form.validateFields();
const modelType =
values.model_type === 'chat' && values.vision
? 'image2text'
: values.model_type;
const data = {
...omit(values, ['vision']),
model_type: modelType,
llm_factory: llmFactory,
max_tokens: values.max_tokens,
};
console.info(data);
onOk?.(data);
};
return (
<Modal
title={t('addLlmTitle', { name: llmFactory })}
open={visible}
onOk={handleOk}
onCancel={hideModal}
okButtonProps={{ loading }}
footer={(originNode: React.ReactNode) => {
return (
<Flex justify={'space-between'}>
<a
href="https://www.volcengine.com/docs/82379/1302008"
target="_blank"
rel="noreferrer"
>
{t('ollamaLink', { name: llmFactory })}
</a>
<Space>{originNode}</Space>
</Flex>
);
}}
>
<Form
name="basic"
style={{ maxWidth: 600 }}
autoComplete="off"
layout={'vertical'}
form={form}
>
<Form.Item<FieldType>
label={t('modelType')}
name="model_type"
initialValue={'chat'}
rules={[{ required: true, message: t('modelTypeMessage') }]}
>
<Select placeholder={t('modelTypeMessage')}>
<Option value="chat">chat</Option>
<Option value="embedding">embedding</Option>
<Option value="image2text">image2text</Option>
</Select>
</Form.Item>
<Form.Item<FieldType>
label={t('modelName')}
name="llm_name"
rules={[{ required: true, message: t('volcModelNameMessage') }]}
>
<Input placeholder={t('volcModelNameMessage')} />
</Form.Item>
<Form.Item<FieldType>
label={t('addEndpointID')}
name="endpoint_id"
rules={[{ required: true, message: t('endpointIDMessage') }]}
>
<Input placeholder={t('endpointIDMessage')} />
</Form.Item>
<Form.Item<FieldType>
label={t('addArkApiKey')}
name="ark_api_key"
rules={[{ required: true, message: t('ArkApiKeyMessage') }]}
>
<Input placeholder={t('ArkApiKeyMessage')} />
</Form.Item>
<Form.Item<FieldType>
label={t('maxTokens')}
name="max_tokens"
rules={[
{ required: true, message: t('maxTokensMessage') },
{
type: 'number',
message: t('maxTokensInvalidMessage'),
},
({}) => ({
validator(_, value) {
if (value < 0) {
return Promise.reject(new Error(t('maxTokensMinMessage')));
}
return Promise.resolve();
},
}),
]}
>
<InputNumber
placeholder={t('maxTokensTip')}
style={{ width: '100%' }}
/>
</Form.Item>
</Form>
</Modal>
);
};
export default VolcEngineModal;

View File

@ -0,0 +1,131 @@
import { useTranslate } from '@/hooks/common-hooks';
import { IModalProps } from '@/interfaces/common';
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
import { Form, Input, InputNumber, Modal, Select } from 'antd';
import omit from 'lodash/omit';
type FieldType = IAddLlmRequestBody & {
vision: boolean;
yiyan_ak: string;
yiyan_sk: string;
};
const { Option } = Select;
const YiyanModal = ({
visible,
hideModal,
onOk,
loading,
llmFactory,
}: IModalProps<IAddLlmRequestBody> & { llmFactory: string }) => {
const [form] = Form.useForm<FieldType>();
const { t } = useTranslate('setting');
const handleOk = async () => {
const values = await form.validateFields();
const modelType =
values.model_type === 'chat' && values.vision
? 'image2text'
: values.model_type;
const data = {
...omit(values, ['vision']),
model_type: modelType,
llm_factory: llmFactory,
max_tokens: values.max_tokens,
};
console.info(data);
onOk?.(data);
};
const handleKeyDown = async (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
await handleOk();
}
};
return (
<Modal
title={t('addLlmTitle', { name: llmFactory })}
open={visible}
onOk={handleOk}
onCancel={hideModal}
okButtonProps={{ loading }}
confirmLoading={loading}
>
<Form
name="basic"
style={{ maxWidth: 600 }}
autoComplete="off"
layout={'vertical'}
form={form}
>
<Form.Item<FieldType>
label={t('modelType')}
name="model_type"
initialValue={'chat'}
rules={[{ required: true, message: t('modelTypeMessage') }]}
>
<Select placeholder={t('modelTypeMessage')}>
<Option value="chat">chat</Option>
<Option value="embedding">embedding</Option>
<Option value="rerank">rerank</Option>
</Select>
</Form.Item>
<Form.Item<FieldType>
label={t('modelName')}
name="llm_name"
rules={[{ required: true, message: t('yiyanModelNameMessage') }]}
>
<Input
placeholder={t('yiyanModelNameMessage')}
onKeyDown={handleKeyDown}
/>
</Form.Item>
<Form.Item<FieldType>
label={t('addyiyanAK')}
name="yiyan_ak"
rules={[{ required: true, message: t('yiyanAKMessage') }]}
>
<Input placeholder={t('yiyanAKMessage')} onKeyDown={handleKeyDown} />
</Form.Item>
<Form.Item<FieldType>
label={t('addyiyanSK')}
name="yiyan_sk"
rules={[{ required: true, message: t('yiyanSKMessage') }]}
>
<Input placeholder={t('yiyanSKMessage')} onKeyDown={handleKeyDown} />
</Form.Item>
<Form.Item<FieldType>
label={t('maxTokens')}
name="max_tokens"
rules={[
{ required: true, message: t('maxTokensMessage') },
{
type: 'number',
message: t('maxTokensInvalidMessage'),
},
({}) => ({
validator(_, value) {
if (value < 0) {
return Promise.reject(new Error(t('maxTokensMinMessage')));
}
return Promise.resolve();
},
}),
]}
>
<InputNumber
placeholder={t('maxTokensTip')}
style={{ width: '100%' }}
/>
</Form.Item>
</Form>
</Modal>
);
};
export default YiyanModal;