-
{t('倍率')}
-
- {
- setModalImageUrl('/ratio.png');
- setIsModalOpenurl(true);
- }}
- />
-
+ ];
+
+ // 倍率列 - 只有在showRatio为true时才包含
+ const ratioColumn = {
+ title: () => (
+
+ {t('倍率')}
+
+ {
+ setModalImageUrl('/ratio.png');
+ setIsModalOpenurl(true);
+ }}
+ />
+
+
+ ),
+ dataIndex: 'model_ratio',
+ render: (text, record, index) => {
+ let content = text;
+ let completionRatio = parseFloat(record.completion_ratio.toFixed(3));
+ content = (
+
+
+ {t('模型倍率')}:{record.quota_type === 0 ? text : t('无')}
+
+
+ {t('补全倍率')}:
+ {record.quota_type === 0 ? completionRatio : t('无')}
+
+
+ {t('分组倍率')}:{groupRatio[selectedGroup]}
+
- ),
- dataIndex: 'model_ratio',
- render: (text, record, index) => {
- let content = text;
- let completionRatio = parseFloat(record.completion_ratio.toFixed(3));
+ );
+ return content;
+ },
+ };
+
+ // 价格列
+ const priceColumn = {
+ title: (
+
+ {t('模型价格')}
+ {/* 计费单位切换 */}
+ setTokenUnit(checked ? 'K' : 'M')}
+ checkedText="K"
+ uncheckedText="M"
+ />
+
+ ),
+ dataIndex: 'model_price',
+ render: (text, record, index) => {
+ let content = text;
+ if (record.quota_type === 0) {
+ let inputRatioPriceUSD = record.model_ratio * 2 * groupRatio[selectedGroup];
+ let completionRatioPriceUSD =
+ record.model_ratio * record.completion_ratio * 2 * groupRatio[selectedGroup];
+
+ const unitDivisor = tokenUnit === 'K' ? 1000 : 1;
+ const unitLabel = tokenUnit === 'K' ? 'K' : 'M';
+
+ let displayInput = displayPrice(inputRatioPriceUSD);
+ let displayCompletion = displayPrice(completionRatioPriceUSD);
+
+ const divisor = unitDivisor;
+ const numInput = parseFloat(displayInput.replace(/[^0-9.]/g, '')) / divisor;
+ const numCompletion = parseFloat(displayCompletion.replace(/[^0-9.]/g, '')) / divisor;
+
+ displayInput = `${currency === 'CNY' ? '¥' : '$'}${numInput.toFixed(3)}`;
+ displayCompletion = `${currency === 'CNY' ? '¥' : '$'}${numCompletion.toFixed(3)}`;
content = (
- {t('模型倍率')}:{record.quota_type === 0 ? text : t('无')}
+ {t('提示')} {displayInput} / 1{unitLabel} tokens
- {t('补全倍率')}:
- {record.quota_type === 0 ? completionRatio : t('无')}
-
-
- {t('分组倍率')}:{groupRatio[selectedGroup]}
+ {t('补全')} {displayCompletion} / 1{unitLabel} tokens
);
- return content;
- },
+ } else {
+ let priceUSD = parseFloat(text) * groupRatio[selectedGroup];
+ let displayVal = displayPrice(priceUSD);
+ content = (
+
+ {t('模型价格')}:{displayVal}
+
+ );
+ }
+ return content;
},
- {
- title: (
-
- {t('模型价格')}
- {/* 计费单位切换 */}
- setTokenUnit(checked ? 'K' : 'M')}
- checkedText="K"
- uncheckedText="M"
- />
-
- ),
- dataIndex: 'model_price',
- render: (text, record, index) => {
- let content = text;
- if (record.quota_type === 0) {
- let inputRatioPriceUSD = record.model_ratio * 2 * groupRatio[selectedGroup];
- let completionRatioPriceUSD =
- record.model_ratio * record.completion_ratio * 2 * groupRatio[selectedGroup];
+ };
- const unitDivisor = tokenUnit === 'K' ? 1000 : 1;
- const unitLabel = tokenUnit === 'K' ? 'K' : 'M';
+ // 根据showRatio决定是否包含倍率列
+ const columns = [...baseColumns];
+ if (showRatio) {
+ columns.push(ratioColumn);
+ }
+ columns.push(priceColumn);
- let displayInput = displayPrice(inputRatioPriceUSD);
- let displayCompletion = displayPrice(completionRatioPriceUSD);
-
- const divisor = unitDivisor;
- const numInput = parseFloat(displayInput.replace(/[^0-9.]/g, '')) / divisor;
- const numCompletion = parseFloat(displayCompletion.replace(/[^0-9.]/g, '')) / divisor;
-
- displayInput = `${currency === 'CNY' ? '¥' : '$'}${numInput.toFixed(3)}`;
- displayCompletion = `${currency === 'CNY' ? '¥' : '$'}${numCompletion.toFixed(3)}`;
- content = (
-
-
- {t('提示')} {displayInput} / 1{unitLabel} tokens
-
-
- {t('补全')} {displayCompletion} / 1{unitLabel} tokens
-
-
- );
- } else {
- let priceUSD = parseFloat(text) * groupRatio[selectedGroup];
- let displayVal = displayPrice(priceUSD);
- content = (
-
- {t('模型价格')}:{displayVal}
-
- );
- }
- return content;
- },
- },
- ];
+ return columns;
};
\ No newline at end of file
diff --git a/web/src/components/table/model-pricing/index.jsx b/web/src/components/table/model-pricing/index.jsx
index a8641ce5..d79be40c 100644
--- a/web/src/components/table/model-pricing/index.jsx
+++ b/web/src/components/table/model-pricing/index.jsx
@@ -17,50 +17,5 @@ along with this program. If not, see
.
For commercial licensing, please contact support@quantumnous.com
*/
-import React from 'react';
-import { Layout, Card, ImagePreview } from '@douyinfe/semi-ui';
-import ModelPricingTabs from './ModelPricingTabs.jsx';
-import ModelPricingFilters from './ModelPricingFilters.jsx';
-import ModelPricingTable from './ModelPricingTable.jsx';
-import ModelPricingHeader from './ModelPricingHeader.jsx';
-import { useModelPricingData } from '../../../hooks/model-pricing/useModelPricingData.js';
-
-const ModelPricingPage = () => {
- const modelPricingData = useModelPricingData();
-
- return (
-
-
-
-
-
- {/* 主卡片容器 */}
-
- {/* 顶部状态卡片 */}
-
-
- {/* 模型分类 Tabs */}
-
-
-
- {/* 搜索和表格区域 */}
-
-
-
-
- {/* 倍率说明图预览 */}
- modelPricingData.setIsModalOpenurl(visible)}
- />
-
-
-
-
-
-
- );
-};
-
-export default ModelPricingPage;
\ No newline at end of file
+// 为了向后兼容,这里重新导出新的 PricingPage 组件
+export { default } from './PricingPage.jsx';
\ No newline at end of file
diff --git a/web/src/components/table/model-pricing/sidebar/PricingCategories.jsx b/web/src/components/table/model-pricing/sidebar/PricingCategories.jsx
new file mode 100644
index 00000000..65cb58c7
--- /dev/null
+++ b/web/src/components/table/model-pricing/sidebar/PricingCategories.jsx
@@ -0,0 +1,44 @@
+/*
+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.jsx';
+
+const PricingCategories = ({ activeKey, setActiveKey, modelCategories, categoryCounts, availableCategories, t }) => {
+ const items = Object.entries(modelCategories)
+ .filter(([key]) => availableCategories.includes(key))
+ .map(([key, category]) => ({
+ value: key,
+ label: category.label,
+ icon: category.icon,
+ tagCount: categoryCounts[key] || 0,
+ }));
+
+ return (
+
+ );
+};
+
+export default PricingCategories;
\ No newline at end of file
diff --git a/web/src/components/table/model-pricing/sidebar/PricingGroups.jsx b/web/src/components/table/model-pricing/sidebar/PricingGroups.jsx
new file mode 100644
index 00000000..32643d76
--- /dev/null
+++ b/web/src/components/table/model-pricing/sidebar/PricingGroups.jsx
@@ -0,0 +1,58 @@
+/*
+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.jsx';
+
+/**
+ * 分组筛选组件
+ * @param {string} filterGroup 当前选中的分组,'all' 表示不过滤
+ * @param {Function} setFilterGroup 设置选中分组
+ * @param {Record
} usableGroup 后端返回的可用分组对象
+ * @param {Function} t i18n
+ */
+const PricingGroups = ({ filterGroup, setFilterGroup, usableGroup = {}, models = [], t }) => {
+ const groups = ['all', ...Object.keys(usableGroup)];
+
+ const items = groups.map((g) => {
+ let count = 0;
+ if (g === 'all') {
+ count = models.length;
+ } else {
+ count = models.filter(m => m.enable_groups && m.enable_groups.includes(g)).length;
+ }
+ return {
+ value: g,
+ label: g === 'all' ? t('全部分组') : g,
+ tagCount: count,
+ };
+ });
+
+ return (
+
+ );
+};
+
+export default PricingGroups;
\ No newline at end of file
diff --git a/web/src/components/table/model-pricing/sidebar/PricingQuotaTypes.jsx b/web/src/components/table/model-pricing/sidebar/PricingQuotaTypes.jsx
new file mode 100644
index 00000000..373f9f5d
--- /dev/null
+++ b/web/src/components/table/model-pricing/sidebar/PricingQuotaTypes.jsx
@@ -0,0 +1,49 @@
+/*
+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.jsx';
+
+/**
+ * 计费类型筛选组件
+ * @param {string|'all'|0|1} filterQuotaType 当前值
+ * @param {Function} setFilterQuotaType setter
+ * @param {Function} t i18n
+ */
+const PricingQuotaTypes = ({ filterQuotaType, setFilterQuotaType, models = [], t }) => {
+ const qtyCount = (type) => models.filter(m => type === 'all' ? true : m.quota_type === type).length;
+
+ const items = [
+ { value: 'all', label: t('全部类型'), tagCount: qtyCount('all') },
+ { value: 0, label: t('按量计费'), tagCount: qtyCount(0) },
+ { value: 1, label: t('按次计费'), tagCount: qtyCount(1) },
+ ];
+
+ return (
+
+ );
+};
+
+export default PricingQuotaTypes;
\ No newline at end of file
diff --git a/web/src/hooks/model-pricing/useModelPricingData.js b/web/src/hooks/model-pricing/useModelPricingData.js
index 60445f1e..ac58d817 100644
--- a/web/src/hooks/model-pricing/useModelPricingData.js
+++ b/web/src/hooks/model-pricing/useModelPricingData.js
@@ -32,6 +32,10 @@ export const useModelPricingData = () => {
const [modalImageUrl, setModalImageUrl] = useState('');
const [isModalOpenurl, setIsModalOpenurl] = useState(false);
const [selectedGroup, setSelectedGroup] = useState('default');
+ // 用于 Table 的可用分组筛选,“all” 表示不过滤
+ const [filterGroup, setFilterGroup] = useState('all');
+ // 计费类型筛选: 'all' | 0 | 1
+ const [filterQuotaType, setFilterQuotaType] = useState('all');
const [activeKey, setActiveKey] = useState('all');
const [pageSize, setPageSize] = useState(10);
const [currency, setCurrency] = useState('USD');
@@ -75,10 +79,22 @@ export const useModelPricingData = () => {
const filteredModels = useMemo(() => {
let result = models;
+ // 分类筛选
if (activeKey !== 'all') {
result = result.filter(model => modelCategories[activeKey].filter(model));
}
+ // 分组筛选
+ if (filterGroup !== 'all') {
+ result = result.filter(model => model.enable_groups.includes(filterGroup));
+ }
+
+ // 计费类型筛选
+ if (filterQuotaType !== 'all') {
+ result = result.filter(model => model.quota_type === filterQuotaType);
+ }
+
+ // 搜索筛选
if (filteredValue.length > 0) {
const searchTerm = filteredValue[0].toLowerCase();
result = result.filter(model =>
@@ -87,7 +103,7 @@ export const useModelPricingData = () => {
}
return result;
- }, [activeKey, models, filteredValue]);
+ }, [activeKey, models, filteredValue, filterGroup, filterQuotaType]);
const rowSelection = useMemo(
() => ({
@@ -184,6 +200,8 @@ export const useModelPricingData = () => {
const handleGroupClick = (group) => {
setSelectedGroup(group);
+ // 同时将分组过滤设置为该分组
+ setFilterGroup(group);
showInfo(
t('当前查看的分组为:{{group}},倍率为:{{ratio}}', {
group: group,
@@ -208,6 +226,10 @@ export const useModelPricingData = () => {
setIsModalOpenurl,
selectedGroup,
setSelectedGroup,
+ filterGroup,
+ setFilterGroup,
+ filterQuotaType,
+ setFilterQuotaType,
activeKey,
setActiveKey,
pageSize,
diff --git a/web/src/pages/Pricing/index.js b/web/src/pages/Pricing/index.js
index 036e94ad..c1066203 100644
--- a/web/src/pages/Pricing/index.js
+++ b/web/src/pages/Pricing/index.js
@@ -21,9 +21,9 @@ import React from 'react';
import ModelPricingPage from '../../components/table/model-pricing';
const Pricing = () => (
-
+ <>
-
+ >
);
export default Pricing;