♻️ refactor(model-pricing): improve table UI and optimize code structure (#1365)
- Replace model count with group ratio display (x2.2, x1) in group filter - Remove redundant "Available Groups" column from pricing table - Remove "Availability" column and related logic completely - Move "Supported Endpoint Types" column to fixed right position - Clean up unused parameters and variables in PricingTableColumns.js - Optimize variable declarations (let → const) and simplify render logic - Improve code readability and reduce memory allocations This refactor enhances user experience by: - Providing clearer group ratio information in filters - Simplifying table layout while maintaining essential functionality - Improving performance through better code organization Breaking changes: None
This commit is contained in:
@@ -84,7 +84,7 @@ const PricingSidebar = ({
|
|||||||
|
|
||||||
<PricingCategories {...categoryProps} setActiveKey={setActiveKey} loading={loading} t={t} />
|
<PricingCategories {...categoryProps} setActiveKey={setActiveKey} loading={loading} t={t} />
|
||||||
|
|
||||||
<PricingGroups filterGroup={filterGroup} setFilterGroup={setFilterGroup} usableGroup={categoryProps.usableGroup} models={categoryProps.models} loading={loading} t={t} />
|
<PricingGroups filterGroup={filterGroup} setFilterGroup={setFilterGroup} usableGroup={categoryProps.usableGroup} groupRatio={categoryProps.groupRatio} models={categoryProps.models} loading={loading} t={t} />
|
||||||
|
|
||||||
<PricingQuotaTypes filterQuotaType={filterQuotaType} setFilterQuotaType={setFilterQuotaType} models={categoryProps.models} loading={loading} t={t} />
|
<PricingQuotaTypes filterQuotaType={filterQuotaType} setFilterQuotaType={setFilterQuotaType} models={categoryProps.models} loading={loading} t={t} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ For commercial licensing, please contact support@quantumnous.com
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Tag, Space, Tooltip, Switch } from '@douyinfe/semi-ui';
|
import { Tag, Space, Tooltip, Switch } from '@douyinfe/semi-ui';
|
||||||
import { IconHelpCircle, IconCheckCircleStroked, IconClose } from '@douyinfe/semi-icons';
|
import { IconHelpCircle } from '@douyinfe/semi-icons';
|
||||||
import { Popover } from '@douyinfe/semi-ui';
|
|
||||||
import { renderModelTag, stringToColor } from '../../../helpers';
|
import { renderModelTag, stringToColor } from '../../../helpers';
|
||||||
|
|
||||||
function renderQuotaType(type, t) {
|
function renderQuotaType(type, t) {
|
||||||
@@ -42,33 +41,6 @@ function renderQuotaType(type, t) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderAvailable(available, t) {
|
|
||||||
if (available) {
|
|
||||||
return (
|
|
||||||
<Popover
|
|
||||||
content={<div style={{ padding: 8 }}>{t('您的分组可以使用该模型')}</div>}
|
|
||||||
position='top'
|
|
||||||
key={String(available)}
|
|
||||||
className="bg-green-50"
|
|
||||||
>
|
|
||||||
<IconCheckCircleStroked style={{ color: 'rgb(22 163 74)' }} size='large' />
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分组不可用时显示红色关闭图标
|
|
||||||
return (
|
|
||||||
<Popover
|
|
||||||
content={<div style={{ padding: 8 }}>{t('你的分组无权使用该模型')}</div>}
|
|
||||||
position='top'
|
|
||||||
key="not-available"
|
|
||||||
className="bg-red-50"
|
|
||||||
>
|
|
||||||
<IconClose style={{ color: 'rgb(239 68 68)' }} size='large' />
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderSupportedEndpoints(endpoints) {
|
function renderSupportedEndpoints(endpoints) {
|
||||||
if (!endpoints || endpoints.length === 0) {
|
if (!endpoints || endpoints.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
@@ -91,22 +63,20 @@ function renderSupportedEndpoints(endpoints) {
|
|||||||
export const getPricingTableColumns = ({
|
export const getPricingTableColumns = ({
|
||||||
t,
|
t,
|
||||||
selectedGroup,
|
selectedGroup,
|
||||||
usableGroup,
|
|
||||||
groupRatio,
|
groupRatio,
|
||||||
copyText,
|
copyText,
|
||||||
setModalImageUrl,
|
setModalImageUrl,
|
||||||
setIsModalOpenurl,
|
setIsModalOpenurl,
|
||||||
currency,
|
currency,
|
||||||
showWithRecharge,
|
|
||||||
tokenUnit,
|
tokenUnit,
|
||||||
setTokenUnit,
|
setTokenUnit,
|
||||||
displayPrice,
|
displayPrice,
|
||||||
handleGroupClick,
|
|
||||||
showRatio,
|
showRatio,
|
||||||
}) => {
|
}) => {
|
||||||
const endpointColumn = {
|
const endpointColumn = {
|
||||||
title: t('可用端点类型'),
|
title: t('可用端点类型'),
|
||||||
dataIndex: 'supported_endpoint_types',
|
dataIndex: 'supported_endpoint_types',
|
||||||
|
fixed: 'right',
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return renderSupportedEndpoints(text);
|
return renderSupportedEndpoints(text);
|
||||||
},
|
},
|
||||||
@@ -135,56 +105,7 @@ export const getPricingTableColumns = ({
|
|||||||
sorter: (a, b) => a.quota_type - b.quota_type,
|
sorter: (a, b) => a.quota_type - b.quota_type,
|
||||||
};
|
};
|
||||||
|
|
||||||
const enableGroupColumn = {
|
const baseColumns = [modelNameColumn, quotaColumn];
|
||||||
title: t('可用分组'),
|
|
||||||
dataIndex: 'enable_groups',
|
|
||||||
render: (text, record, index) => {
|
|
||||||
return (
|
|
||||||
<Space wrap>
|
|
||||||
{text.map((group) => {
|
|
||||||
if (usableGroup[group]) {
|
|
||||||
if (group === selectedGroup) {
|
|
||||||
return (
|
|
||||||
<Tag key={group} color='blue' shape='circle' prefixIcon={<IconCheckCircleStroked />}>
|
|
||||||
{group}
|
|
||||||
</Tag>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<Tag
|
|
||||||
key={group}
|
|
||||||
color='blue'
|
|
||||||
shape='circle'
|
|
||||||
onClick={() => handleGroupClick(group)}
|
|
||||||
className="cursor-pointer hover:opacity-80 transition-opacity"
|
|
||||||
>
|
|
||||||
{group}
|
|
||||||
</Tag>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</Space>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const baseColumns = [endpointColumn, modelNameColumn, quotaColumn, enableGroupColumn];
|
|
||||||
|
|
||||||
const availabilityColumn = {
|
|
||||||
title: t('可用性'),
|
|
||||||
dataIndex: 'available',
|
|
||||||
fixed: 'right',
|
|
||||||
render: (text, record, index) => {
|
|
||||||
return renderAvailable(record.enable_groups.includes(selectedGroup), t);
|
|
||||||
},
|
|
||||||
sorter: (a, b) => {
|
|
||||||
const aAvailable = a.enable_groups.includes(selectedGroup);
|
|
||||||
const bAvailable = b.enable_groups.includes(selectedGroup);
|
|
||||||
return Number(aAvailable) - Number(bAvailable);
|
|
||||||
},
|
|
||||||
defaultSortOrder: 'descend',
|
|
||||||
};
|
|
||||||
|
|
||||||
const ratioColumn = {
|
const ratioColumn = {
|
||||||
title: () => (
|
title: () => (
|
||||||
@@ -203,9 +124,8 @@ export const getPricingTableColumns = ({
|
|||||||
),
|
),
|
||||||
dataIndex: 'model_ratio',
|
dataIndex: 'model_ratio',
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
let content = text;
|
const completionRatio = parseFloat(record.completion_ratio.toFixed(3));
|
||||||
let completionRatio = parseFloat(record.completion_ratio.toFixed(3));
|
const content = (
|
||||||
content = (
|
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="text-gray-700">
|
<div className="text-gray-700">
|
||||||
{t('模型倍率')}:{record.quota_type === 0 ? text : t('无')}
|
{t('模型倍率')}:{record.quota_type === 0 ? text : t('无')}
|
||||||
@@ -238,25 +158,23 @@ export const getPricingTableColumns = ({
|
|||||||
),
|
),
|
||||||
dataIndex: 'model_price',
|
dataIndex: 'model_price',
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
let content = text;
|
|
||||||
if (record.quota_type === 0) {
|
if (record.quota_type === 0) {
|
||||||
let inputRatioPriceUSD = record.model_ratio * 2 * groupRatio[selectedGroup];
|
const inputRatioPriceUSD = record.model_ratio * 2 * groupRatio[selectedGroup];
|
||||||
let completionRatioPriceUSD =
|
const completionRatioPriceUSD =
|
||||||
record.model_ratio * record.completion_ratio * 2 * groupRatio[selectedGroup];
|
record.model_ratio * record.completion_ratio * 2 * groupRatio[selectedGroup];
|
||||||
|
|
||||||
const unitDivisor = tokenUnit === 'K' ? 1000 : 1;
|
const unitDivisor = tokenUnit === 'K' ? 1000 : 1;
|
||||||
const unitLabel = tokenUnit === 'K' ? 'K' : 'M';
|
const unitLabel = tokenUnit === 'K' ? 'K' : 'M';
|
||||||
|
|
||||||
let displayInput = displayPrice(inputRatioPriceUSD);
|
const rawDisplayInput = displayPrice(inputRatioPriceUSD);
|
||||||
let displayCompletion = displayPrice(completionRatioPriceUSD);
|
const rawDisplayCompletion = displayPrice(completionRatioPriceUSD);
|
||||||
|
|
||||||
const divisor = unitDivisor;
|
const numInput = parseFloat(rawDisplayInput.replace(/[^0-9.]/g, '')) / unitDivisor;
|
||||||
const numInput = parseFloat(displayInput.replace(/[^0-9.]/g, '')) / divisor;
|
const numCompletion = parseFloat(rawDisplayCompletion.replace(/[^0-9.]/g, '')) / unitDivisor;
|
||||||
const numCompletion = parseFloat(displayCompletion.replace(/[^0-9.]/g, '')) / divisor;
|
|
||||||
|
|
||||||
displayInput = `${currency === 'CNY' ? '¥' : '$'}${numInput.toFixed(3)}`;
|
const displayInput = `${currency === 'CNY' ? '¥' : '$'}${numInput.toFixed(3)}`;
|
||||||
displayCompletion = `${currency === 'CNY' ? '¥' : '$'}${numCompletion.toFixed(3)}`;
|
const displayCompletion = `${currency === 'CNY' ? '¥' : '$'}${numCompletion.toFixed(3)}`;
|
||||||
content = (
|
return (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="text-gray-700">
|
<div className="text-gray-700">
|
||||||
{t('提示')} {displayInput} / 1{unitLabel} tokens
|
{t('提示')} {displayInput} / 1{unitLabel} tokens
|
||||||
@@ -267,15 +185,14 @@ export const getPricingTableColumns = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let priceUSD = parseFloat(text) * groupRatio[selectedGroup];
|
const priceUSD = parseFloat(text) * groupRatio[selectedGroup];
|
||||||
let displayVal = displayPrice(priceUSD);
|
const displayVal = displayPrice(priceUSD);
|
||||||
content = (
|
return (
|
||||||
<div className="text-gray-700">
|
<div className="text-gray-700">
|
||||||
{t('模型价格')}:{displayVal}
|
{t('模型价格')}:{displayVal}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return content;
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -284,6 +201,6 @@ export const getPricingTableColumns = ({
|
|||||||
columns.push(ratioColumn);
|
columns.push(ratioColumn);
|
||||||
}
|
}
|
||||||
columns.push(priceColumn);
|
columns.push(priceColumn);
|
||||||
columns.push(availabilityColumn);
|
columns.push(endpointColumn);
|
||||||
return columns;
|
return columns;
|
||||||
};
|
};
|
||||||
@@ -25,24 +25,30 @@ import SelectableButtonGroup from '../../../common/ui/SelectableButtonGroup';
|
|||||||
* @param {string} filterGroup 当前选中的分组,'all' 表示不过滤
|
* @param {string} filterGroup 当前选中的分组,'all' 表示不过滤
|
||||||
* @param {Function} setFilterGroup 设置选中分组
|
* @param {Function} setFilterGroup 设置选中分组
|
||||||
* @param {Record<string, any>} usableGroup 后端返回的可用分组对象
|
* @param {Record<string, any>} usableGroup 后端返回的可用分组对象
|
||||||
|
* @param {Record<string, number>} groupRatio 分组倍率对象
|
||||||
* @param {Array} models 模型列表
|
* @param {Array} models 模型列表
|
||||||
* @param {boolean} loading 是否加载中
|
* @param {boolean} loading 是否加载中
|
||||||
* @param {Function} t i18n
|
* @param {Function} t i18n
|
||||||
*/
|
*/
|
||||||
const PricingGroups = ({ filterGroup, setFilterGroup, usableGroup = {}, models = [], loading = false, t }) => {
|
const PricingGroups = ({ filterGroup, setFilterGroup, usableGroup = {}, groupRatio = {}, models = [], loading = false, t }) => {
|
||||||
const groups = ['all', ...Object.keys(usableGroup).filter(key => key !== '')];
|
const groups = ['all', ...Object.keys(usableGroup).filter(key => key !== '')];
|
||||||
|
|
||||||
const items = groups.map((g) => {
|
const items = groups.map((g) => {
|
||||||
let count = 0;
|
let ratioDisplay = '';
|
||||||
if (g === 'all') {
|
if (g === 'all') {
|
||||||
count = models.length;
|
ratioDisplay = t('全部');
|
||||||
} else {
|
} else {
|
||||||
count = models.filter(m => m.enable_groups && m.enable_groups.includes(g)).length;
|
const ratio = groupRatio[g];
|
||||||
|
if (ratio !== undefined && ratio !== null) {
|
||||||
|
ratioDisplay = `x${ratio}`;
|
||||||
|
} else {
|
||||||
|
ratioDisplay = 'x1';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
value: g,
|
value: g,
|
||||||
label: g === 'all' ? t('全部分组') : g,
|
label: g === 'all' ? t('全部分组') : g,
|
||||||
tagCount: count,
|
tagCount: ratioDisplay,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ const PricingFilterModal = ({
|
|||||||
filterGroup={filterGroup}
|
filterGroup={filterGroup}
|
||||||
setFilterGroup={setFilterGroup}
|
setFilterGroup={setFilterGroup}
|
||||||
usableGroup={categoryProps.usableGroup}
|
usableGroup={categoryProps.usableGroup}
|
||||||
|
groupRatio={categoryProps.groupRatio}
|
||||||
models={categoryProps.models}
|
models={categoryProps.models}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
t={t}
|
t={t}
|
||||||
|
|||||||
Reference in New Issue
Block a user