diff --git a/packages/cli/src/i18n/locales/de.js b/packages/cli/src/i18n/locales/de.js index a229a5310..f41c20eb7 100644 --- a/packages/cli/src/i18n/locales/de.js +++ b/packages/cli/src/i18n/locales/de.js @@ -883,9 +883,24 @@ export default { 'Do you want to proceed?': 'Möchten Sie fortfahren?', 'Yes, allow once': 'Ja, einmal erlauben', 'Allow always': 'Immer erlauben', + Yes: 'Ja', No: 'Nein', 'No (esc)': 'Nein (Esc)', 'Yes, allow always for this session': 'Ja, für diese Sitzung immer erlauben', + + // MCP Management Dialog (translations for MCP UI components) + Disable: 'Deaktivieren', + Enable: 'Aktivieren', + Reconnect: 'Neu verbinden', + 'View tools': 'Werkzeuge anzeigen', + '(disabled)': '(deaktiviert)', + 'Error:': 'Fehler:', + Extension: 'Erweiterung', + tool: 'Werkzeug', + connected: 'verbunden', + connecting: 'verbindet', + disconnected: 'getrennt', + error: 'Fehler', 'Modify in progress:': 'Änderung in Bearbeitung:', 'Save and close external editor to continue': 'Speichern und externen Editor schließen, um fortzufahren', diff --git a/packages/cli/src/i18n/locales/en.js b/packages/cli/src/i18n/locales/en.js index b93ac79e8..724426e6d 100644 --- a/packages/cli/src/i18n/locales/en.js +++ b/packages/cli/src/i18n/locales/en.js @@ -750,7 +750,6 @@ export default { 'Manage MCP servers': 'Manage MCP servers', 'Server Detail': 'Server Detail', 'Disable Server': 'Disable Server', - 'Server Logs': 'Server Logs', Tools: 'Tools', 'Tool Detail': 'Tool Detail', 'MCP Management': 'MCP Management', @@ -761,8 +760,6 @@ export default { '↑↓ to navigate · Enter to select · Esc to back', '↑↓ to navigate · Enter to confirm · Esc to back': '↑↓ to navigate · Enter to confirm · Esc to back', - '↑↓ to navigate · M to pause/resume · Q/Esc to back': - '↑↓ to navigate · M to pause/resume · Q/Esc to back', 'User Settings (global)': 'User Settings (global)', 'Workspace Settings (project-specific)': 'Workspace Settings (project-specific)', @@ -772,14 +769,22 @@ export default { 'Press Enter to confirm, Esc to cancel': 'Press Enter to confirm, Esc to cancel', 'View tools': 'View tools', - 'View logs': 'View logs', Reconnect: 'Reconnect', Enable: 'Enable', + Disable: 'Disable', 'Status:': 'Status:', 'Command:': 'Command:', 'Working Directory:': 'Working Directory:', 'Capabilities:': 'Capabilities:', prompts: 'prompts', + '(disabled)': '(disabled)', + 'Error:': 'Error:', + Extension: 'Extension', + tool: 'tool', + connected: 'connected', + connecting: 'connecting', + disconnected: 'disconnected', + error: 'error', // ============================================================================ // Commands - Chat @@ -913,6 +918,7 @@ export default { 'Do you want to proceed?': 'Do you want to proceed?', 'Yes, allow once': 'Yes, allow once', 'Allow always': 'Allow always', + Yes: 'Yes', No: 'No', 'No (esc)': 'No (esc)', 'Yes, allow always for this session': 'Yes, allow always for this session', diff --git a/packages/cli/src/i18n/locales/ja.js b/packages/cli/src/i18n/locales/ja.js index 5e139ec34..556b542d7 100644 --- a/packages/cli/src/i18n/locales/ja.js +++ b/packages/cli/src/i18n/locales/ja.js @@ -623,9 +623,24 @@ export default { 'Do you want to proceed?': '続行しますか?', 'Yes, allow once': 'はい(今回のみ許可)', 'Allow always': '常に許可する', + Yes: 'はい', No: 'いいえ', 'No (esc)': 'いいえ (Esc)', 'Yes, allow always for this session': 'はい、このセッションで常に許可', + + // MCP Management - Core translations + Disable: '無効化', + Enable: '有効化', + Reconnect: '再接続', + 'View tools': 'ツールを表示', + '(disabled)': '(無効)', + 'Error:': 'エラー:', + Extension: '拡張機能', + tool: 'ツール', + connected: '接続済み', + connecting: '接続中', + disconnected: '切断済み', + error: 'エラー', 'Modify in progress:': '変更中:', 'Save and close external editor to continue': '続行するには外部エディタを保存して閉じてください', diff --git a/packages/cli/src/i18n/locales/pt.js b/packages/cli/src/i18n/locales/pt.js index ebaa59464..a41848a4e 100644 --- a/packages/cli/src/i18n/locales/pt.js +++ b/packages/cli/src/i18n/locales/pt.js @@ -889,9 +889,24 @@ export default { 'Do you want to proceed?': 'Você deseja prosseguir?', 'Yes, allow once': 'Sim, permitir uma vez', 'Allow always': 'Permitir sempre', + Yes: 'Sim', No: 'Não', 'No (esc)': 'Não (esc)', 'Yes, allow always for this session': 'Sim, permitir sempre para esta sessão', + + // MCP Management - Core translations + Disable: 'Desativar', + Enable: 'Ativar', + Reconnect: 'Reconectar', + 'View tools': 'Ver ferramentas', + '(disabled)': '(desativado)', + 'Error:': 'Erro:', + Extension: 'Extensão', + tool: 'ferramenta', + connected: 'conectado', + connecting: 'conectando', + disconnected: 'desconectado', + error: 'erro', 'Modify in progress:': 'Modificação em progresso:', 'Save and close external editor to continue': 'Salve e feche o editor externo para continuar', diff --git a/packages/cli/src/i18n/locales/ru.js b/packages/cli/src/i18n/locales/ru.js index b02dd7401..aeb159a11 100644 --- a/packages/cli/src/i18n/locales/ru.js +++ b/packages/cli/src/i18n/locales/ru.js @@ -890,9 +890,24 @@ export default { 'Do you want to proceed?': 'Вы хотите продолжить?', 'Yes, allow once': 'Да, разрешить один раз', 'Allow always': 'Всегда разрешать', + Yes: 'Да', No: 'Нет', 'No (esc)': 'Нет (esc)', 'Yes, allow always for this session': 'Да, всегда разрешать для этой сессии', + + // MCP Management - Core translations + Disable: 'Отключить', + Enable: 'Включить', + Reconnect: 'Переподключить', + 'View tools': 'Просмотреть инструменты', + '(disabled)': '(отключен)', + 'Error:': 'Ошибка:', + Extension: 'Расширение', + tool: 'инструмент', + connected: 'подключен', + connecting: 'подключение', + disconnected: 'отключен', + error: 'ошибка', 'Modify in progress:': 'Идет изменение:', 'Save and close external editor to continue': 'Сохраните и закройте внешний редактор для продолжения', diff --git a/packages/cli/src/i18n/locales/zh.js b/packages/cli/src/i18n/locales/zh.js index a2e279faf..b24379d4d 100644 --- a/packages/cli/src/i18n/locales/zh.js +++ b/packages/cli/src/i18n/locales/zh.js @@ -708,7 +708,6 @@ export default { 'Manage MCP servers': '管理 MCP 服务器', 'Server Detail': '服务器详情', 'Disable Server': '禁用服务器', - 'Server Logs': '服务器日志', Tools: '工具', 'Tool Detail': '工具详情', 'MCP Management': 'MCP 管理', @@ -719,8 +718,6 @@ export default { '↑↓ 导航 · Enter 选择 · Esc 返回', '↑↓ to navigate · Enter to confirm · Esc to back': '↑↓ 导航 · Enter 确认 · Esc 返回', - '↑↓ to navigate · M to pause/resume · Q/Esc to back': - '↑↓ 导航 · M 暂停/继续 · Q/Esc 返回', 'User Settings (global)': '用户设置(全局)', 'Workspace Settings (project-specific)': '工作区设置(项目级)', 'Disable server:': '禁用服务器:', @@ -728,9 +725,17 @@ export default { '选择将服务器添加到排除列表的位置:', 'Press Enter to confirm, Esc to cancel': '按 Enter 确认,Esc 取消', 'View tools': '查看工具', - 'View logs': '查看日志', Reconnect: '重新连接', Enable: '启用', + Disable: '禁用', + '(disabled)': '(已禁用)', + 'Error:': '错误:', + Extension: '扩展', + tool: '工具', + connected: '已连接', + connecting: '连接中', + disconnected: '已断开', + error: '错误', // ============================================================================ // Commands - Chat @@ -857,6 +862,7 @@ export default { 'Do you want to proceed?': '是否继续?', 'Yes, allow once': '是,允许一次', 'Allow always': '总是允许', + Yes: '是', No: '否', 'No (esc)': '否 (esc)', 'Yes, allow always for this session': '是,本次会话总是允许', diff --git a/packages/cli/src/ui/components/mcp/MCPManagementDialog.tsx b/packages/cli/src/ui/components/mcp/MCPManagementDialog.tsx index 472dbb9c1..85468a8ad 100644 --- a/packages/cli/src/ui/components/mcp/MCPManagementDialog.tsx +++ b/packages/cli/src/ui/components/mcp/MCPManagementDialog.tsx @@ -17,7 +17,6 @@ import type { import { MCP_MANAGEMENT_STEPS } from './types.js'; import { ServerListStep } from './steps/ServerListStep.js'; import { ServerDetailStep } from './steps/ServerDetailStep.js'; -import { ServerLogsStep } from './steps/ServerLogsStep.js'; import { ToolListStep } from './steps/ToolListStep.js'; import { ToolDetailStep } from './steps/ToolDetailStep.js'; import { DisableScopeSelectStep } from './steps/DisableScopeSelectStep.js'; @@ -28,9 +27,12 @@ import { type MCPServerConfig, type AnyDeclarativeTool, type DiscoveredMCPPrompt, + createDebugLogger, } from '@qwen-code/qwen-code-core'; import { loadSettings, SettingScope } from '../../../config/settings.js'; +const debugLogger = createDebugLogger('MCP_DIALOG'); + export const MCPManagementDialog: React.FC = ({ onClose, }) => { @@ -46,85 +48,94 @@ export const MCPManagementDialog: React.FC = ({ ]); const [isLoading, setIsLoading] = useState(true); - // Load MCP server data + // Load MCP server data - extracted to a separate function for reuse + const fetchServerData = useCallback(async (): Promise< + MCPServerDisplayInfo[] + > => { + if (!config) return []; + + const mcpServers = config.getMcpServers() || {}; + const toolRegistry = config.getToolRegistry(); + const promptRegistry = config.getPromptRegistry(); + + // Get settings to determine the scope of each server + const settings = loadSettings(); + const userSettings = settings.forScope(SettingScope.User).settings; + const workspaceSettings = settings.forScope( + SettingScope.Workspace, + ).settings; + + const serverInfos: MCPServerDisplayInfo[] = []; + + for (const [name, serverConfig] of Object.entries(mcpServers) as Array< + [string, MCPServerConfig] + >) { + const status = getMCPServerStatus(name); + + // Get tools for this server + const allTools: AnyDeclarativeTool[] = toolRegistry?.getAllTools() || []; + const serverTools = allTools.filter( + (t): t is DiscoveredMCPTool => + t instanceof DiscoveredMCPTool && t.serverName === name, + ); + + // Get prompts for this server + const allPrompts: DiscoveredMCPPrompt[] = + promptRegistry?.getAllPrompts() || []; + const serverPrompts = allPrompts.filter( + (p) => 'serverName' in p && p.serverName === name, + ); + + // Determine source type + let source: 'user' | 'project' | 'extension' = 'user'; + if (serverConfig.extensionName) { + source = 'extension'; + } + + // Determine the scope of the configuration + let scope: 'user' | 'workspace' | 'extension' = 'user'; + if (serverConfig.extensionName) { + scope = 'extension'; + } else if (workspaceSettings.mcpServers?.[name]) { + scope = 'workspace'; + } else if (userSettings.mcpServers?.[name]) { + scope = 'user'; + } + + // Use config.isMcpServerDisabled() to check if server is disabled + const isDisabled = config.isMcpServerDisabled(name); + + serverInfos.push({ + name, + status, + source, + scope, + config: serverConfig, + toolCount: serverTools.length, + promptCount: serverPrompts.length, + isDisabled, + }); + } + + return serverInfos; + }, [config]); + + // Load MCP server data on initial render useEffect(() => { const loadServers = async () => { - if (!config) return; - setIsLoading(true); try { - const mcpServers = config.getMcpServers() || {}; - const toolRegistry = config.getToolRegistry(); - const promptRegistry = await config.getPromptRegistry(); - - // Get settings to determine the scope of each server - const settings = loadSettings(); - const userSettings = settings.forScope(SettingScope.User).settings; - const workspaceSettings = settings.forScope( - SettingScope.Workspace, - ).settings; - - const serverInfos: MCPServerDisplayInfo[] = []; - - for (const [name, serverConfig] of Object.entries(mcpServers) as Array< - [string, MCPServerConfig] - >) { - const status = getMCPServerStatus(name); - - // Get tools for this server - const allTools: AnyDeclarativeTool[] = - toolRegistry?.getAllTools() || []; - const serverTools = allTools.filter( - (t): t is DiscoveredMCPTool => - t instanceof DiscoveredMCPTool && t.serverName === name, - ); - - // Get prompts for this server - const allPrompts: DiscoveredMCPPrompt[] = - promptRegistry?.getAllPrompts() || []; - const serverPrompts = allPrompts.filter( - (p) => 'serverName' in p && p.serverName === name, - ); - - // Determine source type - let source: 'user' | 'project' | 'extension' = 'user'; - if (serverConfig.extensionName) { - source = 'extension'; - } - - // Determine the scope of the configuration - let scope: 'user' | 'workspace' | 'extension' = 'user'; - if (serverConfig.extensionName) { - scope = 'extension'; - } else if (workspaceSettings.mcpServers?.[name]) { - scope = 'workspace'; - } else if (userSettings.mcpServers?.[name]) { - scope = 'user'; - } - - // Use config.isMcpServerDisabled() to check if server is disabled - const isDisabled = config.isMcpServerDisabled(name); - - serverInfos.push({ - name, - status, - source, - scope, - config: serverConfig, - toolCount: serverTools.length, - promptCount: serverPrompts.length, - isDisabled, - }); - } - + const serverInfos = await fetchServerData(); setServers(serverInfos); + } catch (error) { + debugLogger.error('Error loading MCP servers:', error); } finally { setIsLoading(false); } }; loadServers(); - }, [config]); + }, [fetchServerData]); // Selected server const selectedServer = useMemo(() => { @@ -194,11 +205,6 @@ export const MCPManagementDialog: React.FC = ({ handleNavigateToStep(MCP_MANAGEMENT_STEPS.TOOL_LIST); }, [handleNavigateToStep]); - // View server logs - const handleViewLogs = useCallback(() => { - handleNavigateToStep(MCP_MANAGEMENT_STEPS.SERVER_LOGS); - }, [handleNavigateToStep]); - // Select tool const handleSelectTool = useCallback( (tool: MCPToolDisplayInfo) => { @@ -208,79 +214,18 @@ export const MCPManagementDialog: React.FC = ({ [handleNavigateToStep], ); - // Reload server data + // Reload server data - uses the extracted fetchServerData function const reloadServers = useCallback(async () => { - if (!config) return; - setIsLoading(true); try { - const mcpServers = config.getMcpServers() || {}; - const toolRegistry = config.getToolRegistry(); - const promptRegistry = await config.getPromptRegistry(); - - // Get settings to determine the scope of each server - const settings = loadSettings(); - const userSettings = settings.forScope(SettingScope.User).settings; - const workspaceSettings = settings.forScope( - SettingScope.Workspace, - ).settings; - - const serverInfos: MCPServerDisplayInfo[] = []; - - for (const [name, serverConfig] of Object.entries(mcpServers) as Array< - [string, MCPServerConfig] - >) { - const status = getMCPServerStatus(name); - - const allTools: AnyDeclarativeTool[] = - toolRegistry?.getAllTools() || []; - const serverTools = allTools.filter( - (t): t is DiscoveredMCPTool => - t instanceof DiscoveredMCPTool && t.serverName === name, - ); - - const allPrompts: DiscoveredMCPPrompt[] = - promptRegistry?.getAllPrompts() || []; - const serverPrompts = allPrompts.filter( - (p) => 'serverName' in p && p.serverName === name, - ); - - // Determine source type - let source: 'user' | 'project' | 'extension' = 'user'; - if (serverConfig.extensionName) { - source = 'extension'; - } - - // Determine the scope of the configuration - let scope: 'user' | 'workspace' | 'extension' = 'user'; - if (serverConfig.extensionName) { - scope = 'extension'; - } else if (workspaceSettings.mcpServers?.[name]) { - scope = 'workspace'; - } else if (userSettings.mcpServers?.[name]) { - scope = 'user'; - } - - // Use config.isMcpServerDisabled() to check if server is disabled - const isDisabled = config.isMcpServerDisabled(name); - - serverInfos.push({ - name, - status, - source, - scope, - config: serverConfig, - toolCount: serverTools.length, - promptCount: serverPrompts.length, - isDisabled, - }); - } - + const serverInfos = await fetchServerData(); setServers(serverInfos); + } catch (error) { + debugLogger.error('Error reloading MCP servers:', error); } finally { setIsLoading(false); } - }, [config]); + }, [fetchServerData]); // Reconnect server const handleReconnect = useCallback(async () => { @@ -294,8 +239,11 @@ export const MCPManagementDialog: React.FC = ({ } // Reload server data to update status await reloadServers(); - } catch (_error) { - // Error handling - fail silently + } catch (error) { + debugLogger.error( + `Error reconnecting to server '${selectedServer.name}':`, + error, + ); } finally { setIsLoading(false); } @@ -339,8 +287,11 @@ export const MCPManagementDialog: React.FC = ({ // Reload server data await reloadServers(); - } catch (_error) { - // Error handling - fail silently + } catch (error) { + debugLogger.error( + `Error enabling server '${selectedServer.name}':`, + error, + ); } finally { setIsLoading(false); } @@ -397,8 +348,11 @@ export const MCPManagementDialog: React.FC = ({ // Return to server detail page handleNavigateBack(); - } catch (_error) { - // Error handling - fail silently + } catch (error) { + debugLogger.error( + `Error disabling server '${selectedServer.name}':`, + error, + ); } finally { setIsLoading(false); } @@ -421,9 +375,6 @@ export const MCPManagementDialog: React.FC = ({ case MCP_MANAGEMENT_STEPS.DISABLE_SCOPE_SELECT: headerText = t('Disable Server'); break; - case MCP_MANAGEMENT_STEPS.SERVER_LOGS: - headerText = t('Server Logs'); - break; case MCP_MANAGEMENT_STEPS.TOOL_LIST: headerText = t('Tools'); break; @@ -464,7 +415,6 @@ export const MCPManagementDialog: React.FC = ({ = ({ /> ); - case MCP_MANAGEMENT_STEPS.SERVER_LOGS: - return ( - - ); - case MCP_MANAGEMENT_STEPS.TOOL_LIST: return ( = ({ selectedTool, handleSelectServer, handleViewTools, - handleViewLogs, handleReconnect, handleDisable, handleNavigateBack, @@ -543,9 +487,6 @@ export const MCPManagementDialog: React.FC = ({ case MCP_MANAGEMENT_STEPS.DISABLE_SCOPE_SELECT: footerText = t('↑↓ to navigate · Enter to confirm · Esc to back'); break; - case MCP_MANAGEMENT_STEPS.SERVER_LOGS: - footerText = t('↑↓ to navigate · M to pause/resume · Q/Esc to back'); - break; case MCP_MANAGEMENT_STEPS.TOOL_LIST: footerText = t('↑↓ to navigate · Enter to select · Esc to back'); break; diff --git a/packages/cli/src/ui/components/mcp/constants.ts b/packages/cli/src/ui/components/mcp/constants.ts index add1d3287..cfdc2691f 100644 --- a/packages/cli/src/ui/components/mcp/constants.ts +++ b/packages/cli/src/ui/components/mcp/constants.ts @@ -18,6 +18,16 @@ export const MAX_DISPLAY_TOOLS = 10; */ export const MAX_DISPLAY_PROMPTS = 10; +/** + * 日志列表可视区域最大显示数量 + */ +export const VISIBLE_LOGS_COUNT = 15; + +/** + * 工具列表可视区域最大显示数量 + */ +export const VISIBLE_TOOLS_COUNT = 10; + /** * 分组显示名称映射 */ diff --git a/packages/cli/src/ui/components/mcp/index.ts b/packages/cli/src/ui/components/mcp/index.ts index 435ef469d..01ebfee8f 100644 --- a/packages/cli/src/ui/components/mcp/index.ts +++ b/packages/cli/src/ui/components/mcp/index.ts @@ -10,7 +10,6 @@ export { MCPManagementDialog } from './MCPManagementDialog.js'; // Steps export { ServerListStep } from './steps/ServerListStep.js'; export { ServerDetailStep } from './steps/ServerDetailStep.js'; -export { ServerLogsStep } from './steps/ServerLogsStep.js'; export { ToolListStep } from './steps/ToolListStep.js'; export { ToolDetailStep } from './steps/ToolDetailStep.js'; @@ -22,7 +21,6 @@ export type { MCPPromptDisplayInfo, ServerListStepProps, ServerDetailStepProps, - ServerLogsStepProps, ToolListStepProps, ToolDetailStepProps, MCPManagementStep, diff --git a/packages/cli/src/ui/components/mcp/steps/ServerDetailStep.tsx b/packages/cli/src/ui/components/mcp/steps/ServerDetailStep.tsx index 3645f92f3..dc9e1ba35 100644 --- a/packages/cli/src/ui/components/mcp/steps/ServerDetailStep.tsx +++ b/packages/cli/src/ui/components/mcp/steps/ServerDetailStep.tsx @@ -17,12 +17,11 @@ import { formatServerCommand, } from '../utils.js'; -type ServerAction = 'view-tools' | 'view-logs' | 'reconnect' | 'toggle-disable'; +type ServerAction = 'view-tools' | 'reconnect' | 'toggle-disable'; export const ServerDetailStep: React.FC = ({ server, onViewTools, - onViewLogs, onReconnect, onDisable, onBack, @@ -40,13 +39,6 @@ export const ServerDetailStep: React.FC = ({ }, value: 'view-tools' as const, }, - { - key: 'view-logs', - get label() { - return t('View logs'); - }, - value: 'view-logs' as const, - }, { key: 'reconnect', get label() { @@ -72,9 +64,6 @@ export const ServerDetailStep: React.FC = ({ case 'view-tools': onViewTools(); break; - case 'view-logs': - onViewLogs?.(); - break; case 'reconnect': onReconnect?.(); break; @@ -115,7 +104,7 @@ export const ServerDetailStep: React.FC = ({ > {getStatusIcon(server.status)} {t(server.status)} {server.isDisabled && ( - (disabled) + {t('(disabled)')} )} @@ -193,9 +182,6 @@ export const ServerDetailStep: React.FC = ({ case 'view-tools': onViewTools(); break; - case 'view-logs': - onViewLogs?.(); - break; case 'reconnect': onReconnect?.(); break; diff --git a/packages/cli/src/ui/components/mcp/steps/ServerListStep.tsx b/packages/cli/src/ui/components/mcp/steps/ServerListStep.tsx index eec88e854..975ec9ed1 100644 --- a/packages/cli/src/ui/components/mcp/steps/ServerListStep.tsx +++ b/packages/cli/src/ui/components/mcp/steps/ServerListStep.tsx @@ -152,7 +152,7 @@ export const ServerListStep: React.FC = ({ {/* 显示 Scope 和禁用状态 */} [{server.scope}] {server.isDisabled && ( - (disabled) + {t('(disabled)')} )} ); diff --git a/packages/cli/src/ui/components/mcp/steps/ServerLogsStep.tsx b/packages/cli/src/ui/components/mcp/steps/ServerLogsStep.tsx deleted file mode 100644 index 20f063a82..000000000 --- a/packages/cli/src/ui/components/mcp/steps/ServerLogsStep.tsx +++ /dev/null @@ -1,243 +0,0 @@ -/** - * @license - * Copyright 2025 Qwen - * SPDX-License-Identifier: Apache-2.0 - */ - -import { useState, useEffect, useCallback } from 'react'; -import { Box, Text } from 'ink'; -import { theme } from '../../../semantic-colors.js'; -import { useKeypress } from '../../../hooks/useKeypress.js'; -import { t } from '../../../../i18n/index.js'; -import type { ServerLogsStepProps } from '../types.js'; -import { getStatusColor, getStatusIcon } from '../utils.js'; -import { MCPServerStatus, getMCPServerStatus } from '@qwen-code/qwen-code-core'; - -// 模拟日志条目类型 -interface LogEntry { - timestamp: string; - level: 'info' | 'warn' | 'error' | 'debug'; - message: string; -} - -export const ServerLogsStep: React.FC = ({ - server, - onBack, -}) => { - const [logs, setLogs] = useState([]); - const [isMonitoring, setIsMonitoring] = useState(true); - const [selectedIndex, setSelectedIndex] = useState(0); - - // 生成模拟日志数据 - const generateMockLogs = useCallback((): LogEntry[] => { - const now = new Date(); - const baseLogs: LogEntry[] = [ - { - timestamp: new Date(now.getTime() - 5000).toISOString(), - level: 'info', - message: `MCP server '${server?.name}' initializing...`, - }, - { - timestamp: new Date(now.getTime() - 4000).toISOString(), - level: 'info', - message: 'Connecting to transport...', - }, - { - timestamp: new Date(now.getTime() - 3000).toISOString(), - level: server?.status === MCPServerStatus.CONNECTED ? 'info' : 'error', - message: - server?.status === MCPServerStatus.CONNECTED - ? 'Connection established successfully' - : 'Connection failed: ' + (server?.errorMessage || 'Unknown error'), - }, - ]; - - if (server?.status === MCPServerStatus.CONNECTED) { - baseLogs.push( - { - timestamp: new Date(now.getTime() - 2000).toISOString(), - level: 'info', - message: `Discovered ${server.toolCount} tools`, - }, - { - timestamp: new Date(now.getTime() - 1000).toISOString(), - level: 'info', - message: `Discovered ${server.promptCount} prompts`, - }, - { - timestamp: now.toISOString(), - level: 'info', - message: 'Server ready for requests', - }, - ); - } - - return baseLogs; - }, [server]); - - // 初始化日志 - useEffect(() => { - setLogs(generateMockLogs()); - }, [generateMockLogs]); - - // 模拟实时日志更新 - useEffect(() => { - if (!isMonitoring) return; - - const interval = setInterval(() => { - const currentStatus = server?.name - ? getMCPServerStatus(server.name) - : null; - - // 如果状态变化,添加日志 - if (currentStatus && currentStatus !== server?.status) { - const newLog: LogEntry = { - timestamp: new Date().toISOString(), - level: currentStatus === MCPServerStatus.CONNECTED ? 'info' : 'warn', - message: `Server status changed to: ${currentStatus}`, - }; - setLogs((prev) => [...prev.slice(-49), newLog]); // 保留最近50条 - } - }, 5000); - - return () => clearInterval(interval); - }, [isMonitoring, server]); - - // 键盘处理 - useKeypress( - (key) => { - if (key.name === 'escape' || key.name === 'q') { - onBack(); - } else if (key.name === 'up') { - setSelectedIndex((prev) => Math.max(0, prev - 1)); - } else if (key.name === 'down') { - setSelectedIndex((prev) => Math.min(logs.length - 1, prev + 1)); - } else if (key.name === 'm') { - setIsMonitoring((prev) => !prev); - } - }, - { isActive: true }, - ); - - if (!server) { - return ( - - {t('No server selected')} - - ); - } - - const statusColor = getStatusColor(server.status); - - const getLevelColor = (level: LogEntry['level']) => { - switch (level) { - case 'error': - return theme.status.error; - case 'warn': - return theme.status.warning; - case 'debug': - return theme.text.secondary; - default: - return theme.text.primary; - } - }; - - // 可视区域最大显示日志数量 - const VISIBLE_LOGS_COUNT = 15; - - // 计算可视区域的起始索引(滚动窗口) - const scrollOffset = (() => { - if (logs.length <= VISIBLE_LOGS_COUNT) { - return 0; - } - if (selectedIndex < VISIBLE_LOGS_COUNT - 1) { - return 0; - } - return Math.min( - selectedIndex - VISIBLE_LOGS_COUNT + 1, - logs.length - VISIBLE_LOGS_COUNT, - ); - })(); - - // 当前可视的日志列表 - const displayLogs = logs.slice( - scrollOffset, - scrollOffset + VISIBLE_LOGS_COUNT, - ); - - return ( - - {/* 标题栏 */} - - {t('Logs for {{name}}', { name: server.name })} - - {' '} - ({getStatusIcon(server.status)}{' '} - - {t(server.status)} - - ) - - - - {/* 日志列表 */} - - {displayLogs.map((log, index) => { - const actualIndex = scrollOffset + index; - const isSelected = actualIndex === selectedIndex; - const timestamp = new Date(log.timestamp).toLocaleTimeString(); - - return ( - - - - {isSelected ? '❯' : ' '} - - - - {timestamp} - - - - [{log.level.toUpperCase()}] - - - - - {log.message} - - - - ); - })} - - - {/* 滚动指示器 */} - {logs.length > VISIBLE_LOGS_COUNT && ( - - - {scrollOffset > 0 ? '↑ ' : ' '} - {t('{{current}}/{{total}}', { - current: (scrollOffset + 1).toString(), - total: logs.length.toString(), - })} - {scrollOffset + VISIBLE_LOGS_COUNT < logs.length ? ' ↓' : ''} - - - )} - - ); -}; diff --git a/packages/cli/src/ui/components/mcp/steps/ToolListStep.tsx b/packages/cli/src/ui/components/mcp/steps/ToolListStep.tsx index 72bc348cd..4f88b1a53 100644 --- a/packages/cli/src/ui/components/mcp/steps/ToolListStep.tsx +++ b/packages/cli/src/ui/components/mcp/steps/ToolListStep.tsx @@ -10,9 +10,7 @@ import { theme } from '../../../semantic-colors.js'; import { useKeypress } from '../../../hooks/useKeypress.js'; import { t } from '../../../../i18n/index.js'; import type { ToolListStepProps, MCPToolDisplayInfo } from '../types.js'; - -// 可视区域最大显示工具数量 -const VISIBLE_TOOLS_COUNT = 10; +import { VISIBLE_TOOLS_COUNT } from '../constants.js'; export const ToolListStep: React.FC = ({ tools, diff --git a/packages/cli/src/ui/components/mcp/types.ts b/packages/cli/src/ui/components/mcp/types.ts index 2ee40dd2d..85bad67df 100644 --- a/packages/cli/src/ui/components/mcp/types.ts +++ b/packages/cli/src/ui/components/mcp/types.ts @@ -18,9 +18,6 @@ export const MCP_MANAGEMENT_STEPS = { DISABLE_SCOPE_SELECT: 'disable-scope-select', TOOL_LIST: 'tool-list', TOOL_DETAIL: 'tool-detail', - PROMPT_LIST: 'prompt-list', - PROMPT_DETAIL: 'prompt-detail', - SERVER_LOGS: 'server-logs', } as const; export type MCPManagementStep = @@ -124,8 +121,6 @@ export interface ServerDetailStepProps { server: MCPServerDisplayInfo | null; /** 查看工具列表回调 */ onViewTools: () => void; - /** 查看日志回调 */ - onViewLogs?: () => void; /** 重新连接回调 */ onReconnect?: () => void; /** 禁用服务器回调 */ @@ -170,40 +165,6 @@ export interface ToolDetailStepProps { onBack: () => void; } -/** - * PromptListStep组件属性 - */ -export interface PromptListStepProps { - /** Prompt列表 */ - prompts: MCPPromptDisplayInfo[]; - /** 服务器名称 */ - serverName: string; - /** 选择回调 */ - onSelect: (prompt: MCPPromptDisplayInfo) => void; - /** 返回回调 */ - onBack: () => void; -} - -/** - * PromptDetailStep组件属性 - */ -export interface PromptDetailStepProps { - /** Prompt信息 */ - prompt: MCPPromptDisplayInfo | null; - /** 返回回调 */ - onBack: () => void; -} - -/** - * ServerLogsStep组件属性 - */ -export interface ServerLogsStepProps { - /** 服务器信息 */ - server: MCPServerDisplayInfo | null; - /** 返回回调 */ - onBack: () => void; -} - /** * MCP管理对话框属性 */