|
|
@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
For commercial licensing, please contact [email protected]
|
|
|
*/
|
|
|
|
|
|
-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 = ({
|
|
|
<div className='grid grid-cols-2 gap-x-4'>
|
|
|
{categoryData.models.map((model) => (
|
|
|
<Checkbox key={model} value={model} className='my-1'>
|
|
|
- {model}
|
|
|
+ <span className='flex items-center gap-2'>
|
|
|
+ <span>{model}</span>
|
|
|
+ {redirectOnlySet.has(normalizeModelName(model)) && (
|
|
|
+ <Tooltip
|
|
|
+ position='top'
|
|
|
+ content={t('来自模型重定向,尚未加入模型列表')}
|
|
|
+ >
|
|
|
+ <IconInfoCircle
|
|
|
+ size='small'
|
|
|
+ className='text-amber-500 cursor-help'
|
|
|
+ />
|
|
|
+ </Tooltip>
|
|
|
+ )}
|
|
|
+ </span>
|
|
|
</Checkbox>
|
|
|
))}
|
|
|
</div>
|