fix(ui): harden key list rendering and config handling

This commit is contained in:
hkfires
2025-10-31 16:24:43 +08:00
parent 257260b1d2
commit 1e79f918e2

70
app.js
View File

@@ -1498,24 +1498,16 @@ class CLIProxyManager {
} }
// Gemini 密钥 // Gemini 密钥
if (config['generative-language-api-key']) { await this.renderGeminiKeys(Array.isArray(config['generative-language-api-key']) ? config['generative-language-api-key'] : []);
await this.renderGeminiKeys(config['generative-language-api-key']);
}
// Codex 密钥 // Codex 密钥
if (config['codex-api-key']) { await this.renderCodexKeys(Array.isArray(config['codex-api-key']) ? config['codex-api-key'] : []);
await this.renderCodexKeys(config['codex-api-key']);
}
// Claude 密钥 // Claude 密钥
if (config['claude-api-key']) { await this.renderClaudeKeys(Array.isArray(config['claude-api-key']) ? config['claude-api-key'] : []);
await this.renderClaudeKeys(config['claude-api-key']);
}
// OpenAI 兼容提供商 // OpenAI 兼容提供商
if (config['openai-compatibility']) { await this.renderOpenAIProviders(Array.isArray(config['openai-compatibility']) ? config['openai-compatibility'] : []);
await this.renderOpenAIProviders(config['openai-compatibility']);
}
} }
// 回退方法:原来的逐个加载方式 // 回退方法:原来的逐个加载方式
@@ -2179,9 +2171,8 @@ class CLIProxyManager {
async loadGeminiKeys() { async loadGeminiKeys() {
try { try {
const config = await this.getConfig(); const config = await this.getConfig();
if (config['generative-language-api-key']) { const keys = Array.isArray(config['generative-language-api-key']) ? config['generative-language-api-key'] : [];
await this.renderGeminiKeys(config['generative-language-api-key']); await this.renderGeminiKeys(keys);
}
} catch (error) { } catch (error) {
console.error('加载Gemini密钥失败:', error); console.error('加载Gemini密钥失败:', error);
} }
@@ -2190,8 +2181,12 @@ class CLIProxyManager {
// 渲染Gemini密钥列表 // 渲染Gemini密钥列表
async renderGeminiKeys(keys) { async renderGeminiKeys(keys) {
const container = document.getElementById('gemini-keys-list'); const container = document.getElementById('gemini-keys-list');
if (!container) {
return;
}
const list = Array.isArray(keys) ? keys : [];
if (keys.length === 0) { if (list.length === 0) {
container.innerHTML = ` container.innerHTML = `
<div class="empty-state"> <div class="empty-state">
<i class="fab fa-google"></i> <i class="fab fa-google"></i>
@@ -2205,7 +2200,7 @@ class CLIProxyManager {
// 获取使用统计,按 source 聚合 // 获取使用统计,按 source 聚合
const stats = await this.getKeyStats(); const stats = await this.getKeyStats();
container.innerHTML = keys.map((key, index) => { container.innerHTML = list.map((key, index) => {
const masked = this.maskApiKey(key); const masked = this.maskApiKey(key);
const keyStats = stats[key] || stats[masked] || { success: 0, failure: 0 }; const keyStats = stats[key] || stats[masked] || { success: 0, failure: 0 };
return ` return `
@@ -2344,9 +2339,8 @@ class CLIProxyManager {
async loadCodexKeys() { async loadCodexKeys() {
try { try {
const config = await this.getConfig(); const config = await this.getConfig();
if (config['codex-api-key']) { const keys = Array.isArray(config['codex-api-key']) ? config['codex-api-key'] : [];
await this.renderCodexKeys(config['codex-api-key']); await this.renderCodexKeys(keys);
}
} catch (error) { } catch (error) {
console.error('加载Codex密钥失败:', error); console.error('加载Codex密钥失败:', error);
} }
@@ -2355,8 +2349,12 @@ class CLIProxyManager {
// 渲染Codex密钥列表 // 渲染Codex密钥列表
async renderCodexKeys(keys) { async renderCodexKeys(keys) {
const container = document.getElementById('codex-keys-list'); const container = document.getElementById('codex-keys-list');
if (!container) {
return;
}
const list = Array.isArray(keys) ? keys : [];
if (keys.length === 0) { if (list.length === 0) {
container.innerHTML = ` container.innerHTML = `
<div class="empty-state"> <div class="empty-state">
<i class="fas fa-code"></i> <i class="fas fa-code"></i>
@@ -2370,7 +2368,7 @@ class CLIProxyManager {
// 获取使用统计,按 source 聚合 // 获取使用统计,按 source 聚合
const stats = await this.getKeyStats(); const stats = await this.getKeyStats();
container.innerHTML = keys.map((config, index) => { container.innerHTML = list.map((config, index) => {
const rawKey = config['api-key']; const rawKey = config['api-key'];
const masked = rawKey ? this.maskApiKey(rawKey) : ''; const masked = rawKey ? this.maskApiKey(rawKey) : '';
const keyStats = (rawKey && (stats[rawKey] || stats[masked])) || { success: 0, failure: 0 }; const keyStats = (rawKey && (stats[rawKey] || stats[masked])) || { success: 0, failure: 0 };
@@ -2549,9 +2547,8 @@ class CLIProxyManager {
async loadClaudeKeys() { async loadClaudeKeys() {
try { try {
const config = await this.getConfig(); const config = await this.getConfig();
if (config['claude-api-key']) { const keys = Array.isArray(config['claude-api-key']) ? config['claude-api-key'] : [];
await this.renderClaudeKeys(config['claude-api-key']); await this.renderClaudeKeys(keys);
}
} catch (error) { } catch (error) {
console.error('加载Claude密钥失败:', error); console.error('加载Claude密钥失败:', error);
} }
@@ -2560,8 +2557,12 @@ class CLIProxyManager {
// 渲染Claude密钥列表 // 渲染Claude密钥列表
async renderClaudeKeys(keys) { async renderClaudeKeys(keys) {
const container = document.getElementById('claude-keys-list'); const container = document.getElementById('claude-keys-list');
if (!container) {
return;
}
const list = Array.isArray(keys) ? keys : [];
if (keys.length === 0) { if (list.length === 0) {
container.innerHTML = ` container.innerHTML = `
<div class="empty-state"> <div class="empty-state">
<i class="fas fa-brain"></i> <i class="fas fa-brain"></i>
@@ -2575,7 +2576,7 @@ class CLIProxyManager {
// 获取使用统计,按 source 聚合 // 获取使用统计,按 source 聚合
const stats = await this.getKeyStats(); const stats = await this.getKeyStats();
container.innerHTML = keys.map((config, index) => { container.innerHTML = list.map((config, index) => {
const rawKey = config['api-key']; const rawKey = config['api-key'];
const masked = rawKey ? this.maskApiKey(rawKey) : ''; const masked = rawKey ? this.maskApiKey(rawKey) : '';
const keyStats = (rawKey && (stats[rawKey] || stats[masked])) || { success: 0, failure: 0 }; const keyStats = (rawKey && (stats[rawKey] || stats[masked])) || { success: 0, failure: 0 };
@@ -2754,9 +2755,8 @@ class CLIProxyManager {
async loadOpenAIProviders() { async loadOpenAIProviders() {
try { try {
const config = await this.getConfig(); const config = await this.getConfig();
if (config['openai-compatibility']) { const providers = Array.isArray(config['openai-compatibility']) ? config['openai-compatibility'] : [];
await this.renderOpenAIProviders(config['openai-compatibility']); await this.renderOpenAIProviders(providers);
}
} catch (error) { } catch (error) {
console.error('加载OpenAI提供商失败:', error); console.error('加载OpenAI提供商失败:', error);
} }
@@ -2765,8 +2765,12 @@ class CLIProxyManager {
// 渲染OpenAI提供商列表 // 渲染OpenAI提供商列表
async renderOpenAIProviders(providers) { async renderOpenAIProviders(providers) {
const container = document.getElementById('openai-providers-list'); const container = document.getElementById('openai-providers-list');
if (!container) {
return;
}
const list = Array.isArray(providers) ? providers : [];
if (!Array.isArray(providers) || providers.length === 0) { if (list.length === 0) {
container.innerHTML = ` container.innerHTML = `
<div class="empty-state"> <div class="empty-state">
<i class="fas fa-plug"></i> <i class="fas fa-plug"></i>
@@ -2781,7 +2785,7 @@ class CLIProxyManager {
} }
// 根据提供商数量设置滚动条 // 根据提供商数量设置滚动条
if (providers.length > 5) { if (list.length > 5) {
container.style.maxHeight = '400px'; container.style.maxHeight = '400px';
container.style.overflowY = 'auto'; container.style.overflowY = 'auto';
} else { } else {
@@ -2792,7 +2796,7 @@ class CLIProxyManager {
// 获取使用统计,按 source 聚合 // 获取使用统计,按 source 聚合
const stats = await this.getKeyStats(); const stats = await this.getKeyStats();
container.innerHTML = providers.map((provider, index) => { container.innerHTML = list.map((provider, index) => {
const item = typeof provider === 'object' && provider !== null ? provider : {}; const item = typeof provider === 'object' && provider !== null ? provider : {};
// 处理两种API密钥格式新的 api-key-entries 和旧的 api-keys // 处理两种API密钥格式新的 api-key-entries 和旧的 api-keys