mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-03 03:10:50 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe11bfb48f | ||
|
|
ee0d8f82d7 | ||
|
|
0bbb397df5 |
861
app.js
861
app.js
@@ -109,6 +109,47 @@ class CLIProxyManager {
|
||||
this.setupThemeSwitcher();
|
||||
// loadSettings 将在登录成功后调用
|
||||
this.updateLoginConnectionInfo();
|
||||
// 检查主机名,如果不是 localhost 或 127.0.0.1,则隐藏 OAuth 登录框
|
||||
this.checkHostAndHideOAuth();
|
||||
}
|
||||
|
||||
// 检查主机名并隐藏 OAuth 登录框
|
||||
checkHostAndHideOAuth() {
|
||||
const hostname = window.location.hostname;
|
||||
const isLocalhost = hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1';
|
||||
|
||||
if (!isLocalhost) {
|
||||
// 隐藏所有 OAuth 登录卡片
|
||||
const oauthCards = [
|
||||
'codex-oauth-card',
|
||||
'anthropic-oauth-card',
|
||||
'gemini-cli-oauth-card',
|
||||
'qwen-oauth-card',
|
||||
'iflow-oauth-card'
|
||||
];
|
||||
|
||||
oauthCards.forEach(cardId => {
|
||||
const card = document.getElementById(cardId);
|
||||
if (card) {
|
||||
card.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// 如果找不到具体的卡片 ID,尝试通过类名查找
|
||||
const oauthCardElements = document.querySelectorAll('.card');
|
||||
oauthCardElements.forEach(card => {
|
||||
const cardText = card.textContent || '';
|
||||
if (cardText.includes('Codex OAuth') ||
|
||||
cardText.includes('Anthropic OAuth') ||
|
||||
cardText.includes('Gemini CLI OAuth') ||
|
||||
cardText.includes('Qwen OAuth') ||
|
||||
cardText.includes('iFlow OAuth')) {
|
||||
card.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`当前主机名: ${hostname},已隐藏 OAuth 登录框`);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查登录状态
|
||||
@@ -518,6 +559,81 @@ class CLIProxyManager {
|
||||
authFileInput.addEventListener('change', (e) => this.handleFileUpload(e));
|
||||
}
|
||||
|
||||
// Codex OAuth
|
||||
const codexOauthBtn = document.getElementById('codex-oauth-btn');
|
||||
const codexOpenLink = document.getElementById('codex-open-link');
|
||||
const codexCopyLink = document.getElementById('codex-copy-link');
|
||||
|
||||
if (codexOauthBtn) {
|
||||
codexOauthBtn.addEventListener('click', () => this.startCodexOAuth());
|
||||
}
|
||||
if (codexOpenLink) {
|
||||
codexOpenLink.addEventListener('click', () => this.openCodexLink());
|
||||
}
|
||||
if (codexCopyLink) {
|
||||
codexCopyLink.addEventListener('click', () => this.copyCodexLink());
|
||||
}
|
||||
|
||||
// Anthropic OAuth
|
||||
const anthropicOauthBtn = document.getElementById('anthropic-oauth-btn');
|
||||
const anthropicOpenLink = document.getElementById('anthropic-open-link');
|
||||
const anthropicCopyLink = document.getElementById('anthropic-copy-link');
|
||||
|
||||
if (anthropicOauthBtn) {
|
||||
anthropicOauthBtn.addEventListener('click', () => this.startAnthropicOAuth());
|
||||
}
|
||||
if (anthropicOpenLink) {
|
||||
anthropicOpenLink.addEventListener('click', () => this.openAnthropicLink());
|
||||
}
|
||||
if (anthropicCopyLink) {
|
||||
anthropicCopyLink.addEventListener('click', () => this.copyAnthropicLink());
|
||||
}
|
||||
|
||||
// Gemini CLI OAuth
|
||||
const geminiCliOauthBtn = document.getElementById('gemini-cli-oauth-btn');
|
||||
const geminiCliOpenLink = document.getElementById('gemini-cli-open-link');
|
||||
const geminiCliCopyLink = document.getElementById('gemini-cli-copy-link');
|
||||
|
||||
if (geminiCliOauthBtn) {
|
||||
geminiCliOauthBtn.addEventListener('click', () => this.startGeminiCliOAuth());
|
||||
}
|
||||
if (geminiCliOpenLink) {
|
||||
geminiCliOpenLink.addEventListener('click', () => this.openGeminiCliLink());
|
||||
}
|
||||
if (geminiCliCopyLink) {
|
||||
geminiCliCopyLink.addEventListener('click', () => this.copyGeminiCliLink());
|
||||
}
|
||||
|
||||
// Qwen OAuth
|
||||
const qwenOauthBtn = document.getElementById('qwen-oauth-btn');
|
||||
const qwenOpenLink = document.getElementById('qwen-open-link');
|
||||
const qwenCopyLink = document.getElementById('qwen-copy-link');
|
||||
|
||||
if (qwenOauthBtn) {
|
||||
qwenOauthBtn.addEventListener('click', () => this.startQwenOAuth());
|
||||
}
|
||||
if (qwenOpenLink) {
|
||||
qwenOpenLink.addEventListener('click', () => this.openQwenLink());
|
||||
}
|
||||
if (qwenCopyLink) {
|
||||
qwenCopyLink.addEventListener('click', () => this.copyQwenLink());
|
||||
}
|
||||
|
||||
// iFlow OAuth
|
||||
const iflowOauthBtn = document.getElementById('iflow-oauth-btn');
|
||||
const iflowOpenLink = document.getElementById('iflow-open-link');
|
||||
const iflowCopyLink = document.getElementById('iflow-copy-link');
|
||||
|
||||
if (iflowOauthBtn) {
|
||||
iflowOauthBtn.addEventListener('click', () => this.startIflowOAuth());
|
||||
}
|
||||
if (iflowOpenLink) {
|
||||
iflowOpenLink.addEventListener('click', () => this.openIflowLink());
|
||||
}
|
||||
if (iflowCopyLink) {
|
||||
iflowCopyLink.addEventListener('click', () => this.copyIflowLink());
|
||||
}
|
||||
|
||||
// 使用统计
|
||||
const refreshUsageStats = document.getElementById('refresh-usage-stats');
|
||||
const requestsHourBtn = document.getElementById('requests-hour-btn');
|
||||
@@ -2415,6 +2531,751 @@ class CLIProxyManager {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Codex OAuth 相关方法 =====
|
||||
|
||||
// 开始 Codex OAuth 流程
|
||||
async startCodexOAuth() {
|
||||
try {
|
||||
const response = await this.makeRequest('/codex-auth-url?is_webui=1');
|
||||
const authUrl = response.url;
|
||||
const state = this.extractStateFromUrl(authUrl);
|
||||
|
||||
// 显示授权链接
|
||||
const urlInput = document.getElementById('codex-oauth-url');
|
||||
const content = document.getElementById('codex-oauth-content');
|
||||
const status = document.getElementById('codex-oauth-status');
|
||||
|
||||
if (urlInput) {
|
||||
urlInput.value = authUrl;
|
||||
}
|
||||
if (content) {
|
||||
content.style.display = 'block';
|
||||
}
|
||||
if (status) {
|
||||
status.textContent = i18n.t('auth_login.codex_oauth_status_waiting');
|
||||
status.style.color = 'var(--warning-text)';
|
||||
}
|
||||
|
||||
// 开始轮询认证状态
|
||||
this.startCodexOAuthPolling(state);
|
||||
|
||||
} catch (error) {
|
||||
this.showNotification(`${i18n.t('auth_login.codex_oauth_start_error')} ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 从 URL 中提取 state 参数
|
||||
extractStateFromUrl(url) {
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
return urlObj.searchParams.get('state');
|
||||
} catch (error) {
|
||||
console.error('Failed to extract state from URL:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 打开 Codex 授权链接
|
||||
openCodexLink() {
|
||||
const urlInput = document.getElementById('codex-oauth-url');
|
||||
if (urlInput && urlInput.value) {
|
||||
window.open(urlInput.value, '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
// 复制 Codex 授权链接
|
||||
async copyCodexLink() {
|
||||
const urlInput = document.getElementById('codex-oauth-url');
|
||||
if (urlInput && urlInput.value) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(urlInput.value);
|
||||
this.showNotification('链接已复制到剪贴板', 'success');
|
||||
} catch (error) {
|
||||
// 降级方案:使用传统的复制方法
|
||||
urlInput.select();
|
||||
document.execCommand('copy');
|
||||
this.showNotification('链接已复制到剪贴板', 'success');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 开始轮询 OAuth 状态
|
||||
startCodexOAuthPolling(state) {
|
||||
if (!state) {
|
||||
this.showNotification('无法获取认证状态参数', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const pollInterval = setInterval(async () => {
|
||||
try {
|
||||
const response = await this.makeRequest(`/get-auth-status?state=${encodeURIComponent(state)}`);
|
||||
const status = response.status;
|
||||
const statusElement = document.getElementById('codex-oauth-status');
|
||||
|
||||
if (status === 'ok') {
|
||||
// 认证成功
|
||||
clearInterval(pollInterval);
|
||||
// 隐藏授权链接相关内容,恢复到初始状态
|
||||
this.resetCodexOAuthUI();
|
||||
// 显示成功通知
|
||||
this.showNotification(i18n.t('auth_login.codex_oauth_status_success'), 'success');
|
||||
// 刷新认证文件列表
|
||||
this.loadAuthFiles();
|
||||
} else if (status === 'error') {
|
||||
// 认证失败
|
||||
clearInterval(pollInterval);
|
||||
const errorMessage = response.error || 'Unknown error';
|
||||
// 显示错误信息
|
||||
if (statusElement) {
|
||||
statusElement.textContent = `${i18n.t('auth_login.codex_oauth_status_error')} ${errorMessage}`;
|
||||
statusElement.style.color = 'var(--error-text)';
|
||||
}
|
||||
this.showNotification(`${i18n.t('auth_login.codex_oauth_status_error')} ${errorMessage}`, 'error');
|
||||
// 3秒后重置UI,让用户能够重新开始
|
||||
setTimeout(() => {
|
||||
this.resetCodexOAuthUI();
|
||||
}, 3000);
|
||||
} else if (status === 'wait') {
|
||||
// 继续等待
|
||||
if (statusElement) {
|
||||
statusElement.textContent = i18n.t('auth_login.codex_oauth_status_waiting');
|
||||
statusElement.style.color = 'var(--warning-text)';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
clearInterval(pollInterval);
|
||||
const statusElement = document.getElementById('codex-oauth-status');
|
||||
if (statusElement) {
|
||||
statusElement.textContent = `${i18n.t('auth_login.codex_oauth_polling_error')} ${error.message}`;
|
||||
statusElement.style.color = 'var(--error-text)';
|
||||
}
|
||||
this.showNotification(`${i18n.t('auth_login.codex_oauth_polling_error')} ${error.message}`, 'error');
|
||||
// 3秒后重置UI,让用户能够重新开始
|
||||
setTimeout(() => {
|
||||
this.resetCodexOAuthUI();
|
||||
}, 3000);
|
||||
}
|
||||
}, 2000); // 每2秒轮询一次
|
||||
|
||||
// 设置超时,5分钟后停止轮询
|
||||
setTimeout(() => {
|
||||
clearInterval(pollInterval);
|
||||
}, 5 * 60 * 1000);
|
||||
}
|
||||
|
||||
// 重置 Codex OAuth UI 到初始状态
|
||||
resetCodexOAuthUI() {
|
||||
const urlInput = document.getElementById('codex-oauth-url');
|
||||
const content = document.getElementById('codex-oauth-content');
|
||||
const status = document.getElementById('codex-oauth-status');
|
||||
|
||||
// 清空并隐藏授权链接输入框
|
||||
if (urlInput) {
|
||||
urlInput.value = '';
|
||||
}
|
||||
|
||||
// 隐藏整个授权链接内容区域
|
||||
if (content) {
|
||||
content.style.display = 'none';
|
||||
}
|
||||
|
||||
// 清空状态显示
|
||||
if (status) {
|
||||
status.textContent = '';
|
||||
status.style.color = '';
|
||||
status.className = '';
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Anthropic OAuth 相关方法 =====
|
||||
|
||||
// 开始 Anthropic OAuth 流程
|
||||
async startAnthropicOAuth() {
|
||||
try {
|
||||
const response = await this.makeRequest('/anthropic-auth-url?is_webui=1');
|
||||
const authUrl = response.url;
|
||||
const state = this.extractStateFromUrl(authUrl);
|
||||
|
||||
// 显示授权链接
|
||||
const urlInput = document.getElementById('anthropic-oauth-url');
|
||||
const content = document.getElementById('anthropic-oauth-content');
|
||||
const status = document.getElementById('anthropic-oauth-status');
|
||||
|
||||
if (urlInput) {
|
||||
urlInput.value = authUrl;
|
||||
}
|
||||
if (content) {
|
||||
content.style.display = 'block';
|
||||
}
|
||||
if (status) {
|
||||
status.textContent = i18n.t('auth_login.anthropic_oauth_status_waiting');
|
||||
status.style.color = 'var(--warning-text)';
|
||||
}
|
||||
|
||||
// 开始轮询认证状态
|
||||
this.startAnthropicOAuthPolling(state);
|
||||
|
||||
} catch (error) {
|
||||
this.showNotification(`${i18n.t('auth_login.anthropic_oauth_start_error')} ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 打开 Anthropic 授权链接
|
||||
openAnthropicLink() {
|
||||
const urlInput = document.getElementById('anthropic-oauth-url');
|
||||
if (urlInput && urlInput.value) {
|
||||
window.open(urlInput.value, '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
// 复制 Anthropic 授权链接
|
||||
async copyAnthropicLink() {
|
||||
const urlInput = document.getElementById('anthropic-oauth-url');
|
||||
if (urlInput && urlInput.value) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(urlInput.value);
|
||||
this.showNotification('链接已复制到剪贴板', 'success');
|
||||
} catch (error) {
|
||||
// 降级方案:使用传统的复制方法
|
||||
urlInput.select();
|
||||
document.execCommand('copy');
|
||||
this.showNotification('链接已复制到剪贴板', 'success');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 开始轮询 Anthropic OAuth 状态
|
||||
startAnthropicOAuthPolling(state) {
|
||||
if (!state) {
|
||||
this.showNotification('无法获取认证状态参数', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const pollInterval = setInterval(async () => {
|
||||
try {
|
||||
const response = await this.makeRequest(`/get-auth-status?state=${encodeURIComponent(state)}`);
|
||||
const status = response.status;
|
||||
const statusElement = document.getElementById('anthropic-oauth-status');
|
||||
|
||||
if (status === 'ok') {
|
||||
// 认证成功
|
||||
clearInterval(pollInterval);
|
||||
// 隐藏授权链接相关内容,恢复到初始状态
|
||||
this.resetAnthropicOAuthUI();
|
||||
// 显示成功通知
|
||||
this.showNotification(i18n.t('auth_login.anthropic_oauth_status_success'), 'success');
|
||||
// 刷新认证文件列表
|
||||
this.loadAuthFiles();
|
||||
} else if (status === 'error') {
|
||||
// 认证失败
|
||||
clearInterval(pollInterval);
|
||||
const errorMessage = response.error || 'Unknown error';
|
||||
// 显示错误信息
|
||||
if (statusElement) {
|
||||
statusElement.textContent = `${i18n.t('auth_login.anthropic_oauth_status_error')} ${errorMessage}`;
|
||||
statusElement.style.color = 'var(--error-text)';
|
||||
}
|
||||
this.showNotification(`${i18n.t('auth_login.anthropic_oauth_status_error')} ${errorMessage}`, 'error');
|
||||
// 3秒后重置UI,让用户能够重新开始
|
||||
setTimeout(() => {
|
||||
this.resetAnthropicOAuthUI();
|
||||
}, 3000);
|
||||
} else if (status === 'wait') {
|
||||
// 继续等待
|
||||
if (statusElement) {
|
||||
statusElement.textContent = i18n.t('auth_login.anthropic_oauth_status_waiting');
|
||||
statusElement.style.color = 'var(--warning-text)';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
clearInterval(pollInterval);
|
||||
const statusElement = document.getElementById('anthropic-oauth-status');
|
||||
if (statusElement) {
|
||||
statusElement.textContent = `${i18n.t('auth_login.anthropic_oauth_polling_error')} ${error.message}`;
|
||||
statusElement.style.color = 'var(--error-text)';
|
||||
}
|
||||
this.showNotification(`${i18n.t('auth_login.anthropic_oauth_polling_error')} ${error.message}`, 'error');
|
||||
// 3秒后重置UI,让用户能够重新开始
|
||||
setTimeout(() => {
|
||||
this.resetAnthropicOAuthUI();
|
||||
}, 3000);
|
||||
}
|
||||
}, 2000); // 每2秒轮询一次
|
||||
|
||||
// 设置超时,5分钟后停止轮询
|
||||
setTimeout(() => {
|
||||
clearInterval(pollInterval);
|
||||
}, 5 * 60 * 1000);
|
||||
}
|
||||
|
||||
// 重置 Anthropic OAuth UI 到初始状态
|
||||
resetAnthropicOAuthUI() {
|
||||
const urlInput = document.getElementById('anthropic-oauth-url');
|
||||
const content = document.getElementById('anthropic-oauth-content');
|
||||
const status = document.getElementById('anthropic-oauth-status');
|
||||
|
||||
// 清空并隐藏授权链接输入框
|
||||
if (urlInput) {
|
||||
urlInput.value = '';
|
||||
}
|
||||
|
||||
// 隐藏整个授权链接内容区域
|
||||
if (content) {
|
||||
content.style.display = 'none';
|
||||
}
|
||||
|
||||
// 清空状态显示
|
||||
if (status) {
|
||||
status.textContent = '';
|
||||
status.style.color = '';
|
||||
status.className = '';
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Gemini CLI OAuth 相关方法 =====
|
||||
|
||||
// 开始 Gemini CLI OAuth 流程
|
||||
async startGeminiCliOAuth() {
|
||||
try {
|
||||
// 获取项目 ID(可选)
|
||||
const projectId = document.getElementById('gemini-cli-project-id').value.trim();
|
||||
|
||||
// 构建请求 URL
|
||||
let requestUrl = '/gemini-cli-auth-url?is_webui=1';
|
||||
if (projectId) {
|
||||
requestUrl += `&project_id=${encodeURIComponent(projectId)}`;
|
||||
}
|
||||
|
||||
const response = await this.makeRequest(requestUrl);
|
||||
const authUrl = response.url;
|
||||
const state = this.extractStateFromUrl(authUrl);
|
||||
|
||||
// 显示授权链接
|
||||
const urlInput = document.getElementById('gemini-cli-oauth-url');
|
||||
const content = document.getElementById('gemini-cli-oauth-content');
|
||||
const status = document.getElementById('gemini-cli-oauth-status');
|
||||
|
||||
if (urlInput) {
|
||||
urlInput.value = authUrl;
|
||||
}
|
||||
if (content) {
|
||||
content.style.display = 'block';
|
||||
}
|
||||
if (status) {
|
||||
status.textContent = i18n.t('auth_login.gemini_cli_oauth_status_waiting');
|
||||
status.style.color = 'var(--warning-text)';
|
||||
}
|
||||
|
||||
// 开始轮询认证状态
|
||||
this.startGeminiCliOAuthPolling(state);
|
||||
|
||||
} catch (error) {
|
||||
this.showNotification(`${i18n.t('auth_login.gemini_cli_oauth_start_error')} ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 打开 Gemini CLI 授权链接
|
||||
openGeminiCliLink() {
|
||||
const urlInput = document.getElementById('gemini-cli-oauth-url');
|
||||
if (urlInput && urlInput.value) {
|
||||
window.open(urlInput.value, '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
// 复制 Gemini CLI 授权链接
|
||||
async copyGeminiCliLink() {
|
||||
const urlInput = document.getElementById('gemini-cli-oauth-url');
|
||||
if (urlInput && urlInput.value) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(urlInput.value);
|
||||
this.showNotification('链接已复制到剪贴板', 'success');
|
||||
} catch (error) {
|
||||
// 降级方案:使用传统的复制方法
|
||||
urlInput.select();
|
||||
document.execCommand('copy');
|
||||
this.showNotification('链接已复制到剪贴板', 'success');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 开始轮询 Gemini CLI OAuth 状态
|
||||
startGeminiCliOAuthPolling(state) {
|
||||
if (!state) {
|
||||
this.showNotification('无法获取认证状态参数', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const pollInterval = setInterval(async () => {
|
||||
try {
|
||||
const response = await this.makeRequest(`/get-auth-status?state=${encodeURIComponent(state)}`);
|
||||
const status = response.status;
|
||||
const statusElement = document.getElementById('gemini-cli-oauth-status');
|
||||
|
||||
if (status === 'ok') {
|
||||
// 认证成功
|
||||
clearInterval(pollInterval);
|
||||
// 隐藏授权链接相关内容,恢复到初始状态
|
||||
this.resetGeminiCliOAuthUI();
|
||||
// 显示成功通知
|
||||
this.showNotification(i18n.t('auth_login.gemini_cli_oauth_status_success'), 'success');
|
||||
// 刷新认证文件列表
|
||||
this.loadAuthFiles();
|
||||
} else if (status === 'error') {
|
||||
// 认证失败
|
||||
clearInterval(pollInterval);
|
||||
const errorMessage = response.error || 'Unknown error';
|
||||
// 显示错误信息
|
||||
if (statusElement) {
|
||||
statusElement.textContent = `${i18n.t('auth_login.gemini_cli_oauth_status_error')} ${errorMessage}`;
|
||||
statusElement.style.color = 'var(--error-text)';
|
||||
}
|
||||
this.showNotification(`${i18n.t('auth_login.gemini_cli_oauth_status_error')} ${errorMessage}`, 'error');
|
||||
// 3秒后重置UI,让用户能够重新开始
|
||||
setTimeout(() => {
|
||||
this.resetGeminiCliOAuthUI();
|
||||
}, 3000);
|
||||
} else if (status === 'wait') {
|
||||
// 继续等待
|
||||
if (statusElement) {
|
||||
statusElement.textContent = i18n.t('auth_login.gemini_cli_oauth_status_waiting');
|
||||
statusElement.style.color = 'var(--warning-text)';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
clearInterval(pollInterval);
|
||||
const statusElement = document.getElementById('gemini-cli-oauth-status');
|
||||
if (statusElement) {
|
||||
statusElement.textContent = `${i18n.t('auth_login.gemini_cli_oauth_polling_error')} ${error.message}`;
|
||||
statusElement.style.color = 'var(--error-text)';
|
||||
}
|
||||
this.showNotification(`${i18n.t('auth_login.gemini_cli_oauth_polling_error')} ${error.message}`, 'error');
|
||||
// 3秒后重置UI,让用户能够重新开始
|
||||
setTimeout(() => {
|
||||
this.resetGeminiCliOAuthUI();
|
||||
}, 3000);
|
||||
}
|
||||
}, 2000); // 每2秒轮询一次
|
||||
|
||||
// 设置超时,5分钟后停止轮询
|
||||
setTimeout(() => {
|
||||
clearInterval(pollInterval);
|
||||
}, 5 * 60 * 1000);
|
||||
}
|
||||
|
||||
// 重置 Gemini CLI OAuth UI 到初始状态
|
||||
resetGeminiCliOAuthUI() {
|
||||
const urlInput = document.getElementById('gemini-cli-oauth-url');
|
||||
const content = document.getElementById('gemini-cli-oauth-content');
|
||||
const status = document.getElementById('gemini-cli-oauth-status');
|
||||
|
||||
// 清空并隐藏授权链接输入框
|
||||
if (urlInput) {
|
||||
urlInput.value = '';
|
||||
}
|
||||
|
||||
// 隐藏整个授权链接内容区域
|
||||
if (content) {
|
||||
content.style.display = 'none';
|
||||
}
|
||||
|
||||
// 清空状态显示
|
||||
if (status) {
|
||||
status.textContent = '';
|
||||
status.style.color = '';
|
||||
status.className = '';
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Qwen OAuth 相关方法 =====
|
||||
|
||||
// 开始 Qwen OAuth 流程
|
||||
async startQwenOAuth() {
|
||||
try {
|
||||
const response = await this.makeRequest('/qwen-auth-url?is_webui=1');
|
||||
const authUrl = response.url;
|
||||
const state = this.extractStateFromUrl(authUrl);
|
||||
|
||||
// 显示授权链接
|
||||
const urlInput = document.getElementById('qwen-oauth-url');
|
||||
const content = document.getElementById('qwen-oauth-content');
|
||||
const status = document.getElementById('qwen-oauth-status');
|
||||
|
||||
if (urlInput) {
|
||||
urlInput.value = authUrl;
|
||||
}
|
||||
if (content) {
|
||||
content.style.display = 'block';
|
||||
}
|
||||
if (status) {
|
||||
status.textContent = i18n.t('auth_login.qwen_oauth_status_waiting');
|
||||
status.style.color = 'var(--warning-text)';
|
||||
}
|
||||
|
||||
// 开始轮询认证状态
|
||||
this.startQwenOAuthPolling(response.state);
|
||||
|
||||
} catch (error) {
|
||||
this.showNotification(`${i18n.t('auth_login.qwen_oauth_start_error')} ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 打开 Qwen 授权链接
|
||||
openQwenLink() {
|
||||
const urlInput = document.getElementById('qwen-oauth-url');
|
||||
if (urlInput && urlInput.value) {
|
||||
window.open(urlInput.value, '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
// 复制 Qwen 授权链接
|
||||
async copyQwenLink() {
|
||||
const urlInput = document.getElementById('qwen-oauth-url');
|
||||
if (urlInput && urlInput.value) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(urlInput.value);
|
||||
this.showNotification('链接已复制到剪贴板', 'success');
|
||||
} catch (error) {
|
||||
// 降级方案:使用传统的复制方法
|
||||
urlInput.select();
|
||||
document.execCommand('copy');
|
||||
this.showNotification('链接已复制到剪贴板', 'success');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 开始轮询 Qwen OAuth 状态
|
||||
startQwenOAuthPolling(state) {
|
||||
if (!state) {
|
||||
this.showNotification('无法获取认证状态参数', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const pollInterval = setInterval(async () => {
|
||||
try {
|
||||
const response = await this.makeRequest(`/get-auth-status?state=${encodeURIComponent(state)}`);
|
||||
const status = response.status;
|
||||
const statusElement = document.getElementById('qwen-oauth-status');
|
||||
|
||||
if (status === 'ok') {
|
||||
// 认证成功
|
||||
clearInterval(pollInterval);
|
||||
// 隐藏授权链接相关内容,恢复到初始状态
|
||||
this.resetQwenOAuthUI();
|
||||
// 显示成功通知
|
||||
this.showNotification(i18n.t('auth_login.qwen_oauth_status_success'), 'success');
|
||||
// 刷新认证文件列表
|
||||
this.loadAuthFiles();
|
||||
} else if (status === 'error') {
|
||||
// 认证失败
|
||||
clearInterval(pollInterval);
|
||||
const errorMessage = response.error || 'Unknown error';
|
||||
// 显示错误信息
|
||||
if (statusElement) {
|
||||
statusElement.textContent = `${i18n.t('auth_login.qwen_oauth_status_error')} ${errorMessage}`;
|
||||
statusElement.style.color = 'var(--error-text)';
|
||||
}
|
||||
this.showNotification(`${i18n.t('auth_login.qwen_oauth_status_error')} ${errorMessage}`, 'error');
|
||||
// 3秒后重置UI,让用户能够重新开始
|
||||
setTimeout(() => {
|
||||
this.resetQwenOAuthUI();
|
||||
}, 3000);
|
||||
} else if (status === 'wait') {
|
||||
// 继续等待
|
||||
if (statusElement) {
|
||||
statusElement.textContent = i18n.t('auth_login.qwen_oauth_status_waiting');
|
||||
statusElement.style.color = 'var(--warning-text)';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
clearInterval(pollInterval);
|
||||
const statusElement = document.getElementById('qwen-oauth-status');
|
||||
if (statusElement) {
|
||||
statusElement.textContent = `${i18n.t('auth_login.qwen_oauth_polling_error')} ${error.message}`;
|
||||
statusElement.style.color = 'var(--error-text)';
|
||||
}
|
||||
this.showNotification(`${i18n.t('auth_login.qwen_oauth_polling_error')} ${error.message}`, 'error');
|
||||
// 3秒后重置UI,让用户能够重新开始
|
||||
setTimeout(() => {
|
||||
this.resetQwenOAuthUI();
|
||||
}, 3000);
|
||||
}
|
||||
}, 2000); // 每2秒轮询一次
|
||||
|
||||
// 设置超时,5分钟后停止轮询
|
||||
setTimeout(() => {
|
||||
clearInterval(pollInterval);
|
||||
}, 5 * 60 * 1000);
|
||||
}
|
||||
|
||||
// 重置 Qwen OAuth UI 到初始状态
|
||||
resetQwenOAuthUI() {
|
||||
const urlInput = document.getElementById('qwen-oauth-url');
|
||||
const content = document.getElementById('qwen-oauth-content');
|
||||
const status = document.getElementById('qwen-oauth-status');
|
||||
|
||||
// 清空并隐藏授权链接输入框
|
||||
if (urlInput) {
|
||||
urlInput.value = '';
|
||||
}
|
||||
|
||||
// 隐藏整个授权链接内容区域
|
||||
if (content) {
|
||||
content.style.display = 'none';
|
||||
}
|
||||
|
||||
// 清空状态显示
|
||||
if (status) {
|
||||
status.textContent = '';
|
||||
status.style.color = '';
|
||||
status.className = '';
|
||||
}
|
||||
}
|
||||
|
||||
// ===== iFlow OAuth 相关方法 =====
|
||||
|
||||
// 开始 iFlow OAuth 流程
|
||||
async startIflowOAuth() {
|
||||
try {
|
||||
const response = await this.makeRequest('/iflow-auth-url?is_webui=1');
|
||||
const authUrl = response.url;
|
||||
const state = this.extractStateFromUrl(authUrl);
|
||||
|
||||
// 显示授权链接
|
||||
const urlInput = document.getElementById('iflow-oauth-url');
|
||||
const content = document.getElementById('iflow-oauth-content');
|
||||
const status = document.getElementById('iflow-oauth-status');
|
||||
|
||||
if (urlInput) {
|
||||
urlInput.value = authUrl;
|
||||
}
|
||||
if (content) {
|
||||
content.style.display = 'block';
|
||||
}
|
||||
if (status) {
|
||||
status.textContent = i18n.t('auth_login.iflow_oauth_status_waiting');
|
||||
status.style.color = 'var(--warning-text)';
|
||||
}
|
||||
|
||||
// 开始轮询认证状态
|
||||
this.startIflowOAuthPolling(state);
|
||||
|
||||
} catch (error) {
|
||||
this.showNotification(`${i18n.t('auth_login.iflow_oauth_start_error')} ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 打开 iFlow 授权链接
|
||||
openIflowLink() {
|
||||
const urlInput = document.getElementById('iflow-oauth-url');
|
||||
if (urlInput && urlInput.value) {
|
||||
window.open(urlInput.value, '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
// 复制 iFlow 授权链接
|
||||
async copyIflowLink() {
|
||||
const urlInput = document.getElementById('iflow-oauth-url');
|
||||
if (urlInput && urlInput.value) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(urlInput.value);
|
||||
this.showNotification('链接已复制到剪贴板', 'success');
|
||||
} catch (error) {
|
||||
// 降级方案:使用传统的复制方法
|
||||
urlInput.select();
|
||||
document.execCommand('copy');
|
||||
this.showNotification('链接已复制到剪贴板', 'success');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 开始轮询 iFlow OAuth 状态
|
||||
startIflowOAuthPolling(state) {
|
||||
if (!state) {
|
||||
this.showNotification('无法获取认证状态参数', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const pollInterval = setInterval(async () => {
|
||||
try {
|
||||
const response = await this.makeRequest(`/get-auth-status?state=${encodeURIComponent(state)}`);
|
||||
const status = response.status;
|
||||
const statusElement = document.getElementById('iflow-oauth-status');
|
||||
|
||||
if (status === 'ok') {
|
||||
// 认证成功
|
||||
clearInterval(pollInterval);
|
||||
// 隐藏授权链接相关内容,恢复到初始状态
|
||||
this.resetIflowOAuthUI();
|
||||
// 显示成功通知
|
||||
this.showNotification(i18n.t('auth_login.iflow_oauth_status_success'), 'success');
|
||||
// 刷新认证文件列表
|
||||
this.loadAuthFiles();
|
||||
} else if (status === 'error') {
|
||||
// 认证失败
|
||||
clearInterval(pollInterval);
|
||||
const errorMessage = response.error || 'Unknown error';
|
||||
// 显示错误信息
|
||||
if (statusElement) {
|
||||
statusElement.textContent = `${i18n.t('auth_login.iflow_oauth_status_error')} ${errorMessage}`;
|
||||
statusElement.style.color = 'var(--error-text)';
|
||||
}
|
||||
this.showNotification(`${i18n.t('auth_login.iflow_oauth_status_error')} ${errorMessage}`, 'error');
|
||||
// 3秒后重置UI,让用户能够重新开始
|
||||
setTimeout(() => {
|
||||
this.resetIflowOAuthUI();
|
||||
}, 3000);
|
||||
} else if (status === 'wait') {
|
||||
// 继续等待
|
||||
if (statusElement) {
|
||||
statusElement.textContent = i18n.t('auth_login.iflow_oauth_status_waiting');
|
||||
statusElement.style.color = 'var(--warning-text)';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
clearInterval(pollInterval);
|
||||
const statusElement = document.getElementById('iflow-oauth-status');
|
||||
if (statusElement) {
|
||||
statusElement.textContent = `${i18n.t('auth_login.iflow_oauth_polling_error')} ${error.message}`;
|
||||
statusElement.style.color = 'var(--error-text)';
|
||||
}
|
||||
this.showNotification(`${i18n.t('auth_login.iflow_oauth_polling_error')} ${error.message}`, 'error');
|
||||
// 3秒后重置UI,让用户能够重新开始
|
||||
setTimeout(() => {
|
||||
this.resetIflowOAuthUI();
|
||||
}, 3000);
|
||||
}
|
||||
}, 2000); // 每2秒轮询一次
|
||||
|
||||
// 设置超时,5分钟后停止轮询
|
||||
setTimeout(() => {
|
||||
clearInterval(pollInterval);
|
||||
}, 5 * 60 * 1000);
|
||||
}
|
||||
|
||||
// 重置 iFlow OAuth UI 到初始状态
|
||||
resetIflowOAuthUI() {
|
||||
const urlInput = document.getElementById('iflow-oauth-url');
|
||||
const content = document.getElementById('iflow-oauth-content');
|
||||
const status = document.getElementById('iflow-oauth-status');
|
||||
|
||||
// 清空并隐藏授权链接输入框
|
||||
if (urlInput) {
|
||||
urlInput.value = '';
|
||||
}
|
||||
|
||||
// 隐藏整个授权链接内容区域
|
||||
if (content) {
|
||||
content.style.display = 'none';
|
||||
}
|
||||
|
||||
// 清空状态显示
|
||||
if (status) {
|
||||
status.textContent = '';
|
||||
status.style.color = '';
|
||||
status.className = '';
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 使用统计相关方法 =====
|
||||
|
||||
// 初始化图表变量
|
||||
|
||||
136
i18n.js
136
i18n.js
@@ -226,6 +226,74 @@ const i18n = {
|
||||
'auth_login.gemini_web_label_placeholder': '输入标签名称 (可选)',
|
||||
'auth_login.gemini_web_saved': 'Gemini Web Token 保存成功',
|
||||
|
||||
// Codex OAuth
|
||||
'auth_login.codex_oauth_title': 'Codex OAuth',
|
||||
'auth_login.codex_oauth_button': '开始 Codex 登录',
|
||||
'auth_login.codex_oauth_hint': '通过 OAuth 流程登录 Codex 服务,自动获取并保存认证文件。',
|
||||
'auth_login.codex_oauth_url_label': '授权链接:',
|
||||
'auth_login.codex_open_link': '打开链接',
|
||||
'auth_login.codex_copy_link': '复制链接',
|
||||
'auth_login.codex_oauth_status_waiting': '等待认证中...',
|
||||
'auth_login.codex_oauth_status_success': '认证成功!',
|
||||
'auth_login.codex_oauth_status_error': '认证失败:',
|
||||
'auth_login.codex_oauth_start_error': '启动 Codex OAuth 失败:',
|
||||
'auth_login.codex_oauth_polling_error': '检查认证状态失败:',
|
||||
|
||||
// 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.total_requests': '总请求数',
|
||||
@@ -520,6 +588,74 @@ const i18n = {
|
||||
'auth_login.gemini_web_label_placeholder': 'Enter label name (optional)',
|
||||
'auth_login.gemini_web_saved': 'Gemini Web Token saved successfully',
|
||||
|
||||
// Codex OAuth
|
||||
'auth_login.codex_oauth_title': 'Codex OAuth',
|
||||
'auth_login.codex_oauth_button': 'Start Codex Login',
|
||||
'auth_login.codex_oauth_hint': 'Login to Codex service through OAuth flow, automatically obtain and save authentication files.',
|
||||
'auth_login.codex_oauth_url_label': 'Authorization URL:',
|
||||
'auth_login.codex_open_link': 'Open Link',
|
||||
'auth_login.codex_copy_link': 'Copy Link',
|
||||
'auth_login.codex_oauth_status_waiting': 'Waiting for authentication...',
|
||||
'auth_login.codex_oauth_status_success': 'Authentication successful!',
|
||||
'auth_login.codex_oauth_status_error': 'Authentication failed:',
|
||||
'auth_login.codex_oauth_start_error': 'Failed to start Codex OAuth:',
|
||||
'auth_login.codex_oauth_polling_error': 'Failed to check authentication status:',
|
||||
|
||||
// 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_stats.title': 'Usage Statistics',
|
||||
'usage_stats.total_requests': 'Total Requests',
|
||||
|
||||
342
index.html
342
index.html
@@ -1,5 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
@@ -9,6 +10,7 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="i18n.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- 自动登录加载页面 -->
|
||||
<div id="auto-login-loading" class="login-container" style="display: none;">
|
||||
@@ -69,8 +71,10 @@
|
||||
<div class="form-group">
|
||||
<label for="login-api-base" data-i18n="login.custom_connection_label">自定义连接地址:</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="login-api-base" data-i18n="login.custom_connection_placeholder" placeholder="例如: https://example.com:8317">
|
||||
<button type="button" id="login-reset-api-base" class="btn btn-secondary connection-reset-btn">
|
||||
<input type="text" id="login-api-base" data-i18n="login.custom_connection_placeholder"
|
||||
placeholder="例如: https://example.com:8317">
|
||||
<button type="button" id="login-reset-api-base"
|
||||
class="btn btn-secondary connection-reset-btn">
|
||||
<i class="fas fa-location-arrow"></i>
|
||||
<span data-i18n="login.use_current_address">使用当前地址</span>
|
||||
</button>
|
||||
@@ -81,7 +85,8 @@
|
||||
<div class="form-group">
|
||||
<label for="login-management-key" data-i18n="login.management_key_label">管理密钥:</label>
|
||||
<div class="input-group">
|
||||
<input type="password" id="login-management-key" data-i18n="login.management_key_placeholder" placeholder="请输入管理密钥" required>
|
||||
<input type="password" id="login-management-key"
|
||||
data-i18n="login.management_key_placeholder" placeholder="请输入管理密钥" required>
|
||||
<button type="button" class="btn btn-secondary toggle-key-visibility">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
@@ -150,7 +155,8 @@
|
||||
<nav class="sidebar" id="sidebar">
|
||||
<!-- 导航菜单 -->
|
||||
<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>
|
||||
</a></li>
|
||||
<li data-tooltip="API 密钥"><a href="#api-keys" class="nav-item" data-section="api-keys">
|
||||
@@ -187,7 +193,8 @@
|
||||
<!-- Debug 设置 -->
|
||||
<div class="card">
|
||||
<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 class="card-content">
|
||||
<div class="toggle-group">
|
||||
@@ -203,15 +210,21 @@
|
||||
<!-- 代理设置 -->
|
||||
<div class="card">
|
||||
<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 class="card-content">
|
||||
<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">
|
||||
<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>
|
||||
<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>
|
||||
@@ -220,14 +233,17 @@
|
||||
<!-- 请求重试设置 -->
|
||||
<div class="card">
|
||||
<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 class="card-content">
|
||||
<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">
|
||||
<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>
|
||||
@@ -236,7 +252,8 @@
|
||||
<!-- 配额超出行为 -->
|
||||
<div class="card">
|
||||
<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 class="card-content">
|
||||
<div class="toggle-group">
|
||||
@@ -244,14 +261,16 @@
|
||||
<input type="checkbox" id="switch-project-toggle">
|
||||
<span class="slider"></span>
|
||||
</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 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>
|
||||
<span class="toggle-label"
|
||||
data-i18n="basic_settings.quota_switch_preview">切换到预览模型</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -264,7 +283,8 @@
|
||||
|
||||
<div class="card">
|
||||
<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">
|
||||
<i class="fas fa-plus"></i> <span data-i18n="api_keys.add_button">添加密钥</span>
|
||||
</button>
|
||||
@@ -282,9 +302,11 @@
|
||||
<!-- 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>
|
||||
<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>
|
||||
<i class="fas fa-plus"></i> <span
|
||||
data-i18n="ai_providers.gemini_add_button">添加密钥</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
@@ -295,9 +317,11 @@
|
||||
<!-- 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>
|
||||
<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>
|
||||
<i class="fas fa-plus"></i> <span
|
||||
data-i18n="ai_providers.codex_add_button">添加配置</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
@@ -308,9 +332,11 @@
|
||||
<!-- 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>
|
||||
<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>
|
||||
<i class="fas fa-plus"></i> <span
|
||||
data-i18n="ai_providers.claude_add_button">添加配置</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
@@ -321,9 +347,11 @@
|
||||
<!-- 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>
|
||||
<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>
|
||||
<i class="fas fa-plus"></i> <span
|
||||
data-i18n="ai_providers.openai_add_button">添加提供商</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
@@ -347,26 +375,38 @@
|
||||
<!-- 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>
|
||||
<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>
|
||||
<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">
|
||||
<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 值">
|
||||
<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 值">
|
||||
<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="输入标签名称 (可选)">
|
||||
<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>
|
||||
@@ -374,13 +414,16 @@
|
||||
<!-- 认证文件 -->
|
||||
<div class="card">
|
||||
<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">
|
||||
<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 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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -389,6 +432,194 @@
|
||||
<input type="file" id="auth-file-input" accept=".json" style="display: none;">
|
||||
</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>
|
||||
|
||||
<!-- 使用统计 -->
|
||||
@@ -443,12 +674,14 @@
|
||||
<!-- 请求趋势图 -->
|
||||
<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>
|
||||
<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">
|
||||
<button class="btn btn-small active" data-period="day"
|
||||
id="requests-day-btn">
|
||||
<span data-i18n="usage_stats.by_day">按天</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -463,7 +696,8 @@
|
||||
<!-- 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>
|
||||
<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>
|
||||
@@ -484,7 +718,8 @@
|
||||
<!-- 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>
|
||||
<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>
|
||||
@@ -504,7 +739,8 @@
|
||||
<!-- 连接信息卡片 -->
|
||||
<div class="card">
|
||||
<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 class="card-content">
|
||||
<div class="connection-info">
|
||||
@@ -528,7 +764,8 @@
|
||||
<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>
|
||||
<span class="status-indicator disconnected"
|
||||
data-i18n="common.disconnected">未连接</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -537,20 +774,26 @@
|
||||
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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 class="status-label"
|
||||
data-i18n="system_info.last_update_label">最后更新:</span>
|
||||
<span id="last-update" class="status-value">-</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -590,4 +833,5 @@
|
||||
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
516
styles.css
516
styles.css
@@ -149,8 +149,13 @@
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.auto-login-content h2 {
|
||||
@@ -198,7 +203,8 @@
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
margin-top: 8px; /* 与标题拉开距离,避免遮挡 */
|
||||
margin-top: 8px;
|
||||
/* 与标题拉开距离,避免遮挡 */
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@@ -549,12 +555,14 @@
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px; /* 在小屏幕上给控制按钮组添加下边距 */
|
||||
margin-bottom: 10px;
|
||||
/* 在小屏幕上给控制按钮组添加下边距 */
|
||||
}
|
||||
|
||||
/* 登录页面小屏幕优化 */
|
||||
.login-header-top .header-controls {
|
||||
margin: 8px auto 0 auto; /* 顶部留白,避免与标题拥挤 */
|
||||
margin: 8px auto 0 auto;
|
||||
/* 顶部留白,避免与标题拥挤 */
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
padding: 6px;
|
||||
border-radius: 10px;
|
||||
@@ -572,7 +580,8 @@
|
||||
.theme-btn {
|
||||
padding: 8px 16px;
|
||||
font-size: 13px;
|
||||
height: 36px; /* 在小屏幕上稍微减小高度 */
|
||||
height: 36px;
|
||||
/* 在小屏幕上稍微减小高度 */
|
||||
}
|
||||
|
||||
.login-title {
|
||||
@@ -851,7 +860,7 @@ body {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.top-navbar-actions > * {
|
||||
.top-navbar-actions>* {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 36px;
|
||||
@@ -892,7 +901,7 @@ body {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.top-navbar-actions > * {
|
||||
.top-navbar-actions>* {
|
||||
height: 34px;
|
||||
min-height: 34px;
|
||||
}
|
||||
@@ -1049,6 +1058,7 @@ body {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
@@ -1293,11 +1303,11 @@ textarea::placeholder {
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
input:checked+.slider {
|
||||
background: var(--primary-color);
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
input:checked+.slider:before {
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
@@ -1457,6 +1467,7 @@ input:checked + .slider:before {
|
||||
opacity: 0;
|
||||
transform: translateY(-30px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
@@ -1660,7 +1671,7 @@ input:checked + .slider:before {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.top-navbar-actions > * {
|
||||
.top-navbar-actions>* {
|
||||
height: 34px;
|
||||
min-height: 34px;
|
||||
}
|
||||
@@ -1803,7 +1814,9 @@ input:checked + .slider:before {
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
@@ -1899,9 +1912,11 @@ input:checked + .slider:before {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -2211,3 +2226,480 @@ input:checked + .slider:before {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
/* Codex OAuth 样式 */
|
||||
#codex-oauth-content {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
#codex-oauth-url {
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 13px;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-primary);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
#codex-oauth-url:focus {
|
||||
border-color: var(--border-focus);
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
#codex-oauth-status {
|
||||
font-weight: 500;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-primary);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
#codex-oauth-status.success {
|
||||
background: var(--success-bg);
|
||||
border-color: var(--success-border);
|
||||
color: var(--success-text);
|
||||
}
|
||||
|
||||
#codex-oauth-status.error {
|
||||
background: var(--error-bg);
|
||||
border-color: var(--error-border);
|
||||
color: var(--error-text);
|
||||
}
|
||||
|
||||
#codex-oauth-status.warning {
|
||||
background: var(--warning-bg);
|
||||
border-color: var(--warning-border);
|
||||
color: var(--warning-text);
|
||||
}
|
||||
|
||||
/* Codex OAuth 按钮样式 */
|
||||
#codex-open-link,
|
||||
#codex-copy-link {
|
||||
min-width: 100px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#codex-open-link {
|
||||
background: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
#codex-open-link:hover {
|
||||
background: var(--primary-hover);
|
||||
border-color: var(--primary-hover);
|
||||
}
|
||||
|
||||
#codex-copy-link {
|
||||
background: var(--bg-secondary);
|
||||
border-color: var(--border-primary);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
#codex-copy-link:hover {
|
||||
background: var(--bg-tertiary);
|
||||
border-color: var(--border-secondary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* 响应式设计 - Codex OAuth */
|
||||
@media (max-width: 768px) {
|
||||
#codex-oauth-content .input-group {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
#codex-open-link,
|
||||
#codex-copy-link {
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
#codex-oauth-url {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user