From c837f3779e437c3a11bf75e5f0a0002c1707df61 Mon Sep 17 00:00:00 2001
From: sabaimran <65192171+sabaimran@users.noreply.github.com>
Date: Mon, 15 Jul 2024 21:40:55 -0700
Subject: [PATCH] Update the agents page with new UX (#850)
- Use icons/colors for setting the styling of agents
- Update automations page to use the shadcn cards: https://github.com/shadcn-ui/ui
---
.../web/app/agents/agents.module.css | 160 --------
src/interface/web/app/agents/layout.tsx | 4 +-
src/interface/web/app/agents/page.tsx | 364 ++++++++++++------
src/interface/web/app/automations/layout.tsx | 3 +-
.../web/app/components/navMenu/navMenu.tsx | 26 +-
src/interface/web/package.json | 10 +-
src/interface/web/yarn.lock | 8 +-
...0053_agent_style_color_agent_style_icon.py | 61 +++
src/khoj/database/models/__init__.py | 35 ++
src/khoj/routers/api_agents.py | 2 +
src/khoj/routers/api_chat.py | 4 +
yarn.lock | 2 +
12 files changed, 395 insertions(+), 284 deletions(-)
create mode 100644 src/khoj/database/migrations/0053_agent_style_color_agent_style_icon.py
create mode 100644 yarn.lock
diff --git a/src/interface/web/app/agents/agents.module.css b/src/interface/web/app/agents/agents.module.css
index 671f8ecc..af3490b2 100644
--- a/src/interface/web/app/agents/agents.module.css
+++ b/src/interface/web/app/agents/agents.module.css
@@ -16,28 +16,7 @@ div.agentPersonality {
overflow: hidden;
}
-div.agentInfo {
- font-size: medium;
-}
-div.agentInfo a,
-div.agentInfo h2 {
- margin: 0;
-}
-
-div.agent img {
- border-radius: 50%;
- object-fit: cover;
-}
-
-div.agent a {
- text-decoration: none;
-}
-
-div#agentsHeader {
- display: grid;
- grid-template-columns: auto;
-}
button.infoButton {
border: none;
@@ -47,63 +26,6 @@ button.infoButton {
font-size: medium;
}
-div#agentsHeader a,
-div.agentInfo button {
- font-size: 24px;
- font-weight: bold;
- padding: 4px;
- border: none;
- border-radius: 8px;
- font: inherit;
- cursor: pointer;
- transition: background-color 0.3s;
-}
-
-div#agentsHeader a:hover,
-div.agentInfo button:hover {
- box-shadow: 0 0 10px var(--primary-hover);
-}
-
-div.agent {
- display: grid;
- grid-template-columns: auto 1fr auto;
- gap: 4px;
- align-items: center;
- padding: 20px;
- box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
- border-radius: 8px;
- background: linear-gradient(18.48deg,rgba(252, 213, 87, 0.25) 2.76%,rgba(197, 0, 0, 0) 17.23%),linear-gradient(200.6deg,rgba(244, 229, 68, 0.25) 4.13%,rgba(230, 26, 26, 0) 20.54%);
-}
-
-div.agentModal {
- background: linear-gradient(18.48deg,rgba(252, 213, 87, 0.25) 2.76%,rgba(197, 0, 0, 0) 17.23%),linear-gradient(200.6deg,rgba(244, 229, 68, 0.25) 4.13%,rgba(230, 26, 26, 0) 20.54%);
-}
-
-div.agentModalContent button {
- width: 100%;
- margin: 10px 0;
- padding: 8px;
-}
-
-div.agentModalHeader {
- display: grid;
- grid-template-columns: 1fr auto;
-}
-
-div.agentAvatar {
- display: flex;
- align-items: center;
- gap: 8px;
-}
-
-div.agentModalContent p {
- white-space: break-spaces;
- line-height: 1.5;
-}
-
-div.agentInfo {
- text-align: left;
-}
div.agentList {
display: grid;
@@ -115,53 +37,6 @@ div.agentList {
margin-left: auto;
}
-svg.newConvoButton {
- width: 20px;
- margin-left: 5px;
-}
-
-div.agentModalContainer {
- position: fixed; /* Changed from absolute to fixed */
- top: 0;
- left: 0;
- width: 100%;
- height: 100%; /* This ensures it covers the viewport height */
- display: flex;
- justify-content: center;
- align-items: center;
- background-color: rgba(1,1,1,0.5);
- z-index: 1000; /* Ensure it's above other content */
- overflow-y: auto; /* Allows scrolling within the modal if needed */
-}
-
-div.agentModal {
- position: relative;
- width: 50%;
- margin: auto;
- padding: 20px;
- background-color: white;
- border-radius: 8px;
- box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
-}
-
-div.agentModalActions {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 10px;
-}
-
-div.agentModalActions button {
- padding: 8px;
- border: none;
- border-radius: 8px;
- font-size: 16px;
- cursor: pointer;
- transition: background-color 0.3s;
-}
-
-div.agentModalActions button:hover {
- box-shadow: 0 0 10px var(hsla(--background));
-}
@media only screen and (max-width: 700px) {
div.agentList {
@@ -171,39 +46,4 @@ div.agentModalActions button:hover {
margin-left: auto;
grid-template-columns: 1fr;
}
-
- div.agentModal {
- width: 90%;
- }
-
-}
-.loader {
- width: 48px;
- height: 48px;
- border-radius: 50%;
- display: inline-block;
- border-top: 4px solid var(--primary-color);
- border-right: 4px solid transparent;
- box-sizing: border-box;
- animation: rotation 1s linear infinite;
-}
-.loader::after {
- content: '';
- box-sizing: border-box;
- position: absolute;
- left: 0;
- top: 0;
- width: 48px;
- height: 48px;
- border-radius: 50%;
- border-bottom: 4px solid transparent;
- animation: rotation 0.5s linear infinite reverse;
-}
-@keyframes rotation {
- 0% {
- transform: rotate(0deg);
- }
- 100% {
- transform: rotate(360deg);
- }
}
diff --git a/src/interface/web/app/agents/layout.tsx b/src/interface/web/app/agents/layout.tsx
index 7e8e3d59..f24bd08f 100644
--- a/src/interface/web/app/agents/layout.tsx
+++ b/src/interface/web/app/agents/layout.tsx
@@ -3,6 +3,8 @@ import type { Metadata } from "next";
import NavMenu from '../components/navMenu/navMenu';
import styles from './agentsLayout.module.css';
+import "../globals.css";
+
export const metadata: Metadata = {
title: "Khoj AI - Agents",
description: "Use Agents with Khoj AI for deeper, more personalized queries.",
@@ -18,7 +20,7 @@ export default function RootLayout({
}>) {
return (
-
+
{children}
);
diff --git a/src/interface/web/app/agents/page.tsx b/src/interface/web/app/agents/page.tsx
index 458031a1..a8fd1112 100644
--- a/src/interface/web/app/agents/page.tsx
+++ b/src/interface/web/app/agents/page.tsx
@@ -10,13 +10,56 @@ import { useEffect, useState } from 'react';
import { useAuthenticatedData, UserProfile } from '../common/auth';
import { Button } from '@/components/ui/button';
+import {
+ Lightbulb,
+ Robot,
+ Aperture,
+ GraduationCap,
+ Jeep,
+ Island,
+ MathOperations,
+ Asclepius,
+ Couch,
+ Code,
+ Atom,
+ ClockCounterClockwise,
+ PaperPlaneTilt,
+ Info,
+ UserCircle
+} from "@phosphor-icons/react";
+import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
+import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader, DialogTrigger } from '@/components/ui/dialog';
+import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger } from '@/components/ui/drawer';
+import LoginPrompt from '../components/loginPrompt/loginPrompt';
+import Loading, { InlineLoading } from '../components/loading/loading';
+import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
+interface IconMap {
+ [key: string]: (color: string, width: string, height: string) => JSX.Element | null;
+}
+
+const iconMap: IconMap = {
+ Lightbulb: (color: string, width: string, height: string) => ,
+ Robot: (color: string, width: string, height: string) => ,
+ Aperture: (color: string, width: string, height: string) => ,
+ GraduationCap: (color: string, width: string, height: string) => ,
+ Jeep: (color: string, width: string, height: string) => ,
+ Island: (color: string, width: string, height: string) => ,
+ MathOperations: (color: string, width: string, height: string) => ,
+ Asclepius: (color: string, width: string, height: string) => ,
+ Couch: (color: string, width: string, height: string) => ,
+ Code: (color: string, width: string, height: string) =>
,
+ Atom: (color: string, width: string, height: string) => ,
+ ClockCounterClockwise: (color: string, width: string, height: string) => ,
+};
export interface AgentData {
slug: string;
avatar: string;
name: string;
personality: string;
+ color: string;
+ icon: string;
}
async function openChat(slug: string, userData: UserProfile | null) {
@@ -30,7 +73,7 @@ async function openChat(slug: string, userData: UserProfile | null) {
const response = await fetch(`/api/chat/sessions?agent_slug=${slug}`, { method: "POST" });
if (response.status == 200) {
window.location.href = `/chat`;
- } else if(response.status == 403 || response.status == 401) {
+ } else if (response.status == 403 || response.status == 401) {
window.location.href = unauthenticatedRedirectUrl;
} else {
alert("Failed to start chat session");
@@ -39,86 +82,68 @@ async function openChat(slug: string, userData: UserProfile | null) {
const agentsFetcher = () => window.fetch('/api/agents').then(res => res.json()).catch(err => console.log(err));
-interface AgentModalProps {
- data: AgentData;
- setShowModal: (show: boolean) => void;
- userData: UserProfile | null;
-}
interface AgentCardProps {
data: AgentData;
userProfile: UserProfile | null;
+ isMobileWidth: boolean;
}
-function AgentModal(props: AgentModalProps) {
- const [copiedToClipboard, setCopiedToClipboard] = useState(false);
+function getIconFromIconName(iconName: string, color: string = 'gray', width: string = 'w-8', height: string = 'h-8') {
+ const icon = iconMap[iconName];
+ const colorName = color.toLowerCase();
+ const colorClass = convertColorToTextClass(colorName);
- useEffect(() => {
- if (copiedToClipboard) {
- setTimeout(() => setCopiedToClipboard(false), 3000);
- }
- }, [copiedToClipboard]);
+ return icon ? icon(colorClass, width, height) : null;
+}
- return (
-
-
-
-
-
-
-
{props.data.name}
-
-
- {
- navigator.clipboard.writeText(`${window.location.host}/agents?agent=${props.data.slug}`);
- setCopiedToClipboard(true);
- }}>
- {
- copiedToClipboard ?
-
- :
- }
-
- {
- props.setShowModal(false);
- }}>
-
-
-
-
-
{props.data.personality}
-
- openChat(props.data.slug, props.userData)}>
- Chat
-
-
-
-
-
- );
+function convertColorToClass(color: string) {
+ // We can't dyanmically generate the classes for tailwindcss, so we have to explicitly use the whole string.
+ // See models/__init__.py 's definition of the Agent model for the color choices.
+ if (color === 'red') return `bg-red-500 hover:bg-red-600`;
+ if (color === 'yellow') return `bg-yellow-500 hover:bg-yellow-600`;
+ if (color === 'green') return `bg-green-500 hover:bg-green-600`;
+ if (color === 'blue') return `bg-blue-500 hover:bg-blue-600`;
+ if (color === 'orange') return `bg-orange-500 hover:bg-orange-600`;
+ if (color === 'purple') return `bg-purple-500 hover:bg-purple-600`;
+ if (color === 'pink') return `bg-pink-500 hover:bg-pink-600`;
+ if (color === 'teal') return `bg-teal-500 hover:bg-teal-600`;
+ if (color === 'cyan') return `bg-cyan-500 hover:bg-cyan-600`;
+ if (color === 'lime') return `bg-lime-500 hover:bg-lime-600`;
+ if (color === 'indigo') return `bg-indigo-500 hover:bg-indigo-600`;
+ if (color === 'fuschia') return `bg-fuschia-500 hover:bg-fuschia-600`;
+ if (color === 'rose') return `bg-rose-500 hover:bg-rose-600`;
+ if (color === 'sky') return `bg-sky-500 hover:bg-sky-600`;
+ if (color === 'amber') return `bg-amber-500 hover:bg-amber-600`;
+ if (color === 'emerald') return `bg-emerald-500 hover:bg-emerald-600`;
+ return `bg-gray-500 hover:bg-gray-600`;
+}
+
+function convertColorToTextClass(color: string) {
+ if (color === 'red') return `text-red-500`;
+ if (color === 'yellow') return `text-yellow-500`;
+ if (color === 'green') return `text-green-500`;
+ if (color === 'blue') return `text-blue-500`;
+ if (color === 'orange') return `text-orange-500`;
+ if (color === 'purple') return `text-purple-500`;
+ if (color === 'pink') return `text-pink-500`;
+ if (color === 'teal') return `text-teal-500`;
+ if (color === 'cyan') return `text-cyan-500`;
+ if (color === 'lime') return `text-lime-500`;
+ if (color === 'indigo') return `text-indigo-500`;
+ if (color === 'fuschia') return `text-fuschia-500`;
+ if (color === 'rose') return `text-rose-500`;
+ if (color === 'sky') return `text-sky-500`;
+ if (color === 'amber') return `text-amber-500`;
+ if (color === 'emerald') return `text-emerald-500`;
+ return `text-gray-500`;
}
function AgentCard(props: AgentCardProps) {
const searchParams = new URLSearchParams(window.location.search);
const agentSlug = searchParams.get('agent');
const [showModal, setShowModal] = useState(agentSlug === props.data.slug);
+ const [showLoginPrompt, setShowLoginPrompt] = useState(false);
const userData = props.userProfile;
@@ -126,52 +151,144 @@ function AgentCard(props: AgentCardProps) {
window.history.pushState({}, `Khoj AI - Agent ${props.data.slug}`, `/agents?agent=${props.data.slug}`);
}
+ const stylingString = convertColorToClass(props.data.color);
+
return (
-
+
{
- showModal &&
+ showLoginPrompt &&
+
}
-
-
-
+
+
+ {
+ !props.isMobileWidth ?
+ {
+ setShowModal(!showModal);
+ window.history.pushState({}, `Khoj AI - Agents`, `/agents`);
+ }}>
+
+
+ {
+ getIconFromIconName(props.data.icon, props.data.color) ||
+ }
+ {props.data.name}
+
+
+
+
+
+ {
+ getIconFromIconName(props.data.icon, props.data.color) ||
+ }
+ {props.data.name}
+
+
+ {props.data.personality}
+
+ {
+ openChat(props.data.slug, userData);
+ setShowModal(false);
+ }}>
+ Chat
+
+
+
+
+ :
+ {
+ setShowModal(open);
+ window.history.pushState({}, `Khoj AI - Agents`, `/agents`);
+ }}>
+
+
+ {
+ getIconFromIconName(props.data.icon, props.data.color) ||
+ }
+ {props.data.name}
+
+
+
+
+ {props.data.name}
+ Full Prompt
+
+ {props.data.personality}
+
+
+ Done
+
+
+
+
+ }
+
+
+
+
+
setShowModal(true)}>
+ {props.data.personality}
+
-
-
- {
- setShowModal(true);
- } }>
- {props.data.name}
-
-
-
- openChat(props.data.slug, userData)}>
-
-
-
-
-
setShowModal(true)}>
- {props.data.personality}
-
-
-
- );
+
+
+ {
+ props.userProfile ?
+ openChat(props.data.slug, userData)}>
+
+
+ :
+ setShowLoginPrompt(true)}>
+
+
+ }
+
+
+ )
}
export default function Agents() {
const { data, error } = useSWR
('agents', agentsFetcher, { revalidateOnFocus: false });
- const userData = useAuthenticatedData();
+ const authenticatedData = useAuthenticatedData();
+ const [isMobileWidth, setIsMobileWidth] = useState(false);
+ const [showLoginPrompt, setShowLoginPrompt] = useState(false);
+
+ useEffect(() => {
+ if (typeof window !== 'undefined') {
+ setIsMobileWidth(window.innerWidth < 768);
+ }
+
+ window.addEventListener('resize', () => {
+ setIsMobileWidth(window.innerWidth < 768);
+ });
+ }, []);
if (error) {
return (
@@ -193,7 +310,7 @@ export default function Agents() {
Talk to a Specialized Agent
- Loading agents...
+ booting up your agents
);
@@ -201,12 +318,39 @@ export default function Agents() {
return (
-
- Talk to a Specialized Agent
-
+
+ Agents
+
+ {
+ showLoginPrompt &&
+
+ }
+
+
+
+ How this works
+
+ You can use any of these specialized agents to tailor to tune your conversation to your needs.
+ {
+ !authenticatedData &&
+ <>
+
+ setShowLoginPrompt(true)}>
+ Sign In
+
+ >
+
+ }
+
+ Coming Soon: Support for making your own agents.
+
+
{data.map(agent => (
-
+
))}
diff --git a/src/interface/web/app/automations/layout.tsx b/src/interface/web/app/automations/layout.tsx
index 23493c75..7e6f8fc8 100644
--- a/src/interface/web/app/automations/layout.tsx
+++ b/src/interface/web/app/automations/layout.tsx
@@ -1,9 +1,10 @@
-
import type { Metadata } from "next";
import NavMenu from '../components/navMenu/navMenu';
import styles from './automationsLayout.module.css';
import { Toaster } from "@/components/ui/toaster";
+import "../globals.css";
+
export const metadata: Metadata = {
title: "Khoj AI - Automations",
diff --git a/src/interface/web/app/components/navMenu/navMenu.tsx b/src/interface/web/app/components/navMenu/navMenu.tsx
index 2409db3e..5eaad6cd 100644
--- a/src/interface/web/app/components/navMenu/navMenu.tsx
+++ b/src/interface/web/app/components/navMenu/navMenu.tsx
@@ -40,6 +40,7 @@ export default function NavMenu(props: NavMenuProps) {
const [isMobileWidth, setIsMobileWidth] = useState(false);
const [darkMode, setDarkMode] = useState(false);
+ const [initialLoadDone, setInitialLoadDone] = useState(false);
useEffect(() => {
setIsMobileWidth(window.innerWidth < 768);
@@ -62,13 +63,26 @@ export default function NavMenu(props: NavMenuProps) {
if (localStorage.getItem('theme') === 'dark') {
document.documentElement.classList.add('dark');
setDarkMode(true);
- } else if (mq.matches) {
- document.documentElement.classList.add('dark');
- setDarkMode(true);
+ } else if (localStorage.getItem('theme') === 'light') {
+ document.documentElement.classList.remove('dark');
+ setDarkMode(false);
+ } else {
+ const mq = window.matchMedia(
+ "(prefers-color-scheme: dark)"
+ );
+
+ if (mq.matches) {
+ document.documentElement.classList.add('dark');
+ setDarkMode(true);
+ }
}
+
+ setInitialLoadDone(true);
}, []);
+
useEffect(() => {
+ if (!initialLoadDone) return;
toggleDarkMode(darkMode);
}, [darkMode]);
@@ -128,17 +142,17 @@ export default function NavMenu(props: NavMenuProps) {
:
-
+
Chat
-
+
Agents
-
+
Automations
diff --git a/src/interface/web/package.json b/src/interface/web/package.json
index 8c94ea7c..fbb4e926 100644
--- a/src/interface/web/package.json
+++ b/src/interface/web/package.json
@@ -39,17 +39,22 @@
"@types/dompurify": "^3.0.5",
"@types/katex": "^0.16.7",
"@types/markdown-it": "^14.1.1",
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
"autoprefixer": "^10.4.19",
"class-variance-authority": "^0.7.0",
- "clsx": "^2.1.1",
"cmdk": "^1.0.0",
- "cronstrue": "^2.50.0",
"dompurify": "^3.1.6",
+ "eslint": "^8",
+ "eslint-config-next": "14.2.3",
+ "clsx": "^2.1.1",
+ "cronstrue": "^2.50.0",
"katex": "^0.16.10",
"lucide-react": "^0.397.0",
"markdown-it": "^14.1.0",
"markdown-it-highlightjs": "^4.1.0",
"next": "14.2.3",
+ "nodemon": "^3.1.3",
"postcss": "^8.4.38",
"react": "^18",
"react-dom": "^18",
@@ -58,6 +63,7 @@
"swr": "^2.2.5",
"tailwind-merge": "^2.3.0",
"tailwindcss": "^3.4.4",
+ "typescript": "^5",
"tailwindcss-animate": "^1.0.7",
"vaul": "^0.9.1",
"zod": "^3.23.8"
diff --git a/src/interface/web/yarn.lock b/src/interface/web/yarn.lock
index 60696340..65b65b92 100644
--- a/src/interface/web/yarn.lock
+++ b/src/interface/web/yarn.lock
@@ -1140,10 +1140,10 @@
resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-2.0.0.tgz#d43878b5b20222682163ae6f897b20447233bdfd"
integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==
-"@types/node@^20":
- version "20.14.2"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.2.tgz#a5f4d2bcb4b6a87bffcaa717718c5a0f208f4a18"
- integrity sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==
+"@types/node@20.14.10":
+ version "20.14.10"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.10.tgz#a1a218290f1b6428682e3af044785e5874db469a"
+ integrity sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==
dependencies:
undici-types "~5.26.4"
diff --git a/src/khoj/database/migrations/0053_agent_style_color_agent_style_icon.py b/src/khoj/database/migrations/0053_agent_style_color_agent_style_icon.py
new file mode 100644
index 00000000..0aa78c41
--- /dev/null
+++ b/src/khoj/database/migrations/0053_agent_style_color_agent_style_icon.py
@@ -0,0 +1,61 @@
+# Generated by Django 5.0.6 on 2024-07-13 16:26
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("database", "0052_alter_searchmodelconfig_bi_encoder_docs_encode_config_and_more"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="agent",
+ name="style_color",
+ field=models.CharField(
+ choices=[
+ ("blue", "Blue"),
+ ("green", "Green"),
+ ("red", "Red"),
+ ("yellow", "Yellow"),
+ ("orange", "Orange"),
+ ("purple", "Purple"),
+ ("pink", "Pink"),
+ ("teal", "Teal"),
+ ("cyan", "Cyan"),
+ ("lime", "Lime"),
+ ("indigo", "Indigo"),
+ ("fuschia", "Fuschia"),
+ ("rose", "Rose"),
+ ("sky", "Sky"),
+ ("amber", "Amber"),
+ ("emerald", "Emerald"),
+ ],
+ default="blue",
+ max_length=200,
+ ),
+ ),
+ migrations.AddField(
+ model_name="agent",
+ name="style_icon",
+ field=models.CharField(
+ choices=[
+ ("Lightbulb", "Lighbulb"),
+ ("Health", "Health"),
+ ("Robot", "Robot"),
+ ("Aperture", "Aperture"),
+ ("GraduationCap", "Graduation Cap"),
+ ("Jeep", "Jeep"),
+ ("Island", "Island"),
+ ("MathOperations", "Math Operations"),
+ ("Asclepius", "Asclepius"),
+ ("Couch", "Couch"),
+ ("Code", "Code"),
+ ("Atom", "Atom"),
+ ("ClockCounterClockwise", "Clock Counter Clockwise"),
+ ],
+ default="Lightbulb",
+ max_length=200,
+ ),
+ ),
+ ]
diff --git a/src/khoj/database/models/__init__.py b/src/khoj/database/models/__init__.py
index 096d14bc..1f8d55ab 100644
--- a/src/khoj/database/models/__init__.py
+++ b/src/khoj/database/models/__init__.py
@@ -103,6 +103,39 @@ class VoiceModelOption(BaseModel):
class Agent(BaseModel):
+ class StyleColorTypes(models.TextChoices):
+ BLUE = "blue"
+ GREEN = "green"
+ RED = "red"
+ YELLOW = "yellow"
+ ORANGE = "orange"
+ PURPLE = "purple"
+ PINK = "pink"
+ TEAL = "teal"
+ CYAN = "cyan"
+ LIME = "lime"
+ INDIGO = "indigo"
+ FUSCHIA = "fuschia"
+ ROSE = "rose"
+ SKY = "sky"
+ AMBER = "amber"
+ EMERALD = "emerald"
+
+ class StyleIconTypes(models.TextChoices):
+ LIGHBULB = "Lightbulb"
+ HEALTH = "Health"
+ ROBOT = "Robot"
+ APERTURE = "Aperture"
+ GRADUATION_CAP = "GraduationCap"
+ JEEP = "Jeep"
+ ISLAND = "Island"
+ MATH_OPERATIONS = "MathOperations"
+ ASCLEPIUS = "Asclepius"
+ COUCH = "Couch"
+ CODE = "Code"
+ ATOM = "Atom"
+ CLOCK_COUNTER_CLOCKWISE = "ClockCounterClockwise"
+
creator = models.ForeignKey(
KhojUser, on_delete=models.CASCADE, default=None, null=True, blank=True
) # Creator will only be null when the agents are managed by admin
@@ -114,6 +147,8 @@ class Agent(BaseModel):
managed_by_admin = models.BooleanField(default=False)
chat_model = models.ForeignKey(ChatModelOptions, on_delete=models.CASCADE)
slug = models.CharField(max_length=200)
+ style_color = models.CharField(max_length=200, choices=StyleColorTypes.choices, default=StyleColorTypes.BLUE)
+ style_icon = models.CharField(max_length=200, choices=StyleIconTypes.choices, default=StyleIconTypes.LIGHBULB)
class ProcessLock(BaseModel):
diff --git a/src/khoj/routers/api_agents.py b/src/khoj/routers/api_agents.py
index cdef6206..8d497aec 100644
--- a/src/khoj/routers/api_agents.py
+++ b/src/khoj/routers/api_agents.py
@@ -34,6 +34,8 @@ async def all_agents(
"public": agent.public,
"creator": agent.creator.username if agent.creator else None,
"managed_by_admin": agent.managed_by_admin,
+ "color": agent.style_color,
+ "icon": agent.style_icon,
}
)
diff --git a/src/khoj/routers/api_chat.py b/src/khoj/routers/api_chat.py
index 15e7a143..8a288b71 100644
--- a/src/khoj/routers/api_chat.py
+++ b/src/khoj/routers/api_chat.py
@@ -212,6 +212,8 @@ def chat_history(
"name": conversation.agent.name,
"avatar": conversation.agent.avatar,
"isCreator": conversation.agent.creator == user,
+ "color": conversation.agent.style_color,
+ "icon": conversation.agent.style_icon,
"persona": conversation.agent.personality,
}
@@ -267,6 +269,8 @@ def get_shared_chat(
"name": conversation.agent.name,
"avatar": conversation.agent.avatar,
"isCreator": conversation.agent.creator == user,
+ "color": conversation.agent.style_color,
+ "icon": conversation.agent.style_icon,
"persona": conversation.agent.personality,
}
diff --git a/yarn.lock b/yarn.lock
new file mode 100644
index 00000000..4a580188
--- /dev/null
+++ b/yarn.lock
@@ -0,0 +1,2 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1