Merge pull request #4470 from seefs001/feature/show-removed-upstream-models

feat: show removed upstream models in fetch models modal
This commit is contained in:
Calcium-Ion
2026-04-26 20:20:21 +08:00
committed by GitHub
2 changed files with 75 additions and 7 deletions
@@ -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('模型列表已更新'));
@@ -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
/>
<Spin spinning={!models || models.length === 0}>
<Spin
spinning={!models || (models.length === 0 && removedModels.length === 0)}
>
<div style={{ maxHeight: 400, overflowY: 'auto', paddingRight: 8 }}>
{filteredModels.length === 0 ? (
{filteredModels.length === 0 && removedModels.length === 0 ? (
<Empty
image={
<IllustrationNoResult style={{ width: 150, height: 150 }} />
@@ -369,6 +406,14 @@ const ModelSelectModal = ({
{renderModelsByCategory(existingModelsByCategory, 'existing')}
</div>
)}
{activeTab === 'removed' && removedModels.length > 0 && (
<div>
{renderModelsByCategory(
categorizeModels(removedModels),
'removed',
)}
</div>
)}
</Checkbox.Group>
)}
</div>
@@ -382,7 +427,11 @@ const ModelSelectModal = ({
<div className='flex items-center justify-end gap-2'>
{(() => {
const currentModels =
activeTab === 'new' ? newModels : existingModels;
activeTab === 'new'
? newModels
: activeTab === 'removed'
? removedModels
: existingModels;
const currentSelected = currentModels.filter((model) =>
checkedList.includes(model),
).length;