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
This commit is contained in:
sabaimran 2024-07-15 21:40:55 -07:00 committed by GitHub
parent 1c6ed9bc6d
commit c837f3779e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 395 additions and 284 deletions

View file

@ -16,28 +16,7 @@ div.agentPersonality {
overflow: hidden; 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 { button.infoButton {
border: none; border: none;
@ -47,63 +26,6 @@ button.infoButton {
font-size: medium; 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 { div.agentList {
display: grid; display: grid;
@ -115,53 +37,6 @@ div.agentList {
margin-left: auto; 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) { @media only screen and (max-width: 700px) {
div.agentList { div.agentList {
@ -171,39 +46,4 @@ div.agentModalActions button:hover {
margin-left: auto; margin-left: auto;
grid-template-columns: 1fr; 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);
}
} }

View file

@ -3,6 +3,8 @@ import type { Metadata } from "next";
import NavMenu from '../components/navMenu/navMenu'; import NavMenu from '../components/navMenu/navMenu';
import styles from './agentsLayout.module.css'; import styles from './agentsLayout.module.css';
import "../globals.css";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Khoj AI - Agents", title: "Khoj AI - Agents",
description: "Use Agents with Khoj AI for deeper, more personalized queries.", description: "Use Agents with Khoj AI for deeper, more personalized queries.",
@ -18,7 +20,7 @@ export default function RootLayout({
}>) { }>) {
return ( return (
<div className={`${styles.agentsLayout}`}> <div className={`${styles.agentsLayout}`}>
<NavMenu selected="Agents" /> <NavMenu selected="Agents" showLogo={true} />
{children} {children}
</div> </div>
); );

View file

@ -10,13 +10,56 @@ import { useEffect, useState } from 'react';
import { useAuthenticatedData, UserProfile } from '../common/auth'; import { useAuthenticatedData, UserProfile } from '../common/auth';
import { Button } from '@/components/ui/button'; 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) => <Lightbulb className={`${width} ${height} ${color} mr-2`} />,
Robot: (color: string, width: string, height: string) => <Robot className={`${width} ${height} ${color} mr-2`} />,
Aperture: (color: string, width: string, height: string) => <Aperture className={`${width} ${height} ${color} mr-2`} />,
GraduationCap: (color: string, width: string, height: string) => <GraduationCap className={`${width} ${height} ${color} mr-2`} />,
Jeep: (color: string, width: string, height: string) => <Jeep className={`${width} ${height} ${color} mr-2`} />,
Island: (color: string, width: string, height: string) => <Island className={`${width} ${height} ${color} mr-2`} />,
MathOperations: (color: string, width: string, height: string) => <MathOperations className={`${width} ${height} ${color} mr-2`} />,
Asclepius: (color: string, width: string, height: string) => <Asclepius className={`${width} ${height} ${color} mr-2`} />,
Couch: (color: string, width: string, height: string) => <Couch className={`${width} ${height} ${color} mr-2`} />,
Code: (color: string, width: string, height: string) => <Code className={`${width} ${height} ${color} mr-2`} />,
Atom: (color: string, width: string, height: string) => <Atom className={`${width} ${height} ${color} mr-2`} />,
ClockCounterClockwise: (color: string, width: string, height: string) => <ClockCounterClockwise className={`${width} ${height} ${color} mr-2`} />,
};
export interface AgentData { export interface AgentData {
slug: string; slug: string;
avatar: string; avatar: string;
name: string; name: string;
personality: string; personality: string;
color: string;
icon: string;
} }
async function openChat(slug: string, userData: UserProfile | null) { async function openChat(slug: string, userData: UserProfile | null) {
@ -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)); 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 { interface AgentCardProps {
data: AgentData; data: AgentData;
userProfile: UserProfile | null; userProfile: UserProfile | null;
isMobileWidth: boolean;
} }
function AgentModal(props: AgentModalProps) { function getIconFromIconName(iconName: string, color: string = 'gray', width: string = 'w-8', height: string = 'h-8') {
const [copiedToClipboard, setCopiedToClipboard] = useState(false); const icon = iconMap[iconName];
const colorName = color.toLowerCase();
const colorClass = convertColorToTextClass(colorName);
useEffect(() => { return icon ? icon(colorClass, width, height) : null;
if (copiedToClipboard) {
setTimeout(() => setCopiedToClipboard(false), 3000);
} }
}, [copiedToClipboard]);
return ( function convertColorToClass(color: string) {
<div className={styles.agentModalContainer}> // We can't dyanmically generate the classes for tailwindcss, so we have to explicitly use the whole string.
<div className={styles.agentModal}> // See models/__init__.py 's definition of the Agent model for the color choices.
<div className={styles.agentModalContent}> if (color === 'red') return `bg-red-500 hover:bg-red-600`;
<div className={styles.agentModalHeader}> if (color === 'yellow') return `bg-yellow-500 hover:bg-yellow-600`;
<div className={styles.agentAvatar}> if (color === 'green') return `bg-green-500 hover:bg-green-600`;
<Image if (color === 'blue') return `bg-blue-500 hover:bg-blue-600`;
src={props.data.avatar} if (color === 'orange') return `bg-orange-500 hover:bg-orange-600`;
alt={props.data.name} if (color === 'purple') return `bg-purple-500 hover:bg-purple-600`;
width={50} if (color === 'pink') return `bg-pink-500 hover:bg-pink-600`;
height={50} if (color === 'teal') return `bg-teal-500 hover:bg-teal-600`;
/> if (color === 'cyan') return `bg-cyan-500 hover:bg-cyan-600`;
<h2>{props.data.name}</h2> if (color === 'lime') return `bg-lime-500 hover:bg-lime-600`;
</div> if (color === 'indigo') return `bg-indigo-500 hover:bg-indigo-600`;
<div className={styles.agentModalActions}> if (color === 'fuschia') return `bg-fuschia-500 hover:bg-fuschia-600`;
<Button className='bg-transparent hover:bg-yellow-500' onClick={() => { if (color === 'rose') return `bg-rose-500 hover:bg-rose-600`;
navigator.clipboard.writeText(`${window.location.host}/agents?agent=${props.data.slug}`); if (color === 'sky') return `bg-sky-500 hover:bg-sky-600`;
setCopiedToClipboard(true); 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`;
copiedToClipboard ?
<Image
src="copy-button-success.svg"
alt="Copied"
width={24}
height={24} />
: <Image
src="share.svg"
alt="Copy Link"
width={24}
height={24} />
} }
</Button>
<Button className='bg-transparent hover:bg-yellow-500' onClick={() => { function convertColorToTextClass(color: string) {
props.setShowModal(false); if (color === 'red') return `text-red-500`;
}}> if (color === 'yellow') return `text-yellow-500`;
<Image if (color === 'green') return `text-green-500`;
src="close.svg" if (color === 'blue') return `text-blue-500`;
alt="Close" if (color === 'orange') return `text-orange-500`;
width={24} if (color === 'purple') return `text-purple-500`;
height={24} /> if (color === 'pink') return `text-pink-500`;
</Button> if (color === 'teal') return `text-teal-500`;
</div> if (color === 'cyan') return `text-cyan-500`;
</div> if (color === 'lime') return `text-lime-500`;
<p>{props.data.personality}</p> if (color === 'indigo') return `text-indigo-500`;
<div className={styles.agentInfo}> if (color === 'fuschia') return `text-fuschia-500`;
<Button className='bg-yellow-400 hover:bg-yellow-500' onClick={() => openChat(props.data.slug, props.userData)}> if (color === 'rose') return `text-rose-500`;
Chat if (color === 'sky') return `text-sky-500`;
</Button> if (color === 'amber') return `text-amber-500`;
</div> if (color === 'emerald') return `text-emerald-500`;
</div> return `text-gray-500`;
</div>
</div>
);
} }
function AgentCard(props: AgentCardProps) { function AgentCard(props: AgentCardProps) {
const searchParams = new URLSearchParams(window.location.search); const searchParams = new URLSearchParams(window.location.search);
const agentSlug = searchParams.get('agent'); const agentSlug = searchParams.get('agent');
const [showModal, setShowModal] = useState(agentSlug === props.data.slug); const [showModal, setShowModal] = useState(agentSlug === props.data.slug);
const [showLoginPrompt, setShowLoginPrompt] = useState(false);
const userData = props.userProfile; 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}`); window.history.pushState({}, `Khoj AI - Agent ${props.data.slug}`, `/agents?agent=${props.data.slug}`);
} }
const stylingString = convertColorToClass(props.data.color);
return ( return (
<div className={styles.agent}> <Card className='shadow-md bg-secondary rounded-lg hover:shadow-lg'>
{ {
showModal && <AgentModal data={props.data} setShowModal={setShowModal} userData={userData} /> showLoginPrompt &&
<LoginPrompt
loginRedirectMessage={`Sign in to start chatting with ${props.data.name}`}
onOpenChange={setShowLoginPrompt} />
} }
<Link href={`/agent/${props.data.slug}`}> <CardHeader>
<div className={styles.agentAvatar}> <CardTitle>
<Image {
!props.isMobileWidth ?
<Dialog
open={showModal}
onOpenChange={() => {
setShowModal(!showModal);
window.history.pushState({}, `Khoj AI - Agents`, `/agents`);
}}>
<DialogTrigger>
<div className='flex items-center'>
{
getIconFromIconName(props.data.icon, props.data.color) || <Image
src={props.data.avatar} src={props.data.avatar}
alt={props.data.name} alt={props.data.name}
width={50} width={50}
height={50} height={50}
/> />
}
{props.data.name}
</div> </div>
</Link> </DialogTrigger>
<div className={styles.agentInfo}> <DialogContent className='whitespace-pre-line'>
<button className={styles.infoButton} onClick={() => { <DialogHeader>
setShowModal(true); <div className='flex items-center'>
} }> {
<h2>{props.data.name}</h2> getIconFromIconName(props.data.icon, props.data.color) || <Image
</button> src={props.data.avatar}
</div> alt={props.data.name}
<div className={styles.agentInfo}> width={32}
<Button height={50}
className='bg-yellow-400 hover:bg-yellow-500'
onClick={() => openChat(props.data.slug, userData)}>
<Image
src="send.svg"
alt="Chat"
width={40}
height={40}
/> />
</Button> }
{props.data.name}
</div> </div>
</DialogHeader>
{props.data.personality}
<DialogFooter>
<Button
className={`${stylingString}`}
onClick={() => {
openChat(props.data.slug, userData);
setShowModal(false);
}}>
Chat
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
:
<Drawer
open={showModal}
onOpenChange={(open) => {
setShowModal(open);
window.history.pushState({}, `Khoj AI - Agents`, `/agents`);
}}>
<DrawerTrigger>
<div className='flex items-center'>
{
getIconFromIconName(props.data.icon, props.data.color) || <Image
src={props.data.avatar}
alt={props.data.name}
width={50}
height={50}
/>
}
{props.data.name}
</div>
</DrawerTrigger>
<DrawerContent className='whitespace-pre-line p-2'>
<DrawerHeader>
<DrawerTitle>{props.data.name}</DrawerTitle>
<DrawerDescription>Full Prompt</DrawerDescription>
</DrawerHeader>
{props.data.personality}
<DrawerFooter>
<DrawerClose>
Done
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
}
</CardTitle>
</CardHeader>
<CardContent>
<div className={styles.agentPersonality}> <div className={styles.agentPersonality}>
<button className={styles.infoButton} onClick={() => setShowModal(true)}> <button className={styles.infoButton} onClick={() => setShowModal(true)}>
<p>{props.data.personality}</p> <p>{props.data.personality}</p>
</button> </button>
</div> </div>
</div> </CardContent>
); <CardFooter className='flex justify-end'>
{
props.userProfile ?
<Button
className={`${stylingString}`}
onClick={() => openChat(props.data.slug, userData)}>
<PaperPlaneTilt className='w-6 h-6' />
</Button>
:
<Button
className={`${stylingString}`}
onClick={() => setShowLoginPrompt(true)}>
<PaperPlaneTilt className='w-6 h-6' />
</Button>
}
</CardFooter>
</Card>
)
} }
export default function Agents() { export default function Agents() {
const { data, error } = useSWR<AgentData[]>('agents', agentsFetcher, { revalidateOnFocus: false }); const { data, error } = useSWR<AgentData[]>('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) { if (error) {
return ( return (
@ -193,7 +310,7 @@ export default function Agents() {
Talk to a Specialized Agent Talk to a Specialized Agent
</div> </div>
<div className={styles.agentList}> <div className={styles.agentList}>
Loading agents... <InlineLoading /> booting up your agents
</div> </div>
</main> </main>
); );
@ -201,12 +318,39 @@ export default function Agents() {
return ( return (
<main className={styles.main}> <main className={styles.main}>
<div className={`${styles.titleBar} text-5xl`}> <h3
Talk to a Specialized Agent className='text-xl py-4'>
</div> Agents
</h3>
{
showLoginPrompt &&
<LoginPrompt
loginRedirectMessage="Sign in to start chatting with a specialized agent"
onOpenChange={setShowLoginPrompt} />
}
<Alert>
<Info className="h-4 w-4" />
<AlertTitle>How this works</AlertTitle>
<AlertDescription>
You can use any of these specialized agents to tailor to tune your conversation to your needs.
{
!authenticatedData &&
<>
<div className='mt-3' />
<Button onClick={() => setShowLoginPrompt(true)}>
<UserCircle className='w-4 h-4 mr-2' /> Sign In
</Button>
</>
}
<div className='mt-3' />
<strong>Coming Soon:</strong> Support for making your own agents.
</AlertDescription>
</Alert>
<div className={styles.agentList}> <div className={styles.agentList}>
{data.map(agent => ( {data.map(agent => (
<AgentCard key={agent.slug} data={agent} userProfile={userData} /> <AgentCard key={agent.slug} data={agent} userProfile={authenticatedData} isMobileWidth={isMobileWidth} />
))} ))}
</div> </div>
</main> </main>

View file

@ -1,9 +1,10 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import NavMenu from '../components/navMenu/navMenu'; import NavMenu from '../components/navMenu/navMenu';
import styles from './automationsLayout.module.css'; import styles from './automationsLayout.module.css';
import { Toaster } from "@/components/ui/toaster"; import { Toaster } from "@/components/ui/toaster";
import "../globals.css";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Khoj AI - Automations", title: "Khoj AI - Automations",

View file

@ -40,6 +40,7 @@ export default function NavMenu(props: NavMenuProps) {
const [isMobileWidth, setIsMobileWidth] = useState(false); const [isMobileWidth, setIsMobileWidth] = useState(false);
const [darkMode, setDarkMode] = useState(false); const [darkMode, setDarkMode] = useState(false);
const [initialLoadDone, setInitialLoadDone] = useState(false);
useEffect(() => { useEffect(() => {
setIsMobileWidth(window.innerWidth < 768); setIsMobileWidth(window.innerWidth < 768);
@ -62,13 +63,26 @@ export default function NavMenu(props: NavMenuProps) {
if (localStorage.getItem('theme') === 'dark') { if (localStorage.getItem('theme') === 'dark') {
document.documentElement.classList.add('dark'); document.documentElement.classList.add('dark');
setDarkMode(true); setDarkMode(true);
} else if (mq.matches) { } 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'); document.documentElement.classList.add('dark');
setDarkMode(true); setDarkMode(true);
} }
}
setInitialLoadDone(true);
}, []); }, []);
useEffect(() => { useEffect(() => {
if (!initialLoadDone) return;
toggleDarkMode(darkMode); toggleDarkMode(darkMode);
}, [darkMode]); }, [darkMode]);
@ -128,17 +142,17 @@ export default function NavMenu(props: NavMenuProps) {
: :
<Menubar className='items-top inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground'> <Menubar className='items-top inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground'>
<MenubarMenu> <MenubarMenu>
<Link href='/chat' target="_blank" rel="noreferrer" className={`${props.selected.toLowerCase() === 'chat' ? styles.selected : ''} hover:bg-background`}> <Link href='/chat' className={`${props.selected.toLowerCase() === 'chat' ? styles.selected : ''} hover:bg-background`}>
<MenubarTrigger>Chat</MenubarTrigger> <MenubarTrigger>Chat</MenubarTrigger>
</Link> </Link>
</MenubarMenu> </MenubarMenu>
<MenubarMenu> <MenubarMenu>
<Link href='/agents' target="_blank" rel="noreferrer" className={`${props.selected.toLowerCase() === 'agent' ? styles.selected : ''} hover:bg-background`}> <Link href='/agents' className={`${props.selected.toLowerCase() === 'agent' ? styles.selected : ''} hover:bg-background`}>
<MenubarTrigger>Agents</MenubarTrigger> <MenubarTrigger>Agents</MenubarTrigger>
</Link> </Link>
</MenubarMenu> </MenubarMenu>
<MenubarMenu> <MenubarMenu>
<Link href='/automations' target="_blank" rel="noreferrer" className={`${props.selected.toLowerCase() === 'automations' ? styles.selected : ''} hover:bg-background`}> <Link href='/automations' className={`${props.selected.toLowerCase() === 'automations' ? styles.selected : ''} hover:bg-background`}>
<MenubarTrigger>Automations</MenubarTrigger> <MenubarTrigger>Automations</MenubarTrigger>
</Link> </Link>
</MenubarMenu> </MenubarMenu>

View file

@ -39,17 +39,22 @@
"@types/dompurify": "^3.0.5", "@types/dompurify": "^3.0.5",
"@types/katex": "^0.16.7", "@types/katex": "^0.16.7",
"@types/markdown-it": "^14.1.1", "@types/markdown-it": "^14.1.1",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0", "cmdk": "^1.0.0",
"cronstrue": "^2.50.0",
"dompurify": "^3.1.6", "dompurify": "^3.1.6",
"eslint": "^8",
"eslint-config-next": "14.2.3",
"clsx": "^2.1.1",
"cronstrue": "^2.50.0",
"katex": "^0.16.10", "katex": "^0.16.10",
"lucide-react": "^0.397.0", "lucide-react": "^0.397.0",
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",
"markdown-it-highlightjs": "^4.1.0", "markdown-it-highlightjs": "^4.1.0",
"next": "14.2.3", "next": "14.2.3",
"nodemon": "^3.1.3",
"postcss": "^8.4.38", "postcss": "^8.4.38",
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18",
@ -58,6 +63,7 @@
"swr": "^2.2.5", "swr": "^2.2.5",
"tailwind-merge": "^2.3.0", "tailwind-merge": "^2.3.0",
"tailwindcss": "^3.4.4", "tailwindcss": "^3.4.4",
"typescript": "^5",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"vaul": "^0.9.1", "vaul": "^0.9.1",
"zod": "^3.23.8" "zod": "^3.23.8"

View file

@ -1140,10 +1140,10 @@
resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-2.0.0.tgz#d43878b5b20222682163ae6f897b20447233bdfd" resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-2.0.0.tgz#d43878b5b20222682163ae6f897b20447233bdfd"
integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg== integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==
"@types/node@^20": "@types/node@20.14.10":
version "20.14.2" version "20.14.10"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.2.tgz#a5f4d2bcb4b6a87bffcaa717718c5a0f208f4a18" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.10.tgz#a1a218290f1b6428682e3af044785e5874db469a"
integrity sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q== integrity sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==
dependencies: dependencies:
undici-types "~5.26.4" undici-types "~5.26.4"

View file

@ -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,
),
),
]

View file

@ -103,6 +103,39 @@ class VoiceModelOption(BaseModel):
class Agent(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( creator = models.ForeignKey(
KhojUser, on_delete=models.CASCADE, default=None, null=True, blank=True KhojUser, on_delete=models.CASCADE, default=None, null=True, blank=True
) # Creator will only be null when the agents are managed by admin ) # 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) managed_by_admin = models.BooleanField(default=False)
chat_model = models.ForeignKey(ChatModelOptions, on_delete=models.CASCADE) chat_model = models.ForeignKey(ChatModelOptions, on_delete=models.CASCADE)
slug = models.CharField(max_length=200) 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): class ProcessLock(BaseModel):

View file

@ -34,6 +34,8 @@ async def all_agents(
"public": agent.public, "public": agent.public,
"creator": agent.creator.username if agent.creator else None, "creator": agent.creator.username if agent.creator else None,
"managed_by_admin": agent.managed_by_admin, "managed_by_admin": agent.managed_by_admin,
"color": agent.style_color,
"icon": agent.style_icon,
} }
) )

View file

@ -212,6 +212,8 @@ def chat_history(
"name": conversation.agent.name, "name": conversation.agent.name,
"avatar": conversation.agent.avatar, "avatar": conversation.agent.avatar,
"isCreator": conversation.agent.creator == user, "isCreator": conversation.agent.creator == user,
"color": conversation.agent.style_color,
"icon": conversation.agent.style_icon,
"persona": conversation.agent.personality, "persona": conversation.agent.personality,
} }
@ -267,6 +269,8 @@ def get_shared_chat(
"name": conversation.agent.name, "name": conversation.agent.name,
"avatar": conversation.agent.avatar, "avatar": conversation.agent.avatar,
"isCreator": conversation.agent.creator == user, "isCreator": conversation.agent.creator == user,
"color": conversation.agent.style_color,
"icon": conversation.agent.style_icon,
"persona": conversation.agent.personality, "persona": conversation.agent.personality,
} }

2
yarn.lock Normal file
View file

@ -0,0 +1,2 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1