fix: show required fields for provider

This commit is contained in:
jialin
2026-03-18 15:01:04 +08:00
parent 19b88f3375
commit 6e4dd30104
12 changed files with 400 additions and 42 deletions
+15 -1
View File
@@ -23,5 +23,19 @@ export default {
'providers.form.rules.tokens': 'Please enter a valid API Key',
'providers.form.rules.model': 'Please select a model',
'providers.form.model.duplicate': 'Duplicate model exists',
'providers.table.registerRoute': 'Register Route'
'providers.table.registerRoute': 'Register Route',
'providers.form.azureServiceUrl': 'Azure OpenAI Service URL',
'providers.form.ollamaServerHost': 'Ollama Server Host',
'providers.form.ollamaServerPort': 'Ollama Server Port',
'providers.form.hunyuanAuthId': 'Hunyuan Auth ID',
'providers.form.hunyuanAuthKey': 'Hunyuan Auth Key',
'providers.form.cloudflareAccountId': 'Cloudflare Account ID',
'providers.form.targetLang': 'Target Language',
'providers.form.modelVersion': 'Model Version',
'providers.form.tritonDomain': 'Triton Server Domain',
'providers.form.modelVersion.tips':
'Specifies the model version used in Triton Server.',
'providers.form.tritonDomain.tips':
'The domain used to send requests to the Triton Server deployment.',
'providers.form.awsRegion': 'AWS Region'
};
+15 -1
View File
@@ -23,5 +23,19 @@ export default {
'providers.form.rules.tokens': 'Please enter a valid API Key',
'providers.form.rules.model': 'Please select a model',
'providers.form.model.duplicate': 'Duplicate model exists',
'providers.table.registerRoute': 'Register Route'
'providers.table.registerRoute': 'Register Route',
'providers.form.azureServiceUrl': 'Azure OpenAI Service URL',
'providers.form.ollamaServerHost': 'Ollama Server Host',
'providers.form.ollamaServerPort': 'Ollama Server Port',
'providers.form.hunyuanAuthId': 'Hunyuan Auth ID',
'providers.form.hunyuanAuthKey': 'Hunyuan Auth Key',
'providers.form.cloudflareAccountId': 'Cloudflare Account ID',
'providers.form.targetLang': 'Target Language',
'providers.form.modelVersion': 'Model Version',
'providers.form.tritonDomain': 'Triton Server Domain',
'providers.form.modelVersion.tips':
'Specifies the model version used in Triton Server.',
'providers.form.tritonDomain.tips':
'The domain used to send requests to the Triton Server deployment.',
'providers.form.awsRegion': 'AWS Region'
};
+15 -1
View File
@@ -23,5 +23,19 @@ export default {
'providers.form.rules.tokens': 'Please enter a valid API Key',
'providers.form.rules.model': 'Please select a model',
'providers.form.model.duplicate': 'Duplicate model exists',
'providers.table.registerRoute': 'Register Route'
'providers.table.registerRoute': 'Register Route',
'providers.form.azureServiceUrl': 'Azure OpenAI Service URL',
'providers.form.ollamaServerHost': 'Ollama Server Host',
'providers.form.ollamaServerPort': 'Ollama Server Port',
'providers.form.hunyuanAuthId': 'Hunyuan Auth ID',
'providers.form.hunyuanAuthKey': 'Hunyuan Auth Key',
'providers.form.cloudflareAccountId': 'Cloudflare Account ID',
'providers.form.targetLang': 'Target Language',
'providers.form.modelVersion': 'Model Version',
'providers.form.tritonDomain': 'Triton Server Domain',
'providers.form.modelVersion.tips':
'Specifies the model version used in Triton Server.',
'providers.form.tritonDomain.tips':
'The domain used to send requests to the Triton Server deployment.',
'providers.form.awsRegion': 'AWS Region'
};
+13 -1
View File
@@ -23,5 +23,17 @@ export default {
'providers.form.rules.tokens': '请输入有效的 API Key',
'providers.form.rules.model': '请选择模型',
'providers.form.model.duplicate': '存在相同的模型',
'providers.table.registerRoute': '注册路由'
'providers.table.registerRoute': '注册路由',
'providers.form.azureServiceUrl': 'Azure OpenAI 服务 URL',
'providers.form.ollamaServerHost': 'Ollama 服务地址',
'providers.form.ollamaServerPort': 'Ollama 服务端口',
'providers.form.hunyuanAuthId': '混元认证 ID',
'providers.form.hunyuanAuthKey': '混元认证 Key',
'providers.form.cloudflareAccountId': 'Cloudflare 账号 ID',
'providers.form.targetLang': '翻译目标语言',
'providers.form.modelVersion': '模型版本',
'providers.form.modelVersion.tips': '用于指定 Triton Server 中的模型版本。',
'providers.form.tritonDomain': 'Triton Server 域名',
'providers.form.tritonDomain.tips': 'Triton Server 部署的指定请求的域名。',
'providers.form.awsRegion': 'AWS 区域'
};
@@ -1,12 +1,14 @@
import { PageActionType } from '@/config/types';
import { createContext, useContext } from 'react';
import { maasProviderType } from '.';
import { RequiredFields } from './types';
interface FormContextProps {
providerType?: maasProviderType;
action: PageActionType;
currentData?: any;
id?: number;
providerFields?: RequiredFields[];
getCustomConfig?: () => Record<string, any>;
}
+16
View File
@@ -40,3 +40,19 @@ export interface MaasProviderItem {
api_token_count: number;
api_tokens: { hash: string }[];
}
export interface RequiredFields {
type: 'Input' | 'Select' | 'Password';
name: string;
label: {
text: string;
locale?: boolean;
};
required?: boolean;
placeholder?: string;
description?: {
text: string;
locale?: boolean;
};
rules?: any[];
}
+3 -11
View File
@@ -7,8 +7,9 @@ import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import ProviderLogo from '../components/provider-logo';
import { useFormContext } from '../config/form-context';
import { maasProviderOptions, ProviderEnum } from '../config/providers';
import { maasProviderOptions } from '../config/providers';
import { FormData } from '../config/types';
import ProviderConfigs from './provider-configs';
const Basic: React.FC<{
onAPIKeyBlur?: (e: any) => void;
@@ -77,16 +78,7 @@ const Basic: React.FC<{
})}
/>
</Form.Item>
{providerType === ProviderEnum.OPENAI && (
<Form.Item<FormData> name={['config', 'openaiCustomUrl']}>
<SealInput.Input
placeholder="http://<your-inference-server>/v1"
label={intl.formatMessage({
id: 'providers.form.custombeckendUrl'
})}
/>
</Form.Item>
)}
<ProviderConfigs />
<Form.Item<FormData>
name="api_key"
rules={[
+32 -12
View File
@@ -10,9 +10,16 @@ import { json2Yaml, yaml2Json } from '@/pages/backends/config';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import _ from 'lodash';
import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
import {
forwardRef,
useEffect,
useImperativeHandle,
useMemo,
useRef
} from 'react';
import FormContext from '../config/form-context';
import { FormData, MaasProviderItem as ListItem } from '../config/types';
import useProviderRequiredFields from '../hooks/use-provider-required-fields';
import AdvanceConfig from './advance-config';
import Basic from './basic';
import SupportedModels from './supported-models';
@@ -44,14 +51,15 @@ const requiredFields = {
const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
const { action, currentData, onFinish } = props;
const intl = useIntl();
const providerRequiredFieldsMap = useProviderRequiredFields();
const [form] = Form.useForm();
const { getScrollElementScrollableHeight } = useWrapperContext();
const configType = Form.useWatch(['config', 'type'], form);
const scrollTabsRef = useRef<any>(null);
const advanceRef = useRef<any>(null);
const {
activeKey,
collapseKeys,
setCollapseKeys,
handleActiveChange,
handleOnCollapseChange,
updateActiveKey
@@ -81,6 +89,11 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
}
];
const providerFields = useMemo(() => {
const currentProviderFields = providerRequiredFieldsMap[configType] || [];
return currentProviderFields;
}, [providerRequiredFieldsMap, configType]);
const formatAPIKeys = (values: FormData) => {
const apiTokens = values.api_tokens?.filter?.(
(item) => item && item.trim() !== ''
@@ -100,7 +113,11 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
const getCustomConfig = () => {
const customConfig = yaml2Json(advanceRef.current?.getYamlValue() || '');
return customConfig;
const config = form.getFieldValue('config') || {};
return {
...config,
...customConfig
};
};
const handleOnFinish = (values: FormData) => {
@@ -108,8 +125,7 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
..._.omit(values, ['api_key']),
api_tokens: formatAPIKeys(values),
config: {
type: values.config.type,
openaiCustomUrl: values.config.openaiCustomUrl || undefined,
...values.config,
...yaml2Json(advanceRef.current?.getYamlValue() || '')
},
models: _.uniqBy(values.models, 'name'),
@@ -145,16 +161,19 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
const apiTokensList = _.get(currentData, 'api_tokens', []).map(
(item: any) => item.hash || ''
);
const customConfigYaml = json2Yaml(
_.omit(currentData.config, ['type', 'openaiCustomUrl']) || {}
const currentProvider = _.get(
providerRequiredFieldsMap,
[currentData.config.type],
[]
);
const customConfig =
_.omit(currentData.config, ['type', 'openaiCustomUrl']) || {};
const currentRequiredFields = currentProvider.map(
(item: { name: string }) => item.name
);
if (Object.keys(customConfig).length > 0) {
setCollapseKeys((prev) => [...new Set([...prev, TABKeysMap.ADVANCED])]);
}
const customConfigYaml = json2Yaml(
_.omit(currentData.config, ['type', ...currentRequiredFields]) || {}
);
form.setFieldsValue({
...currentData,
@@ -189,6 +208,7 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
action,
id: currentData?.id,
currentData,
providerFields,
getCustomConfig: getCustomConfig
}}
>
+1 -5
View File
@@ -89,11 +89,7 @@ const ModelItem: React.FC<ModelItemProps> = ({
: null,
config: {
type: form.getFieldValue(['config', 'type']) || '',
...customConfig,
openaiCustomUrl:
customConfig?.openaiCustomUrl ||
form.getFieldValue(['config', 'openaiCustomUrl']) ||
null
...customConfig
}
}
});
@@ -0,0 +1,61 @@
import Password from '@/components/seal-form/password';
import SealInput from '@/components/seal-form/seal-input';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import { useFormContext } from '../config/form-context';
import { FormData } from '../config/types';
const ProviderConfigs = () => {
const intl = useIntl();
const form = Form.useFormInstance<FormData>();
const { providerFields } = useFormContext();
const renderLabel = (item: any) => {
return item.label.locale
? intl.formatMessage({ id: item.label.text })
: item.label.text;
};
const renderDescription = (item: any) => {
return item.description
? item.description.locale
? intl.formatMessage({ id: item.description.text })
: item.description.text
: undefined;
};
return (
<>
{providerFields && providerFields.length > 0
? providerFields?.map((item) => {
return (
<Form.Item
name={['config', item.name]}
rules={item.rules}
key={item.name}
>
{item.type === 'Input' && (
<SealInput.Input
required={item.required}
description={renderDescription(item)}
label={renderLabel(item)}
placeholder={item.placeholder}
></SealInput.Input>
)}
{item.type === 'Password' && (
<Password
required={item.required}
label={renderLabel(item)}
description={renderDescription(item)}
placeholder={item.placeholder}
></Password>
)}
</Form.Item>
);
})
: null}
</>
);
};
export default ProviderConfigs;
@@ -18,8 +18,8 @@ const SupportedModels = () => {
const prevConfigRef = useRef<{
type: string;
api_key: string;
openaiCustomUrl: string;
}>({ type: '', api_key: '', openaiCustomUrl: '' });
[key: string]: any;
}>({ type: '', api_key: '' });
const { id, action, currentData, getCustomConfig } = useFormContext();
const generateCurrentAPIKey = (currentAPIKey: string) => {
@@ -42,7 +42,7 @@ const SupportedModels = () => {
const checkConfigChange = (current: {
type: string;
api_key: string;
openaiCustomUrl: string;
[key: string]: any;
}) => {
return (
!_.isEqual(current, prevConfigRef.current) &&
@@ -58,13 +58,12 @@ const SupportedModels = () => {
const proxyConfigEnabled = form.getFieldValue('proxy_enabled');
const currentAPIKey = form.getFieldValue('api_key') || '';
const configType = form.getFieldValue(['config', 'type']);
const openaiCustomUrl = form.getFieldValue(['config', 'openaiCustomUrl']);
const customConfig = getCustomConfig?.();
const currentConfig = {
type: configType,
api_key: currentAPIKey,
openaiCustomUrl: customConfig?.openaiCustomUrl || openaiCustomUrl || ''
...customConfig
};
// Avoid repeated requests with the same API key
@@ -82,9 +81,7 @@ const SupportedModels = () => {
: null,
config: {
type: form.getFieldValue(['config', 'type']) || '',
...customConfig,
openaiCustomUrl:
customConfig?.openaiCustomUrl || openaiCustomUrl || null
...customConfig
}
}
});
@@ -92,8 +89,7 @@ const SupportedModels = () => {
} catch (error) {
prevConfigRef.current = {
type: '',
api_key: '',
openaiCustomUrl: ''
api_key: ''
};
// If validation fails, reset the provider model list to avoid confusion
}
@@ -0,0 +1,221 @@
import useAppUtils from '@/hooks/use-app-utils';
import { useIntl } from '@umijs/max';
import { ProviderEnum } from '../config/providers';
import { RequiredFields } from '../config/types';
const useProviderRequiredFields = () => {
const intl = useIntl();
const { getRuleMessage } = useAppUtils();
const providerRequiredFieldsMap: Record<string, RequiredFields[]> = {
[ProviderEnum.OPENAI]: [
{
type: 'Input',
name: 'openaiCustomUrl',
placeholder: 'http://<your-inference-server>/v1',
required: false,
label: {
text: 'providers.form.custombeckendUrl',
locale: true
}
}
],
[ProviderEnum.AZURE]: [
{
type: 'Input',
name: 'azureServiceUrl',
required: true,
label: {
text: 'providers.form.azureServiceUrl',
locale: true
},
rules: [
{
required: true,
message: getRuleMessage('input', 'providers.form.azureServiceUrl')
}
]
}
],
[ProviderEnum.OLLAMA]: [
{
type: 'Input',
name: 'ollamaServerHost',
required: true,
label: {
text: 'providers.form.ollamaServerHost',
locale: true
},
rules: [
{
required: true,
message: getRuleMessage('input', 'providers.form.ollamaServerHost')
}
]
},
{
type: 'Input',
name: 'ollamaServerPort',
required: true,
label: {
text: 'providers.form.ollamaServerPort',
locale: true
},
rules: [
{
required: true,
message: getRuleMessage('input', 'providers.form.ollamaServerPort')
}
]
}
],
[ProviderEnum.HUNYUAN]: [
{
type: 'Input',
name: 'hunyuanAuthId',
required: true,
label: {
text: 'providers.form.hunyuanAuthId',
locale: true
},
rules: [
{
required: true,
message: getRuleMessage('input', 'providers.form.hunyuanAuthId')
}
]
},
{
type: 'Password',
name: 'hunyuanAuthKey',
required: true,
label: {
text: 'providers.form.hunyuanAuthKey',
locale: true
},
rules: [
{
required: true,
message: getRuleMessage('input', 'providers.form.hunyuanAuthKey')
}
]
}
],
[ProviderEnum.CLOUDFLARE]: [
{
type: 'Input',
name: 'cloudflareAccountId',
required: true,
label: {
text: 'providers.form.cloudflareAccountId',
locale: true
},
rules: [
{
required: true,
message: getRuleMessage(
'input',
'providers.form.cloudflareAccountId'
)
}
]
}
],
[ProviderEnum.DEEPL]: [
{
type: 'Input',
name: 'targetLang',
required: true,
label: {
text: 'providers.form.targetLang',
locale: true
},
rules: [
{
required: true,
message: getRuleMessage('input', 'providers.form.targetLang')
}
]
}
],
[ProviderEnum.BEDROCK]: [
{
type: 'Input',
name: 'modelVersion',
required: false,
label: {
text: 'providers.form.modelVersion',
locale: true
},
description: {
text: 'providers.form.modelVersion.tips',
locale: true
}
},
{
type: 'Input',
name: 'tritonDomain',
required: false,
label: {
text: 'providers.form.tritonDomain',
locale: true
},
description: {
text: 'providers.form.tritonDomain.tips',
locale: true
}
}
],
[ProviderEnum.TRITON]: [
{
type: 'Input',
name: 'awsAccessKey',
required: true,
label: {
text: 'AWS Access Key',
locale: false
},
rules: [
{
required: true,
message: getRuleMessage('input', 'AWS Access Key', false)
}
]
},
{
type: 'Password',
name: 'awsSecretKey',
required: true,
label: {
text: 'AWS Secret Key',
locale: false
},
rules: [
{
required: true,
message: getRuleMessage('input', 'AWS Secret Key', false)
}
]
},
{
type: 'Input',
name: 'awsRegion',
required: true,
label: {
text: 'providers.form.awsRegion',
locale: true
},
rules: [
{
required: true,
message: getRuleMessage('input', 'providers.form.awsRegion')
}
]
}
]
};
return providerRequiredFieldsMap;
};
export default useProviderRequiredFields;