From 77df69b585d563afa4263b5d3f9f96e9d77cff01 Mon Sep 17 00:00:00 2001 From: Sun Tao <2605127667@qq.com> Date: Sun, 12 Oct 2025 14:25:08 +0800 Subject: [PATCH] update --- .../utils/toolkit/google_calendar_toolkit.py | 97 ++++++++++++++++++- src/components/AddWorker/IntegrationList.tsx | 90 +++++++++++------ src/components/AddWorker/ToolSelect.tsx | 87 +++++++++++------ src/pages/Setting/MCP.tsx | 16 +-- .../Setting/components/IntegrationList.tsx | 68 +++++++++---- 5 files changed, 263 insertions(+), 95 deletions(-) diff --git a/backend/app/utils/toolkit/google_calendar_toolkit.py b/backend/app/utils/toolkit/google_calendar_toolkit.py index 34b07bd3..08aaeb16 100644 --- a/backend/app/utils/toolkit/google_calendar_toolkit.py +++ b/backend/app/utils/toolkit/google_calendar_toolkit.py @@ -1,16 +1,29 @@ from typing import Any, Dict, List +import os + from app.component.environment import env from app.service.task import Agents from app.utils.listen.toolkit_listen import listen_toolkit from app.utils.toolkit.abstract_toolkit import AbstractToolkit from camel.toolkits import GoogleCalendarToolkit as BaseGoogleCalendarToolkit +SCOPES = ['https://www.googleapis.com/auth/calendar'] class GoogleCalendarToolkit(BaseGoogleCalendarToolkit, AbstractToolkit): agent_name: str = Agents.social_medium_agent def __init__(self, api_task_id: str, timeout: float | None = None): self.api_task_id = api_task_id + self._token_path = ( + os.environ.get("GOOGLE_CALENDAR_TOKEN_PATH") + or os.path.join( + os.path.expanduser("~"), + ".eigent", + "tokens", + "google_calendar", + f"google_calendar_token_{api_task_id}.json", + ) + ) super().__init__(timeout) @listen_toolkit(BaseGoogleCalendarToolkit.create_event) @@ -24,10 +37,14 @@ class GoogleCalendarToolkit(BaseGoogleCalendarToolkit, AbstractToolkit): attendees_email: List[str] | None = None, timezone: str = "UTC", ) -> Dict[str, Any]: - return super().create_event(event_title, start_time, end_time, description, location, attendees_email, timezone) + return super().create_event( + event_title, start_time, end_time, description, location, attendees_email, timezone + ) @listen_toolkit(BaseGoogleCalendarToolkit.get_events) - def get_events(self, max_results: int = 10, time_min: str | None = None) -> List[Dict[str, Any]] | Dict[str, Any]: + def get_events( + self, max_results: int = 10, time_min: str | None = None + ) -> List[Dict[str, Any]] | Dict[str, Any]: return super().get_events(max_results, time_min) @listen_toolkit(BaseGoogleCalendarToolkit.update_event) @@ -41,7 +58,9 @@ class GoogleCalendarToolkit(BaseGoogleCalendarToolkit, AbstractToolkit): location: str | None = None, attendees_email: List[str] | None = None, ) -> Dict[str, Any]: - return super().update_event(event_id, event_title, start_time, end_time, description, location, attendees_email) + return super().update_event( + event_id, event_title, start_time, end_time, description, location, attendees_email + ) @listen_toolkit(BaseGoogleCalendarToolkit.delete_event) def delete_event(self, event_id: str) -> str: @@ -57,3 +76,75 @@ class GoogleCalendarToolkit(BaseGoogleCalendarToolkit, AbstractToolkit): return cls(api_task_id).get_tools() else: return [] + + def _get_calendar_service(self): + from googleapiclient.discovery import build + from google.auth.transport.requests import Request + + creds = self._authenticate() + + if creds and creds.expired and creds.refresh_token: + creds.refresh(Request()) + try: + os.makedirs(os.path.dirname(self._token_path), exist_ok=True) + with open(self._token_path, "w") as f: + f.write(creds.to_json()) + except Exception: + pass + + return build("calendar", "v3", credentials=creds) + + def _authenticate(self): + from google.oauth2.credentials import Credentials + from google_auth_oauthlib.flow import InstalledAppFlow + from google.auth.transport.requests import Request + + client_id = os.environ.get("GOOGLE_CLIENT_ID") + client_secret = os.environ.get("GOOGLE_CLIENT_SECRET") + refresh_token = os.environ.get("GOOGLE_REFRESH_TOKEN") + token_uri = os.environ.get("GOOGLE_TOKEN_URI", "https://oauth2.googleapis.com/token") + + creds = None + + try: + if os.path.exists(self._token_path): + creds = Credentials.from_authorized_user_file(self._token_path, SCOPES) + except Exception: + creds = None + + if not creds and refresh_token: + creds = Credentials( + None, + refresh_token=refresh_token, + token_uri=token_uri, + client_id=client_id, + client_secret=client_secret, + scopes=SCOPES, + ) + + if not creds: + client_config = { + "installed": { + "client_id": client_id, + "client_secret": client_secret, + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": token_uri, + "redirect_uris": ["http://localhost"], + } + } + flow = InstalledAppFlow.from_client_config(client_config, SCOPES) + creds = flow.run_local_server(port=0) + + + if creds and creds.expired and creds.refresh_token: + creds.refresh(Request()) + + + try: + os.makedirs(os.path.dirname(self._token_path), exist_ok=True) + with open(self._token_path, "w") as f: + f.write(creds.to_json()) + except Exception: + pass + + return creds \ No newline at end of file diff --git a/src/components/AddWorker/IntegrationList.tsx b/src/components/AddWorker/IntegrationList.tsx index 8a2dc55d..7345febb 100644 --- a/src/components/AddWorker/IntegrationList.tsx +++ b/src/components/AddWorker/IntegrationList.tsx @@ -1,7 +1,7 @@ import { Button } from "@/components/ui/button"; import { TooltipSimple } from "@/components/ui/tooltip"; import { CircleAlert } from "lucide-react"; -import { proxyFetchGet, proxyFetchPost, proxyFetchDelete } from "@/api/http"; +import { proxyFetchGet, proxyFetchPost, proxyFetchPut, proxyFetchDelete } from "@/api/http"; import React, { useState, useCallback, useEffect, useRef } from "react"; import ellipseIcon from "@/assets/mcp/Ellipse-25.svg"; @@ -104,7 +104,21 @@ export default function IntegrationList({ config_name: envVarKey, config_value: value, }; - await proxyFetchPost("/api/configs", configPayload); + + // Check if config already exists + const existingConfig = configs.find( + (c: any) => c.config_name === envVarKey && + c.config_group?.toLowerCase() === provider.toLowerCase() + ); + + if (existingConfig) { + // Update existing config + await proxyFetchPut(`/api/configs/${existingConfig.id}`, configPayload); + } else { + // Create new config + await proxyFetchPost("/api/configs", configPayload); + } + if (window.electronAPI?.envWrite) { await window.electronAPI.envWrite(email, { key: envVarKey, value }); } @@ -223,39 +237,55 @@ export default function IntegrationList({ return; } - if (item.key === "Google Calendar") { - let mcp = { - name: "Google Calendar", - key: "Google Calendar", - install_command: { - env: {} as any, - }, - id: 14, - }; - item.env_vars.map((key) => { - mcp.install_command.env[key] = ""; - }); - onShowEnvConfig?.(mcp); - return; - } - if (installed[item.key]) return; - await item.onInstall(); - // refresh configs after install to update installed state indicator - await fetchInstalled(); + if (item.key === "Google Calendar") { + let mcp = { + name: "Google Calendar", + key: "Google Calendar", + install_command: { + env: {} as any, + }, + id: 14, + }; + item.env_vars.map((key) => { + mcp.install_command.env[key] = ""; + }); + onShowEnvConfig?.(mcp); + return; + } + if (installed[item.key]) return; + await item.onInstall(); + // refresh configs after install to update installed state indicator + await fetchInstalled(); }, [installed] ); - const onConnect = (mcp: any) => { - console.log(mcp); - Object.keys(mcp.install_command.env).map(async (key) => { - await saveEnvAndConfig(mcp.key, key, mcp.install_command.env[key]); - }); + const onConnect = async (mcp: any) => { + // Refresh configs first to get latest state + await fetchInstalled(); + + // Save all environment variables + await Promise.all( + Object.keys(mcp.install_command.env).map(async (key) => { + return saveEnvAndConfig(mcp.key, key, mcp.install_command.env[key]); + }) + ); - fetchInstalled(); - addOption(mcp, true); - onClose(); - }; + // After saving env vars, trigger installation/instantiation for Google Calendar + if (mcp.key === "Google Calendar") { + const calendarItem = items.find(item => item.key === "Google Calendar"); + if (calendarItem && calendarItem.onInstall) { + await calendarItem.onInstall(); + } + } + + await fetchInstalled(); + // Don't call addOption here for Google Calendar, as it's already called in onInstall + if (mcp.key !== "Google Calendar") { + addOption(mcp, true); + } + onClose(); + }; const onClose = () => { setShowEnvConfig(false); setActiveMcp(null); diff --git a/src/components/AddWorker/ToolSelect.tsx b/src/components/AddWorker/ToolSelect.tsx index 8a2d9865..7bd4ba99 100644 --- a/src/components/AddWorker/ToolSelect.tsx +++ b/src/components/AddWorker/ToolSelect.tsx @@ -7,7 +7,7 @@ import React, { } from "react"; import { Badge } from "@/components/ui/badge"; import { CircleAlert, Store, X } from "lucide-react"; -import { proxyFetchGet, proxyFetchPost, fetchPost } from "@/api/http"; +import { proxyFetchGet, proxyFetchPost, proxyFetchPut, fetchPost } from "@/api/http"; import { Input } from "../ui/input"; import { Button } from "../ui/button"; import githubIcon from "@/assets/github.svg"; @@ -210,12 +210,30 @@ const ToolSelect = forwardRef< envVarKey: string, value: string ) => { + // First fetch current configs to check for existing ones + const configsRes = await proxyFetchGet("/api/configs"); + const configs = Array.isArray(configsRes) ? configsRes : []; + const configPayload = { config_group: capitalizeFirstLetter(provider), config_name: envVarKey, config_value: value, }; - await proxyFetchPost("/api/configs", configPayload); + + // Check if config already exists + const existingConfig = configs.find( + (c: any) => c.config_name === envVarKey && + c.config_group?.toLowerCase() === provider.toLowerCase() + ); + + if (existingConfig) { + // Update existing config + await proxyFetchPut(`/api/configs/${existingConfig.id}`, configPayload); + } else { + // Create new config + await proxyFetchPost("/api/configs", configPayload); + } + if (window.electronAPI?.envWrite) { await window.electronAPI.envWrite(email, { key: envVarKey, value }); } @@ -233,36 +251,49 @@ const ToolSelect = forwardRef< env[key] = envValue[key]?.value; }); activeMcp.install_command.env = env; - Object.keys(activeMcp.install_command.env).map(async (key) => { - await saveEnvAndConfig( - activeMcp.key, - key, - activeMcp.install_command.env[key] - ); - }); - // Add to selected tools after saving config + // Save all env vars and wait for completion + console.log("[installMcp] Saving env vars for", activeMcp.key); + try { + await Promise.all( + Object.keys(activeMcp.install_command.env).map(async (key) => { + console.log("[installMcp] Saving", key, "=", activeMcp.install_command.env[key]); + return saveEnvAndConfig( + activeMcp.key, + key, + activeMcp.install_command.env[key] + ); + }) + ); + console.log("[installMcp] All env vars saved successfully"); + } catch (error) { + console.error("[installMcp] Failed to save env vars:", error); + // Continue anyway to trigger installation + } + + // Trigger instantiation for Google Calendar if (activeMcp.key === "Google Calendar") { - const calendarItem = { - id: activeMcp.id, - key: activeMcp.key, - name: activeMcp.name, - description: "Google Calendar integration for managing events and schedules", - toolkit: "google_calendar_toolkit", - isLocal: true - }; - addOption(calendarItem, true); + try { + const response = await fetchPost("/install/tool/google_calendar"); + + if (response.success) { + const selectedItem = { + id: activeMcp.id, + key: activeMcp.key, + name: activeMcp.name, + description: "Google Calendar integration for managing events and schedules", + toolkit: "google_calendar_toolkit", + isLocal: true + }; + addOption(selectedItem, true); + } else { + console.error("Failed to install Google Calendar:", response.error || "Unknown error"); + } + } catch (error: any) { + console.error("Failed to install Google Calendar:", error.message); + } } return; - // async function fetchInstalled() { - // try { - // const configsRes = await proxyFetchGet("/api/configs"); - // setConfigs(Array.isArray(configsRes) ? configsRes : []); - // } catch (e) { - // setConfigs([]); - // } - // } - // fetchInstalled(); } setInstalling((prev) => ({ ...prev, [id]: true })); try { diff --git a/src/pages/Setting/MCP.tsx b/src/pages/Setting/MCP.tsx index 4f7cee6b..c324732e 100644 --- a/src/pages/Setting/MCP.tsx +++ b/src/pages/Setting/MCP.tsx @@ -109,7 +109,7 @@ export default function SettingMCP() { proxyFetchGet("/api/config/info").then((res) => { if (res && typeof res === "object") { const baseURL = getProxyBaseURL(); - const list = Object.entries(res).map(([key, value]: [string, any]) => { + const list = Object.entries(res).map(([key, value]: [string, any]) => { let onInstall = null; // Special handling for Notion MCP @@ -136,18 +136,12 @@ export default function SettingMCP() { toast.error(error.message || "Failed to install Notion MCP"); } }; - } else if (key.toLowerCase() === 'google calendar') { + } else if (key.toLowerCase() === 'google calendar') { onInstall = async () => { try { const response = await fetchPost("/install/tool/google_calendar"); if (response.success) { - toast.success("Google Calendar installed successfully"); - // Save to config to mark as installed - await proxyFetchPost("/api/configs", { - config_group: "Google Calendar", - config_name: "GOOGLE_CLIENT_ID", - config_value: response.toolkit_name || "GoogleCalendarToolkit", - }); + toast.success("Google Calendar installed successfully"); // Refresh the integrations list to show the installed state fetchList(); // Force refresh IntegrationList component @@ -181,10 +175,6 @@ export default function SettingMCP() { onInstall, }; }); - console.log("API response:", res); - console.log("Generated list:", list); - console.log("Essential integrations:", essentialIntegrations); - setIntegrations( list.filter( (item) => !essentialIntegrations.find((i) => i.key === item.key) diff --git a/src/pages/Setting/components/IntegrationList.tsx b/src/pages/Setting/components/IntegrationList.tsx index 20fb328a..43e646ad 100644 --- a/src/pages/Setting/components/IntegrationList.tsx +++ b/src/pages/Setting/components/IntegrationList.tsx @@ -8,6 +8,7 @@ import { CircleAlert } from "lucide-react"; import { proxyFetchGet, proxyFetchPost, + proxyFetchPut, proxyFetchDelete, } from "@/api/http"; @@ -102,7 +103,21 @@ export default function IntegrationList({ config_name: envVarKey, config_value: value, }; - await proxyFetchPost("/api/configs", configPayload); + + // Check if config already exists + const existingConfig = configs.find( + (c: any) => c.config_name === envVarKey && + c.config_group?.toLowerCase() === provider.toLowerCase() + ); + + if (existingConfig) { + // Update existing config + await proxyFetchPut(`/api/configs/${existingConfig.id}`, configPayload); + } else { + // Create new config + await proxyFetchPost("/api/configs", configPayload); + } + if (window.electronAPI?.envWrite) { await window.electronAPI.envWrite(email, { key: envVarKey, value }); } @@ -228,38 +243,49 @@ export default function IntegrationList({ return; } - if (item.key === "Google Calendar") { - let mcp = { - name: "Google Calendar", - key: "Google Calendar", - install_command: { - env: {} as any, - }, - id: 14, - }; - item.env_vars.map((key) => { - mcp.install_command.env[key] = ""; - }); - setActiveMcp(mcp); - setShowEnvConfig(true); - return; - } + if (item.key === "Google Calendar") { + let mcp = { + name: "Google Calendar", + key: "Google Calendar", + install_command: { + env: {} as any, + }, + id: 14, + }; + item.env_vars.map((key) => { + mcp.install_command.env[key] = ""; + }); + setActiveMcp(mcp); + setShowEnvConfig(true); + return; + } - if (installed[item.key]) return; - await item.onInstall(); + if (installed[item.key]) return; + await item.onInstall(); }, [installed] ); const onConnect = async (mcp: any) => { - console.log(mcp); + // Refresh configs first to get latest state + await fetchInstalled(); + + // Save all environment variables await Promise.all( Object.keys(mcp.install_command.env).map((key) => { return saveEnvAndConfig(mcp.key, key, mcp.install_command.env[key]); }) ); - fetchInstalled(); + // After saving env vars, trigger installation/instantiation for Google Calendar + if (mcp.key === "Google Calendar") { + const calendarItem = items.find(item => item.key === "Google Calendar"); + if (calendarItem && calendarItem.onInstall) { + await calendarItem.onInstall(); + } + } + + await fetchInstalled(); onClose(); }; const onClose = () => {