From dd98dc4d1c3fa01754e39b444c8e7786bae6cbcd Mon Sep 17 00:00:00 2001 From: Priyanshu Kumar <110410015+cbum-dev@users.noreply.github.com> Date: Mon, 2 Feb 2026 01:12:00 +0530 Subject: [PATCH] added theme in token css and added translations (#980) Co-authored-by: Douglas Co-authored-by: Wendong-Fan Co-authored-by: Wendong-Fan <133094783+Wendong-Fan@users.noreply.github.com> --- src/assets/logo/icon_black.svg | 33 + src/assets/logo/icon_white.svg | 33 + src/assets/logo/logo_black.png | Bin 0 -> 3970 bytes src/assets/logo/logo_white.png | Bin 0 -> 4289 bytes src/components/AddWorker/ToolSelect.tsx | 1571 ++++++------ src/components/AddWorker/index.tsx | 1219 +++++---- .../ChatBox/MessageItem/MarkDown.tsx | 4 +- .../ChatBox/MessageItem/SummaryMarkDown.tsx | 238 +- .../ChatBox/MessageItem/UserMessageCard.tsx | 337 ++- src/components/ChatBox/TaskBox/TaskCard.tsx | 831 +++---- src/components/Dialog/CloseNotice.tsx | 82 +- src/components/Dialog/EndNotice.tsx | 95 +- src/components/Dialog/Privacy.tsx | 332 ++- src/components/Folder/ZoomControls.tsx | 35 +- src/components/Folder/index.tsx | 26 +- src/components/IntegrationList/index.tsx | 2 +- src/components/MenuButton/MenuButton.tsx | 168 +- src/components/SideBar/index.tsx | 101 +- src/components/Terminal/index.tsx | 635 +++-- src/components/ThemeProvider.tsx | 68 +- src/components/TopBar/index.tsx | 27 +- src/components/WorkFlow/MarkDown.tsx | 377 ++- src/components/WorkFlow/node.tsx | 1769 +++++++------ src/components/ui/tooltip.tsx | 60 +- src/i18n/locales/ar/setting.json | 331 +-- src/i18n/locales/de/setting.json | 323 +-- src/i18n/locales/en-us/setting.json | 383 +-- src/i18n/locales/es/setting.json | 324 +-- src/i18n/locales/fr/setting.json | 276 ++- src/i18n/locales/it/setting.json | 322 +-- src/i18n/locales/ja/setting.json | 324 +-- src/i18n/locales/ko/setting.json | 323 +-- src/i18n/locales/ru/setting.json | 324 +-- src/i18n/locales/zh-Hans/setting.json | 381 +-- src/i18n/locales/zh-Hant/setting.json | 323 +-- src/pages/History.tsx | 284 +-- src/pages/Home.tsx | 545 ++-- src/pages/Setting.tsx | 201 +- src/pages/Setting/API.tsx | 218 +- src/pages/Setting/General.tsx | 73 +- src/pages/Setting/MCP.tsx | 1517 ++++++------ src/pages/Setting/MCPMarket.tsx | 760 +++--- src/pages/Setting/Models.tsx | 2195 ++++++++--------- src/pages/Setting/Privacy.tsx | 368 ++- src/style/index.css | 68 + src/style/token.css | 210 +- tailwind.config.js | 1431 ++++++----- 47 files changed, 9729 insertions(+), 9818 deletions(-) create mode 100644 src/assets/logo/icon_black.svg create mode 100644 src/assets/logo/icon_white.svg create mode 100644 src/assets/logo/logo_black.png create mode 100644 src/assets/logo/logo_white.png diff --git a/src/assets/logo/icon_black.svg b/src/assets/logo/icon_black.svg new file mode 100644 index 000000000..752962fb5 --- /dev/null +++ b/src/assets/logo/icon_black.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/logo/icon_white.svg b/src/assets/logo/icon_white.svg new file mode 100644 index 000000000..4cefd0711 --- /dev/null +++ b/src/assets/logo/icon_white.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/logo/logo_black.png b/src/assets/logo/logo_black.png new file mode 100644 index 0000000000000000000000000000000000000000..72106e0e4a965f2959058da81a6b0ae5220de66e GIT binary patch literal 3970 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 Gu>S)NRx_{w literal 0 HcmV?d00001 diff --git a/src/components/AddWorker/ToolSelect.tsx b/src/components/AddWorker/ToolSelect.tsx index 7bf46b055..7d6091770 100644 --- a/src/components/AddWorker/ToolSelect.tsx +++ b/src/components/AddWorker/ToolSelect.tsx @@ -12,905 +12,806 @@ // limitations under the License. // ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. ========= -import { - fetchGet, - fetchPost, - proxyFetchGet, - proxyFetchPost, - proxyFetchPut, -} from '@/api/http'; -import githubIcon from '@/assets/github.svg'; -import IntegrationList from '@/components/IntegrationList'; -import { Badge } from '@/components/ui/badge'; -import { capitalizeFirstLetter, getProxyBaseURL } from '@/lib'; -import { useAuthStore } from '@/store/authStore'; -import { CircleAlert, Store, X } from 'lucide-react'; -import { - forwardRef, - useCallback, - useEffect, - useImperativeHandle, - useRef, - useState, -} from 'react'; -import { useTranslation } from 'react-i18next'; -import { Button } from '../ui/button'; -import { Textarea } from '../ui/textarea'; -import { TooltipSimple } from '../ui/tooltip'; +import React, { + useEffect, + useRef, + useState, + useImperativeHandle, + forwardRef, +} from "react"; +import { Badge } from "@/components/ui/badge"; +import { CircleAlert, Store, X } from "lucide-react"; +import { proxyFetchGet, proxyFetchPost, proxyFetchPut, fetchPost, fetchGet } from "@/api/http"; +import { Input } from "../ui/input"; +import { Textarea } from "../ui/textarea"; +import { Button } from "../ui/button"; +import githubIcon from "@/assets/github.svg"; +import { TooltipSimple } from "../ui/tooltip"; +import IntegrationList from "@/components/IntegrationList"; +import { getProxyBaseURL } from "@/lib"; +import { capitalizeFirstLetter } from "@/lib"; +import { useAuthStore } from "@/store/authStore"; +import { useTranslation } from "react-i18next"; interface McpItem { - id: number; - name: string; - key: string; - description: string; - category?: { name: string }; - home_page?: string; - install_command?: { - env?: { [key: string]: string }; - }; - toolkit?: string; - isLocal?: boolean; + id: number; + name: string; + key: string; + description: string; + category?: { name: string }; + home_page?: string; + install_command?: { + env?: { [key: string]: string }; + }; + toolkit?: string; + isLocal?: boolean; } interface ToolSelectProps { - onShowEnvConfig?: (mcp: McpItem) => void; - onSelectedToolsChange?: (tools: McpItem[]) => void; - initialSelectedTools?: McpItem[]; + onShowEnvConfig?: (mcp: McpItem) => void; + onSelectedToolsChange?: (tools: McpItem[]) => void; + initialSelectedTools?: McpItem[]; } const ToolSelect = forwardRef< - { installMcp: (id: number, env?: any, activeMcp?: any) => Promise }, - ToolSelectProps + { installMcp: (id: number, env?: any, activeMcp?: any) => Promise }, + ToolSelectProps >(({ onShowEnvConfig, onSelectedToolsChange, initialSelectedTools }, ref) => { - const { t } = useTranslation(); - // state management - remove internal selected state, use parent passed initialSelectedTools - const [keyword, setKeyword] = useState(''); - const [mcpList, setMcpList] = useState([]); - const [allMcpList, setAllMcpList] = useState([]); - const [customMcpList, setCustomMcpList] = useState([]); - const [isOpen, setIsOpen] = useState(false); - const [installed, setInstalled] = useState<{ [id: number]: boolean }>({}); - const [installing, setInstalling] = useState<{ [id: number]: boolean }>({}); - const [installedIds, setInstalledIds] = useState([]); - const { email } = useAuthStore(); - // add: integration service list - const [integrations, setIntegrations] = useState([]); - const addOptionRef = useRef<(item: McpItem, isLocal?: boolean) => void>( - null! - ); - const fetchIntegrationsData = useCallback( - (keyword?: string) => { - proxyFetchGet('/api/config/info') - .then((res) => { - if (res && typeof res === 'object' && !res.error) { - const baseURL = getProxyBaseURL(); + const { t } = useTranslation(); + // state management - remove internal selected state, use parent passed initialSelectedTools + const [keyword, setKeyword] = useState(""); + const [mcpList, setMcpList] = useState([]); + const [allMcpList, setAllMcpList] = useState([]); + const [customMcpList, setCustomMcpList] = useState([]); + const [isOpen, setIsOpen] = useState(false); + const [installed, setInstalled] = useState<{ [id: number]: boolean }>({}); + const [installing, setInstalling] = useState<{ [id: number]: boolean }>({}); + const [installedIds, setInstalledIds] = useState([]); + const { email } = useAuthStore(); + // add: integration service list + const [integrations, setIntegrations] = useState([]); + const fetchIntegrationsData = (keyword?: string) => { + proxyFetchGet("/api/config/info").then((res) => { + if (res && typeof res === "object" && !res.error) { + const baseURL = getProxyBaseURL(); - const list = Object.entries(res) - .filter(([key]) => { - if (!keyword) return true; - return key.toLowerCase().includes(keyword.toLowerCase()); - }) - .map(([key, value]: [string, any]) => { - let onInstall = null; + const list = Object.entries(res) + .filter(([key]) => { + if (!keyword) return true; + return key.toLowerCase().includes(keyword.toLowerCase()); + }) + .map(([key, value]: [string, any]) => { + let onInstall = null; - // Special handling for Notion MCP - if (key.toLowerCase() === 'notion') { - onInstall = async () => { - try { - const response = await fetchPost('/install/tool/notion'); - if (response.success) { - // Check if there's a warning (connection failed but installation marked as complete) - if (response.warning) { - console.warn( - 'Notion MCP connection warning:', - response.warning - ); - // Still proceed but log the warning - } - // Save to config to mark as installed - await proxyFetchPost('/api/configs', { - config_group: 'Notion', - config_name: 'MCP_REMOTE_CONFIG_DIR', - config_value: - response.toolkit_name || 'NotionMCPToolkit', - }); - console.log('Notion MCP installed successfully'); - // After successful installation, add to selected tools - const notionItem = { - id: 0, // Use 0 for integration items - key: key, - name: key, - description: - 'Notion workspace integration for reading and managing Notion pages', - toolkit: 'notion_mcp_toolkit', // Add the toolkit name - isLocal: true, - }; - addOptionRef.current?.(notionItem, true); - } else { - console.error( - 'Failed to install Notion MCP:', - response.error || 'Unknown error' - ); - throw new Error( - response.error || 'Failed to install Notion MCP' - ); - } - } catch (error: any) { - console.error( - 'Failed to install Notion MCP:', - error.message - ); - throw error; - } - }; - } else if (key.toLowerCase() === 'google calendar') { - onInstall = async () => { - try { - const response = await fetchPost( - '/install/tool/google_calendar' - ); - if (response.success) { - if (response.warning) { - console.warn( - 'Google Calendar connection warning:', - response.warning - ); - } - try { - const existingConfigs = - await proxyFetchGet('/api/configs'); - const existing = Array.isArray(existingConfigs) - ? existingConfigs.find( - (c: any) => - c.config_group?.toLowerCase() === - 'google calendar' && - c.config_name === 'GOOGLE_REFRESH_TOKEN' - ) - : null; + // Special handling for Notion MCP + if (key.toLowerCase() === 'notion') { + onInstall = async () => { + try { + const response = await fetchPost("/install/tool/notion"); + if (response.success) { + // Check if there's a warning (connection failed but installation marked as complete) + if (response.warning) { + console.warn("Notion MCP connection warning:", response.warning); + // Still proceed but log the warning + } + // Save to config to mark as installed + await proxyFetchPost("/api/configs", { + config_group: "Notion", + config_name: "MCP_REMOTE_CONFIG_DIR", + config_value: response.toolkit_name || "NotionMCPToolkit", + }); + console.log("Notion MCP installed successfully"); + // After successful installation, add to selected tools + const notionItem = { + id: 0, // Use 0 for integration items + key: key, + name: key, + description: "Notion workspace integration for reading and managing Notion pages", + toolkit: "notion_mcp_toolkit", // Add the toolkit name + isLocal: true + }; + addOption(notionItem, true); + } else { + console.error("Failed to install Notion MCP:", response.error || "Unknown error"); + throw new Error(response.error || "Failed to install Notion MCP"); + } + } catch (error: any) { + console.error("Failed to install Notion MCP:", error.message); + throw error; + } + }; + } else if (key.toLowerCase() === 'google calendar') { + onInstall = async () => { + try { + const response = await fetchPost("/install/tool/google_calendar"); + if (response.success) { + if (response.warning) { + console.warn("Google Calendar connection warning:", response.warning); + } + try { + const existingConfigs = await proxyFetchGet("/api/configs"); + const existing = Array.isArray(existingConfigs) + ? existingConfigs.find((c: any) => + c.config_group?.toLowerCase() === "google calendar" && + c.config_name === "GOOGLE_REFRESH_TOKEN" + ) + : null; - const configPayload = { - config_group: 'Google Calendar', - config_name: 'GOOGLE_REFRESH_TOKEN', - config_value: 'exists', - }; + const configPayload = { + config_group: "Google Calendar", + config_name: "GOOGLE_REFRESH_TOKEN", + config_value: "exists", + }; - if (existing) { - await proxyFetchPut( - `/api/configs/${existing.id}`, - configPayload - ); - } else { - await proxyFetchPost('/api/configs', configPayload); - } - } catch (configError) { - console.warn( - 'Failed to persist Google Calendar config', - configError - ); - } - console.log('Google Calendar installed successfully'); - const calendarItem = { - id: 0, // Use 0 for integration items - key: key, - name: key, - description: - 'Google Calendar integration for managing events and schedules', - toolkit: 'google_calendar_toolkit', // Add the toolkit name - isLocal: true, - }; - addOptionRef.current?.(calendarItem, true); - } else if (response.status === 'authorizing') { - console.log( - 'Google Calendar authorization in progress. Please complete in browser.' - ); - if (response.message) { - console.log(response.message); - } - } else { - console.error( - 'Failed to install Google Calendar:', - response.error || 'Unknown error' - ); - throw new Error( - response.error || 'Failed to install Google Calendar' - ); - } - return response; - } catch (error: any) { - if (!error.message?.includes('authorization')) { - console.error( - 'Failed to install Google Calendar:', - error.message - ); - throw error; - } - return null; // Return null on authorization flow errors - } - }; - } else { - onInstall = () => - (window.location.href = `${baseURL}/api/oauth/${key.toLowerCase()}/login`); - } + if (existing) { + await proxyFetchPut(`/api/configs/${existing.id}`, configPayload); + } else { + await proxyFetchPost("/api/configs", configPayload); + } + } catch (configError) { + console.warn("Failed to persist Google Calendar config", configError); + } + console.log("Google Calendar installed successfully"); + const calendarItem = { + id: 0, // Use 0 for integration items + key: key, + name: key, + description: "Google Calendar integration for managing events and schedules", + toolkit: "google_calendar_toolkit", // Add the toolkit name + isLocal: true + }; + addOption(calendarItem, true); + } else if (response.status === "authorizing") { + console.log("Google Calendar authorization in progress. Please complete in browser."); + if (response.message) { + console.log(response.message); + } + } else { + console.error("Failed to install Google Calendar:", response.error || "Unknown error"); + throw new Error(response.error || "Failed to install Google Calendar"); + } + return response; + } catch (error: any) { + if (!error.message?.includes("authorization")) { + console.error("Failed to install Google Calendar:", error.message); + throw error; + } + return null; // Return null on authorization flow errors + } + }; + } else { + onInstall = () => + (window.location.href = `${baseURL}/api/oauth/${key.toLowerCase()}/login`); + } - return { - key: key, - name: key, - env_vars: value.env_vars, - toolkit: value.toolkit, - desc: - value.env_vars && value.env_vars.length > 0 - ? `${t('layout.environmental-variables-required')} ${value.env_vars.join( - ', ' - )}` - : key.toLowerCase() === 'notion' - ? t('layout.notion-workspace-integration') - : key.toLowerCase() === 'google calendar' - ? t('layout.google-calendar-integration') - : '', - onInstall, - }; - }); - setIntegrations(list); - } else { - console.error('Failed to fetch integrations:', res); - setIntegrations([]); - } - }) - .catch((error) => { - console.error('Error fetching integrations:', error); - setIntegrations([]); - }); - }, - [t] - ); + return { + key: key, + name: key, + env_vars: value.env_vars, + toolkit: value.toolkit, + desc: + value.env_vars && value.env_vars.length > 0 + ? `${t("layout.environmental-variables-required")} ${value.env_vars.join( + ", " + )}` + : key.toLowerCase() === 'notion' + ? t("layout.notion-workspace-integration") + : key.toLowerCase() === 'google calendar' + ? t("layout.google-calendar-integration") + : "", + onInstall, + }; + }); + setIntegrations(list); + } else { + console.error("Failed to fetch integrations:", res); + setIntegrations([]); + } + }).catch((error) => { + console.error("Error fetching integrations:", error); + setIntegrations([]); + }); + }; - // Refs - const inputRef = useRef(null); - const debounceTimerRef = useRef(null); - const containerRef = useRef(null); + // Refs + const inputRef = useRef(null); + const debounceTimerRef = useRef(null); + const containerRef = useRef(null); - // constants - const categoryIconMap: Record = { - anthropic: 'Anthropic', - community: 'Community', - official: 'Official', - camel: 'Camel', - }; + // constants + const categoryIconMap: Record = { + anthropic: "Anthropic", + community: "Community", + official: "Official", + camel: "Camel", + }; - const svgIcons = import.meta.glob('@/assets/mcp/*.svg', { - eager: true, - query: '?url', - import: 'default', - }); + const svgIcons = import.meta.glob("@/assets/mcp/*.svg", { + eager: true, + query: "?url", + import: "default", + }); - // data fetching - const fetchData = useCallback((keyword?: string) => { - proxyFetchGet('/api/mcps', { - keyword: keyword || '', - page: 1, - size: 100, - }) - .then((res) => { - // Add defensive check for API errors - if (res && res.items && Array.isArray(res.items)) { - setAllMcpList(res.items); - } else { - console.error('Failed to fetch MCPs:', res); - setAllMcpList([]); - } - }) - .catch((error) => { - console.error('Error fetching MCPs:', error); - setAllMcpList([]); - }); - }, []); + // data fetching + const fetchData = (keyword?: string) => { + proxyFetchGet("/api/mcps", { + keyword: keyword || "", + page: 1, + size: 100, + }).then((res) => { + // Add defensive check for API errors + if (res && res.items && Array.isArray(res.items)) { + setAllMcpList(res.items); + } else { + console.error("Failed to fetch MCPs:", res); + setAllMcpList([]); + } + }).catch((error) => { + console.error("Error fetching MCPs:", error); + setAllMcpList([]); + }); + }; - const fetchInstalledMcps = () => { - proxyFetchGet('/api/mcp/users') - .then((res) => { - let dataList = []; - let ids: number[] = []; - if (Array.isArray(res)) { - ids = res.map((item: any) => item.mcp_id); - dataList = res; - } else if (res && Array.isArray(res.items)) { - ids = res.items.map((item: any) => item.mcp_id); - dataList = res.items; - } - setInstalledIds(ids); + const fetchInstalledMcps = () => { + proxyFetchGet("/api/mcp/users").then((res) => { + let dataList = []; + let ids: number[] = []; + if (Array.isArray(res)) { + ids = res.map((item: any) => item.mcp_id); + dataList = res; + } else if (res && Array.isArray(res.items)) { + ids = res.items.map((item: any) => item.mcp_id); + dataList = res.items; + } + setInstalledIds(ids); - const customMcpList = dataList.filter((item: any) => item.mcp_id === 0); - setCustomMcpList(customMcpList); - }) - .catch((error) => { - console.error('Error fetching installed MCPs:', error); - setInstalledIds([]); - setCustomMcpList([]); - }); - }; + const customMcpList = dataList.filter((item: any) => item.mcp_id === 0); + setCustomMcpList(customMcpList); + }).catch((error) => { + console.error("Error fetching installed MCPs:", error); + setInstalledIds([]); + setCustomMcpList([]); + }); + }; - // only surface installed MCPs from the market list - useEffect(() => { - // Add defensive check and fix logic: should filter when installedIds has items - if (Array.isArray(allMcpList) && installedIds.length > 0) { - const filtered = allMcpList.filter((item) => - installedIds.includes(item.id) - ); - setMcpList(filtered); - } else if (Array.isArray(allMcpList)) { - // If no installed IDs, show empty list instead of all - setMcpList([]); - } - }, [allMcpList, installedIds]); + // only surface installed MCPs from the market list + useEffect(() => { + // Add defensive check and fix logic: should filter when installedIds has items + if (Array.isArray(allMcpList) && installedIds.length > 0) { + const filtered = allMcpList.filter((item) => installedIds.includes(item.id)); + setMcpList(filtered); + } else if (Array.isArray(allMcpList)) { + // If no installed IDs, show empty list instead of all + setMcpList([]); + } + }, [allMcpList, installedIds]); - // public save env/config logic - const saveEnvAndConfig = async ( - provider: string, - 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 : []; + // public save env/config logic + const saveEnvAndConfig = async ( + provider: string, + 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, - }; + const configPayload = { + config_group: capitalizeFirstLetter(provider), + config_name: envVarKey, + config_value: value, + }; - // Check if config already exists - const existingConfig = configs.find( - (c: any) => - c.config_name === envVarKey && - c.config_group?.toLowerCase() === provider.toLowerCase() - ); + // 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 (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 }); - } - }; - // MCP install related - const installMcp = async ( - id: number, - envValue?: { [key: string]: any }, - activeMcp?: any - ) => { - // is exa search or google calendar - if (activeMcp && envValue) { - const env: { [key: string]: string } = {}; - Object.keys(envValue).map((key) => { - env[key] = envValue[key]?.value; - }); - activeMcp.install_command.env = env; + if (window.electronAPI?.envWrite) { + await window.electronAPI.envWrite(email, { key: envVarKey, value }); + } + }; + // MCP install related + const installMcp = async ( + id: number, + envValue?: { [key: string]: any }, + activeMcp?: any + ) => { + // is exa search or google calendar + if (activeMcp && envValue) { + const env: { [key: string]: string } = {}; + Object.keys(envValue).map((key) => { + env[key] = envValue[key]?.value; + }); + activeMcp.install_command.env = env; - // 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 - } + // 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 + } - if (activeMcp.key !== 'Google Calendar') { - const integrationItem = integrations.find( - (item) => item.key === activeMcp.key - ); - addOption( - { - id: activeMcp.id, - key: activeMcp.key, - name: activeMcp.name ?? activeMcp.key, - description: - typeof integrationItem?.desc === 'string' - ? integrationItem.desc - : '', - toolkit: integrationItem?.toolkit, - isLocal: true, - }, - true - ); - return; - } + if (activeMcp.key !== "Google Calendar") { + const integrationItem = integrations.find( + (item) => item.key === activeMcp.key + ); + addOption( + { + id: activeMcp.id, + key: activeMcp.key, + name: activeMcp.name ?? activeMcp.key, + description: + typeof integrationItem?.desc === "string" + ? integrationItem.desc + : "", + toolkit: integrationItem?.toolkit, + isLocal: true, + }, + true + ); + return; + } - // Trigger instantiation for Google Calendar - if (activeMcp.key === 'Google Calendar') { - console.log( - '[ToolSelect installMcp] Starting Google Calendar installation' - ); - try { - const response = await fetchPost('/install/tool/google_calendar'); + // Trigger instantiation for Google Calendar + if (activeMcp.key === "Google Calendar") { + console.log("[ToolSelect installMcp] Starting Google Calendar installation"); + try { + const response = await fetchPost("/install/tool/google_calendar"); - if (response.success) { - console.log('[ToolSelect installMcp] Immediate success'); - // Mark as successfully installed by writing refresh token marker - const existingConfigs = await proxyFetchGet('/api/configs'); - const existing = Array.isArray(existingConfigs) - ? existingConfigs.find( - (c: any) => - c.config_group?.toLowerCase() === 'google calendar' && - c.config_name === 'GOOGLE_REFRESH_TOKEN' - ) - : null; + if (response.success) { + console.log("[ToolSelect installMcp] Immediate success"); + // Mark as successfully installed by writing refresh token marker + const existingConfigs = await proxyFetchGet("/api/configs"); + const existing = Array.isArray(existingConfigs) + ? existingConfigs.find((c: any) => + c.config_group?.toLowerCase() === "google calendar" && + c.config_name === "GOOGLE_REFRESH_TOKEN" + ) + : null; - const configPayload = { - config_group: 'Google Calendar', - config_name: 'GOOGLE_REFRESH_TOKEN', - config_value: 'exists', - }; + const configPayload = { + config_group: "Google Calendar", + config_name: "GOOGLE_REFRESH_TOKEN", + config_value: "exists", + }; - if (existing) { - await proxyFetchPut(`/api/configs/${existing.id}`, configPayload); - } else { - await proxyFetchPost('/api/configs', configPayload); - } + if (existing) { + await proxyFetchPut(`/api/configs/${existing.id}`, configPayload); + } else { + await proxyFetchPost("/api/configs", configPayload); + } - // Refresh integrations to update install status - fetchIntegrationsData(); + // Refresh integrations to update install status + fetchIntegrationsData(); - 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 if (response.status === 'authorizing') { - // Authorization in progress - browser should have opened - console.log( - '[ToolSelect installMcp] Authorization required, starting polling loop' - ); + 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 if (response.status === "authorizing") { + // Authorization in progress - browser should have opened + console.log("[ToolSelect installMcp] Authorization required, starting polling loop"); - // WAIT for OAuth status completion instead of using setInterval - const start = Date.now(); - const timeoutMs = 5 * 60 * 1000; // 5 minutes + // WAIT for OAuth status completion instead of using setInterval + const start = Date.now(); + const timeoutMs = 5 * 60 * 1000; // 5 minutes - while (Date.now() - start < timeoutMs) { - try { - const statusResponse = await fetchGet( - '/oauth/status/google_calendar' - ); - console.log( - '[ToolSelect installMcp] OAuth status:', - statusResponse.status - ); + while (Date.now() - start < timeoutMs) { + try { + const statusResponse = await fetchGet("/oauth/status/google_calendar"); + console.log("[ToolSelect installMcp] OAuth status:", statusResponse.status); - if (statusResponse.status === 'success') { - console.log( - '[ToolSelect installMcp] Authorization completed successfully!' - ); + if (statusResponse.status === "success") { + console.log("[ToolSelect installMcp] Authorization completed successfully!"); - // Try installing again now that authorization is complete - const retryResponse = await fetchPost( - '/install/tool/google_calendar' - ); - if (retryResponse.success) { - // Mark as successfully installed - const existingConfigs = await proxyFetchGet('/api/configs'); - const existing = Array.isArray(existingConfigs) - ? existingConfigs.find( - (c: any) => - c.config_group?.toLowerCase() === - 'google calendar' && - c.config_name === 'GOOGLE_REFRESH_TOKEN' - ) - : null; + // Try installing again now that authorization is complete + const retryResponse = await fetchPost("/install/tool/google_calendar"); + if (retryResponse.success) { + // Mark as successfully installed + const existingConfigs = await proxyFetchGet("/api/configs"); + const existing = Array.isArray(existingConfigs) + ? existingConfigs.find((c: any) => + c.config_group?.toLowerCase() === "google calendar" && + c.config_name === "GOOGLE_REFRESH_TOKEN" + ) + : null; - const configPayload = { - config_group: 'Google Calendar', - config_name: 'GOOGLE_REFRESH_TOKEN', - config_value: 'exists', - }; + const configPayload = { + config_group: "Google Calendar", + config_name: "GOOGLE_REFRESH_TOKEN", + config_value: "exists", + }; - if (existing) { - await proxyFetchPut( - `/api/configs/${existing.id}`, - configPayload - ); - } else { - await proxyFetchPost('/api/configs', configPayload); - } + if (existing) { + await proxyFetchPut(`/api/configs/${existing.id}`, configPayload); + } else { + await proxyFetchPost("/api/configs", configPayload); + } - fetchIntegrationsData(); + fetchIntegrationsData(); - 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); - } - console.log( - '[ToolSelect installMcp] Installation complete, returning' - ); - return; - } else if (statusResponse.status === 'failed') { - console.error( - '[ToolSelect installMcp] Authorization failed:', - statusResponse.error - ); - return; - } else if (statusResponse.status === 'cancelled') { - console.log( - '[ToolSelect installMcp] Authorization cancelled' - ); - return; - } - } catch (error) { - console.error( - '[ToolSelect installMcp] Error polling OAuth status:', - error - ); - } + 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); + } + console.log("[ToolSelect installMcp] Installation complete, returning"); + return; + } else if (statusResponse.status === "failed") { + console.error("[ToolSelect installMcp] Authorization failed:", statusResponse.error); + return; + } else if (statusResponse.status === "cancelled") { + console.log("[ToolSelect installMcp] Authorization cancelled"); + return; + } + } catch (error) { + console.error("[ToolSelect installMcp] Error polling OAuth status:", error); + } - // Wait before next poll - await new Promise((r) => setTimeout(r, 1500)); - } + // Wait before next poll + await new Promise((r) => setTimeout(r, 1500)); + } - console.log('[ToolSelect installMcp] Polling timeout'); - return; - } 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; - } - setInstalling((prev) => ({ ...prev, [id]: true })); - try { - await proxyFetchPost('/api/mcp/install?mcp_id=' + id); - setInstalled((prev) => ({ ...prev, [id]: true })); - const installedMcp = mcpList.find((mcp) => mcp.id === id); - if (window.ipcRenderer && installedMcp) { - const env: { [key: string]: string } = {}; - if (envValue) { - Object.keys(envValue).map((key) => { - env[key] = envValue[key]?.value; - }); - installedMcp.install_command!.env = env; - } + console.log("[ToolSelect installMcp] Polling timeout"); + return; + } 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; + } + setInstalling((prev) => ({ ...prev, [id]: true })); + try { + await proxyFetchPost("/api/mcp/install?mcp_id=" + id); + setInstalled((prev) => ({ ...prev, [id]: true })); + const installedMcp = mcpList.find((mcp) => mcp.id === id); + if (window.ipcRenderer && installedMcp) { + const env: { [key: string]: string } = {}; + if (envValue) { + Object.keys(envValue).map((key) => { + env[key] = envValue[key]?.value; + }); + installedMcp.install_command!.env = env; + } - await window.ipcRenderer.invoke( - 'mcp-install', - installedMcp.key, - installedMcp.install_command - ); - } - // after install successfully, automatically add to selected list - if (installedMcp) { - addOption(installedMcp); - } - } catch (e) { - console.error('Failed to install MCP:', e); - } finally { - setInstalling((prev) => ({ ...prev, [id]: false })); - } - }; + await window.ipcRenderer.invoke( + "mcp-install", + installedMcp.key, + installedMcp.install_command + ); + } + // after install successfully, automatically add to selected list + if (installedMcp) { + addOption(installedMcp); + } + } catch (e) { + console.error("Failed to install MCP:", e); + } finally { + setInstalling((prev) => ({ ...prev, [id]: false })); + } + }; - // expose install method to parent component - useImperativeHandle(ref, () => ({ - installMcp, - })); + // expose install method to parent component + useImperativeHandle(ref, () => ({ + installMcp, + })); - const checkEnv = (id: number) => { - const mcp = mcpList.find((mcp) => mcp.id === id); - if (mcp && Object.keys(mcp?.install_command?.env || {}).length > 0) { - if (onShowEnvConfig) { - onShowEnvConfig(mcp); - } - } else { - installMcp(id); - } - }; + const checkEnv = (id: number) => { + const mcp = mcpList.find((mcp) => mcp.id === id); + if (mcp && Object.keys(mcp?.install_command?.env || {}).length > 0) { + if (onShowEnvConfig) { + onShowEnvConfig(mcp); + } + } else { + installMcp(id); + } + }; - // select management - const addOption = (item: McpItem, isLocal?: boolean) => { - setKeyword(''); - const currentSelected = initialSelectedTools || []; - console.log(currentSelected.find((i) => i.id === item.id)); - if (isLocal) { - if (!currentSelected.find((i) => i.key === item.key)) { - const newSelected = [...currentSelected, { ...item, isLocal }]; - onSelectedToolsChange?.(newSelected); - } - return; - } - if (!currentSelected.find((i) => i.id === item.id)) { - if (!isLocal) isLocal = false; - const newSelected = [...currentSelected, { ...item, isLocal }]; - onSelectedToolsChange?.(newSelected); - } - }; - addOptionRef.current = addOption; + // select management + const addOption = (item: McpItem, isLocal?: boolean) => { + setKeyword(""); + const currentSelected = initialSelectedTools || []; + console.log(currentSelected.find((i) => i.id === item.id)); + if (isLocal) { + if (!currentSelected.find((i) => i.key === item.key)) { + const newSelected = [...currentSelected, { ...item, isLocal }]; + onSelectedToolsChange?.(newSelected); + } + return; + } + if (!currentSelected.find((i) => i.id === item.id)) { + if (!isLocal) isLocal = false; + const newSelected = [...currentSelected, { ...item, isLocal }]; + onSelectedToolsChange?.(newSelected); + } - const removeOption = (item: McpItem) => { - const currentSelected = initialSelectedTools || []; - const newSelected = currentSelected.filter((i) => i.id !== item.id); - onSelectedToolsChange?.(newSelected); - }; + }; - // tool functions - const getCategoryIcon = (categoryName?: string) => { - if (!categoryName) return ; + const removeOption = (item: McpItem) => { + const currentSelected = initialSelectedTools || []; + const newSelected = currentSelected.filter((i) => i.id !== item.id); + onSelectedToolsChange?.(newSelected); + }; - const iconKey = categoryIconMap[categoryName]; - const iconUrl = iconKey - ? (svgIcons[`/src/assets/mcp/${iconKey}.svg`] as string) - : undefined; + // tool functions + const getCategoryIcon = (categoryName?: string) => { + if (!categoryName) return ; - return iconUrl ? ( - {categoryName} - ) : ( - - ); - }; + const iconKey = categoryIconMap[categoryName]; + const iconUrl = iconKey + ? (svgIcons[`/src/assets/mcp/${iconKey}.svg`] as string) + : undefined; - const getGithubRepoName = (homePage?: string) => { - if (!homePage || !homePage.startsWith('https://github.com/')) return null; - const parts = homePage.split('/'); - return parts.length > 4 ? parts[4] : homePage; - }; + return iconUrl ? ( + {categoryName} + ) : ( + + ); + }; - const getInstallButtonText = (itemId: number) => { - if (installedIds.includes(itemId)) return t('layout.installed'); - if (installing[itemId]) return t('layout.installing'); - if (installed[itemId]) return t('layout.installed'); - return t('layout.install'); - }; + const getGithubRepoName = (homePage?: string) => { + if (!homePage || !homePage.startsWith("https://github.com/")) return null; + const parts = homePage.split("/"); + return parts.length > 4 ? parts[4] : homePage; + }; - // Effects - useEffect(() => { - fetchData(); - fetchIntegrationsData(); - fetchInstalledMcps(); - }, [fetchData, fetchIntegrationsData]); + const getInstallButtonText = (itemId: number) => { + if (installedIds.includes(itemId)) return t("layout.installed"); + if (installing[itemId]) return t("layout.installing"); + if (installed[itemId]) return t("layout.installed"); + return t("layout.install"); + }; - useEffect(() => { - if (debounceTimerRef.current) { - clearTimeout(debounceTimerRef.current); - } + // Effects + useEffect(() => { + fetchData(); + fetchIntegrationsData(); + fetchInstalledMcps(); + }, []); - debounceTimerRef.current = setTimeout(() => { - fetchData(keyword); - fetchIntegrationsData(keyword); - }, 500); + useEffect(() => { + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } - return () => { - if (debounceTimerRef.current) { - clearTimeout(debounceTimerRef.current); - } - }; - }, [keyword, fetchIntegrationsData, fetchData]); + debounceTimerRef.current = setTimeout(() => { + fetchData(keyword); + fetchIntegrationsData(keyword); + }, 500); - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if ( - containerRef.current && - !containerRef.current.contains(event.target as Node) - ) { - setIsOpen(false); - } - }; + return () => { + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current); + } + }; + }, [keyword]); - document.addEventListener('mousedown', handleClickOutside); - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, []); + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + containerRef.current && + !containerRef.current.contains(event.target as Node) + ) { + setIsOpen(false); + } + }; - // render functions - const renderSelectedItems = () => ( - <> - {(initialSelectedTools || []).map((item: any) => ( - - {item.name || item.mcp_name || item.key || `tool_${item.id}`} -
- removeOption(item)} - /> -
-
- ))} - - ); + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, []); - const renderMcpItem = (item: McpItem) => ( -
{ - // check if already installed - const isAlreadyInstalled = - installedIds.includes(item.id) || installed[item.id]; + // render functions + const renderSelectedItems = () => ( + <> + {(initialSelectedTools || []).map((item: any) => ( + + {item.name || item.mcp_name || item.key || `tool_${item.id}`} +
+ removeOption(item)} + /> +
+
+ ))} + + ); - if (isAlreadyInstalled) { - // if already installed, add to selected list directly - addOption(item); - setKeyword(''); - } else { - // if not installed, first check environment configuration, then install and add to selected list - checkEnv(item.id); - } - }} - className="flex cursor-pointer justify-between px-3 py-2 hover:bg-gray-100" - > -
- {getCategoryIcon(item.category?.name)} -
- {item.name} -
- - e.stopPropagation()} - /> - -
-
- {getGithubRepoName(item.home_page) && ( -
- github - - {getGithubRepoName(item.home_page)} - -
- )} - -
-
- ); + const renderMcpItem = (item: McpItem) => ( +
{ + // check if already installed + const isAlreadyInstalled = + installedIds.includes(item.id) || installed[item.id]; - const renderCustomMcpItem = (item: any) => ( -
{ - addOption(item); - setKeyword(''); - }} - className="flex cursor-pointer justify-between px-3 py-2 hover:bg-gray-100" - > -
- {/* {getCategoryIcon(item.category?.name)} */} -
- {item.mcp_name} -
- - e.stopPropagation()} - /> - -
-
- -
-
- ); - return ( -
-
-
- {t('workforce.agent-tool')} - - - -
-
{ - inputRef.current?.focus(); - setIsOpen(true); - }} - className="flex max-h-[120px] min-h-[60px] w-full flex-wrap justify-start gap-1 overflow-y-auto rounded-lg border border-solid border-input-border-default bg-input-bg-default px-[6px] py-1" - > - {renderSelectedItems()} -