From f9b5ecc9555544b4c63c926c4e6ac05b15213126 Mon Sep 17 00:00:00 2001 From: CaIon Date: Fri, 6 Mar 2026 23:35:17 +0800 Subject: [PATCH] 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. --- .../table/usage-logs/UsageLogsColumnDefs.jsx | 6 +- .../table/usage-logs/UsageLogsTable.jsx | 3 + .../usage-logs/modals/ColumnSelectorModal.jsx | 16 +- web/src/helpers/render.jsx | 746 ++++++++++++++++++ web/src/hooks/usage-logs/useUsageLogsData.jsx | 110 +-- web/src/i18n/locales/en.json | 40 + web/src/i18n/locales/fr.json | 21 + web/src/i18n/locales/ja.json | 21 + web/src/i18n/locales/ru.json | 21 + web/src/i18n/locales/vi.json | 21 + web/src/i18n/locales/zh-CN.json | 28 + web/src/i18n/locales/zh-TW.json | 28 + 12 files changed, 1014 insertions(+), 47 deletions(-) diff --git a/web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx b/web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx index b1538877a..8d7141dde 100644 --- a/web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx +++ b/web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx @@ -337,6 +337,7 @@ export const getLogsColumns = ({ showUserInfoFunc, openChannelAffinityUsageCacheModal, isAdminUser, + billingDisplayMode = 'price', }) => { return [ { @@ -761,11 +762,10 @@ export const getLogsColumns = ({ Boolean(other?.violation_fee_marker) ) { const feeQuota = other?.fee_quota ?? record?.quota; - const ratioText = formatRatio(other?.group_ratio); const summary = [ t('违规扣费'), - `${t('分组倍率')}:${ratioText}`, `${t('扣费')}:${renderQuota(feeQuota, 6)}`, + `${t('分组倍率')}:${formatRatio(other?.group_ratio)}`, text ? `${t('详情')}:${text}` : null, ] .filter(Boolean) @@ -808,6 +808,7 @@ export const getLogsColumns = ({ 1.0, other?.is_system_prompt_overwritten, 'claude', + billingDisplayMode, ) : renderModelPriceSimple( other.model_ratio, @@ -826,6 +827,7 @@ export const getLogsColumns = ({ 1.0, other?.is_system_prompt_overwritten, 'openai', + billingDisplayMode, ); return ( { openChannelAffinityUsageCacheModal, hasExpandableRows, isAdminUser, + billingDisplayMode, t, COLUMN_KEYS, } = logsData; @@ -56,6 +57,7 @@ const LogsTable = (logsData) => { showUserInfoFunc, openChannelAffinityUsageCacheModal, isAdminUser, + billingDisplayMode, }); }, [ t, @@ -64,6 +66,7 @@ const LogsTable = (logsData) => { showUserInfoFunc, openChannelAffinityUsageCacheModal, isAdminUser, + billingDisplayMode, ]); // Filter columns based on visibility settings diff --git a/web/src/components/table/usage-logs/modals/ColumnSelectorModal.jsx b/web/src/components/table/usage-logs/modals/ColumnSelectorModal.jsx index ab56df8ef..eb0311dc8 100644 --- a/web/src/components/table/usage-logs/modals/ColumnSelectorModal.jsx +++ b/web/src/components/table/usage-logs/modals/ColumnSelectorModal.jsx @@ -18,7 +18,7 @@ For commercial licensing, please contact support@quantumnous.com */ import React from 'react'; -import { Modal, Button, Checkbox } from '@douyinfe/semi-ui'; +import { Modal, Button, Checkbox, RadioGroup, Radio } from '@douyinfe/semi-ui'; import { getLogsColumns } from '../UsageLogsColumnDefs'; const ColumnSelectorModal = ({ @@ -28,6 +28,8 @@ const ColumnSelectorModal = ({ handleColumnVisibilityChange, handleSelectAll, initDefaultColumns, + billingDisplayMode, + setBillingDisplayMode, COLUMN_KEYS, isAdminUser, copyText, @@ -41,6 +43,7 @@ const ColumnSelectorModal = ({ copyText, showUserInfoFunc, isAdminUser, + billingDisplayMode, }); return ( @@ -61,6 +64,17 @@ const ColumnSelectorModal = ({ } >
+
+
{t('计费显示模式')}
+ setBillingDisplayMode(event.target.value)} + > + {t('价格模式(默认)')} + {t('倍率模式')} + +
v === true)} indeterminate={ diff --git a/web/src/helpers/render.jsx b/web/src/helpers/render.jsx index 3ba198cb3..ee1c31625 100644 --- a/web/src/helpers/render.jsx +++ b/web/src/helpers/render.jsx @@ -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 ( + <> +
+

+ {i18next.t('模型价格:{{symbol}}{{price}} / 次', { + symbol, + price: (modelPrice * rate).toFixed(6), + })} +

+

+ {i18next.t( + '模型价格 {{symbol}}{{price}} / 次 * {{ratioType}} {{ratio}} = {{symbol}}{{total}}', + { + symbol, + price: (modelPrice * rate).toFixed(6), + ratioType: ratioLabel, + ratio: groupRatio, + total: (modelPrice * groupRatio * rate).toFixed(6), + }, + )} +

+

{i18next.t('仅供参考,以实际扣费为准')}

+
+ + ); + } + + 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 ( + <> +
+

+ {i18next.t('输入价格:{{symbol}}{{price}} / 1M tokens{{audioPrice}}', { + symbol, + price: (inputRatioPrice * rate).toFixed(6), + audioPrice: audioInputSeperatePrice + ? `,${i18next.t('音频输入价格')} ${symbol}${(audioInputPrice * rate).toFixed(6)} / 1M tokens` + : '', + })} +

+

+ {i18next.t('补全价格:{{symbol}}{{total}} / 1M tokens', { + symbol, + total: (completionRatioPrice * rate).toFixed(6), + })} +

+ {cacheTokens > 0 && ( +

+ {i18next.t('缓存读取价格:{{symbol}}{{total}} / 1M tokens', { + symbol, + total: (inputRatioPrice * cacheRatio * rate).toFixed(6), + })} +

+ )} + {image && imageOutputTokens > 0 && ( +

+ {i18next.t('图片输入价格:{{symbol}}{{total}} / 1M tokens', { + symbol, + total: (imageRatioPrice * rate).toFixed(6), + })} +

+ )} + {webSearch && webSearchCallCount > 0 && ( +

+ {i18next.t('Web搜索价格:{{symbol}}{{price}} / 1K 次', { + symbol, + price: (webSearchPrice * rate).toFixed(6), + })} +

+ )} + {fileSearch && fileSearchCallCount > 0 && ( +

+ {i18next.t('文件搜索价格:{{symbol}}{{price}} / 1K 次', { + symbol, + price: (fileSearchPrice * rate).toFixed(6), + })} +

+ )} + {imageGenerationCall && imageGenerationCallPrice > 0 && ( +

+ {i18next.t('图片生成调用:{{symbol}}{{price}} / 1次', { + symbol, + price: (imageGenerationCallPrice * rate).toFixed(6), + })} +

+ )} +

+ {(() => { + 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), + }, + ); + })()} +

+

{i18next.t('仅供参考,以实际扣费为准')}

+
+ + ); + } + 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 ( + <> +
+

+ {i18next.t('模型价格:{{symbol}}{{price}} / 次', { + symbol, + price: (modelPrice * rate).toFixed(6), + })} +

+

+ {i18next.t( + '模型价格 {{symbol}}{{price}} / 次 * {{ratioType}} {{ratio}} = {{symbol}}{{total}}', + { + symbol, + price: (modelPrice * rate).toFixed(6), + ratioType: ratioLabel, + ratio: groupRatio, + total: (modelPrice * groupRatio * rate).toFixed(6), + }, + )} +

+

{i18next.t('仅供参考,以实际扣费为准')}

+
+ + ); + } + + 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 ( + <> +
+

+ {i18next.t('输入价格:{{symbol}}{{price}} / 1M tokens', { + symbol, + price: (inputRatioPrice * rate).toFixed(6), + })} +

+

+ {i18next.t('补全价格:{{symbol}}{{price}} / 1M tokens', { + symbol, + price: (completionRatioPrice * rate).toFixed(6), + })} +

+ {cacheTokens > 0 && ( +

+ {i18next.t('缓存读取价格:{{symbol}}{{price}} / 1M tokens', { + symbol, + price: (inputRatioPrice * cacheRatio * rate).toFixed(6), + })} +

+ )} +

+ {i18next.t('音频输入价格:{{symbol}}{{price}} / 1M tokens', { + symbol, + price: (inputRatioPrice * audioRatio * rate).toFixed(6), + })} +

+

+ {i18next.t('音频补全价格:{{symbol}}{{price}} / 1M tokens', { + symbol, + price: ( + inputRatioPrice * + audioRatio * + audioCompletionRatio * + rate + ).toFixed(6), + })} +

+

+ {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), + }, + )} +

+

{i18next.t('仅供参考,以实际扣费为准')}

+
+ + ); + } + // 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 ( + <> +
+

+ {i18next.t('模型价格:{{symbol}}{{price}} / 次', { + symbol, + price: (modelPrice * rate).toFixed(6), + })} +

+

+ {i18next.t( + '模型价格 {{symbol}}{{price}} / 次 * {{ratioType}} {{ratio}} = {{symbol}}{{total}}', + { + symbol, + price: (modelPrice * rate).toFixed(6), + ratioType: ratioLabel, + ratio: groupRatio, + total: (modelPrice * groupRatio * rate).toFixed(6), + }, + )} +

+

{i18next.t('仅供参考,以实际扣费为准')}

+
+ + ); + } + + 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 ( + <> +
+

+ {i18next.t('输入价格:{{symbol}}{{price}} / 1M tokens', { + symbol, + price: (inputRatioPrice * rate).toFixed(6), + })} +

+

+ {i18next.t('补全价格:{{symbol}}{{price}} / 1M tokens', { + symbol, + price: (completionRatioPrice * rate).toFixed(6), + })} +

+ {cacheTokens > 0 && ( +

+ {i18next.t('缓存读取价格:{{symbol}}{{price}} / 1M tokens', { + symbol, + price: (cacheRatioPrice * rate).toFixed(6), + })} +

+ )} + {!hasSplitCacheCreation && cacheCreationTokens > 0 && ( +

+ {i18next.t('缓存创建价格:{{symbol}}{{price}} / 1M tokens', { + symbol, + price: (cacheCreationRatioPrice * rate).toFixed(6), + })} +

+ )} + {hasSplitCacheCreation && cacheCreationTokens5m > 0 && ( +

+ {i18next.t('5m缓存创建价格:{{symbol}}{{price}} / 1M tokens', { + symbol, + price: (cacheCreationRatioPrice5m * rate).toFixed(6), + })} +

+ )} + {hasSplitCacheCreation && cacheCreationTokens1h > 0 && ( +

+ {i18next.t('1h缓存创建价格:{{symbol}}{{price}} / 1M tokens', { + symbol, + price: (cacheCreationRatioPrice1h * rate).toFixed(6), + })} +

+ )} +

+ {i18next.t( + '{{breakdown}} * {{ratioType}} {{ratio}} = {{symbol}}{{total}}', + { + breakdown: breakdownText, + ratioType: ratioLabel, + ratio: groupRatio, + symbol, + total: (price * rate).toFixed(6), + }, + )} +

+

{i18next.t('仅供参考,以实际扣费为准')}

+
+ + ); + } + 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, diff --git a/web/src/hooks/usage-logs/useUsageLogsData.jsx b/web/src/hooks/usage-logs/useUsageLogsData.jsx index b69a7cf18..f61426113 100644 --- a/web/src/hooks/usage-logs/useUsageLogsData.jsx +++ b/web/src/hooks/usage-logs/useUsageLogsData.jsx @@ -78,6 +78,9 @@ export const useLogsData = () => { const STORAGE_KEY = isAdminUser ? 'logs-table-columns-admin' : 'logs-table-columns-user'; + const BILLING_DISPLAY_MODE_STORAGE_KEY = isAdminUser + ? 'logs-billing-display-mode-admin' + : 'logs-billing-display-mode-user'; // Statistics state const [stat, setStat] = useState({ @@ -102,50 +105,6 @@ export const useLogsData = () => { logType: '0', }; - // Column visibility state - const [visibleColumns, setVisibleColumns] = useState({}); - const [showColumnSelector, setShowColumnSelector] = useState(false); - - // Compact mode - const [compactMode, setCompactMode] = useTableCompactMode('logs'); - - // User info modal state - const [showUserInfo, setShowUserInfoModal] = useState(false); - const [userInfoData, setUserInfoData] = useState(null); - - // Channel affinity usage cache stats modal state (admin only) - const [ - showChannelAffinityUsageCacheModal, - setShowChannelAffinityUsageCacheModal, - ] = useState(false); - const [channelAffinityUsageCacheTarget, setChannelAffinityUsageCacheTarget] = - useState(null); - - // Load saved column preferences from localStorage - useEffect(() => { - const savedColumns = localStorage.getItem(STORAGE_KEY); - if (savedColumns) { - try { - const parsed = JSON.parse(savedColumns); - const defaults = getDefaultColumnVisibility(); - const merged = { ...defaults, ...parsed }; - - // For non-admin users, force-hide admin-only columns (does not touch admin settings) - if (!isAdminUser) { - merged[COLUMN_KEYS.CHANNEL] = false; - merged[COLUMN_KEYS.USERNAME] = false; - merged[COLUMN_KEYS.RETRY] = false; - } - setVisibleColumns(merged); - } catch (e) { - console.error('Failed to parse saved column preferences', e); - initDefaultColumns(); - } - } else { - initDefaultColumns(); - } - }, []); - // Get default column visibility based on user role const getDefaultColumnVisibility = () => { return { @@ -166,6 +125,58 @@ export const useLogsData = () => { }; }; + const getInitialVisibleColumns = () => { + const defaults = getDefaultColumnVisibility(); + const savedColumns = localStorage.getItem(STORAGE_KEY); + + if (!savedColumns) { + return defaults; + } + + try { + const parsed = JSON.parse(savedColumns); + const merged = { ...defaults, ...parsed }; + + if (!isAdminUser) { + merged[COLUMN_KEYS.CHANNEL] = false; + merged[COLUMN_KEYS.USERNAME] = false; + merged[COLUMN_KEYS.RETRY] = false; + } + + return merged; + } catch (e) { + console.error('Failed to parse saved column preferences', e); + return defaults; + } + }; + + const getInitialBillingDisplayMode = () => { + const savedMode = localStorage.getItem(BILLING_DISPLAY_MODE_STORAGE_KEY); + return savedMode === 'price' || savedMode === 'ratio' ? savedMode : 'price'; + }; + + // Column visibility state + const [visibleColumns, setVisibleColumns] = useState(getInitialVisibleColumns); + const [showColumnSelector, setShowColumnSelector] = useState(false); + const [billingDisplayMode, setBillingDisplayMode] = useState( + getInitialBillingDisplayMode, + ); + + // Compact mode + const [compactMode, setCompactMode] = useTableCompactMode('logs'); + + // User info modal state + const [showUserInfo, setShowUserInfoModal] = useState(false); + const [userInfoData, setUserInfoData] = useState(null); + + // Channel affinity usage cache stats modal state (admin only) + const [ + showChannelAffinityUsageCacheModal, + setShowChannelAffinityUsageCacheModal, + ] = useState(false); + const [channelAffinityUsageCacheTarget, setChannelAffinityUsageCacheTarget] = + useState(null); + // Initialize default column visibility const initDefaultColumns = () => { const defaults = getDefaultColumnVisibility(); @@ -207,6 +218,10 @@ export const useLogsData = () => { } }, [visibleColumns]); + useEffect(() => { + localStorage.setItem(BILLING_DISPLAY_MODE_STORAGE_KEY, billingDisplayMode); + }, [BILLING_DISPLAY_MODE_STORAGE_KEY, billingDisplayMode]); + // 获取表单值的辅助函数,确保所有值都是字符串 const getFormValues = () => { const formValues = formApi ? formApi.getValues() : {}; @@ -406,6 +421,7 @@ export const useLogsData = () => { other.cache_creation_ratio_1h || other.cache_creation_ratio || 1.0, + billingDisplayMode, ) : renderLogContent( other?.model_ratio, @@ -420,6 +436,7 @@ export const useLogsData = () => { other.web_search_call_count || 0, other.file_search || false, other.file_search_call_count || 0, + billingDisplayMode, ), }); if (logs[i]?.content) { @@ -473,6 +490,7 @@ export const useLogsData = () => { other?.user_group_ratio, other?.cache_tokens || 0, other?.cache_ratio || 1.0, + billingDisplayMode, ); } else if (other?.claude) { content = renderClaudeModelPrice( @@ -495,6 +513,7 @@ export const useLogsData = () => { other.cache_creation_ratio_1h || other.cache_creation_ratio || 1.0, + billingDisplayMode, ); } else { content = renderModelPrice( @@ -521,6 +540,7 @@ export const useLogsData = () => { other?.audio_input_price || 0, other?.image_generation_call || false, other?.image_generation_call_price || 0, + billingDisplayMode, ); } expandDataLocal.push({ @@ -764,6 +784,8 @@ export const useLogsData = () => { visibleColumns, showColumnSelector, setShowColumnSelector, + billingDisplayMode, + setBillingDisplayMode, handleColumnVisibilityChange, handleSelectAll, initDefaultColumns, diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index a4d1060b8..8b3295ff2 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -513,6 +513,8 @@ "倍率信息": "Ratio information", "倍率是为了方便换算不同价格的模型": "The magnification is to facilitate the conversion of models with different prices.", "倍率模式": "Ratio Mode", + "计费显示模式": "Billing Display Mode", + "价格模式(默认)": "Price Mode (Default)", "倍率类型": "Ratio type", "偏好设置": "Preferences", "停止测试": "Stop Testing", @@ -909,6 +911,9 @@ "图片生成调用:{{symbol}}{{price}} / 1次": "Image generation call: {{symbol}}{{price}} / 1 time", "图片输入: {{imageRatio}}": "Image input: {{imageRatio}}", "图片输入价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (图片倍率: {{imageRatio}})": "Image input price: {{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (Image ratio: {{imageRatio}})", + "图片输入价格:{{symbol}}{{price}} / 1M tokens": "Image input price: {{symbol}}{{price}} / 1M tokens", + "图片输入价格 {{symbol}}{{price}} / 1M tokens": "Image input price {{symbol}}{{price}} / 1M tokens", + "图片输入 {{price}}": "Image input {{price}}", "图片输入倍率(仅部分模型支持该计费)": "Image input ratio (only supported by some models for billing)", "图片输入相关的倍率设置,键为模型名称,值为倍率,仅部分模型支持该计费": "Ratio settings related to image input, key is model name, value is ratio, only supported by some models for billing", "图生文": "Describe", @@ -1399,6 +1404,7 @@ "按倍率类型筛选": "Filter by ratio type", "按倍率设置": "Set by ratio", "按次": "Per request", + "按次 {{price}} / 次": "Per request {{price}} / request", "按次计费": "Pay per request", "按照如下格式输入:AccessKey|SecretAccessKey|Region": "Enter in the format: AccessKey|SecretAccessKey|Region", "按量计费": "Pay as you go", @@ -1787,7 +1793,10 @@ "模型专用区域": "Model-specific area", "模型价格": "Model price", "模型价格 {{symbol}}{{price}},{{ratioType}} {{ratio}}": "Model price {{symbol}}{{price}}, {{ratioType}} {{ratio}}", + "模型价格 {{symbol}}{{price}} / 次": "Model price {{symbol}}{{price}} / request", "模型价格:{{symbol}}{{price}} * {{ratioType}}:{{ratio}} = {{symbol}}{{total}}": "Model price: {{symbol}}{{price}} * {{ratioType}}: {{ratio}} = {{symbol}}{{total}}", + "模型价格:{{symbol}}{{price}} / 次": "Model price: {{symbol}}{{price}} / request", + "输入 {{price}} / 1M tokens": "Input {{price}} / 1M tokens", "模型倍率": "Model ratio", "模型倍率 {{modelRatio}}": "Model ratio {{modelRatio}}", "模型倍率 {{modelRatio}},缓存倍率 {{cacheRatio}},输出倍率 {{completionRatio}},{{ratioType}} {{ratio}}": "Model ratio {{modelRatio}}, cache ratio {{cacheRatio}}, completion ratio {{completionRatio}}, {{ratioType}} {{ratio}}", @@ -2338,11 +2347,16 @@ "统计次数": "Statistical count", "统计额度": "Statistical quota", "继续": "Continue", + "缓存 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}}": "Cache {{tokens}} tokens / 1M tokens * {{symbol}}{{price}}", "缓存 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}} (倍率: {{ratio}})": "Cache {{tokens}} tokens / 1M tokens * {{symbol}}{{price}} (ratio: {{ratio}})", "缓存 Tokens": "Cache Tokens", "缓存: {{cacheRatio}}": "Cache: {{cacheRatio}}", "缓存价格:{{symbol}}{{price}} * {{cacheRatio}} = {{symbol}}{{total}} / 1M tokens (缓存倍率: {{cacheRatio}})": "Cache price: {{symbol}}{{price}} * {{cacheRatio}} = {{symbol}}{{total}} / 1M tokens (Cache ratio: {{cacheRatio}})", "缓存价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存倍率: {{cacheRatio}})": "Cache price: {{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (Cache ratio: {{cacheRatio}})", + "缓存读取价格:{{symbol}}{{price}} / 1M tokens": "Cache read price: {{symbol}}{{price}} / 1M tokens", + "缓存读取价格 {{symbol}}{{price}} / 1M tokens": "Cache read price {{symbol}}{{price}} / 1M tokens", + "缓存读取 {{price}}": "Cache read {{price}}", + "缓存读取 {{price}}": "Cache read {{price}}", "缓存倍率": "Cache ratio", "缓存倍率 {{cacheRatio}}": "Cache ratio {{cacheRatio}}", "缓存写": "Cache Write", @@ -2352,8 +2366,20 @@ "缓存创建: 1h {{cacheCreationRatio1h}}": "Cache creation: 1h {{cacheCreationRatio1h}}", "缓存创建: 5m {{cacheCreationRatio5m}}": "Cache creation: 5m {{cacheCreationRatio5m}}", "缓存创建: 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}": "Cache creation: 5m {{cacheCreationRatio5m}} / 1h {{cacheCreationRatio1h}}", + "缓存创建 {{price}}": "Cache creation {{price}}", + "5m缓存创建 {{price}}": "5m cache creation {{price}}", + "1h缓存创建 {{price}}": "1h cache creation {{price}}", + "缓存创建 {{price}}": "Cache creation {{price}}", + "5m缓存创建 {{price}}": "5m cache creation {{price}}", + "1h缓存创建 {{price}}": "1h cache creation {{price}}", "缓存创建价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (缓存创建倍率: {{cacheCreationRatio}})": "Cache creation price: {{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens (Cache creation ratio: {{cacheCreationRatio}})", + "缓存创建价格:{{symbol}}{{price}} / 1M tokens": "Cache creation price: {{symbol}}{{price}} / 1M tokens", + "缓存创建价格 {{symbol}}{{price}} / 1M tokens": "Cache creation price {{symbol}}{{price}} / 1M tokens", "缓存创建价格合计:5m {{symbol}}{{five}} + 1h {{symbol}}{{one}} = {{symbol}}{{total}} / 1M tokens": "Cache creation price total: 5m {{symbol}}{{five}} + 1h {{symbol}}{{one}} = {{symbol}}{{total}} / 1M tokens", + "5m缓存创建价格:{{symbol}}{{price}} / 1M tokens": "5m cache creation price: {{symbol}}{{price}} / 1M tokens", + "5m缓存创建价格 {{symbol}}{{price}} / 1M tokens": "5m cache creation price {{symbol}}{{price}} / 1M tokens", + "1h缓存创建价格:{{symbol}}{{price}} / 1M tokens": "1h cache creation price: {{symbol}}{{price}} / 1M tokens", + "1h缓存创建价格 {{symbol}}{{price}} / 1M tokens": "1h cache creation price {{symbol}}{{price}} / 1M tokens", "缓存创建倍率": "Cache creation ratio", "缓存创建倍率 {{cacheCreationRatio}}": "Cache creation ratio {{cacheCreationRatio}}", "缓存创建倍率 1h {{cacheCreationRatio1h}}": "Cache creation multiplier 1h {{cacheCreationRatio1h}}", @@ -2470,8 +2496,16 @@ "获得": "Received", "补全": "Completion", "补全 {{completion}} tokens / 1M tokens * {{symbol}}{{price}}": "Completion {{completion}} tokens / 1M tokens * {{symbol}}{{price}}", + "模型价格 {{symbol}}{{price}} / 次 * {{ratioType}} {{ratio}} = {{symbol}}{{total}}": "Model price {{symbol}}{{price}} / request * {{ratioType}} {{ratio}} = {{symbol}}{{total}}", + "输入 {{input}} tokens / 1M tokens * {{symbol}}{{price}}": "Input {{input}} tokens / 1M tokens * {{symbol}}{{price}}", + "图片输入 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}}": "Image input {{tokens}} tokens / 1M tokens * {{symbol}}{{price}}", + "Web 搜索 {{count}} 次 * {{symbol}}{{price}} / 1K 次": "Web search {{count}} calls * {{symbol}}{{price}} / 1K calls", + "文件搜索 {{count}} 次 * {{symbol}}{{price}} / 1K 次": "File search {{count}} calls * {{symbol}}{{price}} / 1K calls", + "文字价格 {{textPrice}} + 音频价格 {{audioPrice}} * {{ratioType}} {{ratio}} = {{symbol}}{{total}}": "Text price {{textPrice}} + Audio price {{audioPrice}} * {{ratioType}} {{ratio}} = {{symbol}}{{total}}", + "输入与缓存价格合计 * {{ratioType}} {{ratio}} = {{symbol}}{{total}}": "Input and cache pricing subtotal * {{ratioType}} {{ratio}} = {{symbol}}{{total}}", "补全价格:{{symbol}}{{price}} * {{completionRatio}} = {{symbol}}{{total}} / 1M tokens (补全倍率: {{completionRatio}})": "Completion price: {{symbol}}{{price}} * {{completionRatio}} = {{symbol}}{{total}} / 1M tokens (Completion ratio: {{completionRatio}})", "补全价格:{{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens": "Completion price: {{symbol}}{{price}} * {{ratio}} = {{symbol}}{{total}} / 1M tokens", + "补全价格 {{symbol}}{{price}} / 1M tokens": "Completion price {{symbol}}{{price}} / 1M tokens", "补全倍率": "Completion ratio", "补全倍率值": "Completion Ratio Value", "补单": "Complete Order", @@ -2854,6 +2888,7 @@ "输入JSON对象": "Enter JSON Object", "输入价格": "Input Price", "输入价格:{{symbol}}{{price}} / 1M tokens{{audioPrice}}": "Input Price: {{symbol}}{{price}} / 1M tokens{{audioPrice}}", + "输入价格 {{symbol}}{{price}} / 1M tokens": "Input Price {{symbol}}{{price}} / 1M tokens", "输入你注册的 LinuxDO OAuth APP 的 ID": "Enter the ID of your registered LinuxDO OAuth APP", "输入你的账户名{{username}}以确认删除": "Enter your account name{{username}} to confirm deletion", "输入域名后回车": "Enter domain and press Enter", @@ -3228,9 +3263,14 @@ "缓存创建价格": "Input Cache Creation Price", "图片输入价格": "Image Input Price", "音频输入价格": "Audio Input Price", + "音频输入价格:{{symbol}}{{price}} / 1M tokens": "Audio input price: {{symbol}}{{price}} / 1M tokens", "音频补全价格": "Audio Completion Price", + "音频补全价格:{{symbol}}{{price}} / 1M tokens": "Audio completion price: {{symbol}}{{price}} / 1M tokens", "适合 MJ / 任务类等按次收费模型。": "Suitable for MJ and other task-based models billed per request.", "该模型补全倍率由后端固定为 {{ratio}}。补全价格不能在这里修改。": "This model's completion ratio is fixed to {{ratio}} by the backend. The completion price cannot be changed here.", + "Web 搜索调用 {{webSearchCallCount}} 次": "Web search called {{webSearchCallCount}} times", + "文件搜索调用 {{fileSearchCallCount}} 次": "File search called {{fileSearchCallCount}} times", + "实际结算金额:{{symbol}}{{total}}(已包含分组价格调整)": "Actual charge: {{symbol}}{{total}} (group pricing adjustment included)", "空": "Empty" } } diff --git a/web/src/i18n/locales/fr.json b/web/src/i18n/locales/fr.json index 6f2a6b4e6..c6202c83b 100644 --- a/web/src/i18n/locales/fr.json +++ b/web/src/i18n/locales/fr.json @@ -3200,6 +3200,27 @@ "音频补全价格": "Prix de complétion audio", "适合 MJ / 任务类等按次收费模型。": "Convient aux modèles MJ et autres modèles facturés à la requête.", "该模型补全倍率由后端固定为 {{ratio}}。补全价格不能在这里修改。": "Le ratio de complétion de ce modèle est fixé à {{ratio}} par le backend. Le prix de complétion ne peut pas être modifié ici.", + "计费显示模式": "Mode d'affichage de la facturation", + "价格模式(默认)": "Mode prix (par défaut)", + "模型价格 {{symbol}}{{price}} / 次": "Prix du modèle {{symbol}}{{price}} / requête", + "模型价格:{{symbol}}{{price}} / 次": "Prix du modèle : {{symbol}}{{price}} / requête", + "实际结算金额:{{symbol}}{{total}}(已包含分组价格调整)": "Montant facturé réel : {{symbol}}{{total}} (ajustement tarifaire de groupe inclus)", + "缓存读取价格:{{symbol}}{{price}} / 1M tokens": "Prix de lecture du cache : {{symbol}}{{price}} / 1M tokens", + "缓存读取价格 {{symbol}}{{price}} / 1M tokens": "Prix de lecture du cache {{symbol}}{{price}} / 1M tokens", + "缓存创建价格:{{symbol}}{{price}} / 1M tokens": "Prix de création du cache : {{symbol}}{{price}} / 1M tokens", + "缓存创建价格 {{symbol}}{{price}} / 1M tokens": "Prix de création du cache {{symbol}}{{price}} / 1M tokens", + "5m缓存创建价格:{{symbol}}{{price}} / 1M tokens": "Prix de création du cache 5m : {{symbol}}{{price}} / 1M tokens", + "5m缓存创建价格 {{symbol}}{{price}} / 1M tokens": "Prix de création du cache 5m {{symbol}}{{price}} / 1M tokens", + "1h缓存创建价格:{{symbol}}{{price}} / 1M tokens": "Prix de création du cache 1h : {{symbol}}{{price}} / 1M tokens", + "1h缓存创建价格 {{symbol}}{{price}} / 1M tokens": "Prix de création du cache 1h {{symbol}}{{price}} / 1M tokens", + "图片输入价格:{{symbol}}{{price}} / 1M tokens": "Prix d'entrée image : {{symbol}}{{price}} / 1M tokens", + "图片输入价格 {{symbol}}{{price}} / 1M tokens": "Prix d'entrée image {{symbol}}{{price}} / 1M tokens", + "输入价格 {{symbol}}{{price}} / 1M tokens": "Prix d'entrée {{symbol}}{{price}} / 1M tokens", + "补全价格 {{symbol}}{{price}} / 1M tokens": "Prix de complétion {{symbol}}{{price}} / 1M tokens", + "音频输入价格:{{symbol}}{{price}} / 1M tokens": "Prix d'entrée audio : {{symbol}}{{price}} / 1M tokens", + "音频补全价格:{{symbol}}{{price}} / 1M tokens": "Prix de complétion audio : {{symbol}}{{price}} / 1M tokens", + "Web 搜索调用 {{webSearchCallCount}} 次": "Recherche Web appelée {{webSearchCallCount}} fois", + "文件搜索调用 {{fileSearchCallCount}} 次": "Recherche de fichier appelée {{fileSearchCallCount}} fois", "空": "Vide" } } diff --git a/web/src/i18n/locales/ja.json b/web/src/i18n/locales/ja.json index 5bdd764f6..9acc374dd 100644 --- a/web/src/i18n/locales/ja.json +++ b/web/src/i18n/locales/ja.json @@ -3181,6 +3181,27 @@ "音频补全价格": "音声補完価格", "适合 MJ / 任务类等按次收费模型。": "MJ やその他のリクエスト単位課金モデルに適しています。", "该模型补全倍率由后端固定为 {{ratio}}。补全价格不能在这里修改。": "このモデルの補完倍率はバックエンドで {{ratio}} に固定されています。ここでは補完価格を変更できません。", + "计费显示模式": "課金表示モード", + "价格模式(默认)": "価格モード(デフォルト)", + "模型价格 {{symbol}}{{price}} / 次": "モデル価格 {{symbol}}{{price}} / リクエスト", + "模型价格:{{symbol}}{{price}} / 次": "モデル価格:{{symbol}}{{price}} / リクエスト", + "实际结算金额:{{symbol}}{{total}}(已包含分组价格调整)": "実際の請求額:{{symbol}}{{total}}(グループ価格調整込み)", + "缓存读取价格:{{symbol}}{{price}} / 1M tokens": "キャッシュ読み取り価格:{{symbol}}{{price}} / 1M tokens", + "缓存读取价格 {{symbol}}{{price}} / 1M tokens": "キャッシュ読み取り価格 {{symbol}}{{price}} / 1M tokens", + "缓存创建价格:{{symbol}}{{price}} / 1M tokens": "キャッシュ作成価格:{{symbol}}{{price}} / 1M tokens", + "缓存创建价格 {{symbol}}{{price}} / 1M tokens": "キャッシュ作成価格 {{symbol}}{{price}} / 1M tokens", + "5m缓存创建价格:{{symbol}}{{price}} / 1M tokens": "5m キャッシュ作成価格:{{symbol}}{{price}} / 1M tokens", + "5m缓存创建价格 {{symbol}}{{price}} / 1M tokens": "5m キャッシュ作成価格 {{symbol}}{{price}} / 1M tokens", + "1h缓存创建价格:{{symbol}}{{price}} / 1M tokens": "1h キャッシュ作成価格:{{symbol}}{{price}} / 1M tokens", + "1h缓存创建价格 {{symbol}}{{price}} / 1M tokens": "1h キャッシュ作成価格 {{symbol}}{{price}} / 1M tokens", + "图片输入价格:{{symbol}}{{price}} / 1M tokens": "画像入力価格:{{symbol}}{{price}} / 1M tokens", + "图片输入价格 {{symbol}}{{price}} / 1M tokens": "画像入力価格 {{symbol}}{{price}} / 1M tokens", + "输入价格 {{symbol}}{{price}} / 1M tokens": "入力価格 {{symbol}}{{price}} / 1M tokens", + "补全价格 {{symbol}}{{price}} / 1M tokens": "補完価格 {{symbol}}{{price}} / 1M tokens", + "音频输入价格:{{symbol}}{{price}} / 1M tokens": "音声入力価格:{{symbol}}{{price}} / 1M tokens", + "音频补全价格:{{symbol}}{{price}} / 1M tokens": "音声補完価格:{{symbol}}{{price}} / 1M tokens", + "Web 搜索调用 {{webSearchCallCount}} 次": "Web 検索呼び出し {{webSearchCallCount}} 回", + "文件搜索调用 {{fileSearchCallCount}} 次": "ファイル検索呼び出し {{fileSearchCallCount}} 回", "空": "空" } } diff --git a/web/src/i18n/locales/ru.json b/web/src/i18n/locales/ru.json index f89fe0809..35a576963 100644 --- a/web/src/i18n/locales/ru.json +++ b/web/src/i18n/locales/ru.json @@ -3214,6 +3214,27 @@ "音频补全价格": "Цена завершения аудио", "适合 MJ / 任务类等按次收费模型。": "Подходит для MJ и других моделей с тарификацией за запрос.", "该模型补全倍率由后端固定为 {{ratio}}。补全价格不能在这里修改。": "Коэффициент завершения для этой модели зафиксирован на уровне {{ratio}} на бэкенде. Цену завершения нельзя изменить здесь.", + "计费显示模式": "Режим отображения тарификации", + "价格模式(默认)": "Режим цен (по умолчанию)", + "模型价格 {{symbol}}{{price}} / 次": "Цена модели {{symbol}}{{price}} / запрос", + "模型价格:{{symbol}}{{price}} / 次": "Цена модели: {{symbol}}{{price}} / запрос", + "实际结算金额:{{symbol}}{{total}}(已包含分组价格调整)": "Фактическое списание: {{symbol}}{{total}} (включая групповую ценовую корректировку)", + "缓存读取价格:{{symbol}}{{price}} / 1M tokens": "Цена чтения кеша: {{symbol}}{{price}} / 1M tokens", + "缓存读取价格 {{symbol}}{{price}} / 1M tokens": "Цена чтения кеша {{symbol}}{{price}} / 1M tokens", + "缓存创建价格:{{symbol}}{{price}} / 1M tokens": "Цена создания кеша: {{symbol}}{{price}} / 1M tokens", + "缓存创建价格 {{symbol}}{{price}} / 1M tokens": "Цена создания кеша {{symbol}}{{price}} / 1M tokens", + "5m缓存创建价格:{{symbol}}{{price}} / 1M tokens": "Цена создания кеша 5m: {{symbol}}{{price}} / 1M tokens", + "5m缓存创建价格 {{symbol}}{{price}} / 1M tokens": "Цена создания кеша 5m {{symbol}}{{price}} / 1M tokens", + "1h缓存创建价格:{{symbol}}{{price}} / 1M tokens": "Цена создания кеша 1h: {{symbol}}{{price}} / 1M tokens", + "1h缓存创建价格 {{symbol}}{{price}} / 1M tokens": "Цена создания кеша 1h {{symbol}}{{price}} / 1M tokens", + "图片输入价格:{{symbol}}{{price}} / 1M tokens": "Цена входного изображения: {{symbol}}{{price}} / 1M tokens", + "图片输入价格 {{symbol}}{{price}} / 1M tokens": "Цена входного изображения {{symbol}}{{price}} / 1M tokens", + "输入价格 {{symbol}}{{price}} / 1M tokens": "Цена ввода {{symbol}}{{price}} / 1M tokens", + "补全价格 {{symbol}}{{price}} / 1M tokens": "Цена завершения {{symbol}}{{price}} / 1M tokens", + "音频输入价格:{{symbol}}{{price}} / 1M tokens": "Цена входного аудио: {{symbol}}{{price}} / 1M tokens", + "音频补全价格:{{symbol}}{{price}} / 1M tokens": "Цена завершения аудио: {{symbol}}{{price}} / 1M tokens", + "Web 搜索调用 {{webSearchCallCount}} 次": "Web-поиск вызван {{webSearchCallCount}} раз", + "文件搜索调用 {{fileSearchCallCount}} 次": "Поиск файлов вызван {{fileSearchCallCount}} раз", "空": "Пусто" } } diff --git a/web/src/i18n/locales/vi.json b/web/src/i18n/locales/vi.json index 34d4a4c8f..ad18ae10a 100644 --- a/web/src/i18n/locales/vi.json +++ b/web/src/i18n/locales/vi.json @@ -3753,6 +3753,27 @@ "音频补全价格": "Giá hoàn thành âm thanh", "适合 MJ / 任务类等按次收费模型。": "Phù hợp cho MJ và các mô hình tính phí theo lượt gọi tương tự.", "该模型补全倍率由后端固定为 {{ratio}}。补全价格不能在这里修改。": "Tỷ lệ hoàn thành của mô hình này được backend cố định ở {{ratio}}. Không thể chỉnh giá hoàn thành tại đây.", + "计费显示模式": "Chế độ hiển thị tính phí", + "价格模式(默认)": "Chế độ giá (mặc định)", + "模型价格 {{symbol}}{{price}} / 次": "Giá mô hình {{symbol}}{{price}} / lượt gọi", + "模型价格:{{symbol}}{{price}} / 次": "Giá mô hình: {{symbol}}{{price}} / lượt gọi", + "实际结算金额:{{symbol}}{{total}}(已包含分组价格调整)": "Khoản phí thực tế: {{symbol}}{{total}} (đã bao gồm điều chỉnh giá theo nhóm)", + "缓存读取价格:{{symbol}}{{price}} / 1M tokens": "Giá đọc bộ nhớ đệm: {{symbol}}{{price}} / 1M tokens", + "缓存读取价格 {{symbol}}{{price}} / 1M tokens": "Giá đọc bộ nhớ đệm {{symbol}}{{price}} / 1M tokens", + "缓存创建价格:{{symbol}}{{price}} / 1M tokens": "Giá tạo bộ nhớ đệm: {{symbol}}{{price}} / 1M tokens", + "缓存创建价格 {{symbol}}{{price}} / 1M tokens": "Giá tạo bộ nhớ đệm {{symbol}}{{price}} / 1M tokens", + "5m缓存创建价格:{{symbol}}{{price}} / 1M tokens": "Giá tạo bộ nhớ đệm 5m: {{symbol}}{{price}} / 1M tokens", + "5m缓存创建价格 {{symbol}}{{price}} / 1M tokens": "Giá tạo bộ nhớ đệm 5m {{symbol}}{{price}} / 1M tokens", + "1h缓存创建价格:{{symbol}}{{price}} / 1M tokens": "Giá tạo bộ nhớ đệm 1h: {{symbol}}{{price}} / 1M tokens", + "1h缓存创建价格 {{symbol}}{{price}} / 1M tokens": "Giá tạo bộ nhớ đệm 1h {{symbol}}{{price}} / 1M tokens", + "图片输入价格:{{symbol}}{{price}} / 1M tokens": "Giá đầu vào hình ảnh: {{symbol}}{{price}} / 1M tokens", + "图片输入价格 {{symbol}}{{price}} / 1M tokens": "Giá đầu vào hình ảnh {{symbol}}{{price}} / 1M tokens", + "输入价格 {{symbol}}{{price}} / 1M tokens": "Giá đầu vào {{symbol}}{{price}} / 1M tokens", + "补全价格 {{symbol}}{{price}} / 1M tokens": "Giá hoàn thành {{symbol}}{{price}} / 1M tokens", + "音频输入价格:{{symbol}}{{price}} / 1M tokens": "Giá đầu vào âm thanh: {{symbol}}{{price}} / 1M tokens", + "音频补全价格:{{symbol}}{{price}} / 1M tokens": "Giá hoàn thành âm thanh: {{symbol}}{{price}} / 1M tokens", + "Web 搜索调用 {{webSearchCallCount}} 次": "Đã gọi tìm kiếm Web {{webSearchCallCount}} lần", + "文件搜索调用 {{fileSearchCallCount}} 次": "Đã gọi tìm kiếm tệp {{fileSearchCallCount}} lần", "空": "Trống" } } diff --git a/web/src/i18n/locales/zh-CN.json b/web/src/i18n/locales/zh-CN.json index a7af51e19..931e38a72 100644 --- a/web/src/i18n/locales/zh-CN.json +++ b/web/src/i18n/locales/zh-CN.json @@ -1104,6 +1104,7 @@ "按倍率类型筛选": "按倍率类型筛选", "按倍率设置": "按倍率设置", "按次": "按次", + "按次 {{price}} / 次": "按次 {{price}} / 次", "按次计费": "按次计费", "按照如下格式输入:AccessKey|SecretAccessKey|Region": "按照如下格式输入:AccessKey|SecretAccessKey|Region", "按量计费": "按量计费", @@ -2858,6 +2859,33 @@ "音频补全价格": "音频补全价格", "适合 MJ / 任务类等按次收费模型。": "适合 MJ / 任务类等按次收费模型。", "该模型补全倍率由后端固定为 {{ratio}}。补全价格不能在这里修改。": "该模型补全倍率由后端固定为 {{ratio}}。补全价格不能在这里修改。", + "计费显示模式": "计费显示模式", + "价格模式(默认)": "价格模式(默认)", + "模型价格 {{symbol}}{{price}} / 次": "模型价格 {{symbol}}{{price}} / 次", + "模型价格:{{symbol}}{{price}} / 次": "模型价格:{{symbol}}{{price}} / 次", + "输入 {{price}} / 1M tokens": "输入 {{price}} / 1M tokens", + "实际结算金额:{{symbol}}{{total}}(已包含分组价格调整)": "实际结算金额:{{symbol}}{{total}}(已包含分组价格调整)", + "缓存读取价格:{{symbol}}{{price}} / 1M tokens": "缓存读取价格:{{symbol}}{{price}} / 1M tokens", + "缓存读取价格 {{symbol}}{{price}} / 1M tokens": "缓存读取价格 {{symbol}}{{price}} / 1M tokens", + "缓存读取 {{price}}": "缓存读取 {{price}}", + "缓存创建价格:{{symbol}}{{price}} / 1M tokens": "缓存创建价格:{{symbol}}{{price}} / 1M tokens", + "缓存创建价格 {{symbol}}{{price}} / 1M tokens": "缓存创建价格 {{symbol}}{{price}} / 1M tokens", + "缓存创建 {{price}}": "缓存创建 {{price}}", + "5m缓存创建价格:{{symbol}}{{price}} / 1M tokens": "5m缓存创建价格:{{symbol}}{{price}} / 1M tokens", + "5m缓存创建价格 {{symbol}}{{price}} / 1M tokens": "5m缓存创建价格 {{symbol}}{{price}} / 1M tokens", + "5m缓存创建 {{price}}": "5m缓存创建 {{price}}", + "1h缓存创建价格:{{symbol}}{{price}} / 1M tokens": "1h缓存创建价格:{{symbol}}{{price}} / 1M tokens", + "1h缓存创建价格 {{symbol}}{{price}} / 1M tokens": "1h缓存创建价格 {{symbol}}{{price}} / 1M tokens", + "1h缓存创建 {{price}}": "1h缓存创建 {{price}}", + "图片输入价格:{{symbol}}{{price}} / 1M tokens": "图片输入价格:{{symbol}}{{price}} / 1M tokens", + "图片输入价格 {{symbol}}{{price}} / 1M tokens": "图片输入价格 {{symbol}}{{price}} / 1M tokens", + "图片输入 {{price}}": "图片输入 {{price}}", + "输入价格 {{symbol}}{{price}} / 1M tokens": "输入价格 {{symbol}}{{price}} / 1M tokens", + "补全价格 {{symbol}}{{price}} / 1M tokens": "补全价格 {{symbol}}{{price}} / 1M tokens", + "音频输入价格:{{symbol}}{{price}} / 1M tokens": "音频输入价格:{{symbol}}{{price}} / 1M tokens", + "音频补全价格:{{symbol}}{{price}} / 1M tokens": "音频补全价格:{{symbol}}{{price}} / 1M tokens", + "Web 搜索调用 {{webSearchCallCount}} 次": "Web 搜索调用 {{webSearchCallCount}} 次", + "文件搜索调用 {{fileSearchCallCount}} 次": "文件搜索调用 {{fileSearchCallCount}} 次", "空": "空" } } diff --git a/web/src/i18n/locales/zh-TW.json b/web/src/i18n/locales/zh-TW.json index 5567ea8f1..8703b0fca 100644 --- a/web/src/i18n/locales/zh-TW.json +++ b/web/src/i18n/locales/zh-TW.json @@ -1107,6 +1107,7 @@ "按倍率类型筛选": "按倍率類型篩選", "按倍率设置": "按倍率設定", "按次": "按次", + "按次 {{price}} / 次": "按次 {{price}} / 次", "按次计费": "按次計費", "按照如下格式输入:AccessKey|SecretAccessKey|Region": "按照如下格式輸入:AccessKey|SecretAccessKey|Region", "按量计费": "按量計費", @@ -2851,6 +2852,33 @@ "音频补全价格": "音訊補全價格", "适合 MJ / 任务类等按次收费模型。": "適合 MJ / 任務類等按次收費模型。", "该模型补全倍率由后端固定为 {{ratio}}。补全价格不能在这里修改。": "該模型補全倍率由後端固定為 {{ratio}}。補全價格不能在這裡修改。", + "计费显示模式": "計費顯示模式", + "价格模式(默认)": "價格模式(預設)", + "模型价格 {{symbol}}{{price}} / 次": "模型價格 {{symbol}}{{price}} / 次", + "模型价格:{{symbol}}{{price}} / 次": "模型價格:{{symbol}}{{price}} / 次", + "输入 {{price}} / 1M tokens": "輸入 {{price}} / 1M tokens", + "实际结算金额:{{symbol}}{{total}}(已包含分组价格调整)": "實際結算金額:{{symbol}}{{total}}(已包含分組價格調整)", + "缓存读取价格:{{symbol}}{{price}} / 1M tokens": "快取讀取價格:{{symbol}}{{price}} / 1M tokens", + "缓存读取价格 {{symbol}}{{price}} / 1M tokens": "快取讀取價格 {{symbol}}{{price}} / 1M tokens", + "缓存读取 {{price}}": "快取讀取 {{price}}", + "缓存创建价格:{{symbol}}{{price}} / 1M tokens": "快取建立價格:{{symbol}}{{price}} / 1M tokens", + "缓存创建价格 {{symbol}}{{price}} / 1M tokens": "快取建立價格 {{symbol}}{{price}} / 1M tokens", + "缓存创建 {{price}}": "快取建立 {{price}}", + "5m缓存创建价格:{{symbol}}{{price}} / 1M tokens": "5m快取建立價格:{{symbol}}{{price}} / 1M tokens", + "5m缓存创建价格 {{symbol}}{{price}} / 1M tokens": "5m快取建立價格 {{symbol}}{{price}} / 1M tokens", + "5m缓存创建 {{price}}": "5m快取建立 {{price}}", + "1h缓存创建价格:{{symbol}}{{price}} / 1M tokens": "1h快取建立價格:{{symbol}}{{price}} / 1M tokens", + "1h缓存创建价格 {{symbol}}{{price}} / 1M tokens": "1h快取建立價格 {{symbol}}{{price}} / 1M tokens", + "1h缓存创建 {{price}}": "1h快取建立 {{price}}", + "图片输入价格:{{symbol}}{{price}} / 1M tokens": "圖片輸入價格:{{symbol}}{{price}} / 1M tokens", + "图片输入价格 {{symbol}}{{price}} / 1M tokens": "圖片輸入價格 {{symbol}}{{price}} / 1M tokens", + "图片输入 {{price}}": "圖片輸入 {{price}}", + "输入价格 {{symbol}}{{price}} / 1M tokens": "輸入價格 {{symbol}}{{price}} / 1M tokens", + "补全价格 {{symbol}}{{price}} / 1M tokens": "補全價格 {{symbol}}{{price}} / 1M tokens", + "音频输入价格:{{symbol}}{{price}} / 1M tokens": "音訊輸入價格:{{symbol}}{{price}} / 1M tokens", + "音频补全价格:{{symbol}}{{price}} / 1M tokens": "音訊補全價格:{{symbol}}{{price}} / 1M tokens", + "Web 搜索调用 {{webSearchCallCount}} 次": "Web 搜尋呼叫 {{webSearchCallCount}} 次", + "文件搜索调用 {{fileSearchCallCount}} 次": "檔案搜尋呼叫 {{fileSearchCallCount}} 次", "空": "空" } }