feat: update localization strings and add EditMcpJsonPopup component
- Replaced "JSON Schema" and "Normal mode" with "Edit JSON" in localization files for English, Japanese, Russian, Simplified Chinese, and Traditional Chinese. - Introduced a new EditMcpJsonPopup component for editing MCP server configurations in JSON format, enhancing user experience and modularity in MCPSettings.
This commit is contained in:
parent
ed59e0f47e
commit
94ba450323
@ -846,8 +846,7 @@
|
|||||||
"no_packages": "No packages found",
|
"no_packages": "No packages found",
|
||||||
"search_error": "Search error"
|
"search_error": "Search error"
|
||||||
},
|
},
|
||||||
"jsonMode": "JSON Schema",
|
"editJson": "Edit JSON",
|
||||||
"normalMode": "Normal mode",
|
|
||||||
"jsonModeHint": "Edit the JSON representation of the MCP server configuration. Please ensure the format is correct before saving.",
|
"jsonModeHint": "Edit the JSON representation of the MCP server configuration. Please ensure the format is correct before saving.",
|
||||||
"jsonFormatError": "JSON formatting error",
|
"jsonFormatError": "JSON formatting error",
|
||||||
"jsonSaveSuccess": "JSON configuration has been saved.",
|
"jsonSaveSuccess": "JSON configuration has been saved.",
|
||||||
|
|||||||
@ -846,8 +846,7 @@
|
|||||||
"no_packages": "パッケージが見つかりません",
|
"no_packages": "パッケージが見つかりません",
|
||||||
"search_error": "パッケージの検索に失敗しました"
|
"search_error": "パッケージの検索に失敗しました"
|
||||||
},
|
},
|
||||||
"jsonMode": "JSONスキーマ",
|
"editJson": "JSONを編集",
|
||||||
"normalMode": "通常モード",
|
|
||||||
"jsonModeHint": "MCPサーバー設定のJSON表現を編集します。保存する前に、フォーマットが正しいことを確認してください。",
|
"jsonModeHint": "MCPサーバー設定のJSON表現を編集します。保存する前に、フォーマットが正しいことを確認してください。",
|
||||||
"jsonFormatError": "JSONフォーマットエラー",
|
"jsonFormatError": "JSONフォーマットエラー",
|
||||||
"jsonSaveSuccess": "JSON設定が保存されました。",
|
"jsonSaveSuccess": "JSON設定が保存されました。",
|
||||||
|
|||||||
@ -846,8 +846,7 @@
|
|||||||
"no_packages": "Ничего не найдено",
|
"no_packages": "Ничего не найдено",
|
||||||
"search_error": "Ошибка поиска"
|
"search_error": "Ошибка поиска"
|
||||||
},
|
},
|
||||||
"jsonMode": "JSON-схема",
|
"editJson": "Редактировать JSON",
|
||||||
"normalMode": "Обычный режим",
|
|
||||||
"jsonModeHint": "Редактируйте JSON-форматирование конфигурации сервера MCP. Перед сохранением убедитесь, что формат правильный.",
|
"jsonModeHint": "Редактируйте JSON-форматирование конфигурации сервера MCP. Перед сохранением убедитесь, что формат правильный.",
|
||||||
"jsonFormatError": "Ошибка форматирования JSON",
|
"jsonFormatError": "Ошибка форматирования JSON",
|
||||||
"jsonSaveSuccess": "JSON конфигурация сохранена",
|
"jsonSaveSuccess": "JSON конфигурация сохранена",
|
||||||
|
|||||||
@ -846,8 +846,7 @@
|
|||||||
"no_packages": "未找到包",
|
"no_packages": "未找到包",
|
||||||
"search_error": "搜索失败"
|
"search_error": "搜索失败"
|
||||||
},
|
},
|
||||||
"jsonMode": "JSON模式",
|
"editJson": "编辑JSON",
|
||||||
"normalMode": "常规模式",
|
|
||||||
"jsonModeHint": "编辑MCP服务器配置的JSON表示。保存前请确保格式正确。",
|
"jsonModeHint": "编辑MCP服务器配置的JSON表示。保存前请确保格式正确。",
|
||||||
"jsonFormatError": "JSON格式化错误",
|
"jsonFormatError": "JSON格式化错误",
|
||||||
"jsonSaveSuccess": "JSON配置已保存",
|
"jsonSaveSuccess": "JSON配置已保存",
|
||||||
|
|||||||
@ -846,8 +846,7 @@
|
|||||||
"no_packages": "未找到包",
|
"no_packages": "未找到包",
|
||||||
"search_error": "搜索失敗"
|
"search_error": "搜索失敗"
|
||||||
},
|
},
|
||||||
"jsonMode": "JSON模式",
|
"editJson": "編輯JSON",
|
||||||
"normalMode": "常規模式",
|
|
||||||
"jsonModeHint": "編輯MCP伺服器配置的JSON表示。保存前請確保格式正確。",
|
"jsonModeHint": "編輯MCP伺服器配置的JSON表示。保存前請確保格式正確。",
|
||||||
"jsonFormatError": "JSON格式錯誤",
|
"jsonFormatError": "JSON格式錯誤",
|
||||||
"jsonSaveSuccess": "JSON配置已儲存",
|
"jsonSaveSuccess": "JSON配置已儲存",
|
||||||
|
|||||||
152
src/renderer/src/pages/settings/MCPSettings/EditMcpJsonPopup.tsx
Normal file
152
src/renderer/src/pages/settings/MCPSettings/EditMcpJsonPopup.tsx
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import { TopView } from '@renderer/components/TopView'
|
||||||
|
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||||
|
import { setMCPServers } from '@renderer/store/mcp'
|
||||||
|
import { MCPServer } from '@renderer/types'
|
||||||
|
import { Modal, Typography } from 'antd'
|
||||||
|
import TextArea from 'antd/es/input/TextArea'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
resolve: (data: any) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const PopupContainer: React.FC<Props> = ({ resolve }) => {
|
||||||
|
const [open, setOpen] = useState(true)
|
||||||
|
const [jsonConfig, setJsonConfig] = useState('')
|
||||||
|
const [jsonSaving, setJsonSaving] = useState(false)
|
||||||
|
const [jsonError, setJsonError] = useState('')
|
||||||
|
const mcpServers = useAppSelector((state) => state.mcp.servers)
|
||||||
|
|
||||||
|
const dispatch = useAppDispatch()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const ipcRenderer = window.electron.ipcRenderer
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
const mcpServersObj: Record<string, any> = {}
|
||||||
|
|
||||||
|
mcpServers.forEach((server) => {
|
||||||
|
const { name, ...serverData } = server
|
||||||
|
mcpServersObj[name] = serverData
|
||||||
|
})
|
||||||
|
|
||||||
|
const standardFormat = {
|
||||||
|
mcpServers: mcpServersObj
|
||||||
|
}
|
||||||
|
|
||||||
|
const formattedJson = JSON.stringify(standardFormat, null, 2)
|
||||||
|
setJsonConfig(formattedJson)
|
||||||
|
setJsonError('')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to format JSON:', error)
|
||||||
|
setJsonError(t('settings.mcp.jsonFormatError'))
|
||||||
|
}
|
||||||
|
}, [mcpServers, t])
|
||||||
|
|
||||||
|
const onOk = async () => {
|
||||||
|
setJsonSaving(true)
|
||||||
|
try {
|
||||||
|
if (!jsonConfig.trim()) {
|
||||||
|
dispatch(setMCPServers([]))
|
||||||
|
window.message.success(t('settings.mcp.jsonSaveSuccess'))
|
||||||
|
setJsonError('')
|
||||||
|
setJsonSaving(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const parsedConfig = JSON.parse(jsonConfig)
|
||||||
|
|
||||||
|
if (!parsedConfig.mcpServers || typeof parsedConfig.mcpServers !== 'object') {
|
||||||
|
throw new Error(t('settings.mcp.invalidMcpFormat'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const serversArray: MCPServer[] = []
|
||||||
|
for (const [name, serverConfig] of Object.entries(parsedConfig.mcpServers)) {
|
||||||
|
const server: MCPServer = {
|
||||||
|
name,
|
||||||
|
isActive: false,
|
||||||
|
...(serverConfig as any)
|
||||||
|
}
|
||||||
|
serversArray.push(server)
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(setMCPServers(serversArray))
|
||||||
|
ipcRenderer.send('mcp:servers-from-renderer', mcpServers)
|
||||||
|
|
||||||
|
window.message.success(t('settings.mcp.jsonSaveSuccess'))
|
||||||
|
setJsonError('')
|
||||||
|
setOpen(false)
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Failed to save JSON config:', error)
|
||||||
|
setJsonError(error.message || t('settings.mcp.jsonSaveError'))
|
||||||
|
window.message.error(t('settings.mcp.jsonSaveError'))
|
||||||
|
} finally {
|
||||||
|
setJsonSaving(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCancel = () => {
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClose = () => {
|
||||||
|
resolve({})
|
||||||
|
}
|
||||||
|
|
||||||
|
EditMcpJsonPopup.hide = onCancel
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={t('settings.mcp.editJson')}
|
||||||
|
open={open}
|
||||||
|
onOk={onOk}
|
||||||
|
onCancel={onCancel}
|
||||||
|
afterClose={onClose}
|
||||||
|
width={800}
|
||||||
|
height="80vh"
|
||||||
|
loading={jsonSaving}
|
||||||
|
transitionName="ant-move-down"
|
||||||
|
centered>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
|
||||||
|
<Typography.Text type="secondary">
|
||||||
|
{jsonError ? <span style={{ color: 'red' }}>{jsonError}</span> : ''}
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
<TextArea
|
||||||
|
value={jsonConfig}
|
||||||
|
onChange={(e) => setJsonConfig(e.target.value)}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
minHeight: '60vh',
|
||||||
|
marginBottom: '16px'
|
||||||
|
}}
|
||||||
|
onFocus={() => setJsonError('')}
|
||||||
|
/>
|
||||||
|
<Typography.Text type="secondary">{t('settings.mcp.jsonModeHint')}</Typography.Text>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const TopViewKey = 'EditMcpJsonPopup'
|
||||||
|
|
||||||
|
export default class EditMcpJsonPopup {
|
||||||
|
static topviewId = 0
|
||||||
|
static hide() {
|
||||||
|
TopView.hide(TopViewKey)
|
||||||
|
}
|
||||||
|
static show() {
|
||||||
|
return new Promise<any>((resolve) => {
|
||||||
|
TopView.show(
|
||||||
|
<PopupContainer
|
||||||
|
resolve={(v) => {
|
||||||
|
resolve(v)
|
||||||
|
TopView.hide(TopViewKey)
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
TopViewKey
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,8 +6,8 @@ import styled from 'styled-components'
|
|||||||
import { SettingRow, SettingSubtitle } from '..'
|
import { SettingRow, SettingSubtitle } from '..'
|
||||||
|
|
||||||
const InstallNpxUv: FC = () => {
|
const InstallNpxUv: FC = () => {
|
||||||
const [isUvInstalled, setIsUvInstalled] = useState(false)
|
const [isUvInstalled, setIsUvInstalled] = useState(true)
|
||||||
const [isBunInstalled, setIsBunInstalled] = useState(false)
|
const [isBunInstalled, setIsBunInstalled] = useState(true)
|
||||||
const [isInstallingUv, setIsInstallingUv] = useState(false)
|
const [isInstallingUv, setIsInstallingUv] = useState(false)
|
||||||
const [isInstallingBun, setIsInstallingBun] = useState(false)
|
const [isInstallingBun, setIsInstallingBun] = useState(false)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|||||||
@ -67,10 +67,10 @@ const NpxSearch: FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingGroup theme={theme}>
|
<SettingGroup theme={theme} style={{ marginTop: 15 }}>
|
||||||
<SettingTitle>{t('settings.mcp.npx_list.title')}</SettingTitle>
|
<SettingTitle>{t('settings.mcp.npx_list.title')}</SettingTitle>
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
<Paragraph type="secondary" style={{ margin: '0 0 20px 0' }}>
|
<Paragraph type="secondary" style={{ margin: '0 0 10px 0' }}>
|
||||||
{t('settings.mcp.npx_list.desc')}
|
{t('settings.mcp.npx_list.desc')}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ const NpxSearch: FC = () => {
|
|||||||
onChange={(e) => setNpmScope(e.target.value)}
|
onChange={(e) => setNpmScope(e.target.value)}
|
||||||
onPressEnter={handleNpmSearch}
|
onPressEnter={handleNpmSearch}
|
||||||
/>
|
/>
|
||||||
<Button type="primary" icon={<SearchOutlined />} onClick={handleNpmSearch} disabled={searchLoading}>
|
<Button icon={<SearchOutlined />} onClick={handleNpmSearch} disabled={searchLoading}>
|
||||||
{t('settings.mcp.npx_list.search')}
|
{t('settings.mcp.npx_list.search')}
|
||||||
</Button>
|
</Button>
|
||||||
</Space.Compact>
|
</Space.Compact>
|
||||||
|
|||||||
@ -1,14 +1,15 @@
|
|||||||
import { DeleteOutlined, EditOutlined, PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons'
|
import { DeleteOutlined, EditOutlined, PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons'
|
||||||
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
import { useAppSelector } from '@renderer/store'
|
||||||
import { setMCPServers } from '@renderer/store/mcp'
|
|
||||||
import { MCPServer } from '@renderer/types'
|
import { MCPServer } from '@renderer/types'
|
||||||
import { Button, Card, Input, Space, Switch, Table, Tabs, Tag, Tooltip, Typography } from 'antd'
|
import { Button, Space, Switch, Table, Tag, Tooltip, Typography } from 'antd'
|
||||||
import { FC, useState } from 'react'
|
import { FC } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { SettingContainer, SettingDivider, SettingGroup, SettingTitle } from '..'
|
import { SettingContainer, SettingDivider, SettingGroup, SettingTitle } from '..'
|
||||||
import AddMcpServerPopup from './AddMcpServerPopup'
|
import AddMcpServerPopup from './AddMcpServerPopup'
|
||||||
|
import EditMcpJsonPopup from './EditMcpJsonPopup'
|
||||||
import InstallNpxUv from './InstallNpxUv'
|
import InstallNpxUv from './InstallNpxUv'
|
||||||
import NpxSearch from './NpxSearch'
|
import NpxSearch from './NpxSearch'
|
||||||
|
|
||||||
@ -16,13 +17,6 @@ const MCPSettings: FC = () => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const { Paragraph, Text } = Typography
|
const { Paragraph, Text } = Typography
|
||||||
const { TextArea } = Input
|
|
||||||
const [activeTab, setActiveTab] = useState('normal')
|
|
||||||
const [jsonConfig, setJsonConfig] = useState('')
|
|
||||||
const [jsonSaving, setJsonSaving] = useState(false)
|
|
||||||
const [jsonError, setJsonError] = useState('')
|
|
||||||
const dispatch = useAppDispatch()
|
|
||||||
const ipcRenderer = window.electron.ipcRenderer
|
|
||||||
const mcpServers = useAppSelector((state) => state.mcp.servers)
|
const mcpServers = useAppSelector((state) => state.mcp.servers)
|
||||||
|
|
||||||
const handleDelete = (serverName: string) => {
|
const handleDelete = (serverName: string) => {
|
||||||
@ -52,72 +46,6 @@ const MCPSettings: FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleTabChange = (key: string) => {
|
|
||||||
setActiveTab(key)
|
|
||||||
|
|
||||||
if (key === 'json') {
|
|
||||||
try {
|
|
||||||
const mcpServersObj: Record<string, any> = {}
|
|
||||||
|
|
||||||
mcpServers.forEach((server) => {
|
|
||||||
const { name, ...serverData } = server
|
|
||||||
mcpServersObj[name] = serverData
|
|
||||||
})
|
|
||||||
|
|
||||||
const standardFormat = {
|
|
||||||
mcpServers: mcpServersObj
|
|
||||||
}
|
|
||||||
|
|
||||||
const formattedJson = JSON.stringify(standardFormat, null, 2)
|
|
||||||
setJsonConfig(formattedJson)
|
|
||||||
setJsonError('')
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to format JSON:', error)
|
|
||||||
setJsonError(t('settings.mcp.jsonFormatError'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSaveJson = async () => {
|
|
||||||
setJsonSaving(true)
|
|
||||||
try {
|
|
||||||
if (!jsonConfig.trim()) {
|
|
||||||
dispatch(setMCPServers([]))
|
|
||||||
window.message.success(t('settings.mcp.jsonSaveSuccess'))
|
|
||||||
setJsonError('')
|
|
||||||
setJsonSaving(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const parsedConfig = JSON.parse(jsonConfig)
|
|
||||||
|
|
||||||
if (!parsedConfig.mcpServers || typeof parsedConfig.mcpServers !== 'object') {
|
|
||||||
throw new Error(t('settings.mcp.invalidMcpFormat'))
|
|
||||||
}
|
|
||||||
|
|
||||||
const serversArray: MCPServer[] = []
|
|
||||||
for (const [name, serverConfig] of Object.entries(parsedConfig.mcpServers)) {
|
|
||||||
const server: MCPServer = {
|
|
||||||
name,
|
|
||||||
isActive: false,
|
|
||||||
...(serverConfig as any)
|
|
||||||
}
|
|
||||||
serversArray.push(server)
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(setMCPServers(serversArray))
|
|
||||||
ipcRenderer.send('mcp:servers-from-renderer', mcpServers)
|
|
||||||
|
|
||||||
window.message.success(t('settings.mcp.jsonSaveSuccess'))
|
|
||||||
setJsonError('')
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error('Failed to save JSON config:', error)
|
|
||||||
setJsonError(error.message || t('settings.mcp.jsonSaveError'))
|
|
||||||
window.message.error(t('settings.mcp.jsonSaveError'))
|
|
||||||
} finally {
|
|
||||||
setJsonSaving(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: t('settings.mcp.name'),
|
title: t('settings.mcp.name'),
|
||||||
@ -209,79 +137,32 @@ const MCPSettings: FC = () => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</SettingTitle>
|
</SettingTitle>
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
<Paragraph type="secondary" style={{ margin: 0 }}>
|
<HStack gap={15} alignItems="center">
|
||||||
{t('settings.mcp.config_description')}
|
<Button type="primary" icon={<PlusOutlined />} onClick={() => AddMcpServerPopup.show()}>
|
||||||
</Paragraph>
|
{t('settings.mcp.addServer')}
|
||||||
|
</Button>
|
||||||
<Tabs
|
<Button icon={<EditOutlined />} onClick={() => EditMcpJsonPopup.show()}>
|
||||||
activeKey={activeTab}
|
{t('settings.mcp.editJson')}
|
||||||
onChange={handleTabChange}
|
</Button>
|
||||||
items={[
|
</HStack>
|
||||||
{
|
|
||||||
label: t('settings.mcp.normalMode'),
|
|
||||||
key: 'normal',
|
|
||||||
children: (
|
|
||||||
<>
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
|
|
||||||
<Button type="primary" icon={<PlusOutlined />} onClick={() => AddMcpServerPopup.show()}>
|
|
||||||
{t('settings.mcp.addServer')}
|
|
||||||
</Button>
|
|
||||||
<Text type="secondary">
|
|
||||||
{mcpServers.length}{' '}
|
|
||||||
{mcpServers.length === 1 ? t('settings.mcp.serverSingular') : t('settings.mcp.serverPlural')}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Card
|
|
||||||
bordered={false}
|
|
||||||
style={{ background: theme === 'dark' ? '#1f1f1f' : '#fff' }}
|
|
||||||
styles={{ body: { padding: 0 } }}>
|
|
||||||
<Table
|
|
||||||
dataSource={mcpServers}
|
|
||||||
columns={columns}
|
|
||||||
rowKey="name"
|
|
||||||
pagination={false}
|
|
||||||
size="small"
|
|
||||||
locale={{ emptyText: t('settings.mcp.noServers') }}
|
|
||||||
rowClassName={(record) => (!record.isActive ? 'inactive-row' : '')}
|
|
||||||
onRow={(record) => ({
|
|
||||||
style: !record.isActive ? inactiveRowStyle : {}
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('settings.mcp.jsonMode'),
|
|
||||||
key: 'json',
|
|
||||||
children: (
|
|
||||||
<>
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
|
|
||||||
<Button type="primary" onClick={handleSaveJson} loading={jsonSaving}>
|
|
||||||
{t('common.save')}
|
|
||||||
</Button>
|
|
||||||
<Text type="secondary">{jsonError ? <span style={{ color: 'red' }}>{jsonError}</span> : ''}</Text>
|
|
||||||
</div>
|
|
||||||
<Card bordered={false} style={{ background: theme === 'dark' ? '#1f1f1f' : '#fff' }}>
|
|
||||||
<TextArea
|
|
||||||
value={jsonConfig}
|
|
||||||
onChange={(e) => setJsonConfig(e.target.value)}
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
fontFamily: 'monospace',
|
|
||||||
minHeight: '400px',
|
|
||||||
marginBottom: '16px'
|
|
||||||
}}
|
|
||||||
onFocus={() => setJsonError('')}
|
|
||||||
/>
|
|
||||||
<Text type="secondary">{t('settings.mcp.jsonModeHint')}</Text>
|
|
||||||
</Card>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
]}></Tabs>
|
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
|
<Table
|
||||||
|
dataSource={mcpServers}
|
||||||
|
columns={columns}
|
||||||
|
rowKey="name"
|
||||||
|
pagination={false}
|
||||||
|
size="small"
|
||||||
|
locale={{ emptyText: t('settings.mcp.noServers') }}
|
||||||
|
rowClassName={(record) => (!record.isActive ? 'inactive-row' : '')}
|
||||||
|
onRow={(record) => ({
|
||||||
|
style: !record.isActive ? inactiveRowStyle : {}
|
||||||
|
})}
|
||||||
|
style={{
|
||||||
|
borderRadius: '8px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
border: '0.5px solid var(--color-border)'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<NpxSearch />
|
<NpxSearch />
|
||||||
</SettingContainer>
|
</SettingContainer>
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user