Compare commits

...

11 Commits

8 changed files with 3153 additions and 1503 deletions

View File

@@ -27,6 +27,8 @@ jobs:
- name: Build all-in-one HTML - name: Build all-in-one HTML
run: npm run build run: npm run build
env:
VERSION: ${{ github.ref_name }}
- name: Prepare release assets - name: Prepare release assets
run: | run: |

View File

@@ -10,7 +10,7 @@ Example URL:
https://remote.router-for.me/ https://remote.router-for.me/
Minimum required version: ≥ 6.0.0 Minimum required version: ≥ 6.0.0
Recommended version: ≥ 6.0.19 Recommended version: ≥ 6.1.3
Since version 6.0.19, the WebUI has been rolled into the main program. You can access it by going to `/management.html` on the external port after firing up the main project. Since version 6.0.19, the WebUI has been rolled into the main program. You can access it by going to `/management.html` on the external port after firing up the main project.
@@ -43,7 +43,6 @@ Since version 6.0.19, the WebUI has been rolled into the main program. You can a
- Download existing authentication files - Download existing authentication files
- Delete single or all authentication files - Delete single or all authentication files
- Display file details - Display file details
- **Gemini Web Token**: Direct authentication using browser cookies
### Usage Statistics ### Usage Statistics
- **Real-time Analytics**: Track API usage with interactive charts - **Real-time Analytics**: Track API usage with interactive charts

View File

@@ -8,7 +8,9 @@ https://github.com/router-for-me/CLIProxyAPI
https://remote.router-for.me/ https://remote.router-for.me/
最低可用版本 ≥ 6.0.0 最低可用版本 ≥ 6.0.0
推荐版本 ≥ 6.0.19
推荐版本 ≥ 6.1.3
自6.0.19起WebUI已经集成在主程序中 可以通过主项目开启的外部端口的`/management.html`访问 自6.0.19起WebUI已经集成在主程序中 可以通过主项目开启的外部端口的`/management.html`访问
## 功能特点 ## 功能特点
@@ -40,7 +42,6 @@ https://remote.router-for.me/
- 下载现有认证文件 - 下载现有认证文件
- 删除单个或所有认证文件 - 删除单个或所有认证文件
- 显示文件详细信息 - 显示文件详细信息
- **Gemini Web Token**: 使用浏览器 Cookie 直接认证
### 使用统计 ### 使用统计
- **实时分析**: 通过交互式图表跟踪 API 使用情况 - **实时分析**: 通过交互式图表跟踪 API 使用情况

969
app.js

File diff suppressed because it is too large Load Diff

View File

@@ -49,6 +49,35 @@ function escapeForStyle(content) {
return content.replace(/<\/(style)/gi, '<\\/$1'); return content.replace(/<\/(style)/gi, '<\\/$1');
} }
function getVersion() {
// 1. 优先从环境变量获取GitHub Actions 会设置)
if (process.env.VERSION) {
return process.env.VERSION;
}
// 2. 尝试从 git tag 获取
try {
const { execSync } = require('child_process');
const gitTag = execSync('git describe --tags --exact-match 2>/dev/null || git describe --tags 2>/dev/null || echo ""', { encoding: 'utf8' }).trim();
if (gitTag) {
return gitTag;
}
} catch (err) {
console.warn('无法从 git 获取版本号');
}
// 3. 回退到 package.json
try {
const packageJson = JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8'));
return 'v' + packageJson.version;
} catch (err) {
console.warn('无法从 package.json 读取版本号');
}
// 4. 最后使用默认值
return 'v0.0.0-dev';
}
function ensureDistDir() { function ensureDistDir() {
if (fs.existsSync(distDir)) { if (fs.existsSync(distDir)) {
fs.rmSync(distDir, { recursive: true, force: true }); fs.rmSync(distDir, { recursive: true, force: true });
@@ -83,6 +112,11 @@ function build() {
const i18n = escapeForScript(readFile(sourceFiles.i18n)); const i18n = escapeForScript(readFile(sourceFiles.i18n));
const app = escapeForScript(readFile(sourceFiles.app)); const app = escapeForScript(readFile(sourceFiles.app));
// 获取版本号并替换
const version = getVersion();
console.log(`使用版本号: ${version}`);
html = html.replace(/__VERSION__/g, version);
html = html.replace( html = html.replace(
'<link rel="stylesheet" href="styles.css">', '<link rel="stylesheet" href="styles.css">',
`<style> `<style>

182
i18n.js
View File

@@ -99,6 +99,8 @@ 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': '切换到预览模型',
'basic_settings.usage_statistics_title': '使用统计',
'basic_settings.usage_statistics_enable': '启用使用统计',
// API 密钥管理 // API 密钥管理
'api_keys.title': 'API 密钥管理', 'api_keys.title': 'API 密钥管理',
@@ -214,17 +216,74 @@ const i18n = {
'auth_files.delete_all_success': '成功删除', 'auth_files.delete_all_success': '成功删除',
'auth_files.files_count': '个文件', 'auth_files.files_count': '个文件',
// Gemini Web Token
'auth_login.gemini_web_title': 'Gemini Web Token', // Codex OAuth
'auth_login.gemini_web_button': '保存 Gemini Web Token', 'auth_login.codex_oauth_title': 'Codex OAuth',
'auth_login.gemini_web_hint': '从浏览器开发者工具中获取 Gemini 网页版的 Cookie 值,用于直接认证访问 Gemini。', 'auth_login.codex_oauth_button': '开始 Codex 登录',
'auth_login.secure_1psid_label': '__Secure-1PSID Cookie:', 'auth_login.codex_oauth_hint': '通过 OAuth 流程登录 Codex 服务,自动获取并保存认证文件。',
'auth_login.secure_1psid_placeholder': '输入 __Secure-1PSID cookie 值', 'auth_login.codex_oauth_url_label': '授权链接:',
'auth_login.secure_1psidts_label': '__Secure-1PSIDTS Cookie:', 'auth_login.codex_open_link': '打开链接',
'auth_login.secure_1psidts_placeholder': '输入 __Secure-1PSIDTS cookie 值', 'auth_login.codex_copy_link': '复制链接',
'auth_login.gemini_web_label_label': '标签 (可选):', 'auth_login.codex_oauth_status_waiting': '等待认证中...',
'auth_login.gemini_web_label_placeholder': '输入标签名称 (可选)', 'auth_login.codex_oauth_status_success': '认证成功!',
'auth_login.gemini_web_saved': 'Gemini Web Token 保存成功', 'auth_login.codex_oauth_status_error': '认证失败:',
'auth_login.codex_oauth_start_error': '启动 Codex OAuth 失败:',
'auth_login.codex_oauth_polling_error': '检查认证状态失败:',
// Anthropic OAuth
'auth_login.anthropic_oauth_title': 'Anthropic OAuth',
'auth_login.anthropic_oauth_button': '开始 Anthropic 登录',
'auth_login.anthropic_oauth_hint': '通过 OAuth 流程登录 Anthropic (Claude) 服务,自动获取并保存认证文件。',
'auth_login.anthropic_oauth_url_label': '授权链接:',
'auth_login.anthropic_open_link': '打开链接',
'auth_login.anthropic_copy_link': '复制链接',
'auth_login.anthropic_oauth_status_waiting': '等待认证中...',
'auth_login.anthropic_oauth_status_success': '认证成功!',
'auth_login.anthropic_oauth_status_error': '认证失败:',
'auth_login.anthropic_oauth_start_error': '启动 Anthropic OAuth 失败:',
'auth_login.anthropic_oauth_polling_error': '检查认证状态失败:',
// Gemini CLI OAuth
'auth_login.gemini_cli_oauth_title': 'Gemini CLI OAuth',
'auth_login.gemini_cli_oauth_button': '开始 Gemini CLI 登录',
'auth_login.gemini_cli_oauth_hint': '通过 OAuth 流程登录 Google Gemini CLI 服务,自动获取并保存认证文件。',
'auth_login.gemini_cli_project_id_label': 'Google Cloud 项目 ID (可选):',
'auth_login.gemini_cli_project_id_placeholder': '输入 Google Cloud 项目 ID (可选)',
'auth_login.gemini_cli_project_id_hint': '如果指定了项目 ID将使用该项目的认证信息。',
'auth_login.gemini_cli_oauth_url_label': '授权链接:',
'auth_login.gemini_cli_open_link': '打开链接',
'auth_login.gemini_cli_copy_link': '复制链接',
'auth_login.gemini_cli_oauth_status_waiting': '等待认证中...',
'auth_login.gemini_cli_oauth_status_success': '认证成功!',
'auth_login.gemini_cli_oauth_status_error': '认证失败:',
'auth_login.gemini_cli_oauth_start_error': '启动 Gemini CLI OAuth 失败:',
'auth_login.gemini_cli_oauth_polling_error': '检查认证状态失败:',
// Qwen OAuth
'auth_login.qwen_oauth_title': 'Qwen OAuth',
'auth_login.qwen_oauth_button': '开始 Qwen 登录',
'auth_login.qwen_oauth_hint': '通过设备授权流程登录 Qwen 服务,自动获取并保存认证文件。',
'auth_login.qwen_oauth_url_label': '授权链接:',
'auth_login.qwen_open_link': '打开链接',
'auth_login.qwen_copy_link': '复制链接',
'auth_login.qwen_oauth_status_waiting': '等待认证中...',
'auth_login.qwen_oauth_status_success': '认证成功!',
'auth_login.qwen_oauth_status_error': '认证失败:',
'auth_login.qwen_oauth_start_error': '启动 Qwen OAuth 失败:',
'auth_login.qwen_oauth_polling_error': '检查认证状态失败:',
// iFlow OAuth
'auth_login.iflow_oauth_title': 'iFlow OAuth',
'auth_login.iflow_oauth_button': '开始 iFlow 登录',
'auth_login.iflow_oauth_hint': '通过 OAuth 流程登录 iFlow 服务,自动获取并保存认证文件。',
'auth_login.iflow_oauth_url_label': '授权链接:',
'auth_login.iflow_open_link': '打开链接',
'auth_login.iflow_copy_link': '复制链接',
'auth_login.iflow_oauth_status_waiting': '等待认证中...',
'auth_login.iflow_oauth_status_success': '认证成功!',
'auth_login.iflow_oauth_status_error': '认证失败:',
'auth_login.iflow_oauth_start_error': '启动 iFlow OAuth 失败:',
'auth_login.iflow_oauth_polling_error': '检查认证状态失败:',
// 使用统计 // 使用统计
'usage_stats.title': '使用统计', 'usage_stats.title': '使用统计',
@@ -264,6 +323,7 @@ const i18n = {
'notification.retry_updated': '重试设置已更新', 'notification.retry_updated': '重试设置已更新',
'notification.quota_switch_project_updated': '项目切换设置已更新', 'notification.quota_switch_project_updated': '项目切换设置已更新',
'notification.quota_switch_preview_updated': '预览模型切换设置已更新', 'notification.quota_switch_preview_updated': '预览模型切换设置已更新',
'notification.usage_statistics_updated': '使用统计设置已更新',
'notification.api_key_added': 'API密钥添加成功', 'notification.api_key_added': 'API密钥添加成功',
'notification.api_key_updated': 'API密钥更新成功', 'notification.api_key_updated': 'API密钥更新成功',
'notification.api_key_deleted': 'API密钥删除成功', 'notification.api_key_deleted': 'API密钥删除成功',
@@ -347,6 +407,8 @@ const i18n = {
'common.required': 'Required', 'common.required': 'Required',
'common.api_key': 'Key', 'common.api_key': 'Key',
'common.base_url': 'Address', 'common.base_url': 'Address',
'common.proxy_url': 'Proxy',
'common.alias': 'Alias',
// Page titles // Page titles
'title.main': 'CLI Proxy API Management Center', 'title.main': 'CLI Proxy API Management Center',
@@ -408,6 +470,8 @@ 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',
'basic_settings.usage_statistics_title': 'Usage Statistics',
'basic_settings.usage_statistics_enable': 'Enable usage statistics',
// API Keys management // API Keys management
'api_keys.title': 'API Keys Management', 'api_keys.title': 'API Keys Management',
@@ -447,9 +511,12 @@ const i18n = {
'ai_providers.codex_add_modal_key_placeholder': 'Please enter Codex API key', 'ai_providers.codex_add_modal_key_placeholder': 'Please enter Codex API key',
'ai_providers.codex_add_modal_url_label': 'Base URL (Optional):', 'ai_providers.codex_add_modal_url_label': 'Base URL (Optional):',
'ai_providers.codex_add_modal_url_placeholder': 'e.g.: https://api.example.com', 'ai_providers.codex_add_modal_url_placeholder': 'e.g.: https://api.example.com',
'ai_providers.codex_add_modal_proxy_label': 'Proxy URL (Optional):',
'ai_providers.codex_add_modal_proxy_placeholder': 'e.g.: socks5://proxy.example.com:1080',
'ai_providers.codex_edit_modal_title': 'Edit Codex API Configuration', 'ai_providers.codex_edit_modal_title': 'Edit Codex API Configuration',
'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_edit_modal_proxy_label': 'Proxy 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',
@@ -462,9 +529,12 @@ const i18n = {
'ai_providers.claude_add_modal_key_placeholder': 'Please enter Claude API key', 'ai_providers.claude_add_modal_key_placeholder': 'Please enter Claude API key',
'ai_providers.claude_add_modal_url_label': 'Base URL (Optional):', 'ai_providers.claude_add_modal_url_label': 'Base URL (Optional):',
'ai_providers.claude_add_modal_url_placeholder': 'e.g.: https://api.anthropic.com', 'ai_providers.claude_add_modal_url_placeholder': 'e.g.: https://api.anthropic.com',
'ai_providers.claude_add_modal_proxy_label': 'Proxy URL (Optional):',
'ai_providers.claude_add_modal_proxy_placeholder': 'e.g.: socks5://proxy.example.com:1080',
'ai_providers.claude_edit_modal_title': 'Edit Claude API Configuration', 'ai_providers.claude_edit_modal_title': 'Edit Claude API Configuration',
'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_edit_modal_proxy_label': 'Proxy URL (Optional):',
'ai_providers.claude_delete_confirm': 'Are you sure you want to delete this Claude configuration?', 'ai_providers.claude_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',
@@ -478,10 +548,19 @@ const i18n = {
'ai_providers.openai_add_modal_url_placeholder': 'e.g.: https://openrouter.ai/api/v1', 'ai_providers.openai_add_modal_url_placeholder': 'e.g.: https://openrouter.ai/api/v1',
'ai_providers.openai_add_modal_keys_label': 'API Keys (one per line):', 'ai_providers.openai_add_modal_keys_label': 'API Keys (one per line):',
'ai_providers.openai_add_modal_keys_placeholder': 'sk-key1\nsk-key2', 'ai_providers.openai_add_modal_keys_placeholder': 'sk-key1\nsk-key2',
'ai_providers.openai_add_modal_keys_proxy_label': 'Proxy URL (one per line, optional):',
'ai_providers.openai_add_modal_keys_proxy_placeholder': 'socks5://proxy.example.com:1080\n',
'ai_providers.openai_add_modal_models_label': 'Model List (name[, alias] one per line):',
'ai_providers.openai_models_hint': 'Example: gpt-4o-mini or moonshotai/kimi-k2:free, kimi-k2',
'ai_providers.openai_model_name_placeholder': 'Model name, e.g. moonshotai/kimi-k2:free',
'ai_providers.openai_model_alias_placeholder': 'Model alias (optional)',
'ai_providers.openai_models_add_btn': 'Add Model',
'ai_providers.openai_edit_modal_title': 'Edit OpenAI Compatible Provider', 'ai_providers.openai_edit_modal_title': 'Edit OpenAI Compatible Provider',
'ai_providers.openai_edit_modal_name_label': 'Provider Name:', 'ai_providers.openai_edit_modal_name_label': 'Provider Name:',
'ai_providers.openai_edit_modal_url_label': 'Base URL:', 'ai_providers.openai_edit_modal_url_label': 'Base URL:',
'ai_providers.openai_edit_modal_keys_label': 'API Keys (one per line):', 'ai_providers.openai_edit_modal_keys_label': 'API Keys (one per line):',
'ai_providers.openai_edit_modal_keys_proxy_label': 'Proxy URL (one per line, optional):',
'ai_providers.openai_edit_modal_models_label': 'Model List (name[, alias] one per line):',
'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',
@@ -508,17 +587,73 @@ const i18n = {
'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 // Codex OAuth
'auth_login.gemini_web_title': 'Gemini Web Token', 'auth_login.codex_oauth_title': 'Codex OAuth',
'auth_login.gemini_web_button': 'Save Gemini Web Token', 'auth_login.codex_oauth_button': 'Start Codex Login',
'auth_login.gemini_web_hint': 'Obtain the Cookie value of the Gemini web version from the browser\'s developer tools, used for direct authentication to access Gemini.', 'auth_login.codex_oauth_hint': 'Login to Codex service through OAuth flow, automatically obtain and save authentication files.',
'auth_login.secure_1psid_label': '__Secure-1PSID Cookie:', 'auth_login.codex_oauth_url_label': 'Authorization URL:',
'auth_login.secure_1psid_placeholder': 'Enter __Secure-1PSID cookie value', 'auth_login.codex_open_link': 'Open Link',
'auth_login.secure_1psidts_label': '__Secure-1PSIDTS Cookie:', 'auth_login.codex_copy_link': 'Copy Link',
'auth_login.secure_1psidts_placeholder': 'Enter __Secure-1PSIDTS cookie value', 'auth_login.codex_oauth_status_waiting': 'Waiting for authentication...',
'auth_login.gemini_web_label_label': 'Label (Optional):', 'auth_login.codex_oauth_status_success': 'Authentication successful!',
'auth_login.gemini_web_label_placeholder': 'Enter label name (optional)', 'auth_login.codex_oauth_status_error': 'Authentication failed:',
'auth_login.gemini_web_saved': 'Gemini Web Token saved successfully', 'auth_login.codex_oauth_start_error': 'Failed to start Codex OAuth:',
'auth_login.codex_oauth_polling_error': 'Failed to check authentication status:',
// Anthropic OAuth
'auth_login.anthropic_oauth_title': 'Anthropic OAuth',
'auth_login.anthropic_oauth_button': 'Start Anthropic Login',
'auth_login.anthropic_oauth_hint': 'Login to Anthropic (Claude) service through OAuth flow, automatically obtain and save authentication files.',
'auth_login.anthropic_oauth_url_label': 'Authorization URL:',
'auth_login.anthropic_open_link': 'Open Link',
'auth_login.anthropic_copy_link': 'Copy Link',
'auth_login.anthropic_oauth_status_waiting': 'Waiting for authentication...',
'auth_login.anthropic_oauth_status_success': 'Authentication successful!',
'auth_login.anthropic_oauth_status_error': 'Authentication failed:',
'auth_login.anthropic_oauth_start_error': 'Failed to start Anthropic OAuth:',
'auth_login.anthropic_oauth_polling_error': 'Failed to check authentication status:',
// Gemini CLI OAuth
'auth_login.gemini_cli_oauth_title': 'Gemini CLI OAuth',
'auth_login.gemini_cli_oauth_button': 'Start Gemini CLI Login',
'auth_login.gemini_cli_oauth_hint': 'Login to Google Gemini CLI service through OAuth flow, automatically obtain and save authentication files.',
'auth_login.gemini_cli_project_id_label': 'Google Cloud Project ID (Optional):',
'auth_login.gemini_cli_project_id_placeholder': 'Enter Google Cloud Project ID (optional)',
'auth_login.gemini_cli_project_id_hint': 'If a project ID is specified, authentication information for that project will be used.',
'auth_login.gemini_cli_oauth_url_label': 'Authorization URL:',
'auth_login.gemini_cli_open_link': 'Open Link',
'auth_login.gemini_cli_copy_link': 'Copy Link',
'auth_login.gemini_cli_oauth_status_waiting': 'Waiting for authentication...',
'auth_login.gemini_cli_oauth_status_success': 'Authentication successful!',
'auth_login.gemini_cli_oauth_status_error': 'Authentication failed:',
'auth_login.gemini_cli_oauth_start_error': 'Failed to start Gemini CLI OAuth:',
'auth_login.gemini_cli_oauth_polling_error': 'Failed to check authentication status:',
// Qwen OAuth
'auth_login.qwen_oauth_title': 'Qwen OAuth',
'auth_login.qwen_oauth_button': 'Start Qwen Login',
'auth_login.qwen_oauth_hint': 'Login to Qwen service through device authorization flow, automatically obtain and save authentication files.',
'auth_login.qwen_oauth_url_label': 'Authorization URL:',
'auth_login.qwen_open_link': 'Open Link',
'auth_login.qwen_copy_link': 'Copy Link',
'auth_login.qwen_oauth_status_waiting': 'Waiting for authentication...',
'auth_login.qwen_oauth_status_success': 'Authentication successful!',
'auth_login.qwen_oauth_status_error': 'Authentication failed:',
'auth_login.qwen_oauth_start_error': 'Failed to start Qwen OAuth:',
'auth_login.qwen_oauth_polling_error': 'Failed to check authentication status:',
// iFlow OAuth
'auth_login.iflow_oauth_title': 'iFlow OAuth',
'auth_login.iflow_oauth_button': 'Start iFlow Login',
'auth_login.iflow_oauth_hint': 'Login to iFlow service through OAuth flow, automatically obtain and save authentication files.',
'auth_login.iflow_oauth_url_label': 'Authorization URL:',
'auth_login.iflow_open_link': 'Open Link',
'auth_login.iflow_copy_link': 'Copy Link',
'auth_login.iflow_oauth_status_waiting': 'Waiting for authentication...',
'auth_login.iflow_oauth_status_success': 'Authentication successful!',
'auth_login.iflow_oauth_status_error': 'Authentication failed:',
'auth_login.iflow_oauth_start_error': 'Failed to start iFlow OAuth:',
'auth_login.iflow_oauth_polling_error': 'Failed to check authentication status:',
// Usage Statistics // Usage Statistics
'usage_stats.title': 'Usage Statistics', 'usage_stats.title': 'Usage Statistics',
@@ -558,6 +693,7 @@ const i18n = {
'notification.retry_updated': 'Retry settings updated', 'notification.retry_updated': 'Retry settings updated',
'notification.quota_switch_project_updated': 'Project switch settings updated', 'notification.quota_switch_project_updated': 'Project switch settings updated',
'notification.quota_switch_preview_updated': 'Preview model switch settings updated', 'notification.quota_switch_preview_updated': 'Preview model switch settings updated',
'notification.usage_statistics_updated': 'Usage statistics settings updated',
'notification.api_key_added': 'API key added successfully', 'notification.api_key_added': 'API key added successfully',
'notification.api_key_updated': 'API key updated successfully', 'notification.api_key_updated': 'API key updated successfully',
'notification.api_key_deleted': 'API key deleted successfully', 'notification.api_key_deleted': 'API key deleted successfully',
@@ -570,6 +706,8 @@ const i18n = {
'notification.claude_config_added': 'Claude configuration added successfully', 'notification.claude_config_added': 'Claude configuration added successfully',
'notification.claude_config_updated': 'Claude configuration updated successfully', 'notification.claude_config_updated': 'Claude configuration updated successfully',
'notification.claude_config_deleted': 'Claude configuration deleted successfully', 'notification.claude_config_deleted': 'Claude configuration deleted successfully',
'notification.field_required': 'Required fields cannot be empty',
'notification.openai_provider_required': 'Please fill in provider name and Base URL',
'notification.openai_provider_added': 'OpenAI provider added successfully', 'notification.openai_provider_added': 'OpenAI provider added successfully',
'notification.openai_provider_updated': 'OpenAI provider updated successfully', 'notification.openai_provider_updated': 'OpenAI provider updated successfully',
'notification.openai_provider_deleted': 'OpenAI provider deleted successfully', 'notification.openai_provider_deleted': 'OpenAI provider deleted successfully',

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;">
@@ -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>
@@ -150,7 +155,8 @@
<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"
data-section="basic-settings">
<i class="fas fa-sliders-h"></i> <span data-i18n="nav.basic_settings">基础设置</span> <i class="fas fa-sliders-h"></i> <span data-i18n="nav.basic_settings">基础设置</span>
</a></li> </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">
@@ -187,7 +193,8 @@
<!-- Debug 设置 --> <!-- Debug 设置 -->
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3><i class="fas fa-bug"></i> <span data-i18n="basic_settings.debug_title">调试模式</span></h3> <h3><i class="fas fa-bug"></i> <span
data-i18n="basic_settings.debug_title">调试模式</span></h3>
</div> </div>
<div class="card-content"> <div class="card-content">
<div class="toggle-group"> <div class="toggle-group">
@@ -203,15 +210,21 @@
<!-- 代理设置 --> <!-- 代理设置 -->
<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-network-wired"></i> <span
data-i18n="basic_settings.proxy_title">代理设置</span></h3>
</div> </div>
<div class="card-content"> <div class="card-content">
<div class="form-group"> <div class="form-group">
<label for="proxy-url" data-i18n="basic_settings.proxy_url_label">代理 URL:</label> <label for="proxy-url" data-i18n="basic_settings.proxy_url_label">代理
URL:</label>
<div class="input-group"> <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/"> <input type="text" id="proxy-url"
<button id="update-proxy" class="btn btn-primary" data-i18n="basic_settings.proxy_update">更新</button> data-i18n="basic_settings.proxy_url_placeholder"
<button id="clear-proxy" class="btn btn-danger" data-i18n="basic_settings.proxy_clear">清空</button> 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>
@@ -220,14 +233,17 @@
<!-- 请求重试设置 --> <!-- 请求重试设置 -->
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3><i class="fas fa-redo"></i> <span data-i18n="basic_settings.retry_title">请求重试</span></h3> <h3><i class="fas fa-redo"></i> <span
data-i18n="basic_settings.retry_title">请求重试</span></h3>
</div> </div>
<div class="card-content"> <div class="card-content">
<div class="form-group"> <div class="form-group">
<label for="request-retry" data-i18n="basic_settings.retry_count_label">重试次数:</label> <label for="request-retry"
data-i18n="basic_settings.retry_count_label">重试次数:</label>
<div class="input-group"> <div class="input-group">
<input type="number" id="request-retry" min="0" max="10" value="3"> <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> <button id="update-retry" class="btn btn-primary"
data-i18n="basic_settings.retry_update">更新</button>
</div> </div>
</div> </div>
</div> </div>
@@ -236,7 +252,8 @@
<!-- 配额超出行为 --> <!-- 配额超出行为 -->
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3><i class="fas fa-exclamation-triangle"></i> <span data-i18n="basic_settings.quota_title">配额超出行为</span></h3> <h3><i class="fas fa-exclamation-triangle"></i> <span
data-i18n="basic_settings.quota_title">配额超出行为</span></h3>
</div> </div>
<div class="card-content"> <div class="card-content">
<div class="toggle-group"> <div class="toggle-group">
@@ -244,14 +261,34 @@
<input type="checkbox" id="switch-project-toggle"> <input type="checkbox" id="switch-project-toggle">
<span class="slider"></span> <span class="slider"></span>
</label> </label>
<span class="toggle-label" data-i18n="basic_settings.quota_switch_project">自动切换项目</span> <span class="toggle-label"
data-i18n="basic_settings.quota_switch_project">自动切换项目</span>
</div> </div>
<div class="toggle-group"> <div class="toggle-group">
<label class="toggle-switch"> <label class="toggle-switch">
<input type="checkbox" id="switch-preview-model-toggle"> <input type="checkbox" id="switch-preview-model-toggle">
<span class="slider"></span> <span class="slider"></span>
</label> </label>
<span class="toggle-label" data-i18n="basic_settings.quota_switch_preview">切换到预览模型</span> <span class="toggle-label"
data-i18n="basic_settings.quota_switch_preview">切换到预览模型</span>
</div>
</div>
</div>
<!-- 使用统计设置 -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-chart-bar"></i> <span
data-i18n="basic_settings.usage_statistics_title">使用统计</span></h3>
</div>
<div class="card-content">
<div class="toggle-group">
<label class="toggle-switch">
<input type="checkbox" id="usage-statistics-enabled-toggle">
<span class="slider"></span>
</label>
<span class="toggle-label"
data-i18n="basic_settings.usage_statistics_enable">启用使用统计</span>
</div> </div>
</div> </div>
</div> </div>
@@ -264,7 +301,8 @@
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3><i class="fas fa-key"></i> <span data-i18n="api_keys.proxy_auth_title">代理服务认证密钥</span></h3> <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"> <button id="add-api-key" class="btn btn-primary">
<i class="fas fa-plus"></i> <span data-i18n="api_keys.add_button">添加密钥</span> <i class="fas fa-plus"></i> <span data-i18n="api_keys.add_button">添加密钥</span>
</button> </button>
@@ -282,9 +320,11 @@
<!-- Gemini API Keys --> <!-- Gemini API Keys -->
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3><i class="fab fa-google"></i> <span data-i18n="ai_providers.gemini_title">Gemini API 密钥</span></h3> <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"> <button id="add-gemini-key" class="btn btn-primary">
<i class="fas fa-plus"></i> <span data-i18n="ai_providers.gemini_add_button">添加密钥</span> <i class="fas fa-plus"></i> <span
data-i18n="ai_providers.gemini_add_button">添加密钥</span>
</button> </button>
</div> </div>
<div class="card-content"> <div class="card-content">
@@ -295,9 +335,11 @@
<!-- Codex API Keys --> <!-- Codex API Keys -->
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3><i class="fas fa-code"></i> <span data-i18n="ai_providers.codex_title">Codex API 配置</span></h3> <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"> <button id="add-codex-key" class="btn btn-primary">
<i class="fas fa-plus"></i> <span data-i18n="ai_providers.codex_add_button">添加配置</span> <i class="fas fa-plus"></i> <span
data-i18n="ai_providers.codex_add_button">添加配置</span>
</button> </button>
</div> </div>
<div class="card-content"> <div class="card-content">
@@ -308,9 +350,11 @@
<!-- Claude API Keys --> <!-- Claude API Keys -->
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3><i class="fas fa-brain"></i> <span data-i18n="ai_providers.claude_title">Claude API 配置</span></h3> <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"> <button id="add-claude-key" class="btn btn-primary">
<i class="fas fa-plus"></i> <span data-i18n="ai_providers.claude_add_button">添加配置</span> <i class="fas fa-plus"></i> <span
data-i18n="ai_providers.claude_add_button">添加配置</span>
</button> </button>
</div> </div>
<div class="card-content"> <div class="card-content">
@@ -321,9 +365,11 @@
<!-- OpenAI 兼容提供商 --> <!-- OpenAI 兼容提供商 -->
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3><i class="fas fa-plug"></i> <span data-i18n="ai_providers.openai_title">OpenAI 兼容提供商</span></h3> <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"> <button id="add-openai-provider" class="btn btn-primary">
<i class="fas fa-plus"></i> <span data-i18n="ai_providers.openai_add_button">添加提供商</span> <i class="fas fa-plus"></i> <span
data-i18n="ai_providers.openai_add_button">添加提供商</span>
</button> </button>
</div> </div>
<div class="card-content"> <div class="card-content">
@@ -344,43 +390,19 @@
</div> </div>
</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">
<div class="card-header"> <div class="card-header">
<h3><i class="fas fa-file-alt"></i> <span data-i18n="auth_files.title_section">认证文件</span></h3> <h3><i class="fas fa-file-alt"></i> <span
data-i18n="auth_files.title_section">认证文件</span></h3>
<div class="header-actions"> <div class="header-actions">
<button id="upload-auth-file" class="btn btn-primary"> <button id="upload-auth-file" class="btn btn-primary">
<i class="fas fa-upload"></i> <span data-i18n="auth_files.upload_button">上传文件</span> <i class="fas fa-upload"></i> <span
data-i18n="auth_files.upload_button">上传文件</span>
</button> </button>
<button id="delete-all-auth-files" class="btn btn-danger"> <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> <i class="fas fa-trash"></i> <span
data-i18n="auth_files.delete_all_button">删除全部</span>
</button> </button>
</div> </div>
</div> </div>
@@ -389,6 +411,194 @@
<input type="file" id="auth-file-input" accept=".json" style="display: none;"> <input type="file" id="auth-file-input" accept=".json" style="display: none;">
</div> </div>
</div> </div>
<!-- Codex OAuth -->
<div class="card" id="codex-oauth-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>
<!-- Anthropic OAuth -->
<div class="card" id="anthropic-oauth-card">
<div class="card-header">
<h3><i class="fas fa-brain"></i> <span
data-i18n="auth_login.anthropic_oauth_title">Anthropic OAuth</span></h3>
<button id="anthropic-oauth-btn" class="btn btn-primary">
<i class="fas fa-sign-in-alt"></i> <span
data-i18n="auth_login.anthropic_oauth_button">开始 Anthropic 登录</span>
</button>
</div>
<div class="card-content">
<p class="form-hint" style="margin-bottom: 20px;"
data-i18n="auth_login.anthropic_oauth_hint">
通过 OAuth 流程登录 Anthropic (Claude) 服务,自动获取并保存认证文件。
</p>
<div id="anthropic-oauth-content" style="display: none;">
<div class="form-group">
<label data-i18n="auth_login.anthropic_oauth_url_label">授权链接:</label>
<div class="input-group">
<input type="text" id="anthropic-oauth-url" readonly>
<button id="anthropic-open-link" class="btn btn-primary">
<i class="fas fa-external-link-alt"></i> <span
data-i18n="auth_login.anthropic_open_link">打开链接</span>
</button>
<button id="anthropic-copy-link" class="btn btn-secondary">
<i class="fas fa-copy"></i> <span
data-i18n="auth_login.anthropic_copy_link">复制链接</span>
</button>
</div>
</div>
<div id="anthropic-oauth-status" class="form-hint" style="margin-top: 10px;">
</div>
</div>
</div>
</div>
<!-- Gemini CLI OAuth -->
<div class="card" id="gemini-cli-oauth-card">
<div class="card-header">
<h3><i class="fab fa-google"></i> <span
data-i18n="auth_login.gemini_cli_oauth_title">Gemini CLI OAuth</span></h3>
<button id="gemini-cli-oauth-btn" class="btn btn-primary">
<i class="fas fa-sign-in-alt"></i> <span
data-i18n="auth_login.gemini_cli_oauth_button">开始 Gemini CLI 登录</span>
</button>
</div>
<div class="card-content">
<p class="form-hint" style="margin-bottom: 20px;"
data-i18n="auth_login.gemini_cli_oauth_hint">
通过 OAuth 流程登录 Google Gemini CLI 服务,自动获取并保存认证文件。
</p>
<div class="form-group" style="margin-bottom: 20px;">
<label for="gemini-cli-project-id"
data-i18n="auth_login.gemini_cli_project_id_label">Google Cloud 项目 ID
(可选):</label>
<input type="text" id="gemini-cli-project-id"
data-i18n="auth_login.gemini_cli_project_id_placeholder"
placeholder="输入 Google Cloud 项目 ID (可选)">
<div class="form-hint" data-i18n="auth_login.gemini_cli_project_id_hint">
如果指定了项目 ID将使用该项目的认证信息。
</div>
</div>
<div id="gemini-cli-oauth-content" style="display: none;">
<div class="form-group">
<label data-i18n="auth_login.gemini_cli_oauth_url_label">授权链接:</label>
<div class="input-group">
<input type="text" id="gemini-cli-oauth-url" readonly>
<button id="gemini-cli-open-link" class="btn btn-primary">
<i class="fas fa-external-link-alt"></i> <span
data-i18n="auth_login.gemini_cli_open_link">打开链接</span>
</button>
<button id="gemini-cli-copy-link" class="btn btn-secondary">
<i class="fas fa-copy"></i> <span
data-i18n="auth_login.gemini_cli_copy_link">复制链接</span>
</button>
</div>
</div>
<div id="gemini-cli-oauth-status" class="form-hint" style="margin-top: 10px;">
</div>
</div>
</div>
</div>
<!-- Qwen OAuth -->
<div class="card" id="qwen-oauth-card">
<div class="card-header">
<h3><i class="fas fa-robot"></i> <span data-i18n="auth_login.qwen_oauth_title">Qwen
OAuth</span></h3>
<button id="qwen-oauth-btn" class="btn btn-primary">
<i class="fas fa-sign-in-alt"></i> <span
data-i18n="auth_login.qwen_oauth_button">开始 Qwen 登录</span>
</button>
</div>
<div class="card-content">
<p class="form-hint" style="margin-bottom: 20px;"
data-i18n="auth_login.qwen_oauth_hint">
通过设备授权流程登录 Qwen 服务,自动获取并保存认证文件。
</p>
<div id="qwen-oauth-content" style="display: none;">
<div class="form-group">
<label data-i18n="auth_login.qwen_oauth_url_label">授权链接:</label>
<div class="input-group">
<input type="text" id="qwen-oauth-url" readonly>
<button id="qwen-open-link" class="btn btn-primary">
<i class="fas fa-external-link-alt"></i> <span
data-i18n="auth_login.qwen_open_link">打开链接</span>
</button>
<button id="qwen-copy-link" class="btn btn-secondary">
<i class="fas fa-copy"></i> <span
data-i18n="auth_login.qwen_copy_link">复制链接</span>
</button>
</div>
</div>
<div id="qwen-oauth-status" class="form-hint" style="margin-top: 10px;"></div>
</div>
</div>
</div>
<!-- iFlow OAuth -->
<div class="card" id="iflow-oauth-card">
<div class="card-header">
<h3><i class="fas fa-stream"></i> <span
data-i18n="auth_login.iflow_oauth_title">iFlow OAuth</span></h3>
<button id="iflow-oauth-btn" class="btn btn-primary">
<i class="fas fa-sign-in-alt"></i> <span
data-i18n="auth_login.iflow_oauth_button">开始 iFlow 登录</span>
</button>
</div>
<div class="card-content">
<p class="form-hint" style="margin-bottom: 20px;"
data-i18n="auth_login.iflow_oauth_hint">
通过 OAuth 流程登录 iFlow 服务,自动获取并保存认证文件。
</p>
<div id="iflow-oauth-content" style="display: none;">
<div class="form-group">
<label data-i18n="auth_login.iflow_oauth_url_label">授权链接:</label>
<div class="input-group">
<input type="text" id="iflow-oauth-url" readonly>
<button id="iflow-open-link" class="btn btn-primary">
<i class="fas fa-external-link-alt"></i> <span
data-i18n="auth_login.iflow_open_link">打开链接</span>
</button>
<button id="iflow-copy-link" class="btn btn-secondary">
<i class="fas fa-copy"></i> <span
data-i18n="auth_login.iflow_copy_link">复制链接</span>
</button>
</div>
</div>
<div id="iflow-oauth-status" class="form-hint" style="margin-top: 10px;"></div>
</div>
</div>
</div>
</section> </section>
<!-- 使用统计 --> <!-- 使用统计 -->
@@ -443,12 +653,14 @@
<!-- 请求趋势图 --> <!-- 请求趋势图 -->
<div class="card chart-card"> <div class="card chart-card">
<div class="card-header"> <div class="card-header">
<h3><i class="fas fa-chart-line"></i> <span data-i18n="usage_stats.requests_trend">请求趋势</span></h3> <h3><i class="fas fa-chart-line"></i> <span
data-i18n="usage_stats.requests_trend">请求趋势</span></h3>
<div class="chart-controls"> <div class="chart-controls">
<button class="btn btn-small" data-period="hour" id="requests-hour-btn"> <button class="btn btn-small" data-period="hour" id="requests-hour-btn">
<span data-i18n="usage_stats.by_hour">按小时</span> <span data-i18n="usage_stats.by_hour">按小时</span>
</button> </button>
<button class="btn btn-small active" data-period="day" id="requests-day-btn"> <button class="btn btn-small active" data-period="day"
id="requests-day-btn">
<span data-i18n="usage_stats.by_day">按天</span> <span data-i18n="usage_stats.by_day">按天</span>
</button> </button>
</div> </div>
@@ -463,7 +675,8 @@
<!-- Token使用趋势图 --> <!-- Token使用趋势图 -->
<div class="card chart-card"> <div class="card chart-card">
<div class="card-header"> <div class="card-header">
<h3><i class="fas fa-chart-area"></i> <span data-i18n="usage_stats.tokens_trend">Token 使用趋势</span></h3> <h3><i class="fas fa-chart-area"></i> <span
data-i18n="usage_stats.tokens_trend">Token 使用趋势</span></h3>
<div class="chart-controls"> <div class="chart-controls">
<button class="btn btn-small" data-period="hour" id="tokens-hour-btn"> <button class="btn btn-small" data-period="hour" id="tokens-hour-btn">
<span data-i18n="usage_stats.by_hour">按小时</span> <span data-i18n="usage_stats.by_hour">按小时</span>
@@ -484,7 +697,8 @@
<!-- API详细统计 --> <!-- API详细统计 -->
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3><i class="fas fa-list"></i> <span data-i18n="usage_stats.api_details">API 详细统计</span></h3> <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"> <button id="refresh-usage-stats" class="btn btn-primary">
<i class="fas fa-sync-alt"></i> <span data-i18n="usage_stats.refresh">刷新</span> <i class="fas fa-sync-alt"></i> <span data-i18n="usage_stats.refresh">刷新</span>
</button> </button>
@@ -504,7 +718,8 @@
<!-- 连接信息卡片 --> <!-- 连接信息卡片 -->
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3><i class="fas fa-server"></i> <span data-i18n="connection.title">连接信息</span></h3> <h3><i class="fas fa-server"></i> <span data-i18n="connection.title">连接信息</span>
</h3>
</div> </div>
<div class="card-content"> <div class="card-content">
<div class="connection-info"> <div class="connection-info">
@@ -528,7 +743,8 @@
<span data-i18n="connection.status">连接状态:</span> <span data-i18n="connection.status">连接状态:</span>
</div> </div>
<div class="info-value" id="display-connection-status"> <div class="info-value" id="display-connection-status">
<span class="status-indicator disconnected" data-i18n="common.disconnected">未连接</span> <span class="status-indicator disconnected"
data-i18n="common.disconnected">未连接</span>
</div> </div>
</div> </div>
</div> </div>
@@ -537,20 +753,26 @@
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3><i class="fas fa-info-circle"></i> <span data-i18n="system_info.connection_status_title">连接状态</span></h3> <h3><i class="fas fa-info-circle"></i> <span
data-i18n="system_info.connection_status_title">连接状态</span></h3>
</div> </div>
<div class="card-content"> <div class="card-content">
<div id="system-status" class="status-info"> <div id="system-status" class="status-info">
<div class="status-item"> <div class="status-item">
<span class="status-label" data-i18n="system_info.api_status_label">API 状态:</span> <span class="status-label" data-i18n="system_info.api_status_label">API
<span id="api-status" class="status-value" data-i18n="common.disconnected">未连接</span> 状态:</span>
<span id="api-status" class="status-value"
data-i18n="common.disconnected">未连接</span>
</div> </div>
<div class="status-item"> <div class="status-item">
<span class="status-label" data-i18n="system_info.config_status_label">配置状态:</span> <span class="status-label"
<span id="config-status" class="status-value" data-i18n="system_info.not_loaded">未加载</span> data-i18n="system_info.config_status_label">配置状态:</span>
<span id="config-status" class="status-value"
data-i18n="system_info.not_loaded">未加载</span>
</div> </div>
<div class="status-item"> <div class="status-item">
<span class="status-label" data-i18n="system_info.last_update_label">最后更新:</span> <span class="status-label"
data-i18n="system_info.last_update_label">最后更新:</span>
<span id="last-update" class="status-value">-</span> <span id="last-update" class="status-value">-</span>
</div> </div>
</div> </div>
@@ -563,9 +785,9 @@
<!-- 版本信息 --> <!-- 版本信息 -->
<footer class="version-footer"> <footer class="version-footer">
<div class="version-info"> <div class="version-info">
<span data-i18n="footer.version">版本</span>: v0.1.0 <span data-i18n="footer.version">版本</span>: __VERSION__
<span class="separator"></span> <span class="separator"></span>
<span data-i18n="footer.author">作者</span>: Supra4E8C <span data-i18n="footer.author">作者</span>: CLI Proxy API Team
</div> </div>
</footer> </footer>
</div> </div>
@@ -590,4 +812,5 @@
<script src="app.js"></script> <script src="app.js"></script>
</body> </body>
</html> </html>

View File

@@ -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;
} }
@@ -549,12 +555,14 @@
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;
@@ -572,7 +580,8 @@
.theme-btn { .theme-btn {
padding: 8px 16px; padding: 8px 16px;
font-size: 13px; font-size: 13px;
height: 36px; /* 在小屏幕上稍微减小高度 */ height: 36px;
/* 在小屏幕上稍微减小高度 */
} }
.login-title { .login-title {
@@ -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;
} }
@@ -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);
@@ -1660,7 +1671,7 @@ input:checked + .slider:before {
justify-content: flex-end; justify-content: flex-end;
} }
.top-navbar-actions > * { .top-navbar-actions>* {
height: 34px; height: 34px;
min-height: 34px; min-height: 34px;
} }
@@ -1803,7 +1814,9 @@ input:checked + .slider:before {
} }
@keyframes spin { @keyframes spin {
to { transform: rotate(360deg); } to {
transform: rotate(360deg);
}
} }
/* 空状态 */ /* 空状态 */
@@ -1899,53 +1912,17 @@ input:checked + .slider:before {
0% { 0% {
opacity: 1; opacity: 1;
} }
50% { 50% {
opacity: 0.5; opacity: 0.5;
} }
100% { 100% {
opacity: 1; opacity: 1;
} }
} }
/* Gemini Web Token 模态框样式 */
.gemini-web-form .form-group {
margin-bottom: 20px;
}
.gemini-web-form .form-group label {
display: block;
margin-bottom: 8px;
color: var(--text-secondary);
font-weight: 600;
font-size: 14px;
}
.gemini-web-form .form-group input {
width: 100%;
padding: 12px 16px;
border: 2px solid var(--border-primary);
border-radius: 8px;
font-size: 14px;
transition: all 0.3s ease;
background: var(--bg-tertiary);
color: var(--text-primary);
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
}
.gemini-web-form .form-group input:focus {
outline: none;
border-color: var(--border-focus);
box-shadow: 0 0 0 3px var(--border-primary);
}
.gemini-web-form .form-hint {
margin-top: 6px;
color: var(--text-tertiary);
font-size: 12px;
line-height: 1.4;
}
/* 使用统计样式 */ /* 使用统计样式 */
.stats-overview { .stats-overview {
display: grid; display: grid;
@@ -2211,3 +2188,480 @@ 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;
}
}
/* Anthropic OAuth 样式 */
#anthropic-oauth-content {
transition: all 0.3s ease;
}
#anthropic-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);
}
#anthropic-oauth-url:focus {
border-color: var(--border-focus);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
#anthropic-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;
}
#anthropic-oauth-status.success {
background: var(--success-bg);
border-color: var(--success-border);
color: var(--success-text);
}
#anthropic-oauth-status.error {
background: var(--error-bg);
border-color: var(--error-border);
color: var(--error-text);
}
#anthropic-oauth-status.warning {
background: var(--warning-bg);
border-color: var(--warning-border);
color: var(--warning-text);
}
/* Anthropic OAuth 按钮样式 */
#anthropic-open-link,
#anthropic-copy-link {
min-width: 100px;
white-space: nowrap;
}
#anthropic-open-link {
background: var(--primary-color);
border-color: var(--primary-color);
}
#anthropic-open-link:hover {
background: var(--primary-hover);
border-color: var(--primary-hover);
}
#anthropic-copy-link {
background: var(--bg-secondary);
border-color: var(--border-primary);
color: var(--text-secondary);
}
#anthropic-copy-link:hover {
background: var(--bg-tertiary);
border-color: var(--border-secondary);
color: var(--text-primary);
}
/* 响应式设计 - Anthropic OAuth */
@media (max-width: 768px) {
#anthropic-oauth-content .input-group {
flex-direction: column;
align-items: stretch;
}
#anthropic-open-link,
#anthropic-copy-link {
width: 100%;
margin-top: 8px;
}
#anthropic-oauth-url {
font-size: 12px;
}
}
/* Gemini CLI OAuth 样式 */
#gemini-cli-oauth-content {
transition: all 0.3s ease;
}
#gemini-cli-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);
}
#gemini-cli-oauth-url:focus {
border-color: var(--border-focus);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
#gemini-cli-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;
}
#gemini-cli-oauth-status.success {
background: var(--success-bg);
border-color: var(--success-border);
color: var(--success-text);
}
#gemini-cli-oauth-status.error {
background: var(--error-bg);
border-color: var(--error-border);
color: var(--error-text);
}
#gemini-cli-oauth-status.warning {
background: var(--warning-bg);
border-color: var(--warning-border);
color: var(--warning-text);
}
/* Gemini CLI OAuth 按钮样式 */
#gemini-cli-open-link,
#gemini-cli-copy-link {
min-width: 100px;
white-space: nowrap;
}
#gemini-cli-open-link {
background: var(--primary-color);
border-color: var(--primary-color);
}
#gemini-cli-open-link:hover {
background: var(--primary-hover);
border-color: var(--primary-hover);
}
#gemini-cli-copy-link {
background: var(--bg-secondary);
border-color: var(--border-primary);
color: var(--text-secondary);
}
#gemini-cli-copy-link:hover {
background: var(--bg-tertiary);
border-color: var(--border-secondary);
color: var(--text-primary);
}
/* Gemini CLI 项目 ID 输入框样式 */
#gemini-cli-project-id {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 13px;
background: var(--bg-tertiary);
border: 1px solid var(--border-primary);
color: var(--text-secondary);
}
#gemini-cli-project-id:focus {
border-color: var(--border-focus);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
/* 响应式设计 - Gemini CLI OAuth */
@media (max-width: 768px) {
#gemini-cli-oauth-content .input-group {
flex-direction: column;
align-items: stretch;
}
#gemini-cli-open-link,
#gemini-cli-copy-link {
width: 100%;
margin-top: 8px;
}
#gemini-cli-oauth-url {
font-size: 12px;
}
#gemini-cli-project-id {
font-size: 12px;
}
}
/* Qwen OAuth 样式 */
#qwen-oauth-content {
transition: all 0.3s ease;
}
#qwen-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);
}
#qwen-oauth-url:focus {
border-color: var(--border-focus);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
#qwen-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;
}
#qwen-oauth-status.success {
background: var(--success-bg);
border-color: var(--success-border);
color: var(--success-text);
}
#qwen-oauth-status.error {
background: var(--error-bg);
border-color: var(--error-border);
color: var(--error-text);
}
#qwen-oauth-status.warning {
background: var(--warning-bg);
border-color: var(--warning-border);
color: var(--warning-text);
}
/* Qwen OAuth 按钮样式 */
#qwen-open-link,
#qwen-copy-link {
min-width: 100px;
white-space: nowrap;
}
#qwen-open-link {
background: var(--primary-color);
border-color: var(--primary-color);
}
#qwen-open-link:hover {
background: var(--primary-hover);
border-color: var(--primary-hover);
}
#qwen-copy-link {
background: var(--bg-secondary);
border-color: var(--border-primary);
color: var(--text-secondary);
}
#qwen-copy-link:hover {
background: var(--bg-tertiary);
border-color: var(--border-secondary);
color: var(--text-primary);
}
/* 响应式设计 - Qwen OAuth */
@media (max-width: 768px) {
#qwen-oauth-content .input-group {
flex-direction: column;
align-items: stretch;
}
#qwen-open-link,
#qwen-copy-link {
width: 100%;
margin-top: 8px;
}
#qwen-oauth-url {
font-size: 12px;
}
}
/* iFlow OAuth 样式 */
#iflow-oauth-content {
transition: all 0.3s ease;
}
#iflow-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);
}
#iflow-oauth-url:focus {
border-color: var(--border-focus);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
#iflow-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;
}
#iflow-oauth-status.success {
background: var(--success-bg);
border-color: var(--success-border);
color: var(--success-text);
}
#iflow-oauth-status.error {
background: var(--error-bg);
border-color: var(--error-border);
color: var(--error-text);
}
#iflow-oauth-status.warning {
background: var(--warning-bg);
border-color: var(--warning-border);
color: var(--warning-text);
}
/* iFlow OAuth 按钮样式 */
#iflow-open-link,
#iflow-copy-link {
min-width: 100px;
white-space: nowrap;
}
#iflow-open-link {
background: var(--primary-color);
border-color: var(--primary-color);
}
#iflow-open-link:hover {
background: var(--primary-hover);
border-color: var(--primary-hover);
}
#iflow-copy-link {
background: var(--bg-secondary);
border-color: var(--border-primary);
color: var(--text-secondary);
}
#iflow-copy-link:hover {
background: var(--bg-tertiary);
border-color: var(--border-secondary);
color: var(--text-primary);
}
/* 响应式设计 - iFlow OAuth */
@media (max-width: 768px) {
#iflow-oauth-content .input-group {
flex-direction: column;
align-items: stretch;
}
#iflow-open-link,
#iflow-copy-link {
width: 100%;
margin-top: 8px;
}
#iflow-oauth-url {
font-size: 12px;
}
}