feat: enhance tiered billing functionality and UI components

- Introduced new fields for billing mode and expression in the Pricing model.
- Implemented dynamic pricing breakdown component to display tiered billing details.
- Updated various components to support and render tiered billing information.
- Enhanced pricing calculation logic to accommodate dynamic pricing scenarios.
- Added tests for new billing expression functionalities and UI components.
This commit is contained in:
CaIon
2026-03-16 18:57:14 +08:00
parent 91ed4e196a
commit f0589cc478
23 changed files with 1237 additions and 695 deletions
+100 -1
View File
@@ -645,7 +645,17 @@ export const calculateModelPrice = ({
}
}
// 2. 根据计费类型计算价格
// 2. 动态计费(tiered_expr
if (record.billing_mode === 'tiered_expr' && record.billing_expr) {
return {
isDynamicPricing: true,
billingExpr: record.billing_expr,
usedGroup,
usedGroupRatio,
};
}
// 3. 根据计费类型计算价格
if (record.quota_type === 0) {
// 按量计费
const isTokensDisplay = quotaDisplayType === 'TOKENS';
@@ -766,6 +776,18 @@ export const getModelPriceItems = (
t,
quotaDisplayType = 'USD',
) => {
if (priceData.isDynamicPricing) {
return [
{
key: 'dynamic',
label: t('动态计费'),
value: '',
suffix: '',
isDynamic: true,
},
];
}
if (priceData.isPerToken) {
if (quotaDisplayType === 'TOKENS' || priceData.isTokensDisplay) {
return [
@@ -874,6 +896,83 @@ export const getModelPriceItems = (
].filter((item) => item.value !== null && item.value !== undefined && item.value !== '');
};
// 格式化动态计费摘要(用于卡片视图,与 formatPriceInfo 风格统一)
export const formatDynamicPriceSummary = (billingExpr, t) => {
if (!billingExpr) return <span style={{ color: 'var(--semi-color-text-1)' }}>{t('动态计费')}</span>;
const tierMatches = billingExpr.match(/tier\(/g) || [];
const tierCount = tierMatches.length;
const firstTierMatch = billingExpr.match(
/tier\("[^"]*",\s*p\s*\*\s*([\d.eE+-]+)\s*\+\s*c\s*\*\s*([\d.eE+-]+)(?:\s*\+\s*cr\s*\*\s*([\d.eE+-]+))?(?:\s*\+\s*cc\s*\*\s*([\d.eE+-]+))?/,
);
const hasTimeCondition = /\b(?:hour|weekday|month|day)\(/.test(billingExpr);
const hasRequestCondition = /\b(?:param|header)\(/.test(billingExpr);
const tags = [];
if (tierCount > 1) tags.push(`${tierCount}${t('档')}`);
if (hasTimeCondition) tags.push(t('含时间条件'));
if (hasRequestCondition) tags.push(t('含请求条件'));
const unitSuffix = ' / 1M Tokens';
const lineStyle = { color: 'var(--semi-color-text-1)' };
return (
<>
{firstTierMatch && (
<>
<span style={lineStyle}>
{t('输入价格')} ${(Number(firstTierMatch[1]) * 2).toFixed(4)}{unitSuffix}
</span>
<span style={lineStyle}>
{t('输出价格')} ${(Number(firstTierMatch[2]) * 2).toFixed(4)}{unitSuffix}
</span>
{firstTierMatch[3] && (
<span style={lineStyle}>
{t('缓存读取价格')} ${(Number(firstTierMatch[3]) * 2).toFixed(4)}{unitSuffix}
</span>
)}
{firstTierMatch[4] && (
<span style={lineStyle}>
{t('缓存创建价格')} ${(Number(firstTierMatch[4]) * 2).toFixed(4)}{unitSuffix}
</span>
)}
</>
)}
<span style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
<span
style={{
display: 'inline-block',
padding: '1px 6px',
borderRadius: 4,
fontSize: 11,
background: 'var(--semi-color-warning-light-default)',
color: 'var(--semi-color-warning)',
}}
>
{t('动态计费')}
</span>
{tags.map((tag) => (
<span
key={tag}
style={{
display: 'inline-block',
padding: '1px 6px',
borderRadius: 4,
fontSize: 11,
background: 'var(--semi-color-fill-1)',
color: 'var(--semi-color-text-2)',
}}
>
{tag}
</span>
))}
</span>
</>
);
};
// 格式化价格信息(用于卡片视图)
export const formatPriceInfo = (priceData, t, quotaDisplayType = 'USD') => {
const items = getModelPriceItems(priceData, t, quotaDisplayType);