diff --git a/web/src/components/table/channels/modals/EditChannelModal.jsx b/web/src/components/table/channels/modals/EditChannelModal.jsx
index 25e0f95b..4b46444d 100644
--- a/web/src/components/table/channels/modals/EditChannelModal.jsx
+++ b/web/src/components/table/channels/modals/EditChannelModal.jsx
@@ -190,6 +190,30 @@ const EditChannelModal = (props) => {
const [keyMode, setKeyMode] = useState('append'); // 密钥模式:replace(覆盖)或 append(追加)
const [isEnterpriseAccount, setIsEnterpriseAccount] = useState(false); // 是否为企业账户
const [doubaoApiEditUnlocked, setDoubaoApiEditUnlocked] = useState(false); // 豆包渠道自定义 API 地址隐藏入口
+ const redirectModelList = useMemo(() => {
+ const mapping = inputs.model_mapping;
+ if (typeof mapping !== 'string') return [];
+ const trimmed = mapping.trim();
+ if (!trimmed) return [];
+ try {
+ const parsed = JSON.parse(trimmed);
+ if (
+ !parsed ||
+ typeof parsed !== 'object' ||
+ Array.isArray(parsed)
+ ) {
+ return [];
+ }
+ const values = Object.values(parsed)
+ .map((value) =>
+ typeof value === 'string' ? value.trim() : undefined,
+ )
+ .filter((value) => value);
+ return Array.from(new Set(values));
+ } catch (error) {
+ return [];
+ }
+ }, [inputs.model_mapping]);
// 密钥显示状态
const [keyDisplayState, setKeyDisplayState] = useState({
@@ -3044,6 +3068,7 @@ const EditChannelModal = (props) => {
visible={modelModalVisible}
models={fetchedModels}
selected={inputs.models}
+ redirectModels={redirectModelList}
onConfirm={(selectedModels) => {
handleInputChange('models', selectedModels);
showSuccess(t('模型列表已更新'));
diff --git a/web/src/components/table/channels/modals/ModelSelectModal.jsx b/web/src/components/table/channels/modals/ModelSelectModal.jsx
index 0fe98167..21ac768c 100644
--- a/web/src/components/table/channels/modals/ModelSelectModal.jsx
+++ b/web/src/components/table/channels/modals/ModelSelectModal.jsx
@@ -17,7 +17,7 @@ along with this program. If not, see .
For commercial licensing, please contact support@quantumnous.com
*/
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useMemo } from 'react';
import { useIsMobile } from '../../../../hooks/common/useIsMobile';
import {
Modal,
@@ -28,12 +28,13 @@ import {
Empty,
Tabs,
Collapse,
+ Tooltip,
} from '@douyinfe/semi-ui';
import {
IllustrationNoResult,
IllustrationNoResultDark,
} from '@douyinfe/semi-illustrations';
-import { IconSearch } from '@douyinfe/semi-icons';
+import { IconSearch, IconInfoCircle } from '@douyinfe/semi-icons';
import { useTranslation } from 'react-i18next';
import { getModelCategories } from '../../../../helpers/render';
@@ -41,6 +42,7 @@ const ModelSelectModal = ({
visible,
models = [],
selected = [],
+ redirectModels = [],
onConfirm,
onCancel,
}) => {
@@ -50,15 +52,54 @@ const ModelSelectModal = ({
const [activeTab, setActiveTab] = useState('new');
const isMobile = useIsMobile();
+ const normalizeModelName = (model) =>
+ typeof model === 'string' ? model.trim() : '';
+ const normalizedRedirectModels = useMemo(
+ () =>
+ Array.from(
+ new Set(
+ (redirectModels || [])
+ .map((model) => normalizeModelName(model))
+ .filter(Boolean),
+ ),
+ ),
+ [redirectModels],
+ );
+ const normalizedSelectedSet = useMemo(() => {
+ const set = new Set();
+ (selected || []).forEach((model) => {
+ const normalized = normalizeModelName(model);
+ if (normalized) {
+ set.add(normalized);
+ }
+ });
+ return set;
+ }, [selected]);
+ const classificationSet = useMemo(() => {
+ const set = new Set(normalizedSelectedSet);
+ normalizedRedirectModels.forEach((model) => set.add(model));
+ return set;
+ }, [normalizedSelectedSet, normalizedRedirectModels]);
+ const redirectOnlySet = useMemo(() => {
+ const set = new Set();
+ normalizedRedirectModels.forEach((model) => {
+ if (!normalizedSelectedSet.has(model)) {
+ set.add(model);
+ }
+ });
+ return set;
+ }, [normalizedRedirectModels, normalizedSelectedSet]);
const filteredModels = models.filter((m) =>
- m.toLowerCase().includes(keyword.toLowerCase()),
+ String(m || '').toLowerCase().includes(keyword.toLowerCase()),
);
// 分类模型:新获取的模型和已有模型
- const newModels = filteredModels.filter((model) => !selected.includes(model));
+ const isExistingModel = (model) =>
+ classificationSet.has(normalizeModelName(model));
+ const newModels = filteredModels.filter((model) => !isExistingModel(model));
const existingModels = filteredModels.filter((model) =>
- selected.includes(model),
+ isExistingModel(model),
);
// 同步外部选中值
@@ -228,7 +269,20 @@ const ModelSelectModal = ({
{categoryData.models.map((model) => (
- {model}
+
+ {model}
+ {redirectOnlySet.has(normalizeModelName(model)) && (
+
+
+
+ )}
+
))}