diff --git a/web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx b/web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx index cf2c1c96c..f0dcd379e 100644 --- a/web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx +++ b/web/src/components/table/usage-logs/UsageLogsColumnDefs.jsx @@ -286,6 +286,44 @@ function renderModelName(record, copyText, t) { } } +function toTokenNumber(value) { + const parsed = Number(value); + if (!Number.isFinite(parsed) || parsed <= 0) { + return 0; + } + return parsed; +} + +function formatTokenCount(value) { + return toTokenNumber(value).toLocaleString(); +} + +function getPromptCacheSummary(other) { + if (!other || typeof other !== 'object') { + return null; + } + + const cacheReadTokens = toTokenNumber(other.cache_tokens); + const cacheCreationTokens = toTokenNumber(other.cache_creation_tokens); + const cacheCreationTokens5m = toTokenNumber(other.cache_creation_tokens_5m); + const cacheCreationTokens1h = toTokenNumber(other.cache_creation_tokens_1h); + + const hasSplitCacheCreation = + cacheCreationTokens5m > 0 || cacheCreationTokens1h > 0; + const cacheWriteTokens = hasSplitCacheCreation + ? cacheCreationTokens5m + cacheCreationTokens1h + : cacheCreationTokens; + + if (cacheReadTokens <= 0 && cacheWriteTokens <= 0) { + return null; + } + + return { + cacheReadTokens, + cacheWriteTokens, + }; +} + export const getLogsColumns = ({ t, COLUMN_KEYS, @@ -524,11 +562,56 @@ export const getLogsColumns = ({ }, { key: COLUMN_KEYS.PROMPT, - title: t('输入'), + title: ( +
+ {t('输入')} + + + +
+ ), dataIndex: 'prompt_tokens', render: (text, record, index) => { + const other = getLogOther(record.other); + const cacheSummary = getPromptCacheSummary(other); + const hasCacheRead = (cacheSummary?.cacheReadTokens || 0) > 0; + const hasCacheWrite = (cacheSummary?.cacheWriteTokens || 0) > 0; + let cacheText = ''; + if (hasCacheRead && hasCacheWrite) { + cacheText = `${t('缓存读')} ${formatTokenCount(cacheSummary.cacheReadTokens)} · ${t('写')} ${formatTokenCount(cacheSummary.cacheWriteTokens)}`; + } else if (hasCacheRead) { + cacheText = `${t('缓存读')} ${formatTokenCount(cacheSummary.cacheReadTokens)}`; + } else if (hasCacheWrite) { + cacheText = `${t('缓存写')} ${formatTokenCount(cacheSummary.cacheWriteTokens)}`; + } + return record.type === 0 || record.type === 2 || record.type === 5 ? ( - <>{ {text} } +
+ {text} + {cacheText ? ( + + {cacheText} + + ) : null} +
) : ( <> ); diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index c49ec6925..abf021c5b 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -2845,6 +2845,10 @@ "填写服务器地址后自动生成:": "Auto-generated after entering server address: ", "自动生成:": "Auto-generated: ", "请先填写服务器地址,以自动生成完整的端点 URL": "Please enter the server address first to auto-generate full endpoint URLs", - "端点 URL 必须是完整地址(以 http:// 或 https:// 开头)": "Endpoint URL must be a full address (starting with http:// or https://)" + "端点 URL 必须是完整地址(以 http:// 或 https:// 开头)": "Endpoint URL must be a full address (starting with http:// or https://)", + "缓存读": "Cache Read", + "缓存写": "Cache Write", + "写": "Write", + "根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。": "Per Anthropic conventions, /v1/messages input tokens count only non-cached input and exclude cache read/write tokens." } } diff --git a/web/src/i18n/locales/fr.json b/web/src/i18n/locales/fr.json index bd1ac9520..54af7f138 100644 --- a/web/src/i18n/locales/fr.json +++ b/web/src/i18n/locales/fr.json @@ -2719,6 +2719,10 @@ "套餐名称": "Nom du plan", "应付金额": "Montant à payer", "支付": "Payer", - "管理员未开启在线支付功能,请联系管理员配置。": "Le paiement en ligne n'est pas activé par l'administrateur. Veuillez contacter l'administrateur." + "管理员未开启在线支付功能,请联系管理员配置。": "Le paiement en ligne n'est pas activé par l'administrateur. Veuillez contacter l'administrateur.", + "缓存读": "Lecture cache", + "缓存写": "Écriture cache", + "写": "Écriture", + "根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。": "Selon la convention Anthropic, les tokens d'entrée de /v1/messages ne comptent que les entrées non mises en cache et excluent les tokens de lecture/écriture du cache." } } diff --git a/web/src/i18n/locales/ja.json b/web/src/i18n/locales/ja.json index ec3c7da87..98e523413 100644 --- a/web/src/i18n/locales/ja.json +++ b/web/src/i18n/locales/ja.json @@ -2702,6 +2702,10 @@ "套餐名称": "プラン名", "应付金额": "支払金額", "支付": "支払う", - "管理员未开启在线支付功能,请联系管理员配置。": "管理者がオンライン決済を有効にしていません。管理者に連絡してください。" + "管理员未开启在线支付功能,请联系管理员配置。": "管理者がオンライン決済を有効にしていません。管理者に連絡してください。", + "缓存读": "キャッシュ読取", + "缓存写": "キャッシュ書込", + "写": "書込", + "根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。": "Anthropic の仕様により、/v1/messages の入力 tokens は非キャッシュ入力のみを集計し、キャッシュ読み取り/書き込み tokens は含みません。" } } diff --git a/web/src/i18n/locales/ru.json b/web/src/i18n/locales/ru.json index a9500b7d1..d145bb1de 100644 --- a/web/src/i18n/locales/ru.json +++ b/web/src/i18n/locales/ru.json @@ -2732,6 +2732,10 @@ "套餐名称": "Название плана", "应付金额": "К оплате", "支付": "Оплатить", - "管理员未开启在线支付功能,请联系管理员配置。": "Онлайн-оплата не включена администратором. Пожалуйста, свяжитесь с администратором." + "管理员未开启在线支付功能,请联系管理员配置。": "Онлайн-оплата не включена администратором. Пожалуйста, свяжитесь с администратором.", + "缓存读": "Чтение кэша", + "缓存写": "Запись в кэш", + "写": "Запись", + "根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。": "Согласно соглашению Anthropic, входные токены /v1/messages учитывают только некэшированный ввод и не включают токены чтения/записи кэша." } } diff --git a/web/src/i18n/locales/vi.json b/web/src/i18n/locales/vi.json index 4e211f9c5..8f0508096 100644 --- a/web/src/i18n/locales/vi.json +++ b/web/src/i18n/locales/vi.json @@ -3280,6 +3280,10 @@ "套餐名称": "Tên gói", "应付金额": "Số tiền phải trả", "支付": "Thanh toán", - "管理员未开启在线支付功能,请联系管理员配置。": "Quản trị viên chưa bật thanh toán trực tuyến, vui lòng liên hệ quản trị viên." + "管理员未开启在线支付功能,请联系管理员配置。": "Quản trị viên chưa bật thanh toán trực tuyến, vui lòng liên hệ quản trị viên.", + "缓存读": "Đọc bộ nhớ đệm", + "缓存写": "Ghi bộ nhớ đệm", + "写": "Ghi", + "根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。": "Theo quy ước của Anthropic, input tokens của /v1/messages chỉ tính phần đầu vào không dùng cache và không bao gồm tokens đọc/ghi cache." } } diff --git a/web/src/i18n/locales/zh-CN.json b/web/src/i18n/locales/zh-CN.json index 44c8787fd..038c5ac79 100644 --- a/web/src/i18n/locales/zh-CN.json +++ b/web/src/i18n/locales/zh-CN.json @@ -2790,6 +2790,10 @@ "填写服务器地址后自动生成:": "填写服务器地址后自动生成:", "自动生成:": "自动生成:", "请先填写服务器地址,以自动生成完整的端点 URL": "请先填写服务器地址,以自动生成完整的端点 URL", - "端点 URL 必须是完整地址(以 http:// 或 https:// 开头)": "端点 URL 必须是完整地址(以 http:// 或 https:// 开头)" + "端点 URL 必须是完整地址(以 http:// 或 https:// 开头)": "端点 URL 必须是完整地址(以 http:// 或 https:// 开头)", + "缓存读": "缓存读", + "缓存写": "缓存写", + "写": "写", + "根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。": "根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。" } }