diff --git a/src/renderer/src/components/TopView/index.tsx b/src/renderer/src/components/TopView/index.tsx index 868a1c39..adb089fb 100644 --- a/src/renderer/src/components/TopView/index.tsx +++ b/src/renderer/src/components/TopView/index.tsx @@ -31,6 +31,8 @@ const TopViewContainer: React.FC = ({ children }) => { const [messageApi, messageContextHolder] = message.useMessage() const [modal, modalContextHolder] = Modal.useModal() + console.debug('TopViewContainer', elements) + useAppInit() useEffect(() => { diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 41d294af..da654d1f 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -835,7 +835,10 @@ "usage": "Usage", "npm": "NPM", "version": "Version", - "actions": "Actions" + "actions": "Actions", + "scope_required": "Please enter npm scope", + "no_packages": "No packages found", + "search_error": "Search error" } }, "messages.divider": "Show divider between messages", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 5d25c9cd..e310b15f 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -835,7 +835,10 @@ "usage": "使用法", "npm": "NPM", "version": "バージョン", - "actions": "アクション" + "actions": "アクション", + "scope_required": "npm スコープを入力してください", + "no_packages": "パッケージが見つかりません", + "search_error": "パッケージの検索に失敗しました" } }, "messages.divider": "メッセージ間に区切り線を表示", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 4c00a130..13a3539b 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -835,7 +835,10 @@ "usage": "Использование", "npm": "NPM", "version": "Версия", - "actions": "Действия" + "actions": "Действия", + "scope_required": "Пожалуйста, введите область npm", + "no_packages": "Ничего не найдено", + "search_error": "Ошибка поиска" } }, "messages.divider": "Показывать разделитель между сообщениями", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index a5d5703f..8592f91f 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -835,7 +835,10 @@ "usage": "用法", "npm": "NPM", "version": "版本", - "actions": "操作" + "actions": "操作", + "scope_required": "请输入 npm 作用域", + "no_packages": "未找到包", + "search_error": "搜索失败" } }, "messages.divider": "消息分割线", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index d5bf7df8..8bb89750 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -835,7 +835,10 @@ "usage": "用法", "npm": "NPM", "version": "版本", - "actions": "操作" + "actions": "操作", + "scope_required": "請輸入 npm 作用域", + "no_packages": "未找到包", + "search_error": "搜索失敗" } }, "messages.divider": "訊息間顯示分隔線", diff --git a/src/renderer/src/pages/settings/MCPSettings.tsx b/src/renderer/src/pages/settings/MCPSettings.tsx deleted file mode 100644 index ed94d931..00000000 --- a/src/renderer/src/pages/settings/MCPSettings.tsx +++ /dev/null @@ -1,497 +0,0 @@ -import { DeleteOutlined, EditOutlined, PlusOutlined, QuestionCircleOutlined, SearchOutlined } from '@ant-design/icons' -import { useTheme } from '@renderer/context/ThemeProvider' -import { useAppSelector } from '@renderer/store' -import { MCPServer } from '@renderer/types' -import { Button, Card, Form, Input, Modal, Radio, Space, Spin, Switch, Table, Tag, Tooltip, Typography } from 'antd' -import TextArea from 'antd/es/input/TextArea' -import { npxFinder } from 'npx-scope-finder' -import { FC, useEffect, useState } from 'react' -import { useTranslation } from 'react-i18next' - -import { SettingContainer, SettingDivider, SettingGroup, SettingTitle } from '.' - -interface MCPFormValues { - name: string - description?: string - serverType: 'sse' | 'stdio' - baseUrl?: string - command?: string - args?: string - env?: string - isActive: boolean -} - -interface SearchResult { - name: string - description: string - version: string - usage: string - npmLink: string - fullName: string -} - -const MCPSettings: FC = () => { - const { t } = useTranslation() - const { theme } = useTheme() - const { Paragraph, Text } = Typography - const mcpServers = useAppSelector((state) => state.mcp.servers) - - const [isModalVisible, setIsModalVisible] = useState(false) - const [editingServer, setEditingServer] = useState(null) - const [loading, setLoading] = useState(false) - const [form] = Form.useForm() - const [serverType, setServerType] = useState<'sse' | 'stdio'>('stdio') - - // Add new state variables for npm scope search - const [npmScope, setNpmScope] = useState('') - const [searchLoading, setSearchLoading] = useState(false) - const [searchResults, setSearchResults] = useState([]) - - // Add new function to handle npm scope search - const handleNpmSearch = async () => { - if (!npmScope.trim()) { - window.message.warning('Please enter an npm scope') - return - } - - setSearchLoading(true) - try { - // Call npxFinder to search for packages - const packages = await npxFinder(npmScope) - - // Map the packages to our desired format - const formattedResults = packages.map((pkg) => { - return { - key: pkg.name, - name: pkg.name || '', - description: pkg.description || 'No description available', - version: pkg.version || 'Latest', - usage: `npx ${pkg.name}`, - npmLink: pkg.links?.npm || `https://www.npmjs.com/package/${pkg.name}`, - fullName: pkg.name || '' - } - }) - - setSearchResults(formattedResults) - - if (formattedResults.length === 0) { - window.message.info('No packages found for this scope') - } - } catch (error: any) { - window.message.error(`Failed to search npm packages: ${error.message}`) - } finally { - setSearchLoading(false) - } - } - - // Watch the serverType field to update the form layout dynamically - useEffect(() => { - const type = form.getFieldValue('serverType') - if (type) { - setServerType(type) - } - }, [form]) - - const showAddModal = () => { - form.resetFields() - form.setFieldsValue({ serverType: 'stdio', isActive: true }) - setServerType('stdio') - setIsModalVisible(true) - } - - const showEditModal = (server: MCPServer) => { - setEditingServer(server) - // Determine server type based on server properties - const serverType = server.baseUrl ? 'sse' : 'stdio' - setServerType(serverType) - - form.setFieldsValue({ - name: server.name, - description: server.description, - serverType: serverType, - baseUrl: server.baseUrl || '', - command: server.command || '', - args: server.args ? server.args.join('\n') : '', - env: server.env - ? Object.entries(server.env) - .map(([key, value]) => `${key}=${value}`) - .join('\n') - : '', - isActive: server.isActive - }) - setIsModalVisible(true) - } - - const handleCancel = () => { - setIsModalVisible(false) - form.resetFields() - } - - const handleSubmit = async () => { - setLoading(true) - try { - const values = await form.validateFields() - const mcpServer: MCPServer = { - name: values.name, - description: values.description, - isActive: values.isActive - } - - if (values.serverType === 'sse') { - mcpServer.baseUrl = values.baseUrl - } else { - mcpServer.command = values.command - mcpServer.args = values.args ? values.args.split('\n').filter((arg) => arg.trim() !== '') : [] - - const env: Record = {} - if (values.env) { - values.env.split('\n').forEach((line) => { - if (line.trim()) { - const [key, ...chunks] = line.split('=') - const value = chunks.join('=') - if (key && value) { - env[key.trim()] = value.trim() - } - } - }) - } - mcpServer.env = Object.keys(env).length > 0 ? env : undefined - } - - if (editingServer) { - try { - await window.api.mcp.updateServer(mcpServer) - window.message.success(t('settings.mcp.updateSuccess')) - setLoading(false) - setIsModalVisible(false) - form.resetFields() - } catch (error: any) { - window.message.error(`${t('settings.mcp.updateError')}: ${error.message}`) - setLoading(false) - } - } else { - // Check for duplicate name - if (mcpServers.some((server: MCPServer) => server.name === mcpServer.name)) { - window.message.error(t('settings.mcp.duplicateName')) - setLoading(false) - return - } - - try { - await window.api.mcp.addServer(mcpServer) - window.message.success(t('settings.mcp.addSuccess')) - setLoading(false) - setIsModalVisible(false) - form.resetFields() - } catch (error: any) { - window.message.error(`${t('settings.mcp.addError')}: ${error.message}`) - setLoading(false) - } - } - } catch (error: any) { - setLoading(false) - } - } - - const handleDelete = (serverName: string) => { - window.modal.confirm({ - title: t('settings.mcp.confirmDelete'), - content: t('settings.mcp.confirmDeleteMessage'), - okText: t('common.delete'), - okButtonProps: { danger: true }, - cancelText: t('common.cancel'), - centered: true, - onOk: async () => { - try { - await window.api.mcp.deleteServer(serverName) - window.message.success(t('settings.mcp.deleteSuccess')) - } catch (error: any) { - window.message.error(`${t('settings.mcp.deleteError')}: ${error.message}`) - } - } - }) - } - - const handleToggleActive = async (name: string, isActive: boolean) => { - try { - await window.api.mcp.setServerActive(name, isActive) - } catch (error: any) { - window.message.error(`${t('settings.mcp.toggleError')}: ${error.message}`) - } - } - - const columns = [ - { - title: t('settings.mcp.name'), - dataIndex: 'name', - key: 'name', - width: '300px', - render: (text: string, record: MCPServer) => {text} - }, - { - title: t('settings.mcp.type'), - key: 'type', - width: '100px', - render: (_: any, record: MCPServer) => {record.baseUrl ? 'SSE' : 'STDIO'} - }, - { - title: t('settings.mcp.description'), - dataIndex: 'description', - key: 'description', - width: 'auto', - render: (text: string) => { - if (!text) { - return ( - - {t('common.description')} - - ) - } - - return ( - {}, // Empty callback required for proper functionality - tooltip: true - }} - style={{ marginBottom: 0 }}> - {text} - - ) - } - }, - { - title: t('settings.mcp.active'), - dataIndex: 'isActive', - key: 'isActive', - width: '100px', - render: (isActive: boolean, record: MCPServer) => ( - handleToggleActive(record.name, checked)} /> - ) - }, - { - title: t('settings.mcp.actions'), - key: 'actions', - width: '100px', - render: (_: any, record: MCPServer) => ( - - - - - {mcpServers.length}{' '} - {mcpServers.length === 1 ? t('settings.mcp.serverSingular') : t('settings.mcp.serverPlural')} - - - - - (!record.isActive ? 'inactive-row' : '')} - onRow={(record) => ({ - style: !record.isActive ? inactiveRowStyle : {} - })} - /> - - - - - {t('settings.mcp.npx_list.title')} - - - {t('settings.mcp.npx_list.desc')} - - - - - setNpmScope(e.target.value)} - onPressEnter={handleNpmSearch} - /> - - - - {searchLoading ? ( -
- -
- ) : searchResults.length > 0 ? ( - - dataSource={searchResults} - columns={[ - { - title: t('settings.mcp.npx_list.package_name'), - dataIndex: 'name', - key: 'name', - width: '200px' - }, - { - title: t('settings.mcp.npx_list.description'), - key: 'description', - render: (_, record: SearchResult) => ( - - {record.description} - - {t('settings.mcp.npx_list.usage')}: {record.usage} - - - {record.npmLink} - - - ) - }, - { - title: t('settings.mcp.npx_list.version'), - dataIndex: 'version', - key: 'version', - width: '100px' - }, - { - title: t('settings.mcp.npx_list.actions'), - key: 'actions', - width: '100px', - render: (_, record: SearchResult) => ( - - ) - } - ]} - pagination={false} - size="small" - bordered - /> - ) : null} -
-
- - -
- - - - - -