diff --git a/app.js b/app.js
index 27327fa..15401eb 100644
--- a/app.js
+++ b/app.js
@@ -355,7 +355,6 @@ class CLIProxyManager {
// 更新连接信息显示
updateConnectionInfo() {
const apiUrlElement = document.getElementById('display-api-url');
- const keyElement = document.getElementById('display-management-key');
const statusElement = document.getElementById('display-connection-status');
// 显示API地址
@@ -364,14 +363,6 @@ class CLIProxyManager {
}
// 显示密钥(遮蔽显示)
- if (keyElement) {
- if (this.managementKey) {
- const maskedKey = this.maskApiKey(this.managementKey);
- keyElement.textContent = maskedKey;
- } else {
- keyElement.textContent = '-';
- }
- }
// 显示连接状态
if (statusElement) {
@@ -1148,7 +1139,7 @@ class CLIProxyManager {
const config = await this.getConfig(forceRefresh);
// 从配置中提取并设置各个设置项
- this.updateSettingsFromConfig(config);
+ await this.updateSettingsFromConfig(config);
// 认证文件需要单独加载,因为不在配置中
await this.loadAuthFiles();
@@ -1166,7 +1157,7 @@ class CLIProxyManager {
}
// 从配置对象更新所有设置
- updateSettingsFromConfig(config) {
+ async updateSettingsFromConfig(config) {
// 调试设置
if (config.debug !== undefined) {
document.getElementById('debug-toggle').checked = config.debug;
@@ -1216,22 +1207,22 @@ class CLIProxyManager {
// Gemini 密钥
if (config['generative-language-api-key']) {
- this.renderGeminiKeys(config['generative-language-api-key']);
+ await this.renderGeminiKeys(config['generative-language-api-key']);
}
// Codex 密钥
if (config['codex-api-key']) {
- this.renderCodexKeys(config['codex-api-key']);
+ await this.renderCodexKeys(config['codex-api-key']);
}
// Claude 密钥
if (config['claude-api-key']) {
- this.renderClaudeKeys(config['claude-api-key']);
+ await this.renderClaudeKeys(config['claude-api-key']);
}
// OpenAI 兼容提供商
if (config['openai-compatibility']) {
- this.renderOpenAIProviders(config['openai-compatibility']);
+ await this.renderOpenAIProviders(config['openai-compatibility']);
}
}
@@ -1888,7 +1879,7 @@ class CLIProxyManager {
try {
const config = await this.getConfig();
if (config['generative-language-api-key']) {
- this.renderGeminiKeys(config['generative-language-api-key']);
+ await this.renderGeminiKeys(config['generative-language-api-key']);
}
} catch (error) {
console.error('加载Gemini密钥失败:', error);
@@ -1896,7 +1887,7 @@ class CLIProxyManager {
}
// 渲染Gemini密钥列表
- renderGeminiKeys(keys) {
+ async renderGeminiKeys(keys) {
const container = document.getElementById('gemini-keys-list');
if (keys.length === 0) {
@@ -1910,11 +1901,24 @@ class CLIProxyManager {
return;
}
- container.innerHTML = keys.map((key, index) => `
+ // 获取使用统计,按 source 聚合
+ const stats = await this.getKeyStats();
+
+ container.innerHTML = keys.map((key, index) => {
+ const keyStats = stats[key] || { success: 0, failure: 0 };
+ return `
${i18n.t('ai_providers.gemini_item_title')} #${index + 1}
${this.maskApiKey(key)}
+
+
+ 成功: ${keyStats.success}
+
+
+ 失败: ${keyStats.failure}
+
+
- `).join('');
+ `}).join('');
}
// 显示添加Gemini密钥模态框
@@ -2039,7 +2043,7 @@ class CLIProxyManager {
try {
const config = await this.getConfig();
if (config['codex-api-key']) {
- this.renderCodexKeys(config['codex-api-key']);
+ await this.renderCodexKeys(config['codex-api-key']);
}
} catch (error) {
console.error('加载Codex密钥失败:', error);
@@ -2047,7 +2051,7 @@ class CLIProxyManager {
}
// 渲染Codex密钥列表
- renderCodexKeys(keys) {
+ async renderCodexKeys(keys) {
const container = document.getElementById('codex-keys-list');
if (keys.length === 0) {
@@ -2061,13 +2065,26 @@ class CLIProxyManager {
return;
}
- container.innerHTML = keys.map((config, index) => `
+ // 获取使用统计,按 source 聚合
+ const stats = await this.getKeyStats();
+
+ container.innerHTML = keys.map((config, index) => {
+ const keyStats = stats[config['api-key']] || { success: 0, failure: 0 };
+ return `
${i18n.t('ai_providers.codex_item_title')} #${index + 1}
${i18n.t('common.api_key')}: ${this.maskApiKey(config['api-key'])}
${config['base-url'] ? `
${i18n.t('common.base_url')}: ${this.escapeHtml(config['base-url'])}
` : ''}
${config['proxy-url'] ? `
${i18n.t('common.proxy_url')}: ${this.escapeHtml(config['proxy-url'])}
` : ''}
+
+
+ 成功: ${keyStats.success}
+
+
+ 失败: ${keyStats.failure}
+
+
- `).join('');
+ `}).join('');
}
// 显示添加Codex密钥模态框
@@ -2229,7 +2246,7 @@ class CLIProxyManager {
try {
const config = await this.getConfig();
if (config['claude-api-key']) {
- this.renderClaudeKeys(config['claude-api-key']);
+ await this.renderClaudeKeys(config['claude-api-key']);
}
} catch (error) {
console.error('加载Claude密钥失败:', error);
@@ -2237,7 +2254,7 @@ class CLIProxyManager {
}
// 渲染Claude密钥列表
- renderClaudeKeys(keys) {
+ async renderClaudeKeys(keys) {
const container = document.getElementById('claude-keys-list');
if (keys.length === 0) {
@@ -2251,13 +2268,26 @@ class CLIProxyManager {
return;
}
- container.innerHTML = keys.map((config, index) => `
+ // 获取使用统计,按 source 聚合
+ const stats = await this.getKeyStats();
+
+ container.innerHTML = keys.map((config, index) => {
+ const keyStats = stats[config['api-key']] || { success: 0, failure: 0 };
+ return `
${i18n.t('ai_providers.claude_item_title')} #${index + 1}
${i18n.t('common.api_key')}: ${this.maskApiKey(config['api-key'])}
${config['base-url'] ? `
${i18n.t('common.base_url')}: ${this.escapeHtml(config['base-url'])}
` : ''}
${config['proxy-url'] ? `
${i18n.t('common.proxy_url')}: ${this.escapeHtml(config['proxy-url'])}
` : ''}
+
+
+ 成功: ${keyStats.success}
+
+
+ 失败: ${keyStats.failure}
+
+
- `).join('');
+ `}).join('');
}
// 显示添加Claude密钥模态框
@@ -2419,7 +2449,7 @@ class CLIProxyManager {
try {
const config = await this.getConfig();
if (config['openai-compatibility']) {
- this.renderOpenAIProviders(config['openai-compatibility']);
+ await this.renderOpenAIProviders(config['openai-compatibility']);
}
} catch (error) {
console.error('加载OpenAI提供商失败:', error);
@@ -2427,7 +2457,7 @@ class CLIProxyManager {
}
// 渲染OpenAI提供商列表
- renderOpenAIProviders(providers) {
+ async renderOpenAIProviders(providers) {
const container = document.getElementById('openai-providers-list');
if (providers.length === 0) {
@@ -2441,7 +2471,21 @@ class CLIProxyManager {
return;
}
- container.innerHTML = providers.map((provider, index) => `
+ // 获取使用统计,按 source 聚合
+ const stats = await this.getKeyStats();
+
+ container.innerHTML = providers.map((provider, index) => {
+ const apiKeyEntries = provider['api-key-entries'] || [];
+ let totalSuccess = 0;
+ let totalFailure = 0;
+
+ apiKeyEntries.forEach(entry => {
+ const keyStats = stats[entry['api-key']] || { success: 0, failure: 0 };
+ totalSuccess += keyStats.success;
+ totalFailure += keyStats.failure;
+ });
+
+ return `
${this.escapeHtml(provider.name)}
@@ -2449,6 +2493,14 @@ class CLIProxyManager {
${i18n.t('ai_providers.openai_keys_count')}: ${(provider['api-key-entries'] || []).length}
${i18n.t('ai_providers.openai_models_count')}: ${(provider.models || []).length}
${this.renderOpenAIModelBadges(provider.models || [])}
+
+
+ 成功: ${totalSuccess}
+
+
+ 失败: ${totalFailure}
+
+
- `).join('');
+ `}).join('');
}
// 显示添加OpenAI提供商模态框
@@ -3552,6 +3604,53 @@ class CLIProxyManager {
tokensChart = null;
currentUsageData = null;
+ // 获取API密钥的统计信息
+ async getKeyStats() {
+ try {
+ const response = await this.makeRequest('/usage');
+ const usage = response?.usage || null;
+
+ if (!usage) {
+ return {};
+ }
+
+ const sourceStats = {};
+ const apis = usage.apis || {};
+
+ Object.values(apis).forEach(apiEntry => {
+ const models = apiEntry.models || {};
+
+ Object.values(models).forEach(modelEntry => {
+ const details = modelEntry.details || [];
+
+ details.forEach(detail => {
+ const source = detail.source;
+ if (!source) return;
+
+ if (!sourceStats[source]) {
+ sourceStats[source] = {
+ success: 0,
+ failure: 0
+ };
+ }
+
+ const success = detail.success;
+ if (success === false) {
+ sourceStats[source].failure += 1;
+ } else {
+ sourceStats[source].success += 1;
+ }
+ });
+ });
+ });
+
+ return sourceStats;
+ } catch (error) {
+ console.error('获取统计信息失败:', error);
+ return {};
+ }
+ }
+
// 加载使用统计
async loadUsageStats() {
try {
diff --git a/index.html b/index.html
index 85a1feb..73f52d8 100644
--- a/index.html
+++ b/index.html
@@ -785,13 +785,6 @@
-
-
diff --git a/styles.css b/styles.css
index ba01600..7e6c24f 100644
--- a/styles.css
+++ b/styles.css
@@ -2845,3 +2845,83 @@ input:checked+.slider:before {
[data-theme="dark"] .logs-container {
background: rgba(15, 23, 42, 0.3);
}
+
+/* ===== AI提供商统计徽章样式 ===== */
+
+/* 统计信息容器 */
+.item-stats {
+ display: flex;
+ gap: 8px;
+ margin-top: 10px;
+ flex-wrap: wrap;
+}
+
+/* 统计徽章基础样式 */
+.stat-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ padding: 4px 10px;
+ border-radius: 12px;
+ font-size: 12px;
+ font-weight: 500;
+ transition: all 0.2s ease;
+}
+
+/* 成功统计徽章 */
+.stat-badge.stat-success {
+ background-color: var(--success-bg);
+ color: var(--success-text);
+ border: 1px solid var(--success-border);
+}
+
+.stat-badge.stat-success i {
+ font-size: 13px;
+}
+
+/* 失败统计徽章 */
+.stat-badge.stat-failure {
+ background-color: var(--error-bg);
+ color: var(--error-text);
+ border: 1px solid var(--error-border);
+}
+
+.stat-badge.stat-failure i {
+ font-size: 13px;
+}
+
+/* 悬停效果 */
+.stat-badge:hover {
+ transform: translateY(-1px);
+ box-shadow: var(--shadow-sm);
+}
+
+/* 暗色主题适配 */
+[data-theme="dark"] .stat-badge.stat-success {
+ background-color: rgba(6, 78, 59, 0.3);
+ color: #6ee7b7;
+ border-color: rgba(5, 150, 105, 0.5);
+}
+
+[data-theme="dark"] .stat-badge.stat-failure {
+ background-color: rgba(153, 27, 27, 0.3);
+ color: #fca5a5;
+ border-color: rgba(220, 38, 38, 0.5);
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+ .item-stats {
+ gap: 6px;
+ margin-top: 8px;
+ }
+
+ .stat-badge {
+ padding: 3px 8px;
+ font-size: 11px;
+ }
+
+ .stat-badge i {
+ font-size: 11px !important;
+ }
+}
\ No newline at end of file