mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-29 20:20:57 +00:00
fix i18n
This commit is contained in:
parent
25f923e89e
commit
a608fdd243
14 changed files with 194 additions and 471 deletions
|
|
@ -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<MCPManagementDialogProps> = ({
|
||||
onClose,
|
||||
}) => {
|
||||
|
|
@ -46,85 +48,94 @@ export const MCPManagementDialog: React.FC<MCPManagementDialogProps> = ({
|
|||
]);
|
||||
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<MCPManagementDialogProps> = ({
|
|||
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<MCPManagementDialogProps> = ({
|
|||
[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<MCPManagementDialogProps> = ({
|
|||
}
|
||||
// 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<MCPManagementDialogProps> = ({
|
|||
|
||||
// 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<MCPManagementDialogProps> = ({
|
|||
|
||||
// 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<MCPManagementDialogProps> = ({
|
|||
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<MCPManagementDialogProps> = ({
|
|||
<ServerDetailStep
|
||||
server={selectedServer}
|
||||
onViewTools={handleViewTools}
|
||||
onViewLogs={handleViewLogs}
|
||||
onReconnect={handleReconnect}
|
||||
onDisable={handleDisable}
|
||||
onBack={handleNavigateBack}
|
||||
|
|
@ -480,11 +430,6 @@ export const MCPManagementDialog: React.FC<MCPManagementDialogProps> = ({
|
|||
/>
|
||||
);
|
||||
|
||||
case MCP_MANAGEMENT_STEPS.SERVER_LOGS:
|
||||
return (
|
||||
<ServerLogsStep server={selectedServer} onBack={handleNavigateBack} />
|
||||
);
|
||||
|
||||
case MCP_MANAGEMENT_STEPS.TOOL_LIST:
|
||||
return (
|
||||
<ToolListStep
|
||||
|
|
@ -515,7 +460,6 @@ export const MCPManagementDialog: React.FC<MCPManagementDialogProps> = ({
|
|||
selectedTool,
|
||||
handleSelectServer,
|
||||
handleViewTools,
|
||||
handleViewLogs,
|
||||
handleReconnect,
|
||||
handleDisable,
|
||||
handleNavigateBack,
|
||||
|
|
@ -543,9 +487,6 @@ export const MCPManagementDialog: React.FC<MCPManagementDialogProps> = ({
|
|||
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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/**
|
||||
* 分组显示名称映射
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<ServerDetailStepProps> = ({
|
||||
server,
|
||||
onViewTools,
|
||||
onViewLogs,
|
||||
onReconnect,
|
||||
onDisable,
|
||||
onBack,
|
||||
|
|
@ -40,13 +39,6 @@ export const ServerDetailStep: React.FC<ServerDetailStepProps> = ({
|
|||
},
|
||||
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<ServerDetailStepProps> = ({
|
|||
case 'view-tools':
|
||||
onViewTools();
|
||||
break;
|
||||
case 'view-logs':
|
||||
onViewLogs?.();
|
||||
break;
|
||||
case 'reconnect':
|
||||
onReconnect?.();
|
||||
break;
|
||||
|
|
@ -115,7 +104,7 @@ export const ServerDetailStep: React.FC<ServerDetailStepProps> = ({
|
|||
>
|
||||
{getStatusIcon(server.status)} {t(server.status)}
|
||||
{server.isDisabled && (
|
||||
<Text color={theme.status.warning}> (disabled)</Text>
|
||||
<Text color={theme.status.warning}> {t('(disabled)')}</Text>
|
||||
)}
|
||||
</Text>
|
||||
</Box>
|
||||
|
|
@ -193,9 +182,6 @@ export const ServerDetailStep: React.FC<ServerDetailStepProps> = ({
|
|||
case 'view-tools':
|
||||
onViewTools();
|
||||
break;
|
||||
case 'view-logs':
|
||||
onViewLogs?.();
|
||||
break;
|
||||
case 'reconnect':
|
||||
onReconnect?.();
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ export const ServerListStep: React.FC<ServerListStepProps> = ({
|
|||
{/* 显示 Scope 和禁用状态 */}
|
||||
<Text color={theme.text.secondary}> [{server.scope}]</Text>
|
||||
{server.isDisabled && (
|
||||
<Text color={theme.status.warning}> (disabled)</Text>
|
||||
<Text color={theme.status.warning}> {t('(disabled)')}</Text>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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<ServerLogsStepProps> = ({
|
||||
server,
|
||||
onBack,
|
||||
}) => {
|
||||
const [logs, setLogs] = useState<LogEntry[]>([]);
|
||||
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 (
|
||||
<Box>
|
||||
<Text color={theme.status.error}>{t('No server selected')}</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<Box flexDirection="column">
|
||||
{/* 标题栏 */}
|
||||
<Box marginBottom={1}>
|
||||
<Text bold>{t('Logs for {{name}}', { name: server.name })}</Text>
|
||||
<Text color={theme.text.secondary}>
|
||||
{' '}
|
||||
({getStatusIcon(server.status)}{' '}
|
||||
<Text
|
||||
color={
|
||||
statusColor === 'green'
|
||||
? theme.status.success
|
||||
: statusColor === 'yellow'
|
||||
? theme.status.warning
|
||||
: theme.status.error
|
||||
}
|
||||
>
|
||||
{t(server.status)}
|
||||
</Text>
|
||||
)
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{/* 日志列表 */}
|
||||
<Box flexDirection="column" minHeight={VISIBLE_LOGS_COUNT}>
|
||||
{displayLogs.map((log, index) => {
|
||||
const actualIndex = scrollOffset + index;
|
||||
const isSelected = actualIndex === selectedIndex;
|
||||
const timestamp = new Date(log.timestamp).toLocaleTimeString();
|
||||
|
||||
return (
|
||||
<Box key={actualIndex}>
|
||||
<Box minWidth={3}>
|
||||
<Text
|
||||
color={isSelected ? theme.text.accent : theme.text.primary}
|
||||
>
|
||||
{isSelected ? '❯' : ' '}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box minWidth={10}>
|
||||
<Text color={theme.text.secondary}>{timestamp}</Text>
|
||||
</Box>
|
||||
<Box minWidth={8}>
|
||||
<Text color={getLevelColor(log.level)}>
|
||||
[{log.level.toUpperCase()}]
|
||||
</Text>
|
||||
</Box>
|
||||
<Box flexGrow={1}>
|
||||
<Text
|
||||
color={isSelected ? theme.text.accent : theme.text.primary}
|
||||
wrap="truncate"
|
||||
>
|
||||
{log.message}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
|
||||
{/* 滚动指示器 */}
|
||||
{logs.length > VISIBLE_LOGS_COUNT && (
|
||||
<Box marginTop={1}>
|
||||
<Text color={theme.text.secondary}>
|
||||
{scrollOffset > 0 ? '↑ ' : ' '}
|
||||
{t('{{current}}/{{total}}', {
|
||||
current: (scrollOffset + 1).toString(),
|
||||
total: logs.length.toString(),
|
||||
})}
|
||||
{scrollOffset + VISIBLE_LOGS_COUNT < logs.length ? ' ↓' : ''}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
@ -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<ToolListStepProps> = ({
|
||||
tools,
|
||||
|
|
|
|||
|
|
@ -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管理对话框属性
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue