feat: enhance tiered billing logic and improve variable handling in pricing calculations

This commit is contained in:
CaIon
2026-03-16 22:00:36 +08:00
parent f6c0852da9
commit 5b03b39db2
10 changed files with 431 additions and 240 deletions

View File

@@ -20,6 +20,7 @@ For commercial licensing, please contact support@quantumnous.com
import React from 'react';
import { Card, Avatar, Tag, Table, Typography } from '@douyinfe/semi-ui';
import { IconPriceTag } from '@douyinfe/semi-icons';
import { parseTiersFromExpr } from '../../../../../helpers';
import {
splitBillingExprAndRequestRules,
tryParseRequestRuleExpr,
@@ -36,15 +37,6 @@ const { Text } = Typography;
const PRICE_SUFFIX = '$/1M tokens';
function unitCostToPrice(uc) {
return Number(uc) || 0;
}
function formatPrice(uc) {
const p = unitCostToPrice(uc);
return p ? `$${p.toFixed(4)}` : '-';
}
const VAR_LABELS = { p: '输入', c: '输出' };
const OP_LABELS = { '<': '<', '<=': '≤', '>': '>', '>=': '≥' };
const TIME_FUNC_LABELS = { hour: '小时', minute: '分钟', weekday: '星期', month: '月份', day: '日期' };
@@ -71,54 +63,6 @@ function formatConditionSummary(conditions, t) {
.join(' && ');
}
function tryParseTiers(baseExpr) {
if (!baseExpr) return null;
try {
const cacheVars = ['cr', 'cc', 'cc1h'];
const optCache = cacheVars.map((v) => `(?:\\s*\\+\\s*${v}\\s*\\*\\s*([\\d.eE+-]+))?`).join('');
const bodyPat = `p\\s*\\*\\s*([\\d.eE+-]+)\\s*\\+\\s*c\\s*\\*\\s*([\\d.eE+-]+)${optCache}`;
const singleRe = new RegExp(`^tier\\("([^"]*)",\\s*${bodyPat}\\)$`);
const simple = baseExpr.match(singleRe);
if (simple) {
return [{
label: simple[1],
conditions: [],
inputPrice: unitCostToPrice(Number(simple[2])),
outputPrice: unitCostToPrice(Number(simple[3])),
cacheReadPrice: simple[4] ? unitCostToPrice(Number(simple[4])) : null,
cacheCreatePrice: simple[5] ? unitCostToPrice(Number(simple[5])) : null,
cacheCreate1hPrice: simple[6] ? unitCostToPrice(Number(simple[6])) : null,
}];
}
const condGroup = `((?:(?:p|c)\\s*(?:<|<=|>|>=)\\s*[\\d.eE+]+)(?:\\s*&&\\s*(?:p|c)\\s*(?:<|<=|>|>=)\\s*[\\d.eE+]+)*)`;
const tierRe = new RegExp(`(?:${condGroup}\\s*\\?\\s*)?tier\\("([^"]*)",\\s*${bodyPat}\\)`, 'g');
const tiers = [];
let match;
while ((match = tierRe.exec(baseExpr)) !== null) {
const condStr = match[1] || '';
const conditions = [];
if (condStr) {
for (const cp of condStr.split(/\s*&&\s*/)) {
const cm = cp.trim().match(/^(p|c)\s*(<|<=|>|>=)\s*([\d.eE+]+)$/);
if (cm) conditions.push({ var: cm[1], op: cm[2], value: Number(cm[3]) });
}
}
tiers.push({
label: match[2],
conditions,
inputPrice: unitCostToPrice(Number(match[3])),
outputPrice: unitCostToPrice(Number(match[4])),
cacheReadPrice: match[5] ? unitCostToPrice(Number(match[5])) : null,
cacheCreatePrice: match[6] ? unitCostToPrice(Number(match[6])) : null,
cacheCreate1hPrice: match[7] ? unitCostToPrice(Number(match[7])) : null,
});
}
return tiers.length > 0 ? tiers : null;
} catch {
return null;
}
}
function describeCondition(cond, t) {
if (cond.source === SOURCE_TIME) {
@@ -147,7 +91,7 @@ export default function DynamicPricingBreakdown({ billingExpr, t }) {
const { billingExpr: baseExpr, requestRuleExpr: ruleExpr } =
splitBillingExprAndRequestRules(billingExpr || '');
const tiers = tryParseTiers(baseExpr);
const tiers = parseTiersFromExpr(baseExpr);
const ruleGroups = tryParseRequestRuleExpr(ruleExpr || '');
const hasTiers = tiers && tiers.length > 0;
@@ -169,6 +113,17 @@ export default function DynamicPricingBreakdown({ billingExpr, t }) {
);
}
const priceFields = [
['inputPrice', '输入价格'],
['outputPrice', '补全价格'],
['cacheReadPrice', '缓存读取'],
['cacheCreatePrice', '缓存创建'],
['cacheCreate1hPrice', '缓存创建-1h'],
['imagePrice', '图片输入'],
['audioInputPrice', '音频输入'],
['audioOutputPrice', '音频输出'],
];
const tierColumns = [
{
title: t('档位'),
@@ -182,54 +137,21 @@ export default function DynamicPricingBreakdown({ billingExpr, t }) {
</div>
),
},
{
title: `${t('输入价格')} (${PRICE_SUFFIX})`,
dataIndex: 'inputPrice',
render: (v) => <Text strong>${v.toFixed(4)}</Text>,
},
{
title: `${t('输出价格')} (${PRICE_SUFFIX})`,
dataIndex: 'outputPrice',
render: (v) => <Text strong>${v.toFixed(4)}</Text>,
},
...priceFields
.filter(([field]) => hasTiers && tiers.some((tier) => tier[field] > 0))
.map(([field, label]) => ({
title: `${t(label)} (${PRICE_SUFFIX})`,
dataIndex: field,
render: (v) => v > 0 ? <Text strong>${v.toFixed(4)}</Text> : '-',
})),
];
const hasCacheRead = hasTiers && tiers.some((tier) => tier.cacheReadPrice != null);
const hasCacheCreate = hasTiers && tiers.some((tier) => tier.cacheCreatePrice != null);
const hasCache1h = hasTiers && tiers.some((tier) => tier.cacheCreate1hPrice != null);
if (hasCacheRead) {
tierColumns.push({
title: `${t('缓存读取')} (${PRICE_SUFFIX})`,
dataIndex: 'cacheReadPrice',
render: (v) => v != null ? <Text>${v.toFixed(4)}</Text> : '-',
});
}
if (hasCacheCreate) {
tierColumns.push({
title: `${t('缓存创建')} (${PRICE_SUFFIX})`,
dataIndex: 'cacheCreatePrice',
render: (v) => v != null ? <Text>${v.toFixed(4)}</Text> : '-',
});
}
if (hasCache1h) {
tierColumns.push({
title: `${t('缓存创建-1h')} (${PRICE_SUFFIX})`,
dataIndex: 'cacheCreate1hPrice',
render: (v) => v != null ? <Text>${v.toFixed(4)}</Text> : '-',
});
}
const tierData = hasTiers
? tiers.map((tier, i) => ({
key: `tier-${i}`,
label: tier.label,
condSummary: formatConditionSummary(tier.conditions, t),
inputPrice: tier.inputPrice,
outputPrice: tier.outputPrice,
cacheReadPrice: tier.cacheReadPrice,
cacheCreatePrice: tier.cacheCreatePrice,
cacheCreate1hPrice: tier.cacheCreate1hPrice,
...Object.fromEntries(priceFields.map(([field]) => [field, tier[field] || 0])),
}))
: [];