feat(MCPSettings): enhance server management with segmented control and improved layout
This commit is contained in:
parent
a530ce652e
commit
c884b11f01
@ -142,7 +142,7 @@ const McpSettings: React.FC<Props> = ({ server }) => {
|
|||||||
const mcpServer: MCPServer = {
|
const mcpServer: MCPServer = {
|
||||||
id: server.id,
|
id: server.id,
|
||||||
name: values.name,
|
name: values.name,
|
||||||
type: values.serverType,
|
type: values.serverType || server.type,
|
||||||
description: values.description,
|
description: values.description,
|
||||||
isActive: values.isActive,
|
isActive: values.isActive,
|
||||||
registryUrl: values.registryUrl
|
registryUrl: values.registryUrl
|
||||||
@ -343,22 +343,26 @@ const McpSettings: React.FC<Props> = ({ server }) => {
|
|||||||
paddingRight: '10px'
|
paddingRight: '10px'
|
||||||
}}>
|
}}>
|
||||||
<Form.Item name="name" label={t('settings.mcp.name')} rules={[{ required: true, message: '' }]}>
|
<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>
|
||||||
<Form.Item name="description" label={t('settings.mcp.description')}>
|
<Form.Item name="description" label={t('settings.mcp.description')}>
|
||||||
<TextArea rows={2} placeholder={t('common.description')} />
|
<TextArea rows={2} placeholder={t('common.description')} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="serverType" label={t('settings.mcp.type')} rules={[{ required: true }]} initialValue="stdio">
|
{server.type !== 'inMemory' && (
|
||||||
<Radio.Group
|
<Form.Item
|
||||||
onChange={(e) => setServerType(e.target.value)}
|
name="serverType"
|
||||||
disabled={server.type === 'inMemory'}
|
label={t('settings.mcp.type')}
|
||||||
options={[
|
rules={[{ required: true }]}
|
||||||
{ label: t('settings.mcp.stdio'), value: 'stdio' },
|
initialValue="stdio">
|
||||||
{ label: t('settings.mcp.sse'), value: 'sse' },
|
<Radio.Group
|
||||||
{ label: t('settings.mcp.inMemory'), value: 'inMemory' }
|
onChange={(e) => setServerType(e.target.value)}
|
||||||
]}
|
options={[
|
||||||
/>
|
{ label: t('settings.mcp.stdio'), value: 'stdio' },
|
||||||
</Form.Item>
|
{ label: t('settings.mcp.sse'), value: 'sse' }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
{serverType === 'sse' && (
|
{serverType === 'sse' && (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="baseUrl"
|
name="baseUrl"
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { CodeOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons'
|
|||||||
import { nanoid } from '@reduxjs/toolkit'
|
import { nanoid } from '@reduxjs/toolkit'
|
||||||
import DragableList from '@renderer/components/DragableList'
|
import DragableList from '@renderer/components/DragableList'
|
||||||
import IndicatorLight from '@renderer/components/IndicatorLight'
|
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 ListItem from '@renderer/components/ListItem'
|
||||||
import Scrollbar from '@renderer/components/Scrollbar'
|
import Scrollbar from '@renderer/components/Scrollbar'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
@ -10,7 +10,7 @@ import { useMCPServers } from '@renderer/hooks/useMCPServers'
|
|||||||
import { EventEmitter } from '@renderer/services/EventService'
|
import { EventEmitter } from '@renderer/services/EventService'
|
||||||
import { initializeMCPServers } from '@renderer/store/mcp'
|
import { initializeMCPServers } from '@renderer/store/mcp'
|
||||||
import { MCPServer } from '@renderer/types'
|
import { MCPServer } from '@renderer/types'
|
||||||
import { Dropdown, MenuProps } from 'antd'
|
import { Dropdown, MenuProps, Segmented } from 'antd'
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty } from 'lodash'
|
||||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react'
|
import { FC, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -29,6 +29,17 @@ const MCPSettings: FC = () => {
|
|||||||
const [route, setRoute] = useState<'npx-search' | 'mcp-install' | null>(null)
|
const [route, setRoute] = useState<'npx-search' | 'mcp-install' | null>(null)
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const dispatch = useDispatch()
|
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(() => {
|
useEffect(() => {
|
||||||
const unsubs = [
|
const unsubs = [
|
||||||
@ -121,45 +132,63 @@ const MCPSettings: FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<McpList>
|
<McpListContainer>
|
||||||
<ListItem
|
<McpListHeader>
|
||||||
key="add"
|
<Segmented
|
||||||
title={t('settings.mcp.addServer')}
|
size="middle"
|
||||||
active={false}
|
style={{ width: '100%' }}
|
||||||
onClick={onAddMcpServer}
|
block
|
||||||
icon={<PlusOutlined />}
|
shape="round"
|
||||||
titleStyle={{ fontWeight: 500 }}
|
value={mcpListType}
|
||||||
style={{ marginBottom: 5 }}
|
options={[
|
||||||
/>
|
{ value: 'user', label: '我的' },
|
||||||
<DragableList list={mcpServers} onUpdate={updateMcpServers}>
|
{ value: 'system', label: '系统' }
|
||||||
{(server: MCPServer) => (
|
]}
|
||||||
<Dropdown menu={{ items: getMenuItems(server) }} trigger={['contextMenu']} key={server.id}>
|
onChange={(value) => setMcpListType(value as 'system' | 'user')}
|
||||||
<div>
|
/>
|
||||||
<ListItem
|
</McpListHeader>
|
||||||
key={server.id}
|
<McpList>
|
||||||
title={server.name}
|
{mcpListType === 'user' && (
|
||||||
active={selectedMcpServer?.id === server.id}
|
<ListItem
|
||||||
onClick={() => {
|
key="add"
|
||||||
setSelectedMcpServer(server)
|
title={t('settings.mcp.addServer')}
|
||||||
setRoute(null)
|
active={false}
|
||||||
}}
|
onClick={onAddMcpServer}
|
||||||
titleStyle={{ fontWeight: 500 }}
|
icon={<PlusOutlined />}
|
||||||
icon={<CodeOutlined />}
|
titleStyle={{ fontWeight: 500 }}
|
||||||
rightContent={
|
style={{ width: '100%' }}
|
||||||
<IndicatorLight
|
/>
|
||||||
size={6}
|
|
||||||
color={server.isActive ? 'green' : 'var(--color-text-3)'}
|
|
||||||
animation={server.isActive}
|
|
||||||
shadow={false}
|
|
||||||
style={{ marginRight: 4 }}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Dropdown>
|
|
||||||
)}
|
)}
|
||||||
</DragableList>
|
<DragableList list={servers} onUpdate={updateMcpServers}>
|
||||||
</McpList>
|
{(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}
|
{MainContent}
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
@ -169,14 +198,26 @@ const Container = styled(HStack)`
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
`
|
`
|
||||||
|
|
||||||
const McpList = styled(Scrollbar)`
|
const McpListContainer = styled(VStack)`
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 5px;
|
|
||||||
width: var(--settings-width);
|
width: var(--settings-width);
|
||||||
padding: 12px;
|
|
||||||
border-right: 0.5px solid var(--color-border);
|
border-right: 0.5px solid var(--color-border);
|
||||||
height: calc(100vh - var(--navbar-height));
|
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 {
|
.iconfont {
|
||||||
color: var(--color-text-2);
|
color: var(--color-text-2);
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user