From 0d4a082c5a6aa2ea61e63182a41819232cc70219 Mon Sep 17 00:00:00 2001 From: Douglas Date: Wed, 4 Feb 2026 11:50:34 +0000 Subject: [PATCH 1/3] resolve broken SVG import issue --- src/components/AddWorker/ToolSelect.tsx | 1686 ++++++++++++----------- src/pages/Setting/MCPMarket.tsx | 765 +++++----- 2 files changed, 1273 insertions(+), 1178 deletions(-) diff --git a/src/components/AddWorker/ToolSelect.tsx b/src/components/AddWorker/ToolSelect.tsx index 7d6091770..e27bc485e 100644 --- a/src/components/AddWorker/ToolSelect.tsx +++ b/src/components/AddWorker/ToolSelect.tsx @@ -12,806 +12,908 @@ // limitations under the License. // ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. ========= -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"; +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 AnthropicIcon from '@/assets/mcp/Anthropic.svg?url'; +import CamelIcon from '@/assets/mcp/Camel.svg?url'; +import CommunityIcon from '@/assets/mcp/Community.svg?url'; +import OfficialIcon from '@/assets/mcp/Official.svg?url'; 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 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; - - // 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", - }; - - 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([]); - }); - }; - - // 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", - }; - - const svgIcons = import.meta.glob("@/assets/mcp/*.svg", { - eager: true, - query: "?url", - import: "default", - }); - - // 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 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]); - - // 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, - }; - - // 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 }); - } - }; - // 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 - } - - 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"); - - 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", - }; - - if (existing) { - await proxyFetchPut(`/api/configs/${existing.id}`, configPayload); - } else { - await proxyFetchPost("/api/configs", configPayload); - } - - // 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"); - - // 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); - - 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; - - 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); - } - - 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); - } - - // 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; - } - - 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, - })); - - 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); - } - - }; - - 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 iconKey = categoryIconMap[categoryName]; - const iconUrl = iconKey - ? (svgIcons[`/src/assets/mcp/${iconKey}.svg`] as string) - : undefined; - - return iconUrl ? ( - {categoryName} - ) : ( - - ); - }; - - const getGithubRepoName = (homePage?: string) => { - if (!homePage || !homePage.startsWith("https://github.com/")) return null; - const parts = homePage.split("/"); - return parts.length > 4 ? parts[4] : homePage; - }; - - 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"); - }; - - // Effects - useEffect(() => { - fetchData(); - fetchIntegrationsData(); - fetchInstalledMcps(); - }, []); - - useEffect(() => { - if (debounceTimerRef.current) { - clearTimeout(debounceTimerRef.current); - } - - debounceTimerRef.current = setTimeout(() => { - fetchData(keyword); - fetchIntegrationsData(keyword); - }, 500); - - return () => { - if (debounceTimerRef.current) { - clearTimeout(debounceTimerRef.current); - } - }; - }, [keyword]); - - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if ( - containerRef.current && - !containerRef.current.contains(event.target as Node) - ) { - setIsOpen(false); - } - }; - - document.addEventListener("mousedown", handleClickOutside); - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; - }, []); - - // render functions - const renderSelectedItems = () => ( - <> - {(initialSelectedTools || []).map((item: any) => ( - - {item.name || item.mcp_name || item.key || `tool_${item.id}`} -
- removeOption(item)} - /> -
-
- ))} - - ); - - const renderMcpItem = (item: McpItem) => ( -
{ - // check if already installed - const isAlreadyInstalled = - installedIds.includes(item.id) || installed[item.id]; - - 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="cursor-pointer hover:bg-surface-hover-subtle px-3 py-2 flex justify-between" - > -
- {getCategoryIcon(item.category?.name)} -
- {item.name} -
- - e.stopPropagation()} - /> - -
-
- {getGithubRepoName(item.home_page) && ( -
- github - - {getGithubRepoName(item.home_page)} - -
- )} - -
-
- ); - - const renderCustomMcpItem = (item: any) => ( -
{ - addOption(item); - setKeyword(""); - }} - className="cursor-pointer hover:bg-surface-hover-subtle px-3 py-2 flex justify-between" - > -
- {/* {getCategoryIcon(item.category?.name)} */} -
- {item.mcp_name} -
- - e.stopPropagation()} - /> - -
-
- -
-
- ); - return ( -
-
-
- {t("workforce.agent-tool")} - - - -
-
{ - inputRef.current?.focus(); - setIsOpen(true); - }} - className="flex flex-wrap gap-1 justify-start px-[6px] py-1 min-h-[60px] max-h-[120px] overflow-y-auto w-full rounded-lg border border-solid border-input-border-default bg-input-bg-default" - > - {renderSelectedItems()} -