refactor: agents sharing and shared with me logic

This commit is contained in:
Siddhant Rai
2025-05-28 13:58:55 +05:30
parent b7a6bad7cd
commit ad1a944276
6 changed files with 158 additions and 137 deletions

View File

@@ -15,6 +15,7 @@ from flask_restx import fields, inputs, Namespace, Resource
from werkzeug.utils import secure_filename
from application.agents.tools.tool_manager import ToolManager
from pymongo import ReturnDocument
from application.api.user.tasks import (
ingest,
@@ -50,6 +51,7 @@ agents_collection.create_index(
name="shared_index",
background=True,
)
users_collection.create_index("user_id", unique=True)
user = Blueprint("user", __name__)
user_ns = Namespace("user", description="User related operations", path="/")
@@ -85,29 +87,29 @@ def generate_date_range(start_date, end_date):
def ensure_user_doc(user_id):
user_doc = users_collection.find_one({"user_id": user_id})
default_prefs = {
"pinned": [],
"shared_with_me": [],
}
if not user_doc:
user_doc = {
"user_id": user_id,
"agent_preferences": {"pinned": [], "hidden_shared": []},
}
users_collection.insert_one(user_doc)
return user_doc
updated = False
preferences = user_doc.get("agent_preferences", {})
user_doc = users_collection.find_one_and_update(
{"user_id": user_id},
{"$setOnInsert": {"agent_preferences": default_prefs}},
upsert=True,
return_document=ReturnDocument.AFTER,
)
prefs = user_doc.get("agent_preferences", {})
updates = {}
if "pinned" not in prefs:
updates["agent_preferences.pinned"] = []
if "shared_with_me" not in prefs:
updates["agent_preferences.shared_with_me"] = []
if updates:
users_collection.update_one({"user_id": user_id}, {"$set": updates})
user_doc = users_collection.find_one({"user_id": user_id})
if "pinned" not in preferences:
preferences["pinned"] = []
updated = True
if "hidden_shared" not in preferences:
preferences["hidden_shared"] = []
updated = True
if updated:
users_collection.update_one(
{"user_id": user_id}, {"$set": {"agent_preferences": preferences}}
)
user_doc["agent_preferences"] = preferences
return user_doc
@@ -1464,26 +1466,25 @@ class PinnedAgents(Resource):
decoded_token = request.decoded_token
if not decoded_token:
return make_response(jsonify({"success": False}), 401)
user_id = decoded_token.get("sub")
try:
user_doc = ensure_user_doc(user_id)
pinned_ids = user_doc.get("agent_preferences", {}).get("pinned", [])
hidden_ids = set(
user_doc.get("agent_preferences", {}).get("hidden_shared", [])
)
if not pinned_ids:
return make_response(jsonify([]), 200)
pinned_object_ids = [ObjectId(agent_id) for agent_id in pinned_ids]
pinned_agents_cursor = agents_collection.find(
{"_id": {"$in": pinned_object_ids}}
)
pinned_agents = list(pinned_agents_cursor)
existing_ids = {str(agent["_id"]) for agent in pinned_agents}
existing_agents = pinned_agents
existing_ids = {str(agent["_id"]) for agent in existing_agents}
# Clean up any stale pinned IDs
stale_ids = [
agent_id for agent_id in pinned_ids if agent_id not in existing_ids
]
@@ -1523,12 +1524,13 @@ class PinnedAgents(Resource):
"pinned": True,
}
for agent in pinned_agents
if ("source" in agent or "retriever" in agent)
and str(agent["_id"]) not in hidden_ids
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)
@@ -1575,11 +1577,11 @@ class PinAgent(Resource):
return make_response(jsonify({"success": True, "action": action}), 200)
@user_ns.route("/api/hide_shared_agent")
class HideSharedAgent(Resource):
@user_ns.route("/api/remove_shared_agent")
class RemoveSharedAgent(Resource):
@api.doc(
params={"id": "ID of the shared agent"},
description="Hide or unhide a shared agent for the current user",
description="Remove a shared agent from the current user's shared list",
)
def delete(self):
decoded_token = request.decoded_token
@@ -1592,6 +1594,7 @@ class HideSharedAgent(Resource):
return make_response(
jsonify({"success": False, "message": "ID is required"}), 400
)
try:
agent = agents_collection.find_one(
{"_id": ObjectId(agent_id), "shared_publicly": True}
@@ -1601,27 +1604,25 @@ class HideSharedAgent(Resource):
jsonify({"success": False, "message": "Shared agent not found"}),
404,
)
user_doc = ensure_user_doc(user_id)
hidden_list = user_doc.get("agent_preferences", {}).get("hidden_shared", [])
if agent_id in hidden_list:
users_collection.update_one(
{"user_id": user_id},
{"$pull": {"agent_preferences.hidden_shared": agent_id}},
)
action = "unhidden"
else:
users_collection.update_one(
{"user_id": user_id},
{"$addToSet": {"agent_preferences.hidden_shared": agent_id}},
)
action = "hidden"
ensure_user_doc(user_id)
users_collection.update_one(
{"user_id": user_id},
{
"$pull": {
"agent_preferences.shared_with_me": agent_id,
"agent_preferences.pinned": agent_id,
}
},
)
return make_response(jsonify({"success": True, "action": "removed"}), 200)
except Exception as err:
current_app.logger.error(f"Error hiding/unhiding shared agent: {err}")
current_app.logger.error(f"Error removing shared agent: {err}")
return make_response(
jsonify({"success": False, "message": "Server error"}), 500
)
return make_response(jsonify({"success": True, "action": action}), 200)
@user_ns.route("/api/shared_agent")
@@ -1639,19 +1640,22 @@ class SharedAgent(Resource):
return make_response(
jsonify({"success": False, "message": "Token or ID is required"}), 400
)
try:
query = {}
query["shared_publicly"] = True
query["shared_token"] = shared_token
try:
query = {
"shared_publicly": True,
"shared_token": shared_token,
}
shared_agent = agents_collection.find_one(query)
if not shared_agent:
return make_response(
jsonify({"success": False, "message": "Shared agent not found"}),
404,
)
agent_id = str(shared_agent["_id"])
data = {
"id": str(shared_agent["_id"]),
"id": agent_id,
"user": shared_agent.get("user", ""),
"name": shared_agent.get("name", ""),
"description": shared_agent.get("description", ""),
@@ -1672,15 +1676,29 @@ class SharedAgent(Resource):
if tool_data:
enriched_tools.append(tool_data.get("name", ""))
data["tools"] = enriched_tools
decoded_token = getattr(request, "decoded_token", None)
if decoded_token:
user_id = decoded_token.get("sub")
owner_id = shared_agent.get("user")
if user_id != owner_id:
ensure_user_doc(user_id)
users_collection.update_one(
{"user_id": user_id},
{"$addToSet": {"agent_preferences.shared_with_me": agent_id}},
)
return make_response(jsonify(data), 200)
except Exception as err:
current_app.logger.error(f"Error retrieving shared agent: {err}")
return make_response(jsonify({"success": False}), 400)
return make_response(jsonify(data), 200)
@user_ns.route("/api/shared_agents")
class SharedAgents(Resource):
@api.doc(description="Get shared agents")
@api.doc(description="Get shared agents explicitly shared with the user")
def get(self):
try:
decoded_token = request.decoded_token
@@ -1689,29 +1707,25 @@ class SharedAgents(Resource):
user_id = decoded_token.get("sub")
user_doc = ensure_user_doc(user_id)
pinned_ids = set(user_doc.get("agent_preferences", {}).get("pinned", []))
hidden_ids = user_doc.get("agent_preferences", {}).get("hidden_shared", [])
hidden_object_ids = [ObjectId(id) for id in hidden_ids]
shared_with_ids = user_doc.get("agent_preferences", {}).get(
"shared_with_me", []
)
shared_object_ids = [ObjectId(id) for id in shared_with_ids]
shared_agents_cursor = agents_collection.find(
{"shared_publicly": True, "user": {"$ne": user_id}}
{"_id": {"$in": shared_object_ids}, "shared_publicly": True}
)
shared_agents = list(shared_agents_cursor)
shared_ids_set = {agent["_id"] for agent in shared_agents}
hidden_ids_set = set(hidden_object_ids)
stale_hidden_ids = [
str(id) for id in hidden_ids_set if id not in shared_ids_set
]
if stale_hidden_ids:
found_ids_set = {str(agent["_id"]) for agent in shared_agents}
stale_ids = [id for id in shared_with_ids if id not in found_ids_set]
if stale_ids:
users_collection.update_one(
{"user_id": user_id},
{"$pullAll": {"agent_preferences.hidden_shared": stale_hidden_ids}},
{"$pullAll": {"agent_preferences.shared_with_me": stale_ids}},
)
visible_shared_agents = [
agent for agent in shared_agents if agent["_id"] not in hidden_ids_set
]
pinned_ids = set(user_doc.get("agent_preferences", {}).get("pinned", []))
list_shared_agents = [
{
@@ -1728,10 +1742,11 @@ class SharedAgents(Resource):
"shared_token": agent.get("shared_token", ""),
"shared_metadata": agent.get("shared_metadata", {}),
}
for agent in visible_shared_agents
for agent in shared_agents
]
return make_response(jsonify(list_shared_agents), 200)
except Exception as err:
current_app.logger.error(f"Error retrieving shared agents: {err}")
return make_response(jsonify({"success": False}), 400)

View File

@@ -21,6 +21,7 @@ import {
import { useDarkTheme } from '../hooks';
import { selectToken, setSelectedAgent } from '../preferences/preferenceSlice';
import { AppDispatch } from '../store';
import SharedAgentCard from './SharedAgentCard';
import { Agent } from './types';
export default function SharedAgent() {
@@ -193,65 +194,3 @@ export default function SharedAgent() {
</div>
);
}
function SharedAgentCard({ agent }: { agent: Agent }) {
return (
<div className="flex w-full max-w-[720px] flex-col rounded-3xl border border-dark-gray p-6 shadow-sm dark:border-grey sm:w-fit sm:min-w-[480px]">
<div className="flex items-center gap-3">
<div className="flex h-12 w-12 items-center justify-center overflow-hidden rounded-full p-1">
<img src={Robot} className="h-full w-full object-contain" />
</div>
<div className="flex max-h-[92px] w-[80%] flex-col gap-px">
<h2 className="text-base font-semibold text-[#212121] dark:text-[#E0E0E0] sm:text-lg">
{agent.name}
</h2>
<p className="overflow-y-auto text-wrap break-all text-xs text-[#71717A] dark:text-[#949494] sm:text-sm">
{agent.description}
</p>
</div>
</div>
<div className="mt-4 flex items-center gap-8">
{agent.shared_metadata?.shared_by && (
<p className="text-xs font-light text-[#212121] dark:text-[#E0E0E0] sm:text-sm">
by {agent.shared_metadata.shared_by}
</p>
)}
{agent.shared_metadata?.shared_at && (
<p className="text-xs font-light text-[#71717A] dark:text-[#949494] sm:text-sm">
Shared on{' '}
{new Date(agent.shared_metadata.shared_at).toLocaleString('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: true,
})}
</p>
)}
</div>
{agent.tools.length > 0 && (
<div className="mt-8">
<p className="text-sm font-semibold text-[#212121] dark:text-[#E0E0E0] sm:text-base">
Connected Tools
</p>
<div className="mt-2 flex flex-wrap gap-2">
{agent.tools.map((tool, index) => (
<span
key={index}
className="flex items-center gap-1 rounded-full bg-bright-gray px-3 py-1 text-xs font-light text-[#212121] dark:bg-dark-charcoal dark:text-[#E0E0E0]"
>
<img
src={`/toolIcons/tool_${tool}.svg`}
alt={`${tool} icon`}
className="h-3 w-3"
/>{' '}
{tool}
</span>
))}
</div>
</div>
)}
</div>
);
}

View File

@@ -0,0 +1,64 @@
import Robot from '../assets/robot.svg';
import { Agent } from './types';
export default function SharedAgentCard({ agent }: { agent: Agent }) {
return (
<div className="flex w-full max-w-[720px] flex-col rounded-3xl border border-dark-gray p-6 shadow-sm dark:border-grey sm:w-fit sm:min-w-[480px]">
<div className="flex items-center gap-3">
<div className="flex h-12 w-12 items-center justify-center overflow-hidden rounded-full p-1">
<img src={Robot} className="h-full w-full object-contain" />
</div>
<div className="flex max-h-[92px] w-[80%] flex-col gap-px">
<h2 className="text-base font-semibold text-[#212121] dark:text-[#E0E0E0] sm:text-lg">
{agent.name}
</h2>
<p className="overflow-y-auto text-wrap break-all text-xs text-[#71717A] dark:text-[#949494] sm:text-sm">
{agent.description}
</p>
</div>
</div>
<div className="mt-4 flex items-center gap-8">
{agent.shared_metadata?.shared_by && (
<p className="text-xs font-light text-[#212121] dark:text-[#E0E0E0] sm:text-sm">
by {agent.shared_metadata.shared_by}
</p>
)}
{agent.shared_metadata?.shared_at && (
<p className="text-xs font-light text-[#71717A] dark:text-[#949494] sm:text-sm">
Shared on{' '}
{new Date(agent.shared_metadata.shared_at).toLocaleString('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: true,
})}
</p>
)}
</div>
{agent.tools.length > 0 && (
<div className="mt-8">
<p className="text-sm font-semibold text-[#212121] dark:text-[#E0E0E0] sm:text-base">
Connected Tools
</p>
<div className="mt-2 flex flex-wrap gap-2">
{agent.tools.map((tool, index) => (
<span
key={index}
className="flex items-center gap-1 rounded-full bg-bright-gray px-3 py-1 text-xs font-light text-[#212121] dark:bg-dark-charcoal dark:text-[#E0E0E0]"
>
<img
src={`/toolIcons/tool_${tool}.svg`}
alt={`${tool} icon`}
className="h-3 w-3"
/>{' '}
{tool}
</span>
))}
</div>
</div>
)}
</div>
);
}

View File

@@ -286,7 +286,10 @@ function AgentCard({
const handleHideSharedAgent = async () => {
try {
const response = await userService.hideSharedAgent(agent.id ?? '', token);
const response = await userService.removeSharedAgent(
agent.id ?? '',
token,
);
if (!response.ok) throw new Error('Failed to hide shared agent');
const updatedAgents = agents.filter(
(prevAgent) => prevAgent.id !== agent.id,

View File

@@ -18,7 +18,7 @@ const endpoints = {
SHARED_AGENT: (id: string) => `/api/shared_agent?token=${id}`,
SHARED_AGENTS: '/api/shared_agents',
SHARE_AGENT: `/api/share_agent`,
HIDE_SHARED_AGENT: (id: string) => `/api/hide_shared_agent?id=${id}`,
REMOVE_SHARED_AGENT: (id: string) => `/api/remove_shared_agent?id=${id}`,
AGENT_WEBHOOK: (id: string) => `/api/agent_webhook?id=${id}`,
PROMPTS: '/api/get_prompts',
CREATE_PROMPT: '/api/create_prompt',

View File

@@ -41,8 +41,8 @@ const userService = {
apiClient.get(endpoints.USER.SHARED_AGENTS, token),
shareAgent: (data: any, token: string | null): Promise<any> =>
apiClient.put(endpoints.USER.SHARE_AGENT, data, token),
hideSharedAgent: (id: string, token: string | null): Promise<any> =>
apiClient.delete(endpoints.USER.HIDE_SHARED_AGENT(id), token),
removeSharedAgent: (id: string, token: string | null): Promise<any> =>
apiClient.delete(endpoints.USER.REMOVE_SHARED_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> =>