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"
>
) => {
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,
},
};