// CLI Proxy API 管理界面 JavaScript
class CLIProxyManager {
constructor() {
// 仅保存基础地址(不含 /v0/management),请求时自动补齐
this.apiBase = 'http://localhost:8317';
this.apiUrl = this.computeApiUrl(this.apiBase);
this.managementKey = '';
this.isConnected = false;
this.init();
}
// 简易防抖,减少频繁写 localStorage
debounce(fn, delay = 400) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
init() {
this.bindEvents();
this.loadSettings();
this.setupNavigation();
}
// 事件绑定
bindEvents() {
// 认证相关
document.getElementById('test-connection').addEventListener('click', () => this.testConnection());
document.getElementById('toggle-key-visibility').addEventListener('click', () => this.toggleKeyVisibility());
// 连接状态检查
document.getElementById('connection-status').addEventListener('click', () => this.checkConnectionStatus());
document.getElementById('refresh-all').addEventListener('click', () => this.refreshAllData());
// 基础设置
document.getElementById('debug-toggle').addEventListener('change', (e) => this.updateDebug(e.target.checked));
document.getElementById('update-proxy').addEventListener('click', () => this.updateProxyUrl());
document.getElementById('clear-proxy').addEventListener('click', () => this.clearProxyUrl());
document.getElementById('update-retry').addEventListener('click', () => this.updateRequestRetry());
document.getElementById('switch-project-toggle').addEventListener('change', (e) => this.updateSwitchProject(e.target.checked));
document.getElementById('switch-preview-model-toggle').addEventListener('change', (e) => this.updateSwitchPreviewModel(e.target.checked));
document.getElementById('allow-localhost-toggle').addEventListener('change', (e) => this.updateAllowLocalhost(e.target.checked));
// API 密钥管理
document.getElementById('add-api-key').addEventListener('click', () => this.showAddApiKeyModal());
document.getElementById('add-gemini-key').addEventListener('click', () => this.showAddGeminiKeyModal());
document.getElementById('add-codex-key').addEventListener('click', () => this.showAddCodexKeyModal());
document.getElementById('add-claude-key').addEventListener('click', () => this.showAddClaudeKeyModal());
document.getElementById('add-openai-provider').addEventListener('click', () => this.showAddOpenAIProviderModal());
// 认证文件管理
document.getElementById('upload-auth-file').addEventListener('click', () => this.uploadAuthFile());
document.getElementById('delete-all-auth-files').addEventListener('click', () => this.deleteAllAuthFiles());
document.getElementById('auth-file-input').addEventListener('change', (e) => this.handleFileUpload(e));
// 模态框
document.querySelector('.close').addEventListener('click', () => this.closeModal());
window.addEventListener('click', (e) => {
if (e.target === document.getElementById('modal')) {
this.closeModal();
}
});
}
// 设置导航
setupNavigation() {
const navItems = document.querySelectorAll('.nav-item');
navItems.forEach(item => {
item.addEventListener('click', (e) => {
e.preventDefault();
// 移除所有活动状态
navItems.forEach(nav => nav.classList.remove('active'));
document.querySelectorAll('.content-section').forEach(section => section.classList.remove('active'));
// 添加活动状态
item.classList.add('active');
const sectionId = item.getAttribute('data-section');
document.getElementById(sectionId).classList.add('active');
});
});
}
// 规范化基础地址,移除尾部斜杠与 /v0/management
normalizeBase(input) {
let base = (input || '').trim();
if (!base) return '';
// 若用户粘贴了完整地址,剥离后缀
base = base.replace(/\/?v0\/management\/?$/i, '');
base = base.replace(/\/+$/i, '');
// 自动补 http://
if (!/^https?:\/\//i.test(base)) {
base = 'http://' + base;
}
return base;
}
// 由基础地址生成完整管理 API 地址
computeApiUrl(base) {
const b = this.normalizeBase(base);
if (!b) return '';
return b.replace(/\/$/, '') + '/v0/management';
}
setApiBase(newBase) {
this.apiBase = this.normalizeBase(newBase);
this.apiUrl = this.computeApiUrl(this.apiBase);
localStorage.setItem('apiBase', this.apiBase);
localStorage.setItem('apiUrl', this.apiUrl); // 兼容旧字段
}
// 加载设置
loadSettings() {
const savedBase = localStorage.getItem('apiBase');
const savedUrl = localStorage.getItem('apiUrl');
const savedKey = localStorage.getItem('managementKey');
if (savedBase) {
this.setApiBase(savedBase);
document.getElementById('api-url').value = this.apiBase;
} else if (savedUrl) {
const base = (savedUrl || '').replace(/\/?v0\/management\/?$/i, '');
this.setApiBase(base);
document.getElementById('api-url').value = this.apiBase;
} else {
this.setApiBase(this.apiBase);
document.getElementById('api-url').value = this.apiBase;
}
if (savedKey) {
document.getElementById('management-key').value = savedKey;
this.managementKey = savedKey;
}
// 监听API URL和密钥变化
const apiInput = document.getElementById('api-url');
const keyInput = document.getElementById('management-key');
const saveBase = (val) => this.setApiBase(val);
const saveBaseDebounced = this.debounce(saveBase, 500);
apiInput.addEventListener('change', (e) => saveBase(e.target.value));
apiInput.addEventListener('input', (e) => saveBaseDebounced(e.target.value));
const saveKey = (val) => {
this.managementKey = val;
localStorage.setItem('managementKey', this.managementKey);
};
const saveKeyDebounced = this.debounce(saveKey, 500);
keyInput.addEventListener('change', (e) => saveKey(e.target.value));
keyInput.addEventListener('input', (e) => saveKeyDebounced(e.target.value));
}
// API 请求方法
async makeRequest(endpoint, options = {}) {
const url = `${this.apiUrl}${endpoint}`;
const headers = {
'Authorization': `Bearer ${this.managementKey}`,
'Content-Type': 'application/json',
...options.headers
};
try {
const response = await fetch(url, {
...options,
headers
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error || `HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('API请求失败:', error);
throw error;
}
}
// 显示通知
showNotification(message, type = 'info') {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.className = `notification ${type}`;
notification.classList.add('show');
setTimeout(() => {
notification.classList.remove('show');
}, 3000);
}
// 密钥可见性切换
toggleKeyVisibility() {
const keyInput = document.getElementById('management-key');
const toggleButton = document.getElementById('toggle-key-visibility');
if (keyInput.type === 'password') {
keyInput.type = 'text';
toggleButton.innerHTML = '';
} else {
keyInput.type = 'password';
toggleButton.innerHTML = '';
}
}
// 测试连接
async testConnection() {
const button = document.getElementById('test-connection');
const originalText = button.innerHTML;
button.innerHTML = '
连接中...';
button.disabled = true;
try {
await this.makeRequest('/debug');
this.isConnected = true;
this.showNotification('连接成功!', 'success');
this.updateConnectionStatus();
await this.loadAllData();
} catch (error) {
this.isConnected = false;
this.showNotification(`连接失败: ${error.message}`, 'error');
this.updateConnectionStatus();
} finally {
button.innerHTML = originalText;
button.disabled = false;
}
}
// 更新连接状态
updateConnectionStatus() {
const statusButton = document.getElementById('connection-status');
const apiStatus = document.getElementById('api-status');
const lastUpdate = document.getElementById('last-update');
if (this.isConnected) {
statusButton.innerHTML = ' 已连接';
statusButton.className = 'btn btn-success';
apiStatus.textContent = '已连接';
} else {
statusButton.innerHTML = ' 未连接';
statusButton.className = 'btn btn-danger';
apiStatus.textContent = '未连接';
}
lastUpdate.textContent = new Date().toLocaleString('zh-CN');
}
// 检查连接状态
async checkConnectionStatus() {
await this.testConnection();
}
// 刷新所有数据
async refreshAllData() {
if (!this.isConnected) {
this.showNotification('请先建立连接', 'error');
return;
}
const button = document.getElementById('refresh-all');
const originalText = button.innerHTML;
button.innerHTML = ' 刷新中...';
button.disabled = true;
try {
await this.loadAllData();
this.showNotification('数据刷新成功', 'success');
} catch (error) {
this.showNotification(`刷新失败: ${error.message}`, 'error');
} finally {
button.innerHTML = originalText;
button.disabled = false;
}
}
// 加载所有数据
async loadAllData() {
await Promise.all([
this.loadDebugSettings(),
this.loadProxySettings(),
this.loadRetrySettings(),
this.loadQuotaSettings(),
this.loadLocalhostSettings(),
this.loadApiKeys(),
this.loadGeminiKeys(),
this.loadCodexKeys(),
this.loadClaudeKeys(),
this.loadOpenAIProviders(),
this.loadAuthFiles()
]);
}
// 加载调试设置
async loadDebugSettings() {
try {
const data = await this.makeRequest('/debug');
document.getElementById('debug-toggle').checked = data.debug;
} catch (error) {
console.error('加载调试设置失败:', error);
}
}
// 更新调试设置
async updateDebug(enabled) {
try {
await this.makeRequest('/debug', {
method: 'PUT',
body: JSON.stringify({ value: enabled })
});
this.showNotification('调试设置已更新', 'success');
} catch (error) {
this.showNotification(`更新调试设置失败: ${error.message}`, 'error');
// 恢复原状态
document.getElementById('debug-toggle').checked = !enabled;
}
}
// 加载代理设置
async loadProxySettings() {
try {
const data = await this.makeRequest('/proxy-url');
document.getElementById('proxy-url').value = data['proxy-url'] || '';
} catch (error) {
console.error('加载代理设置失败:', error);
}
}
// 更新代理URL
async updateProxyUrl() {
const proxyUrl = document.getElementById('proxy-url').value.trim();
try {
await this.makeRequest('/proxy-url', {
method: 'PUT',
body: JSON.stringify({ value: proxyUrl })
});
this.showNotification('代理设置已更新', 'success');
} catch (error) {
this.showNotification(`更新代理设置失败: ${error.message}`, 'error');
}
}
// 清空代理URL
async clearProxyUrl() {
try {
await this.makeRequest('/proxy-url', { method: 'DELETE' });
document.getElementById('proxy-url').value = '';
this.showNotification('代理设置已清空', 'success');
} catch (error) {
this.showNotification(`清空代理设置失败: ${error.message}`, 'error');
}
}
// 加载重试设置
async loadRetrySettings() {
try {
const data = await this.makeRequest('/request-retry');
document.getElementById('request-retry').value = data['request-retry'];
} catch (error) {
console.error('加载重试设置失败:', error);
}
}
// 更新请求重试
async updateRequestRetry() {
const retryCount = parseInt(document.getElementById('request-retry').value);
try {
await this.makeRequest('/request-retry', {
method: 'PUT',
body: JSON.stringify({ value: retryCount })
});
this.showNotification('重试设置已更新', 'success');
} catch (error) {
this.showNotification(`更新重试设置失败: ${error.message}`, 'error');
}
}
// 加载配额设置
async loadQuotaSettings() {
try {
const [switchProject, switchPreview] = await Promise.all([
this.makeRequest('/quota-exceeded/switch-project'),
this.makeRequest('/quota-exceeded/switch-preview-model')
]);
document.getElementById('switch-project-toggle').checked = switchProject['switch-project'];
document.getElementById('switch-preview-model-toggle').checked = switchPreview['switch-preview-model'];
} catch (error) {
console.error('加载配额设置失败:', error);
}
}
// 更新项目切换设置
async updateSwitchProject(enabled) {
try {
await this.makeRequest('/quota-exceeded/switch-project', {
method: 'PUT',
body: JSON.stringify({ value: enabled })
});
this.showNotification('项目切换设置已更新', 'success');
} catch (error) {
this.showNotification(`更新项目切换设置失败: ${error.message}`, 'error');
document.getElementById('switch-project-toggle').checked = !enabled;
}
}
// 更新预览模型切换设置
async updateSwitchPreviewModel(enabled) {
try {
await this.makeRequest('/quota-exceeded/switch-preview-model', {
method: 'PUT',
body: JSON.stringify({ value: enabled })
});
this.showNotification('预览模型切换设置已更新', 'success');
} catch (error) {
this.showNotification(`更新预览模型切换设置失败: ${error.message}`, 'error');
document.getElementById('switch-preview-model-toggle').checked = !enabled;
}
}
// 加载本地访问设置
async loadLocalhostSettings() {
try {
const data = await this.makeRequest('/allow-localhost-unauthenticated');
document.getElementById('allow-localhost-toggle').checked = data['allow-localhost-unauthenticated'];
} catch (error) {
console.error('加载本地访问设置失败:', error);
}
}
// 更新本地访问设置
async updateAllowLocalhost(enabled) {
try {
await this.makeRequest('/allow-localhost-unauthenticated', {
method: 'PUT',
body: JSON.stringify({ value: enabled })
});
this.showNotification('本地访问设置已更新', 'success');
} catch (error) {
this.showNotification(`更新本地访问设置失败: ${error.message}`, 'error');
document.getElementById('allow-localhost-toggle').checked = !enabled;
}
}
// 加载API密钥
async loadApiKeys() {
try {
const data = await this.makeRequest('/api-keys');
this.renderApiKeys(data['api-keys'] || []);
} catch (error) {
console.error('加载API密钥失败:', error);
}
}
// 渲染API密钥列表
renderApiKeys(keys) {
const container = document.getElementById('api-keys-list');
if (keys.length === 0) {
container.innerHTML = `
`;
return;
}
container.innerHTML = keys.map((key, index) => `
API密钥 #${index + 1}
${this.maskApiKey(key)}
`).join('');
}
// 遮蔽API密钥显示
maskApiKey(key) {
if (key.length <= 8) return key;
return key.substring(0, 4) + '...' + key.substring(key.length - 4);
}
// 显示添加API密钥模态框
showAddApiKeyModal() {
const modal = document.getElementById('modal');
const modalBody = document.getElementById('modal-body');
modalBody.innerHTML = `
添加API密钥
`;
modal.style.display = 'block';
}
// 添加API密钥
async addApiKey() {
const newKey = document.getElementById('new-api-key').value.trim();
if (!newKey) {
this.showNotification('请输入API密钥', 'error');
return;
}
try {
const data = await this.makeRequest('/api-keys');
const currentKeys = data['api-keys'] || [];
currentKeys.push(newKey);
await this.makeRequest('/api-keys', {
method: 'PUT',
body: JSON.stringify(currentKeys)
});
this.closeModal();
this.loadApiKeys();
this.showNotification('API密钥添加成功', 'success');
} catch (error) {
this.showNotification(`添加API密钥失败: ${error.message}`, 'error');
}
}
// 编辑API密钥
editApiKey(index, currentKey) {
const modal = document.getElementById('modal');
const modalBody = document.getElementById('modal-body');
modalBody.innerHTML = `
编辑API密钥
`;
modal.style.display = 'block';
}
// 更新API密钥
async updateApiKey(index) {
const newKey = document.getElementById('edit-api-key').value.trim();
if (!newKey) {
this.showNotification('请输入API密钥', 'error');
return;
}
try {
await this.makeRequest('/api-keys', {
method: 'PATCH',
body: JSON.stringify({ index, value: newKey })
});
this.closeModal();
this.loadApiKeys();
this.showNotification('API密钥更新成功', 'success');
} catch (error) {
this.showNotification(`更新API密钥失败: ${error.message}`, 'error');
}
}
// 删除API密钥
async deleteApiKey(index) {
if (!confirm('确定要删除这个API密钥吗?')) return;
try {
await this.makeRequest(`/api-keys?index=${index}`, { method: 'DELETE' });
this.loadApiKeys();
this.showNotification('API密钥删除成功', 'success');
} catch (error) {
this.showNotification(`删除API密钥失败: ${error.message}`, 'error');
}
}
// 加载Gemini密钥
async loadGeminiKeys() {
try {
const data = await this.makeRequest('/generative-language-api-key');
this.renderGeminiKeys(data['generative-language-api-key'] || []);
} catch (error) {
console.error('加载Gemini密钥失败:', error);
}
}
// 渲染Gemini密钥列表
renderGeminiKeys(keys) {
const container = document.getElementById('gemini-keys-list');
if (keys.length === 0) {
container.innerHTML = `
`;
return;
}
container.innerHTML = keys.map((key, index) => `
Gemini密钥 #${index + 1}
${this.maskApiKey(key)}
`).join('');
}
// 显示添加Gemini密钥模态框
showAddGeminiKeyModal() {
const modal = document.getElementById('modal');
const modalBody = document.getElementById('modal-body');
modalBody.innerHTML = `
添加Gemini API密钥
`;
modal.style.display = 'block';
}
// 添加Gemini密钥
async addGeminiKey() {
const newKey = document.getElementById('new-gemini-key').value.trim();
if (!newKey) {
this.showNotification('请输入Gemini API密钥', 'error');
return;
}
try {
const data = await this.makeRequest('/generative-language-api-key');
const currentKeys = data['generative-language-api-key'] || [];
currentKeys.push(newKey);
await this.makeRequest('/generative-language-api-key', {
method: 'PUT',
body: JSON.stringify(currentKeys)
});
this.closeModal();
this.loadGeminiKeys();
this.showNotification('Gemini密钥添加成功', 'success');
} catch (error) {
this.showNotification(`添加Gemini密钥失败: ${error.message}`, 'error');
}
}
// 编辑Gemini密钥
editGeminiKey(index, currentKey) {
const modal = document.getElementById('modal');
const modalBody = document.getElementById('modal-body');
modalBody.innerHTML = `
编辑Gemini API密钥
`;
modal.style.display = 'block';
}
// 更新Gemini密钥
async updateGeminiKey(oldKey) {
const newKey = document.getElementById('edit-gemini-key').value.trim();
if (!newKey) {
this.showNotification('请输入Gemini API密钥', 'error');
return;
}
try {
await this.makeRequest('/generative-language-api-key', {
method: 'PATCH',
body: JSON.stringify({ old: oldKey, new: newKey })
});
this.closeModal();
this.loadGeminiKeys();
this.showNotification('Gemini密钥更新成功', 'success');
} catch (error) {
this.showNotification(`更新Gemini密钥失败: ${error.message}`, 'error');
}
}
// 删除Gemini密钥
async deleteGeminiKey(key) {
if (!confirm('确定要删除这个Gemini密钥吗?')) return;
try {
await this.makeRequest(`/generative-language-api-key?value=${encodeURIComponent(key)}`, { method: 'DELETE' });
this.loadGeminiKeys();
this.showNotification('Gemini密钥删除成功', 'success');
} catch (error) {
this.showNotification(`删除Gemini密钥失败: ${error.message}`, 'error');
}
}
// 加载Codex密钥
async loadCodexKeys() {
try {
const data = await this.makeRequest('/codex-api-key');
this.renderCodexKeys(data['codex-api-key'] || []);
} catch (error) {
console.error('加载Codex密钥失败:', error);
}
}
// 渲染Codex密钥列表
renderCodexKeys(keys) {
const container = document.getElementById('codex-keys-list');
if (keys.length === 0) {
container.innerHTML = `
`;
return;
}
container.innerHTML = keys.map((config, index) => `
Codex配置 #${index + 1}
密钥: ${this.maskApiKey(config['api-key'])}
${config['base-url'] ? `
地址: ${config['base-url']}
` : ''}
`).join('');
}
// 显示添加Codex密钥模态框
showAddCodexKeyModal() {
const modal = document.getElementById('modal');
const modalBody = document.getElementById('modal-body');
modalBody.innerHTML = `
添加Codex API配置
`;
modal.style.display = 'block';
}
// 添加Codex密钥
async addCodexKey() {
const apiKey = document.getElementById('new-codex-key').value.trim();
const baseUrl = document.getElementById('new-codex-url').value.trim();
if (!apiKey) {
this.showNotification('请输入API密钥', 'error');
return;
}
try {
const data = await this.makeRequest('/codex-api-key');
const currentKeys = data['codex-api-key'] || [];
const newConfig = { 'api-key': apiKey };
if (baseUrl) {
newConfig['base-url'] = baseUrl;
}
currentKeys.push(newConfig);
await this.makeRequest('/codex-api-key', {
method: 'PUT',
body: JSON.stringify(currentKeys)
});
this.closeModal();
this.loadCodexKeys();
this.showNotification('Codex配置添加成功', 'success');
} catch (error) {
this.showNotification(`添加Codex配置失败: ${error.message}`, 'error');
}
}
// 编辑Codex密钥
editCodexKey(index, config) {
const modal = document.getElementById('modal');
const modalBody = document.getElementById('modal-body');
modalBody.innerHTML = `
编辑Codex API配置
`;
modal.style.display = 'block';
}
// 更新Codex密钥
async updateCodexKey(index) {
const apiKey = document.getElementById('edit-codex-key').value.trim();
const baseUrl = document.getElementById('edit-codex-url').value.trim();
if (!apiKey) {
this.showNotification('请输入API密钥', 'error');
return;
}
try {
const newConfig = { 'api-key': apiKey };
if (baseUrl) {
newConfig['base-url'] = baseUrl;
}
await this.makeRequest('/codex-api-key', {
method: 'PATCH',
body: JSON.stringify({ index, value: newConfig })
});
this.closeModal();
this.loadCodexKeys();
this.showNotification('Codex配置更新成功', 'success');
} catch (error) {
this.showNotification(`更新Codex配置失败: ${error.message}`, 'error');
}
}
// 删除Codex密钥
async deleteCodexKey(apiKey) {
if (!confirm('确定要删除这个Codex配置吗?')) return;
try {
await this.makeRequest(`/codex-api-key?api-key=${encodeURIComponent(apiKey)}`, { method: 'DELETE' });
this.loadCodexKeys();
this.showNotification('Codex配置删除成功', 'success');
} catch (error) {
this.showNotification(`删除Codex配置失败: ${error.message}`, 'error');
}
}
// 加载Claude密钥
async loadClaudeKeys() {
try {
const data = await this.makeRequest('/claude-api-key');
this.renderClaudeKeys(data['claude-api-key'] || []);
} catch (error) {
console.error('加载Claude密钥失败:', error);
}
}
// 渲染Claude密钥列表
renderClaudeKeys(keys) {
const container = document.getElementById('claude-keys-list');
if (keys.length === 0) {
container.innerHTML = `
`;
return;
}
container.innerHTML = keys.map((config, index) => `
Claude配置 #${index + 1}
密钥: ${this.maskApiKey(config['api-key'])}
${config['base-url'] ? `
地址: ${config['base-url']}
` : ''}
`).join('');
}
// 显示添加Claude密钥模态框
showAddClaudeKeyModal() {
const modal = document.getElementById('modal');
const modalBody = document.getElementById('modal-body');
modalBody.innerHTML = `
添加Claude API配置
`;
modal.style.display = 'block';
}
// 添加Claude密钥
async addClaudeKey() {
const apiKey = document.getElementById('new-claude-key').value.trim();
const baseUrl = document.getElementById('new-claude-url').value.trim();
if (!apiKey) {
this.showNotification('请输入API密钥', 'error');
return;
}
try {
const data = await this.makeRequest('/claude-api-key');
const currentKeys = data['claude-api-key'] || [];
const newConfig = { 'api-key': apiKey };
if (baseUrl) {
newConfig['base-url'] = baseUrl;
}
currentKeys.push(newConfig);
await this.makeRequest('/claude-api-key', {
method: 'PUT',
body: JSON.stringify(currentKeys)
});
this.closeModal();
this.loadClaudeKeys();
this.showNotification('Claude配置添加成功', 'success');
} catch (error) {
this.showNotification(`添加Claude配置失败: ${error.message}`, 'error');
}
}
// 编辑Claude密钥
editClaudeKey(index, config) {
const modal = document.getElementById('modal');
const modalBody = document.getElementById('modal-body');
modalBody.innerHTML = `
编辑Claude API配置
`;
modal.style.display = 'block';
}
// 更新Claude密钥
async updateClaudeKey(index) {
const apiKey = document.getElementById('edit-claude-key').value.trim();
const baseUrl = document.getElementById('edit-claude-url').value.trim();
if (!apiKey) {
this.showNotification('请输入API密钥', 'error');
return;
}
try {
const newConfig = { 'api-key': apiKey };
if (baseUrl) {
newConfig['base-url'] = baseUrl;
}
await this.makeRequest('/claude-api-key', {
method: 'PATCH',
body: JSON.stringify({ index, value: newConfig })
});
this.closeModal();
this.loadClaudeKeys();
this.showNotification('Claude配置更新成功', 'success');
} catch (error) {
this.showNotification(`更新Claude配置失败: ${error.message}`, 'error');
}
}
// 删除Claude密钥
async deleteClaudeKey(apiKey) {
if (!confirm('确定要删除这个Claude配置吗?')) return;
try {
await this.makeRequest(`/claude-api-key?api-key=${encodeURIComponent(apiKey)}`, { method: 'DELETE' });
this.loadClaudeKeys();
this.showNotification('Claude配置删除成功', 'success');
} catch (error) {
this.showNotification(`删除Claude配置失败: ${error.message}`, 'error');
}
}
// 加载OpenAI提供商
async loadOpenAIProviders() {
try {
const data = await this.makeRequest('/openai-compatibility');
this.renderOpenAIProviders(data['openai-compatibility'] || []);
} catch (error) {
console.error('加载OpenAI提供商失败:', error);
}
}
// 渲染OpenAI提供商列表
renderOpenAIProviders(providers) {
const container = document.getElementById('openai-providers-list');
if (providers.length === 0) {
container.innerHTML = `
暂无OpenAI兼容提供商
点击上方按钮添加第一个提供商
`;
return;
}
container.innerHTML = providers.map((provider, index) => `
${provider.name}
地址: ${provider['base-url']}
密钥数量: ${(provider['api-keys'] || []).length}
模型数量: ${(provider.models || []).length}
`).join('');
}
// 显示添加OpenAI提供商模态框
showAddOpenAIProviderModal() {
const modal = document.getElementById('modal');
const modalBody = document.getElementById('modal-body');
modalBody.innerHTML = `
添加OpenAI兼容提供商
`;
modal.style.display = 'block';
}
// 添加OpenAI提供商
async addOpenAIProvider() {
const name = document.getElementById('new-provider-name').value.trim();
const baseUrl = document.getElementById('new-provider-url').value.trim();
const keysText = document.getElementById('new-provider-keys').value.trim();
if (!name || !baseUrl) {
this.showNotification('请填写提供商名称和Base URL', 'error');
return;
}
try {
const data = await this.makeRequest('/openai-compatibility');
const currentProviders = data['openai-compatibility'] || [];
const apiKeys = keysText ? keysText.split('\n').map(k => k.trim()).filter(k => k) : [];
const newProvider = {
name,
'base-url': baseUrl,
'api-keys': apiKeys,
models: []
};
currentProviders.push(newProvider);
await this.makeRequest('/openai-compatibility', {
method: 'PUT',
body: JSON.stringify(currentProviders)
});
this.closeModal();
this.loadOpenAIProviders();
this.showNotification('OpenAI提供商添加成功', 'success');
} catch (error) {
this.showNotification(`添加OpenAI提供商失败: ${error.message}`, 'error');
}
}
// 编辑OpenAI提供商
editOpenAIProvider(index, provider) {
const modal = document.getElementById('modal');
const modalBody = document.getElementById('modal-body');
const apiKeysText = (provider['api-keys'] || []).join('\n');
modalBody.innerHTML = `
编辑OpenAI兼容提供商
`;
modal.style.display = 'block';
}
// 更新OpenAI提供商
async updateOpenAIProvider(index) {
const name = document.getElementById('edit-provider-name').value.trim();
const baseUrl = document.getElementById('edit-provider-url').value.trim();
const keysText = document.getElementById('edit-provider-keys').value.trim();
if (!name || !baseUrl) {
this.showNotification('请填写提供商名称和Base URL', 'error');
return;
}
try {
const apiKeys = keysText ? keysText.split('\n').map(k => k.trim()).filter(k => k) : [];
const updatedProvider = {
name,
'base-url': baseUrl,
'api-keys': apiKeys,
models: []
};
await this.makeRequest('/openai-compatibility', {
method: 'PATCH',
body: JSON.stringify({ index, value: updatedProvider })
});
this.closeModal();
this.loadOpenAIProviders();
this.showNotification('OpenAI提供商更新成功', 'success');
} catch (error) {
this.showNotification(`更新OpenAI提供商失败: ${error.message}`, 'error');
}
}
// 删除OpenAI提供商
async deleteOpenAIProvider(name) {
if (!confirm('确定要删除这个OpenAI提供商吗?')) return;
try {
await this.makeRequest(`/openai-compatibility?name=${encodeURIComponent(name)}`, { method: 'DELETE' });
this.loadOpenAIProviders();
this.showNotification('OpenAI提供商删除成功', 'success');
} catch (error) {
this.showNotification(`删除OpenAI提供商失败: ${error.message}`, 'error');
}
}
// 加载认证文件
async loadAuthFiles() {
try {
const data = await this.makeRequest('/auth-files');
this.renderAuthFiles(data.files || []);
} catch (error) {
console.error('加载认证文件失败:', error);
}
}
// 渲染认证文件列表
renderAuthFiles(files) {
const container = document.getElementById('auth-files-list');
if (files.length === 0) {
container.innerHTML = `
`;
return;
}
container.innerHTML = files.map(file => `
${file.name}
大小: ${this.formatFileSize(file.size)}
修改时间: ${new Date(file.modtime).toLocaleString('zh-CN')}
`).join('');
}
// 格式化文件大小
formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// 上传认证文件
uploadAuthFile() {
document.getElementById('auth-file-input').click();
}
// 处理文件上传
async handleFileUpload(event) {
const file = event.target.files[0];
if (!file) return;
if (!file.name.endsWith('.json')) {
this.showNotification('只能上传JSON文件', 'error');
return;
}
try {
const formData = new FormData();
formData.append('file', file);
const response = await fetch(`${this.apiUrl}/auth-files`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.managementKey}`
},
body: formData
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error || `HTTP ${response.status}`);
}
this.loadAuthFiles();
this.showNotification('文件上传成功', 'success');
} catch (error) {
this.showNotification(`文件上传失败: ${error.message}`, 'error');
}
// 清空文件输入
event.target.value = '';
}
// 下载认证文件
async downloadAuthFile(filename) {
try {
const response = await fetch(`${this.apiUrl}/auth-files/download?name=${encodeURIComponent(filename)}`, {
headers: {
'Authorization': `Bearer ${this.managementKey}`
}
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
window.URL.revokeObjectURL(url);
this.showNotification('文件下载成功', 'success');
} catch (error) {
this.showNotification(`文件下载失败: ${error.message}`, 'error');
}
}
// 删除认证文件
async deleteAuthFile(filename) {
if (!confirm(`确定要删除文件 "${filename}" 吗?`)) return;
try {
await this.makeRequest(`/auth-files?name=${encodeURIComponent(filename)}`, { method: 'DELETE' });
this.loadAuthFiles();
this.showNotification('文件删除成功', 'success');
} catch (error) {
this.showNotification(`文件删除失败: ${error.message}`, 'error');
}
}
// 删除所有认证文件
async deleteAllAuthFiles() {
if (!confirm('确定要删除所有认证文件吗?此操作不可恢复!')) return;
try {
const response = await this.makeRequest('/auth-files?all=true', { method: 'DELETE' });
this.loadAuthFiles();
this.showNotification(`成功删除 ${response.deleted} 个文件`, 'success');
} catch (error) {
this.showNotification(`删除文件失败: ${error.message}`, 'error');
}
}
// 关闭模态框
closeModal() {
document.getElementById('modal').style.display = 'none';
}
}
// 全局管理器实例
let manager;
// 尝试自动加载根目录 Logo(支持多种常见文件名/扩展名)
function setupSiteLogo() {
const img = document.getElementById('site-logo');
if (!img) return;
const candidates = [
'../logo.svg', '../logo.png', '../logo.jpg', '../logo.jpeg', '../logo.webp', '../logo.gif',
'logo.svg', 'logo.png', 'logo.jpg', 'logo.jpeg', 'logo.webp', 'logo.gif',
'/logo.svg', '/logo.png', '/logo.jpg', '/logo.jpeg', '/logo.webp', '/logo.gif'
];
let idx = 0;
const tryNext = () => {
if (idx >= candidates.length) return;
const test = new Image();
test.onload = () => {
img.src = test.src;
img.style.display = 'inline-block';
};
test.onerror = () => {
idx++;
tryNext();
};
test.src = candidates[idx];
};
tryNext();
}
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', () => {
setupSiteLogo();
manager = new CLIProxyManager();
});