From 65810d918bb599716e35c8ea515a265da909cf2f Mon Sep 17 00:00:00 2001 From: Kadxy <2230318258@qq.com> Date: Thu, 16 Jan 2025 21:30:15 +0800 Subject: [PATCH] feat: improve async operations and UI feedback --- app/components/mcp-market.module.scss | 65 +++++++++ app/components/mcp-market.tsx | 198 ++++++++++++++++---------- app/mcp/actions.ts | 18 ++- 3 files changed, 201 insertions(+), 80 deletions(-) diff --git a/app/components/mcp-market.module.scss b/app/components/mcp-market.module.scss index 46f3c336863..f5c8c0ccae3 100644 --- a/app/components/mcp-market.module.scss +++ b/app/components/mcp-market.module.scss @@ -85,6 +85,50 @@ border-bottom-right-radius: 10px; } + &.loading { + position: relative; + &::after { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient( + 90deg, + transparent, + rgba(255, 255, 255, 0.2), + transparent + ); + background-size: 200% 100%; + animation: loading-pulse 1.5s infinite; + } + } + + .operation-status { + display: inline-flex; + align-items: center; + margin-left: 10px; + padding: 2px 8px; + border-radius: 4px; + font-size: 12px; + background-color: #16a34a; + color: #fff; + animation: pulse 1.5s infinite; + + &[data-status="stopping"] { + background-color: #9ca3af; + } + + &[data-status="starting"] { + background-color: #4ade80; + } + + &[data-status="error"] { + background-color: #f87171; + } + } + .mcp-market-header { display: flex; justify-content: space-between; @@ -585,3 +629,24 @@ } } } + +@keyframes loading-pulse { + 0% { + background-position: 200% 0; + } + 100% { + background-position: -200% 0; + } +} + +@keyframes pulse { + 0% { + opacity: 0.6; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0.6; + } +} diff --git a/app/components/mcp-market.tsx b/app/components/mcp-market.tsx index 9aff190b898..a7cea879de7 100644 --- a/app/components/mcp-market.tsx +++ b/app/components/mcp-market.tsx @@ -52,6 +52,9 @@ export function McpMarketPage() { >({}); const [loadingPresets, setLoadingPresets] = useState(true); const [presetServers, setPresetServers] = useState([]); + const [loadingStates, setLoadingStates] = useState>( + {}, + ); useEffect(() => { const loadPresetServers = async () => { @@ -141,8 +144,12 @@ export function McpMarketPage() { const preset = presetServers.find((s) => s.id === editingServerId); if (!preset || !preset.configSchema || !editingServerId) return; + // 先关闭模态框 + const savingServerId = editingServerId; + setEditingServerId(undefined); + try { - setIsLoading(true); + updateLoadingState(savingServerId, "Updating configuration..."); // 构建服务器配置 const args = [...preset.baseArgs]; const env: Record = {}; @@ -172,25 +179,38 @@ export function McpMarketPage() { ...(Object.keys(env).length > 0 ? { env } : {}), }; + // 检查是否是新增还是编辑 + const isNewServer = !isServerAdded(savingServerId); + + // 如果是编辑现有服务器,保持原有状态 + if (!isNewServer) { + const currentConfig = await getMcpConfigFromFile(); + const currentStatus = currentConfig.mcpServers[savingServerId]?.status; + if (currentStatus) { + serverConfig.status = currentStatus; + } + } + // 更新配置并初始化新服务器 - const newConfig = await addMcpServer(editingServerId, serverConfig); + const newConfig = await addMcpServer(savingServerId, serverConfig); setConfig(newConfig); - // 更新状态 - const status = await getClientStatus(editingServerId); - setClientStatuses((prev) => ({ - ...prev, - [editingServerId]: status, - })); + // 只有新增的服务器才需要获取状态(因为会自动启动) + if (isNewServer) { + const status = await getClientStatus(savingServerId); + setClientStatuses((prev) => ({ + ...prev, + [savingServerId]: status, + })); + } - setEditingServerId(undefined); - showToast("Server configuration saved successfully"); + showToast("Server configuration updated successfully"); } catch (error) { showToast( error instanceof Error ? error.message : "Failed to save configuration", ); } finally { - setIsLoading(false); + updateLoadingState(savingServerId, null); } }; @@ -210,36 +230,24 @@ export function McpMarketPage() { } }; - // 重启所有客户端 - const handleRestartAll = async () => { - try { - setIsLoading(true); - const newConfig = await restartAllClients(); - setConfig(newConfig); - - // 更新所有客户端状态 - const statuses: Record = {}; - for (const clientId of Object.keys(newConfig.mcpServers)) { - statuses[clientId] = await getClientStatus(clientId); + // 更新加载状态的辅助函数 + const updateLoadingState = (id: string, message: string | null) => { + setLoadingStates((prev) => { + if (message === null) { + const { [id]: _, ...rest } = prev; + return rest; } - setClientStatuses(statuses); - - showToast("Successfully restarted all clients"); - } catch (error) { - showToast("Failed to restart clients"); - console.error(error); - } finally { - setIsLoading(false); - } + return { ...prev, [id]: message }; + }); }; - // 添加服务器 + // 修改添加服务器函数 const addServer = async (preset: PresetServer) => { if (!preset.configurable) { try { - setIsLoading(true); - showToast("Creating MCP client..."); - // 如果服务器不需要配置,直接添加 + const serverId = preset.id; + updateLoadingState(serverId, "Creating MCP client..."); + const serverConfig: ServerConfig = { command: preset.command, args: [...preset.baseArgs], @@ -254,7 +262,7 @@ export function McpMarketPage() { [preset.id]: status, })); } finally { - setIsLoading(false); + updateLoadingState(preset.id, null); } } else { // 如果需要配置,打开配置对话框 @@ -263,33 +271,13 @@ export function McpMarketPage() { } }; - // 移除服务器 - // const removeServer = async (id: string) => { - // try { - // setIsLoading(true); - // const newConfig = await removeMcpServer(id); - // setConfig(newConfig); - - // // 移除状态 - // setClientStatuses((prev) => { - // const newStatuses = { ...prev }; - // delete newStatuses[id]; - // return newStatuses; - // }); - // } finally { - // setIsLoading(false); - // } - // }; - - // 暂停服务器 + // 修改暂停服务器函数 const pauseServer = async (id: string) => { try { - setIsLoading(true); - showToast("Stopping server..."); + updateLoadingState(id, "Stopping server..."); const newConfig = await pauseMcpServer(id); setConfig(newConfig); - // 更新状态为暂停 setClientStatuses((prev) => ({ ...prev, [id]: { status: "paused", errorMsg: null }, @@ -299,27 +287,22 @@ export function McpMarketPage() { showToast("Failed to stop server"); console.error(error); } finally { - setIsLoading(false); + updateLoadingState(id, null); } }; - // 恢复服务器 + // 修改恢复服务器函数 const resumeServer = async (id: string) => { try { - setIsLoading(true); - showToast("Starting server..."); + updateLoadingState(id, "Starting server..."); - // 尝试启动服务器 const success = await resumeMcpServer(id); - - // 获取最新状态(这个状态是从 clientsMap 中获取的,反映真实状态) const status = await getClientStatus(id); setClientStatuses((prev) => ({ ...prev, [id]: status, })); - // 根据启动结果显示消息 if (success) { showToast("Server started successfully"); } else { @@ -333,7 +316,29 @@ export function McpMarketPage() { ); console.error(error); } finally { - setIsLoading(false); + updateLoadingState(id, null); + } + }; + + // 修改重启所有客户端函数 + const handleRestartAll = async () => { + try { + updateLoadingState("all", "Restarting all servers..."); + const newConfig = await restartAllClients(); + setConfig(newConfig); + + const statuses: Record = {}; + for (const clientId of Object.keys(newConfig.mcpServers)) { + statuses[clientId] = await getClientStatus(clientId); + } + setClientStatuses(statuses); + + showToast("Successfully restarted all clients"); + } catch (error) { + showToast("Failed to restart clients"); + console.error(error); + } finally { + updateLoadingState("all", null); } }; @@ -445,6 +450,14 @@ export function McpMarketPage() { return statusMap[status.status]; }; + // 获取操作状态的类型 + const getOperationStatusType = (message: string) => { + if (message.toLowerCase().includes("stopping")) return "stopping"; + if (message.toLowerCase().includes("starting")) return "starting"; + if (message.toLowerCase().includes("error")) return "error"; + return "default"; + }; + // 渲染服务器列表 const renderServerList = () => { if (loadingPresets) { @@ -478,29 +491,46 @@ export function McpMarketPage() { .sort((a, b) => { const aStatus = checkServerStatus(a.id).status; const bStatus = checkServerStatus(b.id).status; + const aLoading = loadingStates[a.id]; + const bLoading = loadingStates[b.id]; // 定义状态优先级 const statusPriority: Record = { - error: 0, // 最高优先级 - active: 1, // 运行中 - paused: 2, // 已暂停 - undefined: 3, // 未配置/未找到 + error: 0, // 错误状态最高优先级 + active: 1, // 已启动次之 + starting: 2, // 正在启动 + stopping: 3, // 正在停止 + paused: 4, // 已暂停 + undefined: 5, // 未配置最低优先级 + }; + + // 获取实际状态(包括加载状态) + const getEffectiveStatus = (status: string, loading?: string) => { + if (loading) { + const operationType = getOperationStatusType(loading); + return operationType === "default" ? status : operationType; + } + return status; }; + const aEffectiveStatus = getEffectiveStatus(aStatus, aLoading); + const bEffectiveStatus = getEffectiveStatus(bStatus, bLoading); + // 首先按状态排序 - if (aStatus !== bStatus) { + if (aEffectiveStatus !== bEffectiveStatus) { return ( - (statusPriority[aStatus] || 3) - (statusPriority[bStatus] || 3) + (statusPriority[aEffectiveStatus] ?? 5) - + (statusPriority[bEffectiveStatus] ?? 5) ); } - // 然后按名称排序 + // 状态相同时按名称排序 return a.name.localeCompare(b.name); }) .map((server) => (
@@ -508,7 +538,17 @@ export function McpMarketPage() {
{server.name} - {getServerStatusDisplay(server.id)} + {loadingStates[server.id] && ( + + {loadingStates[server.id]} + + )} + {!loadingStates[server.id] && getServerStatusDisplay(server.id)} {server.repo && (
MCP Market - {isLoading && ( - Loading... + {loadingStates["all"] && ( + + {loadingStates["all"]} + )}
diff --git a/app/mcp/actions.ts b/app/mcp/actions.ts index ba1525be7d4..2248d1327c6 100644 --- a/app/mcp/actions.ts +++ b/app/mcp/actions.ts @@ -98,6 +98,9 @@ async function initializeSingleClient( try { const client = await createClient(clientId, serverConfig); const tools = await listTools(client); + logger.info( + `Supported tools for [${clientId}]: ${JSON.stringify(tools, null, 2)}`, + ); clientsMap.set(clientId, { client, tools, errorMsg: null }); logger.success(`Client [${clientId}] initialized successfully`); } catch (error) { @@ -130,6 +133,13 @@ export async function initializeMcpSystem() { export async function addMcpServer(clientId: string, config: ServerConfig) { try { const currentConfig = await getMcpConfigFromFile(); + const isNewServer = !(clientId in currentConfig.mcpServers); + + // 如果是新服务器,设置默认状态为 active + if (isNewServer && !config.status) { + config.status = "active"; + } + const newConfig = { ...currentConfig, mcpServers: { @@ -138,8 +148,12 @@ export async function addMcpServer(clientId: string, config: ServerConfig) { }, }; await updateMcpConfig(newConfig); - // 只初始化新添加的服务器 - await initializeSingleClient(clientId, config); + + // 只有新服务器或状态为 active 的服务器才初始化 + if (isNewServer || config.status === "active") { + await initializeSingleClient(clientId, config); + } + return newConfig; } catch (error) { logger.error(`Failed to add server [${clientId}]: ${error}`);