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

View File

@@ -1,5 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh-CN"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <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="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="i18n.js"></script> <script src="i18n.js"></script>
</head> </head>
<body> <body>
<!-- 自动登录加载页面 --> <!-- 自动登录加载页面 -->
<div id="auto-login-loading" class="login-container" style="display: none;"> <div id="auto-login-loading" class="login-container" style="display: none;">
@@ -48,7 +50,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="login-body"> <div class="login-body">
<div class="login-connection-info"> <div class="login-connection-info">
<div class="connection-summary"> <div class="connection-summary">
@@ -69,8 +71,10 @@
<div class="form-group"> <div class="form-group">
<label for="login-api-base" data-i18n="login.custom_connection_label">自定义连接地址:</label> <label for="login-api-base" data-i18n="login.custom_connection_label">自定义连接地址:</label>
<div class="input-group"> <div class="input-group">
<input type="text" id="login-api-base" data-i18n="login.custom_connection_placeholder" placeholder="例如: https://example.com:8317"> <input type="text" id="login-api-base" data-i18n="login.custom_connection_placeholder"
<button type="button" id="login-reset-api-base" class="btn btn-secondary connection-reset-btn"> 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> <i class="fas fa-location-arrow"></i>
<span data-i18n="login.use_current_address">使用当前地址</span> <span data-i18n="login.use_current_address">使用当前地址</span>
</button> </button>
@@ -81,7 +85,8 @@
<div class="form-group"> <div class="form-group">
<label for="login-management-key" data-i18n="login.management_key_label">管理密钥:</label> <label for="login-management-key" data-i18n="login.management_key_label">管理密钥:</label>
<div class="input-group"> <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"> <button type="button" class="btn btn-secondary toggle-key-visibility">
<i class="fas fa-eye"></i> <i class="fas fa-eye"></i>
</button> </button>
@@ -89,14 +94,14 @@
</div> </div>
</form> </form>
</div> </div>
<!-- 连接按钮 --> <!-- 连接按钮 -->
<div class="form-actions"> <div class="form-actions">
<button type="button" id="login-submit" class="btn btn-primary login-btn"> <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> <i class="fas fa-plug"></i> <span data-i18n="login.connect_button">Connect</span>
</button> </button>
</div> </div>
<div id="login-error" class="login-error" style="display: none;"> <div id="login-error" class="login-error" style="display: none;">
<i class="fas fa-exclamation-triangle"></i> <i class="fas fa-exclamation-triangle"></i>
<span id="login-error-message"></span> <span id="login-error-message"></span>
@@ -150,24 +155,25 @@
<nav class="sidebar" id="sidebar"> <nav class="sidebar" id="sidebar">
<!-- 导航菜单 --> <!-- 导航菜单 -->
<ul class="nav-menu"> <ul class="nav-menu">
<li data-tooltip="基础设置"><a href="#basic-settings" class="nav-item active" data-section="basic-settings"> <li data-tooltip="基础设置"><a href="#basic-settings" class="nav-item active"
<i class="fas fa-sliders-h"></i> <span data-i18n="nav.basic_settings">基础设置</span> data-section="basic-settings">
</a></li> <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"> <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> <i class="fas fa-key"></i> <span data-i18n="nav.api_keys">API 密钥</span>
</a></li> </a></li>
<li data-tooltip="AI 提供商"><a href="#ai-providers" class="nav-item" data-section="ai-providers"> <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> <i class="fas fa-robot"></i> <span data-i18n="nav.ai_providers">AI 提供商</span>
</a></li> </a></li>
<li data-tooltip="认证文件"><a href="#auth-files" class="nav-item" data-section="auth-files"> <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> <i class="fas fa-file-alt"></i> <span data-i18n="nav.auth_files">认证文件</span>
</a></li> </a></li>
<li data-tooltip="使用统计"><a href="#usage-stats" class="nav-item" data-section="usage-stats"> <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> <i class="fas fa-chart-line"></i> <span data-i18n="nav.usage_stats">使用统计</span>
</a></li> </a></li>
<li data-tooltip="系统信息"><a href="#system-info" class="nav-item" data-section="system-info"> <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> <i class="fas fa-info-circle"></i> <span data-i18n="nav.system_info">系统信息</span>
</a></li> </a></li>
</ul> </ul>
</nav> </nav>
@@ -178,416 +184,501 @@
<div class="main-wrapper" id="main-wrapper"> <div class="main-wrapper" id="main-wrapper">
<!-- 主内容区域 --> <!-- 主内容区域 -->
<div class="main-content"> <div class="main-content">
<!-- 内容区域 --> <!-- 内容区域 -->
<div class="content-area"> <div class="content-area">
<!-- 基础设置 --> <!-- 基础设置 -->
<section id="basic-settings" class="content-section active"> <section id="basic-settings" class="content-section active">
<h2 data-i18n="basic_settings.title">基础设置</h2> <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>
<!-- 代理设置 --> <!-- Debug 设置 -->
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3><i class="fas fa-network-wired"></i> <span data-i18n="basic_settings.proxy_title">代理设置</span></h3> <h3><i class="fas fa-bug"></i> <span
</div> data-i18n="basic_settings.debug_title">调试模式</span></h3>
<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 class="card-content">
</div> <div class="toggle-group">
</div> <label class="toggle-switch">
<input type="checkbox" id="debug-toggle">
<!-- 请求重试设置 --> <span class="slider"></span>
<div class="card"> </label>
<div class="card-header"> <span class="toggle-label" data-i18n="basic_settings.debug_enable">启用调试模式</span>
<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> </div>
</div> </div>
</div> </div>
</div>
</div> <!-- 代理设置 -->
<div class="card">
<div class="card"> <div class="card-header">
<div class="card-header"> <h3><i class="fas fa-network-wired"></i> <span
<h3><i class="fas fa-info-circle"></i> <span data-i18n="system_info.connection_status_title">连接状态</span></h3> data-i18n="basic_settings.proxy_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>
<div class="status-item"> <div class="card-content">
<span class="status-label" data-i18n="system_info.config_status_label">配置状态:</span> <div class="form-group">
<span id="config-status" class="status-value" data-i18n="system_info.not_loaded">未加载</span> <label for="proxy-url" data-i18n="basic_settings.proxy_url_label">代理
</div> URL:</label>
<div class="status-item"> <div class="input-group">
<span class="status-label" data-i18n="system_info.last_update_label">最后更新:</span> <input type="text" id="proxy-url"
<span id="last-update" class="status-value">-</span> 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>
</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> </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> </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>
<!-- /主内容包装器 --> <!-- /主页面 -->
</div>
<!-- /主页面 -->
<!-- 模态框 --> <!-- 模态框 -->
<div id="modal" class="modal"> <div id="modal" class="modal">
<div class="modal-content"> <div class="modal-content">
<span class="close">&times;</span> <span class="close">&times;</span>
<div id="modal-body"></div> <div id="modal-body"></div>
</div>
</div> </div>
</div>
<!-- 通知 --> <!-- 通知 -->
<div id="notification" class="notification"></div> <div id="notification" class="notification"></div>
</div> </div>
<script src="app.js"></script> <script src="app.js"></script>
</body> </body>
</html>
</html>

View File

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