mirror of
https://github.com/khoj-ai/khoj.git
synced 2024-11-27 17:35:07 +01:00
Add web UI views for agents
- Add a page to view all agents - Add slugs to manage agents - Add a view to view single agent - Display active agent when in chat window - Fix post-login redirect issue
This commit is contained in:
parent
6ab649312f
commit
290712c3fe
16 changed files with 1003 additions and 61 deletions
|
@ -268,6 +268,7 @@ def initialize_content(regenerate: bool, search_type: Optional[SearchType] = Non
|
|||
def configure_routes(app):
|
||||
# Import APIs here to setup search types before while configuring server
|
||||
from khoj.routers.api import api
|
||||
from khoj.routers.api_agents import api_agents
|
||||
from khoj.routers.api_chat import api_chat
|
||||
from khoj.routers.api_config import api_config
|
||||
from khoj.routers.indexer import indexer
|
||||
|
@ -275,6 +276,7 @@ def configure_routes(app):
|
|||
|
||||
app.include_router(api, prefix="/api")
|
||||
app.include_router(api_chat, prefix="/api/chat")
|
||||
app.include_router(api_agents, prefix="/api/agents")
|
||||
app.include_router(api_config, prefix="/api/config")
|
||||
app.include_router(indexer, prefix="/api/v1/index")
|
||||
app.include_router(web_client)
|
||||
|
|
|
@ -402,8 +402,29 @@ class AgentAdapters:
|
|||
return await Agent.objects.filter(id=agent_id).afirst()
|
||||
|
||||
@staticmethod
|
||||
def get_all_acessible_agents(user: KhojUser = None):
|
||||
return Agent.objects.filter(Q(public=True) | Q(creator=user)).distinct()
|
||||
async def aget_agent_by_slug(agent_slug: str):
|
||||
return await Agent.objects.filter(slug__iexact=agent_slug.lower()).afirst()
|
||||
|
||||
@staticmethod
|
||||
def get_agent_by_slug(slug: str, user: KhojUser = None):
|
||||
agent = Agent.objects.filter(slug=slug).first()
|
||||
# Check if agent is public or created by the user
|
||||
if agent and (agent.public or agent.creator == user):
|
||||
return agent
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_all_accessible_agents(user: KhojUser = None):
|
||||
return Agent.objects.filter(Q(public=True) | Q(creator=user)).distinct().order_by("created_at")
|
||||
|
||||
@staticmethod
|
||||
async def aget_all_accessible_agents(user: KhojUser = None) -> List[Agent]:
|
||||
get_all_accessible_agents = sync_to_async(
|
||||
lambda: Agent.objects.filter(Q(public=True) | Q(creator=user)).distinct().order_by("created_at").all(),
|
||||
thread_sensitive=True,
|
||||
)
|
||||
agents = await get_all_accessible_agents()
|
||||
return await sync_to_async(list)(agents)
|
||||
|
||||
@staticmethod
|
||||
def get_conversation_agent_by_id(agent_id: int):
|
||||
|
@ -419,12 +440,16 @@ class AgentAdapters:
|
|||
|
||||
@staticmethod
|
||||
def create_default_agent():
|
||||
# First delete the existing default
|
||||
Agent.objects.filter(name=AgentAdapters.DEFAULT_AGENT_NAME).delete()
|
||||
|
||||
default_conversation_config = ConversationAdapters.get_default_conversation_config()
|
||||
default_personality = prompts.personality.format(current_date="placeholder")
|
||||
|
||||
if Agent.objects.filter(name=AgentAdapters.DEFAULT_AGENT_NAME).exists():
|
||||
agent = Agent.objects.filter(name=AgentAdapters.DEFAULT_AGENT_NAME).first()
|
||||
agent.tuning = default_personality
|
||||
agent.chat_model = default_conversation_config
|
||||
agent.save()
|
||||
return agent
|
||||
|
||||
# The default agent is public and managed by the admin. It's handled a little differently than other agents.
|
||||
return Agent.objects.create(
|
||||
name=AgentAdapters.DEFAULT_AGENT_NAME,
|
||||
|
@ -482,10 +507,12 @@ class ConversationAdapters:
|
|||
|
||||
@staticmethod
|
||||
async def acreate_conversation_session(
|
||||
user: KhojUser, client_application: ClientApplication = None, agent_id: int = None
|
||||
user: KhojUser, client_application: ClientApplication = None, agent_slug: str = None
|
||||
):
|
||||
if agent_id:
|
||||
agent = await AgentAdapters.aget_agent_by_id(id)
|
||||
if agent_slug:
|
||||
agent = await AgentAdapters.aget_agent_by_slug(agent_slug)
|
||||
if agent is None:
|
||||
raise HTTPException(status_code=400, detail="Invalid agent id")
|
||||
return await Conversation.objects.acreate(user=user, client=client_application, agent=agent)
|
||||
return await Conversation.objects.acreate(user=user, client=client_application)
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 4.2.10 on 2024-03-11 05:12
|
||||
# Generated by Django 4.2.10 on 2024-03-13 07:38
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
|
@ -23,6 +23,7 @@ class Migration(migrations.Migration):
|
|||
("tools", models.JSONField(default=list)),
|
||||
("public", models.BooleanField(default=False)),
|
||||
("managed_by_admin", models.BooleanField(default=False)),
|
||||
("slug", models.CharField(blank=True, default=None, max_length=200, null=True)),
|
||||
(
|
||||
"chat_model",
|
||||
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="database.chatmodeloptions"),
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import uuid
|
||||
from random import choice
|
||||
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.core.exceptions import ValidationError
|
||||
|
@ -92,13 +93,25 @@ class Agent(BaseModel):
|
|||
public = models.BooleanField(default=False)
|
||||
managed_by_admin = models.BooleanField(default=False)
|
||||
chat_model = models.ForeignKey(ChatModelOptions, on_delete=models.CASCADE)
|
||||
slug = models.CharField(max_length=200, default=None, null=True, blank=True)
|
||||
|
||||
|
||||
@receiver(pre_save, sender=Agent)
|
||||
def check_public_name(sender, instance, **kwargs):
|
||||
if instance.public:
|
||||
def verify_agent(sender, instance, **kwargs):
|
||||
# check if this is a new instance
|
||||
if instance._state.adding:
|
||||
if Agent.objects.filter(name=instance.name, public=True).exists():
|
||||
raise ValidationError(f"A public Agent with the name {instance.name} already exists.")
|
||||
if Agent.objects.filter(name=instance.name, creator=instance.creator).exists():
|
||||
raise ValidationError(f"A private Agent with the name {instance.name} already exists.")
|
||||
|
||||
slug = instance.name.lower().replace(" ", "-")
|
||||
observed_random_numbers = set()
|
||||
while Agent.objects.filter(slug=slug).exists():
|
||||
random_number = choice([i for i in range(0, 10000) if i not in observed_random_numbers])
|
||||
observed_random_numbers.add(random_number)
|
||||
slug = f"{slug}-{random_number}"
|
||||
instance.slug = slug
|
||||
|
||||
|
||||
class NotionConfig(BaseModel):
|
||||
|
|
286
src/khoj/interface/web/agent.html
Normal file
286
src/khoj/interface/web/agent.html
Normal file
|
@ -0,0 +1,286 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0">
|
||||
<title>Khoj - Agents</title>
|
||||
|
||||
<link rel="icon" type="image/png" sizes="128x128" href="/static/assets/icons/favicon-128x128.png?v={{ khoj_version }}">
|
||||
<link rel="manifest" href="/static/khoj.webmanifest?v={{ khoj_version }}">
|
||||
<link rel="stylesheet" href="/static/assets/khoj.css?v={{ khoj_version }}">
|
||||
</head>
|
||||
<script type="text/javascript" src="/static/assets/utils.js?v={{ khoj_version }}"></script>
|
||||
<body>
|
||||
<!--Add Header Logo and Nav Pane-->
|
||||
{% import 'utils.html' as utils %}
|
||||
{{ utils.heading_pane(user_photo, username, is_active, has_documents) }}
|
||||
<div id="agent-metadata-wrapper">
|
||||
<div id="agent-metadata">
|
||||
<div id="agent-avatar-wrapper">
|
||||
<div id="agent-settings-header">Agent Settings</div>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div id="agent-data-wrapper">
|
||||
<div id="agent-avatar-wrapper">
|
||||
<img id="agent-avatar" src="{{ agent.avatar }}" alt="Agent Avatar">
|
||||
<input type="text" id="agent-name-input" value="{{ agent.name }}" {% if agent.creator_not_self %} disabled {% endif %}>
|
||||
</div>
|
||||
<div id="agent-instructions">Instructions</div>
|
||||
<div id="agent-tuning">
|
||||
<p>{{ agent.tuning }}</p>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div id="agent-public">
|
||||
<p>Public</p>
|
||||
<label class="switch">
|
||||
<input type="checkbox" {% if agent.public %} checked {% endif %} {% if agent.creator_not_self %} disabled {% endif %}>
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
</div>
|
||||
<p id="agent-creator" style="display: none;">Creator: {{ agent.creator }}</p>
|
||||
<p id="agent-managed-by-admin" style="display: none;">ⓘ This agent is managed by the administrator</p>
|
||||
<button onclick="openChat('{{ agent.slug }}')">Chat</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="footer">
|
||||
<a href="/agents">All Agents</a>
|
||||
</div>
|
||||
</body>
|
||||
<style>
|
||||
body {
|
||||
background-color: var(--background-color);
|
||||
display: grid;
|
||||
color: var(--main-text-color);
|
||||
text-align: center;
|
||||
font-family: var(--font-family);
|
||||
font-size: medium;
|
||||
font-weight: 300;
|
||||
line-height: 1.5em;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div#agent-settings-header {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
div.divider {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 2px solid var(--main-text-color);
|
||||
}
|
||||
|
||||
div#footer {
|
||||
width: auto;
|
||||
padding: 10px;
|
||||
background-color: var(--background-color);
|
||||
border-top: 1px solid var(--main-text-color);
|
||||
text-align: left;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
div#footer a {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
div#agent-data-wrapper button {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background-color: var(--primary);
|
||||
font: inherit;
|
||||
color: var(--main-text-color);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
div#agent-data-wrapper button:hover {
|
||||
background-color: var(--primary-hover);
|
||||
box-shadow: 0 0 10px var(--primary-hover);
|
||||
}
|
||||
|
||||
input#agent-name-input {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
background-color: #EEEEEE;
|
||||
color: var(--main-text-color);
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
div#agent-instructions {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#agent-metadata {
|
||||
padding: 10px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
text-align: left;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#agent-avatar-wrapper {
|
||||
margin-right: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#agent-avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#agent-name {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
#agent-tuning, #agent-public, #agent-creator, #agent-managed-by-admin {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
#agent-tuning p {
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
#agent-metadata p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#agent-public {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-gap: 12px;
|
||||
}
|
||||
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ccc;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
-webkit-transition: .4s;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: var(--primary-hover);
|
||||
}
|
||||
|
||||
input:focus + .slider {
|
||||
box-shadow: 0 0 1px var(--primary-hover);
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
-webkit-transform: translateX(26px);
|
||||
-ms-transform: translateX(26px);
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
div#agent-data-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-gap: 10px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* Rounded sliders */
|
||||
.slider.round {
|
||||
border-radius: 34px;
|
||||
}
|
||||
|
||||
.slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 700px) {
|
||||
body {
|
||||
grid-template-columns: auto min(70vw, 100%) auto;
|
||||
grid-template-rows: auto auto minmax(80px, 100%) auto;
|
||||
}
|
||||
body > * {
|
||||
grid-column: 2;
|
||||
}
|
||||
#agent-metadata-wrapper {
|
||||
display: block;
|
||||
width: min(30vw, 100%);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
async function openChat(agentId) {
|
||||
let response = await fetch(`/api/chat/sessions?agent_slug=${agentId}`, { method: "POST" });
|
||||
let data = await response.json();
|
||||
if (response.status == 200) {
|
||||
window.location.href = "/";
|
||||
} else {
|
||||
alert("Failed to start chat session");
|
||||
}
|
||||
}
|
||||
|
||||
// Show the agent-managed-by-admin paragraph if the agent is managed by the admin
|
||||
// compare agent.managed_by_admin as a lowercase string to "true"
|
||||
let isManagedByAdmin = "{{ agent.managed_by_admin }}".toLowerCase() === "true";
|
||||
if (isManagedByAdmin) {
|
||||
document.getElementById("agent-managed-by-admin").style.display = "block";
|
||||
} else {
|
||||
document.getElementById("agent-creator").style.display = "block";
|
||||
}
|
||||
|
||||
// Resize the input field based on the length of the value
|
||||
let input = document.getElementById("agent-name-input");
|
||||
input.addEventListener("input", resizeInput);
|
||||
resizeInput.call(input);
|
||||
function resizeInput() {
|
||||
this.style.width = this.value.length + 1 + "ch";
|
||||
}
|
||||
</script>
|
||||
</html>
|
201
src/khoj/interface/web/agents.html
Normal file
201
src/khoj/interface/web/agents.html
Normal file
|
@ -0,0 +1,201 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0">
|
||||
<title>Khoj - Agents</title>
|
||||
|
||||
<link rel="icon" type="image/png" sizes="128x128" href="/static/assets/icons/favicon-128x128.png?v={{ khoj_version }}">
|
||||
<link rel="manifest" href="/static/khoj.webmanifest?v={{ khoj_version }}">
|
||||
<link rel="stylesheet" href="/static/assets/khoj.css?v={{ khoj_version }}">
|
||||
</head>
|
||||
<script type="text/javascript" src="/static/assets/utils.js?v={{ khoj_version }}"></script>
|
||||
<body>
|
||||
<!--Add Header Logo and Nav Pane-->
|
||||
{% import 'utils.html' as utils %}
|
||||
{{ utils.heading_pane(user_photo, username, is_active, has_documents) }}
|
||||
<!-- {{ agents }} -->
|
||||
|
||||
<div id="agents-list">
|
||||
<div id="agents">
|
||||
<div id="agents-header">
|
||||
<h1 id="agents-list-title">Agents</h1>
|
||||
<!-- <div id="create-agent">
|
||||
<a href="/agents/create"><svg class="new-convo-button" viewBox="0 0 35 35" fill="#000000" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16 0c-8.836 0-16 7.163-16 16s7.163 16 16 16c8.837 0 16-7.163 16-16s-7.163-16-16-16zM16 30.032c-7.72 0-14-6.312-14-14.032s6.28-14 14-14 14 6.28 14 14-6.28 14.032-14 14.032zM23 15h-6v-6c0-0.552-0.448-1-1-1s-1 0.448-1 1v6h-6c-0.552 0-1 0.448-1 1s0.448 1 1 1h6v6c0 0.552 0.448 1 1 1s1-0.448 1-1v-6h6c0.552 0 1-0.448 1-1s-0.448-1-1-1z"></path>
|
||||
</svg></a>
|
||||
</div> -->
|
||||
</div>
|
||||
{% for agent in agents %}
|
||||
<div class="agent">
|
||||
<a href="/agent/{{ agent.slug }}">
|
||||
<div class="agent-avatar">
|
||||
<img src="{{ agent.avatar }}" alt="{{ agent.name }}">
|
||||
</div>
|
||||
</a>
|
||||
<div class="agent-info">
|
||||
<a href="/agent/{{ agent.slug }}">
|
||||
<h2>{{ agent.name }}</h2>
|
||||
</a>
|
||||
<p>{{ agent.tuning }}</p>
|
||||
</div>
|
||||
<div class="agent-info">
|
||||
<button onclick="openChat('{{ agent.slug }}')">Talk</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div id="footer">
|
||||
<a href="/">Back to Chat</a>
|
||||
</div>
|
||||
</body>
|
||||
<style>
|
||||
body {
|
||||
background-color: var(--background-color);
|
||||
display: grid;
|
||||
color: var(--main-text-color);
|
||||
text-align: center;
|
||||
font-family: var(--font-family);
|
||||
font-size: medium;
|
||||
font-weight: 300;
|
||||
line-height: 1.5em;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
grid-template-rows: auto minmax(80px, 100%) auto;
|
||||
}
|
||||
|
||||
h1#agents-list-title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.agent-info p {
|
||||
height: 50px; /* Adjust this value as needed */
|
||||
overflow: auto;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
div.agent-info {
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
div.agent-info a,
|
||||
div.agent-info h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.agent img {
|
||||
width: 50px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
div.agent a {
|
||||
text-decoration: none;
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
|
||||
div#agents-header {
|
||||
display: grid;
|
||||
grid-template-columns: auto;
|
||||
}
|
||||
|
||||
div#agents-header a,
|
||||
div.agent-info button {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background-color: var(--primary);
|
||||
font: inherit;
|
||||
color: var(--main-text-color);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
div#agents-header a:hover,
|
||||
div.agent-info button:hover {
|
||||
background-color: var(--primary-hover);
|
||||
box-shadow: 0 0 10px var(--primary-hover);
|
||||
}
|
||||
|
||||
|
||||
div#footer {
|
||||
width: auto;
|
||||
padding: 10px;
|
||||
background-color: var(--background-color);
|
||||
border-top: 1px solid var(--main-text-color);
|
||||
text-align: left;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
div#footer a {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
div.agent {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
background-color: var(--frosted-background-color);
|
||||
border-top: 1px solid var(--main-text-color);
|
||||
}
|
||||
|
||||
div.agent-info {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div#agents {
|
||||
display: grid;
|
||||
grid-auto-flow: row;
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
background-color: var(--frosted-background-color);
|
||||
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||
border-radius: 8px;
|
||||
width: 50%;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
svg.new-convo-button {
|
||||
width: 20px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 700px) {
|
||||
body {
|
||||
grid-template-columns: auto min(70vw, 100%) auto;
|
||||
}
|
||||
body > * {
|
||||
grid-column: 2;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 700px) {
|
||||
div#agents {
|
||||
width: 90%;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
async function openChat(agentId) {
|
||||
let response = await fetch(`/api/chat/sessions?agent_slug=${agentId}`, { method: "POST" });
|
||||
let data = await response.json();
|
||||
if (response.status == 200) {
|
||||
window.location.href = "/";
|
||||
} else if(response.status == 403 || response.status == 401) {
|
||||
window.location.href = "/login?next=/agent/" + agentId;
|
||||
} else {
|
||||
alert("Failed to start chat session");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</html>
|
|
@ -130,7 +130,7 @@ img.khoj-logo {
|
|||
background-color: var(--background-color);
|
||||
min-width: 160px;
|
||||
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||
right: 15vw;
|
||||
right: 5vw;
|
||||
top: 64px;
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
|
|
|
@ -162,7 +162,7 @@
|
|||
height: 40px;
|
||||
}
|
||||
.card-title {
|
||||
font-size: 20px;
|
||||
font-size: medium;
|
||||
font-weight: normal;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
|
|
@ -12,15 +12,16 @@
|
|||
<script type="text/javascript" src="/static/assets/markdown-it.min.js?v={{ khoj_version }}"></script>
|
||||
<script>
|
||||
let welcome_message = `
|
||||
Hi, I am Khoj, your open, personal AI 👋🏽. I can help:
|
||||
Hi, I am Khoj, your open, personal AI 👋🏽. I can:
|
||||
- 🧠 Answer general knowledge questions
|
||||
- 💡 Be a sounding board for your ideas
|
||||
- 📜 Chat with your notes & documents
|
||||
- 🌄 Generate images based on your messages
|
||||
- 🔎 Search the web for answers to your questions
|
||||
- 🎙️ Listen to your audio messages (use the mic by the input box to speak your message)
|
||||
- 📚 Understand files you drag & drop here
|
||||
|
||||
Get the Khoj [Desktop](https://khoj.dev/downloads), [Obsidian](https://docs.khoj.dev/clients/obsidian#setup), [Emacs](https://docs.khoj.dev/clients/emacs#setup) apps to search, chat with your 🖥️ computer docs.
|
||||
Get the Khoj [Desktop](https://khoj.dev/downloads), [Obsidian](https://docs.khoj.dev/clients/obsidian#setup), [Emacs](https://docs.khoj.dev/clients/emacs#setup) apps to search, chat with your 🖥️ computer docs. You can manage all the files you've shared with me at any time by going to [your settings](/config/content-source/computer/).
|
||||
|
||||
To get started, just start typing below. You can also type / to see a list of commands.
|
||||
`.trim()
|
||||
|
@ -115,7 +116,6 @@ To get started, just start typing below. You can also type / to see a list of co
|
|||
linkElement.setAttribute('href', link);
|
||||
linkElement.setAttribute('target', '_blank');
|
||||
linkElement.setAttribute('rel', 'noopener noreferrer');
|
||||
linkElement.classList.add("inline-chat-link");
|
||||
linkElement.classList.add("reference-link");
|
||||
linkElement.setAttribute('title', title);
|
||||
linkElement.textContent = title;
|
||||
|
@ -827,6 +827,33 @@ To get started, just start typing below. You can also type / to see a list of co
|
|||
chatBody.dataset.conversationId = response.conversation_id;
|
||||
chatBody.dataset.conversationTitle = response.slug || `New conversation 🌱`;
|
||||
|
||||
let agentMetadata = response.agent;
|
||||
if (agentMetadata) {
|
||||
let agentName = agentMetadata.name;
|
||||
let agentAvatar = agentMetadata.avatar;
|
||||
let agentOwnedByUser = agentMetadata.isCreator;
|
||||
|
||||
let agentAvatarElement = document.getElementById("agent-avatar");
|
||||
let agentNameElement = document.getElementById("agent-name");
|
||||
|
||||
let agentLinkElement = document.getElementById("agent-link");
|
||||
|
||||
agentAvatarElement.src = agentAvatar;
|
||||
agentNameElement.textContent = agentName;
|
||||
agentLinkElement.setAttribute("href", `/agent/${agentMetadata.slug}`);
|
||||
|
||||
if (agentOwnedByUser) {
|
||||
let agentOwnedByUserElement = document.getElementById("agent-owned-by-user");
|
||||
agentOwnedByUserElement.style.display = "block";
|
||||
}
|
||||
|
||||
let agentMetadataElement = document.getElementById("agent-metadata");
|
||||
agentMetadataElement.style.display = "block";
|
||||
} else {
|
||||
let agentMetadataElement = document.getElementById("agent-metadata");
|
||||
agentMetadataElement.style.display = "none";
|
||||
}
|
||||
|
||||
let chatBodyWrapper = document.getElementById("chat-body-wrapper");
|
||||
const fullChatLog = response.chat || [];
|
||||
|
||||
|
@ -919,12 +946,100 @@ To get started, just start typing below. You can also type / to see a list of co
|
|||
}
|
||||
|
||||
function createNewConversation() {
|
||||
let chatBody = document.getElementById("chat-body");
|
||||
chatBody.innerHTML = "";
|
||||
flashStatusInChatInput("📝 New conversation started");
|
||||
chatBody.dataset.conversationId = "";
|
||||
chatBody.dataset.conversationTitle = "";
|
||||
renderMessage(welcome_message, "khoj");
|
||||
// Create a modal that appears in the middle of the entire screen. It should have a form to create a new conversation.
|
||||
let modal = document.createElement('div');
|
||||
modal.classList.add("modal");
|
||||
modal.id = "new-conversation-modal";
|
||||
let modalContent = document.createElement('div');
|
||||
modalContent.classList.add("modal-content");
|
||||
let modalHeader = document.createElement('div');
|
||||
modalHeader.classList.add("modal-header");
|
||||
let modalTitle = document.createElement('h2');
|
||||
modalTitle.textContent = "New Conversation";
|
||||
let modalCloseButton = document.createElement('button');
|
||||
modalCloseButton.classList.add("modal-close-button");
|
||||
modalCloseButton.innerHTML = "×";
|
||||
modalCloseButton.addEventListener('click', function() {
|
||||
modal.remove();
|
||||
});
|
||||
modalHeader.appendChild(modalTitle);
|
||||
modalHeader.appendChild(modalCloseButton);
|
||||
modalContent.appendChild(modalHeader);
|
||||
let modalBody = document.createElement('div');
|
||||
modalBody.classList.add("modal-body");
|
||||
|
||||
let agentDropDownPicker = document.createElement('select');
|
||||
agentDropDownPicker.setAttribute("id", "agent-dropdown-picker");
|
||||
agentDropDownPicker.setAttribute("name", "agent-dropdown-picker");
|
||||
|
||||
let agentDropDownLabel = document.createElement('label');
|
||||
agentDropDownLabel.setAttribute("for", "agent-dropdown-picker");
|
||||
agentDropDownLabel.textContent = "Who do you want to talk to?";
|
||||
|
||||
fetch('/api/agents')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.length > 0) {
|
||||
data.forEach((agent) => {
|
||||
let agentOption = document.createElement('option');
|
||||
agentOption.setAttribute("value", agent.slug);
|
||||
agentOption.textContent = agent.name;
|
||||
agentDropDownPicker.appendChild(agentOption);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
return;
|
||||
});
|
||||
|
||||
let seeAllAgentsLink = document.createElement('a');
|
||||
seeAllAgentsLink.setAttribute("href", "/agents");
|
||||
seeAllAgentsLink.setAttribute("target", "_blank");
|
||||
seeAllAgentsLink.textContent = "See all agents";
|
||||
|
||||
let newConversationSubmitButton = document.createElement('button');
|
||||
newConversationSubmitButton.setAttribute("type", "submit");
|
||||
newConversationSubmitButton.textContent = "Go";
|
||||
newConversationSubmitButton.id = "new-conversation-submit-button";
|
||||
|
||||
newConversationSubmitButton.addEventListener('click', function(event) {
|
||||
event.preventDefault();
|
||||
let agentSlug = agentDropDownPicker.value;
|
||||
let createURL = `/api/chat/sessions?client=web&agent_slug=${agentSlug}`;
|
||||
let chatBody = document.getElementById("chat-body");
|
||||
fetch(createURL, { method: "POST" })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
chatBody.dataset.conversationId = data.conversation_id;
|
||||
modal.remove();
|
||||
loadChat();
|
||||
})
|
||||
.catch(err => {
|
||||
return;
|
||||
});
|
||||
});
|
||||
|
||||
let closeButton = document.createElement('button');
|
||||
closeButton.id = "close-button";
|
||||
closeButton.innerHTML = "Close";
|
||||
closeButton.classList.add("close-button");
|
||||
closeButton.addEventListener('click', function() {
|
||||
modal.remove();
|
||||
});
|
||||
|
||||
modalBody.appendChild(agentDropDownLabel);
|
||||
modalBody.appendChild(agentDropDownPicker);
|
||||
modalBody.appendChild(seeAllAgentsLink);
|
||||
|
||||
let modalFooter = document.createElement('div');
|
||||
modalFooter.classList.add("modal-footer");
|
||||
modalFooter.appendChild(closeButton);
|
||||
modalFooter.appendChild(newConversationSubmitButton);
|
||||
modalBody.appendChild(modalFooter);
|
||||
|
||||
modalContent.appendChild(modalBody);
|
||||
modal.appendChild(modalContent);
|
||||
document.body.appendChild(modal);
|
||||
}
|
||||
|
||||
function refreshChatSessionsPanel() {
|
||||
|
@ -1175,8 +1290,6 @@ To get started, just start typing below. You can also type / to see a list of co
|
|||
document.getElementById('new-conversation').classList.toggle('collapsed');
|
||||
document.getElementById('existing-conversations').classList.toggle('collapsed');
|
||||
document.getElementById('side-panel-collapse').style.transform = document.getElementById('side-panel').classList.contains('collapsed') ? 'rotate(0deg)' : 'rotate(180deg)';
|
||||
|
||||
document.getElementById('chat-section-wrapper').classList.toggle('mobile-friendly');
|
||||
}
|
||||
</script>
|
||||
<body>
|
||||
|
@ -1196,13 +1309,27 @@ To get started, just start typing below. You can also type / to see a list of co
|
|||
<path d="M16 0c-8.836 0-16 7.163-16 16s7.163 16 16 16c8.837 0 16-7.163 16-16s-7.163-16-16-16zM16 30.032c-7.72 0-14-6.312-14-14.032s6.28-14 14-14 14 6.28 14 14-6.28 14.032-14 14.032zM23 15h-6v-6c0-0.552-0.448-1-1-1s-1 0.448-1 1v6h-6c-0.552 0-1 0.448-1 1s0.448 1 1 1h6v6c0 0.552 0.448 1 1 1s1-0.448 1-1v-6h6c0.552 0 1-0.448 1-1s-0.448-1-1-1z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div id="conversation-list-header" style="display: none;">Conversations</div>
|
||||
</div>
|
||||
<div id="existing-conversations">
|
||||
<div id="conversation-list">
|
||||
<div id="conversation-list-header" style="display: none;">Recent Conversations</div>
|
||||
<div id="conversation-list-body"></div>
|
||||
</div>
|
||||
</div>
|
||||
<a class="inline-chat-link" id="agent-link" href="">
|
||||
<div id="agent-metadata" style="display: none;">
|
||||
Active
|
||||
<div id="agent-metadata-content">
|
||||
<div id="agent-avatar-wrapper">
|
||||
<img id="agent-avatar" src="" alt="Agent Avatar" />
|
||||
</div>
|
||||
<div id="agent-name-wrapper">
|
||||
<div id="agent-name"></div>
|
||||
<div id="agent-owned-by-user" style="display: none;">Edit</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div id="collapse-side-panel">
|
||||
<button
|
||||
|
@ -1278,7 +1405,7 @@ To get started, just start typing below. You can also type / to see a list of co
|
|||
color: var(--main-text-color);
|
||||
text-align: center;
|
||||
font-family: var(--font-family);
|
||||
font-size: 20px;
|
||||
font-size: medium;
|
||||
font-weight: 300;
|
||||
line-height: 1.5em;
|
||||
height: 100vh;
|
||||
|
@ -1429,10 +1556,6 @@ To get started, just start typing below. You can also type / to see a list of co
|
|||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
#chat-section-wrapper.mobile-friendly {
|
||||
grid-template-columns: auto auto;
|
||||
}
|
||||
|
||||
#chat-body-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -1445,10 +1568,15 @@ To get started, just start typing below. You can also type / to see a list of co
|
|||
background: var(--background-color);
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 11px #aaa;
|
||||
overflow-y: scroll;
|
||||
text-align: left;
|
||||
transition: width 0.3s ease-in-out;
|
||||
max-height: 85vh;
|
||||
max-height: 100%;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
}
|
||||
|
||||
div#existing-conversations {
|
||||
max-height: 95%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
|
@ -1470,8 +1598,12 @@ To get started, just start typing below. You can also type / to see a list of co
|
|||
grid-gap: 8px;
|
||||
}
|
||||
|
||||
div#conversation-list {
|
||||
height: 1;
|
||||
}
|
||||
|
||||
div#side-panel-wrapper {
|
||||
display: flex
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#chat-body {
|
||||
|
@ -1861,7 +1993,7 @@ To get started, just start typing below. You can also type / to see a list of co
|
|||
}
|
||||
@media only screen and (min-width: 700px) {
|
||||
body {
|
||||
grid-template-columns: auto min(70vw, 100%) auto;
|
||||
grid-template-columns: auto min(90vw, 100%) auto;
|
||||
grid-template-rows: auto auto minmax(80px, 100%) auto;
|
||||
}
|
||||
body > * {
|
||||
|
@ -1882,6 +2014,7 @@ To get started, just start typing below. You can also type / to see a list of co
|
|||
div#new-conversation {
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--main-text-color);
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
|
@ -2037,6 +2170,169 @@ To get started, just start typing below. You can also type / to see a list of co
|
|||
animation-delay: -0.5s;
|
||||
}
|
||||
|
||||
#agent-metadata-content {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
padding: 10px;
|
||||
background-color: var(--primary);
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#agent-metadata {
|
||||
border-top: 1px solid black;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
#agent-avatar-wrapper {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#agent-avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
#agent-name-wrapper {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#agent-name {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
#agent-instructions {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
height: 50px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#agent-owned-by-user {
|
||||
font-size: 12px;
|
||||
color: #007BFF;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: fixed; /* Stay in place */
|
||||
z-index: 1; /* Sit on top */
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%; /* Full width */
|
||||
height: 100%; /* Full height */
|
||||
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
margin: 15% auto; /* 15% from the top and centered */
|
||||
padding: 20px;
|
||||
border: 1px solid #888;
|
||||
width: 250px;
|
||||
text-align: left;
|
||||
background: var(--background-color);
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 11px #aaa;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
color: var(--main-text-color);
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
display: grid;
|
||||
grid-auto-flow: row;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.modal-body a {
|
||||
/* text-decoration: none; */
|
||||
color: var(--summer-sun);
|
||||
}
|
||||
|
||||
.modal-close-button {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--summer-sun);
|
||||
}
|
||||
|
||||
.modal-close-button:hover,
|
||||
.modal-close-button:focus {
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#new-conversation-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#new-conversation-form label,
|
||||
#new-conversation-form input,
|
||||
#new-conversation-form button {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#new-conversation-form button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: 12px;
|
||||
}
|
||||
|
||||
.modal-body button {
|
||||
cursor: pointer;
|
||||
border-radius: 12px;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--main-text-color);
|
||||
}
|
||||
|
||||
button#new-conversation-submit-button {
|
||||
background: var(--summer-sun);
|
||||
transition: background 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
button#close-button {
|
||||
background: var(--background-color);
|
||||
transition: background 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
button#new-conversation-submit-button:hover {
|
||||
background: var(--primary);
|
||||
}
|
||||
|
||||
button#close-button:hover {
|
||||
background: var(--primary-hover);
|
||||
}
|
||||
|
||||
.modal-body select {
|
||||
padding: 8px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--main-text-color);
|
||||
}
|
||||
|
||||
|
||||
@keyframes lds-ripple {
|
||||
0% {
|
||||
top: 36px;
|
||||
|
|
|
@ -9,26 +9,28 @@
|
|||
<a id="search-nav" class="khoj-nav" href="/search">🔎 Search</a>
|
||||
{% endif %}
|
||||
<!-- Dropdown Menu -->
|
||||
<div id="khoj-nav-menu-container" class="khoj-nav dropdown">
|
||||
{% if user_photo and user_photo != "None" %}
|
||||
{% if is_active %}
|
||||
<img id="profile-picture" class="circle subscribed" src="{{ user_photo }}" alt="{{ username[0].upper() }}" onclick="toggleMenu()" referrerpolicy="no-referrer">
|
||||
{% if username %}
|
||||
<div id="khoj-nav-menu-container" class="khoj-nav dropdown">
|
||||
{% if user_photo and user_photo != "None" %}
|
||||
{% if is_active %}
|
||||
<img id="profile-picture" class="circle subscribed" src="{{ user_photo }}" alt="{{ username[0].upper() }}" onclick="toggleMenu()" referrerpolicy="no-referrer">
|
||||
{% else %}
|
||||
<img id="profile-picture" class="circle" src="{{ user_photo }}" alt="{{ username[0].upper() }}" onclick="toggleMenu()" referrerpolicy="no-referrer">
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<img id="profile-picture" class="circle" src="{{ user_photo }}" alt="{{ username[0].upper() }}" onclick="toggleMenu()" referrerpolicy="no-referrer">
|
||||
{% if is_active %}
|
||||
<div id="profile-picture" class="circle user-initial subscribed" alt="{{ username[0].upper() }}" onclick="toggleMenu()">{{ username[0].upper() }}</div>
|
||||
{% else %}
|
||||
<div id="profile-picture" class="circle user-initial" alt="{{ username[0].upper() }}" onclick="toggleMenu()">{{ username[0].upper() }}</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if is_active %}
|
||||
<div id="profile-picture" class="circle user-initial subscribed" alt="{{ username[0].upper() }}" onclick="toggleMenu()">{{ username[0].upper() }}</div>
|
||||
{% else %}
|
||||
<div id="profile-picture" class="circle user-initial" alt="{{ username[0].upper() }}" onclick="toggleMenu()">{{ username[0].upper() }}</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<div id="khoj-nav-menu" class="khoj-nav-dropdown-content">
|
||||
<div class="khoj-nav-username"> {{ username }} </div>
|
||||
<a id="settings-nav" class="khoj-nav" href="/config">⚙️ Settings</a>
|
||||
<a class="khoj-nav" href="/auth/logout">🔑 Logout</a>
|
||||
<div id="khoj-nav-menu" class="khoj-nav-dropdown-content">
|
||||
<div class="khoj-nav-username"> {{ username }} </div>
|
||||
<a id="settings-nav" class="khoj-nav" href="/config">⚙️ Settings</a>
|
||||
<a class="khoj-nav" href="/auth/logout">🔑 Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
{%- endmacro %}
|
||||
|
|
|
@ -114,7 +114,7 @@ class MarkdownToEntries(TextToEntries):
|
|||
# Append base filename to compiled entry for context to model
|
||||
# Increment heading level for heading entries and make filename as its top level heading
|
||||
prefix = f"# {stem}\n#" if heading else f"# {stem}\n"
|
||||
compiled_entry = f"{prefix}{parsed_entry}"
|
||||
compiled_entry = f"{entry_filename}\n{prefix}{parsed_entry}"
|
||||
entries.append(
|
||||
Entry(
|
||||
compiled=compiled_entry,
|
||||
|
|
|
@ -23,7 +23,7 @@ Today is {current_date} in UTC.
|
|||
|
||||
custom_personality = PromptTemplate.from_template(
|
||||
"""
|
||||
Your are {name}, a personal agent on Khoj.
|
||||
You are {name}, a personal agent on Khoj.
|
||||
Use your general knowledge and past conversation with the user as context to inform your responses.
|
||||
You were created by Khoj Inc. with the following capabilities:
|
||||
|
||||
|
|
39
src/khoj/routers/api_agents.py
Normal file
39
src/khoj/routers/api_agents.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
import json
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.requests import Request
|
||||
from fastapi.responses import Response
|
||||
|
||||
from khoj.database.adapters import AgentAdapters
|
||||
from khoj.database.models import KhojUser
|
||||
from khoj.routers.helpers import CommonQueryParams
|
||||
|
||||
# Initialize Router
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
api_agents = APIRouter()
|
||||
|
||||
|
||||
@api_agents.get("/", response_class=Response)
|
||||
async def all_agents(
|
||||
request: Request,
|
||||
common: CommonQueryParams,
|
||||
) -> Response:
|
||||
user: KhojUser = request.user.object if request.user.is_authenticated else None
|
||||
agents = await AgentAdapters.aget_all_accessible_agents(user)
|
||||
agents_packet = list()
|
||||
for agent in agents:
|
||||
agents_packet.append(
|
||||
{
|
||||
"slug": agent.slug,
|
||||
"avatar": agent.avatar,
|
||||
"name": agent.name,
|
||||
"tuning": agent.tuning,
|
||||
"public": agent.public,
|
||||
"creator": agent.creator.username if agent.creator else None,
|
||||
"managed_by_admin": agent.managed_by_admin,
|
||||
}
|
||||
)
|
||||
return Response(content=json.dumps(agents_packet), media_type="application/json", status_code=200)
|
|
@ -81,9 +81,22 @@ def chat_history(
|
|||
status_code=404,
|
||||
)
|
||||
|
||||
agent_metadata = None
|
||||
if conversation.agent:
|
||||
agent_metadata = {
|
||||
"slug": conversation.agent.slug,
|
||||
"name": conversation.agent.name,
|
||||
"avatar": conversation.agent.avatar,
|
||||
"isCreator": conversation.agent.creator == user,
|
||||
}
|
||||
|
||||
meta_log = conversation.conversation_log
|
||||
meta_log.update(
|
||||
{"conversation_id": conversation.id, "slug": conversation.title if conversation.title else conversation.slug}
|
||||
{
|
||||
"conversation_id": conversation.id,
|
||||
"slug": conversation.title if conversation.title else conversation.slug,
|
||||
"agent": agent_metadata,
|
||||
}
|
||||
)
|
||||
|
||||
update_telemetry_state(
|
||||
|
@ -148,12 +161,12 @@ def chat_sessions(
|
|||
async def create_chat_session(
|
||||
request: Request,
|
||||
common: CommonQueryParams,
|
||||
agent_id: Optional[int] = None,
|
||||
agent_slug: Optional[str] = None,
|
||||
):
|
||||
user = request.user.object
|
||||
|
||||
# Create new Conversation Session
|
||||
conversation = await ConversationAdapters.acreate_conversation_session(user, request.user.client_app)
|
||||
conversation = await ConversationAdapters.acreate_conversation_session(user, request.user.client_app, agent_slug)
|
||||
|
||||
response = {"conversation_id": conversation.id}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ from starlette.authentication import requires
|
|||
from starlette.config import Config
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import HTMLResponse, RedirectResponse, Response
|
||||
from starlette.status import HTTP_302_FOUND
|
||||
|
||||
from khoj.database.adapters import (
|
||||
create_khoj_token,
|
||||
|
@ -90,6 +91,7 @@ async def delete_token(request: Request, token: str) -> str:
|
|||
@auth_router.post("/redirect")
|
||||
async def auth(request: Request):
|
||||
form = await request.form()
|
||||
next_url = request.query_params.get("next", "/")
|
||||
credential = form.get("credential")
|
||||
|
||||
csrf_token_cookie = request.cookies.get("g_csrf_token")
|
||||
|
@ -117,9 +119,9 @@ async def auth(request: Request):
|
|||
metadata={"user_id": str(khoj_user.uuid)},
|
||||
)
|
||||
logger.log(logging.INFO, f"New User Created: {khoj_user.uuid}")
|
||||
RedirectResponse(url="/?status=welcome")
|
||||
return RedirectResponse(url=f"{next_url}", status_code=HTTP_302_FOUND)
|
||||
|
||||
return RedirectResponse(url="/")
|
||||
return RedirectResponse(url=f"{next_url}")
|
||||
|
||||
|
||||
@auth_router.get("/logout")
|
||||
|
|
|
@ -115,8 +115,8 @@ def chat_page(request: Request):
|
|||
|
||||
@web_client.get("/login", response_class=FileResponse)
|
||||
def login_page(request: Request):
|
||||
next_url = request.query_params.get("next", "/")
|
||||
if request.user.is_authenticated:
|
||||
next_url = request.query_params.get("next", "/")
|
||||
return RedirectResponse(url=next_url)
|
||||
google_client_id = os.environ.get("GOOGLE_CLIENT_ID")
|
||||
redirect_uri = str(request.app.url_path_for("auth"))
|
||||
|
@ -125,14 +125,74 @@ def login_page(request: Request):
|
|||
context={
|
||||
"request": request,
|
||||
"google_client_id": google_client_id,
|
||||
"redirect_uri": redirect_uri,
|
||||
"redirect_uri": f"{redirect_uri}?next={next_url}",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@web_client.get("/agents", response_class=HTMLResponse)
|
||||
def agents_page(request: Request):
|
||||
agents = AgentAdapters.get_all_acessible_agents(request.user.object if request.user.is_authenticated else None)
|
||||
user: KhojUser = request.user.object if request.user.is_authenticated else None
|
||||
user_picture = request.session.get("user", {}).get("picture") if user else None
|
||||
agents = AgentAdapters.get_all_accessible_agents(user)
|
||||
agents_packet = list()
|
||||
for agent in agents:
|
||||
agents_packet.append(
|
||||
{
|
||||
"slug": agent.slug,
|
||||
"avatar": agent.avatar,
|
||||
"name": agent.name,
|
||||
"tuning": agent.tuning,
|
||||
"public": agent.public,
|
||||
"creator": agent.creator.username if agent.creator else None,
|
||||
"managed_by_admin": agent.managed_by_admin,
|
||||
}
|
||||
)
|
||||
return templates.TemplateResponse(
|
||||
"agents.html",
|
||||
context={
|
||||
"request": request,
|
||||
"agents": agents_packet,
|
||||
"khoj_version": state.khoj_version,
|
||||
"username": user.username if user else None,
|
||||
"has_documents": False,
|
||||
"is_active": has_required_scope(request, ["premium"]),
|
||||
"user_photo": user_picture,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@web_client.get("/agent/{agent_slug}", response_class=HTMLResponse)
|
||||
def agents_page(request: Request, agent_slug: str):
|
||||
user: KhojUser = request.user.object if request.user.is_authenticated else None
|
||||
user_picture = request.session.get("user", {}).get("picture") if user else None
|
||||
|
||||
agent = AgentAdapters.get_agent_by_slug(agent_slug)
|
||||
|
||||
agent_metadata = {
|
||||
"slug": agent.slug,
|
||||
"avatar": agent.avatar,
|
||||
"name": agent.name,
|
||||
"tuning": agent.tuning,
|
||||
"public": agent.public,
|
||||
"creator": agent.creator.username if agent.creator else None,
|
||||
"managed_by_admin": agent.managed_by_admin,
|
||||
"chat_model": agent.chat_model.chat_model,
|
||||
"creator_not_self": agent.creator != user,
|
||||
}
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"agent.html",
|
||||
context={
|
||||
"request": request,
|
||||
"agent": agent_metadata,
|
||||
"khoj_version": state.khoj_version,
|
||||
"username": user.username if user else None,
|
||||
"has_documents": False,
|
||||
"is_active": has_required_scope(request, ["premium"]),
|
||||
"user_photo": user_picture,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@web_client.get("/config", response_class=HTMLResponse)
|
||||
|
|
Loading…
Reference in a new issue