diff --git a/backend/package/yuxi/agents/buildin/deep_agent/graph.py b/backend/package/yuxi/agents/buildin/deep_agent/graph.py index e25102d6..bedde887 100644 --- a/backend/package/yuxi/agents/buildin/deep_agent/graph.py +++ b/backend/package/yuxi/agents/buildin/deep_agent/graph.py @@ -31,11 +31,12 @@ class DeepAgent(BaseAgent): name = "深度分析智能体" description = "具备规划、深度分析和子智能体协作能力的智能体,可以处理复杂的多步骤任务" context_schema = DeepContext - capabilities = [ - "file_upload", - "todo", - "files", - ] + capabilities = ["file_upload", "files", "todo"] # 支持文件上传功能 + metadata = { + "examples": [ + "调研一下多模态 GraphRAG 的相关论文" + ] + } def __init__(self, **kwargs): super().__init__(**kwargs) diff --git a/backend/package/yuxi/config/static/info.template.yaml b/backend/package/yuxi/config/static/info.template.yaml index aa579d0a..9d0bda63 100644 --- a/backend/package/yuxi/config/static/info.template.yaml +++ b/backend/package/yuxi/config/static/info.template.yaml @@ -11,12 +11,18 @@ organization: # 项目信息 branding: name: "Yuxi" - title: "Yuxi: 更智能的知识库智能体平台" # 系统标题 - subtitle: "大模型驱动的知识库智能体平台" # 副标题 + title: "让智能体可构建、可编排、可落地" # 系统标题 + subtitle: "开源智能体平台套件,融合 RAG 与知识图谱" # 副标题(subtitles 为空时使用) + subtitles: + - "开源智能体平台套件,融合 RAG 与知识图谱" + - "统一编排 Agent、知识库、图谱与工具链" + - "让智能体可构建,让知识可连接,让决策可验证" + - "让智能体可落地,让流程可编排,让协作可扩展" + - "让数据可沉淀,让能力可复用,让系统可进化" features: - label: "GitHub Stars" - value: "4100+" + value: "4600+" description: "开发者社区的认可与支持" icon: "stars" - label: "已解决 Issues" @@ -24,11 +30,11 @@ features: description: "持续改进和问题解决能力" icon: "issues" - label: "累计 Commits" - value: "1300+" + value: "1500+" description: "活跃的开发迭代和功能更新" icon: "commits" - label: "开源协议" - value: "MIT 协议" + value: "MIT License" description: "完全免费,支持商业使用" icon: "license" diff --git a/docs/develop-guides/roadmap.md b/docs/develop-guides/roadmap.md index f1728a3f..63af2d15 100644 --- a/docs/develop-guides/roadmap.md +++ b/docs/develop-guides/roadmap.md @@ -24,7 +24,7 @@ ## v0.6 - +- 调整 Agent 路由为 `/agent/{thread_id}`:`/agent` 进入未选中对话空态,不再在 URL 中展示 `agent_id`;发送首条消息时基于当前 `selectedAgentId` 与配置创建新对话,并自动跳转到对应线程路由。 - 新增 API Key 管理功能,支持外部系统通过 API Key 调用 Agent 对话接口(`POST /api/chat/agent/{agent_id}`)。统一使用 `Authorization: Bearer ` 认证,API Key 以 `yxkey_` 开头。获取 API Key 即代表拥有绑定用户的所有接口访问权限。 - 将 后端代码 和 agents 解耦,agents 作为单独的 package 使用 - 添加 subagents diff --git a/web/public/login-bg.jpg b/web/public/login-bg.jpg index 4716005a..2689b5d4 100644 Binary files a/web/public/login-bg.jpg and b/web/public/login-bg.jpg differ diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue index eb588e87..9d7093c9 100644 --- a/web/src/components/AgentChatComponent.vue +++ b/web/src/components/AgentChatComponent.vue @@ -251,6 +251,7 @@ const props = defineProps({ agentId: { type: String, default: '' }, singleMode: { type: Boolean, default: true } }) +const emit = defineEmits(['thread-change']) // ==================== STORE MANAGEMENT ==================== const agentStore = useAgentStore() @@ -516,7 +517,7 @@ onMounted(() => { for (const entry of entries) { const width = entry.contentRect.width const isTakingSpace = chatUIStore.isSidebarOpen && !isSidebarFloating.value - + if (isTakingSpace) { if (width < 600) { isSidebarFloating.value = true @@ -849,58 +850,21 @@ const sendMessage = async ({ } // ==================== CHAT ACTIONS ==================== -// 检查第一个对话是否为空 -const isFirstChatEmpty = () => { - if (threads.value.length === 0) return false - const chatToReuse = getFirstNonPinnedChat(threads.value) - const messages = threadMessages.value[chatToReuse.id] - // 只有当消息已加载且为空时才返回 true - return messages !== undefined && messages.length === 0 -} - // 获取第一个非置顶的对话 const getFirstNonPinnedChat = (chatList) => { if (!chatList || chatList.length === 0) return null return chatList.find((chat) => !chat.is_pinned) || chatList[0] } -// 如果第一个对话为空,直接切换到第一个非置顶对话 -const switchToFirstChatIfEmpty = async () => { - if (threads.value.length > 0 && isFirstChatEmpty()) { - const chatToReuse = getFirstNonPinnedChat(threads.value) - if (chatState.currentThreadId !== chatToReuse.id) { - await selectChat(chatToReuse.id) - } - return true - } - return false -} - const createNewChat = async () => { - if ( - !AgentValidator.validateAgentId(currentAgentId.value, '创建对话') || - chatUIStore.creatingNewChat - ) - return - - // 如果第一个对话为空,直接切换到第一个对话而不是创建新对话 - if (await switchToFirstChatIfEmpty()) return - - chatUIStore.creatingNewChat = true - try { - const newThread = await createThread(currentAgentId.value, '新的对话') - if (newThread) { - // 中断之前线程的流式输出(如果存在) - const previousThreadId = chatState.currentThreadId - stopThreadStream(previousThreadId) - - chatState.currentThreadId = newThread.id - } - } catch (error) { - handleChatError(error, 'create') - } finally { - chatUIStore.creatingNewChat = false + const previousThreadId = chatState.currentThreadId + if (previousThreadId) { + stopThreadStream(previousThreadId) + // run 模式下仅断开 SSE 订阅,不取消后台运行任务 + stopRunStreamSubscription(previousThreadId) } + // 进入未选中对话空态,路由由 thread-change 统一同步到 /agent + chatState.currentThreadId = null } const selectChat = async (chatId) => { @@ -951,6 +915,38 @@ const selectChat = async (chatId) => { await resumeActiveRunForThread(chatId) } +const selectThreadFromRoute = async (threadId) => { + if (!agentStore.isInitialized) { + await initAll() + } + + if (!threadId) { + const previousThreadId = chatState.currentThreadId + if (previousThreadId) { + stopThreadStream(previousThreadId) + stopRunStreamSubscription(previousThreadId) + } + chatState.currentThreadId = null + return true + } + + if (chatState.currentThreadId === threadId) { + return true + } + + if (!threads.value.length || !threads.value.find((thread) => thread.id === threadId)) { + await loadChatsList() + } + + const targetThread = threads.value.find((thread) => thread.id === threadId) + if (!targetThread) { + return false + } + + await selectChat(threadId) + return true +} + const deleteChat = async (chatId) => { if ( !AgentValidator.validateAgentIdWithError( @@ -964,11 +960,8 @@ const deleteChat = async (chatId) => { await deleteThread(chatId) if (chatState.currentThreadId === chatId) { chatState.currentThreadId = null - // 如果删除的是当前对话,自动创建新对话 + // 删除当前对话后回到空态,发送消息时再创建新线程 await createNewChat() - } else if (chatsList.value.length > 0) { - // 如果删除的不是当前对话,选择第一个非置顶可用对话 - await selectChat(getFirstNonPinnedChat(chatsList.value).id) } } catch (error) { handleChatError(error, 'delete') @@ -1229,7 +1222,8 @@ const buildExportPayload = () => { } defineExpose({ - getExportPayload: buildExportPayload + getExportPayload: buildExportPayload, + selectThreadFromRoute }) const toggleSidebar = () => { @@ -1341,8 +1335,8 @@ const loadChatsList = async () => { chatState.currentThreadId = null } - // 如果有线程但没有选中任何线程,自动选择第一个非置顶对话 - if (threads.value.length > 0 && !chatState.currentThreadId) { + // singleMode 保持旧行为:自动选择首个可用对话 + if (props.singleMode && threads.value.length > 0 && !chatState.currentThreadId) { await selectChat(getFirstNonPinnedChat(threads.value).id) } } catch (error) { @@ -1401,6 +1395,11 @@ watch( }, { deep: true, flush: 'post' } ) + +watch(currentChatId, (threadId, oldThreadId) => { + if (threadId === oldThreadId) return + emit('thread-change', threadId || '') +}) diff --git a/web/src/components/DepartmentManagementComponent.vue b/web/src/components/DepartmentManagementComponent.vue index 68ac0e98..0d577d09 100644 --- a/web/src/components/DepartmentManagementComponent.vue +++ b/web/src/components/DepartmentManagementComponent.vue @@ -3,11 +3,11 @@
-

部门管理

-

管理系统部门,部门下的用户会被隔离管理。

+
部门管理
+

管理系统部门,部门下的用户会被隔离管理。

- - + + 添加部门
@@ -46,9 +46,9 @@ type="text" size="small" @click="showEditDepartmentModal(record)" - class="action-btn" + class="action-btn lucide-icon-btn" > - + @@ -58,9 +58,9 @@ danger @click="confirmDeleteDepartment(record)" :disabled="record.user_count > 0" - class="action-btn" + class="action-btn lucide-icon-btn" > - + @@ -110,7 +110,7 @@