Summary • Ensure “Group Ratio” shows correctly when “All” groups are selected. • Eliminate redundant price calculations in both card and table views. Details 1. PricingCardView.jsx • Removed obsolete renderPriceInfo function. • Calculate priceData once per model and reuse for header price string and footer ratio block. • Display priceData.usedGroupRatio as the group ratio fallback. 2. PricingTableColumns.js • Introduced WeakMap-based cache (getPriceData) to compute priceData only once per row. • Updated ratioColumn & priceColumn to reuse cached priceData. • Now displays priceData.usedGroupRatio, preventing empty cells for “All” group. Benefits • Correct visual output for group ratio across all views. • Reduced duplicate calculations, improving render performance. • Removed dead code, keeping components clean and maintainable.
242 lines
6.3 KiB
JavaScript
242 lines
6.3 KiB
JavaScript
/*
|
||
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 { Tag, Space, Tooltip } from '@douyinfe/semi-ui';
|
||
import { IconHelpCircle } from '@douyinfe/semi-icons';
|
||
import { renderModelTag, stringToColor, calculateModelPrice, getLobeHubIcon } from '../../../../../helpers';
|
||
import { renderLimitedItems, renderDescription } from '../../../../common/ui/RenderUtils';
|
||
|
||
function renderQuotaType(type, t) {
|
||
switch (type) {
|
||
case 1:
|
||
return (
|
||
<Tag color='teal' shape='circle'>
|
||
{t('按次计费')}
|
||
</Tag>
|
||
);
|
||
case 0:
|
||
return (
|
||
<Tag color='violet' shape='circle'>
|
||
{t('按量计费')}
|
||
</Tag>
|
||
);
|
||
default:
|
||
return t('未知');
|
||
}
|
||
}
|
||
|
||
// Render vendor name
|
||
const renderVendor = (vendorName, vendorIcon, t) => {
|
||
if (!vendorName) return '-';
|
||
return (
|
||
<Tag color='white' shape='circle' prefixIcon={getLobeHubIcon(vendorIcon || 'Layers', 14)}>
|
||
{vendorName}
|
||
</Tag>
|
||
);
|
||
};
|
||
|
||
// Render tags list using RenderUtils
|
||
const renderTags = (text) => {
|
||
if (!text) return '-';
|
||
const tagsArr = text.split(',').filter(tag => tag.trim());
|
||
return renderLimitedItems({
|
||
items: tagsArr,
|
||
renderItem: (tag, idx) => (
|
||
<Tag key={idx} color={stringToColor(tag.trim())} shape='circle' size='small'>
|
||
{tag.trim()}
|
||
</Tag>
|
||
),
|
||
maxDisplay: 3
|
||
});
|
||
};
|
||
|
||
function renderSupportedEndpoints(endpoints) {
|
||
if (!endpoints || endpoints.length === 0) {
|
||
return null;
|
||
}
|
||
return (
|
||
<Space wrap>
|
||
{endpoints.map((endpoint, idx) => (
|
||
<Tag
|
||
key={endpoint}
|
||
color={stringToColor(endpoint)}
|
||
shape='circle'
|
||
>
|
||
{endpoint}
|
||
</Tag>
|
||
))}
|
||
</Space>
|
||
);
|
||
}
|
||
|
||
export const getPricingTableColumns = ({
|
||
t,
|
||
selectedGroup,
|
||
groupRatio,
|
||
copyText,
|
||
setModalImageUrl,
|
||
setIsModalOpenurl,
|
||
currency,
|
||
tokenUnit,
|
||
displayPrice,
|
||
showRatio,
|
||
}) => {
|
||
|
||
const priceDataCache = new WeakMap();
|
||
|
||
const getPriceData = (record) => {
|
||
let cache = priceDataCache.get(record);
|
||
if (!cache) {
|
||
cache = calculateModelPrice({
|
||
record,
|
||
selectedGroup,
|
||
groupRatio,
|
||
tokenUnit,
|
||
displayPrice,
|
||
currency,
|
||
});
|
||
priceDataCache.set(record, cache);
|
||
}
|
||
return cache;
|
||
};
|
||
|
||
const endpointColumn = {
|
||
title: t('可用端点类型'),
|
||
dataIndex: 'supported_endpoint_types',
|
||
render: (text, record, index) => {
|
||
return renderSupportedEndpoints(text);
|
||
},
|
||
};
|
||
|
||
const modelNameColumn = {
|
||
title: t('模型名称'),
|
||
dataIndex: 'model_name',
|
||
render: (text, record, index) => {
|
||
return renderModelTag(text, {
|
||
onClick: () => {
|
||
copyText(text);
|
||
}
|
||
});
|
||
},
|
||
onFilter: (value, record) =>
|
||
record.model_name.toLowerCase().includes(value.toLowerCase()),
|
||
};
|
||
|
||
const quotaColumn = {
|
||
title: t('计费类型'),
|
||
dataIndex: 'quota_type',
|
||
render: (text, record, index) => {
|
||
return renderQuotaType(parseInt(text), t);
|
||
},
|
||
sorter: (a, b) => a.quota_type - b.quota_type,
|
||
};
|
||
|
||
const descriptionColumn = {
|
||
title: t('描述'),
|
||
dataIndex: 'description',
|
||
render: (text) => renderDescription(text, 200),
|
||
};
|
||
|
||
const tagsColumn = {
|
||
title: t('标签'),
|
||
dataIndex: 'tags',
|
||
render: renderTags,
|
||
};
|
||
|
||
const vendorColumn = {
|
||
title: t('供应商'),
|
||
dataIndex: 'vendor_name',
|
||
render: (text, record) => renderVendor(text, record.vendor_icon, t),
|
||
};
|
||
|
||
const baseColumns = [modelNameColumn, vendorColumn, descriptionColumn, tagsColumn, quotaColumn];
|
||
|
||
const ratioColumn = {
|
||
title: () => (
|
||
<div className="flex items-center space-x-1">
|
||
<span>{t('倍率')}</span>
|
||
<Tooltip content={t('倍率是为了方便换算不同价格的模型')}>
|
||
<IconHelpCircle
|
||
className="text-blue-500 cursor-pointer"
|
||
onClick={() => {
|
||
setModalImageUrl('/ratio.png');
|
||
setIsModalOpenurl(true);
|
||
}}
|
||
/>
|
||
</Tooltip>
|
||
</div>
|
||
),
|
||
dataIndex: 'model_ratio',
|
||
render: (text, record, index) => {
|
||
const completionRatio = parseFloat(record.completion_ratio.toFixed(3));
|
||
const priceData = getPriceData(record);
|
||
|
||
return (
|
||
<div className="space-y-1">
|
||
<div className="text-gray-700">
|
||
{t('模型倍率')}:{record.quota_type === 0 ? text : t('无')}
|
||
</div>
|
||
<div className="text-gray-700">
|
||
{t('补全倍率')}:{record.quota_type === 0 ? completionRatio : t('无')}
|
||
</div>
|
||
<div className="text-gray-700">
|
||
{t('分组倍率')}:{priceData?.usedGroupRatio ?? '-'}
|
||
</div>
|
||
</div>
|
||
);
|
||
},
|
||
};
|
||
|
||
const priceColumn = {
|
||
title: t('模型价格'),
|
||
dataIndex: 'model_price',
|
||
fixed: 'right',
|
||
render: (text, record, index) => {
|
||
const priceData = getPriceData(record);
|
||
|
||
if (priceData.isPerToken) {
|
||
return (
|
||
<div className="space-y-1">
|
||
<div className="text-gray-700">
|
||
{t('提示')} {priceData.inputPrice} / 1{priceData.unitLabel} tokens
|
||
</div>
|
||
<div className="text-gray-700">
|
||
{t('补全')} {priceData.completionPrice} / 1{priceData.unitLabel} tokens
|
||
</div>
|
||
</div>
|
||
);
|
||
} else {
|
||
return (
|
||
<div className="text-gray-700">
|
||
{t('模型价格')}:{priceData.price}
|
||
</div>
|
||
);
|
||
}
|
||
},
|
||
};
|
||
|
||
const columns = [...baseColumns];
|
||
columns.push(endpointColumn);
|
||
if (showRatio) {
|
||
columns.push(ratioColumn);
|
||
}
|
||
columns.push(priceColumn);
|
||
return columns;
|
||
};
|