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,