feat: enhance tiered billing logic and improve variable handling in pricing calculations
This commit is contained in:
@@ -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])),
|
||||
}))
|
||||
: [];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user