diff --git a/web/src/apis/index.js b/web/src/apis/index.js index 08d66638..6be3f1de 100644 --- a/web/src/apis/index.js +++ b/web/src/apis/index.js @@ -11,6 +11,7 @@ export * from './agent_api' // 智能体API export * from './tasker' // 任务管理API export * from './mindmap_api' // 思维导图API export * from './department_api' // 部门管理API +export * from './mcp_api' // MCP API // 导出基础工具函数 export { diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue index 66a31d92..98c63593 100644 --- a/web/src/components/AgentChatComponent.vue +++ b/web/src/components/AgentChatComponent.vue @@ -136,6 +136,7 @@ :ensure-thread="ensureActiveThread" :has-state-content="hasAgentStateContent" :is-panel-open="isAgentPanelOpen" + :mention="mentionConfig" @send="handleSendOrStop" @attachment-changed="handleAgentStateRefresh" @toggle-panel="toggleAgentPanel" @@ -209,7 +210,7 @@ import { useAgentStore } from '@/stores/agent' import { useChatUIStore } from '@/stores/chatUI' import { storeToRefs } from 'pinia' import { MessageProcessor } from '@/utils/messageProcessor' -import { agentApi, threadApi } from '@/apis' +import { agentApi, threadApi, databaseApi, mcpApi } from '@/apis' import HumanApprovalModal from '@/components/HumanApprovalModal.vue' import { useApproval } from '@/composables/useApproval' import { useAgentStreamHandler } from '@/composables/useAgentStreamHandler' @@ -225,7 +226,14 @@ const emit = defineEmits(['open-config', 'open-agent-modal']) // ==================== STORE MANAGEMENT ==================== const agentStore = useAgentStore() const chatUIStore = useChatUIStore() -const { agents, selectedAgentId, defaultAgentId, selectedAgentConfigId } = storeToRefs(agentStore) +const { + agents, + selectedAgentId, + defaultAgentId, + selectedAgentConfigId, + agentConfig, + configurableItems +} = storeToRefs(agentStore) // ==================== LOCAL CHAT & UI STATE ==================== const userInput = ref('') @@ -268,6 +276,10 @@ const localUIState = reactive({ isInitialRender: true }) +// Mention resources +const availableKnowledgeBases = ref([]) +const availableMcps = ref([]) + // Agent Panel State const isAgentPanelOpen = ref(false) const isResizing = ref(false) @@ -343,6 +355,66 @@ const hasAgentStateContent = computed(() => { return todoCount > 0 || fileCount > 0 || attachmentCount > 0 }) +const mentionConfig = computed(() => { + const rawFiles = currentAgentState.value?.files || [] + const rawAttachments = currentAgentState.value?.attachments || [] + const files = [] + + if (Array.isArray(rawFiles)) { + rawFiles.forEach((item) => { + if (typeof item === 'object' && item !== null) { + Object.entries(item).forEach(([filePath, fileData]) => { + files.push({ + path: filePath, + ...fileData + }) + }) + } + }) + } + + if (Array.isArray(rawAttachments)) { + rawAttachments.forEach((item) => { + if (item && item.file_name) { + files.push({ + path: item.file_name, + ...item + }) + } + }) + } + + // Filter KBs and MCPs based on agent config + const configItems = configurableItems.value || {} + const currentConfig = agentConfig.value || {} + const allowedKbNames = new Set() + const allowedMcpNames = new Set() + + Object.entries(configItems).forEach(([key, item]) => { + const kind = item?.template_metadata?.kind + const val = currentConfig[key] + + if (Array.isArray(val)) { + if (kind === 'knowledges') { + val.forEach((v) => allowedKbNames.add(v)) + } else if (kind === 'mcps') { + val.forEach((v) => allowedMcpNames.add(v)) + } + } + }) + + const knowledgeBases = availableKnowledgeBases.value.filter((kb) => allowedKbNames.has(kb.name)) + const mcps = availableMcps.value.filter((mcp) => allowedMcpNames.has(mcp.name)) + + if (!files.length && !knowledgeBases.length && !mcps.length) return null + + return { + files, + knowledgeBases, + mcps + } +}) + const currentThreadMessages = computed(() => threadMessages.value[currentChatId.value] || []) // 计算是否显示Refs组件的条件 @@ -595,6 +667,19 @@ const fetchAgentState = async (agentId, threadId) => { } catch (error) {} } +const fetchMentionResources = async () => { + try { + const [dbsRes, mcpsRes] = await Promise.all([ + databaseApi.getAccessibleDatabases().catch(() => ({ databases: [] })), + mcpApi.getMcpServers().catch(() => ({ data: [] })) + ]) + availableKnowledgeBases.value = dbsRes.databases || [] + availableMcps.value = mcpsRes.data || [] + } catch (e) { + console.warn('Failed to fetch mention resources', e) + } +} + const ensureActiveThread = async (title = '新的对话') => { if (currentChatId.value) return currentChatId.value try { @@ -1096,6 +1181,7 @@ const initAll = async () => { if (!agentStore.isInitialized) { await agentStore.initialize() } + await fetchMentionResources() } catch (error) { handleChatError(error, 'load') } diff --git a/web/src/components/AgentInputArea.vue b/web/src/components/AgentInputArea.vue index 3b0c2743..f8b790b5 100644 --- a/web/src/components/AgentInputArea.vue +++ b/web/src/components/AgentInputArea.vue @@ -8,6 +8,7 @@ :send-button-disabled="sendButtonDisabled" :placeholder="placeholder" :force-multi-line="hasStateContent" + :mention="mention" @send="handleSend" @keydown="handleKeyDown" > @@ -68,7 +69,8 @@ const props = defineProps({ threadId: { type: String, default: null }, ensureThread: { type: Function, required: true }, hasStateContent: { type: Boolean, default: false }, - isPanelOpen: { type: Boolean, default: false } + isPanelOpen: { type: Boolean, default: false }, + mention: { type: Object, default: () => null } }) const emit = defineEmits([ diff --git a/web/src/components/MessageInputComponent.vue b/web/src/components/MessageInputComponent.vue index d7aa55bf..b81eec15 100644 --- a/web/src/components/MessageInputComponent.vue +++ b/web/src/components/MessageInputComponent.vue @@ -34,12 +34,65 @@ class="user-input" :value="inputValue" @keydown="handleKeyPress" + @keyup="handleKeyUp" @input="handleInput" @focus="focusInput" :placeholder="placeholder" :disabled="disabled" /> + +
+
+ +
+
文件
+
+ {{ item.label }} +
+
+ + +
+
知识库
+
+ {{ item.label }} +
+
+ + +
+
MCP
+
+ {{ item.label }} +
+
+ + +
暂无可引用的项
+
+
+
@@ -63,7 +116,16 @@