import { useCallback, useMemo, useState } from 'react'; import type { AIMCPClientInstallStatus } from '../../types'; import { buildRemoteMCPClientGuide, buildRemoteMCPClientQuickStart, EMPTY_MCP_CLIENT_STATUSES, formatMCPLaunchCommand, isRemoteMCPClientStatus, normalizeMCPClientStatuses, pickPreferredMCPClient, type MCPClientKey, } from '../../utils/mcpClientInstallStatus'; interface MCPClientInstallResult { success?: boolean; client?: string; message?: string; configPath?: string; command?: string; args?: string[]; } interface MCPClientMessageApi { error: (content: string) => unknown; success: (content: string) => unknown; warning: (content: string) => unknown; } interface AIMCPClientInstallerService { AIGetMCPClientInstallStatuses?: () => Promise; AIInstallClaudeCodeMCP?: () => Promise; AIInstallCodexMCP?: () => Promise; } interface UseAIMCPClientInstallerOptions { copyTextToClipboard: (text: string, successMessage: string) => Promise; messageApi: MCPClientMessageApi; onAfterInstall?: () => void; onBeforeInstall?: () => void; onConfigChanged?: () => void; resolveAIService: () => Promise; } export const useAIMCPClientInstaller = ({ copyTextToClipboard, messageApi, onAfterInstall, onBeforeInstall, onConfigChanged, resolveAIService, }: UseAIMCPClientInstallerOptions) => { const [mcpClientStatuses, setMCPClientStatuses] = useState(EMPTY_MCP_CLIENT_STATUSES); const [selectedMCPClient, setSelectedMCPClient] = useState('claude-code'); const [mcpClientSelectionTouched, setMCPClientSelectionTouched] = useState(false); const [mcpClientStatusLoading, setMCPClientStatusLoading] = useState(false); const selectedMCPClientStatus = useMemo( () => mcpClientStatuses.find((item) => item.client === selectedMCPClient) || mcpClientStatuses[0], [mcpClientStatuses, selectedMCPClient], ); const selectedMCPClientCommandText = useMemo( () => isRemoteMCPClientStatus(selectedMCPClientStatus) ? buildRemoteMCPClientQuickStart(selectedMCPClientStatus).launchCommand : formatMCPLaunchCommand(selectedMCPClientStatus), [selectedMCPClientStatus], ); const syncMCPClientStatuses = useCallback((items?: AIMCPClientInstallStatus[]) => { const normalizedStatuses = normalizeMCPClientStatuses(items); setMCPClientStatuses(normalizedStatuses); setSelectedMCPClient((prev) => pickPreferredMCPClient(normalizedStatuses, mcpClientSelectionTouched ? prev : undefined)); }, [mcpClientSelectionTouched]); const handleSelectMCPClient = useCallback((client: MCPClientKey) => { setMCPClientSelectionTouched(true); setSelectedMCPClient(client); }, []); const resetMCPClientSelectionTouched = useCallback(() => { setMCPClientSelectionTouched(false); }, []); const loadMCPClientStatuses = useCallback(async (options?: { silent?: boolean }) => { const silent = options?.silent === true; if (!silent) { setMCPClientStatusLoading(true); } try { const service = await resolveAIService(); if (typeof service?.AIGetMCPClientInstallStatuses !== 'function') { return; } const result = await service.AIGetMCPClientInstallStatuses(); if (Array.isArray(result)) { syncMCPClientStatuses(result); } } catch (error: any) { if (silent) { console.warn('[AI] refresh mcp client statuses failed', error); } else { void messageApi.error(error?.message || '刷新客户端安装状态失败'); } } finally { if (!silent) { setMCPClientStatusLoading(false); } } }, [messageApi, resolveAIService, syncMCPClientStatuses]); const handleInstallSelectedMCPClient = useCallback(async () => { const remoteClient = isRemoteMCPClientStatus(selectedMCPClientStatus); const targetClient = selectedMCPClientStatus?.client === 'codex' ? 'codex' : 'claude-code'; const targetLabel = selectedMCPClientStatus?.displayName || (targetClient === 'codex' ? 'Codex' : 'Claude Code'); if (remoteClient) { try { onBeforeInstall?.(); setMCPClientSelectionTouched(true); await copyTextToClipboard( buildRemoteMCPClientGuide(selectedMCPClientStatus), `${targetLabel} 远程接入说明已复制`, ); } catch (error: any) { void messageApi.error(error?.message || `复制 ${targetLabel} 远程接入说明失败`); } finally { onAfterInstall?.(); } return; } if (selectedMCPClientStatus?.matchesCurrent) { void messageApi.success(`${targetLabel} 已接入当前 GoNavi MCP,无需重复写入`); return; } try { onBeforeInstall?.(); setMCPClientSelectionTouched(true); const service = await resolveAIService(); let result: MCPClientInstallResult; if (targetClient === 'codex') { if (typeof service?.AIInstallCodexMCP !== 'function') { throw new Error('当前版本暂不支持自动安装 Codex MCP'); } result = await service.AIInstallCodexMCP(); } else { if (typeof service?.AIInstallClaudeCodeMCP !== 'function') { throw new Error('当前版本暂不支持自动安装 Claude Code MCP'); } result = await service.AIInstallClaudeCodeMCP(); } await loadMCPClientStatuses({ silent: true }); onConfigChanged?.(); void messageApi.success(result?.message || `已写入 ${targetLabel} 用户级 MCP 配置`); } catch (error: any) { void messageApi.error(error?.message || `安装 ${targetLabel} MCP 失败`); } finally { onAfterInstall?.(); } }, [copyTextToClipboard, loadMCPClientStatuses, messageApi, onAfterInstall, onBeforeInstall, onConfigChanged, resolveAIService, selectedMCPClientStatus]); const handleCopySelectedMCPConfigPath = useCallback(async () => { const configPath = String(selectedMCPClientStatus?.configPath || '').trim(); if (!configPath) { void messageApi.warning('当前没有可复制的配置文件路径'); return; } try { await copyTextToClipboard(configPath, '配置文件路径已复制'); } catch (error: any) { void messageApi.error(error?.message || '复制配置文件路径失败'); } }, [copyTextToClipboard, messageApi, selectedMCPClientStatus]); const handleCopySelectedMCPLaunchCommand = useCallback(async () => { if (!selectedMCPClientCommandText) { void messageApi.warning('当前没有可复制的启动命令'); return; } try { await copyTextToClipboard(selectedMCPClientCommandText, '启动命令已复制'); } catch (error: any) { void messageApi.error(error?.message || '复制启动命令失败'); } }, [copyTextToClipboard, messageApi, selectedMCPClientCommandText]); return { handleCopySelectedMCPConfigPath, handleCopySelectedMCPLaunchCommand, handleInstallSelectedMCPClient, handleSelectMCPClient, loadMCPClientStatuses, mcpClientStatusLoading, mcpClientStatuses, resetMCPClientSelectionTouched, selectedMCPClient, selectedMCPClientCommandText, selectedMCPClientStatus, syncMCPClientStatuses, }; };