diff --git a/web/src/components/table/model-pricing/filter/PricingEndpointTypes.jsx b/web/src/components/table/model-pricing/filter/PricingEndpointTypes.jsx new file mode 100644 index 00000000..d9f22d95 --- /dev/null +++ b/web/src/components/table/model-pricing/filter/PricingEndpointTypes.jsx @@ -0,0 +1,92 @@ +/* +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 from 'react'; +import SelectableButtonGroup from '../../../common/ui/SelectableButtonGroup'; + +/** + * 端点类型筛选组件 + * @param {string|'all'} filterEndpointType 当前值 + * @param {Function} setFilterEndpointType setter + * @param {Array} models 模型列表 + * @param {boolean} loading 是否加载中 + * @param {Function} t i18n + */ +const PricingEndpointTypes = ({ filterEndpointType, setFilterEndpointType, models = [], loading = false, t }) => { + // 获取所有可用的端点类型 + const getAllEndpointTypes = () => { + const endpointTypes = new Set(); + models.forEach(model => { + if (model.supported_endpoint_types && Array.isArray(model.supported_endpoint_types)) { + model.supported_endpoint_types.forEach(endpoint => { + endpointTypes.add(endpoint); + }); + } + }); + return Array.from(endpointTypes).sort(); + }; + + // 计算每个端点类型的模型数量 + const getEndpointTypeCount = (endpointType) => { + if (endpointType === 'all') { + return models.length; + } + return models.filter(model => + model.supported_endpoint_types && + model.supported_endpoint_types.includes(endpointType) + ).length; + }; + + // 端点类型显示名称映射 + const getEndpointTypeLabel = (endpointType) => { + const labelMap = { + 'openai': 'OpenAI', + 'openai-response': 'OpenAI Response', + 'anthropic': 'Anthropic', + 'gemini': 'Gemini', + 'jina-rerank': 'Jina Rerank', + 'image-generation': t('图像生成'), + }; + return labelMap[endpointType] || endpointType; + }; + + const availableEndpointTypes = getAllEndpointTypes(); + + const items = [ + { value: 'all', label: t('全部端点'), tagCount: getEndpointTypeCount('all') }, + ...availableEndpointTypes.map(endpointType => ({ + value: endpointType, + label: getEndpointTypeLabel(endpointType), + tagCount: getEndpointTypeCount(endpointType) + })) + ]; + + return ( + + ); +}; + +export default PricingEndpointTypes; \ No newline at end of file diff --git a/web/src/components/table/model-pricing/layout/PricingSidebar.jsx b/web/src/components/table/model-pricing/layout/PricingSidebar.jsx index 8b4ccfd8..f503e246 100644 --- a/web/src/components/table/model-pricing/layout/PricingSidebar.jsx +++ b/web/src/components/table/model-pricing/layout/PricingSidebar.jsx @@ -22,6 +22,7 @@ import { Button } from '@douyinfe/semi-ui'; import PricingCategories from '../filter/PricingCategories'; import PricingGroups from '../filter/PricingGroups'; import PricingQuotaTypes from '../filter/PricingQuotaTypes'; +import PricingEndpointTypes from '../filter/PricingEndpointTypes'; import PricingDisplaySettings from '../filter/PricingDisplaySettings'; import { resetPricingFilters } from '../../../../helpers/utils'; @@ -40,6 +41,8 @@ const PricingSidebar = ({ setFilterGroup, filterQuotaType, setFilterQuotaType, + filterEndpointType, + setFilterEndpointType, currentPage, setCurrentPage, loading, @@ -58,6 +61,7 @@ const PricingSidebar = ({ setViewMode, setFilterGroup, setFilterQuotaType, + setFilterEndpointType, setCurrentPage, }); @@ -114,6 +118,14 @@ const PricingSidebar = ({ loading={loading} t={t} /> + + ); }; diff --git a/web/src/components/table/model-pricing/modal/PricingFilterModal.jsx b/web/src/components/table/model-pricing/modal/PricingFilterModal.jsx index 3d0601b8..ff8459d4 100644 --- a/web/src/components/table/model-pricing/modal/PricingFilterModal.jsx +++ b/web/src/components/table/model-pricing/modal/PricingFilterModal.jsx @@ -22,6 +22,7 @@ import { Modal, Button } from '@douyinfe/semi-ui'; import PricingCategories from '../filter/PricingCategories'; import PricingGroups from '../filter/PricingGroups'; import PricingQuotaTypes from '../filter/PricingQuotaTypes'; +import PricingEndpointTypes from '../filter/PricingEndpointTypes'; import PricingDisplaySettings from '../filter/PricingDisplaySettings'; import { resetPricingFilters } from '../../../../helpers/utils'; @@ -46,6 +47,8 @@ const PricingFilterModal = ({ setFilterGroup, filterQuotaType, setFilterQuotaType, + filterEndpointType, + setFilterEndpointType, currentPage, setCurrentPage, loading, @@ -63,6 +66,7 @@ const PricingFilterModal = ({ setViewMode, setFilterGroup, setFilterQuotaType, + setFilterEndpointType, setCurrentPage, }); @@ -133,6 +137,14 @@ const PricingFilterModal = ({ loading={loading} t={t} /> + + ); diff --git a/web/src/helpers/utils.js b/web/src/helpers/utils.js index 22b4fbc6..9972fb3a 100644 --- a/web/src/helpers/utils.js +++ b/web/src/helpers/utils.js @@ -682,6 +682,7 @@ export const resetPricingFilters = ({ setViewMode, setFilterGroup, setFilterQuotaType, + setFilterEndpointType, setCurrentPage, }) => { // 重置搜索 @@ -728,6 +729,11 @@ export const resetPricingFilters = ({ setFilterQuotaType('all'); } + // 重置端点类型筛选 + if (typeof setFilterEndpointType === 'function') { + setFilterEndpointType('all'); + } + // 重置当前页面 if (typeof setCurrentPage === 'function') { setCurrentPage(1); diff --git a/web/src/hooks/model-pricing/useModelPricingData.js b/web/src/hooks/model-pricing/useModelPricingData.js index c32ddf84..6d750b87 100644 --- a/web/src/hooks/model-pricing/useModelPricingData.js +++ b/web/src/hooks/model-pricing/useModelPricingData.js @@ -35,6 +35,7 @@ export const useModelPricingData = () => { const [filterGroup, setFilterGroup] = useState('all'); // 用于 Table 的可用分组筛选,“all” 表示不过滤 const [filterQuotaType, setFilterQuotaType] = useState('all'); // 计费类型筛选: 'all' | 0 | 1 const [activeKey, setActiveKey] = useState('all'); + const [filterEndpointType, setFilterEndpointType] = useState('all'); // 端点类型筛选: 'all' | string const [pageSize, setPageSize] = useState(10); const [currentPage, setCurrentPage] = useState(1); const [currency, setCurrency] = useState('USD'); @@ -93,6 +94,14 @@ export const useModelPricingData = () => { result = result.filter(model => model.quota_type === filterQuotaType); } + // 端点类型筛选 + if (filterEndpointType !== 'all') { + result = result.filter(model => + model.supported_endpoint_types && + model.supported_endpoint_types.includes(filterEndpointType) + ); + } + // 搜索筛选 if (searchValue.length > 0) { const searchTerm = searchValue.toLowerCase(); @@ -102,7 +111,7 @@ export const useModelPricingData = () => { } return result; - }, [activeKey, models, searchValue, filterGroup, filterQuotaType]); + }, [activeKey, models, searchValue, filterGroup, filterQuotaType, filterEndpointType]); const rowSelection = useMemo( () => ({ @@ -216,7 +225,7 @@ export const useModelPricingData = () => { // 当筛选条件变化时重置到第一页 useEffect(() => { setCurrentPage(1); - }, [activeKey, filterGroup, filterQuotaType, searchValue]); + }, [activeKey, filterGroup, filterQuotaType, filterEndpointType, searchValue]); return { // 状态 @@ -234,6 +243,8 @@ export const useModelPricingData = () => { setFilterGroup, filterQuotaType, setFilterQuotaType, + filterEndpointType, + setFilterEndpointType, activeKey, setActiveKey, pageSize,