feat(agent): 添加智能体状态刷新功能及API

- 在AgentPopover组件中添加刷新按钮
- 新增获取AgentState的API接口
- 优化AgentState相关计算逻辑
- 实现状态刷新功能并集成到聊天组件
This commit is contained in:
Wenjie Zhang 2025-11-17 12:01:59 +08:00
parent 9a8f1c4ae3
commit b0038d3da5
4 changed files with 85 additions and 10 deletions

View File

@ -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文件加载智能体配置需要登录"""

View File

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

View File

@ -45,8 +45,10 @@
<div class="header__right">
<!-- AgentState 显示按钮 - 只在智能体支持 todo files 能力时显示 -->
<AgentPopover
v-if="hasAgentStateContent"
v-model:visible="agentStatePopoverVisible"
:agent-state="currentAgentState"
@refresh="handleAgentStateRefresh"
>
<div
class="agent-nav-btn agent-state-btn"
@ -369,18 +371,28 @@ const currentAgentState = computed(() => {
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;

View File

@ -25,6 +25,7 @@
>
文件 ({{ fileCount }})
</button>
<a-button type="text" class="refresh-btn" @click="emitRefresh">刷新</a-button>
</div>
<div class="tab-content">
<!-- Todo Display -->
@ -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');
};
</script>
<style scoped lang="less">
@ -223,6 +228,7 @@ const closeModal = () => {
display: flex;
border-bottom: 1px solid var(--gray-200);
position: relative;
align-items: center;
}
.tab {
@ -496,4 +502,10 @@ const closeModal = () => {
}
}
}
</style>
.refresh-btn {
margin-left: auto;
color: var(--gray-700);
}
</style>