feat(MCPSettings): enhance server management with segmented control and improved layout

This commit is contained in:
kangfenmao 2025-04-06 16:59:09 +08:00
parent a530ce652e
commit c884b11f01
2 changed files with 103 additions and 58 deletions

View File

@ -142,7 +142,7 @@ const McpSettings: React.FC<Props> = ({ server }) => {
const mcpServer: MCPServer = {
id: server.id,
name: values.name,
type: values.serverType,
type: values.serverType || server.type,
description: values.description,
isActive: values.isActive,
registryUrl: values.registryUrl
@ -343,22 +343,26 @@ const McpSettings: React.FC<Props> = ({ server }) => {
paddingRight: '10px'
}}>
<Form.Item name="name" label={t('settings.mcp.name')} rules={[{ required: true, message: '' }]}>
<Input placeholder={t('common.name')} />
<Input placeholder={t('common.name')} disabled={server.type === 'inMemory'} />
</Form.Item>
<Form.Item name="description" label={t('settings.mcp.description')}>
<TextArea rows={2} placeholder={t('common.description')} />
</Form.Item>
<Form.Item name="serverType" label={t('settings.mcp.type')} rules={[{ required: true }]} initialValue="stdio">
<Radio.Group
onChange={(e) => setServerType(e.target.value)}
disabled={server.type === 'inMemory'}
options={[
{ label: t('settings.mcp.stdio'), value: 'stdio' },
{ label: t('settings.mcp.sse'), value: 'sse' },
{ label: t('settings.mcp.inMemory'), value: 'inMemory' }
]}
/>
</Form.Item>
{server.type !== 'inMemory' && (
<Form.Item
name="serverType"
label={t('settings.mcp.type')}
rules={[{ required: true }]}
initialValue="stdio">
<Radio.Group
onChange={(e) => setServerType(e.target.value)}
options={[
{ label: t('settings.mcp.stdio'), value: 'stdio' },
{ label: t('settings.mcp.sse'), value: 'sse' }
]}
/>
</Form.Item>
)}
{serverType === 'sse' && (
<Form.Item
name="baseUrl"

View File

@ -2,7 +2,7 @@ import { CodeOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons'
import { nanoid } from '@reduxjs/toolkit'
import DragableList from '@renderer/components/DragableList'
import IndicatorLight from '@renderer/components/IndicatorLight'
import { HStack } from '@renderer/components/Layout'
import { HStack, VStack } from '@renderer/components/Layout'
import ListItem from '@renderer/components/ListItem'
import Scrollbar from '@renderer/components/Scrollbar'
import { useTheme } from '@renderer/context/ThemeProvider'
@ -10,7 +10,7 @@ import { useMCPServers } from '@renderer/hooks/useMCPServers'
import { EventEmitter } from '@renderer/services/EventService'
import { initializeMCPServers } from '@renderer/store/mcp'
import { MCPServer } from '@renderer/types'
import { Dropdown, MenuProps } from 'antd'
import { Dropdown, MenuProps, Segmented } from 'antd'
import { isEmpty } from 'lodash'
import { FC, useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -29,6 +29,17 @@ const MCPSettings: FC = () => {
const [route, setRoute] = useState<'npx-search' | 'mcp-install' | null>(null)
const { theme } = useTheme()
const dispatch = useDispatch()
const [mcpListType, setMcpListType] = useState<'system' | 'user'>('system')
const systemServers = mcpServers.filter((server) => {
return server.type === 'inMemory'
})
const userServers = mcpServers.filter((server) => {
return server.type !== 'inMemory'
})
const servers = mcpListType === 'system' ? systemServers : userServers
useEffect(() => {
const unsubs = [
@ -121,45 +132,63 @@ const MCPSettings: FC = () => {
return (
<Container>
<McpList>
<ListItem
key="add"
title={t('settings.mcp.addServer')}
active={false}
onClick={onAddMcpServer}
icon={<PlusOutlined />}
titleStyle={{ fontWeight: 500 }}
style={{ marginBottom: 5 }}
/>
<DragableList list={mcpServers} onUpdate={updateMcpServers}>
{(server: MCPServer) => (
<Dropdown menu={{ items: getMenuItems(server) }} trigger={['contextMenu']} key={server.id}>
<div>
<ListItem
key={server.id}
title={server.name}
active={selectedMcpServer?.id === server.id}
onClick={() => {
setSelectedMcpServer(server)
setRoute(null)
}}
titleStyle={{ fontWeight: 500 }}
icon={<CodeOutlined />}
rightContent={
<IndicatorLight
size={6}
color={server.isActive ? 'green' : 'var(--color-text-3)'}
animation={server.isActive}
shadow={false}
style={{ marginRight: 4 }}
/>
}
/>
</div>
</Dropdown>
<McpListContainer>
<McpListHeader>
<Segmented
size="middle"
style={{ width: '100%' }}
block
shape="round"
value={mcpListType}
options={[
{ value: 'user', label: '我的' },
{ value: 'system', label: '系统' }
]}
onChange={(value) => setMcpListType(value as 'system' | 'user')}
/>
</McpListHeader>
<McpList>
{mcpListType === 'user' && (
<ListItem
key="add"
title={t('settings.mcp.addServer')}
active={false}
onClick={onAddMcpServer}
icon={<PlusOutlined />}
titleStyle={{ fontWeight: 500 }}
style={{ width: '100%' }}
/>
)}
</DragableList>
</McpList>
<DragableList list={servers} onUpdate={updateMcpServers}>
{(server: MCPServer) => (
<Dropdown menu={{ items: getMenuItems(server) }} trigger={['contextMenu']} key={server.id}>
<div>
<ListItem
key={server.id}
title={server.name}
active={selectedMcpServer?.id === server.id}
onClick={() => {
setSelectedMcpServer(server)
setRoute(null)
}}
titleStyle={{ fontWeight: 500 }}
icon={<CodeOutlined />}
rightContent={
<IndicatorLight
size={6}
color={server.isActive ? 'green' : 'var(--color-text-3)'}
animation={server.isActive}
shadow={false}
style={{ marginRight: 4 }}
/>
}
/>
</div>
</Dropdown>
)}
</DragableList>
</McpList>
</McpListContainer>
{MainContent}
</Container>
)
@ -169,14 +198,26 @@ const Container = styled(HStack)`
flex: 1;
`
const McpList = styled(Scrollbar)`
display: flex;
flex-direction: column;
gap: 5px;
const McpListContainer = styled(VStack)`
width: var(--settings-width);
padding: 12px;
border-right: 0.5px solid var(--color-border);
height: calc(100vh - var(--navbar-height));
`
const McpListHeader = styled(HStack)`
width: 100%;
padding: 10px 12px;
padding-bottom: 0;
justify-content: center;
`
const McpList = styled(Scrollbar)`
display: flex;
flex: 1;
flex-direction: column;
gap: 5px;
width: 100%;
padding: 12px;
.iconfont {
color: var(--color-text-2);
line-height: 16px;