diff --git a/application/api/answer/routes.py b/application/api/answer/routes.py index 704c672c..9af1e27e 100644 --- a/application/api/answer/routes.py +++ b/application/api/answer/routes.py @@ -90,14 +90,23 @@ def get_agent_key(agent_id, user_id): if not agent_id: return None - agent = agents_collection.find_one({"_id": ObjectId(agent_id)}) - if agent is None: - raise Exception("Agent not found", 404) + try: + agent = agents_collection.find_one({"_id": ObjectId(agent_id)}) + if agent is None: + raise Exception("Agent not found", 404) - if agent.get("is_public") or agent.get("user") == user_id: - return str(agent["key"]) + if agent.get("user") == user_id: + agents_collection.update_one( + {"_id": ObjectId(agent_id)}, + {"$set": {"lastUsedAt": datetime.datetime.now(datetime.timezone.utc)}}, + ) + return str(agent["key"]) - raise Exception("Unauthorized access to the agent", 403) + raise Exception("Unauthorized access to the agent", 403) + + except Exception as e: + logger.error(f"Error in get_agent_key: {str(e)}") + raise def get_data_from_api_key(api_key): diff --git a/application/api/user/routes.py b/application/api/user/routes.py index 04303caa..a4bc97e7 100644 --- a/application/api/user/routes.py +++ b/application/api/user/routes.py @@ -970,6 +970,9 @@ class GetAgent(Resource): "tools": agent.get("tools", []), "agent_type": agent["agent_type"], "status": agent["status"], + "createdAt": agent["createdAt"], + "updatedAt": agent["updatedAt"], + "lastUsedAt": agent["lastUsedAt"], "key": f"{agent['key'][:4]}...{agent['key'][-4:]}", } except Exception as err: @@ -1005,6 +1008,9 @@ class GetAgents(Resource): "tools": agent.get("tools", []), "agent_type": agent["agent_type"], "status": agent["status"], + "created_at": agent["createdAt"], + "updated_at": agent["updatedAt"], + "last_used_at": agent["lastUsedAt"], "key": f"{agent['key'][:4]}...{agent['key'][-4:]}", } for agent in agents diff --git a/frontend/src/Navigation.tsx b/frontend/src/Navigation.tsx index 2e64f6b9..0e357a6d 100644 --- a/frontend/src/Navigation.tsx +++ b/frontend/src/Navigation.tsx @@ -43,6 +43,7 @@ import { setConversations, setModalStateDeleteConv, setSelectedAgent, + setAgents, } from './preferences/preferenceSlice'; import Upload from './upload/Upload'; @@ -90,9 +91,17 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { async function getAgents() { const response = await userService.getAgents(token); if (!response.ok) throw new Error('Failed to fetch agents'); - const data = await response.json(); + const data: Agent[] = await response.json(); + dispatch(setAgents(data)); setRecentAgents( - data.filter((agent: Agent) => agent.status === 'published'), + data + .filter((agent: Agent) => agent.status === 'published') + .sort( + (a: Agent, b: Agent) => + new Date(b.last_used_at ?? 0).getTime() - + new Date(a.last_used_at ?? 0).getTime(), + ) + .slice(0, 3), ); } @@ -356,7 +365,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { ) : (
navigate('/agents')} >
@@ -366,7 +375,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { className="h-[18px] w-[18px]" />
-

+

Manage Agents

diff --git a/frontend/src/agents/index.tsx b/frontend/src/agents/index.tsx index 89b9880c..49123cd6 100644 --- a/frontend/src/agents/index.tsx +++ b/frontend/src/agents/index.tsx @@ -1,5 +1,5 @@ import React, { SyntheticEvent, useEffect, useRef, useState } from 'react'; -import { useSelector } from 'react-redux'; +import { useSelector, useDispatch } from 'react-redux'; import { Route, Routes, useNavigate } from 'react-router-dom'; import userService from '../api/services/userService'; @@ -12,10 +12,11 @@ import ThreeDots from '../assets/three-dots.svg'; import ContextMenu, { MenuOption } from '../components/ContextMenu'; import ConfirmationModal from '../modals/ConfirmationModal'; import { ActiveState } from '../models/misc'; -import { selectToken } from '../preferences/preferenceSlice'; +import { selectToken, setSelectedAgent } from '../preferences/preferenceSlice'; import AgentLogs from './AgentLogs'; import NewAgent from './NewAgent'; import { Agent } from './types'; +import Spinner from '../components/Spinner'; export default function Agents() { return ( @@ -33,14 +34,23 @@ function AgentsList() { const token = useSelector(selectToken); const [userAgents, setUserAgents] = useState([]); + const [loading, setLoading] = useState(true); - useEffect(() => { - const getAgents = async () => { + const getAgents = async () => { + try { + setLoading(true); const response = await userService.getAgents(token); if (!response.ok) throw new Error('Failed to fetch agents'); const data = await response.json(); setUserAgents(data); - }; + setLoading(false); + } catch (error) { + console.error('Error:', error); + setLoading(false); + } + }; + + useEffect(() => { getAgents(); }, [token]); return ( @@ -107,9 +117,29 @@ function AgentsList() {
- {userAgents.map((agent, idx) => ( - - ))} + {loading ? ( +
+ +
+ ) : userAgents.length > 0 ? ( + userAgents.map((agent) => ( + + )) + ) : ( +
+

You don’t have any created agents yet

+ +
+ )}
@@ -124,12 +154,15 @@ function AgentCard({ setUserAgents: React.Dispatch>; }) { const navigate = useNavigate(); + const dispatch = useDispatch(); const token = useSelector(selectToken); - const menuRef = useRef(null); + const [isMenuOpen, setIsMenuOpen] = useState(false); const [deleteConfirmation, setDeleteConfirmation] = useState('INACTIVE'); + const menuRef = useRef(null); + const menuOptions: MenuOption[] = [ { icon: Monitoring, @@ -156,13 +189,21 @@ function AgentCard({ { icon: Trash, label: 'Delete', - onClick: () => setDeleteConfirmation('ACTIVE'), + onClick: (e: SyntheticEvent) => { + e.stopPropagation(); + setDeleteConfirmation('ACTIVE'); + }, variant: 'danger', iconWidth: 12, iconHeight: 12, }, ]; + const handleClick = () => { + dispatch(setSelectedAgent(agent)); + navigate(`/`); + }; + const handleDelete = async (agentId: string) => { const response = await userService.deleteAgent(agentId, token); if (!response.ok) throw new Error('Failed to delete agent'); @@ -172,13 +213,17 @@ function AgentCard({ ); }; return ( -
+
handleClick()} + >
{ + onClick={(e) => { + e.stopPropagation(); setIsMenuOpen(true); }} - className="absolute right-4 top-4 cursor-pointer" + className="absolute right-4 top-4 z-50 cursor-pointer" > {'use-agent'} ) => { state.modalState = action.payload; }, + setAgents: (state, action) => { + state.agents = action.payload; + }, setSelectedAgent: (state, action) => { state.selectedAgent = action.payload; }, @@ -103,6 +108,7 @@ export const { setTokenLimit, setModalStateDeleteConv, setPaginatedDocuments, + setAgents, setSelectedAgent, } = prefSlice.actions; export default prefSlice.reducer; @@ -178,5 +184,6 @@ export const selectTokenLimit = (state: RootState) => state.preference.token_limit; export const selectPaginatedDocuments = (state: RootState) => state.preference.paginatedDocuments; +export const selectAgents = (state: RootState) => state.preference.agents; export const selectSelectedAgent = (state: RootState) => state.preference.selectedAgent; diff --git a/frontend/src/store.ts b/frontend/src/store.ts index 3592421b..113185bb 100644 --- a/frontend/src/store.ts +++ b/frontend/src/store.ts @@ -1,4 +1,5 @@ import { configureStore } from '@reduxjs/toolkit'; + import { conversationSlice } from './conversation/conversationSlice'; import { sharedConversationSlice } from './conversation/sharedConversationSlice'; import { @@ -40,6 +41,7 @@ const preloadedState: { preference: Preference } = { ], modalState: 'INACTIVE', paginatedDocuments: null, + agents: null, selectedAgent: null, }, };