From 59bea5ae84e881512b2b73bdb9403db9a664055d Mon Sep 17 00:00:00 2001
From: Supra4E8C <69194597+LTbinglingfeng@users.noreply.github.com>
Date: Wed, 24 Sep 2025 14:21:11 +0800
Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20app.js?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app.js | 3519 ++++++++++++++++++++++++++++++++++----------------------
1 file changed, 2148 insertions(+), 1371 deletions(-)
diff --git a/app.js b/app.js
index 8f791ba..7b43c3b 100644
--- a/app.js
+++ b/app.js
@@ -1,4 +1,4 @@
-// CLI Proxy API 管理界面 JavaScript
+// CLI Proxy API 管理界面 JavaScript
class CLIProxyManager {
constructor() {
// 仅保存基础地址(不含 /v0/management),请求时自动补齐
@@ -6,6 +6,18 @@ class CLIProxyManager {
this.apiUrl = this.computeApiUrl(this.apiBase);
this.managementKey = '';
this.isConnected = false;
+ this.isLoggedIn = false;
+
+ // 配置缓存
+ this.configCache = null;
+ this.cacheTimestamp = null;
+ this.cacheExpiry = 30000; // 30秒缓存过期时间
+
+ // 状态更新定时器
+ this.statusUpdateTimer = null;
+
+ // 主题管理
+ this.currentTheme = 'light';
this.init();
}
@@ -19,71 +31,653 @@ class CLIProxyManager {
};
}
- init() {
- this.bindEvents();
- this.loadSettings();
- this.setupNavigation();
+ // 初始化主题
+ initializeTheme() {
+ // 从本地存储获取用户偏好主题
+ const savedTheme = localStorage.getItem('preferredTheme');
+ if (savedTheme && ['light', 'dark'].includes(savedTheme)) {
+ this.currentTheme = savedTheme;
+ } else {
+ // 根据系统偏好自动选择
+ if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
+ this.currentTheme = 'dark';
+ } else {
+ this.currentTheme = 'light';
+ }
+ }
+
+ this.applyTheme(this.currentTheme);
+ this.updateThemeButtons();
+
+ // 监听系统主题变化
+ if (window.matchMedia) {
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
+ if (!localStorage.getItem('preferredTheme')) {
+ this.currentTheme = e.matches ? 'dark' : 'light';
+ this.applyTheme(this.currentTheme);
+ this.updateThemeButtons();
+ }
+ });
+ }
}
-
- // 事件绑定
- 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');
- });
- });
- }
-
+
+ // 应用主题
+ applyTheme(theme) {
+ if (theme === 'dark') {
+ document.documentElement.setAttribute('data-theme', 'dark');
+ } else {
+ document.documentElement.removeAttribute('data-theme');
+ }
+ this.currentTheme = theme;
+ }
+
+ // 切换主题
+ toggleTheme() {
+ const newTheme = this.currentTheme === 'light' ? 'dark' : 'light';
+ this.applyTheme(newTheme);
+ this.updateThemeButtons();
+ localStorage.setItem('preferredTheme', newTheme);
+ }
+
+ // 更新主题按钮状态
+ updateThemeButtons() {
+ const loginThemeBtn = document.getElementById('theme-toggle');
+ const mainThemeBtn = document.getElementById('theme-toggle-main');
+
+ const updateButton = (btn) => {
+ if (!btn) return;
+ const icon = btn.querySelector('i');
+ if (this.currentTheme === 'dark') {
+ icon.className = 'fas fa-sun';
+ btn.title = i18n.t('theme.switch_to_light');
+ } else {
+ icon.className = 'fas fa-moon';
+ btn.title = i18n.t('theme.switch_to_dark');
+ }
+ };
+
+ updateButton(loginThemeBtn);
+ updateButton(mainThemeBtn);
+ }
+
+ init() {
+ this.initializeTheme();
+ this.checkLoginStatus();
+ this.bindEvents();
+ this.setupNavigation();
+ this.setupLanguageSwitcher();
+ this.setupThemeSwitcher();
+ // loadSettings 将在登录成功后调用
+ }
+
+ // 检查登录状态
+ async checkLoginStatus() {
+ // 检查是否有保存的连接信息
+ const savedBase = localStorage.getItem('apiBase');
+ const savedKey = localStorage.getItem('managementKey');
+ const wasLoggedIn = localStorage.getItem('isLoggedIn') === 'true';
+
+ // 如果有完整的连接信息且之前已登录,尝试自动登录
+ if (savedBase && savedKey && wasLoggedIn) {
+ try {
+ console.log('检测到本地连接数据,尝试自动登录...');
+ this.showAutoLoginLoading();
+ await this.attemptAutoLogin(savedBase, savedKey);
+ return; // 自动登录成功,不显示登录页面
+ } catch (error) {
+ console.log('自动登录失败:', error.message);
+ // 清除无效的登录状态
+ localStorage.removeItem('isLoggedIn');
+ this.hideAutoLoginLoading();
+ }
+ }
+
+ // 如果没有连接信息或自动登录失败,显示登录页面
+ this.showLoginPage();
+ this.loadLoginSettings();
+ }
+
+ // 显示自动登录加载页面
+ showAutoLoginLoading() {
+ document.getElementById('auto-login-loading').style.display = 'flex';
+ document.getElementById('login-page').style.display = 'none';
+ document.getElementById('main-page').style.display = 'none';
+ }
+
+ // 隐藏自动登录加载页面
+ hideAutoLoginLoading() {
+ document.getElementById('auto-login-loading').style.display = 'none';
+ }
+
+ // 尝试自动登录
+ async attemptAutoLogin(apiBase, managementKey) {
+ try {
+ // 设置API基础地址和密钥
+ this.setApiBase(apiBase);
+ this.managementKey = managementKey;
+
+ // 恢复代理设置(如果有)
+ const savedProxy = localStorage.getItem('proxyUrl');
+ if (savedProxy) {
+ // 代理设置会在后续的API请求中自动使用
+ }
+
+ // 测试连接
+ await this.testConnection();
+
+ // 自动登录成功
+ this.isLoggedIn = true;
+ this.hideAutoLoginLoading();
+ this.showMainPage();
+
+ console.log('自动登录成功');
+ return true;
+ } catch (error) {
+ console.error('自动登录失败:', error);
+ // 重置状态
+ this.isLoggedIn = false;
+ this.isConnected = false;
+ throw error;
+ }
+ }
+
+ // 显示登录页面
+ showLoginPage() {
+ document.getElementById('login-page').style.display = 'flex';
+ document.getElementById('main-page').style.display = 'none';
+ this.isLoggedIn = false;
+ }
+
+ // 显示主页面
+ showMainPage() {
+ document.getElementById('login-page').style.display = 'none';
+ document.getElementById('main-page').style.display = 'block';
+ this.isLoggedIn = true;
+ this.updateConnectionInfo();
+ }
+
+ // 登录验证
+ async login(apiBase, managementKey) {
+ try {
+ // 设置API基础地址和密钥
+ this.setApiBase(apiBase);
+ this.managementKey = managementKey;
+ localStorage.setItem('managementKey', this.managementKey);
+
+ // 测试连接并加载所有数据
+ await this.testConnection();
+
+ // 登录成功
+ this.isLoggedIn = true;
+ localStorage.setItem('isLoggedIn', 'true');
+
+ this.showMainPage();
+ // 不需要再调用loadSettings,因为内部状态已经在上面设置了
+
+ return true;
+ } catch (error) {
+ console.error('登录失败:', error);
+ throw error;
+ }
+ }
+
+ // 登出
+ logout() {
+ this.isLoggedIn = false;
+ this.isConnected = false;
+ this.clearCache();
+ this.stopStatusUpdateTimer();
+
+ // 清除本地存储
+ localStorage.removeItem('isLoggedIn');
+ localStorage.removeItem('managementKey');
+
+ this.showLoginPage();
+ }
+
+ // 处理登录表单提交
+ async handleLogin() {
+ // 获取当前活动的选项卡
+ const activeTab = document.querySelector('.tab-button.active').getAttribute('data-tab');
+
+ let apiUrl, managementKey;
+
+ if (activeTab === 'local') {
+ // 本地连接:从端口号构建URL
+ const port = document.getElementById('local-port').value.trim();
+ managementKey = document.getElementById('local-management-key').value.trim();
+
+ if (!port || !managementKey) {
+ this.showLoginError(i18n.t('login.error_required'));
+ return;
+ }
+
+ apiUrl = `http://localhost:${port}`;
+ } else {
+ // 远程连接:使用完整URL
+ apiUrl = document.getElementById('remote-api-url').value.trim();
+ managementKey = document.getElementById('remote-management-key').value.trim();
+
+ if (!apiUrl || !managementKey) {
+ this.showLoginError(i18n.t('login.error_required'));
+ return;
+ }
+ }
+
+ const proxyUrl = document.getElementById('login-proxy-url').value.trim();
+
+ const submitBtn = document.getElementById('login-submit');
+ const originalText = submitBtn.innerHTML;
+
+ try {
+ submitBtn.innerHTML = `
${i18n.t('login.submitting')}`;
+ submitBtn.disabled = true;
+ this.hideLoginError();
+
+ // 如果设置了代理,先保存代理设置
+ if (proxyUrl) {
+ localStorage.setItem('proxyUrl', proxyUrl);
+ }
+
+ await this.login(apiUrl, managementKey);
+
+ } catch (error) {
+ this.showLoginError(`${i18n.t('login.error_title')}: ${error.message}`);
+ } finally {
+ submitBtn.innerHTML = originalText;
+ submitBtn.disabled = false;
+ }
+ }
+
+ // 切换登录页面密钥可见性
+ toggleLoginKeyVisibility(button) {
+ const inputGroup = button.closest('.input-group');
+ const keyInput = inputGroup.querySelector('input[type="password"], input[type="text"]');
+
+ if (keyInput.type === 'password') {
+ keyInput.type = 'text';
+ button.innerHTML = '';
+ } else {
+ keyInput.type = 'password';
+ button.innerHTML = '';
+ }
+ }
+
+ // 显示登录错误
+ showLoginError(message) {
+ const errorDiv = document.getElementById('login-error');
+ const errorMessage = document.getElementById('login-error-message');
+
+ errorMessage.textContent = message;
+ errorDiv.style.display = 'flex';
+ }
+
+ // 隐藏登录错误
+ hideLoginError() {
+ const errorDiv = document.getElementById('login-error');
+ errorDiv.style.display = 'none';
+ }
+
+ // 更新连接信息显示
+ updateConnectionInfo() {
+ const apiUrlElement = document.getElementById('display-api-url');
+ const keyElement = document.getElementById('display-management-key');
+ const statusElement = document.getElementById('display-connection-status');
+
+ // 显示API地址
+ if (apiUrlElement) {
+ apiUrlElement.textContent = this.apiBase || '-';
+ }
+
+ // 显示密钥(遮蔽显示)
+ if (keyElement) {
+ if (this.managementKey) {
+ const maskedKey = this.maskApiKey(this.managementKey);
+ keyElement.textContent = maskedKey;
+ } else {
+ keyElement.textContent = '-';
+ }
+ }
+
+ // 显示连接状态
+ if (statusElement) {
+ let statusHtml = '';
+ if (this.isConnected) {
+ statusHtml = ` ${i18n.t('common.connected')}`;
+ } else {
+ statusHtml = ` ${i18n.t('common.disconnected')}`;
+ }
+ statusElement.innerHTML = statusHtml;
+ }
+ }
+
+
+ // 加载登录页面设置
+ loadLoginSettings() {
+ const savedBase = localStorage.getItem('apiBase');
+ const savedKey = localStorage.getItem('managementKey');
+ const savedProxy = localStorage.getItem('proxyUrl');
+
+ // 检查元素是否存在(确保在登录页面)
+ const localPortInput = document.getElementById('local-port');
+ const remoteApiInput = document.getElementById('remote-api-url');
+ const localKeyInput = document.getElementById('local-management-key');
+ const remoteKeyInput = document.getElementById('remote-management-key');
+ const proxyInput = document.getElementById('login-proxy-url');
+
+ // 设置本地端口和远程API地址
+ if (savedBase) {
+ if (savedBase.includes('localhost')) {
+ // 从本地URL中提取端口号
+ const match = savedBase.match(/localhost:(\d+)/);
+ if (match && localPortInput) {
+ localPortInput.value = match[1];
+ }
+ } else if (remoteApiInput) {
+ remoteApiInput.value = savedBase;
+ }
+ }
+
+ // 设置密钥
+ if (localKeyInput && savedKey) {
+ localKeyInput.value = savedKey;
+ }
+ if (remoteKeyInput && savedKey) {
+ remoteKeyInput.value = savedKey;
+ }
+
+ // 设置代理
+ if (proxyInput && savedProxy) {
+ proxyInput.value = savedProxy;
+ }
+
+ // 设置实时保存监听器
+ this.setupLoginAutoSave();
+ }
+
+ // 设置登录页面自动保存
+ setupLoginAutoSave() {
+ const localPortInput = document.getElementById('local-port');
+ const remoteApiInput = document.getElementById('remote-api-url');
+ const localKeyInput = document.getElementById('local-management-key');
+ const remoteKeyInput = document.getElementById('remote-management-key');
+ const proxyInput = document.getElementById('login-proxy-url');
+
+ const saveLocalBase = (port) => {
+ if (port.trim()) {
+ const apiUrl = `http://localhost:${port}`;
+ this.setApiBase(apiUrl);
+ }
+ };
+ const saveLocalBaseDebounced = this.debounce(saveLocalBase, 500);
+
+ const saveRemoteBase = (val) => {
+ if (val.trim()) {
+ this.setApiBase(val);
+ }
+ };
+ const saveRemoteBaseDebounced = this.debounce(saveRemoteBase, 500);
+
+ const saveKey = (val) => {
+ if (val.trim()) {
+ this.managementKey = val;
+ localStorage.setItem('managementKey', this.managementKey);
+ }
+ };
+ const saveKeyDebounced = this.debounce(saveKey, 500);
+
+ const saveProxy = (val) => {
+ if (val.trim()) {
+ localStorage.setItem('proxyUrl', val);
+ }
+ };
+ const saveProxyDebounced = this.debounce(saveProxy, 500);
+
+ // 绑定本地端口输入框
+ if (localPortInput) {
+ localPortInput.addEventListener('change', (e) => saveLocalBase(e.target.value));
+ localPortInput.addEventListener('input', (e) => saveLocalBaseDebounced(e.target.value));
+ }
+
+ // 绑定远程API输入框
+ if (remoteApiInput) {
+ remoteApiInput.addEventListener('change', (e) => saveRemoteBase(e.target.value));
+ remoteApiInput.addEventListener('input', (e) => saveRemoteBaseDebounced(e.target.value));
+ }
+
+ // 绑定本地密钥输入框
+ if (localKeyInput) {
+ localKeyInput.addEventListener('change', (e) => saveKey(e.target.value));
+ localKeyInput.addEventListener('input', (e) => saveKeyDebounced(e.target.value));
+ }
+
+ // 绑定远程密钥输入框
+ if (remoteKeyInput) {
+ remoteKeyInput.addEventListener('change', (e) => saveKey(e.target.value));
+ remoteKeyInput.addEventListener('input', (e) => saveKeyDebounced(e.target.value));
+ }
+
+ // 绑定代理输入框
+ if (proxyInput) {
+ proxyInput.addEventListener('change', (e) => saveProxy(e.target.value));
+ proxyInput.addEventListener('input', (e) => saveProxyDebounced(e.target.value));
+ }
+ }
+
+ // 事件绑定
+ bindEvents() {
+ // 登录相关(安全绑定)
+ const loginSubmit = document.getElementById('login-submit');
+ const logoutBtn = document.getElementById('logout-btn');
+
+ if (loginSubmit) {
+ loginSubmit.addEventListener('click', () => this.handleLogin());
+ }
+ if (logoutBtn) {
+ logoutBtn.addEventListener('click', () => this.logout());
+ }
+
+ // 选项卡切换事件
+ this.setupTabSwitching();
+
+ // 密钥可见性切换事件
+ this.setupKeyVisibilityToggle();
+
+ // 主页面元素(延迟绑定,在显示主页面时绑定)
+ this.bindMainPageEvents();
+ }
+
+ // 设置选项卡切换
+ setupTabSwitching() {
+ const tabButtons = document.querySelectorAll('.tab-button');
+ const connectionForms = document.querySelectorAll('.connection-form');
+
+ tabButtons.forEach(button => {
+ button.addEventListener('click', () => {
+ const targetTab = button.getAttribute('data-tab');
+
+ // 更新选项卡状态
+ tabButtons.forEach(btn => btn.classList.remove('active'));
+ button.classList.add('active');
+
+ // 切换表单
+ connectionForms.forEach(form => {
+ form.classList.remove('active');
+ if (form.id === `${targetTab}-form`) {
+ form.classList.add('active');
+ }
+ });
+ });
+ });
+ }
+
+ // 设置密钥可见性切换
+ setupKeyVisibilityToggle() {
+ const toggleButtons = document.querySelectorAll('.toggle-key-visibility');
+ toggleButtons.forEach(button => {
+ button.addEventListener('click', () => this.toggleLoginKeyVisibility(button));
+ });
+ }
+
+ // 绑定主页面事件
+ bindMainPageEvents() {
+ // 连接状态检查
+ const connectionStatus = document.getElementById('connection-status');
+ const refreshAll = document.getElementById('refresh-all');
+
+ if (connectionStatus) {
+ connectionStatus.addEventListener('click', () => this.checkConnectionStatus());
+ }
+ if (refreshAll) {
+ refreshAll.addEventListener('click', () => this.refreshAllData());
+ }
+
+ // 基础设置
+ const debugToggle = document.getElementById('debug-toggle');
+ const updateProxy = document.getElementById('update-proxy');
+ const clearProxy = document.getElementById('clear-proxy');
+ const updateRetry = document.getElementById('update-retry');
+ const switchProjectToggle = document.getElementById('switch-project-toggle');
+ const switchPreviewToggle = document.getElementById('switch-preview-model-toggle');
+ const allowLocalhostToggle = document.getElementById('allow-localhost-toggle');
+
+ if (debugToggle) {
+ debugToggle.addEventListener('change', (e) => this.updateDebug(e.target.checked));
+ }
+ if (updateProxy) {
+ updateProxy.addEventListener('click', () => this.updateProxyUrl());
+ }
+ if (clearProxy) {
+ clearProxy.addEventListener('click', () => this.clearProxyUrl());
+ }
+ if (updateRetry) {
+ updateRetry.addEventListener('click', () => this.updateRequestRetry());
+ }
+ if (switchProjectToggle) {
+ switchProjectToggle.addEventListener('change', (e) => this.updateSwitchProject(e.target.checked));
+ }
+ if (switchPreviewToggle) {
+ switchPreviewToggle.addEventListener('change', (e) => this.updateSwitchPreviewModel(e.target.checked));
+ }
+ if (allowLocalhostToggle) {
+ allowLocalhostToggle.addEventListener('change', (e) => this.updateAllowLocalhost(e.target.checked));
+ }
+
+ // API 密钥管理
+ const addApiKey = document.getElementById('add-api-key');
+ const addGeminiKey = document.getElementById('add-gemini-key');
+ const addCodexKey = document.getElementById('add-codex-key');
+ const addClaudeKey = document.getElementById('add-claude-key');
+ const addOpenaiProvider = document.getElementById('add-openai-provider');
+
+ if (addApiKey) {
+ addApiKey.addEventListener('click', () => this.showAddApiKeyModal());
+ }
+ if (addGeminiKey) {
+ addGeminiKey.addEventListener('click', () => this.showAddGeminiKeyModal());
+ }
+ if (addCodexKey) {
+ addCodexKey.addEventListener('click', () => this.showAddCodexKeyModal());
+ }
+ if (addClaudeKey) {
+ addClaudeKey.addEventListener('click', () => this.showAddClaudeKeyModal());
+ }
+ if (addOpenaiProvider) {
+ addOpenaiProvider.addEventListener('click', () => this.showAddOpenAIProviderModal());
+ }
+
+ // 认证文件管理
+ const uploadAuthFile = document.getElementById('upload-auth-file');
+ const deleteAllAuthFiles = document.getElementById('delete-all-auth-files');
+ const authFileInput = document.getElementById('auth-file-input');
+
+ if (uploadAuthFile) {
+ uploadAuthFile.addEventListener('click', () => this.uploadAuthFile());
+ }
+ if (deleteAllAuthFiles) {
+ deleteAllAuthFiles.addEventListener('click', () => this.deleteAllAuthFiles());
+ }
+ if (authFileInput) {
+ authFileInput.addEventListener('change', (e) => this.handleFileUpload(e));
+ }
+
+ // 模态框
+ const closeBtn = document.querySelector('.close');
+ if (closeBtn) {
+ closeBtn.addEventListener('click', () => this.closeModal());
+ }
+
+ window.addEventListener('click', (e) => {
+ const modal = document.getElementById('modal');
+ if (modal && e.target === 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');
+ });
+ });
+ }
+
+ // 设置语言切换
+ setupLanguageSwitcher() {
+ const loginToggle = document.getElementById('language-toggle');
+ const mainToggle = document.getElementById('language-toggle-main');
+
+ if (loginToggle) {
+ loginToggle.addEventListener('click', () => this.toggleLanguage());
+ }
+ if (mainToggle) {
+ mainToggle.addEventListener('click', () => this.toggleLanguage());
+ }
+ }
+
+ // 设置主题切换
+ setupThemeSwitcher() {
+ const loginToggle = document.getElementById('theme-toggle');
+ const mainToggle = document.getElementById('theme-toggle-main');
+
+ if (loginToggle) {
+ loginToggle.addEventListener('click', () => this.toggleTheme());
+ }
+ if (mainToggle) {
+ mainToggle.addEventListener('click', () => this.toggleTheme());
+ }
+ }
+
+ // 切换语言
+ toggleLanguage() {
+ const currentLang = i18n.currentLanguage;
+ const newLang = currentLang === 'zh-CN' ? 'en-US' : 'zh-CN';
+ i18n.setLanguage(newLang);
+
+ // 更新主题按钮文本
+ this.updateThemeButtons();
+
+ // 更新连接状态显示
+ this.updateConnectionStatus();
+
+ // 重新加载所有数据以更新动态内容
+ if (this.isLoggedIn && this.isConnected) {
+ this.loadAllData(true);
+ }
+ }
+
// 规范化基础地址,移除尾部斜杠与 /v0/management
normalizeBase(input) {
let base = (input || '').trim();
@@ -112,1328 +706,1502 @@ class CLIProxyManager {
localStorage.setItem('apiUrl', this.apiUrl); // 兼容旧字段
}
- // 加载设置
- loadSettings() {
+ // 加载设置(简化版,仅加载内部状态)
+ loadSettings() {
const savedBase = localStorage.getItem('apiBase');
const savedUrl = localStorage.getItem('apiUrl');
const savedKey = localStorage.getItem('managementKey');
+ // 只设置内部状态,不操作DOM元素
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');
+
+ if (savedKey) {
+ this.managementKey = savedKey;
+ }
+
+ // 注意:不再处理DOM元素,因为认证配置已改为只读显示
+ // DOM更新由updateConnectionInfo()方法处理
+ }
- 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);
+ // API 请求方法
+ async makeRequest(endpoint, options = {}) {
+ const url = `${this.apiUrl}${endpoint}`;
+ const headers = {
+ 'Authorization': `Bearer ${this.managementKey}`,
+ 'Content-Type': 'application/json',
+ ...options.headers
};
- 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 = `
-
-
-
暂无API密钥
-
点击上方按钮添加第一个密钥
-
- `;
- 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 = `
-
-
-
暂无Gemini密钥
-
点击上方按钮添加第一个密钥
-
- `;
- 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 = `
-
-
-
暂无Codex配置
-
点击上方按钮添加第一个配置
-
- `;
- 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 = `
-
-
-
暂无Claude配置
-
点击上方按钮添加第一个配置
-
- `;
- 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;
-
+ 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() {
+ try {
+ await this.makeRequest('/debug');
+ this.isConnected = true;
+ this.updateConnectionStatus();
+ this.startStatusUpdateTimer();
+ await this.loadAllData();
+ return true;
+ } catch (error) {
+ this.isConnected = false;
+ this.updateConnectionStatus();
+ this.stopStatusUpdateTimer();
+ throw error;
+ }
+ }
+
+ // 更新连接状态
+ updateConnectionStatus() {
+ const statusButton = document.getElementById('connection-status');
+ const apiStatus = document.getElementById('api-status');
+ const configStatus = document.getElementById('config-status');
+ const lastUpdate = document.getElementById('last-update');
+
+ if (this.isConnected) {
+ statusButton.innerHTML = ` ${i18n.t('common.connected')}`;
+ statusButton.className = 'btn btn-success';
+ apiStatus.textContent = i18n.t('common.connected');
+
+ // 更新配置状态
+ if (this.isCacheValid()) {
+ const cacheAge = Math.floor((Date.now() - this.cacheTimestamp) / 1000);
+ configStatus.textContent = `${i18n.t('system_info.cache_data')} (${cacheAge}${i18n.t('system_info.seconds_ago')})`;
+ configStatus.style.color = '#f59e0b'; // 橙色表示缓存
+ } else if (this.configCache) {
+ configStatus.textContent = i18n.t('system_info.real_time_data');
+ configStatus.style.color = '#10b981'; // 绿色表示实时
+ } else {
+ configStatus.textContent = i18n.t('system_info.not_loaded');
+ configStatus.style.color = '#6b7280'; // 灰色表示未加载
+ }
+ } else {
+ statusButton.innerHTML = ` ${i18n.t('common.disconnected')}`;
+ statusButton.className = 'btn btn-danger';
+ apiStatus.textContent = i18n.t('common.disconnected');
+ configStatus.textContent = i18n.t('system_info.not_loaded');
+ configStatus.style.color = '#6b7280';
+ }
+
+ lastUpdate.textContent = new Date().toLocaleString('zh-CN');
+
+ // 更新连接信息显示
+ this.updateConnectionInfo();
+ }
+
+ // 检查连接状态
+ async checkConnectionStatus() {
+ await this.testConnection();
+ }
+
+ // 刷新所有数据
+ async refreshAllData() {
+ if (!this.isConnected) {
+ this.showNotification(i18n.t('notification.connection_required'), 'error');
+ return;
+ }
+
+ const button = document.getElementById('refresh-all');
+ const originalText = button.innerHTML;
+
+ button.innerHTML = ` ${i18n.t('common.loading')}`;
+ button.disabled = true;
+
+ try {
+ // 强制刷新,清除缓存
+ await this.loadAllData(true);
+ this.showNotification(i18n.t('notification.data_refreshed'), 'success');
+ } catch (error) {
+ this.showNotification(`${i18n.t('notification.refresh_failed')}: ${error.message}`, 'error');
+ } finally {
+ button.innerHTML = originalText;
+ button.disabled = false;
+ }
+ }
+
+ // 检查缓存是否有效
+ isCacheValid() {
+ if (!this.configCache || !this.cacheTimestamp) {
+ return false;
+ }
+ return (Date.now() - this.cacheTimestamp) < this.cacheExpiry;
+ }
+
+ // 获取配置(优先使用缓存)
+ async getConfig(forceRefresh = false) {
+ if (!forceRefresh && this.isCacheValid()) {
+ this.updateConnectionStatus(); // 更新状态显示
+ return this.configCache;
+ }
+
+ try {
+ const config = await this.makeRequest('/config');
+ this.configCache = config;
+ this.cacheTimestamp = Date.now();
+ this.updateConnectionStatus(); // 更新状态显示
+ return config;
+ } catch (error) {
+ console.error('获取配置失败:', error);
+ throw error;
+ }
+ }
+
+ // 清除缓存
+ clearCache() {
+ this.configCache = null;
+ this.cacheTimestamp = null;
+ }
+
+ // 启动状态更新定时器
+ startStatusUpdateTimer() {
+ if (this.statusUpdateTimer) {
+ clearInterval(this.statusUpdateTimer);
+ }
+ this.statusUpdateTimer = setInterval(() => {
+ if (this.isConnected) {
+ this.updateConnectionStatus();
+ }
+ }, 1000); // 每秒更新一次
+ }
+
+ // 停止状态更新定时器
+ stopStatusUpdateTimer() {
+ if (this.statusUpdateTimer) {
+ clearInterval(this.statusUpdateTimer);
+ this.statusUpdateTimer = null;
+ }
+ }
+
+ // 加载所有数据 - 使用新的 /config 端点一次性获取所有配置
+ async loadAllData(forceRefresh = false) {
+ try {
+ console.log('使用新的 /config 端点加载所有配置...');
+ // 使用新的 /config 端点一次性获取所有配置
+ const config = await this.getConfig(forceRefresh);
+
+ // 从配置中提取并设置各个设置项
+ this.updateSettingsFromConfig(config);
+
+ // 认证文件需要单独加载,因为不在配置中
+ await this.loadAuthFiles();
+
+ console.log('配置加载完成,使用缓存:', !forceRefresh && this.isCacheValid());
+ } catch (error) {
+ console.error('加载配置失败:', error);
+ console.log('回退到逐个加载方式...');
+ // 如果新方法失败,回退到原来的逐个加载方式
+ await this.loadAllDataLegacy();
+ }
+ }
+
+ // 从配置对象更新所有设置
+ updateSettingsFromConfig(config) {
+ // 调试设置
+ if (config.debug !== undefined) {
+ document.getElementById('debug-toggle').checked = config.debug;
+ }
+
+ // 代理设置
+ if (config['proxy-url'] !== undefined) {
+ document.getElementById('proxy-url').value = config['proxy-url'] || '';
+ }
+
+ // 请求重试设置
+ if (config['request-retry'] !== undefined) {
+ document.getElementById('request-retry').value = config['request-retry'];
+ }
+
+ // 配额超出行为
+ if (config['quota-exceeded']) {
+ if (config['quota-exceeded']['switch-project'] !== undefined) {
+ document.getElementById('switch-project-toggle').checked = config['quota-exceeded']['switch-project'];
+ }
+ if (config['quota-exceeded']['switch-preview-model'] !== undefined) {
+ document.getElementById('switch-preview-model-toggle').checked = config['quota-exceeded']['switch-preview-model'];
+ }
+ }
+
+ // 本地访问设置
+ if (config['allow-localhost-unauthenticated'] !== undefined) {
+ document.getElementById('allow-localhost-toggle').checked = config['allow-localhost-unauthenticated'];
+ }
+
+ // API 密钥
+ if (config['api-keys']) {
+ this.renderApiKeys(config['api-keys']);
+ }
+
+ // Gemini 密钥
+ if (config['generative-language-api-key']) {
+ this.renderGeminiKeys(config['generative-language-api-key']);
+ }
+
+ // Codex 密钥
+ if (config['codex-api-key']) {
+ this.renderCodexKeys(config['codex-api-key']);
+ }
+
+ // Claude 密钥
+ if (config['claude-api-key']) {
+ this.renderClaudeKeys(config['claude-api-key']);
+ }
+
+ // OpenAI 兼容提供商
+ if (config['openai-compatibility']) {
+ this.renderOpenAIProviders(config['openai-compatibility']);
+ }
+ }
+
+ // 回退方法:原来的逐个加载方式
+ async loadAllDataLegacy() {
+ 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 config = await this.getConfig();
+ if (config.debug !== undefined) {
+ document.getElementById('debug-toggle').checked = config.debug;
+ }
+ } catch (error) {
+ console.error('加载调试设置失败:', error);
+ }
+ }
+
+ // 更新调试设置
+ async updateDebug(enabled) {
+ try {
+ await this.makeRequest('/debug', {
+ method: 'PUT',
+ body: JSON.stringify({ value: enabled })
+ });
+ this.clearCache(); // 清除缓存
+ this.showNotification(i18n.t('notification.debug_updated'), 'success');
+ } catch (error) {
+ this.showNotification(`${i18n.t('notification.update_failed')}: ${error.message}`, 'error');
+ // 恢复原状态
+ document.getElementById('debug-toggle').checked = !enabled;
+ }
+ }
+
+ // 加载代理设置
+ async loadProxySettings() {
+ try {
+ const config = await this.getConfig();
+ if (config['proxy-url'] !== undefined) {
+ document.getElementById('proxy-url').value = config['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.clearCache(); // 清除缓存
+ this.showNotification(i18n.t('notification.proxy_updated'), 'success');
+ } catch (error) {
+ this.showNotification(`${i18n.t('notification.update_failed')}: ${error.message}`, 'error');
+ }
+ }
+
+ // 清空代理URL
+ async clearProxyUrl() {
+ try {
+ await this.makeRequest('/proxy-url', { method: 'DELETE' });
+ document.getElementById('proxy-url').value = '';
+ this.clearCache(); // 清除缓存
+ this.showNotification(i18n.t('notification.proxy_cleared'), 'success');
+ } catch (error) {
+ this.showNotification(`${i18n.t('notification.update_failed')}: ${error.message}`, 'error');
+ }
+ }
+
+ // 加载重试设置
+ async loadRetrySettings() {
+ try {
+ const config = await this.getConfig();
+ if (config['request-retry'] !== undefined) {
+ document.getElementById('request-retry').value = config['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.clearCache(); // 清除缓存
+ this.showNotification(i18n.t('notification.retry_updated'), 'success');
+ } catch (error) {
+ this.showNotification(`${i18n.t('notification.update_failed')}: ${error.message}`, 'error');
+ }
+ }
+
+ // 加载配额设置
+ async loadQuotaSettings() {
+ try {
+ const config = await this.getConfig();
+ if (config['quota-exceeded']) {
+ if (config['quota-exceeded']['switch-project'] !== undefined) {
+ document.getElementById('switch-project-toggle').checked = config['quota-exceeded']['switch-project'];
+ }
+ if (config['quota-exceeded']['switch-preview-model'] !== undefined) {
+ document.getElementById('switch-preview-model-toggle').checked = config['quota-exceeded']['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.clearCache(); // 清除缓存
+ this.showNotification(i18n.t('notification.quota_switch_project_updated'), 'success');
+ } catch (error) {
+ this.showNotification(`${i18n.t('notification.update_failed')}: ${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.clearCache(); // 清除缓存
+ this.showNotification(i18n.t('notification.quota_switch_preview_updated'), 'success');
+ } catch (error) {
+ this.showNotification(`${i18n.t('notification.update_failed')}: ${error.message}`, 'error');
+ document.getElementById('switch-preview-model-toggle').checked = !enabled;
+ }
+ }
+
+ // 加载本地访问设置
+ async loadLocalhostSettings() {
+ try {
+ const config = await this.getConfig();
+ if (config['allow-localhost-unauthenticated'] !== undefined) {
+ document.getElementById('allow-localhost-toggle').checked = config['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.clearCache(); // 清除缓存
+ this.showNotification(i18n.t('notification.localhost_updated'), 'success');
+ } catch (error) {
+ this.showNotification(`${i18n.t('notification.update_failed')}: ${error.message}`, 'error');
+ document.getElementById('allow-localhost-toggle').checked = !enabled;
+ }
+ }
+
+ // 加载API密钥
+ async loadApiKeys() {
+ try {
+ const config = await this.getConfig();
+ if (config['api-keys']) {
+ this.renderApiKeys(config['api-keys']);
+ }
+ } catch (error) {
+ console.error('加载API密钥失败:', error);
+ }
+ }
+
+ // 渲染API密钥列表
+ renderApiKeys(keys) {
+ const container = document.getElementById('api-keys-list');
+
+ if (keys.length === 0) {
+ container.innerHTML = `
+
+
+
${i18n.t('api_keys.empty_title')}
+
${i18n.t('api_keys.empty_desc')}
+
+ `;
+ return;
+ }
+
+ container.innerHTML = keys.map((key, index) => `
+
+
+
${i18n.t('api_keys.item_title')} #${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 = `
+ ${i18n.t('api_keys.add_modal_title')}
+
+
+
+
+
+
+
+
+ `;
+
+ modal.style.display = 'block';
+ }
+
+ // 添加API密钥
+ async addApiKey() {
+ const newKey = document.getElementById('new-api-key').value.trim();
+
+ if (!newKey) {
+ this.showNotification(`${i18n.t('notification.please_enter')} ${i18n.t('notification.api_key')}`, '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.clearCache(); // 清除缓存
+ this.closeModal();
+ this.loadApiKeys();
+ this.showNotification(i18n.t('notification.api_key_added'), 'success');
+ } catch (error) {
+ this.showNotification(`${i18n.t('notification.add_failed')}: ${error.message}`, 'error');
+ }
+ }
+
+ // 编辑API密钥
+ editApiKey(index, currentKey) {
+ const modal = document.getElementById('modal');
+ const modalBody = document.getElementById('modal-body');
+
+ modalBody.innerHTML = `
+ ${i18n.t('api_keys.edit_modal_title')}
+
+
+
+
+
+
+
+
+ `;
+
+ modal.style.display = 'block';
+ }
+
+ // 更新API密钥
+ async updateApiKey(index) {
+ const newKey = document.getElementById('edit-api-key').value.trim();
+
+ if (!newKey) {
+ this.showNotification(`${i18n.t('notification.please_enter')} ${i18n.t('notification.api_key')}`, 'error');
+ return;
+ }
+
+ try {
+ await this.makeRequest('/api-keys', {
+ method: 'PATCH',
+ body: JSON.stringify({ index, value: newKey })
+ });
+
+ this.clearCache(); // 清除缓存
+ this.closeModal();
+ this.loadApiKeys();
+ this.showNotification(i18n.t('notification.api_key_updated'), 'success');
+ } catch (error) {
+ this.showNotification(`${i18n.t('notification.update_failed')}: ${error.message}`, 'error');
+ }
+ }
+
+ // 删除API密钥
+ async deleteApiKey(index) {
+ if (!confirm(i18n.t('api_keys.delete_confirm'))) return;
+
+ try {
+ await this.makeRequest(`/api-keys?index=${index}`, { method: 'DELETE' });
+ this.clearCache(); // 清除缓存
+ this.loadApiKeys();
+ this.showNotification(i18n.t('notification.api_key_deleted'), 'success');
+ } catch (error) {
+ this.showNotification(`${i18n.t('notification.delete_failed')}: ${error.message}`, 'error');
+ }
+ }
+
+ // 加载Gemini密钥
+ async loadGeminiKeys() {
+ try {
+ const config = await this.getConfig();
+ if (config['generative-language-api-key']) {
+ this.renderGeminiKeys(config['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 = `
+
+
+
${i18n.t('ai_providers.gemini_empty_title')}
+
${i18n.t('ai_providers.gemini_empty_desc')}
+
+ `;
+ return;
+ }
+
+ container.innerHTML = keys.map((key, index) => `
+
+
+
${i18n.t('ai_providers.gemini_item_title')} #${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.clearCache(); // 清除缓存
+ 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.clearCache(); // 清除缓存
+ this.closeModal();
+ this.loadGeminiKeys();
+ this.showNotification('Gemini密钥更新成功', 'success');
+ } catch (error) {
+ this.showNotification(`更新Gemini密钥失败: ${error.message}`, 'error');
+ }
+ }
+
+ // 删除Gemini密钥
+ async deleteGeminiKey(key) {
+ if (!confirm(i18n.t('ai_providers.gemini_delete_confirm'))) return;
+
+ try {
+ await this.makeRequest(`/generative-language-api-key?value=${encodeURIComponent(key)}`, { method: 'DELETE' });
+ this.clearCache(); // 清除缓存
+ this.loadGeminiKeys();
+ this.showNotification('Gemini密钥删除成功', 'success');
+ } catch (error) {
+ this.showNotification(`删除Gemini密钥失败: ${error.message}`, 'error');
+ }
+ }
+
+ // 加载Codex密钥
+ async loadCodexKeys() {
+ try {
+ const config = await this.getConfig();
+ if (config['codex-api-key']) {
+ this.renderCodexKeys(config['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 = `
+
+
+
${i18n.t('ai_providers.codex_empty_title')}
+
${i18n.t('ai_providers.codex_empty_desc')}
+
+ `;
+ return;
+ }
+
+ container.innerHTML = keys.map((config, index) => `
+
+
+
${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')}: ${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.clearCache(); // 清除缓存
+ 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.clearCache(); // 清除缓存
+ this.closeModal();
+ this.loadCodexKeys();
+ this.showNotification('Codex配置更新成功', 'success');
+ } catch (error) {
+ this.showNotification(`更新Codex配置失败: ${error.message}`, 'error');
+ }
+ }
+
+ // 删除Codex密钥
+ async deleteCodexKey(apiKey) {
+ if (!confirm(i18n.t('ai_providers.codex_delete_confirm'))) return;
+
+ try {
+ await this.makeRequest(`/codex-api-key?api-key=${encodeURIComponent(apiKey)}`, { method: 'DELETE' });
+ this.clearCache(); // 清除缓存
+ this.loadCodexKeys();
+ this.showNotification('Codex配置删除成功', 'success');
+ } catch (error) {
+ this.showNotification(`删除Codex配置失败: ${error.message}`, 'error');
+ }
+ }
+
+ // 加载Claude密钥
+ async loadClaudeKeys() {
+ try {
+ const config = await this.getConfig();
+ if (config['claude-api-key']) {
+ this.renderClaudeKeys(config['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 = `
+
+
+
${i18n.t('ai_providers.claude_empty_title')}
+
${i18n.t('ai_providers.claude_empty_desc')}
+
+ `;
+ return;
+ }
+
+ container.innerHTML = keys.map((config, index) => `
+
+
+
${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')}: ${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.clearCache(); // 清除缓存
+ 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.clearCache(); // 清除缓存
+ this.closeModal();
+ this.loadClaudeKeys();
+ this.showNotification('Claude配置更新成功', 'success');
+ } catch (error) {
+ this.showNotification(`更新Claude配置失败: ${error.message}`, 'error');
+ }
+ }
+
+ // 删除Claude密钥
+ async deleteClaudeKey(apiKey) {
+ if (!confirm(i18n.t('ai_providers.claude_delete_confirm'))) return;
+
+ try {
+ await this.makeRequest(`/claude-api-key?api-key=${encodeURIComponent(apiKey)}`, { method: 'DELETE' });
+ this.clearCache(); // 清除缓存
+ this.loadClaudeKeys();
+ this.showNotification('Claude配置删除成功', 'success');
+ } catch (error) {
+ this.showNotification(`删除Claude配置失败: ${error.message}`, 'error');
+ }
+ }
+
+ // 加载OpenAI提供商
+ async loadOpenAIProviders() {
+ try {
+ const config = await this.getConfig();
+ if (config['openai-compatibility']) {
+ this.renderOpenAIProviders(config['openai-compatibility']);
+ }
+ } catch (error) {
+ console.error('加载OpenAI提供商失败:', error);
+ }
+ }
+
+ // 渲染OpenAI提供商列表
+ renderOpenAIProviders(providers) {
+ const container = document.getElementById('openai-providers-list');
+
+ if (providers.length === 0) {
+ container.innerHTML = `
+
+
+
${i18n.t('ai_providers.openai_empty_title')}
+
${i18n.t('ai_providers.openai_empty_desc')}
+
+ `;
+ return;
+ }
+
+ container.innerHTML = providers.map((provider, index) => `
+
+
+
${provider.name}
+
${i18n.t('common.base_url')}: ${provider['base-url']}
+
${i18n.t('ai_providers.openai_keys_count')}: ${(provider['api-keys'] || []).length}
+
${i18n.t('ai_providers.openai_models_count')}: ${(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.clearCache(); // 清除缓存
+ 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.clearCache(); // 清除缓存
+ this.closeModal();
+ this.loadOpenAIProviders();
+ this.showNotification('OpenAI提供商更新成功', 'success');
+ } catch (error) {
+ this.showNotification(`更新OpenAI提供商失败: ${error.message}`, 'error');
+ }
+ }
+
+ // 删除OpenAI提供商
+ async deleteOpenAIProvider(name) {
+ if (!confirm(i18n.t('ai_providers.openai_delete_confirm'))) return;
+
+ try {
+ await this.makeRequest(`/openai-compatibility?name=${encodeURIComponent(name)}`, { method: 'DELETE' });
+ this.clearCache(); // 清除缓存
+ 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 = `
+
+
+
${i18n.t('auth_files.empty_title')}
+
${i18n.t('auth_files.empty_desc')}
+
+ `;
+ return;
+ }
+
+ container.innerHTML = files.map(file => `
+
+
+
${file.name}
+
${i18n.t('auth_files.file_size')}: ${this.formatFileSize(file.size)}
+
${i18n.t('auth_files.file_modified')}: ${new Date(file.modtime).toLocaleString(i18n.currentLanguage === 'zh-CN' ? 'zh-CN' : 'en-US')}
+
+
+
+
+
+
+ `).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(i18n.t('auth_files.upload_error_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.clearCache(); // 清除缓存
+ this.loadAuthFiles();
+ this.showNotification(i18n.t('auth_files.upload_success'), '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(i18n.t('auth_files.download_success'), 'success');
+ } catch (error) {
+ this.showNotification(`文件下载失败: ${error.message}`, 'error');
+ }
+ }
+
+ // 删除认证文件
+ async deleteAuthFile(filename) {
+ if (!confirm(`${i18n.t('auth_files.delete_confirm')} "${filename}" 吗?`)) return;
+
+ try {
+ await this.makeRequest(`/auth-files?name=${encodeURIComponent(filename)}`, { method: 'DELETE' });
+ this.clearCache(); // 清除缓存
+ this.loadAuthFiles();
+ this.showNotification(i18n.t('auth_files.delete_success'), 'success');
+ } catch (error) {
+ this.showNotification(`文件删除失败: ${error.message}`, 'error');
+ }
+ }
+
+ // 删除所有认证文件
+ async deleteAllAuthFiles() {
+ if (!confirm(i18n.t('auth_files.delete_all_confirm'))) return;
+
+ try {
+ const response = await this.makeRequest('/auth-files?all=true', { method: 'DELETE' });
+ this.clearCache(); // 清除缓存
+ this.loadAuthFiles();
+ this.showNotification(`${i18n.t('auth_files.delete_all_success')} ${response.deleted} ${i18n.t('auth_files.files_count')}`, '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 loginImg = document.getElementById('login-logo');
+ if (!img && !loginImg) 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',
@@ -1444,8 +2212,14 @@ function setupSiteLogo() {
if (idx >= candidates.length) return;
const test = new Image();
test.onload = () => {
- img.src = test.src;
- img.style.display = 'inline-block';
+ if (img) {
+ img.src = test.src;
+ img.style.display = 'inline-block';
+ }
+ if (loginImg) {
+ loginImg.src = test.src;
+ loginImg.style.display = 'inline-block';
+ }
};
test.onerror = () => {
idx++;
@@ -1458,6 +2232,9 @@ function setupSiteLogo() {
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', () => {
+ // 初始化国际化
+ i18n.init();
+
setupSiteLogo();
manager = new CLIProxyManager();
});