feat: add agent timestamps and improve agent retrieval logic

This commit is contained in:
Siddhant Rai
2025-04-15 14:06:20 +05:30
parent 7c69e99914
commit d80eeb044c
7 changed files with 104 additions and 23 deletions

View File

@@ -90,14 +90,23 @@ def get_agent_key(agent_id, user_id):
if not agent_id: if not agent_id:
return None return None
agent = agents_collection.find_one({"_id": ObjectId(agent_id)}) try:
if agent is None: agent = agents_collection.find_one({"_id": ObjectId(agent_id)})
raise Exception("Agent not found", 404) if agent is None:
raise Exception("Agent not found", 404)
if agent.get("is_public") or agent.get("user") == user_id: if agent.get("user") == user_id:
return str(agent["key"]) 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): def get_data_from_api_key(api_key):

View File

@@ -970,6 +970,9 @@ class GetAgent(Resource):
"tools": agent.get("tools", []), "tools": agent.get("tools", []),
"agent_type": agent["agent_type"], "agent_type": agent["agent_type"],
"status": agent["status"], "status": agent["status"],
"createdAt": agent["createdAt"],
"updatedAt": agent["updatedAt"],
"lastUsedAt": agent["lastUsedAt"],
"key": f"{agent['key'][:4]}...{agent['key'][-4:]}", "key": f"{agent['key'][:4]}...{agent['key'][-4:]}",
} }
except Exception as err: except Exception as err:
@@ -1005,6 +1008,9 @@ class GetAgents(Resource):
"tools": agent.get("tools", []), "tools": agent.get("tools", []),
"agent_type": agent["agent_type"], "agent_type": agent["agent_type"],
"status": agent["status"], "status": agent["status"],
"created_at": agent["createdAt"],
"updated_at": agent["updatedAt"],
"last_used_at": agent["lastUsedAt"],
"key": f"{agent['key'][:4]}...{agent['key'][-4:]}", "key": f"{agent['key'][:4]}...{agent['key'][-4:]}",
} }
for agent in agents for agent in agents

View File

@@ -43,6 +43,7 @@ import {
setConversations, setConversations,
setModalStateDeleteConv, setModalStateDeleteConv,
setSelectedAgent, setSelectedAgent,
setAgents,
} from './preferences/preferenceSlice'; } from './preferences/preferenceSlice';
import Upload from './upload/Upload'; import Upload from './upload/Upload';
@@ -90,9 +91,17 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
async function getAgents() { async function getAgents() {
const response = await userService.getAgents(token); const response = await userService.getAgents(token);
if (!response.ok) throw new Error('Failed to fetch agents'); 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( 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) {
</div> </div>
) : ( ) : (
<div <div
className="mx-4 my-auto mt-2 flex h-9 cursor-pointer items-center gap-2 rounded-3xl pl-4" className="mx-4 my-auto mt-2 flex h-9 cursor-pointer items-center gap-2 rounded-3xl pl-4 hover:bg-bright-gray dark:hover:bg-dark-charcoal"
onClick={() => navigate('/agents')} onClick={() => navigate('/agents')}
> >
<div className="flex w-6 justify-center"> <div className="flex w-6 justify-center">
@@ -366,7 +375,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
className="h-[18px] w-[18px]" className="h-[18px] w-[18px]"
/> />
</div> </div>
<p className="overflow-hidden overflow-ellipsis whitespace-nowrap text-sm leading-6 text-eerie-black hover:text-purple-30 dark:text-bright-gray hover:dark:text-purple-30"> <p className="overflow-hidden overflow-ellipsis whitespace-nowrap text-sm leading-6 text-eerie-black dark:text-bright-gray">
Manage Agents Manage Agents
</p> </p>
</div> </div>

View File

@@ -1,5 +1,5 @@
import React, { SyntheticEvent, useEffect, useRef, useState } from 'react'; 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 { Route, Routes, useNavigate } from 'react-router-dom';
import userService from '../api/services/userService'; import userService from '../api/services/userService';
@@ -12,10 +12,11 @@ import ThreeDots from '../assets/three-dots.svg';
import ContextMenu, { MenuOption } from '../components/ContextMenu'; import ContextMenu, { MenuOption } from '../components/ContextMenu';
import ConfirmationModal from '../modals/ConfirmationModal'; import ConfirmationModal from '../modals/ConfirmationModal';
import { ActiveState } from '../models/misc'; import { ActiveState } from '../models/misc';
import { selectToken } from '../preferences/preferenceSlice'; import { selectToken, setSelectedAgent } from '../preferences/preferenceSlice';
import AgentLogs from './AgentLogs'; import AgentLogs from './AgentLogs';
import NewAgent from './NewAgent'; import NewAgent from './NewAgent';
import { Agent } from './types'; import { Agent } from './types';
import Spinner from '../components/Spinner';
export default function Agents() { export default function Agents() {
return ( return (
@@ -33,14 +34,23 @@ function AgentsList() {
const token = useSelector(selectToken); const token = useSelector(selectToken);
const [userAgents, setUserAgents] = useState<Agent[]>([]); const [userAgents, setUserAgents] = useState<Agent[]>([]);
const [loading, setLoading] = useState<boolean>(true);
useEffect(() => { const getAgents = async () => {
const getAgents = async () => { try {
setLoading(true);
const response = await userService.getAgents(token); const response = await userService.getAgents(token);
if (!response.ok) throw new Error('Failed to fetch agents'); if (!response.ok) throw new Error('Failed to fetch agents');
const data = await response.json(); const data = await response.json();
setUserAgents(data); setUserAgents(data);
}; setLoading(false);
} catch (error) {
console.error('Error:', error);
setLoading(false);
}
};
useEffect(() => {
getAgents(); getAgents();
}, [token]); }, [token]);
return ( return (
@@ -107,9 +117,29 @@ function AgentsList() {
</button> </button>
</div> </div>
<div className="flex w-full flex-wrap gap-4"> <div className="flex w-full flex-wrap gap-4">
{userAgents.map((agent, idx) => ( {loading ? (
<AgentCard key={idx} agent={agent} setUserAgents={setUserAgents} /> <div className="flex h-72 w-full items-center justify-center">
))} <Spinner />
</div>
) : userAgents.length > 0 ? (
userAgents.map((agent) => (
<AgentCard
key={agent.id}
agent={agent}
setUserAgents={setUserAgents}
/>
))
) : (
<div className="flex h-72 w-full flex-col items-center justify-center gap-3 text-base text-[#18181B] dark:text-[#E0E0E0]">
<p>You dont have any created agents yet </p>
<button
className="ml-2 rounded-full bg-purple-30 px-4 py-2 text-sm text-white hover:bg-violets-are-blue"
onClick={() => navigate('/agents/new')}
>
New Agent
</button>
</div>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -124,12 +154,15 @@ function AgentCard({
setUserAgents: React.Dispatch<React.SetStateAction<Agent[]>>; setUserAgents: React.Dispatch<React.SetStateAction<Agent[]>>;
}) { }) {
const navigate = useNavigate(); const navigate = useNavigate();
const dispatch = useDispatch();
const token = useSelector(selectToken); const token = useSelector(selectToken);
const menuRef = useRef<HTMLDivElement>(null);
const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false); const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false);
const [deleteConfirmation, setDeleteConfirmation] = const [deleteConfirmation, setDeleteConfirmation] =
useState<ActiveState>('INACTIVE'); useState<ActiveState>('INACTIVE');
const menuRef = useRef<HTMLDivElement>(null);
const menuOptions: MenuOption[] = [ const menuOptions: MenuOption[] = [
{ {
icon: Monitoring, icon: Monitoring,
@@ -156,13 +189,21 @@ function AgentCard({
{ {
icon: Trash, icon: Trash,
label: 'Delete', label: 'Delete',
onClick: () => setDeleteConfirmation('ACTIVE'), onClick: (e: SyntheticEvent) => {
e.stopPropagation();
setDeleteConfirmation('ACTIVE');
},
variant: 'danger', variant: 'danger',
iconWidth: 12, iconWidth: 12,
iconHeight: 12, iconHeight: 12,
}, },
]; ];
const handleClick = () => {
dispatch(setSelectedAgent(agent));
navigate(`/`);
};
const handleDelete = async (agentId: string) => { const handleDelete = async (agentId: string) => {
const response = await userService.deleteAgent(agentId, token); const response = await userService.deleteAgent(agentId, token);
if (!response.ok) throw new Error('Failed to delete agent'); if (!response.ok) throw new Error('Failed to delete agent');
@@ -172,13 +213,17 @@ function AgentCard({
); );
}; };
return ( return (
<div className="relative flex h-44 w-48 flex-col justify-between rounded-[1.2rem] bg-[#F6F6F6] px-6 py-5 dark:bg-[#383838]"> <div
className="relative flex h-44 w-48 cursor-pointer flex-col justify-between rounded-[1.2rem] bg-[#F6F6F6] px-6 py-5 dark:bg-[#383838]"
onClick={(e) => handleClick()}
>
<div <div
ref={menuRef} ref={menuRef}
onClick={() => { onClick={(e) => {
e.stopPropagation();
setIsMenuOpen(true); setIsMenuOpen(true);
}} }}
className="absolute right-4 top-4 cursor-pointer" className="absolute right-4 top-4 z-50 cursor-pointer"
> >
<img src={ThreeDots} alt={'use-agent'} className="h-[19px] w-[19px]" /> <img src={ThreeDots} alt={'use-agent'} className="h-[19px] w-[19px]" />
<ContextMenu <ContextMenu

View File

@@ -11,4 +11,7 @@ export type Agent = {
agent_type: string; agent_type: string;
status: string; status: string;
key?: string; key?: string;
created_at?: string;
updated_at?: string;
last_used_at?: string;
}; };

View File

@@ -24,6 +24,7 @@ export interface Preference {
token: string | null; token: string | null;
modalState: ActiveState; modalState: ActiveState;
paginatedDocuments: Doc[] | null; paginatedDocuments: Doc[] | null;
agents: Agent[] | null;
selectedAgent: Agent | null; selectedAgent: Agent | null;
} }
@@ -49,6 +50,7 @@ const initialState: Preference = {
token: localStorage.getItem('authToken') || null, token: localStorage.getItem('authToken') || null,
modalState: 'INACTIVE', modalState: 'INACTIVE',
paginatedDocuments: null, paginatedDocuments: null,
agents: null,
selectedAgent: null, selectedAgent: null,
}; };
@@ -86,6 +88,9 @@ export const prefSlice = createSlice({
setModalStateDeleteConv: (state, action: PayloadAction<ActiveState>) => { setModalStateDeleteConv: (state, action: PayloadAction<ActiveState>) => {
state.modalState = action.payload; state.modalState = action.payload;
}, },
setAgents: (state, action) => {
state.agents = action.payload;
},
setSelectedAgent: (state, action) => { setSelectedAgent: (state, action) => {
state.selectedAgent = action.payload; state.selectedAgent = action.payload;
}, },
@@ -103,6 +108,7 @@ export const {
setTokenLimit, setTokenLimit,
setModalStateDeleteConv, setModalStateDeleteConv,
setPaginatedDocuments, setPaginatedDocuments,
setAgents,
setSelectedAgent, setSelectedAgent,
} = prefSlice.actions; } = prefSlice.actions;
export default prefSlice.reducer; export default prefSlice.reducer;
@@ -178,5 +184,6 @@ export const selectTokenLimit = (state: RootState) =>
state.preference.token_limit; state.preference.token_limit;
export const selectPaginatedDocuments = (state: RootState) => export const selectPaginatedDocuments = (state: RootState) =>
state.preference.paginatedDocuments; state.preference.paginatedDocuments;
export const selectAgents = (state: RootState) => state.preference.agents;
export const selectSelectedAgent = (state: RootState) => export const selectSelectedAgent = (state: RootState) =>
state.preference.selectedAgent; state.preference.selectedAgent;

View File

@@ -1,4 +1,5 @@
import { configureStore } from '@reduxjs/toolkit'; import { configureStore } from '@reduxjs/toolkit';
import { conversationSlice } from './conversation/conversationSlice'; import { conversationSlice } from './conversation/conversationSlice';
import { sharedConversationSlice } from './conversation/sharedConversationSlice'; import { sharedConversationSlice } from './conversation/sharedConversationSlice';
import { import {
@@ -40,6 +41,7 @@ const preloadedState: { preference: Preference } = {
], ],
modalState: 'INACTIVE', modalState: 'INACTIVE',
paginatedDocuments: null, paginatedDocuments: null,
agents: null,
selectedAgent: null, selectedAgent: null,
}, },
}; };