/* Copyright (C) 2025 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import React, { useState, useEffect } from 'react'; import { useIsMobile } from '../../../../hooks/common/useIsMobile'; import { Modal, Checkbox, Spin, Input, Typography, Empty, Tabs, Collapse } from '@douyinfe/semi-ui'; import { IllustrationNoResult, IllustrationNoResultDark } from '@douyinfe/semi-illustrations'; import { IconSearch } from '@douyinfe/semi-icons'; import { useTranslation } from 'react-i18next'; import { getModelCategories } from '../../../../helpers/render'; const ModelSelectModal = ({ visible, models = [], selected = [], onConfirm, onCancel }) => { const { t } = useTranslation(); const [checkedList, setCheckedList] = useState(selected); const [keyword, setKeyword] = useState(''); const [activeTab, setActiveTab] = useState('new'); const isMobile = useIsMobile(); const filteredModels = models.filter((m) => m.toLowerCase().includes(keyword.toLowerCase())); // 分类模型:新获取的模型和已有模型 const newModels = filteredModels.filter(model => !selected.includes(model)); const existingModels = filteredModels.filter(model => selected.includes(model)); // 同步外部选中值 useEffect(() => { if (visible) { setCheckedList(selected); } }, [visible, selected]); // 当模型列表变化时,设置默认tab useEffect(() => { if (visible) { // 默认显示新获取模型tab,如果没有新模型则显示已有模型 const hasNewModels = newModels.length > 0; setActiveTab(hasNewModels ? 'new' : 'existing'); } }, [visible, newModels.length, selected]); const handleOk = () => { onConfirm && onConfirm(checkedList); }; // 按厂商分类模型 const categorizeModels = (models) => { const categories = getModelCategories(t); const categorizedModels = {}; const uncategorizedModels = []; models.forEach(model => { let foundCategory = false; for (const [key, category] of Object.entries(categories)) { if (key !== 'all' && category.filter({ model_name: model })) { if (!categorizedModels[key]) { categorizedModels[key] = { label: category.label, icon: category.icon, models: [] }; } categorizedModels[key].models.push(model); foundCategory = true; break; } } if (!foundCategory) { uncategorizedModels.push(model); } }); // 如果有未分类模型,添加到"其他"分类 if (uncategorizedModels.length > 0) { categorizedModels['other'] = { label: t('其他'), icon: null, models: uncategorizedModels }; } return categorizedModels; }; const newModelsByCategory = categorizeModels(newModels); const existingModelsByCategory = categorizeModels(existingModels); // Tab列表配置 const tabList = [ ...(newModels.length > 0 ? [{ tab: `${t('新获取的模型')} (${newModels.length})`, itemKey: 'new' }] : []), ...(existingModels.length > 0 ? [{ tab: `${t('已有的模型')} (${existingModels.length})`, itemKey: 'existing' }] : []) ]; // 处理分类全选/取消全选 const handleCategorySelectAll = (categoryModels, isChecked) => { let newCheckedList = [...checkedList]; if (isChecked) { // 全选:添加该分类下所有未选中的模型 categoryModels.forEach(model => { if (!newCheckedList.includes(model)) { newCheckedList.push(model); } }); } else { // 取消全选:移除该分类下所有已选中的模型 newCheckedList = newCheckedList.filter(model => !categoryModels.includes(model)); } setCheckedList(newCheckedList); }; // 检查分类是否全选 const isCategoryAllSelected = (categoryModels) => { return categoryModels.length > 0 && categoryModels.every(model => checkedList.includes(model)); }; // 检查分类是否部分选中 const isCategoryIndeterminate = (categoryModels) => { const selectedCount = categoryModels.filter(model => checkedList.includes(model)).length; return selectedCount > 0 && selectedCount < categoryModels.length; }; const renderModelsByCategory = (modelsByCategory, categoryKeyPrefix) => { const categoryEntries = Object.entries(modelsByCategory); if (categoryEntries.length === 0) return null; // 生成所有面板的key,确保都展开 const allActiveKeys = categoryEntries.map((_, index) => `${categoryKeyPrefix}_${index}`); return ( {categoryEntries.map(([key, categoryData], index) => ( { e.stopPropagation(); // 防止触发面板折叠 handleCategorySelectAll(categoryData.models, e.target.checked); }} onClick={(e) => e.stopPropagation()} // 防止点击checkbox时折叠面板 /> } >
{categoryData.icon} {t('已选择 {{selected}} / {{total}}', { selected: categoryData.models.filter(model => checkedList.includes(model)).length, total: categoryData.models.length })}
{categoryData.models.map((model) => ( {model} ))}
))}
); }; return ( {t('选择模型')}
setActiveTab(key)} />
} visible={visible} onOk={handleOk} onCancel={onCancel} okText={t('确定')} cancelText={t('取消')} size={isMobile ? 'full-width' : 'large'} closeOnEsc maskClosable centered > } placeholder={t('搜索模型')} value={keyword} onChange={(v) => setKeyword(v)} showClear />
{filteredModels.length === 0 ? ( } darkModeImage={} description={t('暂无匹配模型')} style={{ padding: 30 }} /> ) : ( setCheckedList(vals)}> {activeTab === 'new' && newModels.length > 0 && (
{renderModelsByCategory(newModelsByCategory, 'new')}
)} {activeTab === 'existing' && existingModels.length > 0 && (
{renderModelsByCategory(existingModelsByCategory, 'existing')}
)}
)}
{(() => { const currentModels = activeTab === 'new' ? newModels : existingModels; const currentSelected = currentModels.filter(model => checkedList.includes(model)).length; const isAllSelected = currentModels.length > 0 && currentSelected === currentModels.length; const isIndeterminate = currentSelected > 0 && currentSelected < currentModels.length; return ( <> {t('已选择 {{selected}} / {{total}}', { selected: currentSelected, total: currentModels.length })} { handleCategorySelectAll(currentModels, e.target.checked); }} /> ); })()}
); }; export default ModelSelectModal;