Implement the Codex OAuth function, add relevant UI components and styles, optimize the login experience, and fix several UI issues.

This commit is contained in:
Luis Pater
2025-10-06 01:12:34 +08:00
parent 27948b3d5c
commit 0bbb397df5
4 changed files with 1139 additions and 745 deletions

599
app.js

File diff suppressed because it is too large Load Diff

138
i18n.js
View File

@@ -3,7 +3,7 @@ const i18n = {
// 语言配置
currentLanguage: 'zh-CN',
fallbackLanguage: 'zh-CN',
// 语言包
translations: {
'zh-CN': {
@@ -38,15 +38,15 @@ const i18n = {
'common.base_url': '地址',
'common.proxy_url': '代理',
'common.alias': '别名',
// 页面标题
'title.main': 'CLI Proxy API Management Center',
'title.login': 'CLI Proxy API Management Center',
// 自动登录
'auto_login.title': '正在自动登录...',
'auto_login.message': '正在使用本地保存的连接信息尝试连接服务器',
// 登录页面
'login.subtitle': '请输入连接信息以访问管理界面',
'login.connection_title': '连接地址',
@@ -64,18 +64,18 @@ const i18n = {
'login.error_title': '登录失败',
'login.error_required': '请填写完整的连接信息',
'login.error_invalid': '连接失败,请检查地址和密钥',
// 头部导航
'header.check_connection': '检查连接',
'header.refresh_all': '刷新全部',
'header.logout': '登出',
// 连接信息
'connection.title': '连接信息',
'connection.server_address': '服务器地址:',
'connection.management_key': '管理密钥:',
'connection.status': '连接状态:',
// 侧边栏导航
'nav.basic_settings': '基础设置',
'nav.api_keys': 'API 密钥',
@@ -83,7 +83,7 @@ const i18n = {
'nav.auth_files': '认证文件',
'nav.usage_stats': '使用统计',
'nav.system_info': '系统信息',
// 基础设置
'basic_settings.title': '基础设置',
'basic_settings.debug_title': '调试模式',
@@ -99,7 +99,7 @@ const i18n = {
'basic_settings.quota_title': '配额超出行为',
'basic_settings.quota_switch_project': '自动切换项目',
'basic_settings.quota_switch_preview': '切换到预览模型',
// API 密钥管理
'api_keys.title': 'API 密钥管理',
'api_keys.proxy_auth_title': '代理服务认证密钥',
@@ -113,7 +113,7 @@ const i18n = {
'api_keys.edit_modal_title': '编辑API密钥',
'api_keys.edit_modal_key_label': 'API密钥:',
'api_keys.delete_confirm': '确定要删除这个API密钥吗',
// AI 提供商
'ai_providers.title': 'AI 提供商配置',
'ai_providers.gemini_title': 'Gemini API 密钥',
@@ -127,7 +127,7 @@ const i18n = {
'ai_providers.gemini_edit_modal_title': '编辑Gemini API密钥',
'ai_providers.gemini_edit_modal_key_label': 'API密钥:',
'ai_providers.gemini_delete_confirm': '确定要删除这个Gemini密钥吗',
'ai_providers.codex_title': 'Codex API 配置',
'ai_providers.codex_add_button': '添加配置',
'ai_providers.codex_empty_title': '暂无Codex配置',
@@ -145,7 +145,7 @@ const i18n = {
'ai_providers.codex_edit_modal_url_label': 'Base URL (可选):',
'ai_providers.codex_edit_modal_proxy_label': '代理 URL (可选):',
'ai_providers.codex_delete_confirm': '确定要删除这个Codex配置吗',
'ai_providers.claude_title': 'Claude API 配置',
'ai_providers.claude_add_button': '添加配置',
'ai_providers.claude_empty_title': '暂无Claude配置',
@@ -163,7 +163,7 @@ const i18n = {
'ai_providers.claude_edit_modal_url_label': 'Base URL (可选):',
'ai_providers.claude_edit_modal_proxy_label': '代理 URL (可选):',
'ai_providers.claude_delete_confirm': '确定要删除这个Claude配置吗',
'ai_providers.openai_title': 'OpenAI 兼容提供商',
'ai_providers.openai_add_button': '添加提供商',
'ai_providers.openai_empty_title': '暂无OpenAI兼容提供商',
@@ -191,8 +191,8 @@ const i18n = {
'ai_providers.openai_delete_confirm': '确定要删除这个OpenAI提供商吗',
'ai_providers.openai_keys_count': '密钥数量',
'ai_providers.openai_models_count': '模型数量',
// 认证文件管理
'auth_files.title': '认证文件管理',
'auth_files.title_section': '认证文件',
@@ -213,7 +213,7 @@ const i18n = {
'auth_files.delete_success': '文件删除成功',
'auth_files.delete_all_success': '成功删除',
'auth_files.files_count': '个文件',
// Gemini Web Token
'auth_login.gemini_web_title': 'Gemini Web Token',
'auth_login.gemini_web_button': '保存 Gemini Web Token',
@@ -225,7 +225,20 @@ const i18n = {
'auth_login.gemini_web_label_label': '标签 (可选):',
'auth_login.gemini_web_label_placeholder': '输入标签名称 (可选)',
'auth_login.gemini_web_saved': 'Gemini Web Token 保存成功',
// Codex OAuth
'auth_login.codex_oauth_title': 'Codex OAuth',
'auth_login.codex_oauth_button': '开始 Codex 登录',
'auth_login.codex_oauth_hint': '通过 OAuth 流程登录 Codex 服务,自动获取并保存认证文件。',
'auth_login.codex_oauth_url_label': '授权链接:',
'auth_login.codex_open_link': '打开链接',
'auth_login.codex_copy_link': '复制链接',
'auth_login.codex_oauth_status_waiting': '等待认证中...',
'auth_login.codex_oauth_status_success': '认证成功!',
'auth_login.codex_oauth_status_error': '认证失败:',
'auth_login.codex_oauth_start_error': '启动 Codex OAuth 失败:',
'auth_login.codex_oauth_polling_error': '检查认证状态失败:',
// 使用统计
'usage_stats.title': '使用统计',
'usage_stats.total_requests': '总请求数',
@@ -245,7 +258,7 @@ const i18n = {
'usage_stats.tokens_count': 'Token数量',
'usage_stats.models': '模型统计',
'usage_stats.success_rate': '成功率',
// 系统信息
'system_info.title': '系统信息',
'system_info.connection_status_title': '连接状态',
@@ -256,7 +269,7 @@ const i18n = {
'system_info.real_time_data': '实时数据',
'system_info.not_loaded': '未加载',
'system_info.seconds_ago': '秒前',
// 通知消息
'notification.debug_updated': '调试设置已更新',
'notification.proxy_updated': '代理设置已更新',
@@ -298,12 +311,12 @@ const i18n = {
'notification.gemini_api_key': 'Gemini API密钥',
'notification.codex_api_key': 'Codex API密钥',
'notification.claude_api_key': 'Claude API密钥',
// 语言切换
'language.switch': '语言',
'language.chinese': '中文',
'language.english': 'English',
// 主题切换
'theme.switch': '主题',
'theme.light': '亮色',
@@ -311,12 +324,12 @@ const i18n = {
'theme.switch_to_light': '切换到亮色模式',
'theme.switch_to_dark': '切换到暗色模式',
'theme.auto': '跟随系统',
// 页脚
'footer.version': '版本',
'footer.author': '作者'
},
'en-US': {
// Common
'common.login': 'Login',
@@ -347,15 +360,15 @@ const i18n = {
'common.required': 'Required',
'common.api_key': 'Key',
'common.base_url': 'Address',
// Page titles
'title.main': 'CLI Proxy API Management Center',
'title.login': 'CLI Proxy API Management Center',
// Auto login
'auto_login.title': 'Auto Login in Progress...',
'auto_login.message': 'Attempting to connect to server using locally saved connection information',
// Login page
'login.subtitle': 'Please enter connection information to access the management interface',
'login.connection_title': 'Connection Address',
@@ -373,18 +386,18 @@ const i18n = {
'login.error_title': 'Login Failed',
'login.error_required': 'Please fill in complete connection information',
'login.error_invalid': 'Connection failed, please check address and key',
// Header navigation
'header.check_connection': 'Check Connection',
'header.refresh_all': 'Refresh All',
'header.logout': 'Logout',
// Connection info
'connection.title': 'Connection Information',
'connection.server_address': 'Server Address:',
'connection.management_key': 'Management Key:',
'connection.status': 'Connection Status:',
// Sidebar navigation
'nav.basic_settings': 'Basic Settings',
'nav.api_keys': 'API Keys',
@@ -392,7 +405,7 @@ const i18n = {
'nav.auth_files': 'Auth Files',
'nav.usage_stats': 'Usage Statistics',
'nav.system_info': 'System Info',
// Basic settings
'basic_settings.title': 'Basic Settings',
'basic_settings.debug_title': 'Debug Mode',
@@ -408,7 +421,7 @@ const i18n = {
'basic_settings.quota_title': 'Quota Exceeded Behavior',
'basic_settings.quota_switch_project': 'Auto Switch Project',
'basic_settings.quota_switch_preview': 'Switch to Preview Model',
// API Keys management
'api_keys.title': 'API Keys Management',
'api_keys.proxy_auth_title': 'Proxy Service Authentication Keys',
@@ -422,7 +435,7 @@ const i18n = {
'api_keys.edit_modal_title': 'Edit API Key',
'api_keys.edit_modal_key_label': 'API Key:',
'api_keys.delete_confirm': 'Are you sure you want to delete this API key?',
// AI Providers
'ai_providers.title': 'AI Providers Configuration',
'ai_providers.gemini_title': 'Gemini API Keys',
@@ -436,7 +449,7 @@ const i18n = {
'ai_providers.gemini_edit_modal_title': 'Edit Gemini API Key',
'ai_providers.gemini_edit_modal_key_label': 'API Key:',
'ai_providers.gemini_delete_confirm': 'Are you sure you want to delete this Gemini key?',
'ai_providers.codex_title': 'Codex API Configuration',
'ai_providers.codex_add_button': 'Add Configuration',
'ai_providers.codex_empty_title': 'No Codex Configuration',
@@ -451,7 +464,7 @@ const i18n = {
'ai_providers.codex_edit_modal_key_label': 'API Key:',
'ai_providers.codex_edit_modal_url_label': 'Base URL (Optional):',
'ai_providers.codex_delete_confirm': 'Are you sure you want to delete this Codex configuration?',
'ai_providers.claude_title': 'Claude API Configuration',
'ai_providers.claude_add_button': 'Add Configuration',
'ai_providers.claude_empty_title': 'No Claude Configuration',
@@ -466,7 +479,7 @@ const i18n = {
'ai_providers.claude_edit_modal_key_label': 'API Key:',
'ai_providers.claude_edit_modal_url_label': 'Base URL (Optional):',
'ai_providers.claude_delete_confirm': 'Are you sure you want to delete this Claude configuration?',
'ai_providers.openai_title': 'OpenAI Compatible Providers',
'ai_providers.openai_add_button': 'Add Provider',
'ai_providers.openai_empty_title': 'No OpenAI Compatible Providers',
@@ -485,8 +498,8 @@ const i18n = {
'ai_providers.openai_delete_confirm': 'Are you sure you want to delete this OpenAI provider?',
'ai_providers.openai_keys_count': 'Keys Count',
'ai_providers.openai_models_count': 'Models Count',
// Auth files management
'auth_files.title': 'Auth Files Management',
'auth_files.title_section': 'Auth Files',
@@ -507,7 +520,7 @@ const i18n = {
'auth_files.delete_success': 'File deleted successfully',
'auth_files.delete_all_success': 'Successfully deleted',
'auth_files.files_count': 'files',
// Gemini Web Token
'auth_login.gemini_web_title': 'Gemini Web Token',
'auth_login.gemini_web_button': 'Save Gemini Web Token',
@@ -519,7 +532,20 @@ const i18n = {
'auth_login.gemini_web_label_label': 'Label (Optional):',
'auth_login.gemini_web_label_placeholder': 'Enter label name (optional)',
'auth_login.gemini_web_saved': 'Gemini Web Token saved successfully',
// Codex OAuth
'auth_login.codex_oauth_title': 'Codex OAuth',
'auth_login.codex_oauth_button': 'Start Codex Login',
'auth_login.codex_oauth_hint': 'Login to Codex service through OAuth flow, automatically obtain and save authentication files.',
'auth_login.codex_oauth_url_label': 'Authorization URL:',
'auth_login.codex_open_link': 'Open Link',
'auth_login.codex_copy_link': 'Copy Link',
'auth_login.codex_oauth_status_waiting': 'Waiting for authentication...',
'auth_login.codex_oauth_status_success': 'Authentication successful!',
'auth_login.codex_oauth_status_error': 'Authentication failed:',
'auth_login.codex_oauth_start_error': 'Failed to start Codex OAuth:',
'auth_login.codex_oauth_polling_error': 'Failed to check authentication status:',
// Usage Statistics
'usage_stats.title': 'Usage Statistics',
'usage_stats.total_requests': 'Total Requests',
@@ -539,7 +565,7 @@ const i18n = {
'usage_stats.tokens_count': 'Token Count',
'usage_stats.models': 'Model Statistics',
'usage_stats.success_rate': 'Success Rate',
// System info
'system_info.title': 'System Information',
'system_info.connection_status_title': 'Connection Status',
@@ -550,7 +576,7 @@ const i18n = {
'system_info.real_time_data': 'Real-time Data',
'system_info.not_loaded': 'Not Loaded',
'system_info.seconds_ago': 'seconds ago',
// Notification messages
'notification.debug_updated': 'Debug settings updated',
'notification.proxy_updated': 'Proxy settings updated',
@@ -590,12 +616,12 @@ const i18n = {
'notification.gemini_api_key': 'Gemini API key',
'notification.codex_api_key': 'Codex API key',
'notification.claude_api_key': 'Claude API key',
// Language switch
'language.switch': 'Language',
'language.chinese': '中文',
'language.english': 'English',
// Theme switch
'theme.switch': 'Theme',
'theme.light': 'Light',
@@ -603,25 +629,25 @@ const i18n = {
'theme.switch_to_light': 'Switch to light mode',
'theme.switch_to_dark': 'Switch to dark mode',
'theme.auto': 'Follow system',
// Footer
'footer.version': 'Version',
'footer.author': 'Author'
}
},
// 获取翻译文本
t(key, params = {}) {
const translation = this.translations[this.currentLanguage]?.[key] ||
this.translations[this.fallbackLanguage]?.[key] ||
key;
const translation = this.translations[this.currentLanguage]?.[key] ||
this.translations[this.fallbackLanguage]?.[key] ||
key;
// 简单的参数替换
return translation.replace(/\{(\w+)\}/g, (match, param) => {
return params[param] || match;
});
},
// 设置语言
setLanguage(lang) {
if (this.translations[lang]) {
@@ -631,19 +657,19 @@ const i18n = {
this.updateAllTexts();
}
},
// 更新页面语言属性
updatePageLanguage() {
document.documentElement.lang = this.currentLanguage;
},
// 更新所有文本
updateAllTexts() {
// 更新所有带有 data-i18n 属性的元素
document.querySelectorAll('[data-i18n]').forEach(element => {
const key = element.getAttribute('data-i18n');
const text = this.t(key);
if (element.tagName === 'INPUT' && (element.type === 'text' || element.type === 'password')) {
element.placeholder = text;
} else if (element.tagName === 'TITLE') {
@@ -652,7 +678,7 @@ const i18n = {
element.textContent = text;
}
});
// 更新所有带有 data-i18n-html 属性的元素支持HTML
document.querySelectorAll('[data-i18n-html]').forEach(element => {
const key = element.getAttribute('data-i18n-html');
@@ -660,7 +686,7 @@ const i18n = {
element.innerHTML = html;
});
},
// 初始化
init() {
// 从本地存储获取用户偏好语言
@@ -676,7 +702,7 @@ const i18n = {
this.currentLanguage = 'en-US';
}
}
this.updatePageLanguage();
this.updateAllTexts();
}

View File

@@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -9,6 +10,7 @@
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="i18n.js"></script>
</head>
<body>
<!-- 自动登录加载页面 -->
<div id="auto-login-loading" class="login-container" style="display: none;">
@@ -48,7 +50,7 @@
</div>
</div>
</div>
<div class="login-body">
<div class="login-connection-info">
<div class="connection-summary">
@@ -69,8 +71,10 @@
<div class="form-group">
<label for="login-api-base" data-i18n="login.custom_connection_label">自定义连接地址:</label>
<div class="input-group">
<input type="text" id="login-api-base" data-i18n="login.custom_connection_placeholder" placeholder="例如: https://example.com:8317">
<button type="button" id="login-reset-api-base" class="btn btn-secondary connection-reset-btn">
<input type="text" id="login-api-base" data-i18n="login.custom_connection_placeholder"
placeholder="例如: https://example.com:8317">
<button type="button" id="login-reset-api-base"
class="btn btn-secondary connection-reset-btn">
<i class="fas fa-location-arrow"></i>
<span data-i18n="login.use_current_address">使用当前地址</span>
</button>
@@ -81,7 +85,8 @@
<div class="form-group">
<label for="login-management-key" data-i18n="login.management_key_label">管理密钥:</label>
<div class="input-group">
<input type="password" id="login-management-key" data-i18n="login.management_key_placeholder" placeholder="请输入管理密钥" required>
<input type="password" id="login-management-key"
data-i18n="login.management_key_placeholder" placeholder="请输入管理密钥" required>
<button type="button" class="btn btn-secondary toggle-key-visibility">
<i class="fas fa-eye"></i>
</button>
@@ -89,14 +94,14 @@
</div>
</form>
</div>
<!-- 连接按钮 -->
<div class="form-actions">
<button type="button" id="login-submit" class="btn btn-primary login-btn">
<i class="fas fa-plug"></i> <span data-i18n="login.connect_button">Connect</span>
</button>
</div>
<div id="login-error" class="login-error" style="display: none;">
<i class="fas fa-exclamation-triangle"></i>
<span id="login-error-message"></span>
@@ -150,24 +155,25 @@
<nav class="sidebar" id="sidebar">
<!-- 导航菜单 -->
<ul class="nav-menu">
<li data-tooltip="基础设置"><a href="#basic-settings" class="nav-item active" data-section="basic-settings">
<i class="fas fa-sliders-h"></i> <span data-i18n="nav.basic_settings">基础设置</span>
</a></li>
<li data-tooltip="基础设置"><a href="#basic-settings" class="nav-item active"
data-section="basic-settings">
<i class="fas fa-sliders-h"></i> <span data-i18n="nav.basic_settings">基础设置</span>
</a></li>
<li data-tooltip="API 密钥"><a href="#api-keys" class="nav-item" data-section="api-keys">
<i class="fas fa-key"></i> <span data-i18n="nav.api_keys">API 密钥</span>
</a></li>
<i class="fas fa-key"></i> <span data-i18n="nav.api_keys">API 密钥</span>
</a></li>
<li data-tooltip="AI 提供商"><a href="#ai-providers" class="nav-item" data-section="ai-providers">
<i class="fas fa-robot"></i> <span data-i18n="nav.ai_providers">AI 提供商</span>
</a></li>
<i class="fas fa-robot"></i> <span data-i18n="nav.ai_providers">AI 提供商</span>
</a></li>
<li data-tooltip="认证文件"><a href="#auth-files" class="nav-item" data-section="auth-files">
<i class="fas fa-file-alt"></i> <span data-i18n="nav.auth_files">认证文件</span>
</a></li>
<i class="fas fa-file-alt"></i> <span data-i18n="nav.auth_files">认证文件</span>
</a></li>
<li data-tooltip="使用统计"><a href="#usage-stats" class="nav-item" data-section="usage-stats">
<i class="fas fa-chart-line"></i> <span data-i18n="nav.usage_stats">使用统计</span>
</a></li>
<i class="fas fa-chart-line"></i> <span data-i18n="nav.usage_stats">使用统计</span>
</a></li>
<li data-tooltip="系统信息"><a href="#system-info" class="nav-item" data-section="system-info">
<i class="fas fa-info-circle"></i> <span data-i18n="nav.system_info">系统信息</span>
</a></li>
<i class="fas fa-info-circle"></i> <span data-i18n="nav.system_info">系统信息</span>
</a></li>
</ul>
</nav>
@@ -178,416 +184,501 @@
<div class="main-wrapper" id="main-wrapper">
<!-- 主内容区域 -->
<div class="main-content">
<!-- 内容区域 -->
<div class="content-area">
<!-- 基础设置 -->
<section id="basic-settings" class="content-section active">
<h2 data-i18n="basic_settings.title">基础设置</h2>
<!-- Debug 设置 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-bug"></i> <span data-i18n="basic_settings.debug_title">调试模式</span></h3>
</div>
<div class="card-content">
<div class="toggle-group">
<label class="toggle-switch">
<input type="checkbox" id="debug-toggle">
<span class="slider"></span>
</label>
<span class="toggle-label" data-i18n="basic_settings.debug_enable">启用调试模式</span>
</div>
</div>
</div>
<!-- 内容区域 -->
<div class="content-area">
<!-- 基础设置 -->
<section id="basic-settings" class="content-section active">
<h2 data-i18n="basic_settings.title">基础设置</h2>
<!-- 代理设置 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-network-wired"></i> <span data-i18n="basic_settings.proxy_title">代理设置</span></h3>
</div>
<div class="card-content">
<div class="form-group">
<label for="proxy-url" data-i18n="basic_settings.proxy_url_label">代理 URL:</label>
<div class="input-group">
<input type="text" id="proxy-url" data-i18n="basic_settings.proxy_url_placeholder" placeholder="例如: socks5://user:pass@127.0.0.1:1080/">
<button id="update-proxy" class="btn btn-primary" data-i18n="basic_settings.proxy_update">更新</button>
<button id="clear-proxy" class="btn btn-danger" data-i18n="basic_settings.proxy_clear">清空</button>
<!-- Debug 设置 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-bug"></i> <span
data-i18n="basic_settings.debug_title">调试模式</span></h3>
</div>
</div>
</div>
</div>
<!-- 请求重试设置 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-redo"></i> <span data-i18n="basic_settings.retry_title">请求重试</span></h3>
</div>
<div class="card-content">
<div class="form-group">
<label for="request-retry" data-i18n="basic_settings.retry_count_label">重试次数:</label>
<div class="input-group">
<input type="number" id="request-retry" min="0" max="10" value="3">
<button id="update-retry" class="btn btn-primary" data-i18n="basic_settings.retry_update">更新</button>
</div>
</div>
</div>
</div>
<!-- 配额超出行为 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-exclamation-triangle"></i> <span data-i18n="basic_settings.quota_title">配额超出行为</span></h3>
</div>
<div class="card-content">
<div class="toggle-group">
<label class="toggle-switch">
<input type="checkbox" id="switch-project-toggle">
<span class="slider"></span>
</label>
<span class="toggle-label" data-i18n="basic_settings.quota_switch_project">自动切换项目</span>
</div>
<div class="toggle-group">
<label class="toggle-switch">
<input type="checkbox" id="switch-preview-model-toggle">
<span class="slider"></span>
</label>
<span class="toggle-label" data-i18n="basic_settings.quota_switch_preview">切换到预览模型</span>
</div>
</div>
</div>
</section>
<!-- API 密钥管理 -->
<section id="api-keys" class="content-section">
<h2 data-i18n="api_keys.title">API 密钥管理</h2>
<div class="card">
<div class="card-header">
<h3><i class="fas fa-key"></i> <span data-i18n="api_keys.proxy_auth_title">代理服务认证密钥</span></h3>
<button id="add-api-key" class="btn btn-primary">
<i class="fas fa-plus"></i> <span data-i18n="api_keys.add_button">添加密钥</span>
</button>
</div>
<div class="card-content">
<div id="api-keys-list" class="key-list"></div>
</div>
</div>
</section>
<!-- AI 提供商 -->
<section id="ai-providers" class="content-section">
<h2 data-i18n="ai_providers.title">AI 提供商配置</h2>
<!-- Gemini API Keys -->
<div class="card">
<div class="card-header">
<h3><i class="fab fa-google"></i> <span data-i18n="ai_providers.gemini_title">Gemini API 密钥</span></h3>
<button id="add-gemini-key" class="btn btn-primary">
<i class="fas fa-plus"></i> <span data-i18n="ai_providers.gemini_add_button">添加密钥</span>
</button>
</div>
<div class="card-content">
<div id="gemini-keys-list" class="key-list"></div>
</div>
</div>
<!-- Codex API Keys -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-code"></i> <span data-i18n="ai_providers.codex_title">Codex API 配置</span></h3>
<button id="add-codex-key" class="btn btn-primary">
<i class="fas fa-plus"></i> <span data-i18n="ai_providers.codex_add_button">添加配置</span>
</button>
</div>
<div class="card-content">
<div id="codex-keys-list" class="provider-list"></div>
</div>
</div>
<!-- Claude API Keys -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-brain"></i> <span data-i18n="ai_providers.claude_title">Claude API 配置</span></h3>
<button id="add-claude-key" class="btn btn-primary">
<i class="fas fa-plus"></i> <span data-i18n="ai_providers.claude_add_button">添加配置</span>
</button>
</div>
<div class="card-content">
<div id="claude-keys-list" class="provider-list"></div>
</div>
</div>
<!-- OpenAI 兼容提供商 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-plug"></i> <span data-i18n="ai_providers.openai_title">OpenAI 兼容提供商</span></h3>
<button id="add-openai-provider" class="btn btn-primary">
<i class="fas fa-plus"></i> <span data-i18n="ai_providers.openai_add_button">添加提供商</span>
</button>
</div>
<div class="card-content">
<div id="openai-providers-list" class="provider-list"></div>
</div>
</div>
</section>
<!-- 认证文件管理 -->
<section id="auth-files" class="content-section">
<h2 data-i18n="auth_files.title">认证文件管理</h2>
<div class="card" style="margin-bottom: 20px;">
<div class="card-content">
<p class="form-hint" data-i18n="auth_files.description">
这里管理 Qwen 和 Gemini 的认证配置文件。上传 JSON 格式的认证文件以启用相应的 AI 服务。
</p>
</div>
</div>
<!-- Gemini Web Token -->
<div class="card">
<div class="card-header">
<h3><i class="fab fa-google"></i> <span data-i18n="auth_login.gemini_web_title">Gemini Web Token</span></h3>
<button id="gemini-web-token-btn" class="btn btn-primary">
<i class="fas fa-save"></i> <span data-i18n="auth_login.gemini_web_button">保存 Gemini Web Token</span>
</button>
</div>
<div class="card-content">
<p class="form-hint" style="margin-bottom: 20px;" data-i18n="auth_login.gemini_web_hint">
从浏览器开发者工具中获取 Gemini 网页版的 Cookie 值,用于直接认证访问 Gemini。
</p>
<div class="form-group">
<label for="secure-1psid-input" data-i18n="auth_login.secure_1psid_label">__Secure-1PSID Cookie:</label>
<input type="text" id="secure-1psid-input" data-i18n="auth_login.secure_1psid_placeholder" placeholder="输入 __Secure-1PSID cookie 值">
</div>
<div class="form-group">
<label for="secure-1psidts-input" data-i18n="auth_login.secure_1psidts_label">__Secure-1PSIDTS Cookie:</label>
<input type="text" id="secure-1psidts-input" data-i18n="auth_login.secure_1psidts_placeholder" placeholder="输入 __Secure-1PSIDTS cookie 值">
</div>
<div class="form-group">
<label for="gemini-web-label-input" data-i18n="auth_login.gemini_web_label_label">Label (Optional):</label>
<input type="text" id="gemini-web-label-input" data-i18n="auth_login.gemini_web_label_placeholder" placeholder="输入标签名称 (可选)">
</div>
</div>
</div>
<!-- 认证文件 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-file-alt"></i> <span data-i18n="auth_files.title_section">认证文件</span></h3>
<div class="header-actions">
<button id="upload-auth-file" class="btn btn-primary">
<i class="fas fa-upload"></i> <span data-i18n="auth_files.upload_button">上传文件</span>
</button>
<button id="delete-all-auth-files" class="btn btn-danger">
<i class="fas fa-trash"></i> <span data-i18n="auth_files.delete_all_button">删除全部</span>
</button>
</div>
</div>
<div class="card-content">
<div id="auth-files-list" class="file-list"></div>
<input type="file" id="auth-file-input" accept=".json" style="display: none;">
</div>
</div>
</section>
<!-- 使用统计 -->
<section id="usage-stats" class="content-section">
<h2 data-i18n="usage_stats.title">使用统计</h2>
<!-- 概览统计卡片 -->
<div class="stats-overview">
<div class="stat-card">
<div class="stat-icon">
<i class="fas fa-paper-plane"></i>
</div>
<div class="stat-content">
<div class="stat-number" id="total-requests">0</div>
<div class="stat-label" data-i18n="usage_stats.total_requests">总请求数</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon success">
<i class="fas fa-check-circle"></i>
</div>
<div class="stat-content">
<div class="stat-number" id="success-requests">0</div>
<div class="stat-label" data-i18n="usage_stats.success_requests">成功请求</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon error">
<i class="fas fa-exclamation-circle"></i>
</div>
<div class="stat-content">
<div class="stat-number" id="failed-requests">0</div>
<div class="stat-label" data-i18n="usage_stats.failed_requests">失败请求</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">
<i class="fas fa-coins"></i>
</div>
<div class="stat-content">
<div class="stat-number" id="total-tokens">0</div>
<div class="stat-label" data-i18n="usage_stats.total_tokens">总Token数</div>
</div>
</div>
</div>
<!-- 图表区域 -->
<div class="charts-container">
<!-- 请求趋势图 -->
<div class="card chart-card">
<div class="card-header">
<h3><i class="fas fa-chart-line"></i> <span data-i18n="usage_stats.requests_trend">请求趋势</span></h3>
<div class="chart-controls">
<button class="btn btn-small" data-period="hour" id="requests-hour-btn">
<span data-i18n="usage_stats.by_hour">按小时</span>
</button>
<button class="btn btn-small active" data-period="day" id="requests-day-btn">
<span data-i18n="usage_stats.by_day">按天</span>
</button>
</div>
</div>
<div class="card-content">
<div class="chart-container">
<canvas id="requests-chart"></canvas>
</div>
</div>
</div>
<!-- Token使用趋势图 -->
<div class="card chart-card">
<div class="card-header">
<h3><i class="fas fa-chart-area"></i> <span data-i18n="usage_stats.tokens_trend">Token 使用趋势</span></h3>
<div class="chart-controls">
<button class="btn btn-small" data-period="hour" id="tokens-hour-btn">
<span data-i18n="usage_stats.by_hour">按小时</span>
</button>
<button class="btn btn-small active" data-period="day" id="tokens-day-btn">
<span data-i18n="usage_stats.by_day">按天</span>
</button>
</div>
</div>
<div class="card-content">
<div class="chart-container">
<canvas id="tokens-chart"></canvas>
</div>
</div>
</div>
</div>
<!-- API详细统计 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-list"></i> <span data-i18n="usage_stats.api_details">API 详细统计</span></h3>
<button id="refresh-usage-stats" class="btn btn-primary">
<i class="fas fa-sync-alt"></i> <span data-i18n="usage_stats.refresh">刷新</span>
</button>
</div>
<div class="card-content">
<div id="api-stats-table" class="api-stats-table">
<div class="loading-placeholder" data-i18n="common.loading">正在加载...</div>
</div>
</div>
</div>
</section>
<!-- 系统信息 -->
<section id="system-info" class="content-section">
<h2 data-i18n="system_info.title">系统信息</h2>
<!-- 连接信息卡片 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-server"></i> <span data-i18n="connection.title">连接信息</span></h3>
</div>
<div class="card-content">
<div class="connection-info">
<div class="info-item">
<div class="info-label">
<i class="fas fa-globe"></i>
<span data-i18n="connection.server_address">服务器地址:</span>
</div>
<div class="info-value" id="display-api-url">-</div>
</div>
<div class="info-item">
<div class="info-label">
<i class="fas fa-key"></i>
<span data-i18n="connection.management_key">管理密钥:</span>
</div>
<div class="info-value" id="display-management-key">-</div>
</div>
<div class="info-item">
<div class="info-label">
<i class="fas fa-circle"></i>
<span data-i18n="connection.status">连接状态:</span>
</div>
<div class="info-value" id="display-connection-status">
<span class="status-indicator disconnected" data-i18n="common.disconnected">未连接</span>
<div class="card-content">
<div class="toggle-group">
<label class="toggle-switch">
<input type="checkbox" id="debug-toggle">
<span class="slider"></span>
</label>
<span class="toggle-label" data-i18n="basic_settings.debug_enable">启用调试模式</span>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h3><i class="fas fa-info-circle"></i> <span data-i18n="system_info.connection_status_title">连接状态</span></h3>
</div>
<div class="card-content">
<div id="system-status" class="status-info">
<div class="status-item">
<span class="status-label" data-i18n="system_info.api_status_label">API 状态:</span>
<span id="api-status" class="status-value" data-i18n="common.disconnected">未连接</span>
<!-- 代理设置 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-network-wired"></i> <span
data-i18n="basic_settings.proxy_title">代理设置</span></h3>
</div>
<div class="status-item">
<span class="status-label" data-i18n="system_info.config_status_label">配置状态:</span>
<span id="config-status" class="status-value" data-i18n="system_info.not_loaded">未加载</span>
</div>
<div class="status-item">
<span class="status-label" data-i18n="system_info.last_update_label">最后更新:</span>
<span id="last-update" class="status-value">-</span>
<div class="card-content">
<div class="form-group">
<label for="proxy-url" data-i18n="basic_settings.proxy_url_label">代理
URL:</label>
<div class="input-group">
<input type="text" id="proxy-url"
data-i18n="basic_settings.proxy_url_placeholder"
placeholder="例如: socks5://user:pass@127.0.0.1:1080/">
<button id="update-proxy" class="btn btn-primary"
data-i18n="basic_settings.proxy_update">更新</button>
<button id="clear-proxy" class="btn btn-danger"
data-i18n="basic_settings.proxy_clear">清空</button>
</div>
</div>
</div>
</div>
</div>
<!-- 请求重试设置 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-redo"></i> <span
data-i18n="basic_settings.retry_title">请求重试</span></h3>
</div>
<div class="card-content">
<div class="form-group">
<label for="request-retry"
data-i18n="basic_settings.retry_count_label">重试次数:</label>
<div class="input-group">
<input type="number" id="request-retry" min="0" max="10" value="3">
<button id="update-retry" class="btn btn-primary"
data-i18n="basic_settings.retry_update">更新</button>
</div>
</div>
</div>
</div>
<!-- 配额超出行为 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-exclamation-triangle"></i> <span
data-i18n="basic_settings.quota_title">配额超出行为</span></h3>
</div>
<div class="card-content">
<div class="toggle-group">
<label class="toggle-switch">
<input type="checkbox" id="switch-project-toggle">
<span class="slider"></span>
</label>
<span class="toggle-label"
data-i18n="basic_settings.quota_switch_project">自动切换项目</span>
</div>
<div class="toggle-group">
<label class="toggle-switch">
<input type="checkbox" id="switch-preview-model-toggle">
<span class="slider"></span>
</label>
<span class="toggle-label"
data-i18n="basic_settings.quota_switch_preview">切换到预览模型</span>
</div>
</div>
</div>
</section>
<!-- API 密钥管理 -->
<section id="api-keys" class="content-section">
<h2 data-i18n="api_keys.title">API 密钥管理</h2>
<div class="card">
<div class="card-header">
<h3><i class="fas fa-key"></i> <span
data-i18n="api_keys.proxy_auth_title">代理服务认证密钥</span></h3>
<button id="add-api-key" class="btn btn-primary">
<i class="fas fa-plus"></i> <span data-i18n="api_keys.add_button">添加密钥</span>
</button>
</div>
<div class="card-content">
<div id="api-keys-list" class="key-list"></div>
</div>
</div>
</section>
<!-- AI 提供商 -->
<section id="ai-providers" class="content-section">
<h2 data-i18n="ai_providers.title">AI 提供商配置</h2>
<!-- Gemini API Keys -->
<div class="card">
<div class="card-header">
<h3><i class="fab fa-google"></i> <span data-i18n="ai_providers.gemini_title">Gemini
API 密钥</span></h3>
<button id="add-gemini-key" class="btn btn-primary">
<i class="fas fa-plus"></i> <span
data-i18n="ai_providers.gemini_add_button">添加密钥</span>
</button>
</div>
<div class="card-content">
<div id="gemini-keys-list" class="key-list"></div>
</div>
</div>
<!-- Codex API Keys -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-code"></i> <span data-i18n="ai_providers.codex_title">Codex API
配置</span></h3>
<button id="add-codex-key" class="btn btn-primary">
<i class="fas fa-plus"></i> <span
data-i18n="ai_providers.codex_add_button">添加配置</span>
</button>
</div>
<div class="card-content">
<div id="codex-keys-list" class="provider-list"></div>
</div>
</div>
<!-- Claude API Keys -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-brain"></i> <span data-i18n="ai_providers.claude_title">Claude
API 配置</span></h3>
<button id="add-claude-key" class="btn btn-primary">
<i class="fas fa-plus"></i> <span
data-i18n="ai_providers.claude_add_button">添加配置</span>
</button>
</div>
<div class="card-content">
<div id="claude-keys-list" class="provider-list"></div>
</div>
</div>
<!-- OpenAI 兼容提供商 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-plug"></i> <span data-i18n="ai_providers.openai_title">OpenAI
兼容提供商</span></h3>
<button id="add-openai-provider" class="btn btn-primary">
<i class="fas fa-plus"></i> <span
data-i18n="ai_providers.openai_add_button">添加提供商</span>
</button>
</div>
<div class="card-content">
<div id="openai-providers-list" class="provider-list"></div>
</div>
</div>
</section>
<!-- 认证文件管理 -->
<section id="auth-files" class="content-section">
<h2 data-i18n="auth_files.title">认证文件管理</h2>
<div class="card" style="margin-bottom: 20px;">
<div class="card-content">
<p class="form-hint" data-i18n="auth_files.description">
这里管理 Qwen 和 Gemini 的认证配置文件。上传 JSON 格式的认证文件以启用相应的 AI 服务。
</p>
</div>
</div>
<!-- Gemini Web Token -->
<div class="card">
<div class="card-header">
<h3><i class="fab fa-google"></i> <span
data-i18n="auth_login.gemini_web_title">Gemini Web Token</span></h3>
<button id="gemini-web-token-btn" class="btn btn-primary">
<i class="fas fa-save"></i> <span data-i18n="auth_login.gemini_web_button">保存
Gemini Web Token</span>
</button>
</div>
<div class="card-content">
<p class="form-hint" style="margin-bottom: 20px;"
data-i18n="auth_login.gemini_web_hint">
从浏览器开发者工具中获取 Gemini 网页版的 Cookie 值,用于直接认证访问 Gemini。
</p>
<div class="form-group">
<label for="secure-1psid-input"
data-i18n="auth_login.secure_1psid_label">__Secure-1PSID Cookie:</label>
<input type="text" id="secure-1psid-input"
data-i18n="auth_login.secure_1psid_placeholder"
placeholder="输入 __Secure-1PSID cookie 值">
</div>
<div class="form-group">
<label for="secure-1psidts-input"
data-i18n="auth_login.secure_1psidts_label">__Secure-1PSIDTS Cookie:</label>
<input type="text" id="secure-1psidts-input"
data-i18n="auth_login.secure_1psidts_placeholder"
placeholder="输入 __Secure-1PSIDTS cookie 值">
</div>
<div class="form-group">
<label for="gemini-web-label-input"
data-i18n="auth_login.gemini_web_label_label">Label (Optional):</label>
<input type="text" id="gemini-web-label-input"
data-i18n="auth_login.gemini_web_label_placeholder"
placeholder="输入标签名称 (可选)">
</div>
</div>
</div>
<!-- 认证文件 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-file-alt"></i> <span
data-i18n="auth_files.title_section">认证文件</span></h3>
<div class="header-actions">
<button id="upload-auth-file" class="btn btn-primary">
<i class="fas fa-upload"></i> <span
data-i18n="auth_files.upload_button">上传文件</span>
</button>
<button id="delete-all-auth-files" class="btn btn-danger">
<i class="fas fa-trash"></i> <span
data-i18n="auth_files.delete_all_button">删除全部</span>
</button>
</div>
</div>
<div class="card-content">
<div id="auth-files-list" class="file-list"></div>
<input type="file" id="auth-file-input" accept=".json" style="display: none;">
</div>
</div>
<!-- Codex OAuth -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-code"></i> <span data-i18n="auth_login.codex_oauth_title">Codex
OAuth</span></h3>
<button id="codex-oauth-btn" class="btn btn-primary">
<i class="fas fa-sign-in-alt"></i> <span
data-i18n="auth_login.codex_oauth_button">开始 Codex 登录</span>
</button>
</div>
<div class="card-content">
<p class="form-hint" style="margin-bottom: 20px;"
data-i18n="auth_login.codex_oauth_hint">
通过 OAuth 流程登录 Codex 服务,自动获取并保存认证文件。
</p>
<div id="codex-oauth-content" style="display: none;">
<div class="form-group">
<label data-i18n="auth_login.codex_oauth_url_label">授权链接:</label>
<div class="input-group">
<input type="text" id="codex-oauth-url" readonly>
<button id="codex-open-link" class="btn btn-primary">
<i class="fas fa-external-link-alt"></i> <span
data-i18n="auth_login.codex_open_link">打开链接</span>
</button>
<button id="codex-copy-link" class="btn btn-secondary">
<i class="fas fa-copy"></i> <span
data-i18n="auth_login.codex_copy_link">复制链接</span>
</button>
</div>
</div>
<div id="codex-oauth-status" class="form-hint" style="margin-top: 10px;"></div>
</div>
</div>
</div>
</section>
<!-- 使用统计 -->
<section id="usage-stats" class="content-section">
<h2 data-i18n="usage_stats.title">使用统计</h2>
<!-- 概览统计卡片 -->
<div class="stats-overview">
<div class="stat-card">
<div class="stat-icon">
<i class="fas fa-paper-plane"></i>
</div>
<div class="stat-content">
<div class="stat-number" id="total-requests">0</div>
<div class="stat-label" data-i18n="usage_stats.total_requests">总请求数</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon success">
<i class="fas fa-check-circle"></i>
</div>
<div class="stat-content">
<div class="stat-number" id="success-requests">0</div>
<div class="stat-label" data-i18n="usage_stats.success_requests">成功请求</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon error">
<i class="fas fa-exclamation-circle"></i>
</div>
<div class="stat-content">
<div class="stat-number" id="failed-requests">0</div>
<div class="stat-label" data-i18n="usage_stats.failed_requests">失败请求</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">
<i class="fas fa-coins"></i>
</div>
<div class="stat-content">
<div class="stat-number" id="total-tokens">0</div>
<div class="stat-label" data-i18n="usage_stats.total_tokens">总Token数</div>
</div>
</div>
</div>
<!-- 图表区域 -->
<div class="charts-container">
<!-- 请求趋势图 -->
<div class="card chart-card">
<div class="card-header">
<h3><i class="fas fa-chart-line"></i> <span
data-i18n="usage_stats.requests_trend">请求趋势</span></h3>
<div class="chart-controls">
<button class="btn btn-small" data-period="hour" id="requests-hour-btn">
<span data-i18n="usage_stats.by_hour">按小时</span>
</button>
<button class="btn btn-small active" data-period="day"
id="requests-day-btn">
<span data-i18n="usage_stats.by_day">按天</span>
</button>
</div>
</div>
<div class="card-content">
<div class="chart-container">
<canvas id="requests-chart"></canvas>
</div>
</div>
</div>
<!-- Token使用趋势图 -->
<div class="card chart-card">
<div class="card-header">
<h3><i class="fas fa-chart-area"></i> <span
data-i18n="usage_stats.tokens_trend">Token 使用趋势</span></h3>
<div class="chart-controls">
<button class="btn btn-small" data-period="hour" id="tokens-hour-btn">
<span data-i18n="usage_stats.by_hour">按小时</span>
</button>
<button class="btn btn-small active" data-period="day" id="tokens-day-btn">
<span data-i18n="usage_stats.by_day">按天</span>
</button>
</div>
</div>
<div class="card-content">
<div class="chart-container">
<canvas id="tokens-chart"></canvas>
</div>
</div>
</div>
</div>
<!-- API详细统计 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-list"></i> <span data-i18n="usage_stats.api_details">API
详细统计</span></h3>
<button id="refresh-usage-stats" class="btn btn-primary">
<i class="fas fa-sync-alt"></i> <span data-i18n="usage_stats.refresh">刷新</span>
</button>
</div>
<div class="card-content">
<div id="api-stats-table" class="api-stats-table">
<div class="loading-placeholder" data-i18n="common.loading">正在加载...</div>
</div>
</div>
</div>
</section>
<!-- 系统信息 -->
<section id="system-info" class="content-section">
<h2 data-i18n="system_info.title">系统信息</h2>
<!-- 连接信息卡片 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-server"></i> <span data-i18n="connection.title">连接信息</span>
</h3>
</div>
<div class="card-content">
<div class="connection-info">
<div class="info-item">
<div class="info-label">
<i class="fas fa-globe"></i>
<span data-i18n="connection.server_address">服务器地址:</span>
</div>
<div class="info-value" id="display-api-url">-</div>
</div>
<div class="info-item">
<div class="info-label">
<i class="fas fa-key"></i>
<span data-i18n="connection.management_key">管理密钥:</span>
</div>
<div class="info-value" id="display-management-key">-</div>
</div>
<div class="info-item">
<div class="info-label">
<i class="fas fa-circle"></i>
<span data-i18n="connection.status">连接状态:</span>
</div>
<div class="info-value" id="display-connection-status">
<span class="status-indicator disconnected"
data-i18n="common.disconnected">未连接</span>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h3><i class="fas fa-info-circle"></i> <span
data-i18n="system_info.connection_status_title">连接状态</span></h3>
</div>
<div class="card-content">
<div id="system-status" class="status-info">
<div class="status-item">
<span class="status-label" data-i18n="system_info.api_status_label">API
状态:</span>
<span id="api-status" class="status-value"
data-i18n="common.disconnected">未连接</span>
</div>
<div class="status-item">
<span class="status-label"
data-i18n="system_info.config_status_label">配置状态:</span>
<span id="config-status" class="status-value"
data-i18n="system_info.not_loaded">未加载</span>
</div>
<div class="status-item">
<span class="status-label"
data-i18n="system_info.last_update_label">最后更新:</span>
<span id="last-update" class="status-value">-</span>
</div>
</div>
</div>
</div>
</section>
</div>
</section>
<!-- /内容区域 -->
<!-- 版本信息 -->
<footer class="version-footer">
<div class="version-info">
<span data-i18n="footer.version">版本</span>: v0.1.0
<span class="separator"></span>
<span data-i18n="footer.author">作者</span>: Supra4E8C
</div>
</footer>
</div>
<!-- /内容区域 -->
<!-- 版本信息 -->
<footer class="version-footer">
<div class="version-info">
<span data-i18n="footer.version">版本</span>: v0.1.0
<span class="separator"></span>
<span data-i18n="footer.author">作者</span>: Supra4E8C
</div>
</footer>
<!-- /内容区域 -->
</div>
<!-- /主内容区域 -->
<!-- /主内容包装器 -->
</div>
<!-- /主内容包装器 -->
</div>
<!-- /主页面 -->
<!-- /主页面 -->
<!-- 模态框 -->
<div id="modal" class="modal">
<div class="modal-content">
<span class="close">&times;</span>
<div id="modal-body"></div>
<!-- 模态框 -->
<div id="modal" class="modal">
<div class="modal-content">
<span class="close">&times;</span>
<div id="modal-body"></div>
</div>
</div>
</div>
<!-- 通知 -->
<div id="notification" class="notification"></div>
<!-- 通知 -->
<div id="notification" class="notification"></div>
</div>
<script src="app.js"></script>
</body>
</html>
</html>

View File

@@ -9,30 +9,30 @@
:root {
/* 布局尺寸 */
--navbar-height: 69px;
/* 亮色主题(默认) */
--bg-primary: #f5f7fa;
--bg-secondary: #ffffff;
--bg-tertiary: #f8f9fb;
--bg-quaternary: #f7fafc;
--bg-modal: rgba(0, 0, 0, 0.5);
/* 侧边栏颜色 */
--sidebar-bg: #ffffff;
--sidebar-hover: #f0f2f5;
--sidebar-active: #e8f4ff;
--sidebar-border: #e5e7eb;
--text-primary: #1f2937;
--text-secondary: #4b5563;
--text-tertiary: #6b7280;
--text-quaternary: #9ca3af;
--text-inverse: white;
--border-primary: #e5e7eb;
--border-secondary: #d1d5db;
--border-focus: #3b82f6;
--accent-primary: #3b82f6;
--accent-secondary: #e5e7eb;
--accent-tertiary: #f3f4f6;
@@ -40,25 +40,25 @@
--primary-hover: #2563eb;
--card-bg: #ffffff;
--border-color: #e5e7eb;
--success-bg: #d1fae5;
--success-text: #065f46;
--success-border: #6ee7b7;
--error-bg: #fee2e2;
--error-text: #991b1b;
--error-border: #fca5a5;
--warning-bg: #fef3c7;
--warning-text: #92400e;
--warning-border: #fbbf24;
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-primary: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--shadow-secondary: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
--shadow-modal: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
--glass-bg: rgba(255, 255, 255, 0.95);
--glass-border: rgba(229, 231, 235, 0.8);
}
@@ -70,23 +70,23 @@
--bg-tertiary: #1a202c;
--bg-quaternary: #2d3748;
--bg-modal: rgba(0, 0, 0, 0.7);
/* 侧边栏颜色 */
--sidebar-bg: #1f2937;
--sidebar-hover: #374151;
--sidebar-active: #1e3a5f;
--sidebar-border: #374151;
--text-primary: #f9fafb;
--text-secondary: #e5e7eb;
--text-tertiary: #d1d5db;
--text-quaternary: #9ca3af;
--text-inverse: #ffffff;
--border-primary: #374151;
--border-secondary: #4b5563;
--border-focus: #60a5fa;
--accent-primary: #3b82f6;
--accent-secondary: #374151;
--accent-tertiary: #1f2937;
@@ -94,25 +94,25 @@
--primary-hover: #3b82f6;
--card-bg: #1f2937;
--border-color: #374151;
--success-bg: #064e3b;
--success-text: #6ee7b7;
--success-border: #059669;
--error-bg: #7f1d1d;
--error-text: #fca5a5;
--error-border: #dc2626;
--warning-bg: #78350f;
--warning-text: #fbbf24;
--warning-border: #f59e0b;
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3);
--shadow-primary: 0 1px 3px 0 rgba(0, 0, 0, 0.3), 0 1px 2px 0 rgba(0, 0, 0, 0.2);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
--shadow-secondary: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
--shadow-modal: 0 20px 25px -5px rgba(0, 0, 0, 0.4), 0 10px 10px -5px rgba(0, 0, 0, 0.3);
--glass-bg: rgba(31, 41, 59, 0.95);
--glass-border: rgba(75, 85, 99, 0.8);
}
@@ -149,8 +149,13 @@
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.auto-login-content h2 {
@@ -198,7 +203,8 @@
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
margin-top: 8px; /* 与标题拉开距离,避免遮挡 */
margin-top: 8px;
/* 与标题拉开距离,避免遮挡 */
margin-bottom: 20px;
}
@@ -537,29 +543,31 @@
margin: 10px;
max-width: 100%;
}
.login-header-top {
flex-direction: column;
gap: 20px;
align-items: center;
margin-bottom: 30px;
}
.header-controls {
flex-direction: row;
justify-content: center;
gap: 8px;
margin-bottom: 10px; /* 在小屏幕上给控制按钮组添加下边距 */
margin-bottom: 10px;
/* 在小屏幕上给控制按钮组添加下边距 */
}
/* 登录页面小屏幕优化 */
.login-header-top .header-controls {
margin: 8px auto 0 auto; /* 顶部留白,避免与标题拥挤 */
margin: 8px auto 0 auto;
/* 顶部留白,避免与标题拥挤 */
background: rgba(255, 255, 255, 0.08);
padding: 6px;
border-radius: 10px;
}
.login-header-top .language-btn,
.login-header-top .theme-btn {
padding: 6px 10px;
@@ -567,14 +575,15 @@
height: 32px;
min-width: 32px;
}
.language-btn,
.theme-btn {
padding: 8px 16px;
font-size: 13px;
height: 36px; /* 在小屏幕上稍微减小高度 */
height: 36px;
/* 在小屏幕上稍微减小高度 */
}
.login-title {
font-size: 1.5rem;
flex-direction: column;
@@ -583,16 +592,16 @@
justify-content: center;
margin-bottom: 25px;
}
#login-logo {
height: 50px;
}
.login-form .input-group {
flex-direction: column;
align-items: stretch;
}
.login-form .btn-secondary {
width: 100%;
margin-top: 10px;
@@ -851,7 +860,7 @@ body {
margin-left: auto;
}
.top-navbar-actions > * {
.top-navbar-actions>* {
display: inline-flex;
align-items: center;
height: 36px;
@@ -892,7 +901,7 @@ body {
gap: 8px;
}
.top-navbar-actions > * {
.top-navbar-actions>* {
height: 34px;
min-height: 34px;
}
@@ -933,7 +942,7 @@ body {
.header-actions .btn span {
display: none;
}
.header-actions .btn {
padding: 6px 10px;
}
@@ -1017,7 +1026,7 @@ body {
align-items: flex-start;
gap: 8px;
}
.info-value {
max-width: 100%;
width: 100%;
@@ -1049,6 +1058,7 @@ body {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
@@ -1293,11 +1303,11 @@ textarea::placeholder {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
input:checked + .slider {
input:checked+.slider {
background: var(--primary-color);
}
input:checked + .slider:before {
input:checked+.slider:before {
transform: translateX(20px);
}
@@ -1457,6 +1467,7 @@ input:checked + .slider:before {
opacity: 0;
transform: translateY(-30px);
}
to {
opacity: 1;
transform: translateY(0);
@@ -1612,34 +1623,34 @@ input:checked + .slider:before {
transform: translateX(0);
box-shadow: var(--shadow-secondary);
}
/* 移动端强制恢复侧栏展开状态 */
.sidebar.collapsed {
width: 240px !important;
}
.sidebar.collapsed .nav-item span {
opacity: 1 !important;
width: auto !important;
overflow: visible !important;
}
.sidebar.collapsed .nav-item {
justify-content: flex-start !important;
padding: 10px 14px !important;
}
.sidebar.collapsed .nav-item i {
margin-right: 12px !important;
}
.main-wrapper {
margin-left: 0;
width: 100%;
min-height: calc(100vh - var(--navbar-height, 69px));
overflow: hidden;
}
.main-content {
padding: 20px 16px;
}
@@ -1649,18 +1660,18 @@ input:checked + .slider:before {
.top-navbar {
padding: 12px 16px;
}
.top-navbar-title {
font-size: 18px;
}
.top-navbar-actions {
gap: 6px;
flex-wrap: wrap;
justify-content: flex-end;
}
.top-navbar-actions > * {
.top-navbar-actions>* {
height: 34px;
min-height: 34px;
}
@@ -1681,66 +1692,66 @@ input:checked + .slider:before {
.top-navbar-actions .btn span {
display: none;
}
.btn {
padding: 6px 12px;
font-size: 13px;
}
.card {
border-radius: 8px;
}
.card-header {
padding: 16px 20px;
}
.card-content {
padding: 20px;
}
.input-group {
flex-direction: column;
align-items: stretch;
}
.card-header {
flex-direction: column;
gap: 12px;
align-items: flex-start;
}
.card-header .header-actions {
width: 100%;
justify-content: flex-start;
}
/* 模态框响应式 */
.modal-content {
margin: 5% auto;
width: 95%;
max-width: none;
}
#modal-body {
padding: 40px 20px 20px 20px;
}
#modal-body h3 {
font-size: 18px;
margin-bottom: 16px;
}
#modal-body .modal-actions {
flex-direction: column-reverse;
gap: 10px;
}
#modal-body .modal-actions .btn {
width: 100%;
margin: 0;
}
.content-section h2 {
font-size: 20px;
margin-bottom: 20px;
@@ -1803,7 +1814,9 @@ input:checked + .slider:before {
}
@keyframes spin {
to { transform: rotate(360deg); }
to {
transform: rotate(360deg);
}
}
/* 空状态 */
@@ -1899,9 +1912,11 @@ input:checked + .slider:before {
0% {
opacity: 1;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
}
@@ -2211,3 +2226,94 @@ input:checked + .slider:before {
align-self: center;
}
/* Codex OAuth 样式 */
#codex-oauth-content {
transition: all 0.3s ease;
}
#codex-oauth-url {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 13px;
background: var(--bg-tertiary);
border: 1px solid var(--border-primary);
color: var(--text-secondary);
}
#codex-oauth-url:focus {
border-color: var(--border-focus);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
#codex-oauth-status {
font-weight: 500;
padding: 8px 12px;
border-radius: 6px;
background: var(--bg-tertiary);
border: 1px solid var(--border-primary);
transition: all 0.3s ease;
}
#codex-oauth-status.success {
background: var(--success-bg);
border-color: var(--success-border);
color: var(--success-text);
}
#codex-oauth-status.error {
background: var(--error-bg);
border-color: var(--error-border);
color: var(--error-text);
}
#codex-oauth-status.warning {
background: var(--warning-bg);
border-color: var(--warning-border);
color: var(--warning-text);
}
/* Codex OAuth 按钮样式 */
#codex-open-link,
#codex-copy-link {
min-width: 100px;
white-space: nowrap;
}
#codex-open-link {
background: var(--primary-color);
border-color: var(--primary-color);
}
#codex-open-link:hover {
background: var(--primary-hover);
border-color: var(--primary-hover);
}
#codex-copy-link {
background: var(--bg-secondary);
border-color: var(--border-primary);
color: var(--text-secondary);
}
#codex-copy-link:hover {
background: var(--bg-tertiary);
border-color: var(--border-secondary);
color: var(--text-primary);
}
/* 响应式设计 - Codex OAuth */
@media (max-width: 768px) {
#codex-oauth-content .input-group {
flex-direction: column;
align-items: stretch;
}
#codex-open-link,
#codex-copy-link {
width: 100%;
margin-top: 8px;
}
#codex-oauth-url {
font-size: 12px;
}
}