|
@@ -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>
|
|
|
) : (
|
|
) : (
|
|
|
<></>
|
|
<></>
|
|
|
);
|
|
);
|