feat: add billing display mode selection and update pricing rendering
Introduce a billing display mode feature allowing users to toggle between price and ratio views. Update relevant components and hooks to support this new functionality, ensuring consistent pricing information is displayed across the application.
This commit is contained in:
746
web/src/helpers/render.jsx
vendored
746
web/src/helpers/render.jsx
vendored
@@ -1193,6 +1193,35 @@ function getEffectiveRatio(groupRatio, user_group_ratio) {
|
||||
};
|
||||
}
|
||||
|
||||
function isPriceDisplayMode(displayMode) {
|
||||
return displayMode !== 'ratio';
|
||||
}
|
||||
|
||||
function formatCompactDisplayPrice(usdAmount, digits = 6) {
|
||||
const { symbol, rate } = getCurrencyConfig();
|
||||
const amount = Number((usdAmount * rate).toFixed(digits));
|
||||
return `${symbol}${amount}`;
|
||||
}
|
||||
|
||||
function appendPricePart(parts, condition, key, vars) {
|
||||
if (!condition) {
|
||||
return;
|
||||
}
|
||||
parts.push(i18next.t(key, vars));
|
||||
}
|
||||
|
||||
function joinBillingSummary(parts) {
|
||||
return parts.filter(Boolean).join(',');
|
||||
}
|
||||
|
||||
function getGroupRatioText(groupRatio, user_group_ratio) {
|
||||
const { ratio, label } = getEffectiveRatio(groupRatio, user_group_ratio);
|
||||
return i18next.t('{{ratioType}} {{ratio}}', {
|
||||
ratioType: label,
|
||||
ratio,
|
||||
});
|
||||
}
|
||||
|
||||
// Shared core for simple price rendering (used by OpenAI-like and Claude-like variants)
|
||||
function renderPriceSimpleCore({
|
||||
modelRatio,
|
||||
@@ -1210,6 +1239,7 @@ function renderPriceSimpleCore({
|
||||
image = false,
|
||||
imageRatio = 1.0,
|
||||
isSystemPromptOverride = false,
|
||||
displayMode = 'price',
|
||||
}) {
|
||||
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(
|
||||
groupRatio,
|
||||
@@ -1219,6 +1249,15 @@ function renderPriceSimpleCore({
|
||||
|
||||
const { symbol, rate } = getCurrencyConfig();
|
||||
if (modelPrice !== -1) {
|
||||
if (isPriceDisplayMode(displayMode)) {
|
||||
return joinBillingSummary([
|
||||
i18next.t('模型价格:{{symbol}}{{price}} / 次', {
|
||||
symbol: symbol,
|
||||
price: (modelPrice * rate).toFixed(6),
|
||||
}),
|
||||
getGroupRatioText(groupRatio, user_group_ratio),
|
||||
]);
|
||||
}
|
||||
const displayPrice = (modelPrice * rate).toFixed(6);
|
||||
return i18next.t('价格:{{symbol}}{{price}} * {{ratioType}}:{{ratio}}', {
|
||||
symbol: symbol,
|
||||
@@ -1240,6 +1279,71 @@ function renderPriceSimpleCore({
|
||||
const shouldShowCacheCreation1h =
|
||||
hasSplitCacheCreation && cacheCreationTokens1h > 0;
|
||||
|
||||
if (isPriceDisplayMode(displayMode)) {
|
||||
const parts = [];
|
||||
if (modelPrice !== -1) {
|
||||
parts.push(
|
||||
i18next.t('按次 {{price}} / 次', {
|
||||
price: formatCompactDisplayPrice(modelPrice),
|
||||
}),
|
||||
);
|
||||
parts.push(getGroupRatioText(groupRatio, user_group_ratio));
|
||||
return joinBillingSummary(parts);
|
||||
}
|
||||
|
||||
parts.push(
|
||||
i18next.t('输入 {{price}} / 1M tokens', {
|
||||
price: formatCompactDisplayPrice(modelRatio * 2.0),
|
||||
}),
|
||||
);
|
||||
|
||||
if (shouldShowCache) {
|
||||
parts.push(
|
||||
i18next.t('缓存读取 {{price}}', {
|
||||
price: formatCompactDisplayPrice(modelRatio * 2.0 * cacheRatio),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (hasSplitCacheCreation && shouldShowCacheCreation5m) {
|
||||
parts.push(
|
||||
i18next.t('5m缓存创建 {{price}}', {
|
||||
price: formatCompactDisplayPrice(modelRatio * 2.0 * cacheCreationRatio5m),
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (hasSplitCacheCreation && shouldShowCacheCreation1h) {
|
||||
parts.push(
|
||||
i18next.t('1h缓存创建 {{price}}', {
|
||||
price: formatCompactDisplayPrice(modelRatio * 2.0 * cacheCreationRatio1h),
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (!hasSplitCacheCreation && shouldShowLegacyCacheCreation) {
|
||||
parts.push(
|
||||
i18next.t('缓存创建 {{price}}', {
|
||||
price: formatCompactDisplayPrice(modelRatio * 2.0 * cacheCreationRatio),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (image) {
|
||||
parts.push(
|
||||
i18next.t('图片输入 {{price}}', {
|
||||
price: formatCompactDisplayPrice(modelRatio * 2.0 * imageRatio),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
parts.push(getGroupRatioText(groupRatio, user_group_ratio));
|
||||
|
||||
let result = joinBillingSummary(parts);
|
||||
if (isSystemPromptOverride) {
|
||||
result += '\n\r' + i18next.t('系统提示覆盖');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const parts = [];
|
||||
// base: model ratio
|
||||
parts.push(i18next.t('模型: {{ratio}}'));
|
||||
@@ -1314,6 +1418,7 @@ export function renderModelPrice(
|
||||
audioInputPrice = 0,
|
||||
imageGenerationCall = false,
|
||||
imageGenerationCallPrice = 0,
|
||||
displayMode = 'price',
|
||||
) {
|
||||
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(
|
||||
groupRatio,
|
||||
@@ -1324,6 +1429,230 @@ export function renderModelPrice(
|
||||
// 获取货币配置
|
||||
const { symbol, rate } = getCurrencyConfig();
|
||||
|
||||
if (isPriceDisplayMode(displayMode)) {
|
||||
if (modelPrice !== -1) {
|
||||
return (
|
||||
<>
|
||||
<article>
|
||||
<p>
|
||||
{i18next.t('模型价格:{{symbol}}{{price}} / 次', {
|
||||
symbol,
|
||||
price: (modelPrice * rate).toFixed(6),
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
{i18next.t(
|
||||
'模型价格 {{symbol}}{{price}} / 次 * {{ratioType}} {{ratio}} = {{symbol}}{{total}}',
|
||||
{
|
||||
symbol,
|
||||
price: (modelPrice * rate).toFixed(6),
|
||||
ratioType: ratioLabel,
|
||||
ratio: groupRatio,
|
||||
total: (modelPrice * groupRatio * rate).toFixed(6),
|
||||
},
|
||||
)}
|
||||
</p>
|
||||
<p>{i18next.t('仅供参考,以实际扣费为准')}</p>
|
||||
</article>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (completionRatio === undefined) {
|
||||
completionRatio = 0;
|
||||
}
|
||||
const inputRatioPrice = modelRatio * 2.0;
|
||||
const completionRatioPrice = modelRatio * 2.0 * completionRatio;
|
||||
const cacheRatioPrice = modelRatio * 2.0 * cacheRatio;
|
||||
const imageRatioPrice = modelRatio * 2.0 * imageRatio;
|
||||
let effectiveInputTokens =
|
||||
inputTokens - cacheTokens + cacheTokens * cacheRatio;
|
||||
if (image && imageOutputTokens > 0) {
|
||||
effectiveInputTokens =
|
||||
inputTokens - imageOutputTokens + imageOutputTokens * imageRatio;
|
||||
}
|
||||
if (audioInputTokens > 0) {
|
||||
effectiveInputTokens -= audioInputTokens;
|
||||
}
|
||||
const price =
|
||||
(effectiveInputTokens / 1000000) * inputRatioPrice * groupRatio +
|
||||
(audioInputTokens / 1000000) * audioInputPrice * groupRatio +
|
||||
(completionTokens / 1000000) * completionRatioPrice * groupRatio +
|
||||
(webSearchCallCount / 1000) * webSearchPrice * groupRatio +
|
||||
(fileSearchCallCount / 1000) * fileSearchPrice * groupRatio +
|
||||
imageGenerationCallPrice * groupRatio;
|
||||
|
||||
return (
|
||||
<>
|
||||
<article>
|
||||
<p>
|
||||
{i18next.t('输入价格:{{symbol}}{{price}} / 1M tokens{{audioPrice}}', {
|
||||
symbol,
|
||||
price: (inputRatioPrice * rate).toFixed(6),
|
||||
audioPrice: audioInputSeperatePrice
|
||||
? `,${i18next.t('音频输入价格')} ${symbol}${(audioInputPrice * rate).toFixed(6)} / 1M tokens`
|
||||
: '',
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
{i18next.t('补全价格:{{symbol}}{{total}} / 1M tokens', {
|
||||
symbol,
|
||||
total: (completionRatioPrice * rate).toFixed(6),
|
||||
})}
|
||||
</p>
|
||||
{cacheTokens > 0 && (
|
||||
<p>
|
||||
{i18next.t('缓存读取价格:{{symbol}}{{total}} / 1M tokens', {
|
||||
symbol,
|
||||
total: (inputRatioPrice * cacheRatio * rate).toFixed(6),
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
{image && imageOutputTokens > 0 && (
|
||||
<p>
|
||||
{i18next.t('图片输入价格:{{symbol}}{{total}} / 1M tokens', {
|
||||
symbol,
|
||||
total: (imageRatioPrice * rate).toFixed(6),
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
{webSearch && webSearchCallCount > 0 && (
|
||||
<p>
|
||||
{i18next.t('Web搜索价格:{{symbol}}{{price}} / 1K 次', {
|
||||
symbol,
|
||||
price: (webSearchPrice * rate).toFixed(6),
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
{fileSearch && fileSearchCallCount > 0 && (
|
||||
<p>
|
||||
{i18next.t('文件搜索价格:{{symbol}}{{price}} / 1K 次', {
|
||||
symbol,
|
||||
price: (fileSearchPrice * rate).toFixed(6),
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
{imageGenerationCall && imageGenerationCallPrice > 0 && (
|
||||
<p>
|
||||
{i18next.t('图片生成调用:{{symbol}}{{price}} / 1次', {
|
||||
symbol,
|
||||
price: (imageGenerationCallPrice * rate).toFixed(6),
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
<p>
|
||||
{(() => {
|
||||
let inputDesc = '';
|
||||
if (image && imageOutputTokens > 0) {
|
||||
inputDesc = i18next.t(
|
||||
'(输入 {{nonImageInput}} tokens + 图片输入 {{imageInput}} tokens / 1M tokens * {{symbol}}{{price}}',
|
||||
{
|
||||
nonImageInput: inputTokens - imageOutputTokens,
|
||||
imageInput: imageOutputTokens,
|
||||
symbol: symbol,
|
||||
price: (inputRatioPrice * rate).toFixed(6),
|
||||
},
|
||||
);
|
||||
} else if (cacheTokens > 0) {
|
||||
inputDesc = i18next.t(
|
||||
'(输入 {{nonCacheInput}} tokens / 1M tokens * {{symbol}}{{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * {{symbol}}{{cachePrice}}',
|
||||
{
|
||||
nonCacheInput: inputTokens - cacheTokens,
|
||||
cacheInput: cacheTokens,
|
||||
symbol: symbol,
|
||||
price: (inputRatioPrice * rate).toFixed(6),
|
||||
cachePrice: (cacheRatioPrice * rate).toFixed(6),
|
||||
},
|
||||
);
|
||||
} else if (audioInputSeperatePrice && audioInputTokens > 0) {
|
||||
inputDesc = i18next.t(
|
||||
'(输入 {{nonAudioInput}} tokens / 1M tokens * {{symbol}}{{price}} + 音频输入 {{audioInput}} tokens / 1M tokens * {{symbol}}{{audioPrice}}',
|
||||
{
|
||||
nonAudioInput: inputTokens - audioInputTokens,
|
||||
audioInput: audioInputTokens,
|
||||
symbol: symbol,
|
||||
price: (inputRatioPrice * rate).toFixed(6),
|
||||
audioPrice: (audioInputPrice * rate).toFixed(6),
|
||||
},
|
||||
);
|
||||
} else {
|
||||
inputDesc = i18next.t(
|
||||
'(输入 {{input}} tokens / 1M tokens * {{symbol}}{{price}}',
|
||||
{
|
||||
input: inputTokens,
|
||||
symbol: symbol,
|
||||
price: (inputRatioPrice * rate).toFixed(6),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const outputDesc = i18next.t(
|
||||
'输出 {{completion}} tokens / 1M tokens * {{symbol}}{{compPrice}}) * {{ratioType}} {{ratio}}',
|
||||
{
|
||||
completion: completionTokens,
|
||||
symbol: symbol,
|
||||
compPrice: (completionRatioPrice * rate).toFixed(6),
|
||||
ratio: groupRatio,
|
||||
ratioType: ratioLabel,
|
||||
},
|
||||
);
|
||||
|
||||
const extraServices = [
|
||||
webSearch && webSearchCallCount > 0
|
||||
? i18next.t(
|
||||
' + Web搜索 {{count}}次 / 1K 次 * {{symbol}}{{price}} * {{ratioType}} {{ratio}}',
|
||||
{
|
||||
count: webSearchCallCount,
|
||||
symbol: symbol,
|
||||
price: (webSearchPrice * rate).toFixed(6),
|
||||
ratio: groupRatio,
|
||||
ratioType: ratioLabel,
|
||||
},
|
||||
)
|
||||
: '',
|
||||
fileSearch && fileSearchCallCount > 0
|
||||
? i18next.t(
|
||||
' + 文件搜索 {{count}}次 / 1K 次 * {{symbol}}{{price}} * {{ratioType}} {{ratio}}',
|
||||
{
|
||||
count: fileSearchCallCount,
|
||||
symbol: symbol,
|
||||
price: (fileSearchPrice * rate).toFixed(6),
|
||||
ratio: groupRatio,
|
||||
ratioType: ratioLabel,
|
||||
},
|
||||
)
|
||||
: '',
|
||||
imageGenerationCall && imageGenerationCallPrice > 0
|
||||
? i18next.t(
|
||||
' + 图片生成调用 {{symbol}}{{price}} / 1次 * {{ratioType}} {{ratio}}',
|
||||
{
|
||||
symbol: symbol,
|
||||
price: (imageGenerationCallPrice * rate).toFixed(6),
|
||||
ratio: groupRatio,
|
||||
ratioType: ratioLabel,
|
||||
},
|
||||
)
|
||||
: '',
|
||||
].join('');
|
||||
|
||||
return i18next.t(
|
||||
'{{inputDesc}} + {{outputDesc}}{{extraServices}} = {{symbol}}{{total}}',
|
||||
{
|
||||
inputDesc,
|
||||
outputDesc,
|
||||
extraServices,
|
||||
symbol,
|
||||
total: (price * rate).toFixed(6),
|
||||
},
|
||||
);
|
||||
})()}
|
||||
</p>
|
||||
<p>{i18next.t('仅供参考,以实际扣费为准')}</p>
|
||||
</article>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (modelPrice !== -1) {
|
||||
const displayPrice = (modelPrice * rate).toFixed(6);
|
||||
const displayTotal = (modelPrice * groupRatio * rate).toFixed(6);
|
||||
@@ -1573,6 +1902,7 @@ export function renderLogContent(
|
||||
webSearchCallCount = 0,
|
||||
fileSearch = false,
|
||||
fileSearchCallCount = 0,
|
||||
displayMode = 'price',
|
||||
) {
|
||||
const {
|
||||
ratio,
|
||||
@@ -1583,6 +1913,45 @@ export function renderLogContent(
|
||||
// 获取货币配置
|
||||
const { symbol, rate } = getCurrencyConfig();
|
||||
|
||||
if (isPriceDisplayMode(displayMode)) {
|
||||
if (modelPrice !== -1) {
|
||||
return joinBillingSummary([
|
||||
i18next.t('模型价格 {{symbol}}{{price}} / 次', {
|
||||
symbol,
|
||||
price: (modelPrice * rate).toFixed(6),
|
||||
}),
|
||||
getGroupRatioText(groupRatio, user_group_ratio),
|
||||
]);
|
||||
}
|
||||
|
||||
const parts = [
|
||||
i18next.t('输入价格 {{symbol}}{{price}} / 1M tokens', {
|
||||
symbol,
|
||||
price: (modelRatio * 2.0 * rate).toFixed(6),
|
||||
}),
|
||||
i18next.t('补全价格 {{symbol}}{{price}} / 1M tokens', {
|
||||
symbol,
|
||||
price: (modelRatio * 2.0 * completionRatio * rate).toFixed(6),
|
||||
}),
|
||||
];
|
||||
appendPricePart(parts, cacheRatio !== 1.0, '缓存读取价格 {{symbol}}{{price}} / 1M tokens', {
|
||||
symbol,
|
||||
price: (modelRatio * 2.0 * cacheRatio * rate).toFixed(6),
|
||||
});
|
||||
appendPricePart(parts, image, '图片输入价格 {{symbol}}{{price}} / 1M tokens', {
|
||||
symbol,
|
||||
price: (modelRatio * 2.0 * imageRatio * rate).toFixed(6),
|
||||
});
|
||||
appendPricePart(parts, webSearch, 'Web 搜索调用 {{webSearchCallCount}} 次', {
|
||||
webSearchCallCount,
|
||||
});
|
||||
appendPricePart(parts, fileSearch, '文件搜索调用 {{fileSearchCallCount}} 次', {
|
||||
fileSearchCallCount,
|
||||
});
|
||||
parts.push(getGroupRatioText(groupRatio, user_group_ratio));
|
||||
return joinBillingSummary(parts);
|
||||
}
|
||||
|
||||
if (modelPrice !== -1) {
|
||||
return i18next.t('模型价格 {{symbol}}{{price}},{{ratioType}} {{ratio}}', {
|
||||
symbol: symbol,
|
||||
@@ -1647,6 +2016,7 @@ export function renderModelPriceSimple(
|
||||
imageRatio = 1.0,
|
||||
isSystemPromptOverride = false,
|
||||
provider = 'openai',
|
||||
displayMode = 'price',
|
||||
) {
|
||||
return renderPriceSimpleCore({
|
||||
modelRatio,
|
||||
@@ -1664,6 +2034,7 @@ export function renderModelPriceSimple(
|
||||
image,
|
||||
imageRatio,
|
||||
isSystemPromptOverride,
|
||||
displayMode,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1681,6 +2052,7 @@ export function renderAudioModelPrice(
|
||||
user_group_ratio,
|
||||
cacheTokens = 0,
|
||||
cacheRatio = 1.0,
|
||||
displayMode = 'price',
|
||||
) {
|
||||
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(
|
||||
groupRatio,
|
||||
@@ -1691,6 +2063,125 @@ export function renderAudioModelPrice(
|
||||
// 获取货币配置
|
||||
const { symbol, rate } = getCurrencyConfig();
|
||||
|
||||
if (isPriceDisplayMode(displayMode)) {
|
||||
if (modelPrice !== -1) {
|
||||
return (
|
||||
<>
|
||||
<article>
|
||||
<p>
|
||||
{i18next.t('模型价格:{{symbol}}{{price}} / 次', {
|
||||
symbol,
|
||||
price: (modelPrice * rate).toFixed(6),
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
{i18next.t(
|
||||
'模型价格 {{symbol}}{{price}} / 次 * {{ratioType}} {{ratio}} = {{symbol}}{{total}}',
|
||||
{
|
||||
symbol,
|
||||
price: (modelPrice * rate).toFixed(6),
|
||||
ratioType: ratioLabel,
|
||||
ratio: groupRatio,
|
||||
total: (modelPrice * groupRatio * rate).toFixed(6),
|
||||
},
|
||||
)}
|
||||
</p>
|
||||
<p>{i18next.t('仅供参考,以实际扣费为准')}</p>
|
||||
</article>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (completionRatio === undefined) {
|
||||
completionRatio = 0;
|
||||
}
|
||||
audioRatio = parseFloat(audioRatio).toFixed(6);
|
||||
const inputRatioPrice = modelRatio * 2.0;
|
||||
const completionRatioPrice = modelRatio * 2.0 * completionRatio;
|
||||
const textPrice =
|
||||
((inputTokens - cacheTokens + cacheTokens * cacheRatio) / 1000000) *
|
||||
inputRatioPrice *
|
||||
groupRatio +
|
||||
(completionTokens / 1000000) * completionRatioPrice * groupRatio;
|
||||
const audioPrice =
|
||||
(audioInputTokens / 1000000) * inputRatioPrice * audioRatio * groupRatio +
|
||||
(audioCompletionTokens / 1000000) *
|
||||
inputRatioPrice *
|
||||
audioRatio *
|
||||
audioCompletionRatio *
|
||||
groupRatio;
|
||||
const totalPrice = textPrice + audioPrice;
|
||||
|
||||
return (
|
||||
<>
|
||||
<article>
|
||||
<p>
|
||||
{i18next.t('输入价格:{{symbol}}{{price}} / 1M tokens', {
|
||||
symbol,
|
||||
price: (inputRatioPrice * rate).toFixed(6),
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
{i18next.t('补全价格:{{symbol}}{{price}} / 1M tokens', {
|
||||
symbol,
|
||||
price: (completionRatioPrice * rate).toFixed(6),
|
||||
})}
|
||||
</p>
|
||||
{cacheTokens > 0 && (
|
||||
<p>
|
||||
{i18next.t('缓存读取价格:{{symbol}}{{price}} / 1M tokens', {
|
||||
symbol,
|
||||
price: (inputRatioPrice * cacheRatio * rate).toFixed(6),
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
<p>
|
||||
{i18next.t('音频输入价格:{{symbol}}{{price}} / 1M tokens', {
|
||||
symbol,
|
||||
price: (inputRatioPrice * audioRatio * rate).toFixed(6),
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
{i18next.t('音频补全价格:{{symbol}}{{price}} / 1M tokens', {
|
||||
symbol,
|
||||
price: (
|
||||
inputRatioPrice *
|
||||
audioRatio *
|
||||
audioCompletionRatio *
|
||||
rate
|
||||
).toFixed(6),
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
{i18next.t(
|
||||
'文字提示 {{input}} tokens / 1M tokens * {{symbol}}{{textInputPrice}} + 文字补全 {{completion}} tokens / 1M tokens * {{symbol}}{{textCompPrice}} + 音频提示 {{audioInput}} tokens / 1M tokens * {{symbol}}{{audioInputPrice}} + 音频补全 {{audioCompletion}} tokens / 1M tokens * {{symbol}}{{audioCompPrice}} * {{ratioType}} {{ratio}} = {{symbol}}{{total}}',
|
||||
{
|
||||
input: inputTokens,
|
||||
completion: completionTokens,
|
||||
audioInput: audioInputTokens,
|
||||
audioCompletion: audioCompletionTokens,
|
||||
textInputPrice: (inputRatioPrice * rate).toFixed(6),
|
||||
textCompPrice: (completionRatioPrice * rate).toFixed(6),
|
||||
audioInputPrice: (audioRatio * inputRatioPrice * rate).toFixed(6),
|
||||
audioCompPrice: (
|
||||
audioRatio *
|
||||
audioCompletionRatio *
|
||||
inputRatioPrice *
|
||||
rate
|
||||
).toFixed(6),
|
||||
ratioType: ratioLabel,
|
||||
ratio: groupRatio,
|
||||
symbol,
|
||||
total: (totalPrice * rate).toFixed(6),
|
||||
},
|
||||
)}
|
||||
</p>
|
||||
<p>{i18next.t('仅供参考,以实际扣费为准')}</p>
|
||||
</article>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// 1 ratio = $0.002 / 1K tokens
|
||||
if (modelPrice !== -1) {
|
||||
return i18next.t(
|
||||
@@ -1882,6 +2373,7 @@ export function renderClaudeModelPrice(
|
||||
cacheCreationRatio5m = 1.0,
|
||||
cacheCreationTokens1h = 0,
|
||||
cacheCreationRatio1h = 1.0,
|
||||
displayMode = 'price',
|
||||
) {
|
||||
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(
|
||||
groupRatio,
|
||||
@@ -1892,6 +2384,201 @@ export function renderClaudeModelPrice(
|
||||
// 获取货币配置
|
||||
const { symbol, rate } = getCurrencyConfig();
|
||||
|
||||
if (isPriceDisplayMode(displayMode)) {
|
||||
if (modelPrice !== -1) {
|
||||
return (
|
||||
<>
|
||||
<article>
|
||||
<p>
|
||||
{i18next.t('模型价格:{{symbol}}{{price}} / 次', {
|
||||
symbol,
|
||||
price: (modelPrice * rate).toFixed(6),
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
{i18next.t(
|
||||
'模型价格 {{symbol}}{{price}} / 次 * {{ratioType}} {{ratio}} = {{symbol}}{{total}}',
|
||||
{
|
||||
symbol,
|
||||
price: (modelPrice * rate).toFixed(6),
|
||||
ratioType: ratioLabel,
|
||||
ratio: groupRatio,
|
||||
total: (modelPrice * groupRatio * rate).toFixed(6),
|
||||
},
|
||||
)}
|
||||
</p>
|
||||
<p>{i18next.t('仅供参考,以实际扣费为准')}</p>
|
||||
</article>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (completionRatio === undefined) {
|
||||
completionRatio = 0;
|
||||
}
|
||||
|
||||
const inputRatioPrice = modelRatio * 2.0;
|
||||
const completionRatioPrice = modelRatio * 2.0 * completionRatio;
|
||||
const cacheRatioPrice = modelRatio * 2.0 * cacheRatio;
|
||||
const cacheCreationRatioPrice = modelRatio * 2.0 * cacheCreationRatio;
|
||||
const cacheCreationRatioPrice5m = modelRatio * 2.0 * cacheCreationRatio5m;
|
||||
const cacheCreationRatioPrice1h = modelRatio * 2.0 * cacheCreationRatio1h;
|
||||
const hasSplitCacheCreation =
|
||||
cacheCreationTokens5m > 0 || cacheCreationTokens1h > 0;
|
||||
const legacyCacheCreationTokens = hasSplitCacheCreation
|
||||
? 0
|
||||
: cacheCreationTokens;
|
||||
const effectiveInputTokens =
|
||||
inputTokens +
|
||||
cacheTokens * cacheRatio +
|
||||
legacyCacheCreationTokens * cacheCreationRatio +
|
||||
cacheCreationTokens5m * cacheCreationRatio5m +
|
||||
cacheCreationTokens1h * cacheCreationRatio1h;
|
||||
const price =
|
||||
(effectiveInputTokens / 1000000) * inputRatioPrice * groupRatio +
|
||||
(completionTokens / 1000000) * completionRatioPrice * groupRatio;
|
||||
const inputUnitPrice = inputRatioPrice * rate;
|
||||
const completionUnitPrice = completionRatioPrice * rate;
|
||||
const cacheUnitPrice = cacheRatioPrice * rate;
|
||||
const cacheCreationUnitPrice = cacheCreationRatioPrice * rate;
|
||||
const cacheCreationUnitPrice5m = cacheCreationRatioPrice5m * rate;
|
||||
const cacheCreationUnitPrice1h = cacheCreationRatioPrice1h * rate;
|
||||
const cacheCreationUnitPriceTotal =
|
||||
cacheCreationUnitPrice5m + cacheCreationUnitPrice1h;
|
||||
const shouldShowCache = cacheTokens > 0;
|
||||
const shouldShowLegacyCacheCreation =
|
||||
!hasSplitCacheCreation && cacheCreationTokens > 0;
|
||||
const shouldShowCacheCreation5m =
|
||||
hasSplitCacheCreation && cacheCreationTokens5m > 0;
|
||||
const shouldShowCacheCreation1h =
|
||||
hasSplitCacheCreation && cacheCreationTokens1h > 0;
|
||||
|
||||
const breakdownSegments = [
|
||||
i18next.t('提示 {{input}} tokens / 1M tokens * {{symbol}}{{price}}', {
|
||||
input: inputTokens,
|
||||
symbol,
|
||||
price: inputUnitPrice.toFixed(6),
|
||||
}),
|
||||
];
|
||||
|
||||
if (shouldShowCache) {
|
||||
breakdownSegments.push(
|
||||
i18next.t('缓存 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}}', {
|
||||
tokens: cacheTokens,
|
||||
symbol,
|
||||
price: cacheUnitPrice.toFixed(6),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldShowLegacyCacheCreation) {
|
||||
breakdownSegments.push(
|
||||
i18next.t('缓存创建 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}}', {
|
||||
tokens: cacheCreationTokens,
|
||||
symbol,
|
||||
price: cacheCreationUnitPrice.toFixed(6),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldShowCacheCreation5m) {
|
||||
breakdownSegments.push(
|
||||
i18next.t('5m缓存创建 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}}', {
|
||||
tokens: cacheCreationTokens5m,
|
||||
symbol,
|
||||
price: cacheCreationUnitPrice5m.toFixed(6),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (shouldShowCacheCreation1h) {
|
||||
breakdownSegments.push(
|
||||
i18next.t('1h缓存创建 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}}', {
|
||||
tokens: cacheCreationTokens1h,
|
||||
symbol,
|
||||
price: cacheCreationUnitPrice1h.toFixed(6),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
breakdownSegments.push(
|
||||
i18next.t(
|
||||
'补全 {{completion}} tokens / 1M tokens * {{symbol}}{{price}}',
|
||||
{
|
||||
completion: completionTokens,
|
||||
symbol,
|
||||
price: completionUnitPrice.toFixed(6),
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const breakdownText = breakdownSegments.join(' + ');
|
||||
|
||||
return (
|
||||
<>
|
||||
<article>
|
||||
<p>
|
||||
{i18next.t('输入价格:{{symbol}}{{price}} / 1M tokens', {
|
||||
symbol,
|
||||
price: (inputRatioPrice * rate).toFixed(6),
|
||||
})}
|
||||
</p>
|
||||
<p>
|
||||
{i18next.t('补全价格:{{symbol}}{{price}} / 1M tokens', {
|
||||
symbol,
|
||||
price: (completionRatioPrice * rate).toFixed(6),
|
||||
})}
|
||||
</p>
|
||||
{cacheTokens > 0 && (
|
||||
<p>
|
||||
{i18next.t('缓存读取价格:{{symbol}}{{price}} / 1M tokens', {
|
||||
symbol,
|
||||
price: (cacheRatioPrice * rate).toFixed(6),
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
{!hasSplitCacheCreation && cacheCreationTokens > 0 && (
|
||||
<p>
|
||||
{i18next.t('缓存创建价格:{{symbol}}{{price}} / 1M tokens', {
|
||||
symbol,
|
||||
price: (cacheCreationRatioPrice * rate).toFixed(6),
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
{hasSplitCacheCreation && cacheCreationTokens5m > 0 && (
|
||||
<p>
|
||||
{i18next.t('5m缓存创建价格:{{symbol}}{{price}} / 1M tokens', {
|
||||
symbol,
|
||||
price: (cacheCreationRatioPrice5m * rate).toFixed(6),
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
{hasSplitCacheCreation && cacheCreationTokens1h > 0 && (
|
||||
<p>
|
||||
{i18next.t('1h缓存创建价格:{{symbol}}{{price}} / 1M tokens', {
|
||||
symbol,
|
||||
price: (cacheCreationRatioPrice1h * rate).toFixed(6),
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
<p>
|
||||
{i18next.t(
|
||||
'{{breakdown}} * {{ratioType}} {{ratio}} = {{symbol}}{{total}}',
|
||||
{
|
||||
breakdown: breakdownText,
|
||||
ratioType: ratioLabel,
|
||||
ratio: groupRatio,
|
||||
symbol,
|
||||
total: (price * rate).toFixed(6),
|
||||
},
|
||||
)}
|
||||
</p>
|
||||
<p>{i18next.t('仅供参考,以实际扣费为准')}</p>
|
||||
</article>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (modelPrice !== -1) {
|
||||
return i18next.t(
|
||||
'模型价格:{{symbol}}{{price}} * {{ratioType}}:{{ratio}} = {{symbol}}{{total}}',
|
||||
@@ -2150,6 +2837,7 @@ export function renderClaudeLogContent(
|
||||
cacheCreationRatio5m = 1.0,
|
||||
cacheCreationTokens1h = 0,
|
||||
cacheCreationRatio1h = 1.0,
|
||||
displayMode = 'price',
|
||||
) {
|
||||
const { ratio: effectiveGroupRatio, label: ratioLabel } = getEffectiveRatio(
|
||||
groupRatio,
|
||||
@@ -2160,6 +2848,64 @@ export function renderClaudeLogContent(
|
||||
// 获取货币配置
|
||||
const { symbol, rate } = getCurrencyConfig();
|
||||
|
||||
if (isPriceDisplayMode(displayMode)) {
|
||||
if (modelPrice !== -1) {
|
||||
return joinBillingSummary([
|
||||
i18next.t('模型价格 {{symbol}}{{price}} / 次', {
|
||||
symbol,
|
||||
price: (modelPrice * rate).toFixed(6),
|
||||
}),
|
||||
getGroupRatioText(groupRatio, user_group_ratio),
|
||||
]);
|
||||
}
|
||||
|
||||
const parts = [
|
||||
i18next.t('输入价格 {{symbol}}{{price}} / 1M tokens', {
|
||||
symbol,
|
||||
price: (modelRatio * 2.0 * rate).toFixed(6),
|
||||
}),
|
||||
i18next.t('补全价格 {{symbol}}{{price}} / 1M tokens', {
|
||||
symbol,
|
||||
price: (modelRatio * 2.0 * completionRatio * rate).toFixed(6),
|
||||
}),
|
||||
i18next.t('缓存读取价格 {{symbol}}{{price}} / 1M tokens', {
|
||||
symbol,
|
||||
price: (modelRatio * 2.0 * cacheRatio * rate).toFixed(6),
|
||||
}),
|
||||
];
|
||||
const hasSplitCacheCreation =
|
||||
cacheCreationTokens5m > 0 || cacheCreationTokens1h > 0;
|
||||
appendPricePart(
|
||||
parts,
|
||||
hasSplitCacheCreation && cacheCreationTokens5m > 0,
|
||||
'5m缓存创建价格 {{symbol}}{{price}} / 1M tokens',
|
||||
{
|
||||
symbol,
|
||||
price: (modelRatio * 2.0 * cacheCreationRatio5m * rate).toFixed(6),
|
||||
},
|
||||
);
|
||||
appendPricePart(
|
||||
parts,
|
||||
hasSplitCacheCreation && cacheCreationTokens1h > 0,
|
||||
'1h缓存创建价格 {{symbol}}{{price}} / 1M tokens',
|
||||
{
|
||||
symbol,
|
||||
price: (modelRatio * 2.0 * cacheCreationRatio1h * rate).toFixed(6),
|
||||
},
|
||||
);
|
||||
appendPricePart(
|
||||
parts,
|
||||
!hasSplitCacheCreation,
|
||||
'缓存创建价格 {{symbol}}{{price}} / 1M tokens',
|
||||
{
|
||||
symbol,
|
||||
price: (modelRatio * 2.0 * cacheCreationRatio * rate).toFixed(6),
|
||||
},
|
||||
);
|
||||
parts.push(getGroupRatioText(groupRatio, user_group_ratio));
|
||||
return joinBillingSummary(parts);
|
||||
}
|
||||
|
||||
if (modelPrice !== -1) {
|
||||
return i18next.t('模型价格 {{symbol}}{{price}},{{ratioType}} {{ratio}}', {
|
||||
symbol: symbol,
|
||||
|
||||
Reference in New Issue
Block a user