diff --git a/backend/console/pom.xml b/backend/console/pom.xml index 2d695318..3de67f4b 100644 --- a/backend/console/pom.xml +++ b/backend/console/pom.xml @@ -16,7 +16,7 @@ 16.19.0 - 2.0.0 + 2.1.0 true diff --git a/backend/console/src/test/java/com/alibaba/higress/console/FrontEndI18nResourceChecker.java b/backend/console/src/test/java/com/alibaba/higress/console/FrontEndI18nResourceChecker.java index 452429d2..c9b0f786 100644 --- a/backend/console/src/test/java/com/alibaba/higress/console/FrontEndI18nResourceChecker.java +++ b/backend/console/src/test/java/com/alibaba/higress/console/FrontEndI18nResourceChecker.java @@ -42,8 +42,8 @@ public class FrontEndI18nResourceChecker { private static final String I18N_RESOURCE_FILE_NAME = "translation.json"; private static final List TS_FILE_EXTENSIONS = Arrays.asList(".ts", ".tsx"); - private static final Set IMPLICITLY_USED_RESOURCE_KEYS = Set.of("init.title", "login.title", "aiRoute.edit", - "tlsCertificate.editTlsCertificate", "serviceSource.editServiceSource", "llmProvider.edit", + private static final Set IMPLICITLY_USED_RESOURCE_KEYS = Set.of("index.title", "init.title", "login.title", + "aiRoute.edit", "tlsCertificate.editTlsCertificate", "serviceSource.editServiceSource", "llmProvider.edit", "plugins.editPlugin", "route.editRoute", "domain.editDomain", "consumer.edit"); private static final List IMPLICITLY_USED_RESOURCE_KEY_PREFIXES = Arrays.asList("menu.", "request.error.", "serviceSource.types.", "llmProvider.providerTypes.", diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/LlmProviderType.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/LlmProviderType.java index 0f8703d1..1738a259 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/LlmProviderType.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/model/ai/LlmProviderType.java @@ -66,4 +66,6 @@ private LlmProviderType() { public static final String DOUBAO = "doubao"; public static final String COZE = "coze"; + + public static final String TOGETHER_AI = "together-ai"; } diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/ai/AbstractLlmProviderHandler.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/ai/AbstractLlmProviderHandler.java index ec00c684..db3371d3 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/ai/AbstractLlmProviderHandler.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/ai/AbstractLlmProviderHandler.java @@ -38,6 +38,7 @@ import com.alibaba.higress.sdk.constant.CommonKey; import com.alibaba.higress.sdk.constant.HigressConstants; import com.alibaba.higress.sdk.constant.Separators; +import com.alibaba.higress.sdk.exception.ValidationException; import com.alibaba.higress.sdk.model.ServiceSource; import com.alibaba.higress.sdk.model.ai.LlmProvider; import com.alibaba.higress.sdk.model.ai.LlmProviderProtocol; @@ -166,6 +167,21 @@ public UpstreamService buildUpstreamService(String providerName, Map providerConfig); + protected static int getIntConfig(Map providerConfig, String key) { + Object serverPortObj = providerConfig.get(key); + if (serverPortObj instanceof Integer) { + return (Integer)serverPortObj; + } + if (serverPortObj instanceof String serverPortStr) { + try { + return Integer.parseInt(serverPortStr); + } catch (NumberFormatException e) { + throw new ValidationException(key + " must be a number."); + } + } + throw new ValidationException(key + " must be a number."); + } + protected static String generateServiceProviderName(String llmProviderName) { return CommonKey.LLM_SERVICE_NAME_PREFIX + llmProviderName + HigressConstants.INTERNAL_RESOURCE_NAME_SUFFIX; } diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/ai/AzureLlmProviderHandler.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/ai/AzureLlmProviderHandler.java index b23bdbde..9c6e8f6d 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/ai/AzureLlmProviderHandler.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/ai/AzureLlmProviderHandler.java @@ -14,6 +14,7 @@ import java.net.URI; import java.net.URISyntaxException; +import java.util.Locale; import java.util.Map; import org.apache.commons.collections4.MapUtils; @@ -34,20 +35,17 @@ public String getType() { @Override public void validateConfig(Map configurations) { - if (MapUtils.isEmpty(configurations)){ + if (MapUtils.isEmpty(configurations)) { throw new ValidationException("Missing Azure specific configurations."); } - Object serviceUrlObj = configurations.get(SERVICE_URL_KEY); - if (!(serviceUrlObj instanceof String serviceUrl)){ - throw new ValidationException(SERVICE_URL_KEY + " must be a string."); - } - if (StringUtils.isEmpty(serviceUrl)) { - throw new ValidationException(SERVICE_URL_KEY + " cannot be empty."); + URI uri = getServiceUri(configurations); + String scheme = uri.getScheme(); + if (StringUtils.isEmpty(scheme)) { + throw new ValidationException("Azure service URL must have a scheme."); } - try { - new URI(serviceUrl); - } catch (URISyntaxException e) { - throw new ValidationException(SERVICE_URL_KEY + " is not a valid URL.", e); + scheme = scheme.toLowerCase(Locale.ROOT); + if (!scheme.equals(V1McpBridge.PROTOCOL_HTTP) && !scheme.equals(V1McpBridge.PROTOCOL_HTTPS)) { + throw new ValidationException("Azure service URL must have a valid scheme."); } } @@ -58,28 +56,52 @@ protected String getServiceRegistryType(Map providerConfig) { @Override protected String getServiceDomain(Map providerConfig) { - if (MapUtils.isEmpty(providerConfig)){ - return ""; - } - String serviceUrl = (String)providerConfig.get(SERVICE_URL_KEY); - if (StringUtils.isEmpty(serviceUrl)) { - return ""; - } - try { - URI uri = new URI(serviceUrl); - return uri.getHost(); - } catch (URISyntaxException e) { - return null; - } + URI uri = getServiceUri(providerConfig); + return uri.getHost(); } @Override protected int getServicePort(Map providerConfig) { - return 443; + URI uri = getServiceUri(providerConfig); + String scheme = uri.getScheme(); + if (scheme == null) { + return 80; + } + return switch (scheme.toLowerCase(Locale.ROOT)) { + case V1McpBridge.PROTOCOL_HTTP -> 80; + case V1McpBridge.PROTOCOL_HTTPS -> 443; + default -> 80; + }; } @Override protected String getServiceProtocol(Map providerConfig) { - return V1McpBridge.PROTOCOL_HTTPS; + URI uri = getServiceUri(providerConfig); + String scheme = uri.getScheme(); + if (scheme == null) { + return V1McpBridge.PROTOCOL_HTTP; + } + return switch (scheme.toLowerCase(Locale.ROOT)) { + case V1McpBridge.PROTOCOL_HTTP, V1McpBridge.PROTOCOL_HTTPS -> scheme; + default -> V1McpBridge.PROTOCOL_HTTP; + }; + } + + private static URI getServiceUri(Map providerConfig) { + if (MapUtils.isEmpty(providerConfig)) { + throw new ValidationException("Missing Azure specific configurations."); + } + Object serviceUrlObj = providerConfig.get(SERVICE_URL_KEY); + if (!(serviceUrlObj instanceof String serviceUrl)) { + throw new ValidationException(SERVICE_URL_KEY + " must be a string."); + } + if (StringUtils.isEmpty(serviceUrl)) { + throw new ValidationException(SERVICE_URL_KEY + " cannot be empty."); + } + try { + return new URI(serviceUrl); + } catch (URISyntaxException e) { + throw new ValidationException(SERVICE_URL_KEY + " is not a valid URL.", e); + } } } diff --git a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/ai/OllamaLlmProviderHandler.java b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/ai/OllamaLlmProviderHandler.java index 5171252b..53ae7479 100644 --- a/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/ai/OllamaLlmProviderHandler.java +++ b/backend/sdk/src/main/java/com/alibaba/higress/sdk/service/ai/OllamaLlmProviderHandler.java @@ -46,10 +46,7 @@ public void validateConfig(Map configurations) { if (StringUtils.isEmpty(serverHost)) { throw new ValidationException(SERVER_HOST_KEY + " cannot be empty."); } - Object serverPortObj = configurations.get(SERVER_PORT_KEY); - if (!(serverPortObj instanceof Integer serverPort)) { - throw new ValidationException(SERVER_PORT_KEY + " must be a number."); - } + int serverPort = getIntConfig(configurations, SERVER_PORT_KEY); if (!ValidateUtil.checkPort(serverPort)) { throw new ValidationException(SERVER_PORT_KEY + " must be a valid port number."); } diff --git a/frontend/src/locales/en-US/translation.json b/frontend/src/locales/en-US/translation.json index 35b021a5..250ef9e8 100644 --- a/frontend/src/locales/en-US/translation.json +++ b/frontend/src/locales/en-US/translation.json @@ -188,7 +188,30 @@ "providerTypes": { "openai": "OpenAI", "qwen": "Tongyi Qianwen", - "moonshot": "Moonshot" + "moonshot": "Moonshot", + "ai360": "360 Zhinao|", + "azure": "Azure OpenAI", + "baichuan": "Baichuan AI", + "baidu": "ERNIE Bot", + "claude": "Anthropic Claude", + "cloudflare": "Cloudflare Workers AI", + "cohere": "Cohere", + "coze": "Coze", + "deepl": "DeepL", + "deepseek": "DeepSeek", + "doubao": "Doubao", + "gemini": "Google Gemini", + "github": "GitHub Models", + "groq": "Groq", + "hunyuan": "Tencent Hunyuan", + "minimax": "MiniMax", + "mistral": "Mistral", + "ollama": "Ollama", + "together-ai": "Together AI", + "stepfun": "Stepfun", + "spark": "iFlyTek Spark", + "yi": "01.AI", + "zhipuai": "Zhipu AI" }, "providerForm": { "label": { @@ -201,7 +224,10 @@ "healthCheckTimeout": "Health Check Request Timeout", "protocol": "Request Procotol", "serviceName": "Service Name", - "successThreshold": "Min Consecuitive Sucesses to Mark Up a Token" + "successThreshold": "Min Consecuitive Sucesses to Mark Up a Token", + "azureServiceUrl": "Azure Service URL", + "ollamaServerHost": "Ollama Service Host", + "ollamaServerPort": "Ollama Service Port" }, "rules": { "tokenRequired": "Please input auth token", @@ -212,7 +238,14 @@ "healthCheckIntervalRequired": "Please input health check request interval", "healthCheckModelRequired": "Please input health check request LLM model", "protocol": "Please select a request protocol", - "successThresholdRequired": "Please input min consecuitive sucesses to mark up a token" + "successThresholdRequired": "Please input min consecuitive sucesses to mark up a token", + "azureServiceUrlRequired": "Please input Azure service URL", + "ollamaServerHostRequired": "Please input Ollama service host", + "ollamaServerPortRequired": "Please input Ollama service port" + }, + "placeholder": { + "azureServiceUrlPlaceholder": "It shall contain \"/chat/completions\" in the path and \"api-version\" in the query string", + "ollamaServerHostPlaceholder": "Please input a hostname, domain name or IP address" } }, "create": "Create AI Service Provider", diff --git a/frontend/src/locales/zh-CN/translation.json b/frontend/src/locales/zh-CN/translation.json index 27057297..b6987f2f 100644 --- a/frontend/src/locales/zh-CN/translation.json +++ b/frontend/src/locales/zh-CN/translation.json @@ -187,7 +187,30 @@ "providerTypes": { "openai": "OpenAI", "qwen": "通义千问", - "moonshot": "月之暗面" + "moonshot": "月之暗面", + "ai360": "360智脑", + "azure": "Azure OpenAI", + "baichuan": "百川智能", + "baidu": "文心一言", + "claude": "Anthropic Claude", + "cloudflare": "Cloudflare Workers AI", + "cohere": "Cohere", + "coze": "扣子", + "deepl": "DeepL", + "deepseek": "DeepSeek", + "doubao": "豆包", + "gemini": "Google Gemini", + "github": "GitHub模型", + "groq": "Groq", + "hunyuan": "混元", + "minimax": "MiniMax", + "mistral": "Mistral", + "ollama": "Ollama", + "together-ai": "Together AI", + "stepfun": "阶跃星辰", + "spark": "星火", + "yi": "零一万物", + "zhipuai": "智谱AI" }, "providerForm": { "label": { @@ -200,7 +223,10 @@ "healthCheckTimeout": "健康检测请求超时时间", "protocol": "协议", "serviceName": "服务名称", - "successThreshold": "令牌可用时需满足的最小连续健康检测成功次数" + "successThreshold": "令牌可用时需满足的最小连续健康检测成功次数", + "azureServiceUrl": "Azure 服务 URL", + "ollamaServerHost": "Ollama 服务主机名", + "ollamaServerPort": "Ollama 服务端口" }, "rules": { "tokenRequired": "请输入凭证", @@ -211,7 +237,14 @@ "healthCheckIntervalRequired": "请输入健康检测请求发起间隔", "healthCheckModelRequired": "请输入健康检测请求使用的模型名称", "protocol": "请选择请求协议", - "successThresholdRequired": "请输入最小连续健康检测成功次数" + "successThresholdRequired": "请输入最小连续健康检测成功次数", + "azureServiceUrlRequired": "请输入 Azure 服务 URL", + "ollamaServerHostRequired": "请输入 Ollama 服务主机名", + "ollamaServerPortRequired": "请输入 Ollama 服务端口" + }, + "placeholder": { + "azureServiceUrlPlaceholder": "需包含“/chat/completions”路径和“api-version”查询参数", + "ollamaServerHostPlaceholder": "请填写机器名、域名或 IP 地址" } }, "create": "创建AI服务提供者", diff --git a/frontend/src/pages/ai/components/ProviderForm/index.tsx b/frontend/src/pages/ai/components/ProviderForm/index.tsx index 3f1f4b79..c87c5f6c 100644 --- a/frontend/src/pages/ai/components/ProviderForm/index.tsx +++ b/frontend/src/pages/ai/components/ProviderForm/index.tsx @@ -2,12 +2,7 @@ import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; import { Button, Form, Input, InputNumber, Select, Switch } from 'antd'; import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; import { useTranslation } from 'react-i18next'; - -const providerTypeDisplayName = [ - { key: 'openai', label: 'llmProvider.providerTypes.openai' }, - { key: 'qwen', label: 'llmProvider.providerTypes.qwen' }, - { key: 'moonshot', label: 'llmProvider.providerTypes.moonshot' }, -]; +import { aiModelProviders } from '../../configs'; const protocolList = [ { label: "openai/v1", value: "openai/v1" }, @@ -17,6 +12,8 @@ const ProviderForm: React.FC = forwardRef((props: { value: any }, ref) => { const { t } = useTranslation(); const [form] = Form.useForm(); const [failoverEnabled, setFailoverEnabled] = useState(false); + const [providerType, setProviderType] = useState(); + const [providerConfig, setProviderConfig] = useState(); useEffect(() => { form.resetFields(); @@ -27,6 +24,7 @@ const ProviderForm: React.FC = forwardRef((props: { value: any }, ref) => { protocol, tokens, tokenFailoverConfig = {}, + rawConfigs = {}, } = props.value; const { failureThreshold, @@ -49,11 +47,15 @@ const ProviderForm: React.FC = forwardRef((props: { value: any }, ref) => { healthCheckInterval: healthCheckInterval || 5000, healthCheckTimeout: healthCheckTimeout || 10000, healthCheckModel, - }) + rawConfigs, + }); + + onProviderTypeChanged(type); } return () => { setFailoverEnabled(false); + onProviderTypeChanged(null); } }, [props.value]); @@ -78,12 +80,18 @@ const ProviderForm: React.FC = forwardRef((props: { value: any }, ref) => { healthCheckTimeout: values.healthCheckTimeout, healthCheckModel: values.healthCheckModel, }, + rawConfigs: values.rawConfigs, }; return result; }, })); + function onProviderTypeChanged(value: string | null) { + setProviderType(value); + setProviderConfig(value ? aiModelProviders.find(p => p.value === value) : null); + } + return (
{ > + + + ) + } + + { + providerType === 'ollama' && ( + <> + + + + + + + + ) + } + { failoverEnabled ? <> diff --git a/frontend/src/pages/ai/configs.tsx b/frontend/src/pages/ai/configs.tsx index 9495768b..4692ef1e 100644 --- a/frontend/src/pages/ai/configs.tsx +++ b/frontend/src/pages/ai/configs.tsx @@ -28,7 +28,7 @@ export const aiModelProviders = [ ], }, { - label: "Qwen", + label: 'Qwen', value: 'qwen', serviceAddress: 'https://dashscope.aliyuncs.com/compatible-mode/v1', modelNamePattern: 'qwen-*', @@ -51,6 +51,26 @@ export const aiModelProviders = [ }, ], }, + { + label: 'Moonshot', + value: 'moonshot', + serviceAddress: 'https://api.moonshot.cn/v1', + modelNamePattern: 'moonshot-*', + targetModelList: [ + { + label: 'moonshot-v1-8k', + value: 'moonshot-v1-8k', + }, + { + label: 'moonshot-v1-32k', + value: 'moonshot-v1-32k', + }, + { + label: 'moonshot-v1-128k', + value: 'moonshot-v1-128k', + }, + ], + }, { label: 'Azure', value: 'azure', @@ -78,6 +98,12 @@ export const aiModelProviders = [ value: 'gpt-4o-mini', }, ], + getProviderEndpoints: (record) => { + if (!record.rawConfigs) { + return null; + } + return [record.rawConfigs['azureServiceUrl']]; + }, }, { label: 'Claude', @@ -99,26 +125,6 @@ export const aiModelProviders = [ }, ], }, - { - label: "Moonshot", - value: 'moonshot', - serviceAddress: 'https://api.moonshot.cn/v1', - modelNamePattern: 'moonshot-*', - targetModelList: [ - { - label: 'moonshot-v1-8k', - value: 'moonshot-v1-8k', - }, - { - label: 'moonshot-v1-32k', - value: 'moonshot-v1-32k', - }, - { - label: 'moonshot-v1-128k', - value: 'moonshot-v1-128k', - }, - ], - }, { label: 'Baichuan', value: 'baichuan', @@ -235,42 +241,43 @@ export const aiModelProviders = [ }, ], }, - // { - // label: '360智脑', - // value: 'ai360', - // serviceAddress: 'https://api.360.cn', - // targetModelList: [], - // }, - // { - // label: '文心一言', - // value: 'baidu', - // serviceAddress: 'https://aip.baidubce.com', - // targetModelList: [ - // { - // label: 'ERNIE-4.0-8K', - // value: 'ERNIE-4.0-8K', - // }, - // { - // label: 'ERNIE-4.0-8K-Latest', - // value: 'ERNIE-4.0-8K-Latest', - // }, - // { - // label: 'ERNIE-4.0-Turbo-8K', - // value: 'ERNIE-4.0-Turbo-8K', - // }, - // { - // label: 'ERNIE-3.5-8K', - // value: 'ERNIE-3.5-8K', - // }, - // { - // label: 'ERNIE-3.5-128K', - // value: 'ERNIE-3.5-128K', - // } - // ] - // }, + { + label: '360智脑', + value: 'ai360', + serviceAddress: 'https://api.360.cn', + targetModelList: [], + }, + { + label: '文心一言', + value: 'baidu', + serviceAddress: 'https://aip.baidubce.com', + targetModelList: [ + { + label: 'ERNIE-4.0-8K', + value: 'ERNIE-4.0-8K', + }, + { + label: 'ERNIE-4.0-8K-Latest', + value: 'ERNIE-4.0-8K-Latest', + }, + { + label: 'ERNIE-4.0-Turbo-8K', + value: 'ERNIE-4.0-Turbo-8K', + }, + { + label: 'ERNIE-3.5-8K', + value: 'ERNIE-3.5-8K', + }, + { + label: 'ERNIE-3.5-128K', + value: 'ERNIE-3.5-128K', + }, + ], + }, { label: 'Hunyuan', value: 'hunyuan', + enabled: false, serviceAddress: 'https://hunyuan.tencentcloudapi.com', modelNamePattern: 'hunyuan-*', targetModelList: [ @@ -339,6 +346,7 @@ export const aiModelProviders = [ { label: 'Spark', value: 'spark', + enabled: false, serviceAddress: 'https://spark-api-open.xf-yun.com', targetModelList: [ { @@ -427,4 +435,75 @@ export const aiModelProviders = [ }, ], }, + { + label: 'Cohere', + value: 'cohere', + serviceAddress: 'https://api.cohere.com', + targetModelList: [], + }, + { + label: 'Coze', + value: 'coze', + serviceAddress: 'https://api.coze.cn', + targetModelList: [], + }, + { + label: 'DeepSeek', + value: 'deepseek', + serviceAddress: 'https://api.deepseek.com', + targetModelList: [], + }, + { + label: 'GitHub Models', + value: 'github', + serviceAddress: 'https://models.inference.ai.azure.com', + targetModelList: [], + }, + { + label: 'Groq', + value: 'groq', + serviceAddress: 'https://api.groq.com', + targetModelList: [], + }, + { + label: 'Ollama', + value: 'ollama', + tokenRequired: false, + targetModelList: [], + getProviderEndpoints: (record) => { + if (!record.rawConfigs) { + return null; + } + const host = record.rawConfigs['ollamaServerHost']; + const port = record.rawConfigs['ollamaServerPort']; + return host && port ? [`http://${host}:${port}`] : null; + }, + }, + { + label: 'Mistral', + value: 'mistral', + serviceAddress: 'https://api.mistral.ai', + targetModelList: [], + }, + { + label: 'Cloudflare', + value: 'cloudflare', + enabled: false, + serviceAddress: 'https://api.cloudflare.com', + targetModelList: [], + }, + { + label: 'DeepL', + value: 'deepl', + enabled: false, + serviceAddress: 'https://api.deepl.com', + targetModelList: [], + }, + { + label: 'Together AI', + value: 'together-ai', + enabled: false, + serviceAddress: 'https://api.together.xyz', + targetModelList: [], + }, ]; diff --git a/frontend/src/pages/ai/provider.tsx b/frontend/src/pages/ai/provider.tsx index 18fdb6bd..9fba59ec 100644 --- a/frontend/src/pages/ai/provider.tsx +++ b/frontend/src/pages/ai/provider.tsx @@ -1,42 +1,38 @@ import { LlmProvider } from '@/interfaces/llm-provider'; import { addLlmProvider, deleteLlmProvider, getLlmProviders, updateLlmProvider } from '@/services/llm-provider'; -import { ExclamationCircleOutlined, RedoOutlined, EyeTwoTone, EyeInvisibleTwoTone } from '@ant-design/icons'; +import { ExclamationCircleOutlined, EyeInvisibleTwoTone, EyeTwoTone, RedoOutlined } from '@ant-design/icons'; import { PageContainer } from '@ant-design/pro-layout'; import { useRequest } from 'ahooks'; import { Button, Col, Drawer, Form, Modal, Row, Space, Table, Typography } from 'antd'; import React, { useEffect, useRef, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import ProviderForm from './components/ProviderForm'; +import { aiModelProviders } from './configs'; const { Text } = Typography; -const providerTypeDisplayName = { - openai: 'llmProvider.providerTypes.openai', - qwen: 'llmProvider.providerTypes.qwen', - moonshot: 'llmProvider.providerTypes.moonshot', -}; interface FormRef { reset: () => void; handleSubmit: () => Promise; } -const EllipsisMiddle: React.FC = (params: { token: String }) => { - const { token } = params; +const EllipsisMiddle: React.FC = (params: { value: String }) => { + const { value } = params; const [isHidden, setIsHidden] = useState(true); const toggledText = () => { if (!isHidden) { - return token; + return value; } const prefixLength = 3; const suffixLength = 3; - if (token.length - prefixLength - suffixLength > 6) { - return `${token.slice(0, 3)}******${token.slice(-3)}`; + if (value.length - prefixLength - suffixLength > 6) { + return `${value.slice(0, 3)}******${value.slice(-3)}`; } - if (token.length > 2) { - return `${token.slice(0, 1)}******${token.slice(-1)}`; + if (value.length > 2) { + return `${value.slice(0, 1)}******${value.slice(-1)}`; } - return `${token.slice(0, 1)}******`; + return `${value.slice(0, 1)}******`; }; return ( @@ -61,7 +57,12 @@ const LlmProviderList: React.FC = () => { title: t('llmProvider.columns.type'), dataIndex: 'type', key: 'type', - render: (value) => t(providerTypeDisplayName[value] || value), + render: (value) => { + const providerConfig = aiModelProviders.find(c => c.value === value); + const key = `llmProvider.providerTypes.${value}`; + const text = t(key); + return text !== key ? text : (providerConfig?.label || value); + }, }, { title: t('llmProvider.columns.name'), @@ -74,7 +75,11 @@ const LlmProviderList: React.FC = () => { dataIndex: 'endpoints', key: 'endpoints', ellipsis: true, - render: (value) => { + render: (value, record) => { + const providerConfig = aiModelProviders.find(p => p.value === record.type); + if (providerConfig && typeof providerConfig.getProviderEndpoints === 'function') { + value = providerConfig.getProviderEndpoints(record); + } if (!Array.isArray(value) || !value.length) { return '-'; } @@ -89,7 +94,7 @@ const LlmProviderList: React.FC = () => { if (!Array.isArray(value) || !value.length) { return '-'; } - return value.map((token) => ); + return value.map((token) => ); }, }, { diff --git a/frontend/src/pages/route/components/RouteForm/index.tsx b/frontend/src/pages/route/components/RouteForm/index.tsx index 656eee97..f32c773d 100644 --- a/frontend/src/pages/route/components/RouteForm/index.tsx +++ b/frontend/src/pages/route/components/RouteForm/index.tsx @@ -3,7 +3,7 @@ import { OptionItem } from '@/interfaces/common'; import { DEFAULT_DOMAIN, Domain } from '@/interfaces/domain'; import { upstreamServiceToString } from '@/interfaces/route'; import { getGatewayDomains, getGatewayServices } from '@/services'; -import { InfoCircleOutlined, QuestionCircleFilled, QuestionCircleOutlined } from '@ant-design/icons'; +import { QuestionCircleOutlined } from '@ant-design/icons'; import { useRequest } from 'ahooks'; import { Checkbox, Form, Input, Select, Tooltip } from 'antd'; import { uniqueId } from "lodash";