Browse Source

feat: logs cache field (#2920)

* feat: logs cache field

* feat: logs cache field

* feat: logs cache field
Seefs 1 ngày trước cách đây
mục cha
commit
c01bbd006a

+ 85 - 2
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 = ({
 export const getLogsColumns = ({
   t,
   t,
   COLUMN_KEYS,
   COLUMN_KEYS,
@@ -524,11 +562,56 @@ export const getLogsColumns = ({
     },
     },
     {
     {
       key: COLUMN_KEYS.PROMPT,
       key: COLUMN_KEYS.PROMPT,
-      title: t('输入'),
+      title: (
+        <div className='flex items-center gap-1'>
+          {t('输入')}
+          <Tooltip
+            content={t(
+              '根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。',
+            )}
+          >
+            <IconHelpCircle className='text-gray-400 cursor-help' />
+          </Tooltip>
+        </div>
+      ),
       dataIndex: 'prompt_tokens',
       dataIndex: 'prompt_tokens',
       render: (text, record, index) => {
       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 ? (
         return record.type === 0 || record.type === 2 || record.type === 5 ? (
-          <>{<span> {text} </span>}</>
+          <div
+            style={{
+              display: 'inline-flex',
+              flexDirection: 'column',
+              alignItems: 'flex-start',
+              lineHeight: 1.2,
+            }}
+          >
+            <span>{text}</span>
+            {cacheText ? (
+              <span
+                style={{
+                  marginTop: 2,
+                  fontSize: 11,
+                  color: 'var(--semi-color-text-2)',
+                  whiteSpace: 'nowrap',
+                }}
+              >
+                {cacheText}
+              </span>
+            ) : null}
+          </div>
         ) : (
         ) : (
           <></>
           <></>
         );
         );

+ 5 - 1
web/src/i18n/locales/en.json

@@ -2845,6 +2845,10 @@
     "填写服务器地址后自动生成:": "Auto-generated after entering server address: ",
     "填写服务器地址后自动生成:": "Auto-generated after entering server address: ",
     "自动生成:": "Auto-generated: ",
     "自动生成:": "Auto-generated: ",
     "请先填写服务器地址,以自动生成完整的端点 URL": "Please enter the server address first to auto-generate full endpoint URLs",
     "请先填写服务器地址,以自动生成完整的端点 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."
   }
   }
 }
 }

+ 5 - 1
web/src/i18n/locales/fr.json

@@ -2719,6 +2719,10 @@
     "套餐名称": "Nom du plan",
     "套餐名称": "Nom du plan",
     "应付金额": "Montant à payer",
     "应付金额": "Montant à payer",
     "支付": "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."
   }
   }
 }
 }

+ 5 - 1
web/src/i18n/locales/ja.json

@@ -2702,6 +2702,10 @@
     "套餐名称": "プラン名",
     "套餐名称": "プラン名",
     "应付金额": "支払金額",
     "应付金额": "支払金額",
     "支付": "支払う",
     "支付": "支払う",
-    "管理员未开启在线支付功能,请联系管理员配置。": "管理者がオンライン決済を有効にしていません。管理者に連絡してください。"
+    "管理员未开启在线支付功能,请联系管理员配置。": "管理者がオンライン決済を有効にしていません。管理者に連絡してください。",
+    "缓存读": "キャッシュ読取",
+    "缓存写": "キャッシュ書込",
+    "写": "書込",
+    "根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。": "Anthropic の仕様により、/v1/messages の入力 tokens は非キャッシュ入力のみを集計し、キャッシュ読み取り/書き込み tokens は含みません。"
   }
   }
 }
 }

+ 5 - 1
web/src/i18n/locales/ru.json

@@ -2732,6 +2732,10 @@
     "套餐名称": "Название плана",
     "套餐名称": "Название плана",
     "应付金额": "К оплате",
     "应付金额": "К оплате",
     "支付": "Оплатить",
     "支付": "Оплатить",
-    "管理员未开启在线支付功能,请联系管理员配置。": "Онлайн-оплата не включена администратором. Пожалуйста, свяжитесь с администратором."
+    "管理员未开启在线支付功能,请联系管理员配置。": "Онлайн-оплата не включена администратором. Пожалуйста, свяжитесь с администратором.",
+    "缓存读": "Чтение кэша",
+    "缓存写": "Запись в кэш",
+    "写": "Запись",
+    "根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。": "Согласно соглашению Anthropic, входные токены /v1/messages учитывают только некэшированный ввод и не включают токены чтения/записи кэша."
   }
   }
 }
 }

+ 5 - 1
web/src/i18n/locales/vi.json

@@ -3280,6 +3280,10 @@
     "套餐名称": "Tên gói",
     "套餐名称": "Tên gói",
     "应付金额": "Số tiền phải trả",
     "应付金额": "Số tiền phải trả",
     "支付": "Thanh toán",
     "支付": "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."
   }
   }
 }
 }

+ 5 - 1
web/src/i18n/locales/zh-CN.json

@@ -2790,6 +2790,10 @@
     "填写服务器地址后自动生成:": "填写服务器地址后自动生成:",
     "填写服务器地址后自动生成:": "填写服务器地址后自动生成:",
     "自动生成:": "自动生成:",
     "自动生成:": "自动生成:",
     "请先填写服务器地址,以自动生成完整的端点 URL": "请先填写服务器地址,以自动生成完整的端点 URL",
     "请先填写服务器地址,以自动生成完整的端点 URL": "请先填写服务器地址,以自动生成完整的端点 URL",
-    "端点 URL 必须是完整地址(以 http:// 或 https:// 开头)": "端点 URL 必须是完整地址(以 http:// 或 https:// 开头)"
+    "端点 URL 必须是完整地址(以 http:// 或 https:// 开头)": "端点 URL 必须是完整地址(以 http:// 或 https:// 开头)",
+    "缓存读": "缓存读",
+    "缓存写": "缓存写",
+    "写": "写",
+    "根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。": "根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。"
   }
   }
 }
 }