diff --git a/src/khoj/database/adapters/__init__.py b/src/khoj/database/adapters/__init__.py index 31ccd2f2..0390ce23 100644 --- a/src/khoj/database/adapters/__init__.py +++ b/src/khoj/database/adapters/__init__.py @@ -69,6 +69,7 @@ from khoj.search_filter.word_filter import WordFilter from khoj.utils import state from khoj.utils.config import OfflineChatProcessorModel from khoj.utils.helpers import ( + generate_random_internal_agent_name, generate_random_name, in_debug_mode, is_none_or_empty, @@ -806,13 +807,15 @@ class AgentAdapters: privacy_level: str, icon: str, color: str, - chat_model: str, + chat_model: Optional[str], files: List[str], input_tools: List[str], output_modes: List[str], slug: Optional[str] = None, is_hidden: Optional[bool] = False, ): + if not chat_model: + chat_model = ConversationAdapters.get_default_chat_model(user) chat_model_option = await ChatModel.objects.filter(name=chat_model).afirst() # Slug will be None for new agents, which will trigger a new agent creation with a generated, immutable slug @@ -864,6 +867,35 @@ class AgentAdapters: return agent + @staticmethod + @arequire_valid_user + async def aupdate_hidden_agent( + user: KhojUser, + slug: Optional[str] = None, + persona: Optional[str] = None, + chat_model: Optional[str] = None, + input_tools: Optional[List[str]] = None, + output_modes: Optional[List[str]] = None, + ): + random_name = generate_random_internal_agent_name() + + agent = await AgentAdapters.aupdate_agent( + user=user, + name=random_name, + personality=persona, + privacy_level=Agent.PrivacyLevel.PRIVATE, + icon=Agent.StyleIconTypes.LIGHTBULB, + color=Agent.StyleColorTypes.BLUE, + chat_model=chat_model, + files=[], + input_tools=input_tools, + output_modes=output_modes, + slug=slug, + is_hidden=True, + ) + + return agent + class PublicConversationAdapters: @staticmethod diff --git a/src/khoj/database/models/__init__.py b/src/khoj/database/models/__init__.py index 97b24510..b88ae3f3 100644 --- a/src/khoj/database/models/__init__.py +++ b/src/khoj/database/models/__init__.py @@ -285,7 +285,6 @@ class Agent(DbBaseModel): class OutputModeOptions(models.TextChoices): # These map to various ConversationCommand types - TEXT = "text" IMAGE = "image" AUTOMATION = "automation" DIAGRAM = "diagram" diff --git a/src/khoj/routers/api_agents.py b/src/khoj/routers/api_agents.py index 87693f56..432117e1 100644 --- a/src/khoj/routers/api_agents.py +++ b/src/khoj/routers/api_agents.py @@ -9,7 +9,7 @@ from fastapi import APIRouter, Request from fastapi.requests import Request from fastapi.responses import Response from pydantic import BaseModel -from starlette.authentication import requires +from starlette.authentication import has_required_scope, requires from khoj.database.adapters import AgentAdapters, ConversationAdapters from khoj.database.models import Agent, Conversation, KhojUser @@ -41,6 +41,14 @@ class ModifyAgentBody(BaseModel): is_hidden: Optional[bool] = False +class ModifyHiddenAgentBody(BaseModel): + slug: str + persona: Optional[str] = None + chat_model: Optional[str] = None + input_tools: Optional[List[str]] = [] + output_modes: Optional[List[str]] = [] + + @api_agents.get("", response_class=Response) async def all_agents( request: Request, @@ -183,6 +191,93 @@ async def delete_agent( return Response(content=json.dumps({"message": "Agent deleted."}), media_type="application/json", status_code=200) +@api_agents.patch("/hidden", response_class=Response) +@requires(["authenticated"]) +async def update_hidden_agent( + request: Request, + common: CommonQueryParams, + body: ModifyHiddenAgentBody, +) -> Response: + user: KhojUser = request.user.object + + subscribed = has_required_scope(request, ["premium"]) + chat_model = body.chat_model if subscribed else None + + selected_agent = await AgentAdapters.aget_agent_by_slug(body.slug, user) + + if not selected_agent: + return Response( + content=json.dumps({"error": f"Agent with name {body.slug} not found."}), + media_type="application/json", + status_code=404, + ) + + agent = await AgentAdapters.aupdate_hidden_agent( + user, + body.slug, + body.persona, + chat_model, + body.input_tools, + body.output_modes, + ) + + agents_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.name, + "files": body.files, + "input_tools": agent.input_tools, + "output_modes": agent.output_modes, + } + + return Response(content=json.dumps(agents_packet), media_type="application/json", status_code=200) + + +@api_agents.post("/hidden", response_class=Response) +@requires(["authenticated"]) +async def create_hidden_agent( + request: Request, + common: CommonQueryParams, + body: ModifyHiddenAgentBody, +) -> Response: + user: KhojUser = request.user.object + + subscribed = has_required_scope(request, ["premium"]) + chat_model = body.chat_model if subscribed else None + + agent = await AgentAdapters.aupdate_hidden_agent( + user, + body.slug, + body.persona, + chat_model, + body.input_tools, + body.output_modes, + ) + + agents_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.name, + "files": body.files, + "input_tools": agent.input_tools, + "output_modes": agent.output_modes, + } + + return Response(content=json.dumps(agents_packet), media_type="application/json", status_code=200) + + @api_agents.post("", response_class=Response) @requires(["authenticated"]) async def create_agent( @@ -203,6 +298,9 @@ async def create_agent( status_code=400, ) + subscribed = has_required_scope(request, ["premium"]) + chat_model = body.chat_model if subscribed else None + agent = await AgentAdapters.aupdate_agent( user, body.name, @@ -210,7 +308,7 @@ async def create_agent( body.privacy_level, body.icon, body.color, - body.chat_model, + chat_model, body.files, body.input_tools, body.output_modes, @@ -266,6 +364,9 @@ async def update_agent( status_code=404, ) + subscribed = has_required_scope(request, ["premium"]) + chat_model = body.chat_model if subscribed else None + agent = await AgentAdapters.aupdate_agent( user, body.name, @@ -273,7 +374,7 @@ async def update_agent( body.privacy_level, body.icon, body.color, - body.chat_model, + chat_model, body.files, body.input_tools, body.output_modes, diff --git a/src/khoj/routers/api_chat.py b/src/khoj/routers/api_chat.py index 8d9bd7cc..99f585d0 100644 --- a/src/khoj/routers/api_chat.py +++ b/src/khoj/routers/api_chat.py @@ -223,13 +223,17 @@ def chat_history( if conversation.agent.privacy_level == Agent.PrivacyLevel.PRIVATE and conversation.agent.creator != user: conversation.agent = None else: + agent_has_files = EntryAdapters.agent_has_entries(conversation.agent) agent_metadata = { "slug": conversation.agent.slug, "name": conversation.agent.name, - "isCreator": conversation.agent.creator == user, + "is_creator": conversation.agent.creator == user, "color": conversation.agent.style_color, "icon": conversation.agent.style_icon, "persona": conversation.agent.personality, + "is_hidden": conversation.agent.is_hidden, + "chat_model": conversation.agent.chat_model.name, + "has_files": agent_has_files, } meta_log = conversation.conversation_log @@ -282,13 +286,17 @@ def get_shared_chat( if conversation.agent.privacy_level == Agent.PrivacyLevel.PRIVATE: conversation.agent = None else: + agent_has_files = EntryAdapters.agent_has_entries(conversation.agent) agent_metadata = { "slug": conversation.agent.slug, "name": conversation.agent.name, - "isCreator": conversation.agent.creator == user, + "is_creator": conversation.agent.creator == user, "color": conversation.agent.style_color, "icon": conversation.agent.style_icon, "persona": conversation.agent.personality, + "is_hidden": conversation.agent.is_hidden, + "chat_model": conversation.agent.chat_model.name, + "has_files": agent_has_files, } meta_log = conversation.conversation_log