Просмотр исходного кода

feat: show removed upstream models in fetch models modal

Seefs 1 месяц назад
Родитель
Сommit
4c21c4c43b

+ 19 - 0
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('模型列表已更新'));

+ 56 - 7
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
       />
 
-      <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;