From bb2f97182ee3ee7fc15ffb9b25e42fe66a4d4b15 Mon Sep 17 00:00:00 2001 From: Wendong-Fan Date: Mon, 10 Nov 2025 15:22:06 +0800 Subject: [PATCH] enhance: update Google calendar PR494 --- backend/app/utils/oauth_state_manager.py | 14 +++--- .../utils/toolkit/google_calendar_toolkit.py | 43 +++++++++---------- src/components/ChatBox/TaskCard.tsx | 13 +++--- 3 files changed, 34 insertions(+), 36 deletions(-) diff --git a/backend/app/utils/oauth_state_manager.py b/backend/app/utils/oauth_state_manager.py index 0a13f99e3..6f63daa19 100644 --- a/backend/app/utils/oauth_state_manager.py +++ b/backend/app/utils/oauth_state_manager.py @@ -2,7 +2,7 @@ OAuth authorization state manager for background authorization flows """ import threading -from typing import Dict, Optional, Literal +from typing import Dict, Optional, Literal, Any from datetime import datetime from utils import traceroot_wrapper as traceroot logger = traceroot.get_logger("main") @@ -12,13 +12,13 @@ AuthStatus = Literal["pending", "authorizing", "success", "failed", "cancelled"] class OAuthState: """Represents the state of an OAuth authorization flow""" - + def __init__(self, provider: str): self.provider = provider self.status: AuthStatus = "pending" self.error: Optional[str] = None self.thread: Optional[threading.Thread] = None - self.result: Optional[any] = None + self.result: Optional[Any] = None self.started_at = datetime.now() self.completed_at: Optional[datetime] = None self._cancel_event = threading.Event() @@ -72,11 +72,11 @@ class OAuthStateManager: return self._states.get(provider) def update_status( - self, - provider: str, - status: AuthStatus, + self, + provider: str, + status: AuthStatus, error: Optional[str] = None, - result: Optional[any] = None + result: Optional[Any] = None ): """Update the status of an authorization flow""" with self._lock: diff --git a/backend/app/utils/toolkit/google_calendar_toolkit.py b/backend/app/utils/toolkit/google_calendar_toolkit.py index 337001fe1..f89d8a175 100644 --- a/backend/app/utils/toolkit/google_calendar_toolkit.py +++ b/backend/app/utils/toolkit/google_calendar_toolkit.py @@ -142,16 +142,14 @@ class GoogleCalendarToolkit(BaseGoogleCalendarToolkit, AbstractToolkit): Returns the status of the authorization """ from google_auth_oauthlib.flow import InstalledAppFlow - from wsgiref import simple_server - import socket from dotenv import load_dotenv - + # Force reload environment variables from default .env file default_env_path = os.path.join(os.path.expanduser("~"), ".eigent", ".env") if os.path.exists(default_env_path): logger.info(f"Reloading environment variables from {default_env_path}") load_dotenv(dotenv_path=default_env_path, override=True) - + # Check if there's an existing authorization and force stop it old_state = oauth_state_manager.get_state("google_calendar") if old_state and old_state.status in ["pending", "authorizing"]: @@ -164,33 +162,32 @@ class GoogleCalendarToolkit(BaseGoogleCalendarToolkit, AbstractToolkit): logger.info("Old server shutdown successfully") except Exception as e: logger.warning(f"Could not shutdown old server: {e}") - + # Create new state for this authorization state = oauth_state_manager.create_state("google_calendar") - + def auth_flow(): - local_server = None try: state.status = "authorizing" oauth_state_manager.update_status("google_calendar", "authorizing") - + # Reload environment variables in this thread from dotenv import load_dotenv default_env_path = os.path.join(os.path.expanduser("~"), ".eigent", ".env") if os.path.exists(default_env_path): load_dotenv(dotenv_path=default_env_path, override=True) - + client_id = os.environ.get("GOOGLE_CLIENT_ID") client_secret = os.environ.get("GOOGLE_CLIENT_SECRET") token_uri = os.environ.get("GOOGLE_TOKEN_URI") or "https://oauth2.googleapis.com/token" - + logger.info(f"Google Calendar auth - client_id present: {bool(client_id)}, client_secret present: {bool(client_secret)}") - + if not client_id or not client_secret: error_msg = "GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET must be set in environment variables" logger.error(error_msg) raise ValueError(error_msg) - + client_config = { "installed": { "client_id": client_id, @@ -200,20 +197,20 @@ class GoogleCalendarToolkit(BaseGoogleCalendarToolkit, AbstractToolkit): "redirect_uris": ["http://localhost"], } } - print("calendar client_config", client_config) + logger.debug(f"calendar client_config initialized with client_id: {client_id[:10]}...") flow = InstalledAppFlow.from_client_config(client_config, SCOPES) - + # Check for cancellation before starting if state.is_cancelled(): logger.info("Authorization cancelled before starting") return - + # This will automatically open browser and wait for user authorization logger.info("=" * 80) logger.info(f"[Thread {threading.current_thread().name}] Starting local server for Google Calendar authorization") logger.info("Browser should open automatically in a moment...") logger.info("=" * 80) - + # Run local server - this will block until authorization completes # Note: Each call uses a random port (port=0), so multiple concurrent attempts won't conflict try: @@ -227,12 +224,12 @@ class GoogleCalendarToolkit(BaseGoogleCalendarToolkit, AbstractToolkit): except Exception as server_error: logger.error(f"Error during run_local_server: {server_error}") raise - + # Check for cancellation after auth if state.is_cancelled(): logger.info("Authorization cancelled after completion") return - + # Save credentials to token file token_path = os.path.join( os.path.expanduser("~"), @@ -241,7 +238,7 @@ class GoogleCalendarToolkit(BaseGoogleCalendarToolkit, AbstractToolkit): "google_calendar", f"google_calendar_token_{api_task_id}.json", ) - + try: os.makedirs(os.path.dirname(token_path), exist_ok=True) with open(token_path, "w") as f: @@ -249,11 +246,11 @@ class GoogleCalendarToolkit(BaseGoogleCalendarToolkit, AbstractToolkit): logger.info(f"Saved Google Calendar credentials to {token_path}") except Exception as e: logger.warning(f"Could not save credentials: {e}") - + # Update state with success oauth_state_manager.update_status("google_calendar", "success", result=creds) logger.info("Google Calendar authorization successful!") - + except Exception as e: if state.is_cancelled(): logger.info("Authorization was cancelled") @@ -265,11 +262,11 @@ class GoogleCalendarToolkit(BaseGoogleCalendarToolkit, AbstractToolkit): finally: # Clean up server reference state.server = None - + # Start authorization in background thread thread = threading.Thread(target=auth_flow, daemon=True, name=f"GoogleCalendar-OAuth-{state.started_at.timestamp()}") state.thread = thread thread.start() - + logger.info("Started background Google Calendar authorization") return "authorizing" \ No newline at end of file diff --git a/src/components/ChatBox/TaskCard.tsx b/src/components/ChatBox/TaskCard.tsx index 03a85f8a1..660d3763a 100644 --- a/src/components/ChatBox/TaskCard.tsx +++ b/src/components/ChatBox/TaskCard.tsx @@ -333,17 +333,18 @@ export function TaskCard({ if (task.agent) { // Switch to the chatStore that owns this task card (for multi-turn conversations) if (chatId && projectStore.activeProjectId) { + const activeProjectId = projectStore.activeProjectId; const activeChatStore = projectStore.getActiveChatStore(); - const currentChatId = activeChatStore ? Object.keys(projectStore.projects[projectStore.activeProjectId].chatStores).find( - id => projectStore.projects[projectStore.activeProjectId].chatStores[id] === activeChatStore + const currentChatId = activeChatStore ? Object.keys(projectStore.projects[activeProjectId].chatStores).find( + id => projectStore.projects[activeProjectId].chatStores[id] === activeChatStore ) : null; - + // Only switch if this is a different chat - if (currentChatId !== chatId) { - projectStore.setActiveChatStore(projectStore.activeProjectId, chatId); + if (currentChatId && currentChatId !== chatId) { + projectStore.setActiveChatStore(activeProjectId, chatId); } } - + // Set the active workspace and agent chatStore.setActiveWorkSpace( chatStore.activeTaskId as string,