From c1d27448bce4c1d9cf2e32a4f2f2a7528899725b Mon Sep 17 00:00:00 2001 From: Syngnat Date: Thu, 11 Jun 2026 19:47:11 +0800 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(ai):=20=E6=8B=86?= =?UTF-8?q?=E5=88=86=20MCP=20=E5=AE=A2=E6=88=B7=E7=AB=AF=E6=8E=A5=E5=85=A5?= =?UTF-8?q?=E9=9D=A2=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将客户端选择和状态详情拆为独立组件 - 保留本机安装、远程桥接和快速配置展示行为 - 补充选择区和状态区渲染测试 --- .../components/ai/AIMCPClientInstallPanel.tsx | 370 +++--------------- .../ai/AIMCPClientSelectorPanel.test.tsx | 53 +++ .../ai/AIMCPClientSelectorPanel.tsx | 186 +++++++++ .../ai/AIMCPClientStatusPanel.test.tsx | 44 +++ .../components/ai/AIMCPClientStatusPanel.tsx | 170 ++++++++ 5 files changed, 511 insertions(+), 312 deletions(-) create mode 100644 frontend/src/components/ai/AIMCPClientSelectorPanel.test.tsx create mode 100644 frontend/src/components/ai/AIMCPClientSelectorPanel.tsx create mode 100644 frontend/src/components/ai/AIMCPClientStatusPanel.test.tsx create mode 100644 frontend/src/components/ai/AIMCPClientStatusPanel.tsx diff --git a/frontend/src/components/ai/AIMCPClientInstallPanel.tsx b/frontend/src/components/ai/AIMCPClientInstallPanel.tsx index 34bca87..7f50396 100644 --- a/frontend/src/components/ai/AIMCPClientInstallPanel.tsx +++ b/frontend/src/components/ai/AIMCPClientInstallPanel.tsx @@ -1,24 +1,16 @@ import React from 'react'; import { Button } from 'antd'; -import { CheckCircleFilled, CopyOutlined, ReloadOutlined } from '@ant-design/icons'; import type { AIMCPClientInstallStatus } from '../../types'; import { - buildRemoteMCPClientQuickStart, - isMCPClientKey, isRemoteMCPClientStatus, type MCPClientKey, } from '../../utils/mcpClientInstallStatus'; import type { OverlayWorkbenchTheme } from '../../utils/overlayWorkbenchTheme'; -import AIMCPRemoteQuickStartPanel from './AIMCPRemoteQuickStartPanel'; +import AIMCPClientSelectorPanel from './AIMCPClientSelectorPanel'; +import AIMCPClientStatusPanel from './AIMCPClientStatusPanel'; import { getMCPClientDetectionSummary, - getMCPClientInstallStateLabel, - getMCPClientOptionSummary, - getMCPClientStatusSummary, - getMCPClientStatusTone, - getSelectedMCPClientStateLine, - resolveMCPClientCommandName, resolveMCPClientInstallActionLabel, } from './mcpClientInstallPanelState'; @@ -58,329 +50,83 @@ const AIMCPClientInstallPanel: React.FC = ({ onInstall, }) => { const selectedIsRemoteClient = isRemoteMCPClientStatus(selectedStatus); - const remoteQuickStart = selectedIsRemoteClient - ? buildRemoteMCPClientQuickStart(selectedStatus) - : null; return ( -
-
+
-
- 这里是在把 GoNavi MCP 接入 Claude Code / Codex / OpenClaw / Hermans,给外部工具调用,不是给 GoNavi 自己安装插件。 -
-
- Claude Code 和 Codex 会写入本机用户级 MCP 配置;OpenClaw、Hermans 这类云端 Agent 会提供远程接入说明,避免把数据库密码复制到云端。 -
-
- -
-
接入外部客户端
-
- 先选择 1 个目标客户端。本机 CLI 可自动写入或更新配置;远程 Agent 需要通过 MCP 桥接/隧道访问当前 GoNavi,不应保存数据库连接密码。 -
-
-
- {[ - { step: '1', title: '选择目标客户端', detail: '本机 Claude/Codex 可自动安装,OpenClaw/Hermans 走远程接入说明。' }, - { step: '2', title: '写入或复制配置', detail: '自动安装只改用户级 MCP 配置;远程 Agent 复制桥接说明。' }, - { step: '3', title: '重启或配置目标端', detail: '本机 CLI 重启后验证;云端 Agent 配置远程 MCP 地址后验证。' }, - ].map((item) => ( -
-
-
- {item.step} -
-
{item.title}
-
-
{item.detail}
-
- ))} -
- -
-
选择外部客户端
-
- {statuses.map((status) => { - const client = isMCPClientKey(status.client) ? status.client : 'claude-code'; - const remoteClient = isRemoteMCPClientStatus(status); - const active = selectedClient === client; - const tone = getMCPClientStatusTone(status, darkMode); - return ( - - ); - })} -
-
- -
-
+
- 已选客户端状态 + 这里是在把 GoNavi MCP 接入 Claude Code / Codex / OpenClaw / Hermans,给外部工具调用,不是给 GoNavi 自己安装插件。
- 当前目标客户端:{selectedStatus?.displayName || '未选择客户端'} + Claude Code 和 Codex 会写入本机用户级 MCP 配置;OpenClaw、Hermans 这类云端 Agent 会提供远程接入说明,避免把数据库密码复制到云端。
-
-
- {getMCPClientStatusSummary(selectedStatus)} -
- {selectedStatus && ( -
- {getMCPClientStatusTone(selectedStatus, darkMode).label} -
+
+ + + + + +
+
+ {getMCPClientDetectionSummary(selectedStatus)} + {!selectedIsRemoteClient && ( + <> + {' '} + 已经接入当前这份 GoNavi 时,下面的主按钮会自动禁用,避免重复写入。 + )}
-
-
- 当前状态:{getSelectedMCPClientStateLine(selectedStatus)} -
- {selectedIsRemoteClient && ( -
- 远程接入边界:数据库连接信息和密码仍保存在 Windows GoNavi;云端 Agent 默认通过 schema-only MCP 工具读取连接摘要、库表和 DDL,不注册 execute_sql。跨机器接入请使用 GoNavi Streamable HTTP 模式,并配合 token、隧道或反向代理。 -
- )} - {remoteQuickStart && ( - - )} -
- CLI 检测:{selectedIsRemoteClient - ? `远程 Agent 不需要检测本机 ${resolveMCPClientCommandName(selectedStatus)} 命令` - : selectedStatus?.clientDetected - ? `已检测到 ${resolveMCPClientCommandName(selectedStatus)}` - : `未检测到 ${resolveMCPClientCommandName(selectedStatus)},仍可先写配置`} -
- {selectedStatus?.clientPath && ( -
- 命令路径:{selectedStatus.clientPath} -
- )} -
- 检测结果:{selectedStatus?.message || '未检测到接入状态'} -
- {selectedStatus?.configPath && ( -
- 配置文件:{selectedStatus.configPath} -
- )} - {selectedCommandText && ( -
- 启动命令:{selectedCommandText} -
- )} -
- -
- -
-
- {getMCPClientDetectionSummary(selectedStatus)} - {!selectedIsRemoteClient && ( - <> - {' '} - 已经接入当前这份 GoNavi 时,下面的主按钮会自动禁用,避免重复写入。 - - )} -
- -
-
); }; diff --git a/frontend/src/components/ai/AIMCPClientSelectorPanel.test.tsx b/frontend/src/components/ai/AIMCPClientSelectorPanel.test.tsx new file mode 100644 index 0000000..65729a3 --- /dev/null +++ b/frontend/src/components/ai/AIMCPClientSelectorPanel.test.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { renderToStaticMarkup } from 'react-dom/server'; +import { describe, expect, it } from 'vitest'; + +import { buildOverlayWorkbenchTheme } from '../../utils/overlayWorkbenchTheme'; +import AIMCPClientSelectorPanel from './AIMCPClientSelectorPanel'; + +describe('AIMCPClientSelectorPanel', () => { + it('renders local install and remote bridge choices with clear state labels', () => { + const markup = renderToStaticMarkup( + {}} + />, + ); + + expect(markup).toContain('接入外部客户端'); + expect(markup).toContain('选择目标客户端'); + expect(markup).toContain('写入或复制配置'); + expect(markup).toContain('重启或配置目标端'); + expect(markup).toContain('Codex'); + expect(markup).toContain('已接入'); + expect(markup).toContain('OpenClaw'); + expect(markup).toContain('远程桥接'); + expect(markup).toContain('当前已选中,将复制远程接入说明'); + expect(markup).toContain('云端 Agent'); + }); +}); diff --git a/frontend/src/components/ai/AIMCPClientSelectorPanel.tsx b/frontend/src/components/ai/AIMCPClientSelectorPanel.tsx new file mode 100644 index 0000000..57e47d7 --- /dev/null +++ b/frontend/src/components/ai/AIMCPClientSelectorPanel.tsx @@ -0,0 +1,186 @@ +import React from 'react'; +import { CheckCircleFilled } from '@ant-design/icons'; + +import type { AIMCPClientInstallStatus } from '../../types'; +import { + isMCPClientKey, + isRemoteMCPClientStatus, + type MCPClientKey, +} from '../../utils/mcpClientInstallStatus'; +import type { OverlayWorkbenchTheme } from '../../utils/overlayWorkbenchTheme'; +import { + getMCPClientInstallStateLabel, + getMCPClientOptionSummary, + getMCPClientStatusTone, +} from './mcpClientInstallPanelState'; + +interface AIMCPClientSelectorPanelProps { + statuses: AIMCPClientInstallStatus[]; + selectedClient: MCPClientKey; + darkMode: boolean; + overlayTheme: OverlayWorkbenchTheme; + cardBorder: string; + statusLoading: boolean; + onSelectClient: (client: MCPClientKey) => void; +} + +const MCP_CLIENT_INSTALL_STEPS = [ + { step: '1', title: '选择目标客户端', detail: '本机 Claude/Codex 可自动安装,OpenClaw/Hermans 走远程接入说明。' }, + { step: '2', title: '写入或复制配置', detail: '自动安装只改用户级 MCP 配置;远程 Agent 复制桥接说明。' }, + { step: '3', title: '重启或配置目标端', detail: '本机 CLI 重启后验证;云端 Agent 配置远程 MCP 地址后验证。' }, +]; + +const AIMCPClientSelectorPanel: React.FC = ({ + statuses, + selectedClient, + darkMode, + overlayTheme, + cardBorder, + statusLoading, + onSelectClient, +}) => ( + <> +
+
接入外部客户端
+
+ 先选择 1 个目标客户端。本机 CLI 可自动写入或更新配置;远程 Agent 需要通过 MCP 桥接/隧道访问当前 GoNavi,不应保存数据库连接密码。 +
+
+
+ {MCP_CLIENT_INSTALL_STEPS.map((item) => ( +
+
+
+ {item.step} +
+
{item.title}
+
+
{item.detail}
+
+ ))} +
+ +
+
选择外部客户端
+
+ {statuses.map((status) => { + const client = isMCPClientKey(status.client) ? status.client : 'claude-code'; + const remoteClient = isRemoteMCPClientStatus(status); + const active = selectedClient === client; + const tone = getMCPClientStatusTone(status, darkMode); + return ( + + ); + })} +
+
+ +); + +export default AIMCPClientSelectorPanel; diff --git a/frontend/src/components/ai/AIMCPClientStatusPanel.test.tsx b/frontend/src/components/ai/AIMCPClientStatusPanel.test.tsx new file mode 100644 index 0000000..2576bd6 --- /dev/null +++ b/frontend/src/components/ai/AIMCPClientStatusPanel.test.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { renderToStaticMarkup } from 'react-dom/server'; +import { describe, expect, it } from 'vitest'; + +import { buildOverlayWorkbenchTheme } from '../../utils/overlayWorkbenchTheme'; +import AIMCPClientStatusPanel from './AIMCPClientStatusPanel'; + +describe('AIMCPClientStatusPanel', () => { + it('renders selected remote client status, boundary notes, and remote quick-start actions', () => { + const markup = renderToStaticMarkup( + {}} + onCopyConfigPath={() => {}} + onCopyLaunchCommand={() => {}} + />, + ); + + expect(markup).toContain('已选客户端状态'); + expect(markup).toContain('当前目标客户端:Hermans'); + expect(markup).toContain('需要通过远程 MCP 桥接调用当前 GoNavi'); + expect(markup).toContain('远程接入边界'); + expect(markup).toContain('不注册 execute_sql'); + expect(markup).toContain('Hermans 远程 MCP 快速配置'); + expect(markup).toContain('CLI 检测:远程 Agent 不需要检测本机 hermans 命令'); + expect(markup).toContain('刷新状态'); + expect(markup).toContain('复制配置路径'); + expect(markup).toContain('复制启动命令'); + }); +}); diff --git a/frontend/src/components/ai/AIMCPClientStatusPanel.tsx b/frontend/src/components/ai/AIMCPClientStatusPanel.tsx new file mode 100644 index 0000000..399e0e9 --- /dev/null +++ b/frontend/src/components/ai/AIMCPClientStatusPanel.tsx @@ -0,0 +1,170 @@ +import React from 'react'; +import { Button } from 'antd'; +import { CopyOutlined, ReloadOutlined } from '@ant-design/icons'; + +import type { AIMCPClientInstallStatus } from '../../types'; +import { + buildRemoteMCPClientQuickStart, + isRemoteMCPClientStatus, +} from '../../utils/mcpClientInstallStatus'; +import type { OverlayWorkbenchTheme } from '../../utils/overlayWorkbenchTheme'; +import AIMCPRemoteQuickStartPanel from './AIMCPRemoteQuickStartPanel'; +import { + getMCPClientStatusSummary, + getMCPClientStatusTone, + getSelectedMCPClientStateLine, + resolveMCPClientCommandName, +} from './mcpClientInstallPanelState'; + +interface AIMCPClientStatusPanelProps { + selectedStatus?: AIMCPClientInstallStatus; + selectedCommandText: string; + darkMode: boolean; + overlayTheme: OverlayWorkbenchTheme; + cardBorder: string; + statusLoading: boolean; + onRefreshStatus: () => void; + onCopyConfigPath: () => void; + onCopyLaunchCommand: () => void; +} + +const AIMCPClientStatusPanel: React.FC = ({ + selectedStatus, + selectedCommandText, + darkMode, + overlayTheme, + cardBorder, + statusLoading, + onRefreshStatus, + onCopyConfigPath, + onCopyLaunchCommand, +}) => { + const selectedIsRemoteClient = isRemoteMCPClientStatus(selectedStatus); + const remoteQuickStart = selectedIsRemoteClient + ? buildRemoteMCPClientQuickStart(selectedStatus) + : null; + + return ( +
+
+
+ 已选客户端状态 +
+
+ 当前目标客户端:{selectedStatus?.displayName || '未选择客户端'} +
+
+
+ {getMCPClientStatusSummary(selectedStatus)} +
+ {selectedStatus && ( +
+ {getMCPClientStatusTone(selectedStatus, darkMode).label} +
+ )} +
+
+
+ 当前状态:{getSelectedMCPClientStateLine(selectedStatus)} +
+ {selectedIsRemoteClient && ( +
+ 远程接入边界:数据库连接信息和密码仍保存在 Windows GoNavi;云端 Agent 默认通过 schema-only MCP 工具读取连接摘要、库表和 DDL,不注册 execute_sql。跨机器接入请使用 GoNavi Streamable HTTP 模式,并配合 token、隧道或反向代理。 +
+ )} + {remoteQuickStart && ( + + )} +
+ CLI 检测:{selectedIsRemoteClient + ? `远程 Agent 不需要检测本机 ${resolveMCPClientCommandName(selectedStatus)} 命令` + : selectedStatus?.clientDetected + ? `已检测到 ${resolveMCPClientCommandName(selectedStatus)}` + : `未检测到 ${resolveMCPClientCommandName(selectedStatus)},仍可先写配置`} +
+ {selectedStatus?.clientPath && ( +
+ 命令路径:{selectedStatus.clientPath} +
+ )} +
+ 检测结果:{selectedStatus?.message || '未检测到接入状态'} +
+ {selectedStatus?.configPath && ( +
+ 配置文件:{selectedStatus.configPath} +
+ )} + {selectedCommandText && ( +
+ 启动命令:{selectedCommandText} +
+ )} +
+ + + +
+
+ ); +}; + +export default AIMCPClientStatusPanel;