feat: add billing expression system documentation and enhance tiered billing logic
- Introduced a new rule for the Billing Expression System, emphasizing the importance of reading `pkg/billingexpr/expr.md` for dynamic billing. - Updated the billing expression logic to support new variables and improved handling of image and audio tokens. - Enhanced the tiered billing functionality with versioning support for expressions and refined quota calculations. - Added tests to validate the new billing expression features and ensure correctness in pricing calculations.
This commit is contained in:
Vendored
+17
-29
@@ -21,6 +21,11 @@ import i18next from 'i18next';
|
||||
import { Modal, Tag, Typography, Avatar } from '@douyinfe/semi-ui';
|
||||
import { copy, showSuccess } from './utils';
|
||||
import { MOBILE_BREAKPOINT } from '../hooks/common/useIsMobile';
|
||||
import {
|
||||
BILLING_VARS,
|
||||
BILLING_VAR_KEY_TO_FIELD,
|
||||
BILLING_VAR_REGEX,
|
||||
} from '../constants';
|
||||
import { visit } from 'unist-util-visit';
|
||||
import * as LobeIcons from '@lobehub/icons';
|
||||
import {
|
||||
@@ -2210,22 +2215,22 @@ export function renderLogContent(opts) {
|
||||
}
|
||||
}
|
||||
|
||||
const TIER_VAR_KEYS = ['p', 'c', 'cr', 'cc', 'cc1h', 'img', 'ai', 'ao'];
|
||||
const TIER_VAR_TO_FIELD = {
|
||||
p: 'inputPrice', c: 'outputPrice',
|
||||
cr: 'cacheReadPrice', cc: 'cacheCreatePrice', cc1h: 'cacheCreate1hPrice',
|
||||
img: 'imagePrice', ai: 'audioInputPrice', ao: 'audioOutputPrice',
|
||||
};
|
||||
export function stripExprVersion(exprStr) {
|
||||
if (!exprStr) return { version: 1, body: '' };
|
||||
const m = exprStr.match(/^v(\d+):([\s\S]*)$/);
|
||||
if (m) return { version: Number(m[1]), body: m[2] };
|
||||
return { version: 1, body: exprStr };
|
||||
}
|
||||
|
||||
function parseTierBody(bodyStr) {
|
||||
const coeffs = {};
|
||||
const re = new RegExp(`\\b(${TIER_VAR_KEYS.join('|')})\\s*\\*\\s*([\\d.eE+-]+)`, 'g');
|
||||
const re = new RegExp(BILLING_VAR_REGEX.source, 'g');
|
||||
let m;
|
||||
while ((m = re.exec(bodyStr)) !== null) {
|
||||
if (!(m[1] in coeffs)) coeffs[m[1]] = Number(m[2]);
|
||||
}
|
||||
const tier = {};
|
||||
for (const [varName, field] of Object.entries(TIER_VAR_TO_FIELD)) {
|
||||
for (const [varName, field] of Object.entries(BILLING_VAR_KEY_TO_FIELD)) {
|
||||
tier[field] = coeffs[varName] || 0;
|
||||
}
|
||||
return tier;
|
||||
@@ -2234,11 +2239,12 @@ function parseTierBody(bodyStr) {
|
||||
export function parseTiersFromExpr(exprStr) {
|
||||
if (!exprStr) return [];
|
||||
try {
|
||||
const { body } = stripExprVersion(exprStr);
|
||||
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*([^)]+)\\)`, 'g');
|
||||
const tiers = [];
|
||||
let m;
|
||||
while ((m = tierRe.exec(exprStr)) !== null) {
|
||||
while ((m = tierRe.exec(body)) !== null) {
|
||||
const condStr = m[1] || '';
|
||||
const conditions = [];
|
||||
if (condStr) {
|
||||
@@ -2281,16 +2287,7 @@ export function renderTieredModelPrice(opts) {
|
||||
const { symbol, rate } = getCurrencyConfig();
|
||||
const gr = groupRatio || 1;
|
||||
|
||||
const priceLines = [
|
||||
['inputPrice', '输入价格'],
|
||||
['outputPrice', '补全价格'],
|
||||
['cacheReadPrice', '缓存读取价格'],
|
||||
['cacheCreatePrice', '缓存创建价格'],
|
||||
['cacheCreate1hPrice', '1h缓存创建价格'],
|
||||
['imagePrice', '图片输入价格'],
|
||||
['audioInputPrice', '音频输入价格'],
|
||||
['audioOutputPrice', '音频输出价格'],
|
||||
];
|
||||
const priceLines = BILLING_VARS.map((v) => [v.field, v.label]);
|
||||
|
||||
const lines = [
|
||||
buildBillingText('命中档位:{{tier}}', { tier: matchedTier || tier.label }),
|
||||
@@ -2331,16 +2328,7 @@ export function renderTieredModelPriceSimple(opts) {
|
||||
];
|
||||
|
||||
if (tier && isPriceDisplayMode(displayMode)) {
|
||||
const priceSegments = [
|
||||
['inputPrice', '输入'],
|
||||
['outputPrice', '补全'],
|
||||
['cacheReadPrice', '缓存读'],
|
||||
['cacheCreatePrice', '缓存创建'],
|
||||
['cacheCreate1hPrice', '1h缓存创建'],
|
||||
['imagePrice', '图片输入'],
|
||||
['audioInputPrice', '音频输入'],
|
||||
['audioOutputPrice', '音频输出'],
|
||||
];
|
||||
const priceSegments = BILLING_VARS.map((v) => [v.field, v.shortLabel]);
|
||||
for (const [field, label] of priceSegments) {
|
||||
if (tier[field] > 0) {
|
||||
segments.push({
|
||||
|
||||
Vendored
+8
-16
@@ -18,7 +18,7 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import { Toast, Pagination } from '@douyinfe/semi-ui';
|
||||
import { toastConstants } from '../constants';
|
||||
import { toastConstants, BILLING_VARS, BILLING_VAR_REGEX } from '../constants';
|
||||
import React from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import {
|
||||
@@ -901,30 +901,22 @@ export const formatDynamicPriceSummary = (billingExpr, t, groupRatio = 1) => {
|
||||
if (!billingExpr) return <span style={{ color: 'var(--semi-color-text-1)' }}>{t('动态计费')}</span>;
|
||||
|
||||
const gr = groupRatio || 1;
|
||||
const tierMatches = billingExpr.match(/tier\(/g) || [];
|
||||
const exprBody = billingExpr.replace(/^v\d+:/, '');
|
||||
const tierMatches = exprBody.match(/tier\(/g) || [];
|
||||
const tierCount = tierMatches.length;
|
||||
|
||||
const varCoeffs = {};
|
||||
const varRe = /\b(p|c|cr|cc|cc1h|img|ai|ao)\s*\*\s*([\d.eE+-]+)/g;
|
||||
const varRe = new RegExp(BILLING_VAR_REGEX.source, 'g');
|
||||
let vm;
|
||||
while ((vm = varRe.exec(billingExpr)) !== null) {
|
||||
while ((vm = varRe.exec(exprBody)) !== null) {
|
||||
if (!(vm[1] in varCoeffs)) varCoeffs[vm[1]] = Number(vm[2]);
|
||||
}
|
||||
const hasCoeffs = 'p' in varCoeffs || 'c' in varCoeffs;
|
||||
|
||||
const varLabels = [
|
||||
['p', '输入价格'],
|
||||
['c', '补全价格'],
|
||||
['cr', '缓存读取价格'],
|
||||
['cc', '缓存创建价格'],
|
||||
['cc1h', '1h缓存创建价格'],
|
||||
['img', '图片输入价格'],
|
||||
['ai', '音频输入价格'],
|
||||
['ao', '音频输出价格'],
|
||||
];
|
||||
const varLabels = BILLING_VARS.map((v) => [v.key, v.label]);
|
||||
|
||||
const hasTimeCondition = /\b(?:hour|weekday|month|day)\(/.test(billingExpr);
|
||||
const hasRequestCondition = /\b(?:param|header)\(/.test(billingExpr);
|
||||
const hasTimeCondition = /\b(?:hour|weekday|month|day)\(/.test(exprBody);
|
||||
const hasRequestCondition = /\b(?:param|header)\(/.test(exprBody);
|
||||
|
||||
const tags = [];
|
||||
if (tierCount > 1) tags.push(`${tierCount}${t('档')}`);
|
||||
|
||||
Reference in New Issue
Block a user