onClickMessage(event, props.chatMessage, props.setReferencePanelData, props.setShowReferencePanel) : undefined}>
{/*
- {/* Add a copy button, thumbs up, and thumbs down buttons */}
-
-
- {renderTimeStamp(props.chatMessage.created)}
-
-
- {
- referencesValid &&
-
-
-
- }
-
diff --git a/src/interface/web/app/components/loading/loading.module.css b/src/interface/web/app/components/loading/loading.module.css
new file mode 100644
index 00000000..e22778a4
--- /dev/null
+++ b/src/interface/web/app/components/loading/loading.module.css
@@ -0,0 +1,24 @@
+/* HTML:
*/
+.loader {
+ --c: conic-gradient(from -90deg, hsla(var(--secondary)) 90deg, #0000 0);
+ background: var(--c), var(--c);
+ background-size: 40% 40%;
+ animation: l19 1s infinite alternate;
+}
+
+@keyframes l19 {
+
+ 0%,
+ 10% {
+ background-position: 0 0, 0 calc(100%/3)
+ }
+
+ 50% {
+ background-position: 0 0, calc(100%/3) calc(100%/3)
+ }
+
+ 90%,
+ 100% {
+ background-position: 0 0, calc(100%/3) 0
+ }
+}
diff --git a/src/interface/web/app/components/loading/loading.tsx b/src/interface/web/app/components/loading/loading.tsx
new file mode 100644
index 00000000..709a1eaa
--- /dev/null
+++ b/src/interface/web/app/components/loading/loading.tsx
@@ -0,0 +1,26 @@
+import styles from './loading.module.css';
+
+export default function Loading() {
+ // return (
+ //
+ //
+ // Loading...
+ //
+ //
+ //
+ //
+ //
+ //
+ // )
+ return (
+
+ );
+
+ return (
+
+
+ Loading...
+
+
+ )
+}
diff --git a/src/interface/web/app/components/navMenu/navMenu.module.css b/src/interface/web/app/components/navMenu/navMenu.module.css
index 45ca0d28..50998706 100644
--- a/src/interface/web/app/components/navMenu/navMenu.module.css
+++ b/src/interface/web/app/components/navMenu/navMenu.module.css
@@ -11,26 +11,19 @@ menu.menu a {
gap: 4px;
}
-menu.menu a.selected {
- background-color: var(--primary-hover);
-}
-
-menu.menu a:hover {
- background-color: var(--primary-hover);
-}
-
-menu.menu {
- display: flex;
- justify-content: space-around;
- padding: 0;
- margin: 0;
+a.selected {
+ background-color: hsl(var(--accent));
}
div.titleBar {
- display: grid;
- grid-template-columns: 1fr auto;
- padding: 16px 0;
- margin: auto;
+ display: flex;
+ padding-left: 12px;
+ padding-right: 32px;
+ padding-top: 16px;
+ padding-bottom: 16px;
+ justify-content: space-between;
+ align-content: space-evenly;
+ align-items: start;
}
div.titleBar menu {
@@ -75,14 +68,6 @@ div.settingsMenuOptions {
border-radius: 8px;
}
-div.settingsMenuOptions a {
- padding: 4px;
-}
-
-div.settingsMenuUsername {
- font-weight: bold;
-}
-
@media screen and (max-width: 600px) {
menu.menu span {
display: none;
@@ -91,4 +76,8 @@ div.settingsMenuUsername {
div.settingsMenuOptions {
right: 4px;
}
+
+ div.titleBar {
+ padding: 8px;
+ }
}
diff --git a/src/interface/web/app/components/navMenu/navMenu.tsx b/src/interface/web/app/components/navMenu/navMenu.tsx
index 7d2522d3..5749e90c 100644
--- a/src/interface/web/app/components/navMenu/navMenu.tsx
+++ b/src/interface/web/app/components/navMenu/navMenu.tsx
@@ -1,106 +1,125 @@
'use client'
import styles from './navMenu.module.css';
-import Image from 'next/image';
import Link from 'next/link';
import { useAuthenticatedData, UserProfile } from '@/app/common/auth';
-import { useState } from 'react';
+import { useState, useEffect } from 'react';
+
+import {
+ Menubar,
+ MenubarContent,
+ MenubarItem,
+ MenubarMenu,
+ MenubarSeparator,
+ MenubarTrigger,
+} from "@/components/ui/menubar";
+
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
interface NavMenuProps {
selected: string;
+ showLogo?: boolean;
+ title?: string;
}
-function SettingsMenu(props: UserProfile) {
- const [showSettings, setShowSettings] = useState(false);
-
- return (
-
-
setShowSettings(!showSettings)}>
-
-
- {showSettings && (
-
-
{props.username}
-
- Settings
-
-
- Github
-
-
- Help
-
-
- Logout
-
-
- )}
-
- );
-}
export default function NavMenu(props: NavMenuProps) {
- let userData = useAuthenticatedData();
+ const userData = useAuthenticatedData();
+ const [displayTitle, setDisplayTitle] = useState
(props.title || props.selected.toUpperCase());
+
+ const [isMobileWidth, setIsMobileWidth] = useState(false);
+
+ useEffect(() => {
+ setIsMobileWidth(window.innerWidth < 768);
+ setDisplayTitle(props.title || props.selected.toUpperCase());
+
+ }, [props.title]);
+
return (
-
-
-
-
+
+ {displayTitle &&
{displayTitle}
}
+
+ {
+ isMobileWidth ?
+
+ =
+
+
+ Chat
+
+
+ Agents
+
+
+ Automations
+
+ {userData && <>
+
+ Profile
+
+ Settings
+
+
+ Help
+
+
+ Logout
+
+ >}
+
+
+ :
+
+
+
+ Chat
+
+
+
+
+ Agents
+
+
+
+
+ Automations
+
+
+ {userData &&
+
+ Profile
+
+
+
+ Settings
+
+
+
+
+
+ Help
+
+
+
+
+
+ Logout
+
+
+
+
+ }
+
+ }
)
}
diff --git a/src/interface/web/app/components/referencePanel/referencePanel.module.css b/src/interface/web/app/components/referencePanel/referencePanel.module.css
index 52f7445e..a6179e46 100644
--- a/src/interface/web/app/components/referencePanel/referencePanel.module.css
+++ b/src/interface/web/app/components/referencePanel/referencePanel.module.css
@@ -8,7 +8,6 @@ div.panel {
}
div.panel a {
- color: var(--intense-green);
text-decoration: underline;
}
@@ -29,3 +28,13 @@ div.singleReference {
background-color: var(--frosted-background-color);
margin-top: 8px;
}
+
+@media screen and (max-width: 768px) {
+ div.panel {
+ padding: 0.5rem;
+ }
+
+ div.singleReference {
+ padding: 4px;
+ }
+}
diff --git a/src/interface/web/app/components/referencePanel/referencePanel.tsx b/src/interface/web/app/components/referencePanel/referencePanel.tsx
index 93ea439d..76e7cd7d 100644
--- a/src/interface/web/app/components/referencePanel/referencePanel.tsx
+++ b/src/interface/web/app/components/referencePanel/referencePanel.tsx
@@ -26,7 +26,7 @@ export function hasValidReferences(referencePanelData: SingleChatMessage | null)
(referencePanelData.onlineContext && Object.keys(referencePanelData.onlineContext).length > 0 &&
Object.values(referencePanelData.onlineContext).some(
(onlineContextData) =>
- (onlineContextData.webpages && onlineContextData.webpages.length > 0)|| onlineContextData.answerBox || onlineContextData.peopleAlsoAsk || onlineContextData.knowledgeGraph))
+ (onlineContextData.webpages && onlineContextData.webpages.length > 0) || onlineContextData.answerBox || onlineContextData.peopleAlsoAsk || onlineContextData.knowledgeGraph || onlineContextData.organic ))
)
);
}
@@ -98,6 +98,7 @@ function OnlineReferences(props: { onlineContext: OnlineContextData, query: stri
const answerBox = props.onlineContext.answerBox;
const peopleAlsoAsk = props.onlineContext.peopleAlsoAsk;
const knowledgeGraph = props.onlineContext.knowledgeGraph;
+ const organic = props.onlineContext.organic;
return (
@@ -126,6 +127,24 @@ function OnlineReferences(props: { onlineContext: OnlineContextData, query: stri
)
}
+ {
+ organic && organic.map((organicData, index) => {
+ return (
+
+
+
+
+ {organicData.snippet}
+
+
+
+ )
+ })
+ }
{
peopleAlsoAsk && peopleAlsoAsk.map((people, index) => {
return (
diff --git a/src/interface/web/app/components/sidePanel/chatHistorySidePanel.tsx b/src/interface/web/app/components/sidePanel/chatHistorySidePanel.tsx
index a6561423..3959ce45 100644
--- a/src/interface/web/app/components/sidePanel/chatHistorySidePanel.tsx
+++ b/src/interface/web/app/components/sidePanel/chatHistorySidePanel.tsx
@@ -5,69 +5,254 @@ import styles from "./sidePanel.module.css";
import { useEffect, useState } from "react";
import { UserProfile } from "@/app/common/auth";
+import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
import Link from "next/link";
+import useSWR from "swr";
+
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+
+import { ScrollArea } from "@/components/ui/scroll-area";
interface ChatHistory {
conversation_id: string;
slug: string;
+ agent_name: string;
+ agent_avatar: string;
+ compressed: boolean;
}
-function ChatSession(prop: ChatHistory) {
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+
+import { Pencil, Trash, Share } from "@phosphor-icons/react";
+
+import { Button } from "@/components/ui/button";
+
+interface GroupedChatHistory {
+ [key: string]: ChatHistory[];
+}
+
+function renameConversation(conversationId: string, newTitle: string) {
+ const editUrl = `/api/chat/title?client=web&conversation_id=${conversationId}&title=${newTitle}`;
+
+ fetch(editUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ })
+ .then(response => response.json())
+ .then(data => {
+ console.log(data);
+ })
+ .catch(err => {
+ console.error(err);
+ return;
+ });
+}
+
+function shareConversation(conversationId: string) {
+ const shareUrl = `/api/chat/share?client=web&conversation_id=${conversationId}`;
+
+ fetch(shareUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ })
+ .then(response => response.json())
+ .then(data => {
+ console.log(data);
+ })
+ .catch(err => {
+ console.error(err);
+ return;
+ });
+}
+
+function deleteConversation(conversationId: string) {
+ const deleteUrl = `/api/chat/delete?client=web&conversation_id=${conversationId}`;
+
+ fetch(deleteUrl, {
+ method: 'DELETE',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ })
+ .then(response => response.json())
+ .then(data => {
+ console.log(data);
+ })
+ .catch(err => {
+ console.error(err);
+ return;
+ });
+}
+
+interface ChatSessionActionMenuProps {
+ conversationId: string;
+}
+
+function ChatSessionActionMenu(props: ChatSessionActionMenuProps) {
+
return (
-
-
-
{prop.slug || "New Conversation 🌱"}
+
+ :
+
+
+ renameConversation(props.conversationId, 'New Title')}>
+ Rename
+
+
+
+ shareConversation(props.conversationId)}>
+ Share
+
+
+
+ deleteConversation(props.conversationId)}>
+ Delete
+
+
+
+
+ )
+}
+
+function ChatSession(props: ChatHistory) {
+ const [isHovered, setIsHovered] = useState(false);
+
+ return (
+
setIsHovered(true)}
+ onMouseLeave={() => setIsHovered(false)}
+ key={props.conversation_id}
+ className={`${styles.session} ${props.compressed ? styles.compressed : '!max-w-full'} ${isHovered ? `${styles.sessionHover}` : ''}`}>
+
+
{props.slug || "New Conversation 🌱"}
+
);
}
interface ChatSessionsModalProps {
- data: ChatHistory[];
- setIsExpanded: React.Dispatch
>;
+ data: GroupedChatHistory | null;
}
-function ChatSessionsModal({data, setIsExpanded}: ChatSessionsModalProps) {
+function ChatSessionsModal({ data }: ChatSessionsModalProps) {
return (
-
-
- {data.map((chatHistory) => (
-
- ))}
- setIsExpanded(false)}>
- Close
-
-
-
+
);
}
-export default function SidePanel() {
+const fetchChatHistory = async (url: string) => {
+ const response = await fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+ return response.json();
+};
+
+export const useChatHistoryRecentFetchRequest = (url: string) => {
+ const { data, error } = useSWR(url, fetchChatHistory);
+
+ return {
+ data,
+ isLoading: !error && !data,
+ isError: error,
+ };
+};
+
+interface SidePanelProps {
+ webSocketConnected?: boolean;
+}
+
+
+export default function SidePanel(props: SidePanelProps) {
const [data, setData] = useState(null);
- const [dataToShow, setDataToShow] = useState(null);
+ const [organizedData, setOrganizedData] = useState(null);
+ const [subsetOrganizedData, setSubsetOrganizedData] = useState(null);
const [isLoading, setLoading] = useState(true)
const [enabled, setEnabled] = useState(false);
- const [isExpanded, setIsExpanded] = useState(false);
const [userProfile, setUserProfile] = useState(null);
- useEffect(() => {
+ const { data: chatHistory } = useChatHistoryRecentFetchRequest('/api/chat/sessions');
- fetch('/api/chat/sessions', { method: 'GET' })
- .then(response => response.json())
- .then((data: ChatHistory[]) => {
- setLoading(false);
- // Render chat options, if any
- if (data) {
- setData(data);
- setDataToShow(data.slice(0, 5));
+ useEffect(() => {
+ if (chatHistory) {
+ setData(chatHistory);
+
+ const groupedData: GroupedChatHistory = {};
+ const subsetOrganizedData: GroupedChatHistory = {};
+ let numAdded = 0;
+ chatHistory.forEach((chatHistory) => {
+ if (!groupedData[chatHistory.agent_name]) {
+ groupedData[chatHistory.agent_name] = [];
+ }
+ groupedData[chatHistory.agent_name].push(chatHistory);
+
+ // Add to subsetOrganizedData if less than 8
+ if (numAdded < 8) {
+ if (!subsetOrganizedData[chatHistory.agent_name]) {
+ subsetOrganizedData[chatHistory.agent_name] = [];
+ }
+ subsetOrganizedData[chatHistory.agent_name].push(chatHistory);
+ numAdded++;
}
- })
- .catch(err => {
- console.error(err);
- return;
});
+ setSubsetOrganizedData(subsetOrganizedData);
+ setOrganizedData(groupedData);
+ }
+ }, [chatHistory]);
+
+ useEffect(() => {
fetch('/api/v1/user', { method: 'GET' })
.then(response => response.json())
@@ -78,66 +263,79 @@ export default function SidePanel() {
console.error(err);
return;
});
- }, []);
+ }, []);
- return (
-
+ return (
+
{
enabled ?
-
+
-
- { userProfile &&
-
-

-
{userProfile?.username}
-
- }
-
setEnabled(false)}>
{/* Push Close Icon */}
-
Recent Conversations
-
-
- {dataToShow && dataToShow.map((chatHistory) => (
-
- ))}
+
+
+ {subsetOrganizedData && Object.keys(subsetOrganizedData).map((agentName) => (
+
+
+
+ {agentName}
+
+ {subsetOrganizedData[agentName].map((chatHistory) => (
+
+ ))}
+
+ ))}
+
+
{
(data && data.length > 5) && (
- (isExpanded) ?
-
- :
-
{
- setIsExpanded(true);
- }}>
- Show All
-
+
)
}
+ {userProfile &&
+
+
+
+
+ {userProfile.username[0]}
+
+
+
+
{userProfile?.username}
+ {/* Connected Indicator */}
+
+
+
+ {props.webSocketConnected ? "Connected" : "Disconnected"}
+
+
+
+
+ }
- :
+ :
- { userProfile &&
-
-

-
- }
+ {userProfile &&
+
+
+
+
+ {userProfile.username[0]}
+
+
+
+ }
setEnabled(true)}>
{/* Pull Open Icon */}
@@ -146,6 +344,6 @@ export default function SidePanel() {
}
-
- );
+
+ );
}
diff --git a/src/interface/web/app/components/sidePanel/sidePanel.module.css b/src/interface/web/app/components/sidePanel/sidePanel.module.css
index ea675fa0..f8c33aee 100644
--- a/src/interface/web/app/components/sidePanel/sidePanel.module.css
+++ b/src/interface/web/app/components/sidePanel/sidePanel.module.css
@@ -2,9 +2,27 @@ div.session {
padding: 0.5rem;
margin-bottom: 0.25rem;
border-radius: 0.5rem;
- color: var(--main-text-color);
cursor: pointer;
max-width: 14rem;
+ font-size: medium;
+ display: grid;
+ grid-template-columns: minmax(auto, 350px) 1fr;
+}
+
+div.compressed {
+ grid-template-columns: minmax(auto, 12rem) 1fr 1fr;
+}
+
+div.sessionHover {
+ background-color: var(--secondary-accent);
+}
+
+div.session:hover {
+ background-color: var(--secondary-accent);
+}
+
+div.session a {
+ text-decoration: none;
}
button.button {
@@ -17,9 +35,6 @@ button.button {
}
button.showMoreButton {
- background: var(--intense-green);
- border: none;
- color: var(--frosted-background-color);
border-radius: 0.5rem;
padding: 8px;
}
@@ -28,9 +43,8 @@ div.panel {
display: grid;
grid-auto-flow: row;
padding: 1rem;
- border-radius: 1rem;
- background-color: var(--calm-blue);
- color: var(--main-text-color);
+ background-color: hsla(var(--primary-foreground));
+ background-color: var(--secondary-background-color);
height: 100%;
overflow-y: auto;
max-width: auto;
@@ -47,14 +61,12 @@ div.collapsed {
grid-template-columns: 1fr;
}
-div.session:hover {
- background-color: var(--calmer-blue);
-}
-
p.session {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
+ text-align: left;
+ font-size: small;
}
div.header {
@@ -62,10 +74,18 @@ div.header {
grid-template-columns: 1fr auto;
}
-img.profile {
- width: 24px;
- height: 24px;
- border-radius: 50%;
+div.profile {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ gap: 1rem;
+ align-items: center;
+ align-self: flex-end;
+ margin-top: auto;
+}
+
+div.panelWrapper {
+ display: flex;
+ flex-direction: column;
}
@@ -96,3 +116,13 @@ div.modalSessionsList div.session {
max-width: 100%;
text-overflow: ellipsis;
}
+
+@media screen and (max-width: 768px) {
+ div.panel {
+ padding: 0.5rem;
+ }
+
+ div.singleReference {
+ padding: 4px;
+ }
+}
diff --git a/src/interface/web/app/globals.css b/src/interface/web/app/globals.css
index be5c7aeb..dfebb72a 100644
--- a/src/interface/web/app/globals.css
+++ b/src/interface/web/app/globals.css
@@ -26,7 +26,14 @@
--ring: 209.1 100% 40.8%;
--radius: 0.5rem;
--font-family: "Noto Sans", "Noto Sans Arabic", sans-serif !important;
+
+ /* Khoj Custom Colors */
--primary-hover: #fee285;
+ --frosted-background-color: #F5F3F2;
+ --secondary-background-color: #F7F7F5;
+ --secondary-accent: #EDEDED;
+ --khoj-orange: #FFE7D1;
+ --border-color: #e2e2e2;
}
.dark {
diff --git a/src/interface/web/components/ui/avatar.tsx b/src/interface/web/components/ui/avatar.tsx
new file mode 100644
index 00000000..51e507ba
--- /dev/null
+++ b/src/interface/web/components/ui/avatar.tsx
@@ -0,0 +1,50 @@
+"use client"
+
+import * as React from "react"
+import * as AvatarPrimitive from "@radix-ui/react-avatar"
+
+import { cn } from "@/lib/utils"
+
+const Avatar = React.forwardRef<
+ React.ElementRef
,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+Avatar.displayName = AvatarPrimitive.Root.displayName
+
+const AvatarImage = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AvatarImage.displayName = AvatarPrimitive.Image.displayName
+
+const AvatarFallback = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
+
+export { Avatar, AvatarImage, AvatarFallback }
diff --git a/src/interface/web/components/ui/dropdown-menu.tsx b/src/interface/web/components/ui/dropdown-menu.tsx
new file mode 100644
index 00000000..f69a0d64
--- /dev/null
+++ b/src/interface/web/components/ui/dropdown-menu.tsx
@@ -0,0 +1,200 @@
+"use client"
+
+import * as React from "react"
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
+import { Check, ChevronRight, Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const DropdownMenu = DropdownMenuPrimitive.Root
+
+const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
+
+const DropdownMenuGroup = DropdownMenuPrimitive.Group
+
+const DropdownMenuPortal = DropdownMenuPrimitive.Portal
+
+const DropdownMenuSub = DropdownMenuPrimitive.Sub
+
+const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
+
+const DropdownMenuSubTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, children, ...props }, ref) => (
+
+ {children}
+
+
+))
+DropdownMenuSubTrigger.displayName =
+ DropdownMenuPrimitive.SubTrigger.displayName
+
+const DropdownMenuSubContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DropdownMenuSubContent.displayName =
+ DropdownMenuPrimitive.SubContent.displayName
+
+const DropdownMenuContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, sideOffset = 4, ...props }, ref) => (
+
+
+
+))
+DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
+
+const DropdownMenuItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+
+))
+DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
+
+const DropdownMenuCheckboxItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, checked, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+DropdownMenuCheckboxItem.displayName =
+ DropdownMenuPrimitive.CheckboxItem.displayName
+
+const DropdownMenuRadioItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
+
+const DropdownMenuLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+
+))
+DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
+
+const DropdownMenuSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
+
+const DropdownMenuShortcut = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => {
+ return (
+
+ )
+}
+DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
+
+export {
+ DropdownMenu,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuGroup,
+ DropdownMenuPortal,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuRadioGroup,
+}
diff --git a/src/interface/web/components/ui/menubar.tsx b/src/interface/web/components/ui/menubar.tsx
new file mode 100644
index 00000000..5586fa9b
--- /dev/null
+++ b/src/interface/web/components/ui/menubar.tsx
@@ -0,0 +1,236 @@
+"use client"
+
+import * as React from "react"
+import * as MenubarPrimitive from "@radix-ui/react-menubar"
+import { Check, ChevronRight, Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const MenubarMenu = MenubarPrimitive.Menu
+
+const MenubarGroup = MenubarPrimitive.Group
+
+const MenubarPortal = MenubarPrimitive.Portal
+
+const MenubarSub = MenubarPrimitive.Sub
+
+const MenubarRadioGroup = MenubarPrimitive.RadioGroup
+
+const Menubar = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+Menubar.displayName = MenubarPrimitive.Root.displayName
+
+const MenubarTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName
+
+const MenubarSubTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, children, ...props }, ref) => (
+
+ {children}
+
+
+))
+MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName
+
+const MenubarSubContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName
+
+const MenubarContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(
+ (
+ { className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
+ ref
+ ) => (
+
+
+
+ )
+)
+MenubarContent.displayName = MenubarPrimitive.Content.displayName
+
+const MenubarItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+
+))
+MenubarItem.displayName = MenubarPrimitive.Item.displayName
+
+const MenubarCheckboxItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, checked, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName
+
+const MenubarRadioItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName
+
+const MenubarLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+
+))
+MenubarLabel.displayName = MenubarPrimitive.Label.displayName
+
+const MenubarSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName
+
+const MenubarShortcut = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => {
+ return (
+
+ )
+}
+MenubarShortcut.displayname = "MenubarShortcut"
+
+export {
+ Menubar,
+ MenubarMenu,
+ MenubarTrigger,
+ MenubarContent,
+ MenubarItem,
+ MenubarSeparator,
+ MenubarLabel,
+ MenubarCheckboxItem,
+ MenubarRadioGroup,
+ MenubarRadioItem,
+ MenubarPortal,
+ MenubarSubContent,
+ MenubarSubTrigger,
+ MenubarGroup,
+ MenubarSub,
+ MenubarShortcut,
+}
diff --git a/src/interface/web/components/ui/navigation-menu.tsx b/src/interface/web/components/ui/navigation-menu.tsx
new file mode 100644
index 00000000..1419f566
--- /dev/null
+++ b/src/interface/web/components/ui/navigation-menu.tsx
@@ -0,0 +1,128 @@
+import * as React from "react"
+import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
+import { cva } from "class-variance-authority"
+import { ChevronDown } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const NavigationMenu = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ {children}
+
+
+))
+NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
+
+const NavigationMenuList = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
+
+const NavigationMenuItem = NavigationMenuPrimitive.Item
+
+const navigationMenuTriggerStyle = cva(
+ "group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
+)
+
+const NavigationMenuTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ {children}{" "}
+
+
+))
+NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
+
+const NavigationMenuContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
+
+const NavigationMenuLink = NavigationMenuPrimitive.Link
+
+const NavigationMenuViewport = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+))
+NavigationMenuViewport.displayName =
+ NavigationMenuPrimitive.Viewport.displayName
+
+const NavigationMenuIndicator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+))
+NavigationMenuIndicator.displayName =
+ NavigationMenuPrimitive.Indicator.displayName
+
+export {
+ navigationMenuTriggerStyle,
+ NavigationMenu,
+ NavigationMenuList,
+ NavigationMenuItem,
+ NavigationMenuContent,
+ NavigationMenuTrigger,
+ NavigationMenuLink,
+ NavigationMenuIndicator,
+ NavigationMenuViewport,
+}
diff --git a/src/interface/web/components/ui/scroll-area.tsx b/src/interface/web/components/ui/scroll-area.tsx
new file mode 100644
index 00000000..0b4a48d8
--- /dev/null
+++ b/src/interface/web/components/ui/scroll-area.tsx
@@ -0,0 +1,48 @@
+"use client"
+
+import * as React from "react"
+import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
+
+import { cn } from "@/lib/utils"
+
+const ScrollArea = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+ {children}
+
+
+
+
+))
+ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
+
+const ScrollBar = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, orientation = "vertical", ...props }, ref) => (
+
+
+
+))
+ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
+
+export { ScrollArea, ScrollBar }
diff --git a/src/interface/web/components/ui/textarea.tsx b/src/interface/web/components/ui/textarea.tsx
new file mode 100644
index 00000000..9f9a6dc5
--- /dev/null
+++ b/src/interface/web/components/ui/textarea.tsx
@@ -0,0 +1,24 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+export interface TextareaProps
+ extends React.TextareaHTMLAttributes {}
+
+const Textarea = React.forwardRef(
+ ({ className, ...props }, ref) => {
+ return (
+
+ )
+ }
+)
+Textarea.displayName = "Textarea"
+
+export { Textarea }
diff --git a/src/interface/web/package.json b/src/interface/web/package.json
index 575fff4d..db1b0b3d 100644
--- a/src/interface/web/package.json
+++ b/src/interface/web/package.json
@@ -17,9 +17,15 @@
"windowsexport": "yarn build && xcopy out ..\\..\\khoj\\interface\\built /E /Y && yarn windowscollectstatic"
},
"dependencies": {
+ "@phosphor-icons/react": "^2.1.7",
"@radix-ui/react-alert-dialog": "^1.1.1",
+ "@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-dialog": "^1.1.1",
+ "@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-label": "^2.1.0",
+ "@radix-ui/react-menubar": "^1.1.1",
+ "@radix-ui/react-navigation-menu": "^1.2.0",
+ "@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@types/katex": "^0.16.7",
"@types/markdown-it": "^14.1.1",
diff --git a/src/interface/web/yarn.lock b/src/interface/web/yarn.lock
index 646008d0..40dfdd7a 100644
--- a/src/interface/web/yarn.lock
+++ b/src/interface/web/yarn.lock
@@ -318,6 +318,33 @@
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f"
integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==
+"@floating-ui/core@^1.0.0":
+ version "1.6.3"
+ resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.3.tgz#5e7bb92843f47fd1d8dcb9b3cc3c243aaed54f95"
+ integrity sha512-1ZpCvYf788/ZXOhRQGFxnYQOVgeU+pi0i+d0Ow34La7qjIXETi6RNswGVKkA6KcDO8/+Ysu2E/CeUmmeEBDvTg==
+ dependencies:
+ "@floating-ui/utils" "^0.2.3"
+
+"@floating-ui/dom@^1.0.0":
+ version "1.6.6"
+ resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.6.tgz#be54c1ab2d19112ad323e63dbeb08185fed0ffd3"
+ integrity sha512-qiTYajAnh3P+38kECeffMSQgbvXty2VB6rS+42iWR4FPIlZjLK84E9qtLnMTLIpPz2znD/TaFqaiavMUrS+Hcw==
+ dependencies:
+ "@floating-ui/core" "^1.0.0"
+ "@floating-ui/utils" "^0.2.3"
+
+"@floating-ui/react-dom@^2.0.0":
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.1.tgz#cca58b6b04fc92b4c39288252e285e0422291fb0"
+ integrity sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==
+ dependencies:
+ "@floating-ui/dom" "^1.0.0"
+
+"@floating-ui/utils@^0.2.3":
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.3.tgz#506fcc73f730affd093044cb2956c31ba6431545"
+ integrity sha512-XGndio0l5/Gvd6CLIABvsav9HHezgDFFhDfHk1bvLfr9ni8dojqLSvBbotJEjmIwNHL7vK4QzBJTdBRoB+c1ww==
+
"@humanwhocodes/config-array@^0.11.14":
version "0.11.14"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b"
@@ -459,11 +486,21 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
+"@phosphor-icons/react@^2.1.7":
+ version "2.1.7"
+ resolved "https://registry.yarnpkg.com/@phosphor-icons/react/-/react-2.1.7.tgz#b11a4b25849b7e3849970b688d9fe91e5d4fd8d7"
+ integrity sha512-g2e2eVAn1XG2a+LI09QU3IORLhnFNAFkNbo2iwbX6NOKSLOwvEMmTa7CgOzEbgNWR47z8i8kwjdvYZ5fkGx1mQ==
+
"@pkgjs/parseargs@^0.11.0":
version "0.11.0"
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
+"@radix-ui/number@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.1.0.tgz#1e95610461a09cdf8bb05c152e76ca1278d5da46"
+ integrity sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==
+
"@radix-ui/primitive@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.0.tgz#42ef83b3b56dccad5d703ae8c42919a68798bbe2"
@@ -481,6 +518,33 @@
"@radix-ui/react-primitive" "2.0.0"
"@radix-ui/react-slot" "1.1.0"
+"@radix-ui/react-arrow@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz#744f388182d360b86285217e43b6c63633f39e7a"
+ integrity sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==
+ dependencies:
+ "@radix-ui/react-primitive" "2.0.0"
+
+"@radix-ui/react-avatar@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-avatar/-/react-avatar-1.1.0.tgz#457c81334c93f4608df15f081e7baa286558d6a2"
+ integrity sha512-Q/PbuSMk/vyAd/UoIShVGZ7StHHeRFYU7wXmi5GV+8cLXflZAEpHL/F697H1klrzxKXNtZ97vWiC0q3RKUH8UA==
+ dependencies:
+ "@radix-ui/react-context" "1.1.0"
+ "@radix-ui/react-primitive" "2.0.0"
+ "@radix-ui/react-use-callback-ref" "1.1.0"
+ "@radix-ui/react-use-layout-effect" "1.1.0"
+
+"@radix-ui/react-collection@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.1.0.tgz#f18af78e46454a2360d103c2251773028b7724ed"
+ integrity sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==
+ dependencies:
+ "@radix-ui/react-compose-refs" "1.1.0"
+ "@radix-ui/react-context" "1.1.0"
+ "@radix-ui/react-primitive" "2.0.0"
+ "@radix-ui/react-slot" "1.1.0"
+
"@radix-ui/react-compose-refs@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz#656432461fc8283d7b591dcf0d79152fae9ecc74"
@@ -511,6 +575,11 @@
aria-hidden "^1.1.1"
react-remove-scroll "2.5.7"
+"@radix-ui/react-direction@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.1.0.tgz#a7d39855f4d077adc2a1922f9c353c5977a09cdc"
+ integrity sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==
+
"@radix-ui/react-dismissable-layer@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz#2cd0a49a732372513733754e6032d3fb7988834e"
@@ -522,6 +591,19 @@
"@radix-ui/react-use-callback-ref" "1.1.0"
"@radix-ui/react-use-escape-keydown" "1.1.0"
+"@radix-ui/react-dropdown-menu@^2.1.1":
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.1.tgz#3dc578488688250dbbe109d9ff2ca28a9bca27ec"
+ integrity sha512-y8E+x9fBq9qvteD2Zwa4397pUVhYsh9iq44b5RD5qu1GMJWBCBuVg1hMyItbc6+zH00TxGRqd9Iot4wzf3OoBQ==
+ dependencies:
+ "@radix-ui/primitive" "1.1.0"
+ "@radix-ui/react-compose-refs" "1.1.0"
+ "@radix-ui/react-context" "1.1.0"
+ "@radix-ui/react-id" "1.1.0"
+ "@radix-ui/react-menu" "2.1.1"
+ "@radix-ui/react-primitive" "2.0.0"
+ "@radix-ui/react-use-controllable-state" "1.1.0"
+
"@radix-ui/react-focus-guards@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz#8e9abb472a9a394f59a1b45f3dd26cfe3fc6da13"
@@ -550,6 +632,82 @@
dependencies:
"@radix-ui/react-primitive" "2.0.0"
+"@radix-ui/react-menu@2.1.1":
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-2.1.1.tgz#bd623ace0e1ae1ac78023a505fec0541d59fb346"
+ integrity sha512-oa3mXRRVjHi6DZu/ghuzdylyjaMXLymx83irM7hTxutQbD+7IhPKdMdRHD26Rm+kHRrWcrUkkRPv5pd47a2xFQ==
+ dependencies:
+ "@radix-ui/primitive" "1.1.0"
+ "@radix-ui/react-collection" "1.1.0"
+ "@radix-ui/react-compose-refs" "1.1.0"
+ "@radix-ui/react-context" "1.1.0"
+ "@radix-ui/react-direction" "1.1.0"
+ "@radix-ui/react-dismissable-layer" "1.1.0"
+ "@radix-ui/react-focus-guards" "1.1.0"
+ "@radix-ui/react-focus-scope" "1.1.0"
+ "@radix-ui/react-id" "1.1.0"
+ "@radix-ui/react-popper" "1.2.0"
+ "@radix-ui/react-portal" "1.1.1"
+ "@radix-ui/react-presence" "1.1.0"
+ "@radix-ui/react-primitive" "2.0.0"
+ "@radix-ui/react-roving-focus" "1.1.0"
+ "@radix-ui/react-slot" "1.1.0"
+ "@radix-ui/react-use-callback-ref" "1.1.0"
+ aria-hidden "^1.1.1"
+ react-remove-scroll "2.5.7"
+
+"@radix-ui/react-menubar@^1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-menubar/-/react-menubar-1.1.1.tgz#e126514cb1c46e0a4f9fba7d016e578cc4e41f22"
+ integrity sha512-V05Hryq/BE2m+rs8d5eLfrS0jmSWSDHEbG7jEyLA5D5J9jTvWj/o3v3xDN9YsOlH6QIkJgiaNDaP+S4T1rdykw==
+ dependencies:
+ "@radix-ui/primitive" "1.1.0"
+ "@radix-ui/react-collection" "1.1.0"
+ "@radix-ui/react-compose-refs" "1.1.0"
+ "@radix-ui/react-context" "1.1.0"
+ "@radix-ui/react-direction" "1.1.0"
+ "@radix-ui/react-id" "1.1.0"
+ "@radix-ui/react-menu" "2.1.1"
+ "@radix-ui/react-primitive" "2.0.0"
+ "@radix-ui/react-roving-focus" "1.1.0"
+ "@radix-ui/react-use-controllable-state" "1.1.0"
+
+"@radix-ui/react-navigation-menu@^1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.0.tgz#884c9b9fd141cc5db257bd3f6bf3b84e349c6617"
+ integrity sha512-OQ8tcwAOR0DhPlSY3e4VMXeHiol7la4PPdJWhhwJiJA+NLX0SaCaonOkRnI3gCDHoZ7Fo7bb/G6q25fRM2Y+3Q==
+ dependencies:
+ "@radix-ui/primitive" "1.1.0"
+ "@radix-ui/react-collection" "1.1.0"
+ "@radix-ui/react-compose-refs" "1.1.0"
+ "@radix-ui/react-context" "1.1.0"
+ "@radix-ui/react-direction" "1.1.0"
+ "@radix-ui/react-dismissable-layer" "1.1.0"
+ "@radix-ui/react-id" "1.1.0"
+ "@radix-ui/react-presence" "1.1.0"
+ "@radix-ui/react-primitive" "2.0.0"
+ "@radix-ui/react-use-callback-ref" "1.1.0"
+ "@radix-ui/react-use-controllable-state" "1.1.0"
+ "@radix-ui/react-use-layout-effect" "1.1.0"
+ "@radix-ui/react-use-previous" "1.1.0"
+ "@radix-ui/react-visually-hidden" "1.1.0"
+
+"@radix-ui/react-popper@1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.2.0.tgz#a3e500193d144fe2d8f5d5e60e393d64111f2a7a"
+ integrity sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==
+ dependencies:
+ "@floating-ui/react-dom" "^2.0.0"
+ "@radix-ui/react-arrow" "1.1.0"
+ "@radix-ui/react-compose-refs" "1.1.0"
+ "@radix-ui/react-context" "1.1.0"
+ "@radix-ui/react-primitive" "2.0.0"
+ "@radix-ui/react-use-callback-ref" "1.1.0"
+ "@radix-ui/react-use-layout-effect" "1.1.0"
+ "@radix-ui/react-use-rect" "1.1.0"
+ "@radix-ui/react-use-size" "1.1.0"
+ "@radix-ui/rect" "1.1.0"
+
"@radix-ui/react-portal@1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.1.1.tgz#1957f1eb2e1aedfb4a5475bd6867d67b50b1d15f"
@@ -573,6 +731,36 @@
dependencies:
"@radix-ui/react-slot" "1.1.0"
+"@radix-ui/react-roving-focus@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz#b30c59daf7e714c748805bfe11c76f96caaac35e"
+ integrity sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==
+ dependencies:
+ "@radix-ui/primitive" "1.1.0"
+ "@radix-ui/react-collection" "1.1.0"
+ "@radix-ui/react-compose-refs" "1.1.0"
+ "@radix-ui/react-context" "1.1.0"
+ "@radix-ui/react-direction" "1.1.0"
+ "@radix-ui/react-id" "1.1.0"
+ "@radix-ui/react-primitive" "2.0.0"
+ "@radix-ui/react-use-callback-ref" "1.1.0"
+ "@radix-ui/react-use-controllable-state" "1.1.0"
+
+"@radix-ui/react-scroll-area@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-scroll-area/-/react-scroll-area-1.1.0.tgz#50b24b0fc9ada151d176395bcf47b2ec68feada5"
+ integrity sha512-9ArIZ9HWhsrfqS765h+GZuLoxaRHD/j0ZWOWilsCvYTpYJp8XwCqNG7Dt9Nu/TItKOdgLGkOPCodQvDc+UMwYg==
+ dependencies:
+ "@radix-ui/number" "1.1.0"
+ "@radix-ui/primitive" "1.1.0"
+ "@radix-ui/react-compose-refs" "1.1.0"
+ "@radix-ui/react-context" "1.1.0"
+ "@radix-ui/react-direction" "1.1.0"
+ "@radix-ui/react-presence" "1.1.0"
+ "@radix-ui/react-primitive" "2.0.0"
+ "@radix-ui/react-use-callback-ref" "1.1.0"
+ "@radix-ui/react-use-layout-effect" "1.1.0"
+
"@radix-ui/react-slot@1.1.0", "@radix-ui/react-slot@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.1.0.tgz#7c5e48c36ef5496d97b08f1357bb26ed7c714b84"
@@ -604,6 +792,37 @@
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz#3c2c8ce04827b26a39e442ff4888d9212268bd27"
integrity sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==
+"@radix-ui/react-use-previous@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz#d4dd37b05520f1d996a384eb469320c2ada8377c"
+ integrity sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==
+
+"@radix-ui/react-use-rect@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz#13b25b913bd3e3987cc9b073a1a164bb1cf47b88"
+ integrity sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==
+ dependencies:
+ "@radix-ui/rect" "1.1.0"
+
+"@radix-ui/react-use-size@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz#b4dba7fbd3882ee09e8d2a44a3eed3a7e555246b"
+ integrity sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==
+ dependencies:
+ "@radix-ui/react-use-layout-effect" "1.1.0"
+
+"@radix-ui/react-visually-hidden@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz#ad47a8572580f7034b3807c8e6740cd41038a5a2"
+ integrity sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==
+ dependencies:
+ "@radix-ui/react-primitive" "2.0.0"
+
+"@radix-ui/rect@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.1.0.tgz#f817d1d3265ac5415dadc67edab30ae196696438"
+ integrity sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==
+
"@rushstack/eslint-patch@^1.3.3":
version "1.10.3"
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.10.3.tgz#391d528054f758f81e53210f1a1eebcf1a8b1d20"
diff --git a/src/khoj/database/adapters/__init__.py b/src/khoj/database/adapters/__init__.py
index 7eda29d4..fb64f3b0 100644
--- a/src/khoj/database/adapters/__init__.py
+++ b/src/khoj/database/adapters/__init__.py
@@ -644,7 +644,11 @@ class ConversationAdapters:
@staticmethod
def get_conversation_sessions(user: KhojUser, client_application: ClientApplication = None):
- return Conversation.objects.filter(user=user, client=client_application).order_by("-updated_at")
+ return (
+ Conversation.objects.filter(user=user, client=client_application)
+ .prefetch_related("agent")
+ .order_by("-updated_at")
+ )
@staticmethod
async def aset_conversation_title(
diff --git a/src/khoj/routers/api_chat.py b/src/khoj/routers/api_chat.py
index a014fca5..482662e6 100644
--- a/src/khoj/routers/api_chat.py
+++ b/src/khoj/routers/api_chat.py
@@ -2,7 +2,7 @@ import json
import logging
import math
from datetime import datetime
-from typing import Any, Dict, List, Optional
+from typing import Dict, Optional
from urllib.parse import unquote
from asgiref.sync import sync_to_async
@@ -15,7 +15,6 @@ from websockets import ConnectionClosedOK
from khoj.database.adapters import (
ConversationAdapters,
- DataStoreAdapters,
EntryAdapters,
FileObjectAdapters,
PublicConversationAdapters,
@@ -95,53 +94,6 @@ def get_file_filter(request: Request, conversation_id: str) -> Response:
return Response(content=json.dumps(file_filters), media_type="application/json", status_code=200)
-class FactCheckerStoreDataFormat(BaseModel):
- factToVerify: str
- response: str
- references: Any
- childReferences: List[Any]
- runId: str
- modelUsed: Dict[str, Any]
-
-
-class FactCheckerStoreData(BaseModel):
- runId: str
- storeData: FactCheckerStoreDataFormat
-
-
-@api_chat.post("/store/factchecker", response_class=Response)
-@requires(["authenticated"])
-async def store_factchecker(request: Request, common: CommonQueryParams, data: FactCheckerStoreData):
- user = request.user.object
-
- update_telemetry_state(
- request=request,
- telemetry_type="api",
- api="store_factchecker",
- **common.__dict__,
- )
- fact_checker_key = f"factchecker_{data.runId}"
- await DataStoreAdapters.astore_data(data.storeData.model_dump_json(), fact_checker_key, user, private=False)
- return Response(content=json.dumps({"status": "ok"}), media_type="application/json", status_code=200)
-
-
-@api_chat.get("/store/factchecker", response_class=Response)
-async def get_factchecker(request: Request, common: CommonQueryParams, runId: str):
- update_telemetry_state(
- request=request,
- telemetry_type="api",
- api="read_factchecker",
- **common.__dict__,
- )
-
- fact_checker_key = f"factchecker_{runId}"
-
- data = await DataStoreAdapters.aretrieve_public_data(fact_checker_key)
- if data is None:
- return Response(status_code=404)
- return Response(content=json.dumps(data.value), media_type="application/json", status_code=200)
-
-
@api_chat.post("/conversation/file-filters", response_class=Response)
@requires(["authenticated"])
def add_file_filter(request: Request, filter: FilterRequest):
@@ -418,15 +370,26 @@ def duplicate_chat_history_public_conversation(
def chat_sessions(
request: Request,
common: CommonQueryParams,
+ recent: Optional[bool] = False,
):
user = request.user.object
# Load Conversation Sessions
- sessions = ConversationAdapters.get_conversation_sessions(user, request.user.client_app).values_list(
- "id", "slug", "title"
- )
+ conversations = ConversationAdapters.get_conversation_sessions(user, request.user.client_app)
+ if recent:
+ conversations = conversations[:8]
- session_values = [{"conversation_id": session[0], "slug": session[2] or session[1]} for session in sessions]
+ sessions = conversations.values_list("id", "slug", "title", "agent__slug", "agent__name", "agent__avatar")
+
+ session_values = [
+ {
+ "conversation_id": session[0],
+ "slug": session[2] or session[1],
+ "agent_name": session[4],
+ "agent_avatar": session[5],
+ }
+ for session in sessions
+ ]
update_telemetry_state(
request=request,