feat: Add endpoint type filter to model pricing system

- Create PricingEndpointTypes.jsx component for endpoint type filtering
- Add filterEndpointType state management in useModelPricingData hook
- Integrate endpoint type filtering logic in filteredModels computation
- Update PricingSidebar.jsx to include endpoint type filter component
- Update PricingFilterModal.jsx to support endpoint type filtering on mobile
- Extend resetPricingFilters utility function to include endpoint type reset
- Support filtering models by endpoint types (OpenAI, Anthropic, Gemini, etc.)
- Display model count for each endpoint type with localized labels
- Ensure filter state resets to first page when endpoint type changes

This enhancement allows users to filter models by their supported endpoint types,
providing more granular control over model selection in the pricing interface.
This commit is contained in:
t0ng7u
2025-07-24 03:25:57 +08:00
parent 53be79a00e
commit 59a76b3970
5 changed files with 135 additions and 2 deletions

View File

@@ -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 <https://www.gnu.org/licenses/>.
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 (
<SelectableButtonGroup
title={t('端点类型')}
items={items}
activeValue={filterEndpointType}
onChange={setFilterEndpointType}
loading={loading}
t={t}
/>
);
};
export default PricingEndpointTypes;

View File

@@ -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}
/>
<PricingEndpointTypes
filterEndpointType={filterEndpointType}
setFilterEndpointType={setFilterEndpointType}
models={categoryProps.models}
loading={loading}
t={t}
/>
</div>
);
};

View File

@@ -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}
/>
<PricingEndpointTypes
filterEndpointType={filterEndpointType}
setFilterEndpointType={setFilterEndpointType}
models={categoryProps.models}
loading={loading}
t={t}
/>
</div>
</Modal>
);

View File

@@ -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);

View File

@@ -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,