Compare commits

...

7 Commits

Author SHA1 Message Date
hkfires
86d60aad77 增加使用统计开关 2025-10-13 15:56:26 +08:00
Supra4E8C
020fccc032 1 2025-10-11 15:51:51 +08:00
Supra4E8C
c162ab3a54 删除gemini web tokens 2025-10-11 15:02:57 +08:00
Supra4E8C
85d12e15d8 Merge pull request #4 from tombii/main
Add missing English translations.
2025-10-11 14:51:40 +08:00
tombii
ebffb49f52 Add missing English translations. 2025-10-08 22:08:36 +02:00
Supra4E8C
316c1ffc0d Update README_CN.md 2025-10-06 16:02:15 +08:00
Supra4E8C
b3e54e7f14 Update README.md 2025-10-06 16:01:56 +08:00
6 changed files with 894 additions and 1013 deletions

View File

@@ -10,7 +10,7 @@ Example URL:
https://remote.router-for.me/
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.
@@ -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
- Delete single or all authentication files
- Display file details
- **Gemini Web Token**: Direct authentication using browser cookies
### Usage Statistics
- **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/
最低可用版本 ≥ 6.0.0
推荐版本 ≥ 6.0.19
推荐版本 ≥ 6.1.3
自6.0.19起WebUI已经集成在主程序中 可以通过主项目开启的外部端口的`/management.html`访问
## 功能特点
@@ -40,7 +42,6 @@ https://remote.router-for.me/
- 下载现有认证文件
- 删除单个或所有认证文件
- 显示文件详细信息
- **Gemini Web Token**: 使用浏览器 Cookie 直接认证
### 使用统计
- **实时分析**: 通过交互式图表跟踪 API 使用情况

152
app.js
View File

@@ -494,6 +494,7 @@ class CLIProxyManager {
const updateRetry = document.getElementById('update-retry');
const switchProjectToggle = document.getElementById('switch-project-toggle');
const switchPreviewToggle = document.getElementById('switch-preview-model-toggle');
const usageStatisticsToggle = document.getElementById('usage-statistics-enabled-toggle');
if (debugToggle) {
debugToggle.addEventListener('change', (e) => this.updateDebug(e.target.checked));
@@ -513,6 +514,9 @@ class CLIProxyManager {
if (switchPreviewToggle) {
switchPreviewToggle.addEventListener('change', (e) => this.updateSwitchPreviewModel(e.target.checked));
}
if (usageStatisticsToggle) {
usageStatisticsToggle.addEventListener('change', (e) => this.updateUsageStatisticsEnabled(e.target.checked));
}
// API 密钥管理
const addApiKey = document.getElementById('add-api-key');
@@ -538,12 +542,6 @@ class CLIProxyManager {
}
// Gemini Web Token
const geminiWebTokenBtn = document.getElementById('gemini-web-token-btn');
if (geminiWebTokenBtn) {
geminiWebTokenBtn.addEventListener('click', () => this.showGeminiWebTokenModal());
}
// 认证文件管理
const uploadAuthFile = document.getElementById('upload-auth-file');
const deleteAllAuthFiles = document.getElementById('delete-all-auth-files');
@@ -1158,6 +1156,13 @@ class CLIProxyManager {
}
}
if (config['usage-statistics-enabled'] !== undefined) {
const usageToggle = document.getElementById('usage-statistics-enabled-toggle');
if (usageToggle) {
usageToggle.checked = config['usage-statistics-enabled'];
}
}
// API 密钥
if (config['api-keys']) {
@@ -1192,6 +1197,7 @@ class CLIProxyManager {
this.loadProxySettings(),
this.loadRetrySettings(),
this.loadQuotaSettings(),
this.loadUsageStatisticsSettings(),
this.loadApiKeys(),
this.loadGeminiKeys(),
this.loadCodexKeys(),
@@ -1314,6 +1320,39 @@ class CLIProxyManager {
}
}
// 加载使用统计设置
async loadUsageStatisticsSettings() {
try {
const config = await this.getConfig();
if (config['usage-statistics-enabled'] !== undefined) {
const usageToggle = document.getElementById('usage-statistics-enabled-toggle');
if (usageToggle) {
usageToggle.checked = config['usage-statistics-enabled'];
}
}
} catch (error) {
console.error('加载使用统计设置失败:', error);
}
}
// 更新使用统计设置
async updateUsageStatisticsEnabled(enabled) {
try {
await this.makeRequest('/usage-statistics-enabled', {
method: 'PUT',
body: JSON.stringify({ value: enabled })
});
this.clearCache();
this.showNotification(i18n.t('notification.usage_statistics_updated'), 'success');
} catch (error) {
this.showNotification(`${i18n.t('notification.update_failed')}: ${error.message}`, 'error');
const usageToggle = document.getElementById('usage-statistics-enabled-toggle');
if (usageToggle) {
usageToggle.checked = !enabled;
}
}
}
// 更新项目切换设置
async updateSwitchProject(enabled) {
try {
@@ -2430,107 +2469,6 @@ class CLIProxyManager {
// 显示 Gemini Web Token 模态框
showGeminiWebTokenModal() {
const inlineSecure1psid = document.getElementById('secure-1psid-input');
const inlineSecure1psidts = document.getElementById('secure-1psidts-input');
const inlineLabel = document.getElementById('gemini-web-label-input');
const modalBody = document.getElementById('modal-body');
modalBody.innerHTML = `
<h3>${i18n.t('auth_login.gemini_web_button')}</h3>
<div class="gemini-web-form">
<div class="form-group">
<label for="modal-secure-1psid">${i18n.t('auth_login.secure_1psid_label')}</label>
<input type="text" id="modal-secure-1psid" placeholder="${i18n.t('auth_login.secure_1psid_placeholder')}" required>
<div class="form-hint">从浏览器开发者工具 → Application → Cookies 中获取</div>
</div>
<div class="form-group">
<label for="modal-secure-1psidts">${i18n.t('auth_login.secure_1psidts_label')}</label>
<input type="text" id="modal-secure-1psidts" placeholder="${i18n.t('auth_login.secure_1psidts_placeholder')}" required>
<div class="form-hint">从浏览器开发者工具 → Application → Cookies 中获取</div>
</div>
<div class="form-group">
<label for="modal-gemini-web-label">${i18n.t('auth_login.gemini_web_label_label')}</label>
<input type="text" id="modal-gemini-web-label" placeholder="${i18n.t('auth_login.gemini_web_label_placeholder')}">
<div class="form-hint">为此认证文件设置一个标签名称(可选)</div>
</div>
<div class="modal-actions">
<button class="btn btn-secondary" onclick="manager.closeModal()">${i18n.t('common.cancel')}</button>
<button class="btn btn-primary" onclick="manager.saveGeminiWebToken()">${i18n.t('common.save')}</button>
</div>
</div>
`;
this.showModal();
const modalSecure1psid = document.getElementById('modal-secure-1psid');
const modalSecure1psidts = document.getElementById('modal-secure-1psidts');
const modalLabel = document.getElementById('modal-gemini-web-label');
if (modalSecure1psid && inlineSecure1psid) {
modalSecure1psid.value = inlineSecure1psid.value.trim();
}
if (modalSecure1psidts && inlineSecure1psidts) {
modalSecure1psidts.value = inlineSecure1psidts.value.trim();
}
if (modalLabel && inlineLabel) {
modalLabel.value = inlineLabel.value.trim();
}
if (modalSecure1psid) {
modalSecure1psid.focus();
}
}
// 保存 Gemini Web Token
async saveGeminiWebToken() {
const secure1psid = document.getElementById('modal-secure-1psid').value.trim();
const secure1psidts = document.getElementById('modal-secure-1psidts').value.trim();
const label = document.getElementById('modal-gemini-web-label').value.trim();
if (!secure1psid || !secure1psidts) {
this.showNotification('请填写完整的 Cookie 信息', 'error');
return;
}
try {
const requestBody = {
secure_1psid: secure1psid,
secure_1psidts: secure1psidts
};
// 如果提供了 label则添加到请求体中
if (label) {
requestBody.label = label;
}
const response = await this.makeRequest('/gemini-web-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestBody)
});
this.closeModal();
this.loadAuthFiles(); // 刷新认证文件列表
const inlineSecure1psid = document.getElementById('secure-1psid-input');
const inlineSecure1psidts = document.getElementById('secure-1psidts-input');
const inlineLabel = document.getElementById('gemini-web-label-input');
if (inlineSecure1psid) {
inlineSecure1psid.value = secure1psid;
}
if (inlineSecure1psidts) {
inlineSecure1psidts.value = secure1psidts;
}
if (inlineLabel) {
inlineLabel.value = label;
}
this.showNotification(`${i18n.t('auth_login.gemini_web_saved')}: ${response.file}`, 'success');
} catch (error) {
this.showNotification(`保存失败: ${error.message}`, 'error');
}
}
// ===== Codex OAuth 相关方法 =====
// 开始 Codex OAuth 流程

48
i18n.js
View File

@@ -99,6 +99,8 @@ const i18n = {
'basic_settings.quota_title': '配额超出行为',
'basic_settings.quota_switch_project': '自动切换项目',
'basic_settings.quota_switch_preview': '切换到预览模型',
'basic_settings.usage_statistics_title': '使用统计',
'basic_settings.usage_statistics_enable': '启用使用统计',
// API 密钥管理
'api_keys.title': 'API 密钥管理',
@@ -214,17 +216,6 @@ const i18n = {
'auth_files.delete_all_success': '成功删除',
'auth_files.files_count': '个文件',
// Gemini Web Token
'auth_login.gemini_web_title': 'Gemini Web Token',
'auth_login.gemini_web_button': '保存 Gemini Web Token',
'auth_login.gemini_web_hint': '从浏览器开发者工具中获取 Gemini 网页版的 Cookie 值,用于直接认证访问 Gemini。',
'auth_login.secure_1psid_label': '__Secure-1PSID Cookie:',
'auth_login.secure_1psid_placeholder': '输入 __Secure-1PSID cookie 值',
'auth_login.secure_1psidts_label': '__Secure-1PSIDTS Cookie:',
'auth_login.secure_1psidts_placeholder': '输入 __Secure-1PSIDTS cookie 值',
'auth_login.gemini_web_label_label': '标签 (可选):',
'auth_login.gemini_web_label_placeholder': '输入标签名称 (可选)',
'auth_login.gemini_web_saved': 'Gemini Web Token 保存成功',
// Codex OAuth
'auth_login.codex_oauth_title': 'Codex OAuth',
@@ -332,6 +323,7 @@ const i18n = {
'notification.retry_updated': '重试设置已更新',
'notification.quota_switch_project_updated': '项目切换设置已更新',
'notification.quota_switch_preview_updated': '预览模型切换设置已更新',
'notification.usage_statistics_updated': '使用统计设置已更新',
'notification.api_key_added': 'API密钥添加成功',
'notification.api_key_updated': 'API密钥更新成功',
'notification.api_key_deleted': 'API密钥删除成功',
@@ -415,6 +407,8 @@ const i18n = {
'common.required': 'Required',
'common.api_key': 'Key',
'common.base_url': 'Address',
'common.proxy_url': 'Proxy',
'common.alias': 'Alias',
// Page titles
'title.main': 'CLI Proxy API Management Center',
@@ -476,6 +470,8 @@ const i18n = {
'basic_settings.quota_title': 'Quota Exceeded Behavior',
'basic_settings.quota_switch_project': 'Auto Switch Project',
'basic_settings.quota_switch_preview': 'Switch to Preview Model',
'basic_settings.usage_statistics_title': 'Usage Statistics',
'basic_settings.usage_statistics_enable': 'Enable usage statistics',
// API Keys management
'api_keys.title': 'API Keys Management',
@@ -515,9 +511,12 @@ const i18n = {
'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_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_key_label': 'API Key:',
'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.claude_title': 'Claude API Configuration',
@@ -530,9 +529,12 @@ const i18n = {
'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_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_key_label': 'API Key:',
'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.openai_title': 'OpenAI Compatible Providers',
@@ -546,10 +548,19 @@ const i18n = {
'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_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_name_label': 'Provider Name:',
'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_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_keys_count': 'Keys Count',
'ai_providers.openai_models_count': 'Models Count',
@@ -576,18 +587,6 @@ const i18n = {
'auth_files.delete_all_success': 'Successfully deleted',
'auth_files.files_count': 'files',
// Gemini Web Token
'auth_login.gemini_web_title': 'Gemini Web Token',
'auth_login.gemini_web_button': 'Save Gemini Web Token',
'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.secure_1psid_label': '__Secure-1PSID Cookie:',
'auth_login.secure_1psid_placeholder': 'Enter __Secure-1PSID cookie value',
'auth_login.secure_1psidts_label': '__Secure-1PSIDTS Cookie:',
'auth_login.secure_1psidts_placeholder': 'Enter __Secure-1PSIDTS cookie value',
'auth_login.gemini_web_label_label': 'Label (Optional):',
'auth_login.gemini_web_label_placeholder': 'Enter label name (optional)',
'auth_login.gemini_web_saved': 'Gemini Web Token saved successfully',
// Codex OAuth
'auth_login.codex_oauth_title': 'Codex OAuth',
'auth_login.codex_oauth_button': 'Start Codex Login',
@@ -694,6 +693,7 @@ const i18n = {
'notification.retry_updated': 'Retry settings updated',
'notification.quota_switch_project_updated': 'Project 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_updated': 'API key updated successfully',
'notification.api_key_deleted': 'API key deleted successfully',
@@ -706,6 +706,8 @@ const i18n = {
'notification.claude_config_added': 'Claude configuration added successfully',
'notification.claude_config_updated': 'Claude configuration updated 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_updated': 'OpenAI provider updated successfully',
'notification.openai_provider_deleted': 'OpenAI provider deleted successfully',

View File

@@ -275,6 +275,24 @@
</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>
</section>
<!-- API 密钥管理 -->
@@ -372,45 +390,6 @@
</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">
@@ -806,9 +785,9 @@
<!-- 版本信息 -->
<footer class="version-footer">
<div class="version-info">
<span data-i18n="footer.version">版本</span>: v0.1.0
<span data-i18n="footer.version">版本</span>: v0.1.3
<span class="separator"></span>
<span data-i18n="footer.author">作者</span>: Supra4E8C
<span data-i18n="footer.author">作者</span>: CLI Proxy API Team
</div>
</footer>
</div>

View File

@@ -1923,44 +1923,6 @@ input:checked+.slider:before {
}
/* 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 {
display: grid;