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}} 次", "空": "空" } }