From b0038d3da573cdfefc405f61190e1352624d834b Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Mon, 17 Nov 2025 12:01:59 +0800 Subject: [PATCH] =?UTF-8?q?feat(agent):=20=E6=B7=BB=E5=8A=A0=E6=99=BA?= =?UTF-8?q?=E8=83=BD=E4=BD=93=E7=8A=B6=E6=80=81=E5=88=B7=E6=96=B0=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=8F=8AAPI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在AgentPopover组件中添加刷新按钮 - 新增获取AgentState的API接口 - 优化AgentState相关计算逻辑 - 实现状态刷新功能并集成到聊天组件 --- server/routers/chat_router.py | 28 +++++++++++++++ web/src/apis/agent_api.js | 8 +++++ web/src/components/AgentChatComponent.vue | 43 ++++++++++++++++++----- web/src/components/AgentPopover.vue | 16 +++++++-- 4 files changed, 85 insertions(+), 10 deletions(-) diff --git a/server/routers/chat_router.py b/server/routers/chat_router.py index 24ecc964..7d5e9ba6 100644 --- a/server/routers/chat_router.py +++ b/server/routers/chat_router.py @@ -882,6 +882,34 @@ async def get_agent_history( raise HTTPException(status_code=500, detail=f"获取智能体历史消息出错: {str(e)}") +@chat.get("/agent/{agent_id}/state") +async def get_agent_state( + agent_id: str, + thread_id: str, + current_user: User = Depends(get_required_user), + db: Session = Depends(get_db), +): + try: + if not agent_manager.get_agent(agent_id): + raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在") + + conv_manager = ConversationManager(db) + _require_user_conversation(conv_manager, thread_id, str(current_user.id)) + + agent = agent_manager.get_agent(agent_id) + graph = await agent.get_graph() + langgraph_config = {"configurable": {"user_id": str(current_user.id), "thread_id": thread_id}} + state = await graph.aget_state(langgraph_config) + agent_state = _extract_agent_state(getattr(state, "values", {})) if state else {} + + return {"agent_state": agent_state} + except HTTPException: + raise + except Exception as e: + logger.error(f"获取AgentState出错: {e}, {traceback.format_exc()}") + raise HTTPException(status_code=500, detail=f"获取AgentState出错: {str(e)}") + + @chat.get("/agent/{agent_id}/config") async def get_agent_config(agent_id: str, current_user: User = Depends(get_required_user)): """从YAML文件加载智能体配置(需要登录)""" diff --git a/web/src/apis/agent_api.js b/web/src/apis/agent_api.js index 86b70607..e4ed4dc9 100644 --- a/web/src/apis/agent_api.js +++ b/web/src/apis/agent_api.js @@ -73,6 +73,14 @@ export const agentApi = { */ getAgentHistory: (agentId, threadId) => apiGet(`/api/chat/agent/${agentId}/history?thread_id=${threadId}`), + /** + * 获取指定会话的 AgentState + * @param {string} agentId - 智能体ID + * @param {string} threadId - 会话ID + * @returns {Promise} - AgentState + */ + getAgentState: (agentId, threadId) => apiGet(`/api/chat/agent/${agentId}/state?thread_id=${threadId}`), + /** * Submit feedback for a message * @param {number} messageId - Message ID diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue index 15c09a50..cefa2896 100644 --- a/web/src/components/AgentChatComponent.vue +++ b/web/src/components/AgentChatComponent.vue @@ -45,8 +45,10 @@
{ return currentChatId.value ? getThreadState(currentChatId.value)?.agentState || null : null; }); +const countFiles = (files) => { + if (!Array.isArray(files)) return 0; + let c = 0; + for (const item of files) { + if (item && typeof item === 'object') c += Object.keys(item).length; + } + return c; +}; + const hasAgentStateContent = computed(() => { - const agentState = currentAgentState.value; - if (!agentState) return false; - return (agentState.todos && agentState.todos.length > 0) || - (agentState.files && Object.keys(agentState.files).length > 0); + const s = currentAgentState.value; + if (!s) return false; + const todoCount = Array.isArray(s.todos) ? s.todos.length : 0; + const fileCount = countFiles(s.files); + return todoCount > 0 || fileCount > 0; }); const totalAgentStateItems = computed(() => { - const agentState = currentAgentState.value; - if (!agentState) return 0; - const todoCount = agentState.todos ? agentState.todos.length : 0; - const fileCount = agentState.files ? Object.keys(agentState.files).length : 0; + const s = currentAgentState.value; + if (!s) return 0; + const todoCount = Array.isArray(s.todos) ? s.todos.length : 0; + const fileCount = countFiles(s.files); return todoCount + fileCount; }); @@ -756,6 +768,15 @@ const fetchThreadMessages = async ({ agentId, threadId, delay = 0 }) => { } }; +const fetchAgentState = async (agentId, threadId) => { + if (!agentId || !threadId) return; + try { + const res = await agentApi.getAgentState(agentId, threadId); + const ts = getThreadState(threadId); + if (ts) ts.agentState = res.agent_state || null; + } catch (error) {} +}; + const loadThreadAttachments = async (threadId, { silent = false } = {}) => { if (!threadId) return; try { @@ -954,6 +975,7 @@ const selectChat = async (chatId) => { try { await fetchThreadMessages({ agentId: currentAgentId.value, threadId: chatId }); await loadThreadAttachments(chatId, { silent: true }); + await fetchAgentState(currentAgentId.value, chatId); } catch (error) { handleChatError(error, 'load'); } finally { @@ -1240,6 +1262,11 @@ const toggleSidebar = () => { }; const openAgentModal = () => emit('open-agent-modal'); +const handleAgentStateRefresh = async () => { + if (!currentAgentId.value || !currentChatId.value) return; + await fetchAgentState(currentAgentId.value, currentChatId.value); +}; + // ==================== HELPER FUNCTIONS ==================== const getLastMessage = (conv) => { if (!conv?.messages?.length) return null; diff --git a/web/src/components/AgentPopover.vue b/web/src/components/AgentPopover.vue index b42bc91b..719ae034 100644 --- a/web/src/components/AgentPopover.vue +++ b/web/src/components/AgentPopover.vue @@ -25,6 +25,7 @@ > 文件 ({{ fileCount }}) + 刷新
@@ -103,7 +104,7 @@ const props = defineProps({ } }); -const emit = defineEmits(['update:visible']); +const emit = defineEmits(['update:visible', 'refresh']); const activeTab = ref('todos'); const modalVisible = ref(false); @@ -209,6 +210,10 @@ const closeModal = () => { currentFile.value = null; currentFilePath.value = ''; }; + +const emitRefresh = () => { + emit('refresh'); +}; \ No newline at end of file + +.refresh-btn { + margin-left: auto; + color: var(--gray-700); +} + +