From 369cf52346ae5e058d97b321582d47749f5333ef Mon Sep 17 00:00:00 2001 From: Supra4E8C Date: Sat, 18 Oct 2025 17:04:29 +0800 Subject: [PATCH] 0.1.7 --- app.js | 161 ++++++++++++++++++++++++++++++++++++++++++----------- index.html | 7 --- styles.css | 80 ++++++++++++++++++++++++++ 3 files changed, 210 insertions(+), 38 deletions(-) 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