feat(agent): 添加智能体状态刷新功能及API
- 在AgentPopover组件中添加刷新按钮 - 新增获取AgentState的API接口 - 优化AgentState相关计算逻辑 - 实现状态刷新功能并集成到聊天组件
This commit is contained in:
parent
9a8f1c4ae3
commit
b0038d3da5
@ -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文件加载智能体配置(需要登录)"""
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user