feat: implement pinning functionality for agents with UI updates

This commit is contained in:
Siddhant Rai
2025-05-06 16:12:55 +05:30
parent dcfcbf54be
commit 07fa656e7c
12 changed files with 307 additions and 117 deletions

View File

@@ -110,7 +110,9 @@ class DeleteConversation(Resource):
{"_id": ObjectId(conversation_id), "user": decoded_token["sub"]}
)
except Exception as err:
current_app.logger.error(f"Error deleting conversation: {err}", exc_info=True)
current_app.logger.error(
f"Error deleting conversation: {err}", exc_info=True
)
return make_response(jsonify({"success": False}), 400)
return make_response(jsonify({"success": True}), 200)
@@ -128,7 +130,9 @@ class DeleteAllConversations(Resource):
try:
conversations_collection.delete_many({"user": user_id})
except Exception as err:
current_app.logger.error(f"Error deleting all conversations: {err}", exc_info=True)
current_app.logger.error(
f"Error deleting all conversations: {err}", exc_info=True
)
return make_response(jsonify({"success": False}), 400)
return make_response(jsonify({"success": True}), 200)
@@ -166,7 +170,9 @@ class GetConversations(Resource):
for conversation in conversations
]
except Exception as err:
current_app.logger.error(f"Error retrieving conversations: {err}", exc_info=True)
current_app.logger.error(
f"Error retrieving conversations: {err}", exc_info=True
)
return make_response(jsonify({"success": False}), 400)
return make_response(jsonify(list_conversations), 200)
@@ -194,7 +200,9 @@ class GetSingleConversation(Resource):
if not conversation:
return make_response(jsonify({"status": "not found"}), 404)
except Exception as err:
current_app.logger.error(f"Error retrieving conversation: {err}", exc_info=True)
current_app.logger.error(
f"Error retrieving conversation: {err}", exc_info=True
)
return make_response(jsonify({"success": False}), 400)
data = {
@@ -236,7 +244,9 @@ class UpdateConversationName(Resource):
{"$set": {"name": data["name"]}},
)
except Exception as err:
current_app.logger.error(f"Error updating conversation name: {err}", exc_info=True)
current_app.logger.error(
f"Error updating conversation name: {err}", exc_info=True
)
return make_response(jsonify({"success": False}), 400)
return make_response(jsonify({"success": True}), 200)
@@ -377,7 +387,9 @@ class DeleteOldIndexes(Resource):
except FileNotFoundError:
pass
except Exception as err:
current_app.logger.error(f"Error deleting old indexes: {err}", exc_info=True)
current_app.logger.error(
f"Error deleting old indexes: {err}", exc_info=True
)
return make_response(jsonify({"success": False}), 400)
sources_collection.delete_one({"_id": ObjectId(source_id)})
@@ -577,7 +589,9 @@ class UploadRemote(Resource):
loader=data["source"],
)
except Exception as err:
current_app.logger.error(f"Error uploading remote source: {err}", exc_info=True)
current_app.logger.error(
f"Error uploading remote source: {err}", exc_info=True
)
return make_response(jsonify({"success": False}), 400)
return make_response(jsonify({"success": True, "task_id": task.id}), 200)
@@ -689,7 +703,9 @@ class PaginatedSources(Resource):
return make_response(jsonify(response), 200)
except Exception as err:
current_app.logger.error(f"Error retrieving paginated sources: {err}", exc_info=True)
current_app.logger.error(
f"Error retrieving paginated sources: {err}", exc_info=True
)
return make_response(jsonify({"success": False}), 400)
@@ -996,23 +1012,28 @@ class GetAgent(Resource):
return make_response(jsonify({"status": "Not found"}), 404)
data = {
"id": str(agent["_id"]),
"name": agent["name"],
"description": agent["description"],
"name": agent.get("name", ""),
"description": agent.get("description", ""),
"source": (
str(db.dereference(agent["source"])["_id"])
if "source" in agent and isinstance(agent["source"], DBRef)
else ""
),
"chunks": agent["chunks"],
"chunks": agent.get("chunks", ""),
"retriever": agent.get("retriever", ""),
"prompt_id": agent["prompt_id"],
"prompt_id": agent.get("prompt_id", ""),
"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:]}",
"agent_type": agent.get("agent_type", ""),
"status": agent.get("status", ""),
"created_at": agent.get("createdAt", ""),
"updated_at": agent.get("updatedAt", ""),
"last_used_at": agent.get("lastUsedAt", ""),
"key": (
f"{agent['key'][:4]}...{agent['key'][-4:]}"
if "key" in agent
else ""
),
"pinned": agent.get("pinned", False),
}
except Exception as err:
current_app.logger.error(f"Error retrieving agent: {err}", exc_info=True)
@@ -1034,23 +1055,28 @@ class GetAgents(Resource):
list_agents = [
{
"id": str(agent["_id"]),
"name": agent["name"],
"description": agent["description"],
"name": agent.get("name", ""),
"description": agent.get("description", ""),
"source": (
str(db.dereference(agent["source"])["_id"])
if "source" in agent and isinstance(agent["source"], DBRef)
else ""
),
"chunks": agent["chunks"],
"chunks": agent.get("chunks", ""),
"retriever": agent.get("retriever", ""),
"prompt_id": agent["prompt_id"],
"prompt_id": agent.get("prompt_id", ""),
"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:]}",
"agent_type": agent.get("agent_type", ""),
"status": agent.get("status", ""),
"created_at": agent.get("createdAt", ""),
"updated_at": agent.get("updatedAt", ""),
"last_used_at": agent.get("lastUsedAt", ""),
"key": (
f"{agent['key'][:4]}...{agent['key'][-4:]}"
if "key" in agent
else ""
),
"pinned": agent.get("pinned", False),
}
for agent in agents
if "source" in agent or "retriever" in agent
@@ -1196,7 +1222,9 @@ class UpdateAgent(Resource):
existing_agent = agents_collection.find_one({"_id": oid, "user": user})
except Exception as err:
return make_response(
current_app.logger.error(f"Error finding agent {agent_id}: {err}", exc_info=True),
current_app.logger.error(
f"Error finding agent {agent_id}: {err}", exc_info=True
),
jsonify({"success": False, "message": "Database error finding agent"}),
500,
)
@@ -1319,7 +1347,9 @@ class UpdateAgent(Resource):
)
except Exception as err:
current_app.logger.error(f"Error updating agent {agent_id}: {err}", exc_info=True)
current_app.logger.error(
f"Error updating agent {agent_id}: {err}", exc_info=True
)
return make_response(
jsonify({"success": False, "message": "Database error during update"}),
500,
@@ -1368,6 +1398,86 @@ class DeleteAgent(Resource):
return make_response(jsonify({"id": deleted_id}), 200)
@user_ns.route("/api/pinned_agents")
class PinnedAgents(Resource):
@api.doc(description="Get pinned agents for the user")
def get(self):
decoded_token = request.decoded_token
if not decoded_token:
return make_response(jsonify({"success": False}), 401)
user = decoded_token.get("sub")
try:
pinned_agents = agents_collection.find({"user": user, "pinned": True})
list_pinned_agents = [
{
"id": str(agent["_id"]),
"name": agent.get("name", ""),
"description": agent.get("description", ""),
"source": (
str(db.dereference(agent["source"])["_id"])
if "source" in agent and isinstance(agent["source"], DBRef)
else ""
),
"chunks": agent.get("chunks", ""),
"retriever": agent.get("retriever", ""),
"prompt_id": agent.get("prompt_id", ""),
"tools": agent.get("tools", []),
"agent_type": agent.get("agent_type", ""),
"status": agent.get("status", ""),
"created_at": agent.get("createdAt", ""),
"updated_at": agent.get("updatedAt", ""),
"last_used_at": agent.get("lastUsedAt", ""),
"key": (
f"{agent['key'][:4]}...{agent['key'][-4:]}"
if "key" in agent
else ""
),
"pinned": agent.get("pinned", False),
}
for agent in pinned_agents
if "source" in agent or "retriever" in agent
]
except Exception as err:
current_app.logger.error(f"Error retrieving pinned agents: {err}")
return make_response(jsonify({"success": False}), 400)
return make_response(jsonify(list_pinned_agents), 200)
@user_ns.route("/api/pin_agent")
class PinAgent(Resource):
@api.doc(params={"id": "ID of the agent"}, description="Pin or unpin an agent")
def post(self):
decoded_token = request.decoded_token
if not decoded_token:
return make_response(jsonify({"success": False}), 401)
user = decoded_token.get("sub")
agent_id = request.args.get("id")
if not agent_id:
return make_response(
jsonify({"success": False, "message": "ID is required"}), 400
)
try:
agent = agents_collection.find_one(
{"_id": ObjectId(agent_id), "user": user}
)
if not agent:
return make_response(
jsonify({"success": False, "message": "Agent not found"}), 404
)
pinned_status = not agent.get("pinned", False)
agents_collection.update_one(
{"_id": ObjectId(agent_id), "user": user},
{"$set": {"pinned": pinned_status}},
)
except Exception as err:
current_app.logger.error(f"Error pinning/unpinning agent: {err}")
return make_response(jsonify({"success": False}), 400)
return make_response(jsonify({"success": True}), 200)
@user_ns.route("/api/agent_webhook")
class AgentWebhook(Resource):
@api.doc(
@@ -1405,7 +1515,9 @@ class AgentWebhook(Resource):
full_webhook_url = f"{base_url}/api/webhooks/agents/{webhook_token}"
except Exception as err:
current_app.logger.error(f"Error generating webhook URL: {err}", exc_info=True)
current_app.logger.error(
f"Error generating webhook URL: {err}", exc_info=True
)
return make_response(
jsonify({"success": False, "message": "Error generating webhook URL"}),
400,
@@ -1694,7 +1806,9 @@ class ShareConversation(Resource):
201,
)
except Exception as err:
current_app.logger.error(f"Error sharing conversation: {err}", exc_info=True)
current_app.logger.error(
f"Error sharing conversation: {err}", exc_info=True
)
return make_response(jsonify({"success": False}), 400)
@@ -1750,7 +1864,9 @@ class GetPubliclySharedConversations(Resource):
res["api_key"] = shared["api_key"]
return make_response(jsonify(res), 200)
except Exception as err:
current_app.logger.error(f"Error getting shared conversation: {err}", exc_info=True)
current_app.logger.error(
f"Error getting shared conversation: {err}", exc_info=True
)
return make_response(jsonify({"success": False}), 400)
@@ -1870,7 +1986,9 @@ class GetMessageAnalytics(Resource):
daily_messages[entry["_id"]] = entry["count"]
except Exception as err:
current_app.logger.error(f"Error getting message analytics: {err}", exc_info=True)
current_app.logger.error(
f"Error getting message analytics: {err}", exc_info=True
)
return make_response(jsonify({"success": False}), 400)
return make_response(
@@ -2029,7 +2147,9 @@ class GetTokenAnalytics(Resource):
daily_token_usage[entry["_id"]["day"]] = entry["total_tokens"]
except Exception as err:
current_app.logger.error(f"Error getting token analytics: {err}", exc_info=True)
current_app.logger.error(
f"Error getting token analytics: {err}", exc_info=True
)
return make_response(jsonify({"success": False}), 400)
return make_response(
@@ -2194,7 +2314,9 @@ class GetFeedbackAnalytics(Resource):
}
except Exception as err:
current_app.logger.error(f"Error getting feedback analytics: {err}", exc_info=True)
current_app.logger.error(
f"Error getting feedback analytics: {err}", exc_info=True
)
return make_response(jsonify({"success": False}), 400)
return make_response(
@@ -2330,7 +2452,9 @@ class ManageSync(Resource):
update_data,
)
except Exception as err:
current_app.logger.error(f"Error updating sync frequency: {err}", exc_info=True)
current_app.logger.error(
f"Error updating sync frequency: {err}", exc_info=True
)
return make_response(jsonify({"success": False}), 400)
return make_response(jsonify({"success": True}), 200)
@@ -2391,7 +2515,9 @@ class AvailableTools(Resource):
}
)
except Exception as err:
current_app.logger.error(f"Error getting available tools: {err}", exc_info=True)
current_app.logger.error(
f"Error getting available tools: {err}", exc_info=True
)
return make_response(jsonify({"success": False}), 400)
return make_response(jsonify({"success": True, "data": tools_metadata}), 200)
@@ -2595,7 +2721,9 @@ class UpdateToolConfig(Resource):
{"$set": {"config": data["config"]}},
)
except Exception as err:
current_app.logger.error(f"Error updating tool config: {err}", exc_info=True)
current_app.logger.error(
f"Error updating tool config: {err}", exc_info=True
)
return make_response(jsonify({"success": False}), 400)
return make_response(jsonify({"success": True}), 200)
@@ -2634,7 +2762,9 @@ class UpdateToolActions(Resource):
{"$set": {"actions": data["actions"]}},
)
except Exception as err:
current_app.logger.error(f"Error updating tool actions: {err}", exc_info=True)
current_app.logger.error(
f"Error updating tool actions: {err}", exc_info=True
)
return make_response(jsonify({"success": False}), 400)
return make_response(jsonify({"success": True}), 200)
@@ -2671,7 +2801,9 @@ class UpdateToolStatus(Resource):
{"$set": {"status": data["status"]}},
)
except Exception as err:
current_app.logger.error(f"Error updating tool status: {err}", exc_info=True)
current_app.logger.error(
f"Error updating tool status: {err}", exc_info=True
)
return make_response(jsonify({"success": False}), 400)
return make_response(jsonify({"success": True}), 200)

View File

@@ -9415,13 +9415,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
},
"node_modules/unified": {
"version": "11.0.5",
"resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",

View File

@@ -13,12 +13,14 @@ import Expand from './assets/expand.svg';
import Github from './assets/github.svg';
import Hamburger from './assets/hamburger.svg';
import openNewChat from './assets/openNewChat.svg';
import Pin from './assets/pin.svg';
import Robot from './assets/robot.svg';
import SettingGear from './assets/settingGear.svg';
import Spark from './assets/spark.svg';
import SpinnerDark from './assets/spinner-dark.svg';
import Spinner from './assets/spinner.svg';
import Twitter from './assets/TwitterX.svg';
import UnPin from './assets/unpin.svg';
import Help from './components/Help';
import {
handleAbort,
@@ -35,16 +37,16 @@ import JWTModal from './modals/JWTModal';
import { ActiveState } from './models/misc';
import { getConversations } from './preferences/preferenceApi';
import {
selectAgents,
selectConversationId,
selectConversations,
selectModalStateDeleteConv,
selectSelectedAgent,
selectToken,
setAgents,
setConversations,
setModalStateDeleteConv,
setSelectedAgent,
setAgents,
selectAgents,
} from './preferences/preferenceSlice';
import Upload from './upload/Upload';
@@ -80,24 +82,35 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
async function fetchRecentAgents() {
try {
let recentAgents: Agent[] = [];
const response = await userService.getPinnedAgents(token);
if (!response.ok) throw new Error('Failed to fetch pinned agents');
const pinnedAgents: Agent[] = await response.json();
if (pinnedAgents.length >= 3) {
setRecentAgents(pinnedAgents);
return;
}
let tempAgents: Agent[] = [];
if (!agents) {
const response = await userService.getAgents(token);
if (!response.ok) throw new Error('Failed to fetch agents');
const data: Agent[] = await response.json();
dispatch(setAgents(data));
recentAgents = data;
} else recentAgents = agents;
setRecentAgents(
recentAgents
.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),
);
tempAgents = data;
} else tempAgents = agents;
const additionalAgents = tempAgents
.filter(
(agent: Agent) =>
agent.status === 'published' &&
!pinnedAgents.some((pinned) => pinned.id === agent.id),
)
.sort(
(a: Agent, b: Agent) =>
new Date(b.last_used_at ?? 0).getTime() -
new Date(a.last_used_at ?? 0).getTime(),
)
.slice(0, 3 - pinnedAgents.length);
setRecentAgents([...pinnedAgents, ...additionalAgents]);
console.log(additionalAgents);
} catch (error) {
console.error('Failed to fetch recent agents: ', error);
}
@@ -116,7 +129,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
}
useEffect(() => {
if (token) fetchRecentAgents();
fetchRecentAgents();
}, [agents, token, dispatch]);
useEffect(() => {
@@ -152,6 +165,17 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
navigate('/');
};
const handleTogglePin = (agent: Agent) => {
userService.togglePinAgent(agent.id ?? '', token).then((response) => {
if (response.ok) {
const updatedAgents = agents?.map((a) =>
a.id === agent.id ? { ...a, pinned: !a.pinned } : a,
);
dispatch(setAgents(updatedAgents));
}
});
};
const handleConversationClick = (index: string) => {
conversationService
.getConversation(index, token)
@@ -336,23 +360,39 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
{recentAgents.map((agent, idx) => (
<div
key={idx}
className={`mx-4 my-auto mt-4 flex h-9 cursor-pointer items-center gap-2 rounded-3xl pl-4 hover:bg-bright-gray dark:hover:bg-dark-charcoal ${
className={`mx-4 my-auto mt-4 flex h-9 cursor-pointer items-center justify-between rounded-3xl pl-4 hover:bg-bright-gray dark:hover:bg-dark-charcoal ${
agent.id === selectedAgent?.id && !conversationId
? 'bg-bright-gray dark:bg-dark-charcoal'
: ''
}`}
onClick={() => handleAgentClick(agent)}
>
<div className="flex w-6 justify-center">
<img
src={agent.image ?? Robot}
alt="agent-logo"
className="h-6 w-6 rounded-full"
/>
<div className="flex items-center gap-2">
<div className="flex w-6 justify-center">
<img
src={agent.image ?? Robot}
alt="agent-logo"
className="h-6 w-6 rounded-full"
/>
</div>
<p className="overflow-hidden overflow-ellipsis whitespace-nowrap text-sm leading-6 text-eerie-black dark:text-bright-gray">
{agent.name}
</p>
</div>
<div className="flex items-center px-3">
<button
className="rounded-full hover:opacity-75"
onClick={(e) => {
e.stopPropagation();
handleTogglePin(agent);
}}
>
<img
src={agent.pinned ? UnPin : Pin}
className="h-4 w-4"
></img>
</button>
</div>
<p className="overflow-hidden overflow-ellipsis whitespace-nowrap text-sm leading-6 text-eerie-black dark:text-bright-gray">
{agent.name}
</p>
</div>
))}
</div>

View File

@@ -4,10 +4,10 @@ import { useNavigate, useParams } from 'react-router-dom';
import userService from '../api/services/userService';
import ArrowLeft from '../assets/arrow-left.svg';
import Spinner from '../components/Spinner';
import { selectToken } from '../preferences/preferenceSlice';
import Analytics from '../settings/Analytics';
import Logs from '../settings/Logs';
import Spinner from '../components/Spinner';
import { Agent } from './types';
export default function AgentLogs() {
@@ -54,11 +54,16 @@ export default function AgentLogs() {
</h1>
</div>
<div className="mt-6 flex flex-col gap-3 px-4">
<h2 className="text-sm font-semibold text-black dark:text-[#E0E0E0]">
Agent Name
</h2>
{agent && (
<p className="text-[#28292E] dark:text-[#E0E0E0]">{agent.name}</p>
<div className="flex flex-col gap-1">
<p className="text-[#28292E] dark:text-[#E0E0E0]">{agent.name}</p>
<p className="text-xs text-[#28292E] dark:text-[#E0E0E0]/40">
{agent.last_used_at
? 'Last used at ' +
new Date(agent.last_used_at).toLocaleString()
: 'No usage history'}
</p>
</div>
)}
</div>
{loadingAgent ? (
@@ -74,7 +79,7 @@ export default function AgentLogs() {
<Spinner />
</div>
) : (
agent && <Logs agentId={agentId} tableHeader="Agent endpoint logs" />
agent && <Logs agentId={agent.id} tableHeader="Agent endpoint logs" />
)}
</div>
);

View File

@@ -1,28 +1,30 @@
import React, { SyntheticEvent, useEffect, useRef, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { SyntheticEvent, useEffect, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Route, Routes, useNavigate } from 'react-router-dom';
import userService from '../api/services/userService';
import Copy from '../assets/copy-linear.svg';
import Edit from '../assets/edit.svg';
import Monitoring from '../assets/monitoring.svg';
import Pin from '../assets/pin.svg';
import Trash from '../assets/red-trash.svg';
import Robot from '../assets/robot.svg';
import ThreeDots from '../assets/three-dots.svg';
import UnPin from '../assets/unpin.svg';
import ContextMenu, { MenuOption } from '../components/ContextMenu';
import Spinner from '../components/Spinner';
import ConfirmationModal from '../modals/ConfirmationModal';
import { ActiveState } from '../models/misc';
import {
selectToken,
setSelectedAgent,
setAgents,
selectAgents,
selectSelectedAgent,
selectToken,
setAgents,
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 (
@@ -42,7 +44,6 @@ function AgentsList() {
const agents = useSelector(selectAgents);
const selectedAgent = useSelector(selectSelectedAgent);
const [userAgents, setUserAgents] = useState<Agent[]>(agents || []);
const [loading, setLoading] = useState<boolean>(true);
const getAgents = async () => {
@@ -51,7 +52,6 @@ function AgentsList() {
const response = await userService.getAgents(token);
if (!response.ok) throw new Error('Failed to fetch agents');
const data = await response.json();
setUserAgents(data);
dispatch(setAgents(data));
setLoading(false);
} catch (error) {
@@ -133,14 +133,9 @@ function AgentsList() {
<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}
agents={userAgents}
setUserAgents={setUserAgents}
/>
) : agents && agents.length > 0 ? (
agents.map((agent) => (
<AgentCard key={agent.id} agent={agent} agents={agents} />
))
) : (
<div className="flex h-72 w-full flex-col items-center justify-center gap-3 text-base text-[#18181B] dark:text-[#E0E0E0]">
@@ -159,15 +154,7 @@ function AgentsList() {
);
}
function AgentCard({
agent,
agents,
setUserAgents,
}: {
agent: Agent;
agents: Agent[];
setUserAgents: React.Dispatch<React.SetStateAction<Agent[]>>;
}) {
function AgentCard({ agent, agents }: { agent: Agent; agents: Agent[] }) {
const navigate = useNavigate();
const dispatch = useDispatch();
const token = useSelector(selectToken);
@@ -178,6 +165,21 @@ function AgentCard({
const menuRef = useRef<HTMLDivElement>(null);
const togglePin = async () => {
try {
const response = await userService.togglePinAgent(agent.id ?? '', token);
if (!response.ok) throw new Error('Failed to pin agent');
const updatedAgents = agents.map((prevAgent) => {
if (prevAgent.id === agent.id)
return { ...prevAgent, pinned: !prevAgent.pinned };
return prevAgent;
});
dispatch(setAgents(updatedAgents));
} catch (error) {
console.error('Error:', error);
}
};
const menuOptions: MenuOption[] = [
{
icon: Monitoring,
@@ -201,6 +203,17 @@ function AgentCard({
iconWidth: 14,
iconHeight: 14,
},
{
icon: agent.pinned ? UnPin : Pin,
label: agent.pinned ? 'Unpin' : 'Pin agent',
onClick: (e: SyntheticEvent) => {
e.stopPropagation();
togglePin();
},
variant: 'primary',
iconWidth: 18,
iconHeight: 18,
},
{
icon: Trash,
label: 'Delete',
@@ -209,8 +222,8 @@ function AgentCard({
setDeleteConfirmation('ACTIVE');
},
variant: 'danger',
iconWidth: 12,
iconHeight: 12,
iconWidth: 13,
iconHeight: 13,
},
];
@@ -225,9 +238,6 @@ function AgentCard({
const response = await userService.deleteAgent(agentId, token);
if (!response.ok) throw new Error('Failed to delete agent');
const data = await response.json();
setUserAgents((prevAgents) =>
prevAgents.filter((prevAgent) => prevAgent.id !== data.id),
);
dispatch(setAgents(agents.filter((prevAgent) => prevAgent.id !== data.id)));
};
return (
@@ -244,7 +254,7 @@ function AgentCard({
e.stopPropagation();
setIsMenuOpen(true);
}}
className="absolute right-4 top-4 z-50 cursor-pointer"
className="absolute right-4 top-4 z-10 cursor-pointer"
>
<img src={ThreeDots} alt={'use-agent'} className="h-[19px] w-[19px]" />
<ContextMenu

View File

@@ -11,6 +11,8 @@ export type Agent = {
agent_type: string;
status: string;
key?: string;
incoming_webhook_token?: string;
pinned?: boolean;
created_at?: string;
updated_at?: string;
last_used_at?: string;

View File

@@ -13,6 +13,8 @@ const endpoints = {
CREATE_AGENT: '/api/create_agent',
UPDATE_AGENT: (agent_id: string) => `/api/update_agent/${agent_id}`,
DELETE_AGENT: (id: string) => `/api/delete_agent?id=${id}`,
PINNED_AGENTS: '/api/pinned_agents',
TOGGLE_PIN_AGENT: (id: string) => `/api/pin_agent?id=${id}`,
AGENT_WEBHOOK: (id: string) => `/api/agent_webhook?id=${id}`,
PROMPTS: '/api/get_prompts',
CREATE_PROMPT: '/api/create_prompt',

View File

@@ -31,6 +31,10 @@ const userService = {
apiClient.put(endpoints.USER.UPDATE_AGENT(agent_id), data, token),
deleteAgent: (id: string, token: string | null): Promise<any> =>
apiClient.delete(endpoints.USER.DELETE_AGENT(id), token),
getPinnedAgents: (token: string | null): Promise<any> =>
apiClient.get(endpoints.USER.PINNED_AGENTS, token),
togglePinAgent: (id: string, token: string | null): Promise<any> =>
apiClient.post(endpoints.USER.TOGGLE_PIN_AGENT(id), {}, token),
getAgentWebhook: (id: string, token: string | null): Promise<any> =>
apiClient.get(endpoints.USER.AGENT_WEBHOOK(id), token),
getPrompts: (token: string | null): Promise<any> =>

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#747474" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pin-icon lucide-pin"><path d="M12 17v5"/><path d="M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z"/></svg>

After

Width:  |  Height:  |  Size: 458 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#747474" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pin-off-icon lucide-pin-off"><path d="M12 17v5"/><path d="M15 9.34V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H7.89"/><path d="m2 2 20 20"/><path d="M9 9v1.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h11"/></svg>

After

Width:  |  Height:  |  Size: 416 B

View File

@@ -104,8 +104,8 @@ export default function ContextMenu({
}}
className={`flex items-center justify-start gap-4 p-3 transition-colors duration-200 ease-in-out ${index === 0 ? 'rounded-t-xl' : ''} ${index === options.length - 1 ? 'rounded-b-xl' : ''} ${
option.variant === 'danger'
? 'text-rosso-corsa hover:bg-bright-gray dark:text-red-2000 dark:hover:bg-charcoal-grey'
: 'text-eerie-black hover:bg-bright-gray dark:text-bright-gray dark:hover:bg-charcoal-grey'
? 'text-rosso-corsa hover:bg-bright-gray dark:text-red-2000 dark:hover:bg-charcoal-grey/20'
: 'text-eerie-black hover:bg-bright-gray dark:text-bright-gray dark:hover:bg-charcoal-grey/20'
} `}
>
{option.icon && (
@@ -115,7 +115,7 @@ export default function ContextMenu({
height={option.iconHeight || 16}
src={option.icon}
alt={option.label}
className={`cursor-pointer hover:opacity-75 ${option.iconClassName || ''}`}
className={`cursor-pointer ${option.iconClassName || ''}`}
/>
</div>
)}

View File

@@ -50,11 +50,11 @@ body.dark {
@layer components {
.table-default {
@apply block w-full table-auto justify-center rounded-xl border border-silver dark:border-silver/40 text-center dark:text-bright-gray overflow-auto;
@apply block w-full table-auto justify-center overflow-auto rounded-xl border border-silver text-center dark:border-silver/40 dark:text-bright-gray;
}
.table-default th {
@apply p-4 font-normal text-gray-400 text-nowrap;
@apply text-nowrap p-4 font-normal text-gray-400;
}
.table-default th {
@@ -66,7 +66,7 @@ body.dark {
}
.table-default td {
@apply border-t w-full border-silver dark:border-silver/40 px-4 py-2;
@apply w-full border-t border-silver px-4 py-2 dark:border-silver/40;
}
.table-default td:last-child {