From 970297f3ae4c51515d06e93b609102ce94787fe0 Mon Sep 17 00:00:00 2001 From: Supra4E8C Date: Sun, 23 Nov 2025 17:56:17 +0800 Subject: [PATCH] feat(antigravity): implement Antigravity OAuth integration with UI elements and functionality --- app.js | 16 +++++ i18n.js | 26 +++++++++ index.html | 36 ++++++++++++ src/modules/oauth.js | 129 +++++++++++++++++++++++++++++++++++++++++ src/utils/constants.js | 2 + 5 files changed, 209 insertions(+) diff --git a/app.js b/app.js index 9fa5919..b2fd76c 100644 --- a/app.js +++ b/app.js @@ -221,6 +221,7 @@ class CLIProxyManager { const cardText = card.textContent || ''; if (cardText.includes('Codex OAuth') || cardText.includes('Anthropic OAuth') || + cardText.includes('Antigravity OAuth') || cardText.includes('Gemini CLI OAuth') || cardText.includes('Qwen OAuth') || cardText.includes('iFlow OAuth')) { @@ -430,6 +431,21 @@ class CLIProxyManager { anthropicCopyLink.addEventListener('click', () => this.copyAnthropicLink()); } + // Antigravity OAuth + const antigravityOauthBtn = document.getElementById('antigravity-oauth-btn'); + const antigravityOpenLink = document.getElementById('antigravity-open-link'); + const antigravityCopyLink = document.getElementById('antigravity-copy-link'); + + if (antigravityOauthBtn) { + antigravityOauthBtn.addEventListener('click', () => this.startAntigravityOAuth()); + } + if (antigravityOpenLink) { + antigravityOpenLink.addEventListener('click', () => this.openAntigravityLink()); + } + if (antigravityCopyLink) { + antigravityCopyLink.addEventListener('click', () => this.copyAntigravityLink()); + } + // Gemini CLI OAuth const geminiCliOauthBtn = document.getElementById('gemini-cli-oauth-btn'); const geminiCliOpenLink = document.getElementById('gemini-cli-open-link'); diff --git a/i18n.js b/i18n.js index 5a623e0..3df27ce 100644 --- a/i18n.js +++ b/i18n.js @@ -333,6 +333,19 @@ const i18n = { 'auth_login.anthropic_oauth_start_error': '启动 Anthropic OAuth 失败:', 'auth_login.anthropic_oauth_polling_error': '检查认证状态失败:', + // Antigravity OAuth + 'auth_login.antigravity_oauth_title': 'Antigravity OAuth', + 'auth_login.antigravity_oauth_button': '开始 Antigravity 登录', + 'auth_login.antigravity_oauth_hint': '通过 OAuth 流程登录 Antigravity(Google 账号)服务,自动获取并保存认证文件。', + 'auth_login.antigravity_oauth_url_label': '授权链接:', + 'auth_login.antigravity_open_link': '打开链接', + 'auth_login.antigravity_copy_link': '复制链接', + 'auth_login.antigravity_oauth_status_waiting': '等待认证中...', + 'auth_login.antigravity_oauth_status_success': '认证成功!', + 'auth_login.antigravity_oauth_status_error': '认证失败:', + 'auth_login.antigravity_oauth_start_error': '启动 Antigravity OAuth 失败:', + 'auth_login.antigravity_oauth_polling_error': '检查认证状态失败:', + // Gemini CLI OAuth 'auth_login.gemini_cli_oauth_title': 'Gemini CLI OAuth', 'auth_login.gemini_cli_oauth_button': '开始 Gemini CLI 登录', @@ -854,6 +867,19 @@ const i18n = { 'auth_login.anthropic_oauth_start_error': 'Failed to start Anthropic OAuth:', 'auth_login.anthropic_oauth_polling_error': 'Failed to check authentication status:', + // Antigravity OAuth + 'auth_login.antigravity_oauth_title': 'Antigravity OAuth', + 'auth_login.antigravity_oauth_button': 'Start Antigravity Login', + 'auth_login.antigravity_oauth_hint': 'Login to Antigravity service (Google account) through OAuth flow, automatically obtain and save authentication files.', + 'auth_login.antigravity_oauth_url_label': 'Authorization URL:', + 'auth_login.antigravity_open_link': 'Open Link', + 'auth_login.antigravity_copy_link': 'Copy Link', + 'auth_login.antigravity_oauth_status_waiting': 'Waiting for authentication...', + 'auth_login.antigravity_oauth_status_success': 'Authentication successful!', + 'auth_login.antigravity_oauth_status_error': 'Authentication failed:', + 'auth_login.antigravity_oauth_start_error': 'Failed to start Antigravity OAuth:', + 'auth_login.antigravity_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', diff --git a/index.html b/index.html index 0e230b9..3fc0b1d 100644 --- a/index.html +++ b/index.html @@ -628,6 +628,42 @@ + +
+
+

Antigravity OAuth

+ +
+
+

+ 通过 OAuth 流程登录 Antigravity(Google 账号)服务,自动获取并保存认证文件。 +

+ +
+
+
diff --git a/src/modules/oauth.js b/src/modules/oauth.js index c29af47..01a1258 100644 --- a/src/modules/oauth.js +++ b/src/modules/oauth.js @@ -300,6 +300,135 @@ export const oauthModule = { } }, + // ===== Antigravity OAuth 相关方法 ===== + + // 开始 Antigravity OAuth 流程 + async startAntigravityOAuth() { + try { + const response = await this.makeRequest('/antigravity-auth-url?is_webui=1'); + const authUrl = response.url; + const state = response.state || this.extractStateFromUrl(authUrl); + + // 显示授权链接 + const urlInput = document.getElementById('antigravity-oauth-url'); + const content = document.getElementById('antigravity-oauth-content'); + const status = document.getElementById('antigravity-oauth-status'); + + if (urlInput) { + urlInput.value = authUrl; + } + if (content) { + content.style.display = 'block'; + } + if (status) { + status.textContent = i18n.t('auth_login.antigravity_oauth_status_waiting'); + status.style.color = 'var(--warning-text)'; + } + + // 开始轮询认证状态 + this.startAntigravityOAuthPolling(state); + + } catch (error) { + this.showNotification(`${i18n.t('auth_login.antigravity_oauth_start_error')} ${error.message}`, 'error'); + } + }, + + // 打开 Antigravity 授权链接 + openAntigravityLink() { + const urlInput = document.getElementById('antigravity-oauth-url'); + if (urlInput && urlInput.value) { + window.open(urlInput.value, '_blank'); + } + }, + + // 复制 Antigravity 授权链接 + async copyAntigravityLink() { + const urlInput = document.getElementById('antigravity-oauth-url'); + if (urlInput && urlInput.value) { + try { + await navigator.clipboard.writeText(urlInput.value); + this.showNotification(i18n.t('notification.link_copied'), 'success'); + } catch (error) { + urlInput.select(); + document.execCommand('copy'); + this.showNotification(i18n.t('notification.link_copied'), 'success'); + } + } + }, + + // 开始轮询 Antigravity OAuth 状态 + startAntigravityOAuthPolling(state) { + if (!state) { + this.showNotification(i18n.t('auth_login.missing_state'), '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('antigravity-oauth-status'); + + if (status === 'ok') { + clearInterval(pollInterval); + this.resetAntigravityOAuthUI(); + this.showNotification(i18n.t('auth_login.antigravity_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.antigravity_oauth_status_error')} ${errorMessage}`; + statusElement.style.color = 'var(--error-text)'; + } + this.showNotification(`${i18n.t('auth_login.antigravity_oauth_status_error')} ${errorMessage}`, 'error'); + setTimeout(() => { + this.resetAntigravityOAuthUI(); + }, 3000); + } else if (status === 'wait') { + if (statusElement) { + statusElement.textContent = i18n.t('auth_login.antigravity_oauth_status_waiting'); + statusElement.style.color = 'var(--warning-text)'; + } + } + } catch (error) { + clearInterval(pollInterval); + const statusElement = document.getElementById('antigravity-oauth-status'); + if (statusElement) { + statusElement.textContent = `${i18n.t('auth_login.antigravity_oauth_polling_error')} ${error.message}`; + statusElement.style.color = 'var(--error-text)'; + } + this.showNotification(`${i18n.t('auth_login.antigravity_oauth_polling_error')} ${error.message}`, 'error'); + setTimeout(() => { + this.resetAntigravityOAuthUI(); + }, 3000); + } + }, 2000); + + setTimeout(() => { + clearInterval(pollInterval); + }, 5 * 60 * 1000); + }, + + // 重置 Antigravity OAuth UI 到初始状态 + resetAntigravityOAuthUI() { + const urlInput = document.getElementById('antigravity-oauth-url'); + const content = document.getElementById('antigravity-oauth-content'); + const status = document.getElementById('antigravity-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 流程 diff --git a/src/utils/constants.js b/src/utils/constants.js index a50a05c..65bf533 100644 --- a/src/utils/constants.js +++ b/src/utils/constants.js @@ -109,6 +109,7 @@ export const REQUEST_TIMEOUT_MS = 30 * 1000; export const OAUTH_CARD_IDS = [ 'codex-oauth-card', 'anthropic-oauth-card', + 'antigravity-oauth-card', 'gemini-cli-oauth-card', 'qwen-oauth-card', 'iflow-oauth-card' @@ -120,6 +121,7 @@ export const OAUTH_CARD_IDS = [ export const OAUTH_PROVIDERS = { CODEX: 'codex', ANTHROPIC: 'anthropic', + ANTIGRAVITY: 'antigravity', GEMINI_CLI: 'gemini-cli', QWEN: 'qwen', IFLOW: 'iflow'