diff --git a/src/locales/en-US/provider.ts b/src/locales/en-US/provider.ts index cf041d9b..571b0b52 100644 --- a/src/locales/en-US/provider.ts +++ b/src/locales/en-US/provider.ts @@ -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' }; diff --git a/src/locales/ja-JP/provider.ts b/src/locales/ja-JP/provider.ts index cf041d9b..571b0b52 100644 --- a/src/locales/ja-JP/provider.ts +++ b/src/locales/ja-JP/provider.ts @@ -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' }; diff --git a/src/locales/ru-RU/provider.ts b/src/locales/ru-RU/provider.ts index cf041d9b..571b0b52 100644 --- a/src/locales/ru-RU/provider.ts +++ b/src/locales/ru-RU/provider.ts @@ -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' }; diff --git a/src/locales/zh-CN/provider.ts b/src/locales/zh-CN/provider.ts index 3ae085dd..db0f10a6 100644 --- a/src/locales/zh-CN/provider.ts +++ b/src/locales/zh-CN/provider.ts @@ -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 区域' }; diff --git a/src/pages/maas-provider/config/form-context.ts b/src/pages/maas-provider/config/form-context.ts index 1f9dc9d4..ad64057b 100644 --- a/src/pages/maas-provider/config/form-context.ts +++ b/src/pages/maas-provider/config/form-context.ts @@ -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; } diff --git a/src/pages/maas-provider/config/types.ts b/src/pages/maas-provider/config/types.ts index 6d0c17ed..f5183253 100644 --- a/src/pages/maas-provider/config/types.ts +++ b/src/pages/maas-provider/config/types.ts @@ -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[]; +} diff --git a/src/pages/maas-provider/forms/basic.tsx b/src/pages/maas-provider/forms/basic.tsx index 2f6f1f0e..c00ea244 100644 --- a/src/pages/maas-provider/forms/basic.tsx +++ b/src/pages/maas-provider/forms/basic.tsx @@ -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<{ })} /> - {providerType === ProviderEnum.OPENAI && ( - name={['config', 'openaiCustomUrl']}> - - - )} + name="api_key" rules={[ diff --git a/src/pages/maas-provider/forms/index.tsx b/src/pages/maas-provider/forms/index.tsx index d7730322..e6f0947b 100644 --- a/src/pages/maas-provider/forms/index.tsx +++ b/src/pages/maas-provider/forms/index.tsx @@ -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 = 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(null); const advanceRef = useRef(null); const { activeKey, collapseKeys, - setCollapseKeys, handleActiveChange, handleOnCollapseChange, updateActiveKey @@ -81,6 +89,11 @@ const ProviderForm: React.FC = 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 = 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 = 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 = 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 = forwardRef((props, ref) => { action, id: currentData?.id, currentData, + providerFields, getCustomConfig: getCustomConfig }} > diff --git a/src/pages/maas-provider/forms/model-item.tsx b/src/pages/maas-provider/forms/model-item.tsx index 9e37e9a4..e3321e60 100644 --- a/src/pages/maas-provider/forms/model-item.tsx +++ b/src/pages/maas-provider/forms/model-item.tsx @@ -89,11 +89,7 @@ const ModelItem: React.FC = ({ : null, config: { type: form.getFieldValue(['config', 'type']) || '', - ...customConfig, - openaiCustomUrl: - customConfig?.openaiCustomUrl || - form.getFieldValue(['config', 'openaiCustomUrl']) || - null + ...customConfig } } }); diff --git a/src/pages/maas-provider/forms/provider-configs.tsx b/src/pages/maas-provider/forms/provider-configs.tsx new file mode 100644 index 00000000..99d0f5ab --- /dev/null +++ b/src/pages/maas-provider/forms/provider-configs.tsx @@ -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(); + 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 ( + + {item.type === 'Input' && ( + + )} + {item.type === 'Password' && ( + + )} + + ); + }) + : null} + + ); +}; + +export default ProviderConfigs; diff --git a/src/pages/maas-provider/forms/supported-models.tsx b/src/pages/maas-provider/forms/supported-models.tsx index 42ca3307..0aea1f7a 100644 --- a/src/pages/maas-provider/forms/supported-models.tsx +++ b/src/pages/maas-provider/forms/supported-models.tsx @@ -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 } diff --git a/src/pages/maas-provider/hooks/use-provider-required-fields.tsx b/src/pages/maas-provider/hooks/use-provider-required-fields.tsx new file mode 100644 index 00000000..1ee1e8ee --- /dev/null +++ b/src/pages/maas-provider/hooks/use-provider-required-fields.tsx @@ -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 = { + [ProviderEnum.OPENAI]: [ + { + type: 'Input', + name: 'openaiCustomUrl', + placeholder: 'http:///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;