From 25df81993cb9403e4dc2668197e9fa1f9631f738 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Sun, 20 Oct 2024 10:52:14 -0700 Subject: [PATCH] Show agents sorted by mru, Select mru agent by default on web app Have get agents API return agents ordered intelligently - Put the default agent first - Sort used agents by most recently chatted with agent for ease of access - Randomly shuffle the remaining unused agents for discoverability --- src/interface/web/app/page.tsx | 19 +++-------- src/khoj/routers/api_agents.py | 60 ++++++++++++++++++++++------------ 2 files changed, 44 insertions(+), 35 deletions(-) diff --git a/src/interface/web/app/page.tsx b/src/interface/web/app/page.tsx index 45b91ad2..3976a6b6 100644 --- a/src/interface/web/app/page.tsx +++ b/src/interface/web/app/page.tsx @@ -109,22 +109,13 @@ function ChatBodyData(props: ChatBodyDataProps) { }, [props.chatOptionsData]); useEffect(() => { - const nSlice = props.isMobileWidth ? 2 : 4; - const shuffledAgents = agentsData ? [...agentsData].sort(() => 0.5 - Math.random()) : []; - const agents = agentsData ? [agentsData[0]] : []; // Always add the first/default agent. - - shuffledAgents.forEach((agent) => { - if (!agents.find((a) => a.slug === agent.slug)) { - agents.push(agent); - } - }); - + const agents = (agentsData || []).filter((agent) => agent !== null && agent !== undefined); setAgents(agents); + // set the selected agent to the most recently used agent, first agent is always khoj + setSelectedAgent(agents.length > 1 ? agents[1].slug : "khoj"); - //generate colored icons for the selected agents - const agentIcons = agents - .filter((agent) => agent !== null && agent !== undefined) - .map((agent) => getIconFromIconName(agent.icon, agent.color)!); + // generate colored icons for the available agents + const agentIcons = agents.map((agent) => getIconFromIconName(agent.icon, agent.color)!); setAgentIcons(agentIcons); }, [agentsData, props.isMobileWidth]); diff --git a/src/khoj/routers/api_agents.py b/src/khoj/routers/api_agents.py index 9aaf6d05..b47bb039 100644 --- a/src/khoj/routers/api_agents.py +++ b/src/khoj/routers/api_agents.py @@ -1,5 +1,7 @@ import json import logging +import random +from datetime import datetime, timezone from typing import Dict, List, Optional from asgiref.sync import sync_to_async @@ -9,8 +11,8 @@ from fastapi.responses import Response from pydantic import BaseModel from starlette.authentication import requires -from khoj.database.adapters import AgentAdapters -from khoj.database.models import Agent, KhojUser +from khoj.database.adapters import AgentAdapters, ConversationAdapters +from khoj.database.models import Agent, Conversation, KhojUser from khoj.routers.helpers import CommonQueryParams, acheck_if_safe_prompt from khoj.utils.helpers import ( ConversationCommand, @@ -45,30 +47,46 @@ async def all_agents( ) -> Response: user: KhojUser = request.user.object if request.user.is_authenticated else None agents = await AgentAdapters.aget_all_accessible_agents(user) + default_agent = await AgentAdapters.aget_default_agent() + default_agent_packet = None agents_packet = list() for agent in agents: files = agent.fileobject_set.all() file_names = [file.file_name for file in files] - agents_packet.append( - { - "slug": agent.slug, - "name": agent.name, - "persona": agent.personality, - "creator": agent.creator.username if agent.creator else None, - "managed_by_admin": agent.managed_by_admin, - "color": agent.style_color, - "icon": agent.style_icon, - "privacy_level": agent.privacy_level, - "chat_model": agent.chat_model.chat_model, - "files": file_names, - "input_tools": agent.input_tools, - "output_modes": agent.output_modes, - } - ) + agent_packet = { + "slug": agent.slug, + "name": agent.name, + "persona": agent.personality, + "creator": agent.creator.username if agent.creator else None, + "managed_by_admin": agent.managed_by_admin, + "color": agent.style_color, + "icon": agent.style_icon, + "privacy_level": agent.privacy_level, + "chat_model": agent.chat_model.chat_model, + "files": file_names, + "input_tools": agent.input_tools, + "output_modes": agent.output_modes, + } + if agent.slug == default_agent.slug: + default_agent_packet = agent_packet + else: + agents_packet.append(agent_packet) + + # Load Conversation Sessions + min_date = datetime.min.replace(tzinfo=timezone.utc) + conversations = [] + if user: + conversations = await sync_to_async(list[Conversation])( + ConversationAdapters.get_conversation_sessions(user, request.user.client_app).reverse() + ) + conversation_times = {conv.agent.slug: conv.updated_at for conv in conversations} + + # Put default agent first, then sort by mru and finally shuffle unused randomly + random.shuffle(agents_packet) + agents_packet.sort(key=lambda x: conversation_times.get(x["slug"]) or min_date, reverse=True) + if default_agent_packet: + agents_packet.insert(0, default_agent_packet) - # Make sure that the agent named 'khoj' is first in the list. Everything else is sorted by name. - agents_packet.sort(key=lambda x: x["name"]) - agents_packet.sort(key=lambda x: x["slug"] == "khoj", reverse=True) return Response(content=json.dumps(agents_packet), media_type="application/json", status_code=200)