-
- {people.snippet}
+ <>
+
+
+
+
+
-
- )
- })
- }
- {
- knowledgeGraph && (
-
-
-
-
- {knowledgeGraph.description}
-
+
+
+
+
+
+
-
- )
- }
-
+
+
+
+ >
)
}
-export default function ReferencePanel(props: ReferencePanelProps) {
+export function constructAllReferences(contextData: Context[], onlineData: { [key: string]: OnlineContextData }) {
+
+ const onlineReferences: OnlineReferenceData[] = [];
+ const contextReferences: NotesContextReferenceData[] = [];
+
+ if (onlineData) {
+ let localOnlineReferences = [];
+ for (const [key, value] of Object.entries(onlineData)) {
+ if (value.answerBox) {
+ localOnlineReferences.push({
+ title: value.answerBox.title,
+ description: value.answerBox.answer,
+ link: value.answerBox.source
+ });
+ }
+ if (value.knowledgeGraph) {
+ localOnlineReferences.push({
+ title: value.knowledgeGraph.title,
+ description: value.knowledgeGraph.description,
+ link: value.knowledgeGraph.descriptionLink
+ });
+ }
+
+ if (value.webpages) {
+ // If webpages is of type Array, iterate through it and add each webpage to the localOnlineReferences array
+ if (value.webpages instanceof Array) {
+ let webPageResults = value.webpages.map((webPage) => {
+ return {
+ title: webPage.query,
+ description: webPage.snippet,
+ link: webPage.link
+ }
+ });
+ localOnlineReferences.push(...webPageResults);
+ } else {
+ let singleWebpage = value.webpages as WebPage;
+
+ // If webpages is an object, add the object to the localOnlineReferences array
+ localOnlineReferences.push({
+ title: singleWebpage.query,
+ description: singleWebpage.snippet,
+ link: singleWebpage.link
+ });
+ }
+ }
+
+ if (value.organic) {
+ let organicResults = value.organic.map((organicContext) => {
+ return {
+ title: organicContext.title,
+ description: organicContext.snippet,
+ link: organicContext.link
+ }
+ });
+
+ localOnlineReferences.push(...organicResults);
+ }
+ }
+
+ onlineReferences.push(...localOnlineReferences);
+ }
+
+ if (contextData) {
+
+ let localContextReferences = contextData.map((context) => {
+ if (!context.compiled) {
+ const fileContent = context as unknown as string;
+ const title = fileContent.split('\n')[0];
+ const content = fileContent.split('\n').slice(1).join('\n');
+ return {
+ title: title,
+ content: content
+ };
+ }
+ return {
+ title: context.file,
+ content: context.compiled
+ }
+ });
+
+ contextReferences.push(...localContextReferences);
+ }
+
+ return {
+ notesReferenceCardData: contextReferences,
+ onlineReferenceCardData: onlineReferences
+ }
+}
+
+
+export interface TeaserReferenceSectionProps {
+ notesReferenceCardData: NotesContextReferenceData[];
+ onlineReferenceCardData: OnlineReferenceData[];
+ isMobileWidth: boolean;
+}
+
+export function TeaserReferencesSection(props: TeaserReferenceSectionProps) {
+ const [numTeaserSlots, setNumTeaserSlots] = useState(3);
+
+ useEffect(() => {
+ setNumTeaserSlots(props.isMobileWidth ? 1 : 3);
+ }, [props.isMobileWidth]);
+
+ const notesDataToShow = props.notesReferenceCardData.slice(0, numTeaserSlots);
+ const onlineDataToShow = notesDataToShow.length < numTeaserSlots ? props.onlineReferenceCardData.slice(0, numTeaserSlots - notesDataToShow.length) : [];
+
+ const shouldShowShowMoreButton = props.notesReferenceCardData.length > 0 || props.onlineReferenceCardData.length > 0;
- if (!props.referencePanelData) {
+ const numReferences = props.notesReferenceCardData.length + props.onlineReferenceCardData.length;
+
+ if (numReferences === 0) {
return null;
}
- if (!hasValidReferences(props.referencePanelData)) {
+ return (
+
+
+ References
+
+ {numReferences} sources
+
+
+
+ {
+ notesDataToShow.map((note, index) => {
+ return
+ })
+ }
+ {
+ onlineDataToShow.map((online, index) => {
+ return
+ })
+ }
+ {
+ shouldShowShowMoreButton &&
+
+ }
+
+
+ )
+}
+
+
+interface ReferencePanelDataProps {
+ notesReferenceCardData: NotesContextReferenceData[];
+ onlineReferenceCardData: OnlineReferenceData[];
+}
+
+export default function ReferencePanel(props: ReferencePanelDataProps) {
+
+ if (!props.notesReferenceCardData && !props.onlineReferenceCardData) {
return null;
}
- return (
-
- References
- {
- props.referencePanelData?.context.map((context, index) => {
- return
- })
- }
- {
- Object.entries(props.referencePanelData?.onlineContext || {}).map(([key, onlineContextData], index) => {
- return
- })
- }
-
- );
+ return (
+
+
+ View references
+
+
+
+ References
+ View all references for this response
+
+
+ {
+ props.notesReferenceCardData.map((note, index) => {
+ return
+ })
+ }
+ {
+ props.onlineReferenceCardData.map((online, index) => {
+ return
+ })
+ }
+
+
+
+ );
}
diff --git a/src/interface/web/app/components/sidePanel/chatHistorySidePanel.tsx b/src/interface/web/app/components/sidePanel/chatHistorySidePanel.tsx
index a6561423b..351a77aa6 100644
--- a/src/interface/web/app/components/sidePanel/chatHistorySidePanel.tsx
+++ b/src/interface/web/app/components/sidePanel/chatHistorySidePanel.tsx
@@ -2,150 +2,787 @@
import styles from "./sidePanel.module.css";
-import { useEffect, useState } from "react";
+import { Suspense, useEffect, useState } from "react";
-import { UserProfile } from "@/app/common/auth";
+import { UserProfile, useAuthenticatedData } from "@/app/common/auth";
+import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
import Link from "next/link";
+import useSWR from "swr";
+import Image from "next/image";
+
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "@/components/ui/command";
+
+import { InlineLoading } from "../loading/loading";
+
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+
+import {
+ Drawer,
+ DrawerClose,
+ DrawerContent,
+ DrawerDescription,
+ DrawerFooter,
+ DrawerHeader,
+ DrawerTitle,
+ DrawerTrigger,
+} from "@/components/ui/drawer";
+
+
+import { ScrollArea } from "@/components/ui/scroll-area";
+
+import { ArrowRight, ArrowLeft, ArrowDown, Spinner, Check, FolderPlus, DotsThreeVertical, House, StackPlus, UserCirclePlus } from "@phosphor-icons/react";
interface ChatHistory {
conversation_id: string;
slug: string;
+ agent_name: string;
+ agent_avatar: string;
+ compressed: boolean;
+ created: string;
+}
+
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+
+import { Pencil, Trash, Share } from "@phosphor-icons/react";
+
+import { Button, buttonVariants } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog";
+import { modifyFileFilterForConversation } from "@/app/common/chatFunctions";
+import { ScrollAreaScrollbar } from "@radix-ui/react-scroll-area";
+
+// Define a fetcher function
+const fetcher = (url: string) => fetch(url).then((res) => res.json());
+
+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: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ })
+ .then(response => response.json())
+ .then(data => {
+ })
+ .catch(err => {
+ console.error(err);
+ return;
+ });
+}
+
+function shareConversation(conversationId: string, setShareUrl: (url: string) => void) {
+ 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 => {
+ setShareUrl(data.url);
+ })
+ .catch(err => {
+ console.error(err);
+ return;
+ });
+}
+
+function deleteConversation(conversationId: string) {
+ const deleteUrl = `/api/chat/history?client=web&conversation_id=${conversationId}`;
+
+ fetch(deleteUrl, {
+ method: 'DELETE',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ })
+ .then(response => response.json())
+ .then(data => {
+ })
+ .catch(err => {
+ console.error(err);
+ return;
+ });
+}
+
+
+interface FilesMenuProps {
+ conversationId: string | null;
+ uploadedFiles: string[];
+ isMobileWidth: boolean;
+}
+
+function FilesMenu(props: FilesMenuProps) {
+ // Use SWR to fetch files
+ const { data: files, error } = useSWR
(props.conversationId ? '/api/config/data/computer' : null, fetcher);
+ const { data: selectedFiles, error: selectedFilesError } = useSWR(props.conversationId ? `/api/chat/conversation/file-filters/${props.conversationId}` : null, fetcher);
+ const [isOpen, setIsOpen] = useState(false);
+ const [unfilteredFiles, setUnfilteredFiles] = useState([]);
+ const [addedFiles, setAddedFiles] = useState([]);
+
+ useEffect(() => {
+ if (!files) return;
+
+ // First, sort lexically
+ files.sort();
+ let sortedFiles = files;
+
+ if (addedFiles) {
+ console.log("addedFiles in useeffect hook", addedFiles);
+ sortedFiles = addedFiles.concat(sortedFiles.filter((filename: string) => !addedFiles.includes(filename)));
+ }
+
+ setUnfilteredFiles(sortedFiles);
+
+ }, [files, addedFiles]);
+
+ useEffect(() => {
+ for (const file of props.uploadedFiles) {
+ setAddedFiles((addedFiles) => [...addedFiles, file]);
+ }
+ }, [props.uploadedFiles]);
+
+ useEffect(() => {
+ if (selectedFiles) {
+ setAddedFiles(selectedFiles);
+ }
+
+ }, [selectedFiles]);
+
+ const removeAllFiles = () => {
+ modifyFileFilterForConversation(props.conversationId, addedFiles, setAddedFiles, 'remove');
+ }
+
+ const addAllFiles = () => {
+ modifyFileFilterForConversation(props.conversationId, unfilteredFiles, setAddedFiles, 'add');
+ }
+
+ if (!props.conversationId) return (<>>);
+
+ if (error) return Failed to load files
;
+ if (selectedFilesError) return Failed to load selected files
;
+ if (!files) return ;
+ if (!selectedFiles) return ;
+
+ const FilesMenuCommandBox = () => {
+ return (
+
+
+
+ No results found.
+
+ {
+ removeAllFiles();
+ }}
+ >
+
+ Clear all
+
+ {
+ addAllFiles();
+ }}
+ >
+
+ Select all
+
+
+
+ {unfilteredFiles.map((filename: string) => (
+ addedFiles && addedFiles.includes(filename) ?
+ {
+ modifyFileFilterForConversation(props.conversationId, [value], setAddedFiles, 'remove');
+ }}
+ >
+
+ {filename}
+
+ :
+ {
+ modifyFileFilterForConversation(props.conversationId, [value], setAddedFiles, 'add');
+ }}
+ >
+ {filename}
+
+ ))}
+
+
+
+ );
+ }
+
+ if (props.isMobileWidth) {
+ return (
+ <>
+
+
+ Manage Files
+
+
+
+ Files
+ Manage files for this conversation
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+ }
+
+ return (
+ <>
+
+
+
+
+
+ Manage Context
+
+ Using {addedFiles.length == 0 ? files.length : addedFiles.length} files
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+
+}
+
+interface SessionsAndFilesProps {
+ webSocketConnected?: boolean;
+ setEnabled: (enabled: boolean) => void;
+ subsetOrganizedData: GroupedChatHistory | null;
+ organizedData: GroupedChatHistory | null;
+ data: ChatHistory[] | null;
+ userProfile: UserProfile | null;
+ conversationId: string | null;
+ uploadedFiles: string[];
+ isMobileWidth: boolean;
+}
+
+function SessionsAndFiles(props: SessionsAndFilesProps) {
+ return (
+ <>
+
+
+
+ {props.subsetOrganizedData != null && Object.keys(props.subsetOrganizedData).map((timeGrouping) => (
+
+
+ {timeGrouping}
+
+ {props.subsetOrganizedData && props.subsetOrganizedData[timeGrouping].map((chatHistory) => (
+
+ ))}
+
+ ))}
+
+
+ {
+ (props.data && props.data.length > 5) && (
+
+ )
+ }
+
+ {props.userProfile &&
+
+ }>
+ )
+}
+
+interface ChatSessionActionMenuProps {
+ conversationId: string;
+}
+
+function ChatSessionActionMenu(props: ChatSessionActionMenuProps) {
+ const [renamedTitle, setRenamedTitle] = useState('');
+ const [isRenaming, setIsRenaming] = useState(false);
+ const [isSharing, setIsSharing] = useState(false);
+ const [isDeleting, setIsDeleting] = useState(false);
+ const [shareUrl, setShareUrl] = useState('');
+ const [showShareUrl, setShowShareUrl] = useState(false);
+
+ const [isOpen, setIsOpen] = useState(false);
+
+ useEffect(() => {
+ if (isSharing) {
+ shareConversation(props.conversationId, setShareUrl);
+ setShowShareUrl(true);
+ setIsSharing(false);
+ }
+ }, [isSharing]);
+
+ if (isRenaming) {
+ return (
+
+ )
+ }
+
+ if (isSharing || showShareUrl) {
+ if (shareUrl) {
+ navigator.clipboard.writeText(shareUrl);
+ }
+ return (
+
+ )
+ }
+
+ if (isDeleting) {
+ return (
+ setIsDeleting(open)}>
+
+
+ Delete Conversation
+
+ Are you sure you want to delete this conversation? This action cannot be undone.
+
+
+
+ Cancel
+ {
+ deleteConversation(props.conversationId);
+ setIsDeleting(false);
+ }}
+ className="bg-rose-500 hover:bg-rose-600">Delete
+
+
+
+ )
+ }
+
+ return (
+ setIsOpen(open)}
+ open={isOpen}>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
}
-function ChatSession(prop: ChatHistory) {
+function ChatSession(props: ChatHistory) {
+ const [isHovered, setIsHovered] = useState(false);
+
return (
-
-
-
{prop.slug || "New Conversation 🌱"}
+
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 }: ChatSessionsModalProps) {
+ return (
+
+ );
+}
+
+interface UserProfileProps {
+ userProfile: UserProfile;
+ webSocketConnected?: boolean;
+ collapsed: boolean;
}
-function ChatSessionsModal({data, setIsExpanded}: ChatSessionsModalProps) {
+function UserProfileComponent(props: UserProfileProps) {
+ if (props.collapsed) {
+ return (
+
+
+
+
+ {props.userProfile.username[0]}
+
+
+
+ );
+ }
+
return (
-
-
- {data.map((chatHistory) => (
-
- ))}
-
+
+
+
+
+
+ {props.userProfile.username[0]}
+
+
+
+
+
{props.userProfile?.username}
+ {/* Connected Indicator */}
+
+
+
+ {props.webSocketConnected ? "Connected" : "Disconnected"}
+
+
);
+
+}
+
+const fetchChatHistory = async (url: string) => {
+ const response = await fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
+ return response.json();
+};
+
+export const useChatSessionsFetchRequest = (url: string) => {
+ const { data, error } = useSWR
(url, fetchChatHistory);
+
+ return {
+ data,
+ isLoading: !error && !data,
+ isError: error,
+ };
+};
+
+interface SidePanelProps {
+ webSocketConnected?: boolean;
+ conversationId: string | null;
+ uploadedFiles: string[];
}
-export default function SidePanel() {
+
+export default function SidePanel(props: SidePanelProps) {
const [data, setData] = useState(null);
- const [dataToShow, setDataToShow] = useState(null);
- const [isLoading, setLoading] = useState(true)
+ const [organizedData, setOrganizedData] = useState(null);
+ const [subsetOrganizedData, setSubsetOrganizedData] = useState(null);
const [enabled, setEnabled] = useState(false);
- const [isExpanded, setIsExpanded] = useState(false);
- const [userProfile, setUserProfile] = useState(null);
+ const authenticatedData = useAuthenticatedData();
+ const { data: chatSessions } = useChatSessionsFetchRequest(authenticatedData ? `/api/chat/sessions` : '');
+
- useEffect(() => {
+ const [isMobileWidth, setIsMobileWidth] = useState(false);
- 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 (chatSessions) {
+ setData(chatSessions);
+
+ const groupedData: GroupedChatHistory = {};
+ const subsetOrganizedData: GroupedChatHistory = {};
+ let numAdded = 0;
+
+ const currentDate = new Date();
+
+ chatSessions.forEach((chatHistory) => {
+ const chatDate = new Date(chatHistory.created);
+ const diffTime = Math.abs(currentDate.getTime() - chatDate.getTime());
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
+
+ const timeGrouping = diffDays < 7 ? 'Recent' : diffDays < 30 ? 'Last Month' : 'All Time';
+ if (!groupedData[timeGrouping]) {
+ groupedData[timeGrouping] = [];
}
- })
- .catch(err => {
- console.error(err);
- return;
- });
+ groupedData[timeGrouping].push(chatHistory);
- fetch('/api/v1/user', { method: 'GET' })
- .then(response => response.json())
- .then((data: UserProfile) => {
- setUserProfile(data);
- })
- .catch(err => {
- console.error(err);
- return;
+ // Add to subsetOrganizedData if less than 8
+ if (numAdded < 8) {
+ if (!subsetOrganizedData[timeGrouping]) {
+ subsetOrganizedData[timeGrouping] = [];
+ }
+ subsetOrganizedData[timeGrouping].push(chatHistory);
+ numAdded++;
+ }
});
- }, []);
- return (
-
+
+ setSubsetOrganizedData(subsetOrganizedData);
+ setOrganizedData(groupedData);
+ }
+ }, [chatSessions]);
+
+ useEffect(() => {
+ if (window.innerWidth < 768) {
+ setIsMobileWidth(true);
+ }
+
+ window.addEventListener('resize', () => {
+ setIsMobileWidth(window.innerWidth < 768);
+ });
+ }, []);
+
+ return (
+
+
+
+ {
+ authenticatedData &&
+ isMobileWidth ?
+
+
+
+
+ Sessions and Files
+ View all conversation sessions and manage conversation file filters
+
+
+
+
+
+
+
+
+
+
+
+ :
+
+ }
+
{
- enabled ?
-
-
-
- { userProfile &&
-
-
-
{userProfile?.username}
-
- }
-
-
-
Recent Conversations
-
-
- {dataToShow && dataToShow.map((chatHistory) => (
-
- ))}
-
- {
- (data && data.length > 5) && (
- (isExpanded) ?
-
- :
-
- )
- }
-
- :
-
-
- { userProfile &&
-
-
-
- }
-
-
-
+ authenticatedData && enabled &&
+
+
+
}
-
-
- );
+ {
+ !authenticatedData && enabled &&
+
+
+
+
+
+
+
+ {/* Redirect to login page */}
+
+
+
+ }
+
+ );
}
diff --git a/src/interface/web/app/components/sidePanel/sidePanel.module.css b/src/interface/web/app/components/sidePanel/sidePanel.module.css
index ea675fa0f..ed03f94cc 100644
--- a/src/interface/web/app/components/sidePanel/sidePanel.module.css
+++ b/src/interface/web/app/components/sidePanel/sidePanel.module.css
@@ -2,9 +2,28 @@ 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: hsla(var(--popover));
+}
+
+div.session:hover {
+ background-color: hsla(var(--popover));
+ color: hsla(var(--popover-foreground));
+}
+
+div.session a {
+ text-decoration: none;
}
button.button {
@@ -17,29 +36,23 @@ button.button {
}
button.showMoreButton {
- background: var(--intense-green);
- border: none;
- color: var(--frosted-background-color);
border-radius: 0.5rem;
padding: 8px;
}
div.panel {
- display: grid;
- grid-auto-flow: row;
+ display: flex;
+ flex-direction: column;
padding: 1rem;
- border-radius: 1rem;
- background-color: var(--calm-blue);
- color: var(--main-text-color);
- height: 100%;
overflow-y: auto;
max-width: auto;
+ transition: background-color 0.5s;
}
div.expanded {
- display: grid;
- grid-template-columns: 1fr auto;
gap: 1rem;
+ background-color: hsla(var(--muted));
+ height: 100%;
}
div.collapsed {
@@ -47,14 +60,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 +73,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;
+ margin-top: auto;
+}
+
+div.panelWrapper {
+ display: grid;
+ grid-template-rows: auto 1fr auto auto;
+ height: 100%;
}
@@ -75,7 +94,7 @@ div.modalSessionsList {
left: 0;
width: 100%;
height: 100%;
- background-color: var(--frosted-background-color);
+ background-color: hsla(var(--frosted-background-color));
z-index: 1;
display: flex;
justify-content: center;
@@ -86,7 +105,7 @@ div.modalSessionsList {
div.modalSessionsList div.content {
max-width: 80%;
max-height: 80%;
- background-color: var(--frosted-background-color);
+ background-color: hsla(var(--frosted-background-color));
overflow: auto;
padding: 20px;
border-radius: 10px;
@@ -96,3 +115,33 @@ div.modalSessionsList div.session {
max-width: 100%;
text-overflow: ellipsis;
}
+
+@media screen and (max-width: 768px) {
+ div.panel {
+ padding: 0.5rem;
+ position: absolute;
+ width: 100%;
+ }
+
+ div.expanded {
+ z-index: 1;
+ }
+
+ div.singleReference {
+ padding: 4px;
+ }
+
+ div.panelWrapper {
+ width: 100%;
+ }
+
+ div.session.compressed {
+ max-width: 100%;
+ grid-template-columns: minmax(auto, 350px) 1fr;
+ }
+
+ div.session {
+ max-width: 100%;
+ grid-template-columns: 200px 1fr;
+ }
+}
diff --git a/src/interface/web/app/factchecker/page.tsx b/src/interface/web/app/factchecker/page.tsx
index c04da078b..01748c9a0 100644
--- a/src/interface/web/app/factchecker/page.tsx
+++ b/src/interface/web/app/factchecker/page.tsx
@@ -140,6 +140,7 @@ function ReferenceVerification(props: ReferenceVerificationProps) {
const [initialResponse, setInitialResponse] = useState("");
const [isLoading, setIsLoading] = useState(true);
const verificationStatement = `${props.message}. Use this link for reference: ${props.additionalLink}`;
+ const [isMobileWidth, setIsMobileWidth] = useState(false);
useEffect(() => {
if (props.prefilledResponse) {
@@ -149,6 +150,12 @@ function ReferenceVerification(props: ReferenceVerificationProps) {
verifyStatement(verificationStatement, props.conversationId, setIsLoading, setInitialResponse, () => {});
}
+ setIsMobileWidth(window.innerWidth < 768);
+
+ window.addEventListener('resize', () => {
+ setIsMobileWidth(window.innerWidth < 768);
+ })
+
}, [verificationStatement, props.conversationId, props.prefilledResponse]);
useEffect(() => {
@@ -170,13 +177,12 @@ function ReferenceVerification(props: ReferenceVerificationProps) {
{
automationId: "",
by: "AI",
- intent: {},
message: initialResponse,
context: [],
created: (new Date()).toISOString(),
- onlineContext: {}
- }
- } setReferencePanelData={() => {}} setShowReferencePanel={() => {}} />
+ onlineContext: {},
+ }}
+ isMobileWidth={isMobileWidth} />
)
}
@@ -236,6 +242,7 @@ export default function FactChecker() {
const [initialReferences, setInitialReferences] = useState
();
const [childReferences, setChildReferences] = useState();
const [modelUsed, setModelUsed] = useState();
+ const [isMobileWidth, setIsMobileWidth] = useState(false);
const [conversationID, setConversationID] = useState("");
const [runId, setRunId] = useState("");
@@ -251,6 +258,15 @@ export default function FactChecker() {
setChildReferences(newReferences);
}
+ useEffect(() => {
+ setIsMobileWidth(window.innerWidth < 768);
+
+ window.addEventListener('resize', () => {
+ setIsMobileWidth(window.innerWidth < 768);
+ })
+
+ }, []);
+
let userData = useAuthenticatedData();
function storeData() {
@@ -390,6 +406,7 @@ export default function FactChecker() {
const seenLinks = new Set();
+
// Any links that are present in webpages should not be searched again
Object.entries(initialReferences.online || {}).map(([key, onlineData], index) => {
const webpages = onlineData?.webpages || [];
@@ -536,13 +553,12 @@ export default function FactChecker() {
{
automationId: "",
by: "AI",
- intent: {},
message: initialResponse,
context: [],
created: (new Date()).toISOString(),
onlineContext: {}
}
- } setReferencePanelData={() => {}} setShowReferencePanel={() => {}} />
+ } isMobileWidth={isMobileWidth} />
diff --git a/src/interface/web/app/globals.css b/src/interface/web/app/globals.css
index be5c7aebd..5bb6648bc 100644
--- a/src/interface/web/app/globals.css
+++ b/src/interface/web/app/globals.css
@@ -26,30 +26,313 @@
--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: 20 13% 95%;
+ --secondary-background-color: #F7F7F5;
+ --secondary-accent: #EDEDED;
+ --khoj-orange: #FFE7D1;
+ --border-color: #e2e2e2;
+ --box-shadow-color: rgba(0, 0, 0, 0.03);
+
+ /* Imported from Highlight.js */
+ pre code.hljs {
+ display: block;
+ overflow-x: auto;
+ padding: 1em
+ }
+
+ code.hljs {
+ padding: 3px 5px
+ }
+
+ /*!
+ Theme: GitHub
+ Description: Light theme as seen on github.com
+ Author: github.com
+ Maintainer: @Hirse
+ Updated: 2021-05-15
+
+ Outdated base version: https://github.com/primer/github-syntax-light
+ Current colors taken from GitHub's CSS
+ */
+ .hljs {
+ color: #24292e;
+ background: #ffffff
+ }
+
+ .hljs-doctag,
+ .hljs-keyword,
+ .hljs-meta .hljs-keyword,
+ .hljs-template-tag,
+ .hljs-template-variable,
+ .hljs-type,
+ .hljs-variable.language_ {
+ /* prettylights-syntax-keyword */
+ color: #d73a49
+ }
+
+ .hljs-title,
+ .hljs-title.class_,
+ .hljs-title.class_.inherited__,
+ .hljs-title.function_ {
+ /* prettylights-syntax-entity */
+ color: #6f42c1
+ }
+
+ .hljs-attr,
+ .hljs-attribute,
+ .hljs-literal,
+ .hljs-meta,
+ .hljs-number,
+ .hljs-operator,
+ .hljs-variable,
+ .hljs-selector-attr,
+ .hljs-selector-class,
+ .hljs-selector-id {
+ /* prettylights-syntax-constant */
+ color: #005cc5
+ }
+
+ .hljs-regexp,
+ .hljs-string,
+ .hljs-meta .hljs-string {
+ /* prettylights-syntax-string */
+ color: #032f62
+ }
+
+ .hljs-built_in,
+ .hljs-symbol {
+ /* prettylights-syntax-variable */
+ color: #e36209
+ }
+
+ .hljs-comment,
+ .hljs-code,
+ .hljs-formula {
+ /* prettylights-syntax-comment */
+ color: #6a737d
+ }
+
+ .hljs-name,
+ .hljs-quote,
+ .hljs-selector-tag,
+ .hljs-selector-pseudo {
+ /* prettylights-syntax-entity-tag */
+ color: #22863a
+ }
+
+ .hljs-subst {
+ /* prettylights-syntax-storage-modifier-import */
+ color: #24292e
+ }
+
+ .hljs-section {
+ /* prettylights-syntax-markup-heading */
+ color: #005cc5;
+ font-weight: bold
+ }
+
+ .hljs-bullet {
+ /* prettylights-syntax-markup-list */
+ color: #735c0f
+ }
+
+ .hljs-emphasis {
+ /* prettylights-syntax-markup-italic */
+ color: #24292e;
+ font-style: italic
+ }
+
+ .hljs-strong {
+ /* prettylights-syntax-markup-bold */
+ color: #24292e;
+ font-weight: bold
+ }
+
+ .hljs-addition {
+ /* prettylights-syntax-markup-inserted */
+ color: #22863a;
+ background-color: #f0fff4
+ }
+
+ .hljs-deletion {
+ /* prettylights-syntax-markup-deleted */
+ color: #b31d28;
+ background-color: #ffeef0
+ }
+
+ .hljs-char.escape_,
+ .hljs-link,
+ .hljs-params,
+ .hljs-property,
+ .hljs-punctuation,
+ .hljs-tag {
+ /* purposely ignored */
+
+ }
}
.dark {
- --background: 224 71.4% 4.1%;
+ --background: 0 0% 14%;
--foreground: 210 20% 98%;
- --card: 224 71.4% 4.1%;
+ --card: 0 0% 14%;
--card-foreground: 210 20% 98%;
- --popover: 224 71.4% 4.1%;
+ --popover: 0 0% 14%;
--popover-foreground: 210 20% 98%;
--primary: 263.4 70% 50.4%;
--primary-foreground: 210 20% 98%;
- --secondary: 215 27.9% 16.9%;
+ --secondary: 0 0% 9%;
--secondary-foreground: 210 20% 98%;
- --muted: 215 27.9% 16.9%;
+ --muted: 0 0% 9%;
--muted-foreground: 217.9 10.6% 64.9%;
- --accent: 215 27.9% 16.9%;
+ --accent: 0 0% 9%;
--accent-foreground: 210 20% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 20% 98%;
- --border: 215 27.9% 16.9%;
- --input: 215 27.9% 16.9%;
+ --border: 0 0% 9%;
+ --input: 0 0% 9%;
--ring: 263.4 70% 50.4%;
--font-family: "Noto Sans", "Noto Sans Arabic", sans-serif !important;
+ --box-shadow-color: rgba(255, 255, 255, 0.05);
+
+ /* Imported from highlight.js */
+ pre code.hljs {
+ display: block;
+ overflow-x: auto;
+ padding: 1em
+ }
+
+ code.hljs {
+ padding: 3px 5px
+ }
+
+ /*!
+ Theme: GitHub Dark
+ Description: Dark theme as seen on github.com
+ Author: github.com
+ Maintainer: @Hirse
+ Updated: 2021-05-15
+
+ Outdated base version: https://github.com/primer/github-syntax-dark
+ Current colors taken from GitHub's CSS
+ */
+ .hljs {
+ color: #c9d1d9;
+ background: #0d1117
+ }
+
+ .hljs-doctag,
+ .hljs-keyword,
+ .hljs-meta .hljs-keyword,
+ .hljs-template-tag,
+ .hljs-template-variable,
+ .hljs-type,
+ .hljs-variable.language_ {
+ /* prettylights-syntax-keyword */
+ color: #ff7b72
+ }
+
+ .hljs-title,
+ .hljs-title.class_,
+ .hljs-title.class_.inherited__,
+ .hljs-title.function_ {
+ /* prettylights-syntax-entity */
+ color: #d2a8ff
+ }
+
+ .hljs-attr,
+ .hljs-attribute,
+ .hljs-literal,
+ .hljs-meta,
+ .hljs-number,
+ .hljs-operator,
+ .hljs-variable,
+ .hljs-selector-attr,
+ .hljs-selector-class,
+ .hljs-selector-id {
+ /* prettylights-syntax-constant */
+ color: #79c0ff
+ }
+
+ .hljs-regexp,
+ .hljs-string,
+ .hljs-meta .hljs-string {
+ /* prettylights-syntax-string */
+ color: #a5d6ff
+ }
+
+ .hljs-built_in,
+ .hljs-symbol {
+ /* prettylights-syntax-variable */
+ color: #ffa657
+ }
+
+ .hljs-comment,
+ .hljs-code,
+ .hljs-formula {
+ /* prettylights-syntax-comment */
+ color: #8b949e
+ }
+
+ .hljs-name,
+ .hljs-quote,
+ .hljs-selector-tag,
+ .hljs-selector-pseudo {
+ /* prettylights-syntax-entity-tag */
+ color: #7ee787
+ }
+
+ .hljs-subst {
+ /* prettylights-syntax-storage-modifier-import */
+ color: #c9d1d9
+ }
+
+ .hljs-section {
+ /* prettylights-syntax-markup-heading */
+ color: #1f6feb;
+ font-weight: bold
+ }
+
+ .hljs-bullet {
+ /* prettylights-syntax-markup-list */
+ color: #f2cc60
+ }
+
+ .hljs-emphasis {
+ /* prettylights-syntax-markup-italic */
+ color: #c9d1d9;
+ font-style: italic
+ }
+
+ .hljs-strong {
+ /* prettylights-syntax-markup-bold */
+ color: #c9d1d9;
+ font-weight: bold
+ }
+
+ .hljs-addition {
+ /* prettylights-syntax-markup-inserted */
+ color: #aff5b4;
+ background-color: #033a16
+ }
+
+ .hljs-deletion {
+ /* prettylights-syntax-markup-deleted */
+ color: #ffdcd7;
+ background-color: #67060c
+ }
+
+ .hljs-char.escape_,
+ .hljs-link,
+ .hljs-params,
+ .hljs-property,
+ .hljs-punctuation,
+ .hljs-tag {
+ /* purposely ignored */
+
+ }
}
}
diff --git a/src/interface/web/app/page.module.css b/src/interface/web/app/page.module.css
index 5c4b1e6a2..40fc1b059 100644
--- a/src/interface/web/app/page.module.css
+++ b/src/interface/web/app/page.module.css
@@ -1,230 +1,224 @@
.main {
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- align-items: center;
- padding: 6rem;
- min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ align-items: center;
+ padding: 6rem;
+ min-height: 100vh;
}
.description {
- display: inherit;
- justify-content: inherit;
- align-items: inherit;
- font-size: 0.85rem;
- max-width: var(--max-width);
- width: 100%;
- z-index: 2;
- font-family: var(--font-mono);
+ display: inherit;
+ justify-content: inherit;
+ align-items: inherit;
+ font-size: 0.85rem;
+ max-width: var(--max-width);
+ width: 100%;
+ z-index: 2;
+ font-family: var(--font-mono);
}
.description a {
- display: flex;
- justify-content: center;
- align-items: center;
- gap: 0.5rem;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 0.5rem;
}
.description p {
- position: relative;
- margin: 0;
- padding: 1rem;
- background-color: rgba(var(--callout-rgb), 0.5);
- border: 1px solid rgba(var(--callout-border-rgb), 0.3);
- border-radius: var(--border-radius);
+ position: relative;
+ margin: 0;
+ padding: 1rem;
+ background-color: rgba(var(--callout-rgb), 0.5);
+ border: 1px solid rgba(var(--callout-border-rgb), 0.3);
+ border-radius: var(--border-radius);
}
.code {
- font-weight: 700;
- font-family: var(--font-mono);
+ font-weight: 700;
+ font-family: var(--font-mono);
}
.grid {
- display: grid;
- grid-template-columns: repeat(4, minmax(25%, auto));
- max-width: 100%;
- width: var(--max-width);
+ display: grid;
+ grid-template-columns: repeat(4, minmax(25%, auto));
+ max-width: 100%;
+ width: var(--max-width);
}
.card {
- padding: 1rem 1.2rem;
- border-radius: var(--border-radius);
- background: rgba(var(--card-rgb), 0);
- border: 1px solid rgba(var(--card-border-rgb), 0);
- transition: background 200ms, border 200ms;
+ padding: 1rem 1.2rem;
+ border-radius: var(--border-radius);
+ background: rgba(var(--card-rgb), 0);
+ border: 1px solid rgba(var(--card-border-rgb), 0);
+ transition: background 200ms, border 200ms;
}
.card span {
- display: inline-block;
- transition: transform 200ms;
+ display: inline-block;
+ transition: transform 200ms;
}
.card h2 {
- font-weight: 600;
- margin-bottom: 0.7rem;
+ font-weight: 600;
+ margin-bottom: 0.7rem;
}
.card p {
- margin: 0;
- opacity: 0.6;
- font-size: 0.9rem;
- line-height: 1.5;
- max-width: 30ch;
- text-wrap: balance;
+ margin: 0;
+ opacity: 0.6;
+ font-size: 0.9rem;
+ line-height: 1.5;
+ max-width: 30ch;
+ text-wrap: balance;
}
.center {
- display: flex;
- justify-content: center;
- align-items: center;
- position: relative;
- padding: 4rem 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ position: relative;
+ padding: 4rem 0;
}
.center::before {
- background: var(--secondary-glow);
- border-radius: 50%;
- width: 480px;
- height: 360px;
- margin-left: -400px;
+ background: var(--secondary-glow);
+ border-radius: 50%;
+ width: 480px;
+ height: 360px;
+ margin-left: -400px;
}
.center::after {
- background: var(--primary-glow);
- width: 240px;
- height: 180px;
- z-index: -1;
+ background: var(--primary-glow);
+ width: 240px;
+ height: 180px;
+ z-index: -1;
}
.center::before,
.center::after {
- content: "";
- left: 50%;
- position: absolute;
- filter: blur(45px);
- transform: translateZ(0);
+ content: "";
+ left: 50%;
+ position: absolute;
+ filter: blur(45px);
+ transform: translateZ(0);
}
.logo {
- position: relative;
+ position: relative;
}
+
/* Enable hover only on non-touch devices */
@media (hover: hover) and (pointer: fine) {
- .card:hover {
- background: rgba(var(--card-rgb), 0.1);
- border: 1px solid rgba(var(--card-border-rgb), 0.15);
- }
+ .card:hover {
+ background: rgba(var(--card-rgb), 0.1);
+ border: 1px solid rgba(var(--card-border-rgb), 0.15);
+ }
- .card:hover span {
- transform: translateX(4px);
- }
+ .card:hover span {
+ transform: translateX(4px);
+ }
}
@media (prefers-reduced-motion) {
- .card:hover span {
- transform: none;
- }
+ .card:hover span {
+ transform: none;
+ }
}
/* Mobile */
@media (max-width: 700px) {
- .content {
- padding: 4rem;
- }
-
- .grid {
- grid-template-columns: 1fr;
- margin-bottom: 120px;
- max-width: 320px;
- text-align: center;
- }
-
- .card {
- padding: 1rem 2.5rem;
- }
-
- .card h2 {
- margin-bottom: 0.5rem;
- }
-
- .center {
- padding: 8rem 0 6rem;
- }
-
- .center::before {
- transform: none;
- height: 300px;
- }
-
- .description {
- font-size: 0.8rem;
- }
-
- .description a {
- padding: 1rem;
- }
-
- .description p,
- .description div {
- display: flex;
- justify-content: center;
- position: fixed;
- width: 100%;
- }
-
- .description p {
- align-items: center;
- inset: 0 0 auto;
- padding: 2rem 1rem 1.4rem;
- border-radius: 0;
- border: none;
- border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
- background: linear-gradient(
- to bottom,
- rgba(var(--background-start-rgb), 1),
- rgba(var(--callout-rgb), 0.5)
- );
- background-clip: padding-box;
- backdrop-filter: blur(24px);
- }
-
- .description div {
- align-items: flex-end;
- pointer-events: none;
- inset: auto 0 0;
- padding: 2rem;
- height: 200px;
- background: linear-gradient(
- to bottom,
- transparent 0%,
- rgb(var(--background-end-rgb)) 40%
- );
- z-index: 1;
- }
+ .content {
+ padding: 4rem;
+ }
+
+ .grid {
+ grid-template-columns: 1fr;
+ margin-bottom: 120px;
+ max-width: 320px;
+ text-align: center;
+ }
+
+ .card {
+ padding: 1rem 2.5rem;
+ }
+
+ .card h2 {
+ margin-bottom: 0.5rem;
+ }
+
+ .center {
+ padding: 8rem 0 6rem;
+ }
+
+ .center::before {
+ transform: none;
+ height: 300px;
+ }
+
+ .description {
+ font-size: 0.8rem;
+ }
+
+ .description a {
+ padding: 1rem;
+ }
+
+ .description p,
+ .description div {
+ display: flex;
+ justify-content: center;
+ position: fixed;
+ width: 100%;
+ }
+
+ .description p {
+ align-items: center;
+ inset: 0 0 auto;
+ padding: 2rem 1rem 1.4rem;
+ border-radius: 0;
+ border: none;
+ border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
+ background: linear-gradient(to bottom,
+ rgba(var(--background-start-rgb), 1),
+ rgba(var(--callout-rgb), 0.5));
+ background-clip: padding-box;
+ backdrop-filter: blur(24px);
+ }
+
+ .description div {
+ align-items: flex-end;
+ pointer-events: none;
+ inset: auto 0 0;
+ padding: 2rem;
+ height: 200px;
+ background: linear-gradient(to bottom,
+ transparent 0%,
+ rgb(var(--background-end-rgb)) 40%);
+ z-index: 1;
+ }
}
/* Tablet and Smaller Desktop */
@media (min-width: 701px) and (max-width: 1120px) {
- .grid {
- grid-template-columns: repeat(2, 50%);
- }
+ .grid {
+ grid-template-columns: repeat(2, 50%);
+ }
}
@media (prefers-color-scheme: dark) {
- .vercelLogo {
- filter: invert(1);
- }
-
- .logo {
- filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
- }
+ .logo {
+ filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
+ }
}
@keyframes rotate {
- from {
- transform: rotate(360deg);
- }
- to {
- transform: rotate(0deg);
- }
+ from {
+ transform: rotate(360deg);
+ }
+
+ to {
+ transform: rotate(0deg);
+ }
}
diff --git a/src/interface/web/app/share/chat/layout.tsx b/src/interface/web/app/share/chat/layout.tsx
new file mode 100644
index 000000000..efd3c5f6b
--- /dev/null
+++ b/src/interface/web/app/share/chat/layout.tsx
@@ -0,0 +1,33 @@
+import type { Metadata } from "next";
+import { Noto_Sans } from "next/font/google";
+import "../../globals.css";
+
+const inter = Noto_Sans({ subsets: ["latin"] });
+
+export const metadata: Metadata = {
+ title: "Khoj AI - Chat",
+ description: "Use this page to view a chat with Khoj AI.",
+};
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
+
+ {children}
+
+
+ );
+}
diff --git a/src/interface/web/app/share/chat/page.tsx b/src/interface/web/app/share/chat/page.tsx
new file mode 100644
index 000000000..e9653dd0e
--- /dev/null
+++ b/src/interface/web/app/share/chat/page.tsx
@@ -0,0 +1,324 @@
+'use client'
+
+import styles from './sharedChat.module.css';
+import React, { Suspense, useEffect, useRef, useState } from 'react';
+
+import SidePanel from '../../components/sidePanel/chatHistorySidePanel';
+import ChatHistory from '../../components/chatHistory/chatHistory';
+import NavMenu from '../../components/navMenu/navMenu';
+import Loading from '../../components/loading/loading';
+
+import 'katex/dist/katex.min.css';
+
+import { welcomeConsole } from '../../common/utils';
+import { useAuthenticatedData } from '@/app/common/auth';
+
+import ChatInputArea, { ChatOptions } from '@/app/components/chatInputArea/chatInputArea';
+import { StreamMessage } from '@/app/components/chatMessage/chatMessage';
+import { handleCompiledReferences, handleImageResponse, setupWebSocket } from '@/app/common/chatFunctions';
+
+
+interface ChatBodyDataProps {
+ chatOptionsData: ChatOptions | null;
+ setTitle: (title: string) => void;
+ setUploadedFiles: (files: string[]) => void;
+ isMobileWidth?: boolean;
+ publicConversationSlug: string;
+ streamedMessages: StreamMessage[];
+ isLoggedIn: boolean;
+ conversationId?: string;
+ setQueryToProcess: (query: string) => void;
+}
+
+
+function ChatBodyData(props: ChatBodyDataProps) {
+ const [message, setMessage] = useState('');
+ const [processingMessage, setProcessingMessage] = useState(false);
+
+ useEffect(() => {
+ if (message) {
+ setProcessingMessage(true);
+ props.setQueryToProcess(message);
+ }
+ }, [message]);
+
+ useEffect(() => {
+ console.log("Streamed messages", props.streamedMessages);
+ if (props.streamedMessages &&
+ props.streamedMessages.length > 0 &&
+ props.streamedMessages[props.streamedMessages.length - 1].completed) {
+
+ setProcessingMessage(false);
+ } else {
+ setMessage('');
+ }
+ }, [props.streamedMessages]);
+
+ if (!props.publicConversationSlug && !props.conversationId) {
+ return (
+
+ Whoops, nothing to see here!
+
+ );
+ }
+
+ return (
+ <>
+
+
+
+
+ setMessage(message)}
+ sendDisabled={processingMessage}
+ chatOptionsData={props.chatOptionsData}
+ conversationId={props.conversationId}
+ isMobileWidth={props.isMobileWidth}
+ setUploadedFiles={props.setUploadedFiles} />
+
+ >
+ );
+}
+
+export default function SharedChat() {
+ const [chatOptionsData, setChatOptionsData] = useState(null);
+ const [isLoading, setLoading] = useState(true);
+ const [title, setTitle] = useState('Khoj AI - Chat');
+ const [conversationId, setConversationID] = useState(undefined);
+ const [chatWS, setChatWS] = useState(null);
+ const [messages, setMessages] = useState([]);
+ const [queryToProcess, setQueryToProcess] = useState('');
+ const [processQuerySignal, setProcessQuerySignal] = useState(false);
+ const [uploadedFiles, setUploadedFiles] = useState([]);
+ const [isMobileWidth, setIsMobileWidth] = useState(false);
+ const [paramSlug, setParamSlug] = useState(undefined);
+
+ const authenticatedData = useAuthenticatedData();
+
+ welcomeConsole();
+
+ const handleWebSocketMessage = (event: MessageEvent) => {
+ let chunk = event.data;
+
+ let currentMessage = messages.find(message => !message.completed);
+
+ if (!currentMessage) {
+ console.error("No current message found");
+ return;
+ }
+
+ // Process WebSocket streamed data
+ if (chunk === "start_llm_response") {
+ console.log("Started streaming", new Date());
+ } else if (chunk === "end_llm_response") {
+ currentMessage.completed = true;
+ } else {
+ // Get the current message
+ // Process and update state with the new message
+ if (chunk.includes("application/json")) {
+ chunk = JSON.parse(chunk);
+ }
+
+ const contentType = chunk["content-type"];
+ if (contentType === "application/json") {
+ try {
+ if (chunk.image || chunk.detail) {
+ let responseWithReference = handleImageResponse(chunk);
+ console.log("Image response", responseWithReference);
+ if (responseWithReference.response) currentMessage.rawResponse = responseWithReference.response;
+ if (responseWithReference.online) currentMessage.onlineContext = responseWithReference.online;
+ if (responseWithReference.context) currentMessage.context = responseWithReference.context;
+ } else if (chunk.type == "status") {
+ currentMessage.trainOfThought.push(chunk.message);
+ } else if (chunk.type == "rate_limit") {
+ console.log("Rate limit message", chunk);
+ currentMessage.rawResponse = chunk.message;
+ } else {
+ console.log("any message", chunk);
+ }
+ } catch (error) {
+ console.error("Error processing message", error);
+ currentMessage.completed = true;
+ } finally {
+ // no-op
+ }
+
+ } else {
+ // Update the current message with the new chunk
+ if (chunk && chunk.includes("### compiled references:")) {
+ let responseWithReference = handleCompiledReferences(chunk, "");
+ currentMessage.rawResponse += responseWithReference.response;
+
+ if (responseWithReference.response) currentMessage.rawResponse = responseWithReference.response;
+ if (responseWithReference.online) currentMessage.onlineContext = responseWithReference.online;
+ if (responseWithReference.context) currentMessage.context = responseWithReference.context;
+ } else {
+ // If the chunk is not a JSON object, just display it as is
+ currentMessage.rawResponse += chunk;
+ }
+
+ }
+ };
+ // Update the state with the new message, currentMessage
+ setMessages([...messages]);
+ }
+
+
+ useEffect(() => {
+ fetch('/api/chat/options')
+ .then(response => response.json())
+ .then((data: ChatOptions) => {
+ setLoading(false);
+ // Render chat options, if any
+ if (data) {
+ setChatOptionsData(data);
+ }
+ })
+ .catch(err => {
+ console.error(err);
+ return;
+ });
+
+ setIsMobileWidth(window.innerWidth < 786);
+
+ window.addEventListener('resize', () => {
+ setIsMobileWidth(window.innerWidth < 786);
+ });
+
+ setParamSlug(window.location.pathname.split('/').pop() || '');
+
+ }, []);
+
+ useEffect(() => {
+ if (queryToProcess && !conversationId) {
+ fetch(`/api/chat/share/fork?public_conversation_slug=${paramSlug}`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ })
+ .then(response => response.json())
+ .then(data => {
+ setConversationID(data.conversation_id);
+ })
+ .catch(err => {
+ console.error(err);
+ return;
+ });
+ return;
+ }
+
+
+ if (chatWS && queryToProcess) {
+ // Add a new object to the state
+ const newStreamMessage: StreamMessage = {
+ rawResponse: "",
+ trainOfThought: [],
+ context: [],
+ onlineContext: {},
+ completed: false,
+ timestamp: (new Date()).toISOString(),
+ rawQuery: queryToProcess || "",
+ }
+ setMessages(prevMessages => [...prevMessages, newStreamMessage]);
+ setProcessQuerySignal(true);
+ } else {
+ if (!chatWS) {
+ console.error("No WebSocket connection available");
+ }
+ if (!queryToProcess) {
+ console.error("No query to process");
+ }
+ }
+ }, [queryToProcess]);
+
+ useEffect(() => {
+ if (processQuerySignal && chatWS) {
+ setProcessQuerySignal(false);
+ chatWS.onmessage = handleWebSocketMessage;
+ chatWS?.send(queryToProcess);
+ }
+ }, [processQuerySignal]);
+
+ useEffect(() => {
+ if (chatWS) {
+ chatWS.onmessage = handleWebSocketMessage;
+ }
+ }, [chatWS]);
+
+ useEffect(() => {
+ (async () => {
+ if (conversationId) {
+ const newWS = await setupWebSocket(conversationId, queryToProcess);
+ if (!newWS) {
+ console.error("No WebSocket connection available");
+ return;
+ }
+ setChatWS(newWS);
+
+ // Add a new object to the state
+ const newStreamMessage: StreamMessage = {
+ rawResponse: "",
+ trainOfThought: [],
+ context: [],
+ onlineContext: {},
+ completed: false,
+ timestamp: (new Date()).toISOString(),
+ rawQuery: queryToProcess || "",
+ }
+ setMessages(prevMessages => [...prevMessages, newStreamMessage]);
+ }
+ })();
+ }, [conversationId]);
+
+ if (isLoading) {
+ return ;
+ }
+
+ if (!paramSlug) {
+ return (
+
+ Whoops, nothing to see here!
+
+ );
+ }
+
+
+ return (
+
+
+ {title}
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/interface/web/app/share/chat/sharedChat.module.css b/src/interface/web/app/share/chat/sharedChat.module.css
new file mode 100644
index 000000000..177d77cc3
--- /dev/null
+++ b/src/interface/web/app/share/chat/sharedChat.module.css
@@ -0,0 +1,127 @@
+div.main {
+ height: 100vh;
+ color: hsla(var(--foreground));
+}
+
+.suggestions {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+ gap: 1rem;
+ justify-content: center;
+}
+
+div.inputBox {
+ border: 1px solid var(--border-color);
+ border-radius: 16px;
+ box-shadow: 0 4px 10px var(--box-shadow-color);
+ margin-bottom: 20px;
+ gap: 12px;
+ padding-left: 20px;
+ padding-right: 20px;
+ align-content: center;
+}
+
+
+input.inputBox {
+ border: none;
+}
+
+input.inputBox:focus {
+ outline: none;
+ background-color: transparent;
+}
+
+div.inputBox:focus {
+ box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
+}
+
+div.chatBodyFull {
+ display: grid;
+ grid-template-columns: 1fr;
+ height: 100%;
+}
+
+button.inputBox {
+ border: none;
+ outline: none;
+ background-color: transparent;
+ cursor: pointer;
+ border-radius: 0.5rem;
+ padding: 0.5rem;
+ background: linear-gradient(var(--calm-green), var(--calm-blue));
+}
+
+div.chatBody {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ height: 100%;
+}
+
+.inputBox {
+ color: hsla(var(--foreground));
+}
+
+div.chatLayout {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ gap: 1rem;
+}
+
+div.chatBox {
+ display: grid;
+ height: 100%;
+}
+
+div.titleBar {
+ display: grid;
+ grid-template-columns: 1fr auto;
+}
+
+div.chatBoxBody {
+ display: grid;
+ height: 100%;
+ width: 70%;
+ margin: auto;
+}
+
+div.agentIndicator a {
+ display: flex;
+ text-align: center;
+ align-content: center;
+ align-items: center;
+}
+
+div.agentIndicator {
+ padding: 10px;
+}
+
+
+@media (max-width: 768px) {
+ div.chatBody {
+ grid-template-columns: 0fr 1fr;
+ }
+
+ div.chatBox {
+ padding: 0;
+ }
+}
+
+@media screen and (max-width: 768px) {
+ div.inputBox {
+ margin-bottom: 0px;
+ }
+
+ div.chatBoxBody {
+ width: 100%;
+ }
+
+ div.chatBox {
+ padding: 0;
+ }
+
+ div.chatLayout {
+ gap: 0;
+ grid-template-columns: 1fr;
+ }
+
+}
diff --git a/src/interface/web/components/ui/avatar.tsx b/src/interface/web/components/ui/avatar.tsx
new file mode 100644
index 000000000..51e507ba9
--- /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/collapsible.tsx b/src/interface/web/components/ui/collapsible.tsx
new file mode 100644
index 000000000..9fa48946a
--- /dev/null
+++ b/src/interface/web/components/ui/collapsible.tsx
@@ -0,0 +1,11 @@
+"use client"
+
+import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
+
+const Collapsible = CollapsiblePrimitive.Root
+
+const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
+
+const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
+
+export { Collapsible, CollapsibleTrigger, CollapsibleContent }
diff --git a/src/interface/web/components/ui/command.tsx b/src/interface/web/components/ui/command.tsx
new file mode 100644
index 000000000..ef793c869
--- /dev/null
+++ b/src/interface/web/components/ui/command.tsx
@@ -0,0 +1,155 @@
+"use client"
+
+import * as React from "react"
+import { type DialogProps } from "@radix-ui/react-dialog"
+import { Command as CommandPrimitive } from "cmdk"
+import { Search } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { Dialog, DialogContent } from "@/components/ui/dialog"
+
+const Command = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+Command.displayName = CommandPrimitive.displayName
+
+interface CommandDialogProps extends DialogProps {}
+
+const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
+ return (
+
+ )
+}
+
+const CommandInput = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+))
+
+CommandInput.displayName = CommandPrimitive.Input.displayName
+
+const CommandList = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+
+CommandList.displayName = CommandPrimitive.List.displayName
+
+const CommandEmpty = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>((props, ref) => (
+
+))
+
+CommandEmpty.displayName = CommandPrimitive.Empty.displayName
+
+const CommandGroup = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+
+CommandGroup.displayName = CommandPrimitive.Group.displayName
+
+const CommandSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+CommandSeparator.displayName = CommandPrimitive.Separator.displayName
+
+const CommandItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+
+CommandItem.displayName = CommandPrimitive.Item.displayName
+
+const CommandShortcut = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => {
+ return (
+
+ )
+}
+CommandShortcut.displayName = "CommandShortcut"
+
+export {
+ Command,
+ CommandDialog,
+ CommandInput,
+ CommandList,
+ CommandEmpty,
+ CommandGroup,
+ CommandItem,
+ CommandShortcut,
+ CommandSeparator,
+}
diff --git a/src/interface/web/components/ui/drawer.tsx b/src/interface/web/components/ui/drawer.tsx
new file mode 100644
index 000000000..6a0ef53dd
--- /dev/null
+++ b/src/interface/web/components/ui/drawer.tsx
@@ -0,0 +1,118 @@
+"use client"
+
+import * as React from "react"
+import { Drawer as DrawerPrimitive } from "vaul"
+
+import { cn } from "@/lib/utils"
+
+const Drawer = ({
+ shouldScaleBackground = true,
+ ...props
+}: React.ComponentProps) => (
+
+)
+Drawer.displayName = "Drawer"
+
+const DrawerTrigger = DrawerPrimitive.Trigger
+
+const DrawerPortal = DrawerPrimitive.Portal
+
+const DrawerClose = DrawerPrimitive.Close
+
+const DrawerOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
+
+const DrawerContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+
+ {children}
+
+
+))
+DrawerContent.displayName = "DrawerContent"
+
+const DrawerHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+DrawerHeader.displayName = "DrawerHeader"
+
+const DrawerFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+DrawerFooter.displayName = "DrawerFooter"
+
+const DrawerTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DrawerTitle.displayName = DrawerPrimitive.Title.displayName
+
+const DrawerDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DrawerDescription.displayName = DrawerPrimitive.Description.displayName
+
+export {
+ Drawer,
+ DrawerPortal,
+ DrawerOverlay,
+ DrawerTrigger,
+ DrawerClose,
+ DrawerContent,
+ DrawerHeader,
+ DrawerFooter,
+ DrawerTitle,
+ DrawerDescription,
+}
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 000000000..f69a0d64c
--- /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 000000000..5586fa9b2
--- /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 000000000..1419f5669
--- /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/popover.tsx b/src/interface/web/components/ui/popover.tsx
new file mode 100644
index 000000000..a0ec48bee
--- /dev/null
+++ b/src/interface/web/components/ui/popover.tsx
@@ -0,0 +1,31 @@
+"use client"
+
+import * as React from "react"
+import * as PopoverPrimitive from "@radix-ui/react-popover"
+
+import { cn } from "@/lib/utils"
+
+const Popover = PopoverPrimitive.Root
+
+const PopoverTrigger = PopoverPrimitive.Trigger
+
+const PopoverContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
+
+
+
+))
+PopoverContent.displayName = PopoverPrimitive.Content.displayName
+
+export { Popover, PopoverTrigger, PopoverContent }
diff --git a/src/interface/web/components/ui/progress.tsx b/src/interface/web/components/ui/progress.tsx
new file mode 100644
index 000000000..f0c3c29e4
--- /dev/null
+++ b/src/interface/web/components/ui/progress.tsx
@@ -0,0 +1,32 @@
+"use client"
+
+import * as React from "react"
+import * as ProgressPrimitive from "@radix-ui/react-progress"
+
+import { cn } from "@/lib/utils"
+
+interface ProgressProps extends React.ComponentPropsWithoutRef {
+ indicatorColor?: string
+}
+
+const Progress = React.forwardRef<
+ React.ElementRef,
+ ProgressProps
+>(({ className, value, indicatorColor, ...props }, ref) => (
+
+
+
+))
+Progress.displayName = ProgressPrimitive.Root.displayName
+
+export { Progress }
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 000000000..0b4a48d87
--- /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/sheet.tsx b/src/interface/web/components/ui/sheet.tsx
new file mode 100644
index 000000000..a37f17ba0
--- /dev/null
+++ b/src/interface/web/components/ui/sheet.tsx
@@ -0,0 +1,140 @@
+"use client"
+
+import * as React from "react"
+import * as SheetPrimitive from "@radix-ui/react-dialog"
+import { cva, type VariantProps } from "class-variance-authority"
+import { X } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Sheet = SheetPrimitive.Root
+
+const SheetTrigger = SheetPrimitive.Trigger
+
+const SheetClose = SheetPrimitive.Close
+
+const SheetPortal = SheetPrimitive.Portal
+
+const SheetOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
+
+const sheetVariants = cva(
+ "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
+ {
+ variants: {
+ side: {
+ top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
+ bottom:
+ "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
+ left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
+ right:
+ "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
+ },
+ },
+ defaultVariants: {
+ side: "right",
+ },
+ }
+)
+
+interface SheetContentProps
+ extends React.ComponentPropsWithoutRef,
+ VariantProps {}
+
+const SheetContent = React.forwardRef<
+ React.ElementRef,
+ SheetContentProps
+>(({ side = "right", className, children, ...props }, ref) => (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+))
+SheetContent.displayName = SheetPrimitive.Content.displayName
+
+const SheetHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+SheetHeader.displayName = "SheetHeader"
+
+const SheetFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+SheetFooter.displayName = "SheetFooter"
+
+const SheetTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+SheetTitle.displayName = SheetPrimitive.Title.displayName
+
+const SheetDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+SheetDescription.displayName = SheetPrimitive.Description.displayName
+
+export {
+ Sheet,
+ SheetPortal,
+ SheetOverlay,
+ SheetTrigger,
+ SheetClose,
+ SheetContent,
+ SheetHeader,
+ SheetFooter,
+ SheetTitle,
+ SheetDescription,
+}
diff --git a/src/interface/web/components/ui/textarea.tsx b/src/interface/web/components/ui/textarea.tsx
new file mode 100644
index 000000000..9f9a6dc56
--- /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/components/ui/toggle.tsx b/src/interface/web/components/ui/toggle.tsx
new file mode 100644
index 000000000..b6f2e7d32
--- /dev/null
+++ b/src/interface/web/components/ui/toggle.tsx
@@ -0,0 +1,45 @@
+"use client"
+
+import * as React from "react"
+import * as TogglePrimitive from "@radix-ui/react-toggle"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const toggleVariants = cva(
+ "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
+ {
+ variants: {
+ variant: {
+ default: "bg-transparent",
+ outline:
+ "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
+ },
+ size: {
+ default: "h-10 px-3",
+ sm: "h-9 px-2.5",
+ lg: "h-11 px-5",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+const Toggle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, variant, size, ...props }, ref) => (
+
+))
+
+Toggle.displayName = TogglePrimitive.Root.displayName
+
+export { Toggle, toggleVariants }
diff --git a/src/interface/web/package.json b/src/interface/web/package.json
index 575fff4d8..e762b9742 100644
--- a/src/interface/web/package.json
+++ b/src/interface/web/package.json
@@ -17,15 +17,28 @@
"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-collapsible": "^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-popover": "^1.1.1",
+ "@radix-ui/react-progress": "^1.1.0",
+ "@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
+ "@radix-ui/react-toggle": "^1.1.0",
+ "@types/dompurify": "^3.0.5",
"@types/katex": "^0.16.7",
"@types/markdown-it": "^14.1.1",
"autoprefixer": "^10.4.19",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
+ "cmdk": "^1.0.0",
+ "dompurify": "^3.1.6",
"katex": "^0.16.10",
"lucide-react": "^0.397.0",
"markdown-it": "^14.1.0",
@@ -38,7 +51,8 @@
"swr": "^2.2.5",
"tailwind-merge": "^2.3.0",
"tailwindcss": "^3.4.4",
- "tailwindcss-animate": "^1.0.7"
+ "tailwindcss-animate": "^1.0.7",
+ "vaul": "^0.9.1"
},
"devDependencies": {
"@types/node": "^20",
diff --git a/src/interface/web/tsconfig.json b/src/interface/web/tsconfig.json
index ee33bd894..608039017 100644
--- a/src/interface/web/tsconfig.json
+++ b/src/interface/web/tsconfig.json
@@ -1,40 +1,41 @@
{
- "compilerOptions": {
- "lib": [
- "dom",
- "dom.iterable",
- "esnext"
+ "compilerOptions": {
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "downlevelIteration": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": [
+ "./*"
+ ]
+ }
+ },
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts",
+ "out/types/**/*.ts"
],
- "allowJs": true,
- "skipLibCheck": true,
- "strict": true,
- "noEmit": true,
- "esModuleInterop": true,
- "module": "esnext",
- "moduleResolution": "bundler",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "jsx": "preserve",
- "incremental": true,
- "plugins": [
- {
- "name": "next"
- }
- ],
- "paths": {
- "@/*": [
- "./*"
- ]
- }
- },
- "include": [
- "next-env.d.ts",
- "**/*.ts",
- "**/*.tsx",
- ".next/types/**/*.ts",
- "out/types/**/*.ts"
- ],
- "exclude": [
- "node_modules"
- ]
+ "exclude": [
+ "node_modules"
+ ]
}
diff --git a/src/interface/web/yarn.lock b/src/interface/web/yarn.lock
index 646008d02..27c25989a 100644
--- a/src/interface/web/yarn.lock
+++ b/src/interface/web/yarn.lock
@@ -245,7 +245,7 @@
"@babel/helper-plugin-utils" "^7.24.7"
"@babel/plugin-syntax-typescript" "^7.24.7"
-"@babel/runtime@^7.23.2", "@babel/runtime@^7.24.1":
+"@babel/runtime@^7.13.10", "@babel/runtime@^7.23.2", "@babel/runtime@^7.24.1":
version "7.24.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12"
integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==
@@ -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,28 @@
"@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.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.1.tgz#e46f9958b35d10e9f6dc71c497305c22e3e55dbd"
+ integrity sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+
"@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,17 +525,93 @@
"@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-collapsible@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-collapsible/-/react-collapsible-1.1.0.tgz#4d49ddcc7b7d38f6c82f1fd29674f6fab5353e77"
+ integrity sha512-zQY7Epa8sTL0mq4ajSJpjgn2YmCgyrG7RsQgLp3C0LQVkG7+Tf6Pv1CeNWZLyqMjhdPkBa5Lx7wYBeSu7uCSTA==
+ 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-presence" "1.1.0"
+ "@radix-ui/react-primitive" "2.0.0"
+ "@radix-ui/react-use-controllable-state" "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.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz#7ed868b66946aa6030e580b1ffca386dd4d21989"
+ integrity sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+
"@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"
integrity sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==
+"@radix-ui/react-context@1.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.0.1.tgz#fe46e67c96b240de59187dcb7a1a50ce3e2ec00c"
+ integrity sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+
"@radix-ui/react-context@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.0.tgz#6df8d983546cfd1999c8512f3a8ad85a6e7fcee8"
integrity sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==
-"@radix-ui/react-dialog@1.1.1", "@radix-ui/react-dialog@^1.1.1":
+"@radix-ui/react-dialog@1.0.5":
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz#71657b1b116de6c7a0b03242d7d43e01062c7300"
+ integrity sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/primitive" "1.0.1"
+ "@radix-ui/react-compose-refs" "1.0.1"
+ "@radix-ui/react-context" "1.0.1"
+ "@radix-ui/react-dismissable-layer" "1.0.5"
+ "@radix-ui/react-focus-guards" "1.0.1"
+ "@radix-ui/react-focus-scope" "1.0.4"
+ "@radix-ui/react-id" "1.0.1"
+ "@radix-ui/react-portal" "1.0.4"
+ "@radix-ui/react-presence" "1.0.1"
+ "@radix-ui/react-primitive" "1.0.3"
+ "@radix-ui/react-slot" "1.0.2"
+ "@radix-ui/react-use-controllable-state" "1.0.1"
+ aria-hidden "^1.1.1"
+ react-remove-scroll "2.5.5"
+
+"@radix-ui/react-dialog@1.1.1", "@radix-ui/react-dialog@^1.0.4", "@radix-ui/react-dialog@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz#4906507f7b4ad31e22d7dad69d9330c87c431d44"
integrity sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==
@@ -511,6 +631,23 @@
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.0.5":
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz#3f98425b82b9068dfbab5db5fff3df6ebf48b9d4"
+ integrity sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/primitive" "1.0.1"
+ "@radix-ui/react-compose-refs" "1.0.1"
+ "@radix-ui/react-primitive" "1.0.3"
+ "@radix-ui/react-use-callback-ref" "1.0.1"
+ "@radix-ui/react-use-escape-keydown" "1.0.3"
+
"@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,11 +659,41 @@
"@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.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz#1ea7e32092216b946397866199d892f71f7f98ad"
+ integrity sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+
"@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"
integrity sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==
+"@radix-ui/react-focus-scope@1.0.4":
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz#2ac45fce8c5bb33eb18419cdc1905ef4f1906525"
+ integrity sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/react-compose-refs" "1.0.1"
+ "@radix-ui/react-primitive" "1.0.3"
+ "@radix-ui/react-use-callback-ref" "1.0.1"
+
"@radix-ui/react-focus-scope@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz#ebe2891a298e0a33ad34daab2aad8dea31caf0b2"
@@ -536,6 +703,14 @@
"@radix-ui/react-primitive" "2.0.0"
"@radix-ui/react-use-callback-ref" "1.1.0"
+"@radix-ui/react-id@1.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.1.tgz#73cdc181f650e4df24f0b6a5b7aa426b912c88c0"
+ integrity sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/react-use-layout-effect" "1.0.1"
+
"@radix-ui/react-id@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.1.0.tgz#de47339656594ad722eb87f94a6b25f9cffae0ed"
@@ -550,6 +725,111 @@
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-popover@^1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-popover/-/react-popover-1.1.1.tgz#604b783cdb3494ed4f16a58c17f0e81e61ab7775"
+ integrity sha512-3y1A3isulwnWhvTTwmIreiB8CF4L+qRjZnK1wYLO7pplddzXKby/GnZ2M7OZY3qgnl6p9AodUIHRYGXNah8Y7g==
+ 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-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-slot" "1.1.0"
+ "@radix-ui/react-use-controllable-state" "1.1.0"
+ aria-hidden "^1.1.1"
+ react-remove-scroll "2.5.7"
+
+"@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.0.4":
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.4.tgz#df4bfd353db3b1e84e639e9c63a5f2565fb00e15"
+ integrity sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/react-primitive" "1.0.3"
+
"@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"
@@ -558,6 +838,15 @@
"@radix-ui/react-primitive" "2.0.0"
"@radix-ui/react-use-layout-effect" "1.1.0"
+"@radix-ui/react-presence@1.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.0.1.tgz#491990ba913b8e2a5db1b06b203cb24b5cdef9ba"
+ integrity sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/react-compose-refs" "1.0.1"
+ "@radix-ui/react-use-layout-effect" "1.0.1"
+
"@radix-ui/react-presence@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.0.tgz#227d84d20ca6bfe7da97104b1a8b48a833bfb478"
@@ -566,6 +855,14 @@
"@radix-ui/react-compose-refs" "1.1.0"
"@radix-ui/react-use-layout-effect" "1.1.0"
+"@radix-ui/react-primitive@1.0.3":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz#d49ea0f3f0b2fe3ab1cb5667eb03e8b843b914d0"
+ integrity sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/react-slot" "1.0.2"
+
"@radix-ui/react-primitive@2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz#fe05715faa9203a223ccc0be15dc44b9f9822884"
@@ -573,6 +870,52 @@
dependencies:
"@radix-ui/react-slot" "1.1.0"
+"@radix-ui/react-progress@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-progress/-/react-progress-1.1.0.tgz#28c267885ec154fc557ec7a66cb462787312f7e2"
+ integrity sha512-aSzvnYpP725CROcxAOEBVZZSIQVQdHgBr2QQFKySsaD14u8dNT0batuXI+AAGDdAHfXH8rbnHmjYFqVJ21KkRg==
+ dependencies:
+ "@radix-ui/react-context" "1.1.0"
+ "@radix-ui/react-primitive" "2.0.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.0.2":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.2.tgz#a9ff4423eade67f501ffb32ec22064bc9d3099ab"
+ integrity sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/react-compose-refs" "1.0.1"
+
"@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"
@@ -580,11 +923,35 @@
dependencies:
"@radix-ui/react-compose-refs" "1.1.0"
+"@radix-ui/react-toggle@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-toggle/-/react-toggle-1.1.0.tgz#1f7697b82917019330a16c6f96f649f46b4606cf"
+ integrity sha512-gwoxaKZ0oJ4vIgzsfESBuSgJNdc0rv12VhHgcqN0TEJmmZixXG/2XpsLK8kzNWYcnaoRIEEQc0bEi3dIvdUpjw==
+ dependencies:
+ "@radix-ui/primitive" "1.1.0"
+ "@radix-ui/react-primitive" "2.0.0"
+ "@radix-ui/react-use-controllable-state" "1.1.0"
+
+"@radix-ui/react-use-callback-ref@1.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz#f4bb1f27f2023c984e6534317ebc411fc181107a"
+ integrity sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+
"@radix-ui/react-use-callback-ref@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz#bce938ca413675bc937944b0d01ef6f4a6dc5bf1"
integrity sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==
+"@radix-ui/react-use-controllable-state@1.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz#ecd2ced34e6330caf89a82854aa2f77e07440286"
+ integrity sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/react-use-callback-ref" "1.0.1"
+
"@radix-ui/react-use-controllable-state@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz#1321446857bb786917df54c0d4d084877aab04b0"
@@ -592,6 +959,14 @@
dependencies:
"@radix-ui/react-use-callback-ref" "1.1.0"
+"@radix-ui/react-use-escape-keydown@1.0.3":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz#217b840c250541609c66f67ed7bab2b733620755"
+ integrity sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/react-use-callback-ref" "1.0.1"
+
"@radix-ui/react-use-escape-keydown@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz#31a5b87c3b726504b74e05dac1edce7437b98754"
@@ -599,11 +974,49 @@
dependencies:
"@radix-ui/react-use-callback-ref" "1.1.0"
+"@radix-ui/react-use-layout-effect@1.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz#be8c7bc809b0c8934acf6657b577daf948a75399"
+ integrity sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+
"@radix-ui/react-use-layout-effect@1.1.0":
version "1.1.0"
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"
@@ -632,6 +1045,13 @@
mkdirp "^2.1.6"
path-browserify "^1.0.1"
+"@types/dompurify@^3.0.5":
+ version "3.0.5"
+ resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-3.0.5.tgz#02069a2fcb89a163bacf1a788f73cb415dd75cb7"
+ integrity sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==
+ dependencies:
+ "@types/trusted-types" "*"
+
"@types/json5@^0.0.29":
version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
@@ -687,6 +1107,11 @@
"@types/prop-types" "*"
csstype "^3.0.2"
+"@types/trusted-types@*":
+ version "2.0.7"
+ resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11"
+ integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==
+
"@typescript-eslint/parser@^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.2.0.tgz#44356312aea8852a3a82deebdacd52ba614ec07a"
@@ -1161,6 +1586,14 @@ clsx@^2.1.1:
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
+cmdk@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/cmdk/-/cmdk-1.0.0.tgz#0a095fdafca3dfabed82d1db78a6262fb163ded9"
+ integrity sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==
+ dependencies:
+ "@radix-ui/react-dialog" "1.0.5"
+ "@radix-ui/react-primitive" "1.0.3"
+
code-block-writer@^12.0.0:
version "12.0.0"
resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-12.0.0.tgz#4dd58946eb4234105aff7f0035977b2afdc2a770"
@@ -1371,6 +1804,11 @@ doctrine@^3.0.0:
dependencies:
esutils "^2.0.2"
+dompurify@^3.1.6:
+ version "3.1.6"
+ resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.1.6.tgz#43c714a94c6a7b8801850f82e756685300a027e2"
+ integrity sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==
+
eastasianwidth@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
@@ -3164,7 +3602,7 @@ react-is@^16.13.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
-react-remove-scroll-bar@^2.3.4:
+react-remove-scroll-bar@^2.3.3, react-remove-scroll-bar@^2.3.4:
version "2.3.6"
resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz#3e585e9d163be84a010180b18721e851ac81a29c"
integrity sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==
@@ -3172,6 +3610,17 @@ react-remove-scroll-bar@^2.3.4:
react-style-singleton "^2.2.1"
tslib "^2.0.0"
+react-remove-scroll@2.5.5:
+ version "2.5.5"
+ resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz#1e31a1260df08887a8a0e46d09271b52b3a37e77"
+ integrity sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==
+ dependencies:
+ react-remove-scroll-bar "^2.3.3"
+ react-style-singleton "^2.2.1"
+ tslib "^2.1.0"
+ use-callback-ref "^1.3.0"
+ use-sidecar "^1.1.2"
+
react-remove-scroll@2.5.7:
version "2.5.7"
resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz#15a1fd038e8497f65a695bf26a4a57970cac1ccb"
@@ -3884,6 +4333,13 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
+vaul@^0.9.1:
+ version "0.9.1"
+ resolved "https://registry.yarnpkg.com/vaul/-/vaul-0.9.1.tgz#3640198e04636b209b1f907fcf3079bec6ecc66b"
+ integrity sha512-fAhd7i4RNMinx+WEm6pF3nOl78DFkAazcN04ElLPFF9BMCNGbY/kou8UMhIcicm0rJCNePJP0Yyza60gGOD0Jw==
+ dependencies:
+ "@radix-ui/react-dialog" "^1.0.4"
+
wcwidth@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
diff --git a/src/khoj/database/adapters/__init__.py b/src/khoj/database/adapters/__init__.py
index 20019c567..cffb2be38 100644
--- a/src/khoj/database/adapters/__init__.py
+++ b/src/khoj/database/adapters/__init__.py
@@ -645,7 +645,11 @@ def get_conversation_by_user(
@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(
@@ -799,11 +803,14 @@ async def aget_summarizer_conversation_config():
def create_conversation_from_public_conversation(
user: KhojUser, public_conversation: PublicConversation, client_app: ClientApplication
):
+ scrubbed_title = public_conversation.title if public_conversation.title else public_conversation.slug
+ if scrubbed_title:
+ scrubbed_title = scrubbed_title.replace("-", " ")
return Conversation.objects.create(
user=user,
conversation_log=public_conversation.conversation_log,
client=client_app,
- slug=public_conversation.slug,
+ slug=scrubbed_title,
title=public_conversation.title,
agent=public_conversation.agent,
)
@@ -944,6 +951,34 @@ async def aset_user_text_to_image_model(user: KhojUser, text_to_image_model_conf
)
return new_config
+ @staticmethod
+ def add_files_to_filter(user: KhojUser, conversation_id: int, files: List[str]):
+ conversation = ConversationAdapters.get_conversation_by_user(user, conversation_id=conversation_id)
+ file_list = EntryAdapters.get_all_filenames_by_source(user, "computer")
+ for filename in files:
+ if filename in file_list and filename not in conversation.file_filters:
+ conversation.file_filters.append(filename)
+ conversation.save()
+
+ # remove files from conversation.file_filters that are not in file_list
+ conversation.file_filters = [file for file in conversation.file_filters if file in file_list]
+ conversation.save()
+ return conversation.file_filters
+
+ @staticmethod
+ def remove_files_from_filter(user: KhojUser, conversation_id: int, files: List[str]):
+ conversation = ConversationAdapters.get_conversation_by_user(user, conversation_id=conversation_id)
+ for filename in files:
+ if filename in conversation.file_filters:
+ conversation.file_filters.remove(filename)
+ conversation.save()
+
+ # remove files from conversation.file_filters that are not in file_list
+ file_list = EntryAdapters.get_all_filenames_by_source(user, "computer")
+ conversation.file_filters = [file for file in conversation.file_filters if file in file_list]
+ conversation.save()
+ return conversation.file_filters
+
class FileObjectAdapters:
@staticmethod
diff --git a/src/khoj/routers/api.py b/src/khoj/routers/api.py
index cbe198918..ef5394acf 100644
--- a/src/khoj/routers/api.py
+++ b/src/khoj/routers/api.py
@@ -372,7 +372,7 @@ async def extract_references_and_questions(
logger.info(f"🔍 Searching knowledge base with queries: {inferred_queries}")
if send_status_func:
inferred_queries_str = "\n- " + "\n- ".join(inferred_queries)
- await send_status_func(f"**🔍 Searching Documents for:** {inferred_queries_str}")
+ await send_status_func(f"**Searching Documents for:** {inferred_queries_str}")
for query in inferred_queries:
n_items = min(n, 3) if using_offline_chat else n
search_results.extend(
diff --git a/src/khoj/routers/api_chat.py b/src/khoj/routers/api_chat.py
index be28622b4..debb6f61f 100644
--- a/src/khoj/routers/api_chat.py
+++ b/src/khoj/routers/api_chat.py
@@ -2,7 +2,7 @@
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
@@ -16,7 +16,6 @@
from khoj.app.settings import ALLOWED_HOSTS
from khoj.database.adapters import (
ConversationAdapters,
- DataStoreAdapters,
EntryAdapters,
FileObjectAdapters,
PublicConversationAdapters,
@@ -58,7 +57,7 @@
get_device,
is_none_or_empty,
)
-from khoj.utils.rawconfig import FilterRequest, LocationData
+from khoj.utils.rawconfig import FileFilterRequest, FilesFilterRequest, LocationData
# Initialize Router
logger = logging.getLogger(__name__)
@@ -92,68 +91,36 @@ 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)
+@api_chat.delete("/conversation/file-filters/bulk", 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__,
- )
+def remove_files_filter(request: Request, filter: FilesFilterRequest) -> Response:
+ conversation_id = int(filter.conversation_id)
+ files_filter = filter.filenames
+ file_filters = ConversationAdapters.remove_files_from_filter(request.user.object, conversation_id, files_filter)
+ return Response(content=json.dumps(file_filters), media_type="application/json", status_code=200)
- 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/bulk", response_class=Response)
+@requires(["authenticated"])
+def add_files_filter(request: Request, filter: FilesFilterRequest):
+ try:
+ conversation_id = int(filter.conversation_id)
+ files_filter = filter.filenames
+ file_filters = ConversationAdapters.add_files_to_filter(request.user.object, conversation_id, files_filter)
+ return Response(content=json.dumps(file_filters), media_type="application/json", status_code=200)
+ except Exception as e:
+ logger.error(f"Error adding file filter {filter.filename}: {e}", exc_info=True)
+ raise HTTPException(status_code=422, detail=str(e))
@api_chat.post("/conversation/file-filters", response_class=Response)
@requires(["authenticated"])
-def add_file_filter(request: Request, filter: FilterRequest):
+def add_file_filter(request: Request, filter: FileFilterRequest):
try:
- conversation = ConversationAdapters.get_conversation_by_user(
- request.user.object, conversation_id=int(filter.conversation_id)
- )
- file_list = EntryAdapters.get_all_filenames_by_source(request.user.object, "computer")
- if filter.filename in file_list and filter.filename not in conversation.file_filters:
- conversation.file_filters.append(filter.filename)
- conversation.save()
- # remove files from conversation.file_filters that are not in file_list
- conversation.file_filters = [file for file in conversation.file_filters if file in file_list]
- conversation.save()
- return Response(content=json.dumps(conversation.file_filters), media_type="application/json", status_code=200)
+ conversation_id = int(filter.conversation_id)
+ files_filter = [filter.filename]
+ file_filters = ConversationAdapters.add_files_to_filter(request.user.object, conversation_id, files_filter)
+ return Response(content=json.dumps(file_filters), media_type="application/json", status_code=200)
except Exception as e:
logger.error(f"Error adding file filter {filter.filename}: {e}", exc_info=True)
raise HTTPException(status_code=422, detail=str(e))
@@ -161,18 +128,11 @@ def add_file_filter(request: Request, filter: FilterRequest):
@api_chat.delete("/conversation/file-filters", response_class=Response)
@requires(["authenticated"])
-def remove_file_filter(request: Request, filter: FilterRequest) -> Response:
- conversation = ConversationAdapters.get_conversation_by_user(
- request.user.object, conversation_id=int(filter.conversation_id)
- )
- if filter.filename in conversation.file_filters:
- conversation.file_filters.remove(filter.filename)
- conversation.save()
- # remove files from conversation.file_filters that are not in file_list
- file_list = EntryAdapters.get_all_filenames_by_source(request.user.object, "computer")
- conversation.file_filters = [file for file in conversation.file_filters if file in file_list]
- conversation.save()
- return Response(content=json.dumps(conversation.file_filters), media_type="application/json", status_code=200)
+def remove_file_filter(request: Request, filter: FileFilterRequest) -> Response:
+ conversation_id = int(filter.conversation_id)
+ files_filter = [filter.filename]
+ file_filters = ConversationAdapters.remove_files_from_filter(request.user.object, conversation_id, files_filter)
+ return Response(content=json.dumps(file_filters), media_type="application/json", status_code=200)
class FeedbackData(BaseModel):
@@ -309,10 +269,15 @@ def get_shared_chat(
}
meta_log = conversation.conversation_log
+ scrubbed_title = conversation.title if conversation.title else conversation.slug
+
+ if scrubbed_title:
+ scrubbed_title = scrubbed_title.replace("-", " ")
+
meta_log.update(
{
"conversation_id": conversation.id,
- "slug": conversation.title if conversation.title else conversation.slug,
+ "slug": scrubbed_title,
"agent": agent_metadata,
}
)
@@ -328,7 +293,7 @@ def get_shared_chat(
update_telemetry_state(
request=request,
telemetry_type="api",
- api="public_conversation_history",
+ api="chat_history",
**common.__dict__,
)
@@ -370,7 +335,7 @@ def fork_public_conversation(
public_conversation = PublicConversationAdapters.get_public_conversation_by_slug(public_conversation_slug)
# Duplicate Public Conversation to User's Private Conversation
- ConversationAdapters.create_conversation_from_public_conversation(
+ new_conversation = ConversationAdapters.create_conversation_from_public_conversation(
user, public_conversation, request.user.client_app
)
@@ -386,7 +351,16 @@ def fork_public_conversation(
redirect_uri = str(request.app.url_path_for("chat_page"))
- return Response(status_code=200, content=json.dumps({"status": "ok", "next_url": redirect_uri}))
+ return Response(
+ status_code=200,
+ content=json.dumps(
+ {
+ "status": "ok",
+ "next_url": redirect_uri,
+ "conversation_id": new_conversation.id,
+ }
+ ),
+ )
@api_chat.post("/share")
@@ -427,15 +401,29 @@ 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]
+
+ sessions = conversations.values_list(
+ "id", "slug", "title", "agent__slug", "agent__name", "agent__avatar", "created_at"
)
- session_values = [{"conversation_id": session[0], "slug": session[2] or session[1]} for session in sessions]
+ session_values = [
+ {
+ "conversation_id": session[0],
+ "slug": session[2] or session[1],
+ "agent_name": session[4],
+ "agent_avatar": session[5],
+ "created": session[6].strftime("%Y-%m-%d %H:%M:%S"),
+ }
+ for session in sessions
+ ]
update_telemetry_state(
request=request,
@@ -477,7 +465,6 @@ async def create_chat_session(
@api_chat.get("/options", response_class=Response)
-@requires(["authenticated"])
async def chat_options(
request: Request,
common: CommonQueryParams,
@@ -641,7 +628,7 @@ async def send_rate_limit_message(message: str):
user_message_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
conversation_commands = [get_conversation_command(query=q, any_references=True)]
- await send_status_update(f"**👀 Understanding Query**: {q}")
+ await send_status_update(f"**Understanding Query**: {q}")
meta_log = conversation.conversation_log
is_automated_task = conversation_commands == [ConversationCommand.AutomatedTask]
@@ -650,10 +637,10 @@ async def send_rate_limit_message(message: str):
if conversation_commands == [ConversationCommand.Default] or is_automated_task:
conversation_commands = await aget_relevant_information_sources(q, meta_log, is_automated_task)
conversation_commands_str = ", ".join([cmd.value for cmd in conversation_commands])
- await send_status_update(f"**🗃️ Chose Data Sources to Search:** {conversation_commands_str}")
+ await send_status_update(f"**Chose Data Sources to Search:** {conversation_commands_str}")
mode = await aget_relevant_output_modes(q, meta_log, is_automated_task)
- await send_status_update(f"**🧑🏾💻 Decided Response Mode:** {mode.value}")
+ await send_status_update(f"**Decided Response Mode:** {mode.value}")
if mode not in conversation_commands:
conversation_commands.append(mode)
@@ -690,7 +677,7 @@ async def send_rate_limit_message(message: str):
contextual_data = " ".join([file.raw_text for file in file_object])
if not q:
q = "Create a general summary of the file"
- await send_status_update(f"**🧑🏾💻 Constructing Summary Using:** {file_object[0].file_name}")
+ await send_status_update(f"**Constructing Summary Using:** {file_object[0].file_name}")
response = await extract_relevant_summary(q, contextual_data)
response_log = str(response)
await send_complete_llm_response(response_log)
@@ -775,7 +762,7 @@ async def send_rate_limit_message(message: str):
if compiled_references:
headings = "\n- " + "\n- ".join(set([c.get("compiled", c).split("\n")[0] for c in compiled_references]))
- await send_status_update(f"**📜 Found Relevant Notes**: {headings}")
+ await send_status_update(f"**Found Relevant Notes**: {headings}")
online_results: Dict = dict()
@@ -811,7 +798,7 @@ async def send_rate_limit_message(message: str):
for webpage in direct_web_pages[query]["webpages"]:
webpages.append(webpage["link"])
- await send_status_update(f"**📚 Read web pages**: {webpages}")
+ await send_status_update(f"**Read web pages**: {webpages}")
except ValueError as e:
logger.warning(
f"Error directly reading webpages: {e}. Attempting to respond without online results", exc_info=True
@@ -861,7 +848,7 @@ async def send_rate_limit_message(message: str):
await send_complete_llm_response(json.dumps(content_obj))
continue
- await send_status_update(f"**💭 Generating a well-informed response**")
+ await send_status_update(f"**Generating a well-informed response**")
llm_response, chat_metadata = await agenerate_chat_response(
defiltered_query,
meta_log,
diff --git a/src/khoj/utils/rawconfig.py b/src/khoj/utils/rawconfig.py
index 8ed635e65..617f37ea2 100644
--- a/src/khoj/utils/rawconfig.py
+++ b/src/khoj/utils/rawconfig.py
@@ -27,11 +27,16 @@ class LocationData(BaseModel):
country: Optional[str]
-class FilterRequest(BaseModel):
+class FileFilterRequest(BaseModel):
filename: str
conversation_id: str
+class FilesFilterRequest(BaseModel):
+ filenames: List[str]
+ conversation_id: str
+
+
class TextConfigBase(ConfigBase):
compressed_jsonl: Path
embeddings_file: Path