feat(MCPSettings): enhance MCP server management and localization updates
- Added a new SVG icon for npm in the MCP settings. - Introduced a custom hook `useMCPServer` for retrieving a specific MCP server by ID. - Updated localization files to include new error messages for tool and prompt loading in English, Japanese, Russian, and Chinese. - Refactored MCP settings components for improved navigation and state management, including the use of React Router for routing. - Enhanced the Npx search functionality and UI for better user experience.
This commit is contained in:
parent
b62c59eb52
commit
72e18fbcc1
1
src/renderer/src/assets/images/mcp/npm.svg
Normal file
1
src/renderer/src/assets/images/mcp/npm.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1744456106953" class="icon" viewBox="0 0 2633 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1486" xmlns:xlink="http://www.w3.org/1999/xlink" width="514.2578125" height="200"><path d="M0 0v877.843607h731.440328v146.156393H1316.724263v-146.156393h1316.724262V0z m731.440328 731.196014h-146.156393V292.312786h-146.485574v438.883228H146.485574V146.238688h584.954754z m438.880656 0v146.567869h-292.405369V146.238688H1463.209837v585.037049H1170.320984z m1316.888853 0H2341.30033V292.312786h-146.56787v438.883228h-146.485574V292.312786h-145.909508v438.883228H1609.283935V146.238688h878.008197zM1170.238688 292.477377H1316.724263v292.644539h-146.485575z" fill="#CB3837" p-id="1487"></path></svg>
|
||||||
|
After Width: | Height: | Size: 845 B |
@ -26,3 +26,10 @@ export const useMCPServers = () => {
|
|||||||
updateMcpServers: (servers: MCPServer[]) => dispatch(setMCPServers(servers))
|
updateMcpServers: (servers: MCPServer[]) => dispatch(setMCPServers(servers))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useMCPServer = (id: string) => {
|
||||||
|
const { mcpServers } = useMCPServers()
|
||||||
|
return {
|
||||||
|
server: mcpServers.find((server) => server.id === id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1064,7 +1064,6 @@
|
|||||||
"newServer": "MCP Server",
|
"newServer": "MCP Server",
|
||||||
"npx_list": {
|
"npx_list": {
|
||||||
"actions": "Actions",
|
"actions": "Actions",
|
||||||
"desc": "Search and add npm packages as MCP servers",
|
|
||||||
"description": "Description",
|
"description": "Description",
|
||||||
"no_packages": "No packages found",
|
"no_packages": "No packages found",
|
||||||
"npm": "NPM",
|
"npm": "NPM",
|
||||||
@ -1073,7 +1072,6 @@
|
|||||||
"scope_required": "Please enter npm scope",
|
"scope_required": "Please enter npm scope",
|
||||||
"search": "Search",
|
"search": "Search",
|
||||||
"search_error": "Search error",
|
"search_error": "Search error",
|
||||||
"title": "NPX Package List",
|
|
||||||
"usage": "Usage",
|
"usage": "Usage",
|
||||||
"version": "Version"
|
"version": "Version"
|
||||||
},
|
},
|
||||||
@ -1099,14 +1097,16 @@
|
|||||||
"tools": {
|
"tools": {
|
||||||
"inputSchema": "Input Schema",
|
"inputSchema": "Input Schema",
|
||||||
"availableTools": "Available Tools",
|
"availableTools": "Available Tools",
|
||||||
"noToolsAvailable": "No tools available"
|
"noToolsAvailable": "No tools available",
|
||||||
|
"loadError": "Get tools Error"
|
||||||
},
|
},
|
||||||
"prompts": {
|
"prompts": {
|
||||||
"availablePrompts": "Available Prompts",
|
"availablePrompts": "Available Prompts",
|
||||||
"noPromptsAvailable": "No prompts available",
|
"noPromptsAvailable": "No prompts available",
|
||||||
"arguments": "Arguments",
|
"arguments": "Arguments",
|
||||||
"requiredField": "Required Field",
|
"requiredField": "Required Field",
|
||||||
"genericError": "Get prompt Error"
|
"genericError": "Get prompt Error",
|
||||||
|
"loadError": "Get prompts Error"
|
||||||
},
|
},
|
||||||
"deleteServer": "Delete Server",
|
"deleteServer": "Delete Server",
|
||||||
"deleteServerConfirm": "Are you sure you want to delete this server?",
|
"deleteServerConfirm": "Are you sure you want to delete this server?",
|
||||||
|
|||||||
@ -1063,7 +1063,6 @@
|
|||||||
"newServer": "MCP サーバー",
|
"newServer": "MCP サーバー",
|
||||||
"npx_list": {
|
"npx_list": {
|
||||||
"actions": "アクション",
|
"actions": "アクション",
|
||||||
"desc": "npm パッケージを検索して MCP サーバーとして追加",
|
|
||||||
"description": "説明",
|
"description": "説明",
|
||||||
"no_packages": "パッケージが見つかりません",
|
"no_packages": "パッケージが見つかりません",
|
||||||
"npm": "NPM",
|
"npm": "NPM",
|
||||||
@ -1072,7 +1071,6 @@
|
|||||||
"scope_required": "npm スコープを入力してください",
|
"scope_required": "npm スコープを入力してください",
|
||||||
"search": "検索",
|
"search": "検索",
|
||||||
"search_error": "パッケージの検索に失敗しました",
|
"search_error": "パッケージの検索に失敗しました",
|
||||||
"title": "NPX パッケージリスト",
|
|
||||||
"usage": "使用法",
|
"usage": "使用法",
|
||||||
"version": "バージョン"
|
"version": "バージョン"
|
||||||
},
|
},
|
||||||
@ -1098,14 +1096,16 @@
|
|||||||
"tools": {
|
"tools": {
|
||||||
"inputSchema": "入力スキーマ",
|
"inputSchema": "入力スキーマ",
|
||||||
"availableTools": "利用可能なツール",
|
"availableTools": "利用可能なツール",
|
||||||
"noToolsAvailable": "利用可能なツールなし"
|
"noToolsAvailable": "利用可能なツールなし",
|
||||||
|
"loadError": "ツール取得エラー"
|
||||||
},
|
},
|
||||||
"prompts": {
|
"prompts": {
|
||||||
"availablePrompts": "利用可能なプロンプト",
|
"availablePrompts": "利用可能なプロンプト",
|
||||||
"noPromptsAvailable": "利用可能なプロンプトはありません",
|
"noPromptsAvailable": "利用可能なプロンプトはありません",
|
||||||
"arguments": "引数",
|
"arguments": "引数",
|
||||||
"requiredField": "必須フィールド",
|
"requiredField": "必須フィールド",
|
||||||
"genericError": "プロンプト取得エラー"
|
"genericError": "プロンプト取得エラー",
|
||||||
|
"loadError": "プロンプト取得エラー"
|
||||||
},
|
},
|
||||||
"deleteServer": "サーバーを削除",
|
"deleteServer": "サーバーを削除",
|
||||||
"deleteServerConfirm": "このサーバーを削除してもよろしいですか?",
|
"deleteServerConfirm": "このサーバーを削除してもよろしいですか?",
|
||||||
|
|||||||
@ -1063,7 +1063,6 @@
|
|||||||
"newServer": "MCP сервер",
|
"newServer": "MCP сервер",
|
||||||
"npx_list": {
|
"npx_list": {
|
||||||
"actions": "Действия",
|
"actions": "Действия",
|
||||||
"desc": "Поиск и добавление npm пакетов в качестве MCP серверов",
|
|
||||||
"description": "Описание",
|
"description": "Описание",
|
||||||
"no_packages": "Ничего не найдено",
|
"no_packages": "Ничего не найдено",
|
||||||
"npm": "NPM",
|
"npm": "NPM",
|
||||||
@ -1072,7 +1071,6 @@
|
|||||||
"scope_required": "Пожалуйста, введите область npm",
|
"scope_required": "Пожалуйста, введите область npm",
|
||||||
"search": "Поиск",
|
"search": "Поиск",
|
||||||
"search_error": "Ошибка поиска",
|
"search_error": "Ошибка поиска",
|
||||||
"title": "Список пакетов NPX",
|
|
||||||
"usage": "Использование",
|
"usage": "Использование",
|
||||||
"version": "Версия"
|
"version": "Версия"
|
||||||
},
|
},
|
||||||
@ -1098,14 +1096,16 @@
|
|||||||
"tools": {
|
"tools": {
|
||||||
"inputSchema": "Схема ввода",
|
"inputSchema": "Схема ввода",
|
||||||
"availableTools": "Доступные инструменты",
|
"availableTools": "Доступные инструменты",
|
||||||
"noToolsAvailable": "Нет доступных инструментов"
|
"noToolsAvailable": "Нет доступных инструментов",
|
||||||
|
"loadError": "Ошибка получения инструментов"
|
||||||
},
|
},
|
||||||
"prompts": {
|
"prompts": {
|
||||||
"availablePrompts": "Доступные подсказки",
|
"availablePrompts": "Доступные подсказки",
|
||||||
"noPromptsAvailable": "Нет доступных подсказок",
|
"noPromptsAvailable": "Нет доступных подсказок",
|
||||||
"arguments": "Аргументы",
|
"arguments": "Аргументы",
|
||||||
"requiredField": "Обязательное поле",
|
"requiredField": "Обязательное поле",
|
||||||
"genericError": "Ошибка получения подсказки"
|
"genericError": "Ошибка получения подсказки",
|
||||||
|
"loadError": "Ошибка получения подсказок"
|
||||||
},
|
},
|
||||||
"deleteServer": "Удалить сервер",
|
"deleteServer": "Удалить сервер",
|
||||||
"deleteServerConfirm": "Вы уверены, что хотите удалить этот сервер?",
|
"deleteServerConfirm": "Вы уверены, что хотите удалить этот сервер?",
|
||||||
|
|||||||
@ -1064,7 +1064,6 @@
|
|||||||
"newServer": "MCP 服务器",
|
"newServer": "MCP 服务器",
|
||||||
"npx_list": {
|
"npx_list": {
|
||||||
"actions": "操作",
|
"actions": "操作",
|
||||||
"desc": "搜索并添加 npm 包作为 MCP 服务",
|
|
||||||
"description": "描述",
|
"description": "描述",
|
||||||
"no_packages": "未找到包",
|
"no_packages": "未找到包",
|
||||||
"npm": "NPM",
|
"npm": "NPM",
|
||||||
@ -1073,7 +1072,6 @@
|
|||||||
"scope_required": "请输入 npm 作用域",
|
"scope_required": "请输入 npm 作用域",
|
||||||
"search": "搜索",
|
"search": "搜索",
|
||||||
"search_error": "搜索失败",
|
"search_error": "搜索失败",
|
||||||
"title": "NPX 包列表",
|
|
||||||
"usage": "用法",
|
"usage": "用法",
|
||||||
"version": "版本"
|
"version": "版本"
|
||||||
},
|
},
|
||||||
@ -1099,14 +1097,16 @@
|
|||||||
"tools": {
|
"tools": {
|
||||||
"inputSchema": "输入模式",
|
"inputSchema": "输入模式",
|
||||||
"availableTools": "可用工具",
|
"availableTools": "可用工具",
|
||||||
"noToolsAvailable": "无可用工具"
|
"noToolsAvailable": "无可用工具",
|
||||||
|
"loadError": "获取工具失败"
|
||||||
},
|
},
|
||||||
"prompts": {
|
"prompts": {
|
||||||
"availablePrompts": "可用提示",
|
"availablePrompts": "可用提示",
|
||||||
"noPromptsAvailable": "无可用提示",
|
"noPromptsAvailable": "无可用提示",
|
||||||
"arguments": "参数",
|
"arguments": "参数",
|
||||||
"requiredField": "必填字段",
|
"requiredField": "必填字段",
|
||||||
"genericError": "获取提示错误"
|
"genericError": "获取提示错误",
|
||||||
|
"loadError": "获取提示失败"
|
||||||
},
|
},
|
||||||
"deleteServer": "删除服务器",
|
"deleteServer": "删除服务器",
|
||||||
"deleteServerConfirm": "确定要删除此服务器吗?",
|
"deleteServerConfirm": "确定要删除此服务器吗?",
|
||||||
|
|||||||
@ -1063,7 +1063,6 @@
|
|||||||
"newServer": "MCP 伺服器",
|
"newServer": "MCP 伺服器",
|
||||||
"npx_list": {
|
"npx_list": {
|
||||||
"actions": "操作",
|
"actions": "操作",
|
||||||
"desc": "搜索並添加 npm 包作為 MCP 服務",
|
|
||||||
"description": "描述",
|
"description": "描述",
|
||||||
"no_packages": "未找到包",
|
"no_packages": "未找到包",
|
||||||
"npm": "NPM",
|
"npm": "NPM",
|
||||||
@ -1072,7 +1071,6 @@
|
|||||||
"scope_required": "請輸入 npm 作用域",
|
"scope_required": "請輸入 npm 作用域",
|
||||||
"search": "搜索",
|
"search": "搜索",
|
||||||
"search_error": "搜索失敗",
|
"search_error": "搜索失敗",
|
||||||
"title": "NPX 包列表",
|
|
||||||
"usage": "用法",
|
"usage": "用法",
|
||||||
"version": "版本"
|
"version": "版本"
|
||||||
},
|
},
|
||||||
@ -1098,14 +1096,16 @@
|
|||||||
"tools": {
|
"tools": {
|
||||||
"inputSchema": "輸入模式",
|
"inputSchema": "輸入模式",
|
||||||
"availableTools": "可用工具",
|
"availableTools": "可用工具",
|
||||||
"noToolsAvailable": "無可用工具"
|
"noToolsAvailable": "無可用工具",
|
||||||
|
"loadError": "獲取工具失敗"
|
||||||
},
|
},
|
||||||
"prompts": {
|
"prompts": {
|
||||||
"availablePrompts": "可用提示",
|
"availablePrompts": "可用提示",
|
||||||
"noPromptsAvailable": "無可用提示",
|
"noPromptsAvailable": "無可用提示",
|
||||||
"arguments": "參數",
|
"arguments": "參數",
|
||||||
"requiredField": "必填欄位",
|
"requiredField": "必填欄位",
|
||||||
"genericError": "獲取提示錯誤"
|
"genericError": "獲取提示錯誤",
|
||||||
|
"loadError": "獲取提示失敗"
|
||||||
},
|
},
|
||||||
"deleteServer": "刪除伺服器",
|
"deleteServer": "刪除伺服器",
|
||||||
"deleteServerConfirm": "確定要刪除此伺服器嗎?",
|
"deleteServerConfirm": "確定要刪除此伺服器嗎?",
|
||||||
|
|||||||
@ -912,7 +912,6 @@
|
|||||||
"noServers": "Δεν έχουν ρυθμιστεί διακομιστές",
|
"noServers": "Δεν έχουν ρυθμιστεί διακομιστές",
|
||||||
"npx_list": {
|
"npx_list": {
|
||||||
"actions": "Ενέργειες",
|
"actions": "Ενέργειες",
|
||||||
"desc": "Αναζητήστε και προσθέστε πακέτα npm ως υπηρεσίες MCP",
|
|
||||||
"description": "Περιγραφή",
|
"description": "Περιγραφή",
|
||||||
"no_packages": "Δεν βρέθηκαν πακέτα",
|
"no_packages": "Δεν βρέθηκαν πακέτα",
|
||||||
"npm": "NPM",
|
"npm": "NPM",
|
||||||
@ -921,7 +920,6 @@
|
|||||||
"scope_required": "Παρακαλώ εισαγάγετε το σκοπό του npm",
|
"scope_required": "Παρακαλώ εισαγάγετε το σκοπό του npm",
|
||||||
"search": "Αναζήτηση",
|
"search": "Αναζήτηση",
|
||||||
"search_error": "Η αναζήτηση απέτυχε",
|
"search_error": "Η αναζήτηση απέτυχε",
|
||||||
"title": "Λίστα πακέτων NPX",
|
|
||||||
"usage": "Χρήση",
|
"usage": "Χρήση",
|
||||||
"version": "Έκδοση"
|
"version": "Έκδοση"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -912,7 +912,6 @@
|
|||||||
"noServers": "No se han configurado servidores",
|
"noServers": "No se han configurado servidores",
|
||||||
"npx_list": {
|
"npx_list": {
|
||||||
"actions": "Acciones",
|
"actions": "Acciones",
|
||||||
"desc": "Buscar y agregar paquetes npm como servicios MCP",
|
|
||||||
"description": "Descripción",
|
"description": "Descripción",
|
||||||
"no_packages": "No se encontraron paquetes",
|
"no_packages": "No se encontraron paquetes",
|
||||||
"npm": "NPM",
|
"npm": "NPM",
|
||||||
@ -921,7 +920,6 @@
|
|||||||
"scope_required": "Por favor ingrese el ámbito npm",
|
"scope_required": "Por favor ingrese el ámbito npm",
|
||||||
"search": "Buscar",
|
"search": "Buscar",
|
||||||
"search_error": "Error de búsqueda",
|
"search_error": "Error de búsqueda",
|
||||||
"title": "Lista de paquetes NPX",
|
|
||||||
"usage": "Uso",
|
"usage": "Uso",
|
||||||
"version": "Versión"
|
"version": "Versión"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -912,7 +912,6 @@
|
|||||||
"noServers": "Aucun serveur configuré",
|
"noServers": "Aucun serveur configuré",
|
||||||
"npx_list": {
|
"npx_list": {
|
||||||
"actions": "Actions",
|
"actions": "Actions",
|
||||||
"desc": "Rechercher et ajouter un package npm en tant que service MCP",
|
|
||||||
"description": "Description",
|
"description": "Description",
|
||||||
"no_packages": "Aucun package trouvé",
|
"no_packages": "Aucun package trouvé",
|
||||||
"npm": "NPM",
|
"npm": "NPM",
|
||||||
@ -921,7 +920,6 @@
|
|||||||
"scope_required": "Veuillez entrer le scope npm",
|
"scope_required": "Veuillez entrer le scope npm",
|
||||||
"search": "Rechercher",
|
"search": "Rechercher",
|
||||||
"search_error": "La recherche a échoué",
|
"search_error": "La recherche a échoué",
|
||||||
"title": "Liste des packages NPX",
|
|
||||||
"usage": "Utilisation",
|
"usage": "Utilisation",
|
||||||
"version": "Version"
|
"version": "Version"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -912,7 +912,6 @@
|
|||||||
"noServers": "Nenhum servidor configurado",
|
"noServers": "Nenhum servidor configurado",
|
||||||
"npx_list": {
|
"npx_list": {
|
||||||
"actions": "Ações",
|
"actions": "Ações",
|
||||||
"desc": "Pesquise e adicione pacotes npm como serviço MCP",
|
|
||||||
"description": "Descrição",
|
"description": "Descrição",
|
||||||
"no_packages": "Nenhum pacote encontrado",
|
"no_packages": "Nenhum pacote encontrado",
|
||||||
"npm": "NPM",
|
"npm": "NPM",
|
||||||
@ -921,7 +920,6 @@
|
|||||||
"scope_required": "Insira o escopo npm",
|
"scope_required": "Insira o escopo npm",
|
||||||
"search": "Pesquisar",
|
"search": "Pesquisar",
|
||||||
"search_error": "Falha na pesquisa",
|
"search_error": "Falha na pesquisa",
|
||||||
"title": "Lista de Pacotes NPX",
|
|
||||||
"usage": "Uso",
|
"usage": "Uso",
|
||||||
"version": "Versão"
|
"version": "Versão"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { CheckCircleOutlined, QuestionCircleOutlined, WarningOutlined } from '@ant-design/icons'
|
import { CheckCircleOutlined, QuestionCircleOutlined, WarningOutlined } from '@ant-design/icons'
|
||||||
import { Center, VStack } from '@renderer/components/Layout'
|
import { Center, VStack } from '@renderer/components/Layout'
|
||||||
import { EventEmitter } from '@renderer/services/EventService'
|
|
||||||
import { Alert, Button } from 'antd'
|
import { Alert, Button } from 'antd'
|
||||||
import { FC, useEffect, useState } from 'react'
|
import { FC, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useNavigate } from 'react-router'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import { SettingDescription, SettingRow, SettingSubtitle } from '..'
|
import { SettingDescription, SettingRow, SettingSubtitle } from '..'
|
||||||
@ -21,6 +21,7 @@ const InstallNpxUv: FC<Props> = ({ mini = false }) => {
|
|||||||
const [bunPath, setBunPath] = useState<string | null>(null)
|
const [bunPath, setBunPath] = useState<string | null>(null)
|
||||||
const [binariesDir, setBinariesDir] = useState<string | null>(null)
|
const [binariesDir, setBinariesDir] = useState<string | null>(null)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const checkBinaries = async () => {
|
const checkBinaries = async () => {
|
||||||
const uvExists = await window.api.isBinaryExist('uv')
|
const uvExists = await window.api.isBinaryExist('uv')
|
||||||
@ -78,7 +79,7 @@ const InstallNpxUv: FC<Props> = ({ mini = false }) => {
|
|||||||
icon={installed ? <CheckCircleOutlined /> : <WarningOutlined />}
|
icon={installed ? <CheckCircleOutlined /> : <WarningOutlined />}
|
||||||
className="nodrag"
|
className="nodrag"
|
||||||
color={installed ? 'green' : 'danger'}
|
color={installed ? 'green' : 'danger'}
|
||||||
onClick={() => EventEmitter.emit('mcp:mcp-install')}
|
onClick={() => navigate('/settings/mcp/mcp-install')}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { Button, Flex, Form, Input, Radio, Switch, Tabs } from 'antd'
|
|||||||
import TextArea from 'antd/es/input/TextArea'
|
import TextArea from 'antd/es/input/TextArea'
|
||||||
import React, { useCallback, useEffect, useState } from 'react'
|
import React, { useCallback, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useNavigate } from 'react-router'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import { SettingContainer, SettingDivider, SettingGroup, SettingTitle } from '..'
|
import { SettingContainer, SettingDivider, SettingGroup, SettingTitle } from '..'
|
||||||
@ -58,6 +59,8 @@ const McpSettings: React.FC<Props> = ({ server }) => {
|
|||||||
const [isShowRegistry, setIsShowRegistry] = useState(false)
|
const [isShowRegistry, setIsShowRegistry] = useState(false)
|
||||||
const [registry, setRegistry] = useState<Registry[]>()
|
const [registry, setRegistry] = useState<Registry[]>()
|
||||||
|
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const serverType: MCPServer['type'] = server.type || (server.baseUrl ? 'sse' : 'stdio')
|
const serverType: MCPServer['type'] = server.type || (server.baseUrl ? 'sse' : 'stdio')
|
||||||
setServerType(serverType)
|
setServerType(serverType)
|
||||||
@ -114,10 +117,9 @@ const McpSettings: React.FC<Props> = ({ server }) => {
|
|||||||
setLoadingServer(server.id)
|
setLoadingServer(server.id)
|
||||||
const localTools = await window.api.mcp.listTools(server)
|
const localTools = await window.api.mcp.listTools(server)
|
||||||
setTools(localTools)
|
setTools(localTools)
|
||||||
// window.message.success(t('settings.mcp.toolsLoaded'))
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.message.error({
|
window.message.error({
|
||||||
content: t('settings.mcp.toolsLoadError') + formatError(error),
|
content: t('settings.mcp.tools.loadError') + ' ' + formatError(error),
|
||||||
key: 'mcp-tools-error'
|
key: 'mcp-tools-error'
|
||||||
})
|
})
|
||||||
} finally {
|
} finally {
|
||||||
@ -134,7 +136,7 @@ const McpSettings: React.FC<Props> = ({ server }) => {
|
|||||||
setPrompts(localPrompts)
|
setPrompts(localPrompts)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.message.error({
|
window.message.error({
|
||||||
content: t('settings.mcp.promptsLoadError') + formatError(error),
|
content: t('settings.mcp.prompts.loadError') + ' ' + formatError(error),
|
||||||
key: 'mcp-prompts-error'
|
key: 'mcp-prompts-error'
|
||||||
})
|
})
|
||||||
setPrompts([])
|
setPrompts([])
|
||||||
@ -258,6 +260,7 @@ const McpSettings: React.FC<Props> = ({ server }) => {
|
|||||||
await window.api.mcp.removeServer(server)
|
await window.api.mcp.removeServer(server)
|
||||||
deleteMCPServer(server.id)
|
deleteMCPServer(server.id)
|
||||||
window.message.success({ content: t('settings.mcp.deleteSuccess'), key: 'mcp-list' })
|
window.message.success({ content: t('settings.mcp.deleteSuccess'), key: 'mcp-list' })
|
||||||
|
navigate('/settings/mcp')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|||||||
@ -2,15 +2,16 @@ import { EditOutlined, ExportOutlined, SearchOutlined } from '@ant-design/icons'
|
|||||||
import { NavbarRight } from '@renderer/components/app/Navbar'
|
import { NavbarRight } from '@renderer/components/app/Navbar'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import { isWindows } from '@renderer/config/constant'
|
import { isWindows } from '@renderer/config/constant'
|
||||||
import { EventEmitter } from '@renderer/services/EventService'
|
|
||||||
import { Button } from 'antd'
|
import { Button } from 'antd'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useNavigate } from 'react-router'
|
||||||
|
|
||||||
import EditMcpJsonPopup from './EditMcpJsonPopup'
|
import EditMcpJsonPopup from './EditMcpJsonPopup'
|
||||||
import InstallNpxUv from './InstallNpxUv'
|
import InstallNpxUv from './InstallNpxUv'
|
||||||
|
|
||||||
export const McpSettingsNavbar = () => {
|
export const McpSettingsNavbar = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const navigate = useNavigate()
|
||||||
const onClick = () => window.open('https://mcp.so/', '_blank')
|
const onClick = () => window.open('https://mcp.so/', '_blank')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -19,7 +20,7 @@ export const McpSettingsNavbar = () => {
|
|||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
type="text"
|
type="text"
|
||||||
onClick={() => EventEmitter.emit('mcp:npx-search')}
|
onClick={() => navigate('/settings/mcp/npx-search')}
|
||||||
icon={<SearchOutlined />}
|
icon={<SearchOutlined />}
|
||||||
className="nodrag"
|
className="nodrag"
|
||||||
style={{ fontSize: 13, height: 28, borderRadius: 20 }}>
|
style={{ fontSize: 13, height: 28, borderRadius: 20 }}>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { PlusOutlined, SearchOutlined } from '@ant-design/icons'
|
import { CheckOutlined, PlusOutlined } from '@ant-design/icons'
|
||||||
import { nanoid } from '@reduxjs/toolkit'
|
import { nanoid } from '@reduxjs/toolkit'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import npmLogo from '@renderer/assets/images/mcp/npm.svg'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { Center, HStack } from '@renderer/components/Layout'
|
||||||
import { useMCPServers } from '@renderer/hooks/useMCPServers'
|
import { useMCPServers } from '@renderer/hooks/useMCPServers'
|
||||||
import { builtinMCPServers } from '@renderer/store/mcp'
|
import { builtinMCPServers } from '@renderer/store/mcp'
|
||||||
import { MCPServer } from '@renderer/types'
|
import { MCPServer } from '@renderer/types'
|
||||||
@ -11,8 +11,6 @@ import { type FC, useEffect, useState } from 'react'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import { SettingDivider, SettingGroup, SettingTitle } from '..'
|
|
||||||
|
|
||||||
interface SearchResult {
|
interface SearchResult {
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
@ -29,9 +27,7 @@ let _searchResults: SearchResult[] = []
|
|||||||
|
|
||||||
const NpxSearch: FC<{
|
const NpxSearch: FC<{
|
||||||
setSelectedMcpServer: (server: MCPServer) => void
|
setSelectedMcpServer: (server: MCPServer) => void
|
||||||
setRoute: (route: string | null) => void
|
}> = ({ setSelectedMcpServer }) => {
|
||||||
}> = ({ setSelectedMcpServer, setRoute }) => {
|
|
||||||
const { theme } = useTheme()
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { Text, Link } = Typography
|
const { Text, Link } = Typography
|
||||||
|
|
||||||
@ -39,7 +35,7 @@ const NpxSearch: FC<{
|
|||||||
const [npmScope, setNpmScope] = useState('@cherry')
|
const [npmScope, setNpmScope] = useState('@cherry')
|
||||||
const [searchLoading, setSearchLoading] = useState(false)
|
const [searchLoading, setSearchLoading] = useState(false)
|
||||||
const [searchResults, setSearchResults] = useState<SearchResult[]>(_searchResults)
|
const [searchResults, setSearchResults] = useState<SearchResult[]>(_searchResults)
|
||||||
const { addMCPServer } = useMCPServers()
|
const { addMCPServer, mcpServers } = useMCPServers()
|
||||||
|
|
||||||
_searchResults = searchResults
|
_searchResults = searchResults
|
||||||
|
|
||||||
@ -119,118 +115,134 @@ const NpxSearch: FC<{
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingGroup theme={theme} css={SettingGroup}>
|
<Container>
|
||||||
<div>
|
<Center>
|
||||||
<SettingTitle>
|
<Space direction="vertical" style={{ marginBottom: 20, width: 500 }}>
|
||||||
{t('settings.mcp.npx_list.title')} <Text type="secondary">{t('settings.mcp.npx_list.desc')}</Text>
|
<Center style={{ marginBottom: 20 }}>
|
||||||
</SettingTitle>
|
<img src={npmLogo} alt="npm" width={100} />
|
||||||
<SettingDivider />
|
</Center>
|
||||||
|
<Space.Compact style={{ width: '100%' }}>
|
||||||
<Space direction="vertical" style={{ width: '100%' }}>
|
|
||||||
<Space.Compact style={{ width: '100%', marginBottom: 10 }}>
|
|
||||||
<Input
|
<Input
|
||||||
placeholder={t('settings.mcp.npx_list.scope_placeholder')}
|
placeholder={t('settings.mcp.npx_list.scope_placeholder')}
|
||||||
value={npmScope}
|
value={npmScope}
|
||||||
onChange={(e) => setNpmScope(e.target.value)}
|
onChange={(e) => setNpmScope(e.target.value)}
|
||||||
onPressEnter={() => handleNpmSearch(npmScope)}
|
onPressEnter={() => handleNpmSearch(npmScope)}
|
||||||
|
size="large"
|
||||||
|
styles={{ input: { borderRadius: 100 } }}
|
||||||
/>
|
/>
|
||||||
<Button icon={<SearchOutlined />} onClick={() => handleNpmSearch(npmScope)} disabled={searchLoading}>
|
|
||||||
{t('settings.mcp.npx_list.search')}
|
|
||||||
</Button>
|
|
||||||
</Space.Compact>
|
</Space.Compact>
|
||||||
<HStack alignItems="center" mt="-5px" mb="5px">
|
<HStack alignItems="center" justifyContent="center">
|
||||||
{npmScopes.map((scope) => (
|
{npmScopes.map((scope) => (
|
||||||
<Tag
|
<Tag
|
||||||
key={scope}
|
key={scope}
|
||||||
|
bordered={false}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setNpmScope(scope)
|
setNpmScope(scope)
|
||||||
handleNpmSearch(scope)
|
handleNpmSearch(scope)
|
||||||
}}
|
}}
|
||||||
style={{ cursor: searchLoading ? 'not-allowed' : 'pointer' }}>
|
style={{
|
||||||
|
cursor: searchLoading ? 'not-allowed' : 'pointer',
|
||||||
|
borderRadius: 100,
|
||||||
|
backgroundColor: 'var(--color-background-mute)'
|
||||||
|
}}>
|
||||||
{scope}
|
{scope}
|
||||||
</Tag>
|
</Tag>
|
||||||
))}
|
))}
|
||||||
</HStack>
|
</HStack>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</Center>
|
||||||
|
{searchLoading && (
|
||||||
|
<Center>
|
||||||
|
<Spin />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
{!searchLoading && (
|
||||||
|
<ResultList>
|
||||||
|
{searchResults?.map((record) => {
|
||||||
|
const isInstalled = mcpServers.some((server) => server.name === record.name)
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
size="small"
|
||||||
|
key={record.name}
|
||||||
|
title={
|
||||||
|
<Typography.Title level={5} style={{ margin: 0 }} className="selectable">
|
||||||
|
{record.name}
|
||||||
|
</Typography.Title>
|
||||||
|
}
|
||||||
|
extra={
|
||||||
|
<Flex>
|
||||||
|
<Tag bordered={false} color="processing">
|
||||||
|
v{record.version}
|
||||||
|
</Tag>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={
|
||||||
|
isInstalled ? <CheckOutlined style={{ color: 'var(--color-primary)' }} /> : <PlusOutlined />
|
||||||
|
}
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
if (isInstalled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
<ResultList>
|
const buildInServer = builtinMCPServers.find((server) => server.name === record.name)
|
||||||
{searchLoading ? (
|
|
||||||
<div style={{ textAlign: 'center', padding: '20px' }}>
|
|
||||||
<Spin />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
searchResults?.map((record) => (
|
|
||||||
<Card
|
|
||||||
size="small"
|
|
||||||
key={record.name}
|
|
||||||
title={
|
|
||||||
<Typography.Title level={5} style={{ margin: 0 }} className="selectable">
|
|
||||||
{record.name}
|
|
||||||
</Typography.Title>
|
|
||||||
}
|
|
||||||
extra={
|
|
||||||
<Flex>
|
|
||||||
<Tag bordered={false} color="processing">
|
|
||||||
v{record.version}
|
|
||||||
</Tag>
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
icon={<PlusOutlined />}
|
|
||||||
size="small"
|
|
||||||
onClick={() => {
|
|
||||||
const buildInServer = builtinMCPServers.find((server) => server.name === record.name)
|
|
||||||
|
|
||||||
if (buildInServer) {
|
if (buildInServer) {
|
||||||
addMCPServer(buildInServer)
|
addMCPServer(buildInServer)
|
||||||
|
window.message.success({ content: t('settings.mcp.addSuccess'), key: 'mcp-add-server' })
|
||||||
|
setSelectedMcpServer(buildInServer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const newServer = {
|
||||||
|
id: nanoid(),
|
||||||
|
name: record.name,
|
||||||
|
description: `${record.description}\n\n${t('settings.mcp.npx_list.usage')}: ${record.usage}\n${t('settings.mcp.npx_list.npm')}: ${record.npmLink}`,
|
||||||
|
command: 'npx',
|
||||||
|
args: ['-y', record.fullName],
|
||||||
|
isActive: false,
|
||||||
|
type: record.type
|
||||||
|
}
|
||||||
|
|
||||||
|
addMCPServer(newServer)
|
||||||
window.message.success({ content: t('settings.mcp.addSuccess'), key: 'mcp-add-server' })
|
window.message.success({ content: t('settings.mcp.addSuccess'), key: 'mcp-add-server' })
|
||||||
setSelectedMcpServer(buildInServer)
|
setSelectedMcpServer(newServer)
|
||||||
setRoute(null)
|
}}
|
||||||
return
|
/>
|
||||||
}
|
</Flex>
|
||||||
const newServer = {
|
}>
|
||||||
id: nanoid(),
|
<Space direction="vertical" size="small">
|
||||||
name: record.name,
|
<Text className="selectable">{record.description}</Text>
|
||||||
description: `${record.description}\n\n${t('settings.mcp.npx_list.usage')}: ${record.usage}\n${t('settings.mcp.npx_list.npm')}: ${record.npmLink}`,
|
<Text type="secondary" className="selectable">
|
||||||
command: 'npx',
|
{t('settings.mcp.npx_list.usage')}: {record.usage}
|
||||||
args: ['-y', record.fullName],
|
</Text>
|
||||||
isActive: false,
|
<Link href={record.npmLink} target="_blank" rel="noopener noreferrer">
|
||||||
type: record.type
|
{record.npmLink}
|
||||||
}
|
</Link>
|
||||||
|
</Space>
|
||||||
addMCPServer(newServer)
|
</Card>
|
||||||
window.message.success({ content: t('settings.mcp.addSuccess'), key: 'mcp-add-server' })
|
)
|
||||||
setSelectedMcpServer(newServer)
|
})}
|
||||||
setRoute(null)
|
</ResultList>
|
||||||
}}
|
)}
|
||||||
/>
|
</Container>
|
||||||
</Flex>
|
|
||||||
}>
|
|
||||||
<Space direction="vertical" size="small">
|
|
||||||
<Text className="selectable">{record.description}</Text>
|
|
||||||
<Text type="secondary" className="selectable">
|
|
||||||
{t('settings.mcp.npx_list.usage')}: {record.usage}
|
|
||||||
</Text>
|
|
||||||
<Link href={record.npmLink} target="_blank" rel="noopener noreferrer">
|
|
||||||
{record.npmLink}
|
|
||||||
</Link>
|
|
||||||
</Space>
|
|
||||||
</Card>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</ResultList>
|
|
||||||
</SettingGroup>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const ResultList = styled.div`
|
const Container = styled.div`
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
width: calc(100% + 10px);
|
`
|
||||||
|
|
||||||
|
const ResultList = styled.div`
|
||||||
|
flex: 1;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
width: 100%;
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
overflow-y: scroll;
|
overflow-y: auto;
|
||||||
`
|
`
|
||||||
|
|
||||||
export default NpxSearch
|
export default NpxSearch
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
import { ArrowLeftOutlined, CodeOutlined, PlusOutlined } from '@ant-design/icons'
|
import { ArrowLeftOutlined, CodeOutlined, PlusOutlined } from '@ant-design/icons'
|
||||||
import { nanoid } from '@reduxjs/toolkit'
|
import { nanoid } from '@reduxjs/toolkit'
|
||||||
import IndicatorLight from '@renderer/components/IndicatorLight'
|
import IndicatorLight from '@renderer/components/IndicatorLight'
|
||||||
import { HStack, VStack } from '@renderer/components/Layout'
|
import { VStack } from '@renderer/components/Layout'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import { useMCPServers } from '@renderer/hooks/useMCPServers'
|
import { useMCPServers } from '@renderer/hooks/useMCPServers'
|
||||||
import { EventEmitter } from '@renderer/services/EventService'
|
|
||||||
import { MCPServer } from '@renderer/types'
|
import { MCPServer } from '@renderer/types'
|
||||||
import { isEmpty } from 'lodash'
|
import { FC, useCallback, useEffect, useState } from 'react'
|
||||||
import { FC, useEffect, useMemo, useState } from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Route, Routes, useLocation, useNavigate } from 'react-router'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import { SettingContainer } from '..'
|
import { SettingContainer, SettingTitle } from '..'
|
||||||
import InstallNpxUv from './InstallNpxUv'
|
import InstallNpxUv from './InstallNpxUv'
|
||||||
import McpSettings from './McpSettings'
|
import McpSettings from './McpSettings'
|
||||||
import NpxSearch from './NpxSearch'
|
import NpxSearch from './NpxSearch'
|
||||||
@ -20,18 +20,13 @@ const MCPSettings: FC = () => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { mcpServers, addMCPServer } = useMCPServers()
|
const { mcpServers, addMCPServer } = useMCPServers()
|
||||||
const [selectedMcpServer, setSelectedMcpServer] = useState<MCPServer | null>(null)
|
const [selectedMcpServer, setSelectedMcpServer] = useState<MCPServer | null>(null)
|
||||||
const [route, setRoute] = useState<'npx-search' | 'mcp-install' | null>(null)
|
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
useEffect(() => {
|
const location = useLocation()
|
||||||
const unsubs = [
|
const pathname = location.pathname
|
||||||
EventEmitter.on('mcp:npx-search', () => setRoute('npx-search')),
|
|
||||||
EventEmitter.on('mcp:mcp-install', () => setRoute('mcp-install'))
|
|
||||||
]
|
|
||||||
return () => unsubs.forEach((unsub) => unsub())
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const onAddMcpServer = async () => {
|
const onAddMcpServer = useCallback(async () => {
|
||||||
const newServer = {
|
const newServer = {
|
||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
name: t('settings.mcp.newServer'),
|
name: t('settings.mcp.newServer'),
|
||||||
@ -45,13 +40,12 @@ const MCPSettings: FC = () => {
|
|||||||
addMCPServer(newServer)
|
addMCPServer(newServer)
|
||||||
window.message.success({ content: t('settings.mcp.addSuccess'), key: 'mcp-list' })
|
window.message.success({ content: t('settings.mcp.addSuccess'), key: 'mcp-list' })
|
||||||
setSelectedMcpServer(newServer)
|
setSelectedMcpServer(newServer)
|
||||||
setRoute(null)
|
}, [addMCPServer, t])
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const _selectedMcpServer = mcpServers.find((server) => server.id === selectedMcpServer?.id)
|
const _selectedMcpServer = mcpServers.find((server) => server.id === selectedMcpServer?.id)
|
||||||
setSelectedMcpServer(_selectedMcpServer || mcpServers[0])
|
setSelectedMcpServer(_selectedMcpServer || mcpServers[0])
|
||||||
}, [mcpServers, route, selectedMcpServer])
|
}, [mcpServers, selectedMcpServer])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Check if the selected server still exists in the updated mcpServers list
|
// Check if the selected server still exists in the updated mcpServers list
|
||||||
@ -65,98 +59,90 @@ const MCPSettings: FC = () => {
|
|||||||
}
|
}
|
||||||
}, [mcpServers, selectedMcpServer])
|
}, [mcpServers, selectedMcpServer])
|
||||||
|
|
||||||
const MainContent = useMemo(() => {
|
const McpServersList = useCallback(
|
||||||
if (route === 'npx-search' || isEmpty(mcpServers)) {
|
() => (
|
||||||
return (
|
<GridContainer>
|
||||||
<SettingContainer theme={theme}>
|
<GridHeader>
|
||||||
<NpxSearch
|
<SettingTitle>{t('settings.mcp.newServer')}</SettingTitle>
|
||||||
setRoute={(route) => setRoute(route as 'npx-search' | 'mcp-install' | null)}
|
</GridHeader>
|
||||||
setSelectedMcpServer={setSelectedMcpServer}
|
<ServersGrid>
|
||||||
/>
|
<AddServerCard onClick={onAddMcpServer}>
|
||||||
</SettingContainer>
|
<PlusOutlined style={{ fontSize: 24 }} />
|
||||||
)
|
<AddServerText>{t('settings.mcp.addServer')}</AddServerText>
|
||||||
}
|
</AddServerCard>
|
||||||
|
{mcpServers.map((server) => (
|
||||||
|
<ServerCard
|
||||||
|
key={server.id}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedMcpServer(server)
|
||||||
|
navigate(`/settings/mcp/server/${server.id}`)
|
||||||
|
}}>
|
||||||
|
<ServerHeader>
|
||||||
|
<ServerIcon>
|
||||||
|
<CodeOutlined />
|
||||||
|
</ServerIcon>
|
||||||
|
<ServerName>{server.name}</ServerName>
|
||||||
|
<StatusIndicator>
|
||||||
|
<IndicatorLight
|
||||||
|
size={6}
|
||||||
|
color={server.isActive ? 'green' : 'var(--color-text-3)'}
|
||||||
|
animation={server.isActive}
|
||||||
|
shadow={false}
|
||||||
|
/>
|
||||||
|
</StatusIndicator>
|
||||||
|
</ServerHeader>
|
||||||
|
<ServerDescription>
|
||||||
|
{server.description &&
|
||||||
|
server.description.substring(0, 60) + (server.description.length > 60 ? '...' : '')}
|
||||||
|
</ServerDescription>
|
||||||
|
</ServerCard>
|
||||||
|
))}
|
||||||
|
</ServersGrid>
|
||||||
|
</GridContainer>
|
||||||
|
),
|
||||||
|
[mcpServers, navigate, onAddMcpServer, t]
|
||||||
|
)
|
||||||
|
|
||||||
if (route === 'mcp-install') {
|
const isHome = pathname === '/settings/mcp'
|
||||||
return (
|
|
||||||
<SettingContainer theme={theme}>
|
|
||||||
<InstallNpxUv />
|
|
||||||
</SettingContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (selectedMcpServer) {
|
|
||||||
return <McpSettings server={selectedMcpServer} />
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<NpxSearch
|
|
||||||
setRoute={(route) => setRoute(route as 'npx-search' | 'mcp-install' | null)}
|
|
||||||
setSelectedMcpServer={setSelectedMcpServer}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}, [mcpServers, route, selectedMcpServer, theme])
|
|
||||||
|
|
||||||
const goBackToGrid = () => {
|
|
||||||
setSelectedMcpServer(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
{selectedMcpServer ? (
|
{!isHome && (
|
||||||
<DetailViewContainer>
|
<BackButtonContainer>
|
||||||
<BackButtonContainer>
|
<Link to="/settings/mcp">
|
||||||
<BackButton onClick={goBackToGrid}>
|
<BackButton>
|
||||||
<ArrowLeftOutlined /> {t('common.back')}
|
<ArrowLeftOutlined /> {t('common.back')}
|
||||||
</BackButton>
|
</BackButton>
|
||||||
</BackButtonContainer>
|
</Link>
|
||||||
<DetailContent>{MainContent}</DetailContent>
|
</BackButtonContainer>
|
||||||
</DetailViewContainer>
|
|
||||||
) : (
|
|
||||||
<GridContainer>
|
|
||||||
<GridHeader>
|
|
||||||
<h2>{t('settings.mcp.newServer')}</h2>
|
|
||||||
</GridHeader>
|
|
||||||
<ServersGrid>
|
|
||||||
<AddServerCard onClick={onAddMcpServer}>
|
|
||||||
<PlusOutlined style={{ fontSize: 24 }} />
|
|
||||||
<AddServerText>{t('settings.mcp.addServer')}</AddServerText>
|
|
||||||
</AddServerCard>
|
|
||||||
|
|
||||||
{mcpServers.map((server) => (
|
|
||||||
<ServerCard
|
|
||||||
key={server.id}
|
|
||||||
onClick={() => {
|
|
||||||
setSelectedMcpServer(server)
|
|
||||||
setRoute(null)
|
|
||||||
}}>
|
|
||||||
<ServerHeader>
|
|
||||||
<ServerIcon>
|
|
||||||
<CodeOutlined />
|
|
||||||
</ServerIcon>
|
|
||||||
<ServerName>{server.name}</ServerName>
|
|
||||||
<StatusIndicator>
|
|
||||||
<IndicatorLight
|
|
||||||
size={6}
|
|
||||||
color={server.isActive ? 'green' : 'var(--color-text-3)'}
|
|
||||||
animation={server.isActive}
|
|
||||||
shadow={false}
|
|
||||||
/>
|
|
||||||
</StatusIndicator>
|
|
||||||
</ServerHeader>
|
|
||||||
<ServerDescription>
|
|
||||||
{server.description &&
|
|
||||||
server.description.substring(0, 60) + (server.description.length > 60 ? '...' : '')}
|
|
||||||
</ServerDescription>
|
|
||||||
</ServerCard>
|
|
||||||
))}
|
|
||||||
</ServersGrid>
|
|
||||||
</GridContainer>
|
|
||||||
)}
|
)}
|
||||||
|
<MainContainer>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<McpServersList />} />
|
||||||
|
<Route path="server/:id" element={selectedMcpServer ? <McpSettings server={selectedMcpServer} /> : null} />
|
||||||
|
<Route
|
||||||
|
path="npx-search"
|
||||||
|
element={
|
||||||
|
<SettingContainer theme={theme}>
|
||||||
|
<NpxSearch setSelectedMcpServer={setSelectedMcpServer} />
|
||||||
|
</SettingContainer>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="mcp-install"
|
||||||
|
element={
|
||||||
|
<SettingContainer theme={theme}>
|
||||||
|
<InstallNpxUv />
|
||||||
|
</SettingContainer>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Routes>
|
||||||
|
</MainContainer>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Container = styled(HStack)`
|
const Container = styled(VStack)`
|
||||||
flex: 1;
|
flex: 1;
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -251,20 +237,13 @@ const AddServerText = styled.div`
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
`
|
`
|
||||||
|
|
||||||
const DetailViewContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
`
|
|
||||||
|
|
||||||
const BackButtonContainer = styled.div`
|
const BackButtonContainer = styled.div`
|
||||||
padding: 16px 0 0 20px;
|
padding: 12px 0 0 12px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
background-color: var(--color-background);
|
||||||
`
|
`
|
||||||
|
|
||||||
const DetailContent = styled.div`
|
const MainContainer = styled.div`
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
`
|
`
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import {
|
|||||||
ThunderboltOutlined
|
ThunderboltOutlined
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
||||||
import { isLocalAi } from '@renderer/config/env'
|
|
||||||
import { useSidebarIconShow } from '@renderer/hooks/useSidebarIcon'
|
import { useSidebarIconShow } from '@renderer/hooks/useSidebarIcon'
|
||||||
import ModelSettings from '@renderer/pages/settings/ModelSettings/ModelSettings'
|
import ModelSettings from '@renderer/pages/settings/ModelSettings/ModelSettings'
|
||||||
// 导入useAppSelector
|
// 导入useAppSelector
|
||||||
@ -46,26 +45,22 @@ const SettingsPage: FC = () => {
|
|||||||
<Container>
|
<Container>
|
||||||
<Navbar>
|
<Navbar>
|
||||||
<NavbarCenter style={{ borderRight: 'none' }}>{t('settings.title')}</NavbarCenter>
|
<NavbarCenter style={{ borderRight: 'none' }}>{t('settings.title')}</NavbarCenter>
|
||||||
{pathname === '/settings/mcp' && <McpSettingsNavbar />}
|
{pathname.includes('/settings/mcp') && <McpSettingsNavbar />}
|
||||||
</Navbar>
|
</Navbar>
|
||||||
<ContentContainer id="content-container">
|
<ContentContainer id="content-container">
|
||||||
<SettingMenus>
|
<SettingMenus>
|
||||||
{!isLocalAi && (
|
<MenuItemLink to="/settings/provider">
|
||||||
<>
|
<MenuItem className={isRoute('/settings/provider')}>
|
||||||
<MenuItemLink to="/settings/provider">
|
<CloudOutlined />
|
||||||
<MenuItem className={isRoute('/settings/provider')}>
|
{t('settings.provider.title')}
|
||||||
<CloudOutlined />
|
</MenuItem>
|
||||||
{t('settings.provider.title')}
|
</MenuItemLink>
|
||||||
</MenuItem>
|
<MenuItemLink to="/settings/model">
|
||||||
</MenuItemLink>
|
<MenuItem className={isRoute('/settings/model')}>
|
||||||
<MenuItemLink to="/settings/model">
|
<i className="iconfont icon-ai-model" />
|
||||||
<MenuItem className={isRoute('/settings/model')}>
|
{t('settings.model')}
|
||||||
<i className="iconfont icon-ai-model" />
|
</MenuItem>
|
||||||
{t('settings.model')}
|
</MenuItemLink>
|
||||||
</MenuItem>
|
|
||||||
</MenuItemLink>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<MenuItemLink to="/settings/web-search">
|
<MenuItemLink to="/settings/web-search">
|
||||||
<MenuItem className={isRoute('/settings/web-search')}>
|
<MenuItem className={isRoute('/settings/web-search')}>
|
||||||
<GlobalOutlined />
|
<GlobalOutlined />
|
||||||
@ -134,13 +129,13 @@ const SettingsPage: FC = () => {
|
|||||||
<Route path="provider" element={<ProvidersList />} />
|
<Route path="provider" element={<ProvidersList />} />
|
||||||
<Route path="model" element={<ModelSettings />} />
|
<Route path="model" element={<ModelSettings />} />
|
||||||
<Route path="web-search" element={<WebSearchSettings />} />
|
<Route path="web-search" element={<WebSearchSettings />} />
|
||||||
<Route path="mcp" element={<MCPSettings />} />
|
<Route path="mcp/*" element={<MCPSettings />} />
|
||||||
<Route path="general/*" element={<GeneralSettings />} />
|
<Route path="general" element={<GeneralSettings />} />
|
||||||
<Route path="display" element={<DisplaySettings />} />
|
<Route path="display" element={<DisplaySettings />} />
|
||||||
{showMiniAppSettings && <Route path="miniapps" element={<MiniAppSettings />} />}
|
{showMiniAppSettings && <Route path="miniapps" element={<MiniAppSettings />} />}
|
||||||
<Route path="shortcut" element={<ShortcutSettings />} />
|
<Route path="shortcut" element={<ShortcutSettings />} />
|
||||||
<Route path="quickAssistant" element={<QuickAssistantSettings />} />
|
<Route path="quickAssistant" element={<QuickAssistantSettings />} />
|
||||||
<Route path="data/*" element={<DataSettings />} />
|
<Route path="data" element={<DataSettings />} />
|
||||||
<Route path="about" element={<AboutSettings />} />
|
<Route path="about" element={<AboutSettings />} />
|
||||||
<Route path="quickPhrase" element={<QuickPhraseSettings />} />
|
<Route path="quickPhrase" element={<QuickPhraseSettings />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user