diff --git a/web/src/components/table/channels/modals/EditChannelModal.jsx b/web/src/components/table/channels/modals/EditChannelModal.jsx index f0395d2f..fad105b1 100644 --- a/web/src/components/table/channels/modals/EditChannelModal.jsx +++ b/web/src/components/table/channels/modals/EditChannelModal.jsx @@ -269,6 +269,24 @@ const EditChannelModal = (props) => { return []; } }, [inputs.model_mapping]); + const redirectModelKeyList = 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 keys = Object.keys(parsed) + .map((key) => key.trim()) + .filter((key) => key); + return Array.from(new Set(keys)); + } catch (error) { + return []; + } + }, [inputs.model_mapping]); const upstreamDetectedModels = useMemo( () => Array.from( @@ -3842,6 +3860,7 @@ const EditChannelModal = (props) => { models={fetchedModels} selected={inputs.models} redirectModels={redirectModelList} + redirectSourceModels={redirectModelKeyList} 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 b38580b6..d6e46f10 100644 --- a/web/src/components/table/channels/modals/ModelSelectModal.jsx +++ b/web/src/components/table/channels/modals/ModelSelectModal.jsx @@ -43,6 +43,7 @@ const ModelSelectModal = ({ models = [], selected = [], redirectModels = [], + redirectSourceModels = [], onConfirm, onCancel, }) => { @@ -54,6 +55,14 @@ const ModelSelectModal = ({ if (typeof model === 'object' && model.model_name) return model.model_name; return String(model ?? ''); }; + const normalizeModelList = (modelList = []) => + Array.from( + new Set( + (modelList || []) + .map((model) => getModelName(model).trim()) + .filter(Boolean), + ), + ); const normalizedSelected = useMemo( () => (selected || []).map(getModelName), @@ -78,6 +87,10 @@ const ModelSelectModal = ({ ), [redirectModels], ); + const normalizedRedirectSourceSet = useMemo( + () => new Set(normalizeModelList(redirectSourceModels)), + [redirectSourceModels], + ); const normalizedSelectedSet = useMemo(() => { const set = new Set(); (selected || []).forEach((model) => { @@ -116,6 +129,16 @@ const ModelSelectModal = ({ const existingModels = filteredModels.filter((model) => isExistingModel(model), ); + const fetchedModelSet = useMemo( + () => new Set(normalizeModelList(models)), + [models], + ); + const removedModels = normalizeModelList(selected).filter( + (model) => + !fetchedModelSet.has(model) && + !normalizedRedirectSourceSet.has(model) && + model.toLowerCase().includes(keyword.toLowerCase()), + ); // 同步外部选中值 useEffect(() => { @@ -127,11 +150,15 @@ const ModelSelectModal = ({ // 当模型列表变化时,设置默认tab useEffect(() => { if (visible) { - // 默认显示新获取模型tab,如果没有新模型则显示已有模型 - const hasNewModels = newModels.length > 0; - setActiveTab(hasNewModels ? 'new' : 'existing'); + if (newModels.length > 0) { + setActiveTab('new'); + } else if (removedModels.length > 0) { + setActiveTab('removed'); + } else { + setActiveTab('existing'); + } } - }, [visible, newModels.length, selected]); + }, [visible, newModels.length, removedModels.length, selected]); const handleOk = () => { onConfirm && onConfirm(checkedList); @@ -197,6 +224,14 @@ const ModelSelectModal = ({ }, ] : []), + ...(removedModels.length > 0 + ? [ + { + tab: `${t('上游已删除的模型')} (${removedModels.length})`, + itemKey: 'removed', + }, + ] + : []), ]; // 处理分类全选/取消全选 @@ -343,9 +378,11 @@ const ModelSelectModal = ({ showClear /> - +
- {filteredModels.length === 0 ? ( + {filteredModels.length === 0 && removedModels.length === 0 ? ( @@ -369,6 +406,14 @@ const ModelSelectModal = ({ {renderModelsByCategory(existingModelsByCategory, 'existing')}
)} + {activeTab === 'removed' && removedModels.length > 0 && ( +
+ {renderModelsByCategory( + categorizeModels(removedModels), + 'removed', + )} +
+ )} )} @@ -382,7 +427,11 @@ const ModelSelectModal = ({
{(() => { const currentModels = - activeTab === 'new' ? newModels : existingModels; + activeTab === 'new' + ? newModels + : activeTab === 'removed' + ? removedModels + : existingModels; const currentSelected = currentModels.filter((model) => checkedList.includes(model), ).length;