diff --git a/src/renderer/src/config/models.ts b/src/renderer/src/config/models.ts index 0507beba..ac20258d 100644 --- a/src/renderer/src/config/models.ts +++ b/src/renderer/src/config/models.ts @@ -205,7 +205,7 @@ export const FUNCTION_CALLING_MODELS = [ 'claude', 'qwen', 'hunyuan', - 'deepseek-ai/', + 'deepseek', 'glm-4(?:-[\\w-]+)?', 'learnlm(?:-[\\w-]+)?', 'gemini(?:-[\\w-]+)?' // 提前排除了gemini的嵌入模型 diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 8a5e85f4..f60db5dc 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -46,6 +46,11 @@ "search": "Search assistants...", "settings.default_model": "Default Model", "settings.knowledge_base": "Knowledge Base Settings", + "settings.mcp": "MCP Servers", + "settings.mcp.enableFirst": "Enable this server in MCP settings first", + "settings.mcp.title": "MCP Settings", + "settings.mcp.noServersAvailable": "No MCP servers available. Add servers in settings", + "settings.mcp.description": "Default enabled MCP servers", "settings.model": "Model Settings", "settings.preset_messages": "Preset Messages", "settings.prompt": "Prompt Settings", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index d775707a..f7d92b1e 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -44,6 +44,11 @@ "save.success": "保存に成功しました", "save.title": "エージェントに保存", "search": "アシスタントを検索...", + "settings.mcp": "MCP サーバー", + "settings.mcp.enableFirst": "まず MCP 設定でこのサーバーを有効にしてください", + "settings.mcp.title": "MCP 設定", + "settings.mcp.noServersAvailable": "利用可能な MCP サーバーがありません。設定でサーバーを追加してください", + "settings.mcp.description": "デフォルトで有効な MCP サーバー", "settings.default_model": "デフォルトモデル", "settings.knowledge_base": "ナレッジベース設定", "settings.model": "モデル設定", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index de4f0a66..d28f562c 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -44,6 +44,11 @@ "save.success": "Успешно сохранено", "save.title": "Сохранить в агента", "search": "Поиск ассистентов...", + "settings.mcp": "Серверы MCP", + "settings.mcp.enableFirst": "Сначала включите этот сервер в настройках MCP", + "settings.mcp.title": "Настройки MCP", + "settings.mcp.noServersAvailable": "Нет доступных серверов MCP. Добавьте серверы в настройках", + "settings.mcp.description": "Серверы MCP, включенные по умолчанию", "settings.default_model": "Модель по умолчанию", "settings.knowledge_base": "Настройки базы знаний", "settings.model": "Настройки модели", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 3ff6a79d..343b8ad0 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -44,6 +44,11 @@ "save.success": "保存成功", "save.title": "保存到智能体", "search": "搜索助手", + "settings.mcp": "MCP 服务器", + "settings.mcp.enableFirst": "请先在 MCP 设置中启用此服务器", + "settings.mcp.title": "MCP 设置", + "settings.mcp.noServersAvailable": "无可用 MCP 服务器。请在设置中添加服务器", + "settings.mcp.description": "默认启用的 MCP 服务器", "settings.default_model": "默认模型", "settings.knowledge_base": "知识库设置", "settings.model": "模型设置", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 14027805..d71503a0 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -44,6 +44,11 @@ "save.success": "儲存成功", "save.title": "儲存到智慧代理人", "search": "搜尋助手...", + "settings.mcp": "MCP 伺服器", + "settings.mcp.enableFirst": "請先在 MCP 設定中啟用此伺服器", + "settings.mcp.title": "MCP 設定", + "settings.mcp.noServersAvailable": "無可用 MCP 伺服器。請在設定中新增伺服器", + "settings.mcp.description": "預設啟用的 MCP 伺服器", "settings.default_model": "預設模型", "settings.knowledge_base": "知識庫設定", "settings.model": "模型設定", diff --git a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx index eb96e138..9e674daa 100644 --- a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx @@ -91,7 +91,7 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = const [isTranslating, setIsTranslating] = useState(false) const [selectedKnowledgeBases, setSelectedKnowledgeBases] = useState([]) const [mentionModels, setMentionModels] = useState([]) - const [enabledMCPs, setEnabledMCPs] = useState([]) + const [enabledMCPs, setEnabledMCPs] = useState(assistant.mcpServers || []) const [isMentionPopupOpen, setIsMentionPopupOpen] = useState(false) const [isDragging, setIsDragging] = useState(false) const [textareaHeight, setTextareaHeight] = useState() @@ -145,6 +145,15 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = } }, [textareaHeight]) + // reset state when assistant changes + useEffect(() => { + // Reset to assistant default model + assistant.defaultModel && setModel(assistant.defaultModel) + + // Reset to assistant knowledge mcp servers + setEnabledMCPs(assistant.mcpServers || []) + }, [assistant, setModel]) + const sendMessage = useCallback(async () => { if (inputEmpty || loading) { return @@ -323,8 +332,11 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = await db.topics.add({ id: topic.id, messages: [] }) await addAssistantMessagesToTopic({ assistant, topic }) + // Clear previous state // Reset to assistant default model assistant.defaultModel && setModel(assistant.defaultModel) + // Reset to assistant knowledge mcp servers + setEnabledMCPs(assistant.mcpServers || []) addTopic(topic) setActiveTopic(topic) diff --git a/src/renderer/src/pages/settings/AssistantSettings/AssistantMCPSettings.tsx b/src/renderer/src/pages/settings/AssistantSettings/AssistantMCPSettings.tsx new file mode 100644 index 00000000..5f44b424 --- /dev/null +++ b/src/renderer/src/pages/settings/AssistantSettings/AssistantMCPSettings.tsx @@ -0,0 +1,198 @@ +import { InfoCircleOutlined } from '@ant-design/icons' +import { Box } from '@renderer/components/Layout' +import { useMCPServers } from '@renderer/hooks/useMCPServers' +import { Assistant, AssistantSettings } from '@renderer/types' +import { Empty, Switch, Tooltip } from 'antd' +import { useTranslation } from 'react-i18next' +import styled from 'styled-components' + +export interface MCPServer { + id: string + name: string + description?: string + baseUrl?: string + command?: string + args?: string[] + env?: Record + isActive: boolean +} + +interface Props { + assistant: Assistant + updateAssistant: (assistant: Assistant) => void + updateAssistantSettings: (settings: AssistantSettings) => void +} + +const AssistantMCPSettings: React.FC = ({ assistant, updateAssistant }) => { + const { t } = useTranslation() + + const { mcpServers: allMcpServers } = useMCPServers() + const onUpdate = (ids: string[]) => { + const mcpServers = ids + .map((id) => allMcpServers.find((server) => server.id === id)) + .filter((server): server is MCPServer => server !== undefined && server.isActive) + const _assistant = { ...assistant, mcpServers } + updateAssistant(_assistant) + } + + const handleServerToggle = (serverId: string) => { + const currentServerIds = assistant.mcpServers?.map((server) => server.id) || [] + + if (currentServerIds.includes(serverId)) { + // Remove server if it's already enabled + onUpdate(currentServerIds.filter((id) => id !== serverId)) + } else { + // Add server if it's not enabled + onUpdate([...currentServerIds, serverId]) + } + } + + const enabledCount = assistant.mcpServers?.length || 0 + + return ( + + + + {t('assistants.settings.mcp.title')} + + + + + {allMcpServers.length > 0 && ( + + {enabledCount} / {allMcpServers.length} {t('settings.mcp.active')} + + )} + + + {allMcpServers.length > 0 ? ( + + {allMcpServers.map((server) => { + const isEnabled = assistant.mcpServers?.some((s) => s.id === server.id) || false + + return ( + + + {server.name} + {server.description && {server.description}} + {server.baseUrl && {server.baseUrl}} + + + handleServerToggle(server.id)} + size="small" + /> + + + ) + })} + + ) : ( + + + + )} + + ) +} + +const Container = styled.div` + display: flex; + flex: 1; + flex-direction: column; + overflow: hidden; + padding: 12px; + background-color: ${(props) => props.theme.colors?.background || '#f9f9f9'}; + border-radius: 8px; +` + +const HeaderContainer = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; +` + +const InfoIcon = styled(InfoCircleOutlined)` + margin-left: 6px; + font-size: 14px; + color: ${(props) => props.theme.colors?.textSecondary || '#8c8c8c'}; + cursor: help; +` + +const EnabledCount = styled.span` + font-size: 12px; + color: ${(props) => props.theme.colors?.textSecondary || '#8c8c8c'}; +` + +const EmptyContainer = styled.div` + display: flex; + flex: 1; + justify-content: center; + align-items: center; + padding: 40px 0; +` + +const ServerList = styled.div` + display: flex; + flex-direction: column; + gap: 8px; + overflow-y: auto; + padding: 4px; +` + +const ServerItem = styled.div<{ isEnabled: boolean }>` + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 16px; + border-radius: 8px; + background-color: ${(props) => props.theme.colors?.cardBackground || '#fff'}; + border: 1px solid ${(props) => props.theme.colors?.border || '#e6e6e6'}; + transition: all 0.2s ease; + + &:hover { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + transform: translateY(-1px); + } + + opacity: ${(props) => (props.isEnabled ? 1 : 0.7)}; +` + +const ServerInfo = styled.div` + display: flex; + flex-direction: column; + flex: 1; + overflow: hidden; +` + +const ServerName = styled.div` + font-weight: 600; + margin-bottom: 4px; +` + +const ServerDescription = styled.div` + font-size: 0.85rem; + color: ${(props) => props.theme.colors?.textSecondary || '#8c8c8c'}; + margin-bottom: 3px; +` + +const ServerUrl = styled.div` + font-size: 0.8rem; + color: ${(props) => props.theme.colors?.textTertiary || '#bfbfbf'}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +` + +export default AssistantMCPSettings diff --git a/src/renderer/src/pages/settings/AssistantSettings/index.tsx b/src/renderer/src/pages/settings/AssistantSettings/index.tsx index 6276f90f..1f68ba4b 100644 --- a/src/renderer/src/pages/settings/AssistantSettings/index.tsx +++ b/src/renderer/src/pages/settings/AssistantSettings/index.tsx @@ -10,6 +10,7 @@ import { useTranslation } from 'react-i18next' import styled from 'styled-components' import AssistantKnowledgeBaseSettings from './AssistantKnowledgeBaseSettings' +import AssistantMCPSettings from './AssistantMCPSettings' import AssistantMessagesSettings from './AssistantMessagesSettings' import AssistantModelSettings from './AssistantModelSettings' import AssistantPromptSettings from './AssistantPromptSettings' @@ -19,7 +20,7 @@ interface AssistantSettingPopupShowParams { tab?: AssistantSettingPopupTab } -type AssistantSettingPopupTab = 'prompt' | 'model' | 'messages' | 'knowledge_base' +type AssistantSettingPopupTab = 'prompt' | 'model' | 'messages' | 'knowledge_base' | 'mcp' interface Props extends AssistantSettingPopupShowParams { resolve: (assistant: Assistant) => void @@ -68,6 +69,10 @@ const AssistantSettingPopupContainer: React.FC = ({ resolve, tab, ...prop showKnowledgeIcon && { key: 'knowledge_base', label: t('assistants.settings.knowledge_base') + }, + { + key: 'mcp', + label: t('assistants.settings.mcp') } ].filter(Boolean) as { key: string; label: string }[] @@ -133,6 +138,13 @@ const AssistantSettingPopupContainer: React.FC = ({ resolve, tab, ...prop updateAssistantSettings={updateAssistantSettings} /> )} + {menu === 'mcp' && ( + + )} diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index 6903c183..1fe0e2d1 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -17,6 +17,7 @@ export type Assistant = { messages?: AssistantMessage[] enableWebSearch?: boolean enableGenerateImage?: boolean + mcpServers?: MCPServer[] } export type AssistantMessage = {