mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-03 11:20:50 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5be40092f7 | ||
|
|
d422606f99 | ||
|
|
8b07159c35 |
139
app.js
139
app.js
@@ -2432,14 +2432,18 @@ class CLIProxyManager {
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = keys.map((key, index) => `
|
||||
container.innerHTML = keys.map((key, index) => {
|
||||
const normalizedKey = typeof key === 'string' ? key : String(key ?? '');
|
||||
const maskedDisplay = this.escapeHtml(this.maskApiKey(normalizedKey));
|
||||
const keyArgument = JSON.stringify(normalizedKey).replace(/"/g, '"');
|
||||
return `
|
||||
<div class="key-item">
|
||||
<div class="item-content">
|
||||
<div class="item-title">${i18n.t('api_keys.item_title')} #${index + 1}</div>
|
||||
<div class="item-value">${this.maskApiKey(key)}</div>
|
||||
<div class="item-value">${maskedDisplay}</div>
|
||||
</div>
|
||||
<div class="item-actions">
|
||||
<button class="btn btn-secondary" onclick="manager.editApiKey(${index}, '${key}')">
|
||||
<button class="btn btn-secondary" onclick="manager.editApiKey(${index}, ${keyArgument})">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button class="btn btn-danger" onclick="manager.deleteApiKey(${index})">
|
||||
@@ -2447,19 +2451,24 @@ class CLIProxyManager {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// 遮蔽API密钥显示
|
||||
maskApiKey(key) {
|
||||
if (key.length > 8) {
|
||||
return key.substring(0, 4) + '...' + key.substring(key.length - 4);
|
||||
} else if (key.length > 4) {
|
||||
return key.substring(0, 2) + '...' + key.substring(key.length - 2);
|
||||
} else if (key.length > 2) {
|
||||
return key.substring(0, 1) + '...' + key.substring(key.length - 1);
|
||||
if (key === null || key === undefined) {
|
||||
return '';
|
||||
}
|
||||
return key;
|
||||
const normalizedKey = typeof key === 'string' ? key : String(key);
|
||||
if (normalizedKey.length > 8) {
|
||||
return normalizedKey.substring(0, 4) + '...' + normalizedKey.substring(normalizedKey.length - 4);
|
||||
} else if (normalizedKey.length > 4) {
|
||||
return normalizedKey.substring(0, 2) + '...' + normalizedKey.substring(normalizedKey.length - 2);
|
||||
} else if (normalizedKey.length > 2) {
|
||||
return normalizedKey.substring(0, 1) + '...' + normalizedKey.substring(normalizedKey.length - 1);
|
||||
}
|
||||
return normalizedKey;
|
||||
}
|
||||
|
||||
// HTML 转义,防止 XSS
|
||||
@@ -2794,7 +2803,8 @@ class CLIProxyManager {
|
||||
|
||||
container.innerHTML = normalizedList.map((config, index) => {
|
||||
const rawKey = config['api-key'] || '';
|
||||
const masked = rawKey ? this.maskApiKey(rawKey) : '';
|
||||
const masked = this.maskApiKey(rawKey || '');
|
||||
const maskedDisplay = this.escapeHtml(masked);
|
||||
const keyStats = (rawKey && (stats[rawKey] || stats[masked])) || { success: 0, failure: 0 };
|
||||
const configJson = JSON.stringify(config).replace(/"/g, '"');
|
||||
const apiKeyJson = JSON.stringify(rawKey || '').replace(/"/g, '"');
|
||||
@@ -2803,7 +2813,7 @@ class CLIProxyManager {
|
||||
<div class="key-item">
|
||||
<div class="item-content">
|
||||
<div class="item-title">${i18n.t('ai_providers.gemini_item_title')} #${index + 1}</div>
|
||||
<div class="item-value">${this.maskApiKey(rawKey || '')}</div>
|
||||
<div class="item-value">${maskedDisplay}</div>
|
||||
${baseUrl ? `<div class="item-subtitle">${i18n.t('common.base_url')}: ${this.escapeHtml(baseUrl)}</div>` : ''}
|
||||
${this.renderHeaderBadges(config.headers)}
|
||||
<div class="item-stats">
|
||||
@@ -3070,14 +3080,16 @@ class CLIProxyManager {
|
||||
const stats = keyStats;
|
||||
|
||||
container.innerHTML = list.map((config, index) => {
|
||||
const rawKey = config['api-key'];
|
||||
const masked = rawKey ? this.maskApiKey(rawKey) : '';
|
||||
const rawKey = config['api-key'] || '';
|
||||
const masked = this.maskApiKey(rawKey || '');
|
||||
const maskedDisplay = this.escapeHtml(masked);
|
||||
const keyStats = (rawKey && (stats[rawKey] || stats[masked])) || { success: 0, failure: 0 };
|
||||
const deleteArg = JSON.stringify(rawKey).replace(/"/g, '"');
|
||||
return `
|
||||
<div class="provider-item">
|
||||
<div class="item-content">
|
||||
<div class="item-title">${i18n.t('ai_providers.codex_item_title')} #${index + 1}</div>
|
||||
<div class="item-subtitle">${i18n.t('common.api_key')}: ${this.maskApiKey(config['api-key'])}</div>
|
||||
<div class="item-subtitle">${i18n.t('common.api_key')}: ${maskedDisplay}</div>
|
||||
${config['base-url'] ? `<div class="item-subtitle">${i18n.t('common.base_url')}: ${this.escapeHtml(config['base-url'])}</div>` : ''}
|
||||
${config['proxy-url'] ? `<div class="item-subtitle">${i18n.t('common.proxy_url')}: ${this.escapeHtml(config['proxy-url'])}</div>` : ''}
|
||||
${this.renderHeaderBadges(config.headers)}
|
||||
@@ -3094,7 +3106,7 @@ class CLIProxyManager {
|
||||
<button class="btn btn-secondary" onclick="manager.editCodexKey(${index}, ${JSON.stringify(config).replace(/"/g, '"')})">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button class="btn btn-danger" onclick="manager.deleteCodexKey('${config['api-key']}')">
|
||||
<button class="btn btn-danger" onclick="manager.deleteCodexKey(${deleteArg})">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -3302,17 +3314,26 @@ class CLIProxyManager {
|
||||
const stats = keyStats;
|
||||
|
||||
container.innerHTML = list.map((config, index) => {
|
||||
const rawKey = config['api-key'];
|
||||
const masked = rawKey ? this.maskApiKey(rawKey) : '';
|
||||
const rawKey = config['api-key'] || '';
|
||||
const masked = this.maskApiKey(rawKey || '');
|
||||
const maskedDisplay = this.escapeHtml(masked);
|
||||
const keyStats = (rawKey && (stats[rawKey] || stats[masked])) || { success: 0, failure: 0 };
|
||||
const deleteArg = JSON.stringify(rawKey).replace(/"/g, '"');
|
||||
const models = Array.isArray(config.models) ? config.models : [];
|
||||
const modelsCountHtml = models.length
|
||||
? `<div class="item-subtitle">${i18n.t('ai_providers.claude_models_count')}: ${models.length}</div>`
|
||||
: '';
|
||||
const modelsBadgesHtml = this.renderModelBadges(models);
|
||||
return `
|
||||
<div class="provider-item">
|
||||
<div class="item-content">
|
||||
<div class="item-title">${i18n.t('ai_providers.claude_item_title')} #${index + 1}</div>
|
||||
<div class="item-subtitle">${i18n.t('common.api_key')}: ${this.maskApiKey(config['api-key'])}</div>
|
||||
<div class="item-subtitle">${i18n.t('common.api_key')}: ${maskedDisplay}</div>
|
||||
${config['base-url'] ? `<div class="item-subtitle">${i18n.t('common.base_url')}: ${this.escapeHtml(config['base-url'])}</div>` : ''}
|
||||
${config['proxy-url'] ? `<div class="item-subtitle">${i18n.t('common.proxy_url')}: ${this.escapeHtml(config['proxy-url'])}</div>` : ''}
|
||||
${this.renderHeaderBadges(config.headers)}
|
||||
${modelsCountHtml}
|
||||
${modelsBadgesHtml}
|
||||
<div class="item-stats">
|
||||
<span class="stat-badge stat-success">
|
||||
<i class="fas fa-check-circle"></i> ${i18n.t('stats.success')}: ${keyStats.success}
|
||||
@@ -3326,7 +3347,7 @@ class CLIProxyManager {
|
||||
<button class="btn btn-secondary" onclick="manager.editClaudeKey(${index}, ${JSON.stringify(config).replace(/"/g, '"')})">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button class="btn btn-danger" onclick="manager.deleteClaudeKey('${config['api-key']}')">
|
||||
<button class="btn btn-danger" onclick="manager.deleteClaudeKey(${deleteArg})">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -3359,6 +3380,12 @@ class CLIProxyManager {
|
||||
<div id="new-claude-headers-wrapper" class="header-input-list"></div>
|
||||
<button type="button" class="btn btn-secondary" onclick="manager.addHeaderField('new-claude-headers-wrapper')">${i18n.t('common.custom_headers_add')}</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>${i18n.t('ai_providers.claude_models_label')}</label>
|
||||
<p class="form-hint">${i18n.t('ai_providers.claude_models_hint')}</p>
|
||||
<div id="new-claude-models-wrapper" class="model-input-list"></div>
|
||||
<button type="button" class="btn btn-secondary" onclick="manager.addModelField('new-claude-models-wrapper')">${i18n.t('ai_providers.claude_models_add_btn')}</button>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button class="btn btn-secondary" onclick="manager.closeModal()">${i18n.t('common.cancel')}</button>
|
||||
<button class="btn btn-primary" onclick="manager.addClaudeKey()">${i18n.t('common.add')}</button>
|
||||
@@ -3367,6 +3394,7 @@ class CLIProxyManager {
|
||||
|
||||
modal.style.display = 'block';
|
||||
this.populateHeaderFields('new-claude-headers-wrapper');
|
||||
this.populateModelFields('new-claude-models-wrapper');
|
||||
}
|
||||
|
||||
// 添加Claude密钥
|
||||
@@ -3375,6 +3403,7 @@ class CLIProxyManager {
|
||||
const baseUrl = document.getElementById('new-claude-url').value.trim();
|
||||
const proxyUrl = document.getElementById('new-claude-proxy').value.trim();
|
||||
const headers = this.collectHeaderInputs('new-claude-headers-wrapper');
|
||||
const models = this.collectModelInputs('new-claude-models-wrapper');
|
||||
|
||||
if (!apiKey) {
|
||||
this.showNotification(i18n.t('notification.field_required'), 'error');
|
||||
@@ -3393,6 +3422,9 @@ class CLIProxyManager {
|
||||
newConfig['proxy-url'] = proxyUrl;
|
||||
}
|
||||
this.applyHeadersToConfig(newConfig, headers);
|
||||
if (models.length) {
|
||||
newConfig.models = models;
|
||||
}
|
||||
|
||||
currentKeys.push(newConfig);
|
||||
|
||||
@@ -3435,6 +3467,12 @@ class CLIProxyManager {
|
||||
<div id="edit-claude-headers-wrapper" class="header-input-list"></div>
|
||||
<button type="button" class="btn btn-secondary" onclick="manager.addHeaderField('edit-claude-headers-wrapper')">${i18n.t('common.custom_headers_add')}</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>${i18n.t('ai_providers.claude_models_label')}</label>
|
||||
<p class="form-hint">${i18n.t('ai_providers.claude_models_hint')}</p>
|
||||
<div id="edit-claude-models-wrapper" class="model-input-list"></div>
|
||||
<button type="button" class="btn btn-secondary" onclick="manager.addModelField('edit-claude-models-wrapper')">${i18n.t('ai_providers.claude_models_add_btn')}</button>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button class="btn btn-secondary" onclick="manager.closeModal()">${i18n.t('common.cancel')}</button>
|
||||
<button class="btn btn-primary" onclick="manager.updateClaudeKey(${index})">${i18n.t('common.update')}</button>
|
||||
@@ -3443,6 +3481,7 @@ class CLIProxyManager {
|
||||
|
||||
modal.style.display = 'block';
|
||||
this.populateHeaderFields('edit-claude-headers-wrapper', config.headers || null);
|
||||
this.populateModelFields('edit-claude-models-wrapper', Array.isArray(config.models) ? config.models : []);
|
||||
}
|
||||
|
||||
// 更新Claude密钥
|
||||
@@ -3451,6 +3490,7 @@ class CLIProxyManager {
|
||||
const baseUrl = document.getElementById('edit-claude-url').value.trim();
|
||||
const proxyUrl = document.getElementById('edit-claude-proxy').value.trim();
|
||||
const headers = this.collectHeaderInputs('edit-claude-headers-wrapper');
|
||||
const models = this.collectModelInputs('edit-claude-models-wrapper');
|
||||
|
||||
if (!apiKey) {
|
||||
this.showNotification(i18n.t('notification.field_required'), 'error');
|
||||
@@ -3466,6 +3506,9 @@ class CLIProxyManager {
|
||||
newConfig['proxy-url'] = proxyUrl;
|
||||
}
|
||||
this.applyHeadersToConfig(newConfig, headers);
|
||||
if (models.length) {
|
||||
newConfig.models = models;
|
||||
}
|
||||
|
||||
await this.makeRequest('/claude-api-key', {
|
||||
method: 'PATCH',
|
||||
@@ -3572,6 +3615,7 @@ class CLIProxyManager {
|
||||
totalFailure += keyStats.failure;
|
||||
});
|
||||
|
||||
const deleteArg = JSON.stringify(name).replace(/"/g, '"');
|
||||
return `
|
||||
<div class="provider-item">
|
||||
<div class="item-content">
|
||||
@@ -3580,7 +3624,7 @@ class CLIProxyManager {
|
||||
${this.renderHeaderBadges(item.headers)}
|
||||
<div class="item-subtitle">${i18n.t('ai_providers.openai_keys_count')}: ${apiKeyEntries.length}</div>
|
||||
<div class="item-subtitle">${i18n.t('ai_providers.openai_models_count')}: ${models.length}</div>
|
||||
${this.renderOpenAIModelBadges(models)}
|
||||
${this.renderModelBadges(models)}
|
||||
<div class="item-stats">
|
||||
<span class="stat-badge stat-success">
|
||||
<i class="fas fa-check-circle"></i> ${i18n.t('stats.success')}: ${totalSuccess}
|
||||
@@ -3594,7 +3638,7 @@ class CLIProxyManager {
|
||||
<button class="btn btn-secondary" onclick="manager.editOpenAIProvider(${index}, ${JSON.stringify(item).replace(/"/g, '"')})">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button class="btn btn-danger" onclick="manager.deleteOpenAIProvider('${this.escapeHtml(name)}')">
|
||||
<button class="btn btn-danger" onclick="manager.deleteOpenAIProvider(${deleteArg})">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -3830,7 +3874,22 @@ class CLIProxyManager {
|
||||
// 渲染认证文件列表
|
||||
async renderAuthFiles(files, keyStats = null) {
|
||||
const container = document.getElementById('auth-files-list');
|
||||
const visibleFiles = Array.isArray(files) ? files.filter(file => file.disabled !== true) : [];
|
||||
const isRuntimeOnlyFile = (file) => {
|
||||
if (!file) return false;
|
||||
const runtimeValue = file.runtime_only;
|
||||
return runtimeValue === true || runtimeValue === 'true';
|
||||
};
|
||||
const shouldDisplayDisabledGeminiCli = (file) => {
|
||||
if (!file) return false;
|
||||
const provider = typeof file.provider === 'string' ? file.provider.toLowerCase() : '';
|
||||
const type = typeof file.type === 'string' ? file.type.toLowerCase() : '';
|
||||
const isGeminiCli = provider === 'gemini-cli' || type === 'gemini-cli';
|
||||
return isGeminiCli && !isRuntimeOnlyFile(file);
|
||||
};
|
||||
const visibleFiles = Array.isArray(files) ? files.filter(file => {
|
||||
if (!file) return false;
|
||||
return shouldDisplayDisabledGeminiCli(file) || file.disabled !== true;
|
||||
}) : [];
|
||||
this.cachedAuthFiles = visibleFiles.map(file => ({ ...file }));
|
||||
|
||||
if (visibleFiles.length === 0) {
|
||||
@@ -3865,14 +3924,16 @@ class CLIProxyManager {
|
||||
this.updateFilterButtons(existingTypes);
|
||||
|
||||
container.innerHTML = visibleFiles.map(file => {
|
||||
const rawFileName = typeof file.name === 'string' ? file.name : '';
|
||||
const safeFileName = this.escapeHtml(rawFileName);
|
||||
// 认证文件的统计匹配逻辑:
|
||||
// 1. 首先尝试完整文件名匹配
|
||||
// 2. 如果没有匹配,尝试脱敏文件名匹配(去掉扩展名后的脱敏版本)
|
||||
let fileStats = stats[file.name] || { success: 0, failure: 0 };
|
||||
let fileStats = stats[rawFileName] || { success: 0, failure: 0 };
|
||||
|
||||
// 如果完整文件名没有统计,尝试基于文件名的脱敏版本匹配
|
||||
if (fileStats.success === 0 && fileStats.failure === 0) {
|
||||
const nameWithoutExt = file.name.replace(/\.[^/.]+$/, ""); // 去掉扩展名
|
||||
const nameWithoutExt = rawFileName.replace(/\.[^/.]+$/, ""); // 去掉扩展名
|
||||
|
||||
const possibleSources = [];
|
||||
|
||||
@@ -3951,15 +4012,20 @@ class CLIProxyManager {
|
||||
}
|
||||
const typeBadge = `<span class="file-type-badge ${fileType}">${i18n.t(typeDisplayKey)}</span>`;
|
||||
|
||||
// 检查是否为 runtime-only 文件
|
||||
const isRuntimeOnly = file.runtime_only === true;
|
||||
// Determine whether the entry is runtime-only
|
||||
const isRuntimeOnly = isRuntimeOnlyFile(file);
|
||||
|
||||
// 生成操作按钮 HTML,runtime-only 文件显示虚拟标记
|
||||
const shouldShowMainFlag = shouldDisplayDisabledGeminiCli(file);
|
||||
const mainFlagButton = shouldShowMainFlag ? `
|
||||
<button class="btn-small btn-warning main-flag-btn" title="主" disabled>主</button>` : '';
|
||||
|
||||
// Build action buttons; runtime-only entries display placeholder badge
|
||||
const actionsHtml = isRuntimeOnly ? `
|
||||
<div class="item-actions">
|
||||
<span class="virtual-auth-badge">虚拟认证文件</span>
|
||||
</div>` : `
|
||||
<div class="item-actions" data-filename="${file.name}">
|
||||
<div class="item-actions" data-filename="${safeFileName}">
|
||||
${mainFlagButton}
|
||||
<button class="btn-small btn-info" data-action="showDetails" title="详细信息">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
</button>
|
||||
@@ -3972,9 +4038,9 @@ class CLIProxyManager {
|
||||
</div>`;
|
||||
|
||||
return `
|
||||
<div class="file-item" data-file-type="${fileType}" data-file-name="${this.escapeHtml(file.name)}" ${isRuntimeOnly ? 'data-runtime-only="true"' : ''}>
|
||||
<div class="file-item" data-file-type="${fileType}" data-file-name="${safeFileName}" ${isRuntimeOnly ? 'data-runtime-only="true"' : ''}>
|
||||
<div class="item-content">
|
||||
<div class="item-title">${typeBadge}${file.name}</div>
|
||||
<div class="item-title">${typeBadge}${safeFileName}</div>
|
||||
<div class="item-meta">
|
||||
<span class="item-subtitle">${i18n.t('auth_files.file_modified')}: ${new Date(file.modtime).toLocaleString(i18n.currentLanguage === 'zh-CN' ? 'zh-CN' : 'en-US')}</span>
|
||||
<span class="item-subtitle">${i18n.t('auth_files.file_size')}: ${this.formatFileSize(file.size)}</span>
|
||||
@@ -4463,11 +4529,12 @@ class CLIProxyManager {
|
||||
// 显示JSON模态框
|
||||
showJsonModal(filename, jsonContent) {
|
||||
// 创建模态框HTML
|
||||
const safeFilename = this.escapeHtml(filename || '');
|
||||
const modalHtml = `
|
||||
<div id="json-modal" class="json-modal">
|
||||
<div class="json-modal-content">
|
||||
<div class="json-modal-header">
|
||||
<h3><i class="fas fa-file-code"></i> ${filename}</h3>
|
||||
<h3><i class="fas fa-file-code"></i> ${safeFilename}</h3>
|
||||
<button class="json-modal-close" data-action="close">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
@@ -5850,8 +5917,8 @@ class CLIProxyManager {
|
||||
row.className = 'model-input-row';
|
||||
row.innerHTML = `
|
||||
<div class="input-group">
|
||||
<input type="text" class="model-name-input" placeholder="${i18n.t('ai_providers.openai_model_name_placeholder')}" value="${model.name ? this.escapeHtml(model.name) : ''}">
|
||||
<input type="text" class="model-alias-input" placeholder="${i18n.t('ai_providers.openai_model_alias_placeholder')}" value="${model.alias ? this.escapeHtml(model.alias) : ''}">
|
||||
<input type="text" class="model-name-input" placeholder="${i18n.t('common.model_name_placeholder')}" value="${model.name ? this.escapeHtml(model.name) : ''}">
|
||||
<input type="text" class="model-alias-input" placeholder="${i18n.t('common.model_alias_placeholder')}" value="${model.alias ? this.escapeHtml(model.alias) : ''}">
|
||||
<button type="button" class="btn btn-small btn-danger model-remove-btn"><i class="fas fa-trash"></i></button>
|
||||
</div>
|
||||
`;
|
||||
@@ -5904,7 +5971,7 @@ class CLIProxyManager {
|
||||
return models;
|
||||
}
|
||||
|
||||
renderOpenAIModelBadges(models) {
|
||||
renderModelBadges(models) {
|
||||
if (!models || models.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
12
i18n.js
12
i18n.js
@@ -46,6 +46,8 @@ const i18n = {
|
||||
'common.custom_headers_add': '添加请求头',
|
||||
'common.custom_headers_key_placeholder': 'Header 名称,例如 X-Custom-Header',
|
||||
'common.custom_headers_value_placeholder': 'Header 值',
|
||||
'common.model_name_placeholder': '模型名称,例如 claude-3-5-sonnet-20241022',
|
||||
'common.model_alias_placeholder': '模型别名 (可选)',
|
||||
|
||||
// 页面标题
|
||||
'title.main': 'CLI Proxy API Management Center',
|
||||
@@ -184,6 +186,10 @@ const i18n = {
|
||||
'ai_providers.claude_edit_modal_url_label': 'Base URL (可选):',
|
||||
'ai_providers.claude_edit_modal_proxy_label': '代理 URL (可选):',
|
||||
'ai_providers.claude_delete_confirm': '确定要删除这个Claude配置吗?',
|
||||
'ai_providers.claude_models_label': '自定义模型 (可选):',
|
||||
'ai_providers.claude_models_hint': '为空表示使用全部模型;可填写 name[, alias] 以限制或重命名模型。',
|
||||
'ai_providers.claude_models_add_btn': '添加模型',
|
||||
'ai_providers.claude_models_count': '模型数量',
|
||||
|
||||
'ai_providers.openai_title': 'OpenAI 兼容提供商',
|
||||
'ai_providers.openai_add_button': '添加提供商',
|
||||
@@ -533,6 +539,8 @@ const i18n = {
|
||||
'common.custom_headers_add': 'Add Header',
|
||||
'common.custom_headers_key_placeholder': 'Header name, e.g. X-Custom-Header',
|
||||
'common.custom_headers_value_placeholder': 'Header value',
|
||||
'common.model_name_placeholder': 'Model name, e.g. claude-3-5-sonnet-20241022',
|
||||
'common.model_alias_placeholder': 'Model alias (optional)',
|
||||
|
||||
// Page titles
|
||||
'title.main': 'CLI Proxy API Management Center',
|
||||
@@ -671,6 +679,10 @@ const i18n = {
|
||||
'ai_providers.claude_edit_modal_url_label': 'Base URL (Optional):',
|
||||
'ai_providers.claude_edit_modal_proxy_label': 'Proxy URL (Optional):',
|
||||
'ai_providers.claude_delete_confirm': 'Are you sure you want to delete this Claude configuration?',
|
||||
'ai_providers.claude_models_label': 'Custom Models (Optional):',
|
||||
'ai_providers.claude_models_hint': 'Leave empty to allow all models, or add name[, alias] entries to limit/alias them.',
|
||||
'ai_providers.claude_models_add_btn': 'Add Model',
|
||||
'ai_providers.claude_models_count': 'Models Count',
|
||||
|
||||
'ai_providers.openai_title': 'OpenAI Compatible Providers',
|
||||
'ai_providers.openai_add_button': 'Add Provider',
|
||||
|
||||
15
styles.css
15
styles.css
@@ -1248,6 +1248,21 @@ body {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-small.btn-warning {
|
||||
background: #ff9800;
|
||||
color: #fff;
|
||||
border-color: #ff9800;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.btn-small.btn-warning:disabled,
|
||||
.btn-small.btn-warning[disabled] {
|
||||
background: #fb8c00;
|
||||
border-color: #fb8c00;
|
||||
color: #fff;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.btn-small i {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user