mirror of https://github.com/opensumi/core
style: improve MCP configuration page style (#4475)
This commit is contained in:
parent
1725b0d09a
commit
b1eeb004c8
|
@ -45,34 +45,86 @@
|
|||
.serverItem {
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
background-color: var(--editorWidget-background);
|
||||
border: 1px solid var(--border-color);
|
||||
background-color: var(--editor-background);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
.serverHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
.iconButton {
|
||||
padding: 2px 5px;
|
||||
margin-left: 5px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
line-height: 20px;
|
||||
font-size: 14px;
|
||||
&:hover {
|
||||
background-color: var(--badge-background);
|
||||
}
|
||||
}
|
||||
.serverActionButton {
|
||||
height: 24px;
|
||||
background-color: var(--badge-background);
|
||||
color: var(--badge-foreground);
|
||||
span {
|
||||
min-width: 52px;
|
||||
}
|
||||
&.active {
|
||||
i {
|
||||
color: var(--testing-iconPassed);
|
||||
}
|
||||
}
|
||||
i {
|
||||
color: var(--testing-iconErrored);
|
||||
margin-right: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
border-color: var(--focusBorder);
|
||||
}
|
||||
}
|
||||
|
||||
.serverHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.serverTitleRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text-indent: 12px;
|
||||
}
|
||||
|
||||
.serverActions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.serverName {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.serverStatusIcon {
|
||||
border-radius: 50%;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
margin-left: 12px;
|
||||
display: inline-block;
|
||||
&.active {
|
||||
background-color: var(--testing-iconPassed);
|
||||
box-shadow: 0 0 0 1px var(--testing-iconPassed);
|
||||
}
|
||||
&.inactive {
|
||||
background-color: var(--testing-iconErrored);
|
||||
box-shadow: 0 0 0 1px var(--testing-iconErrored);
|
||||
}
|
||||
}
|
||||
|
||||
.serverStatus,
|
||||
|
@ -104,26 +156,7 @@
|
|||
color: var(--notification-error-foreground);
|
||||
}
|
||||
|
||||
.serverActions {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.iconButton {
|
||||
padding: 4px;
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--icon-foreground);
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--list-hover-background);
|
||||
}
|
||||
}
|
||||
|
||||
.serverDetail {
|
||||
margin-top: 12px;
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
background-color: var(--editor-background);
|
||||
|
@ -181,11 +214,9 @@
|
|||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 8px;
|
||||
background-color: var(--editorWidget-background);
|
||||
color: var(--button-secondary-foreground);
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
border: 1px solid var(--border-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import cls from 'classnames';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { Badge } from '@opensumi/ide-components';
|
||||
import { Badge, Button, Popover, PopoverTriggerType } from '@opensumi/ide-components';
|
||||
import { AINativeSettingSectionsId, ILogger, useInjectable } from '@opensumi/ide-core-browser';
|
||||
import { PreferenceService } from '@opensumi/ide-core-browser/lib/preferences';
|
||||
import { PreferenceScope, localize } from '@opensumi/ide-core-common';
|
||||
|
@ -178,43 +178,60 @@ export const MCPConfigView: React.FC = () => {
|
|||
<div key={server.name} className={styles.serverItem}>
|
||||
<div className={styles.serverHeader}>
|
||||
<div className={styles.serverTitleRow}>
|
||||
<h3 className={styles.serverName}>{server.name}</h3>
|
||||
<h3 className={styles.serverName}>
|
||||
{server.name}
|
||||
<span
|
||||
className={cls(styles.serverStatusIcon, server.isStarted ? styles.active : styles.inactive)}
|
||||
></span>
|
||||
</h3>
|
||||
</div>
|
||||
<div className={styles.serverActions}>
|
||||
{server.name !== BUILTIN_MCP_SERVER_NAME && (
|
||||
<button className={styles.iconButton} title='Edit' onClick={() => handleEditServer(server)}>
|
||||
<i className='codicon codicon-edit' />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className={styles.iconButton}
|
||||
title={server.isStarted ? 'Stop' : 'Start'}
|
||||
onClick={() => handleServerControl(server.name, !server.isStarted)}
|
||||
<Popover
|
||||
id='mcp-server-action-popover'
|
||||
trigger={PopoverTriggerType.hover}
|
||||
content={
|
||||
server.isStarted ? localize('ai.native.mcp.disable.title') : localize('ai.native.mcp.enable.title')
|
||||
}
|
||||
>
|
||||
<i
|
||||
className={`codicon ${
|
||||
loadingServer === server.name
|
||||
? 'codicon-loading kt-icon-loading'
|
||||
: server.isStarted
|
||||
? 'codicon-debug-stop'
|
||||
: 'codicon-debug-start'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
<Button
|
||||
type='default'
|
||||
className={cls(styles.serverActionButton, server.isStarted && styles.active)}
|
||||
onClick={() => handleServerControl(server.name, !server.isStarted)}
|
||||
>
|
||||
<i
|
||||
className={`codicon ${
|
||||
loadingServer === server.name
|
||||
? 'codicon-loading kt-icon-loading'
|
||||
: server.isStarted
|
||||
? 'codicon-check'
|
||||
: 'codicon-circle'
|
||||
}`}
|
||||
/>
|
||||
<span>{localize(server.isStarted ? 'ai.native.mcp.enabled' : 'ai.native.mcp.disabled')}</span>
|
||||
</Button>
|
||||
</Popover>
|
||||
{server.name !== BUILTIN_MCP_SERVER_NAME && (
|
||||
<button className={styles.iconButton} title='Delete' onClick={() => handleDeleteServer(server.name)}>
|
||||
<i className='codicon codicon-trash' />
|
||||
</button>
|
||||
<Button
|
||||
type='icon'
|
||||
iconClass='codicon codicon-edit'
|
||||
className={styles.iconButton}
|
||||
title='Edit'
|
||||
onClick={() => handleEditServer(server)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{server.name !== BUILTIN_MCP_SERVER_NAME && (
|
||||
<Button
|
||||
type='icon'
|
||||
iconClass='codicon codicon-trash'
|
||||
className={styles.iconButton}
|
||||
title='Delete'
|
||||
onClick={() => handleDeleteServer(server.name)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.serverDetail}>
|
||||
<div className={styles.detailRow}>
|
||||
<span className={styles.detailLabel}>Status:</span>
|
||||
<span className={`${styles.serverStatus} ${server.isStarted ? styles.running : styles.stopped}`}>
|
||||
{server.isStarted ? localize('ai.native.mcp.running') : localize('ai.native.mcp.stopped')}
|
||||
</span>
|
||||
</div>
|
||||
{server.type && (
|
||||
<div className={styles.detailRow}>
|
||||
<span className={styles.detailLabel}>Type:</span>
|
||||
|
@ -228,9 +245,9 @@ export const MCPConfigView: React.FC = () => {
|
|||
<span className={styles.detailLabel}>Tools:</span>
|
||||
<span className={styles.detailContent}>
|
||||
{server.tools.map((tool, index) => (
|
||||
<span key={index} className={styles.toolTag}>
|
||||
{tool}
|
||||
</span>
|
||||
<Badge key={index} className={styles.toolTag} title={tool.description}>
|
||||
{tool.name}
|
||||
</Badge>
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -46,6 +46,9 @@
|
|||
gap: 8px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
.secondaryButton {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.formRow {
|
||||
|
|
|
@ -263,7 +263,7 @@ export const MCPServerForm: FC<Props> = ({ visible, initialData, onSave, onCance
|
|||
</div>
|
||||
{renderFormItems()}
|
||||
<div className={styles.formActions}>
|
||||
<Button onClick={onCancel} type='ghost'>
|
||||
<Button onClick={onCancel} type='primary' className={styles.secondaryButton}>
|
||||
{localize('ai.native.mcp.buttonCancel')}
|
||||
</Button>
|
||||
<Button onClick={handleSubmit} type='primary'>
|
||||
|
|
|
@ -37,7 +37,7 @@ export const AI_CHAT_LOGO_AVATAR_ID = 'AI-Chat-Logo-Avatar';
|
|||
export const AI_MENU_BAR_DEBUG_TOOLBAR = 'AI_MENU_BAR_DEBUG_TOOLBAR';
|
||||
|
||||
// 内置 MCP 服务器名称
|
||||
export const BUILTIN_MCP_SERVER_NAME = 'builtin';
|
||||
export const BUILTIN_MCP_SERVER_NAME = 'Builtin';
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link DESIGN_MENUBAR_CONTAINER_VIEW_ID} instead
|
||||
|
@ -134,7 +134,7 @@ export interface ISumiMCPServerBackend {
|
|||
initBuiltinMCPServer(enabled: boolean): void;
|
||||
initExternalMCPServers(servers: MCPServerDescription[]): void;
|
||||
getAllMCPTools(): Promise<MCPTool[]>;
|
||||
getServers(): Promise<Array<{ name: string; isStarted: boolean }>>;
|
||||
getServers(): Promise<Array<{ name: string; isStarted: boolean; tools: MCPTool[] }>>;
|
||||
startServer(serverName: string): Promise<void>;
|
||||
stopServer(serverName: string): Promise<void>;
|
||||
addOrUpdateServer(description: MCPServerDescription): void;
|
||||
|
|
|
@ -46,7 +46,7 @@ export interface IMCPServerProxyService {
|
|||
export interface MCPServer {
|
||||
name: string;
|
||||
isStarted: boolean;
|
||||
tools?: string[];
|
||||
tools?: MCPTool[];
|
||||
command?: string;
|
||||
type?: string;
|
||||
serverHost?: string;
|
||||
|
|
|
@ -136,12 +136,16 @@ export class SumiMCPServerBackend extends RPCService<IMCPServerProxyService> imp
|
|||
const servers = Array.from(this.mcpServerManager.getServers().entries());
|
||||
const serverInfos = await Promise.all(
|
||||
servers.map(async ([serverName, server]) => {
|
||||
let toolNames: string[] = [];
|
||||
let tools: MCPTool[] = [];
|
||||
if (server.isStarted()) {
|
||||
// 只获取正在运行的 MCP Server 的工具列表
|
||||
const toolsResponse = await server.getTools();
|
||||
this.logger.log(`Server ${serverName} tools:`, toolsResponse.tools);
|
||||
toolNames = toolsResponse.tools.map((tool) => tool.name);
|
||||
tools = toolsResponse.tools.map((tool) => ({
|
||||
name: tool.name,
|
||||
description: tool.description || '',
|
||||
inputSchema: tool.inputSchema,
|
||||
providerName: serverName,
|
||||
}));
|
||||
}
|
||||
|
||||
// OpenSumi 内置的 MCP Server
|
||||
|
@ -150,7 +154,7 @@ export class SumiMCPServerBackend extends RPCService<IMCPServerProxyService> imp
|
|||
name: server.getServerName(),
|
||||
isStarted: server.isStarted(),
|
||||
type: MCP_SERVER_TYPE.BUILTIN,
|
||||
tools: toolNames,
|
||||
tools,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -161,7 +165,7 @@ export class SumiMCPServerBackend extends RPCService<IMCPServerProxyService> imp
|
|||
isStarted: server.isStarted(),
|
||||
type: MCP_SERVER_TYPE.STDIO,
|
||||
command: server.command + ' ' + (server.args?.join(' ') || ''),
|
||||
tools: toolNames,
|
||||
tools,
|
||||
};
|
||||
} else if (server instanceof SSEMCPServer) {
|
||||
return {
|
||||
|
@ -169,7 +173,7 @@ export class SumiMCPServerBackend extends RPCService<IMCPServerProxyService> imp
|
|||
isStarted: server.isStarted(),
|
||||
type: MCP_SERVER_TYPE.SSE,
|
||||
serverHost: server.serverHost,
|
||||
tools: toolNames,
|
||||
tools,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -178,7 +182,7 @@ export class SumiMCPServerBackend extends RPCService<IMCPServerProxyService> imp
|
|||
isStarted: server.isStarted(),
|
||||
type: '[MOCK] stdio',
|
||||
command: '[MOCK] npx sumi-ide-mcp-server',
|
||||
tools: toolNames,
|
||||
tools,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
|
|
@ -1621,8 +1621,10 @@ export const localizationBundle = {
|
|||
'ai.native.mcp.command.isRequired': 'Command is required',
|
||||
'ai.native.mcp.serverHost.isRequired': 'SSE URL is required',
|
||||
'ai.native.mcp.manage.connections': 'Manage your MCP server connections',
|
||||
'ai.native.mcp.running': 'Running',
|
||||
'ai.native.mcp.stopped': 'Stopped',
|
||||
'ai.native.mcp.enabled': 'Enabled',
|
||||
'ai.native.mcp.disabled': 'Disabled',
|
||||
'ai.native.mcp.enable.title': 'Start this service',
|
||||
'ai.native.mcp.disable.title': 'Stop this service',
|
||||
|
||||
// MCP View
|
||||
'ai.native.mcp.tool.arguments': 'Arguments',
|
||||
|
|
|
@ -1384,8 +1384,10 @@ export const localizationBundle = {
|
|||
'ai.native.mcp.command.isRequired': '命令不能为空',
|
||||
'ai.native.mcp.serverHost.isRequired': 'SSE URL 不能为空',
|
||||
'ai.native.mcp.manage.connections': '管理你的 MCP 服务器连接',
|
||||
'ai.native.mcp.running': '运行中',
|
||||
'ai.native.mcp.stopped': '已停止',
|
||||
'ai.native.mcp.enabled': '已启用',
|
||||
'ai.native.mcp.disabled': '已禁用',
|
||||
'ai.native.mcp.enable.title': '启动该服务',
|
||||
'ai.native.mcp.disable.title': '停止该服务',
|
||||
|
||||
// MCP View
|
||||
'ai.native.mcp.tool.arguments': '参数',
|
||||
|
|
Loading…
Reference in New Issue