Feat/assistant level mcp (#4220)

This commit is contained in:
LiuVaayne 2025-03-31 21:10:33 +08:00 committed by GitHub
parent 8a7db19e73
commit a5b0480418
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 251 additions and 3 deletions

View File

@ -205,7 +205,7 @@ export const FUNCTION_CALLING_MODELS = [
'claude',
'qwen',
'hunyuan',
'deepseek-ai/',
'deepseek',
'glm-4(?:-[\\w-]+)?',
'learnlm(?:-[\\w-]+)?',
'gemini(?:-[\\w-]+)?' // 提前排除了gemini的嵌入模型

View File

@ -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",

View File

@ -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": "モデル設定",

View File

@ -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": "Настройки модели",

View File

@ -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": "模型设置",

View File

@ -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": "模型設定",

View File

@ -91,7 +91,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
const [isTranslating, setIsTranslating] = useState(false)
const [selectedKnowledgeBases, setSelectedKnowledgeBases] = useState<KnowledgeBase[]>([])
const [mentionModels, setMentionModels] = useState<Model[]>([])
const [enabledMCPs, setEnabledMCPs] = useState<MCPServer[]>([])
const [enabledMCPs, setEnabledMCPs] = useState<MCPServer[]>(assistant.mcpServers || [])
const [isMentionPopupOpen, setIsMentionPopupOpen] = useState(false)
const [isDragging, setIsDragging] = useState(false)
const [textareaHeight, setTextareaHeight] = useState<number>()
@ -145,6 +145,15 @@ const Inputbar: FC<Props> = ({ 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<Props> = ({ 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)

View File

@ -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<string, string>
isActive: boolean
}
interface Props {
assistant: Assistant
updateAssistant: (assistant: Assistant) => void
updateAssistantSettings: (settings: AssistantSettings) => void
}
const AssistantMCPSettings: React.FC<Props> = ({ 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 (
<Container>
<HeaderContainer>
<Box style={{ fontWeight: 'bold', fontSize: '16px' }}>
{t('assistants.settings.mcp.title')}
<Tooltip title={t('assistants.settings.mcp.description', 'Select MCP servers to use with this assistant')}>
<InfoIcon />
</Tooltip>
</Box>
{allMcpServers.length > 0 && (
<EnabledCount>
{enabledCount} / {allMcpServers.length} {t('settings.mcp.active')}
</EnabledCount>
)}
</HeaderContainer>
{allMcpServers.length > 0 ? (
<ServerList>
{allMcpServers.map((server) => {
const isEnabled = assistant.mcpServers?.some((s) => s.id === server.id) || false
return (
<ServerItem key={server.id} isEnabled={isEnabled}>
<ServerInfo>
<ServerName>{server.name}</ServerName>
{server.description && <ServerDescription>{server.description}</ServerDescription>}
{server.baseUrl && <ServerUrl>{server.baseUrl}</ServerUrl>}
</ServerInfo>
<Tooltip
title={
!server.isActive
? t('assistants.settings.mcp.enableFirst', 'Enable this server in MCP settings first')
: undefined
}>
<Switch
checked={isEnabled}
disabled={!server.isActive}
onChange={() => handleServerToggle(server.id)}
size="small"
/>
</Tooltip>
</ServerItem>
)
})}
</ServerList>
) : (
<EmptyContainer>
<Empty
description={t('assistants.settings.mcp.noAvaliable', 'No MCP servers available')}
image={Empty.PRESENTED_IMAGE_SIMPLE}
/>
</EmptyContainer>
)}
</Container>
)
}
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

View File

@ -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<Props> = ({ 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<Props> = ({ resolve, tab, ...prop
updateAssistantSettings={updateAssistantSettings}
/>
)}
{menu === 'mcp' && (
<AssistantMCPSettings
assistant={assistant}
updateAssistant={updateAssistant}
updateAssistantSettings={updateAssistantSettings}
/>
)}
</Settings>
</HStack>
</StyledModal>

View File

@ -17,6 +17,7 @@ export type Assistant = {
messages?: AssistantMessage[]
enableWebSearch?: boolean
enableGenerateImage?: boolean
mcpServers?: MCPServer[]
}
export type AssistantMessage = {