feat(api-potluck): 添加缓存 tokens 统计支持
- 在 Claude 转换器中增加缓存 tokens 字段提取 - 扩展 API 路由返回缓存 tokens 数据 - 在 key-manager 中新增缓存 tokens 统计逻辑 - 更新前端页面展示缓存 tokens 统计信息 - 统一所有相关模块的缓存 tokens 处理逻辑
This commit is contained in:
parent
51c0af2f15
commit
1a56e422c4
8 changed files with 73 additions and 22 deletions
|
|
@ -315,6 +315,10 @@ export class ClaudeConverter extends BaseConverter {
|
||||||
prompt_tokens: claudeResponse.usage?.input_tokens || 0,
|
prompt_tokens: claudeResponse.usage?.input_tokens || 0,
|
||||||
completion_tokens: claudeResponse.usage?.output_tokens || 0,
|
completion_tokens: claudeResponse.usage?.output_tokens || 0,
|
||||||
total_tokens: (claudeResponse.usage?.input_tokens || 0) + (claudeResponse.usage?.output_tokens || 0),
|
total_tokens: (claudeResponse.usage?.input_tokens || 0) + (claudeResponse.usage?.output_tokens || 0),
|
||||||
|
cached_tokens: claudeResponse.usage?.cache_read_input_tokens || 0,
|
||||||
|
prompt_tokens_details: {
|
||||||
|
cached_tokens: claudeResponse.usage?.cache_read_input_tokens || 0
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -423,13 +423,15 @@ export async function handlePotluckUserApiRoutes(method, path, req, res) {
|
||||||
resetDate: keyData.lastResetDate,
|
resetDate: keyData.lastResetDate,
|
||||||
promptTokens: keyData.todayPromptTokens || 0,
|
promptTokens: keyData.todayPromptTokens || 0,
|
||||||
completionTokens: keyData.todayCompletionTokens || 0,
|
completionTokens: keyData.todayCompletionTokens || 0,
|
||||||
totalTokens: keyData.todayTotalTokens || 0
|
totalTokens: keyData.todayTotalTokens || 0,
|
||||||
|
cachedTokens: keyData.todayCachedTokens || 0
|
||||||
},
|
},
|
||||||
total: keyData.totalUsage,
|
total: keyData.totalUsage,
|
||||||
tokens: {
|
tokens: {
|
||||||
prompt: keyData.totalPromptTokens || 0,
|
prompt: keyData.totalPromptTokens || 0,
|
||||||
completion: keyData.totalCompletionTokens || 0,
|
completion: keyData.totalCompletionTokens || 0,
|
||||||
total: keyData.totalTokens || 0
|
total: keyData.totalTokens || 0,
|
||||||
|
cached: keyData.totalCachedTokens || 0
|
||||||
},
|
},
|
||||||
lastUsedAt: keyData.lastUsedAt,
|
lastUsedAt: keyData.lastUsedAt,
|
||||||
createdAt: keyData.createdAt,
|
createdAt: keyData.createdAt,
|
||||||
|
|
|
||||||
|
|
@ -65,12 +65,20 @@ function normalizeUsageCandidate(candidate) {
|
||||||
candidate.total_tokens ??
|
candidate.total_tokens ??
|
||||||
usage?.total_tokens ??
|
usage?.total_tokens ??
|
||||||
usage?.totalTokenCount
|
usage?.totalTokenCount
|
||||||
) || promptTokens + completionTokens;
|
);
|
||||||
|
|
||||||
|
const cachedTokens = toNumber(
|
||||||
|
candidate.cached_tokens ??
|
||||||
|
usage?.cached_tokens ??
|
||||||
|
usage?.cache_read_input_tokens ??
|
||||||
|
usage?.cachedContentTokenCount
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
promptTokens,
|
promptTokens,
|
||||||
completionTokens,
|
completionTokens,
|
||||||
totalTokens
|
totalTokens: totalTokens || (promptTokens + completionTokens),
|
||||||
|
cachedTokens
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,7 +87,8 @@ function mergeUsage(baseUsage, nextUsage) {
|
||||||
return {
|
return {
|
||||||
promptTokens: Math.max(baseUsage.promptTokens, nextUsage.promptTokens),
|
promptTokens: Math.max(baseUsage.promptTokens, nextUsage.promptTokens),
|
||||||
completionTokens: Math.max(baseUsage.completionTokens, nextUsage.completionTokens),
|
completionTokens: Math.max(baseUsage.completionTokens, nextUsage.completionTokens),
|
||||||
totalTokens: Math.max(baseUsage.totalTokens, nextUsage.totalTokens)
|
totalTokens: Math.max(baseUsage.totalTokens, nextUsage.totalTokens),
|
||||||
|
cachedTokens: Math.max(baseUsage.cachedTokens || 0, nextUsage.cachedTokens || 0)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,7 +96,8 @@ function extractUsage(...candidates) {
|
||||||
return candidates.reduce((usage, candidate) => mergeUsage(usage, normalizeUsageCandidate(candidate)), {
|
return candidates.reduce((usage, candidate) => mergeUsage(usage, normalizeUsageCandidate(candidate)), {
|
||||||
promptTokens: 0,
|
promptTokens: 0,
|
||||||
completionTokens: 0,
|
completionTokens: 0,
|
||||||
totalTokens: 0
|
totalTokens: 0,
|
||||||
|
cachedTokens: 0
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,8 @@ function createUsageBucket() {
|
||||||
requestCount: 0,
|
requestCount: 0,
|
||||||
promptTokens: 0,
|
promptTokens: 0,
|
||||||
completionTokens: 0,
|
completionTokens: 0,
|
||||||
totalTokens: 0
|
totalTokens: 0,
|
||||||
|
cachedTokens: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,7 +76,8 @@ function normalizeUsageBucket(bucket) {
|
||||||
requestCount: toNumber(bucket?.requestCount),
|
requestCount: toNumber(bucket?.requestCount),
|
||||||
promptTokens: toNumber(bucket?.promptTokens),
|
promptTokens: toNumber(bucket?.promptTokens),
|
||||||
completionTokens: toNumber(bucket?.completionTokens),
|
completionTokens: toNumber(bucket?.completionTokens),
|
||||||
totalTokens: toNumber(bucket?.totalTokens)
|
totalTokens: toNumber(bucket?.totalTokens),
|
||||||
|
cachedTokens: toNumber(bucket?.cachedTokens)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,9 +107,11 @@ function normalizeKeyData(keyData = {}) {
|
||||||
todayPromptTokens: toNumber(keyData.todayPromptTokens),
|
todayPromptTokens: toNumber(keyData.todayPromptTokens),
|
||||||
todayCompletionTokens: toNumber(keyData.todayCompletionTokens),
|
todayCompletionTokens: toNumber(keyData.todayCompletionTokens),
|
||||||
todayTotalTokens: toNumber(keyData.todayTotalTokens),
|
todayTotalTokens: toNumber(keyData.todayTotalTokens),
|
||||||
|
todayCachedTokens: toNumber(keyData.todayCachedTokens),
|
||||||
totalPromptTokens: toNumber(keyData.totalPromptTokens),
|
totalPromptTokens: toNumber(keyData.totalPromptTokens),
|
||||||
totalCompletionTokens: toNumber(keyData.totalCompletionTokens),
|
totalCompletionTokens: toNumber(keyData.totalCompletionTokens),
|
||||||
totalTokens: toNumber(keyData.totalTokens),
|
totalTokens: toNumber(keyData.totalTokens),
|
||||||
|
totalCachedTokens: toNumber(keyData.totalCachedTokens),
|
||||||
usageHistory: {}
|
usageHistory: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -131,6 +135,7 @@ function addUsage(target, usage = {}) {
|
||||||
target.promptTokens += toNumber(usage.promptTokens);
|
target.promptTokens += toNumber(usage.promptTokens);
|
||||||
target.completionTokens += toNumber(usage.completionTokens);
|
target.completionTokens += toNumber(usage.completionTokens);
|
||||||
target.totalTokens += toNumber(usage.totalTokens);
|
target.totalTokens += toNumber(usage.totalTokens);
|
||||||
|
target.cachedTokens += toNumber(usage.cachedTokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -172,7 +177,7 @@ function syncWriteToFile() {
|
||||||
try {
|
try {
|
||||||
const dir = path.dirname(KEYS_STORE_FILE);
|
const dir = path.dirname(KEYS_STORE_FILE);
|
||||||
if (!existsSync(dir)) {
|
if (!existsSync(dir)) {
|
||||||
require('fs').mkdirSync(dir, { recursive: true });
|
mkdirSync(dir, { recursive: true });
|
||||||
}
|
}
|
||||||
writeFileSync(KEYS_STORE_FILE, JSON.stringify(keyStore, null, 2), 'utf8');
|
writeFileSync(KEYS_STORE_FILE, JSON.stringify(keyStore, null, 2), 'utf8');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -248,6 +253,7 @@ function checkAndResetDailyCount(keyData) {
|
||||||
keyData.todayPromptTokens = 0;
|
keyData.todayPromptTokens = 0;
|
||||||
keyData.todayCompletionTokens = 0;
|
keyData.todayCompletionTokens = 0;
|
||||||
keyData.todayTotalTokens = 0;
|
keyData.todayTotalTokens = 0;
|
||||||
|
keyData.todayCachedTokens = 0;
|
||||||
keyData.lastResetDate = today;
|
keyData.lastResetDate = today;
|
||||||
}
|
}
|
||||||
return keyData;
|
return keyData;
|
||||||
|
|
@ -255,7 +261,6 @@ function checkAndResetDailyCount(keyData) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建新的 API Key
|
* 创建新的 API Key
|
||||||
|
|
||||||
* @param {string} name - Key 名称
|
* @param {string} name - Key 名称
|
||||||
* @param {number} [dailyLimit] - 每日限额,不传则使用配置的默认值
|
* @param {number} [dailyLimit] - 每日限额,不传则使用配置的默认值
|
||||||
*/
|
*/
|
||||||
|
|
@ -278,9 +283,11 @@ export async function createKey(name = '', dailyLimit = null) {
|
||||||
todayPromptTokens: 0,
|
todayPromptTokens: 0,
|
||||||
todayCompletionTokens: 0,
|
todayCompletionTokens: 0,
|
||||||
todayTotalTokens: 0,
|
todayTotalTokens: 0,
|
||||||
|
todayCachedTokens: 0,
|
||||||
totalPromptTokens: 0,
|
totalPromptTokens: 0,
|
||||||
totalCompletionTokens: 0,
|
totalCompletionTokens: 0,
|
||||||
totalTokens: 0,
|
totalTokens: 0,
|
||||||
|
totalCachedTokens: 0,
|
||||||
lastResetDate: today,
|
lastResetDate: today,
|
||||||
lastUsedAt: null,
|
lastUsedAt: null,
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
@ -354,6 +361,7 @@ export async function resetKeyUsage(keyId) {
|
||||||
keyStore.keys[keyId].todayPromptTokens = 0;
|
keyStore.keys[keyId].todayPromptTokens = 0;
|
||||||
keyStore.keys[keyId].todayCompletionTokens = 0;
|
keyStore.keys[keyId].todayCompletionTokens = 0;
|
||||||
keyStore.keys[keyId].todayTotalTokens = 0;
|
keyStore.keys[keyId].todayTotalTokens = 0;
|
||||||
|
keyStore.keys[keyId].todayCachedTokens = 0;
|
||||||
keyStore.keys[keyId].lastResetDate = getTodayDateString();
|
keyStore.keys[keyId].lastResetDate = getTodayDateString();
|
||||||
if (!keyStore.keys[keyId].usageHistory) keyStore.keys[keyId].usageHistory = {};
|
if (!keyStore.keys[keyId].usageHistory) keyStore.keys[keyId].usageHistory = {};
|
||||||
keyStore.keys[keyId].usageHistory[getTodayDateString()] = normalizeUsageHistoryDay();
|
keyStore.keys[keyId].usageHistory[getTodayDateString()] = normalizeUsageHistoryDay();
|
||||||
|
|
@ -448,7 +456,7 @@ export async function validateKey(apiKey) {
|
||||||
* @param {string} apiKey - API Key
|
* @param {string} apiKey - API Key
|
||||||
* @param {string} provider - 使用的提供商
|
* @param {string} provider - 使用的提供商
|
||||||
* @param {string} model - 使用的模型
|
* @param {string} model - 使用的模型
|
||||||
* @param {{promptTokens?: number, completionTokens?: number, totalTokens?: number}} usage - token 用量
|
* @param {{promptTokens?: number, completionTokens?: number, totalTokens?: number, cachedTokens?: number}} usage - token 用量
|
||||||
*/
|
*/
|
||||||
export async function incrementUsage(apiKey, provider = 'unknown', model = 'unknown', usage = {}) {
|
export async function incrementUsage(apiKey, provider = 'unknown', model = 'unknown', usage = {}) {
|
||||||
ensureLoaded();
|
ensureLoaded();
|
||||||
|
|
@ -469,9 +477,11 @@ export async function incrementUsage(apiKey, provider = 'unknown', model = 'unkn
|
||||||
keyData.todayPromptTokens += toNumber(usage.promptTokens);
|
keyData.todayPromptTokens += toNumber(usage.promptTokens);
|
||||||
keyData.todayCompletionTokens += toNumber(usage.completionTokens);
|
keyData.todayCompletionTokens += toNumber(usage.completionTokens);
|
||||||
keyData.todayTotalTokens += toNumber(usage.totalTokens);
|
keyData.todayTotalTokens += toNumber(usage.totalTokens);
|
||||||
|
keyData.todayCachedTokens += toNumber(usage.cachedTokens);
|
||||||
keyData.totalPromptTokens += toNumber(usage.promptTokens);
|
keyData.totalPromptTokens += toNumber(usage.promptTokens);
|
||||||
keyData.totalCompletionTokens += toNumber(usage.completionTokens);
|
keyData.totalCompletionTokens += toNumber(usage.completionTokens);
|
||||||
keyData.totalTokens += toNumber(usage.totalTokens);
|
keyData.totalTokens += toNumber(usage.totalTokens);
|
||||||
|
keyData.totalCachedTokens += toNumber(usage.cachedTokens);
|
||||||
keyData.lastUsedAt = new Date().toISOString();
|
keyData.lastUsedAt = new Date().toISOString();
|
||||||
|
|
||||||
// 记录个人按天统计 (每个 Key 独立)
|
// 记录个人按天统计 (每个 Key 独立)
|
||||||
|
|
@ -514,8 +524,8 @@ export async function getStats() {
|
||||||
ensureLoaded();
|
ensureLoaded();
|
||||||
const keys = Object.values(keyStore.keys);
|
const keys = Object.values(keyStore.keys);
|
||||||
let enabledKeys = 0, todayTotalUsage = 0, totalUsage = 0;
|
let enabledKeys = 0, todayTotalUsage = 0, totalUsage = 0;
|
||||||
let todayPromptTokens = 0, todayCompletionTokens = 0, todayTotalTokens = 0;
|
let todayPromptTokens = 0, todayCompletionTokens = 0, todayTotalTokens = 0, todayCachedTokens = 0;
|
||||||
let totalPromptTokens = 0, totalCompletionTokens = 0, totalTokens = 0;
|
let totalPromptTokens = 0, totalCompletionTokens = 0, totalTokens = 0, totalCachedTokens = 0;
|
||||||
const aggregatedHistory = {};
|
const aggregatedHistory = {};
|
||||||
|
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
|
|
@ -526,9 +536,11 @@ export async function getStats() {
|
||||||
todayPromptTokens += key.todayPromptTokens || 0;
|
todayPromptTokens += key.todayPromptTokens || 0;
|
||||||
todayCompletionTokens += key.todayCompletionTokens || 0;
|
todayCompletionTokens += key.todayCompletionTokens || 0;
|
||||||
todayTotalTokens += key.todayTotalTokens || 0;
|
todayTotalTokens += key.todayTotalTokens || 0;
|
||||||
|
todayCachedTokens += key.todayCachedTokens || 0;
|
||||||
totalPromptTokens += key.totalPromptTokens || 0;
|
totalPromptTokens += key.totalPromptTokens || 0;
|
||||||
totalCompletionTokens += key.totalCompletionTokens || 0;
|
totalCompletionTokens += key.totalCompletionTokens || 0;
|
||||||
totalTokens += key.totalTokens || 0;
|
totalTokens += key.totalTokens || 0;
|
||||||
|
totalCachedTokens += key.totalCachedTokens || 0;
|
||||||
|
|
||||||
// 汇总每个 Key 的历史数据
|
// 汇总每个 Key 的历史数据
|
||||||
if (key.usageHistory) {
|
if (key.usageHistory) {
|
||||||
|
|
@ -566,9 +578,11 @@ export async function getStats() {
|
||||||
todayPromptTokens,
|
todayPromptTokens,
|
||||||
todayCompletionTokens,
|
todayCompletionTokens,
|
||||||
todayTotalTokens,
|
todayTotalTokens,
|
||||||
|
todayCachedTokens,
|
||||||
totalPromptTokens,
|
totalPromptTokens,
|
||||||
totalCompletionTokens,
|
totalCompletionTokens,
|
||||||
totalTokens,
|
totalTokens,
|
||||||
|
totalCachedTokens,
|
||||||
usageHistory: aggregatedHistory
|
usageHistory: aggregatedHistory
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -576,7 +590,7 @@ export async function getStats() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量更新所有 Key 的每日限额
|
* 批量更新所有 Key 的每日限额
|
||||||
* @param {number} newLimit - 新的每日限额
|
* @param {number} newLimit - 新s的每日限额
|
||||||
* @returns {Promise<{total: number, updated: number}>}
|
* @returns {Promise<{total: number, updated: number}>}
|
||||||
*/
|
*/
|
||||||
export async function applyDailyLimitToAllKeys(newLimit) {
|
export async function applyDailyLimitToAllKeys(newLimit) {
|
||||||
|
|
|
||||||
|
|
@ -254,7 +254,7 @@ function normalizeUsageCandidate(candidate) {
|
||||||
return {
|
return {
|
||||||
promptTokens,
|
promptTokens,
|
||||||
completionTokens,
|
completionTokens,
|
||||||
totalTokens: totalTokens || promptTokens + completionTokens,
|
totalTokens: totalTokens || (promptTokens + completionTokens),
|
||||||
cachedTokens
|
cachedTokens
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -414,10 +414,18 @@
|
||||||
<div class="label">今日 Tokens</div>
|
<div class="label">今日 Tokens</div>
|
||||||
<div class="value" id="statTodayTokens">0</div>
|
<div class="value" id="statTodayTokens">0</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="stat-card cyan">
|
||||||
|
<div class="label">今日缓存 Tokens</div>
|
||||||
|
<div class="value" id="statTodayCachedTokens">0</div>
|
||||||
|
</div>
|
||||||
<div class="stat-card purple">
|
<div class="stat-card purple">
|
||||||
<div class="label">累计 Tokens</div>
|
<div class="label">累计 Tokens</div>
|
||||||
<div class="value" id="statTotalTokens">0</div>
|
<div class="value" id="statTotalTokens">0</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="stat-card cyan">
|
||||||
|
<div class="label">累计缓存 Tokens</div>
|
||||||
|
<div class="value" id="statTotalCachedTokens">0</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 我的使用分布统计区域 -->
|
<!-- 我的使用分布统计区域 -->
|
||||||
|
|
@ -596,7 +604,9 @@
|
||||||
|
|
||||||
// Token 用量
|
// Token 用量
|
||||||
document.getElementById('statTodayTokens').textContent = formatTokenCompact(data.usage?.totalTokens || 0);
|
document.getElementById('statTodayTokens').textContent = formatTokenCompact(data.usage?.totalTokens || 0);
|
||||||
|
document.getElementById('statTodayCachedTokens').textContent = formatTokenCompact(data.usage?.cachedTokens || 0);
|
||||||
document.getElementById('statTotalTokens').textContent = formatTokenCompact(data.tokens?.total || 0);
|
document.getElementById('statTotalTokens').textContent = formatTokenCompact(data.tokens?.total || 0);
|
||||||
|
document.getElementById('statTotalCachedTokens').textContent = formatTokenCompact(data.tokens?.cached || 0);
|
||||||
|
|
||||||
// 最后使用时间
|
// 最后使用时间
|
||||||
if (data.lastUsedAt) {
|
if (data.lastUsedAt) {
|
||||||
|
|
|
||||||
|
|
@ -616,10 +616,18 @@
|
||||||
<div class="label">今日总 Tokens</div>
|
<div class="label">今日总 Tokens</div>
|
||||||
<div class="value" id="todayTokens">0</div>
|
<div class="value" id="todayTokens">0</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="stat-card cyan">
|
||||||
|
<div class="label">今日缓存 Tokens</div>
|
||||||
|
<div class="value" id="todayCachedTokens">0</div>
|
||||||
|
</div>
|
||||||
<div class="stat-card pink">
|
<div class="stat-card pink">
|
||||||
<div class="label">累计 Tokens</div>
|
<div class="label">累计 Tokens</div>
|
||||||
<div class="value" id="totalTokens">0</div>
|
<div class="value" id="totalTokens">0</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="stat-card cyan">
|
||||||
|
<div class="label">累计缓存 Tokens</div>
|
||||||
|
<div class="value" id="totalCachedTokens">0</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 使用分布统计区域 -->
|
<!-- 使用分布统计区域 -->
|
||||||
|
|
@ -835,7 +843,9 @@
|
||||||
document.getElementById('todayUsage').textContent = stats.todayTotalUsage;
|
document.getElementById('todayUsage').textContent = stats.todayTotalUsage;
|
||||||
document.getElementById('totalUsage').textContent = stats.totalUsage;
|
document.getElementById('totalUsage').textContent = stats.totalUsage;
|
||||||
document.getElementById('todayTokens').textContent = formatTokenCompact(stats.todayTotalTokens);
|
document.getElementById('todayTokens').textContent = formatTokenCompact(stats.todayTotalTokens);
|
||||||
|
document.getElementById('todayCachedTokens').textContent = formatTokenCompact(stats.todayCachedTokens || 0);
|
||||||
document.getElementById('totalTokens').textContent = formatTokenCompact(stats.totalTokens);
|
document.getElementById('totalTokens').textContent = formatTokenCompact(stats.totalTokens);
|
||||||
|
document.getElementById('totalCachedTokens').textContent = formatTokenCompact(stats.totalCachedTokens || 0);
|
||||||
|
|
||||||
// 渲染使用历史分布
|
// 渲染使用历史分布
|
||||||
renderUsageHistory(stats.usageHistory);
|
renderUsageHistory(stats.usageHistory);
|
||||||
|
|
@ -1043,13 +1053,13 @@
|
||||||
<div class="key-stat">
|
<div class="key-stat">
|
||||||
<div class="label">今日/限额</div>
|
<div class="label">今日/限额</div>
|
||||||
<div class="value ${valueClass}">${key.todayUsage}/${key.dailyLimit}</div>
|
<div class="value ${valueClass}">${key.todayUsage}/${key.dailyLimit}</div>
|
||||||
<div class="value muted">${formatTokenCompact(key.todayTotalTokens || 0)} Tokens</div>
|
<div class="value muted">${formatTokenCompact(key.todayTotalTokens || 0)} Tokens ${key.todayCachedTokens ? `(含 ${formatTokenCompact(key.todayCachedTokens)} 缓存)` : ''}</div>
|
||||||
<div class="progress-bar"><div class="fill ${progressClass}" style="width:${Math.min(usagePercent, 100)}%"></div></div>
|
<div class="progress-bar"><div class="fill ${progressClass}" style="width:${Math.min(usagePercent, 100)}%"></div></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="key-stat">
|
<div class="key-stat">
|
||||||
<div class="label">累计</div>
|
<div class="label">累计</div>
|
||||||
<div class="value">${key.totalUsage}</div>
|
<div class="value">${key.totalUsage}</div>
|
||||||
<div class="value muted">${formatTokenCompact(key.totalTokens || 0)} Tokens</div>
|
<div class="value muted">${formatTokenCompact(key.totalTokens || 0)} Tokens ${key.totalCachedTokens ? `(含 ${formatTokenCompact(key.totalCachedTokens)} 缓存)` : ''}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="key-stat">
|
<div class="key-stat">
|
||||||
<div class="label">最后调用</div>
|
<div class="label">最后调用</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue