mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-04 13:20:17 +00:00
Format next.js web app with prettier
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
'use client'
|
||||
"use client";
|
||||
|
||||
import styles from "./sidePanel.module.css";
|
||||
|
||||
@@ -40,10 +40,22 @@ import {
|
||||
DrawerTrigger,
|
||||
} from "@/components/ui/drawer";
|
||||
|
||||
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
|
||||
import { ArrowRight, ArrowLeft, ArrowDown, Spinner, Check, FolderPlus, DotsThreeVertical, House, StackPlus, UserCirclePlus, Sidebar, NotePencil } from "@phosphor-icons/react";
|
||||
import {
|
||||
ArrowRight,
|
||||
ArrowLeft,
|
||||
ArrowDown,
|
||||
Spinner,
|
||||
Check,
|
||||
FolderPlus,
|
||||
DotsThreeVertical,
|
||||
House,
|
||||
StackPlus,
|
||||
UserCirclePlus,
|
||||
Sidebar,
|
||||
NotePencil,
|
||||
} from "@phosphor-icons/react";
|
||||
|
||||
interface ChatHistory {
|
||||
conversation_id: string;
|
||||
@@ -62,17 +74,23 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
|
||||
import { Pencil, Trash, Share } from "@phosphor-icons/react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog";
|
||||
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";
|
||||
import { KhojLogo, KhojLogoType } from "@/app/components/logo/khogLogo";
|
||||
@@ -89,15 +107,14 @@ function renameConversation(conversationId: string, newTitle: string) {
|
||||
const editUrl = `/api/chat/title?client=web&conversation_id=${conversationId}&title=${newTitle}`;
|
||||
|
||||
fetch(editUrl, {
|
||||
method: 'PATCH',
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
})
|
||||
.catch(err => {
|
||||
.then((response) => response.json())
|
||||
.then((data) => {})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
return;
|
||||
});
|
||||
@@ -107,16 +124,16 @@ function shareConversation(conversationId: string, setShareUrl: (url: string) =>
|
||||
const shareUrl = `/api/chat/share?client=web&conversation_id=${conversationId}`;
|
||||
|
||||
fetch(shareUrl, {
|
||||
method: 'POST',
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
setShareUrl(data.url);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
return;
|
||||
});
|
||||
@@ -126,15 +143,14 @@ function deleteConversation(conversationId: string) {
|
||||
const deleteUrl = `/api/chat/history?client=web&conversation_id=${conversationId}`;
|
||||
|
||||
fetch(deleteUrl, {
|
||||
method: 'DELETE',
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
})
|
||||
.catch(err => {
|
||||
.then((response) => response.json())
|
||||
.then((data) => {})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
return;
|
||||
});
|
||||
@@ -148,8 +164,14 @@ interface FilesMenuProps {
|
||||
|
||||
function FilesMenu(props: FilesMenuProps) {
|
||||
// Use SWR to fetch files
|
||||
const { data: files, error } = useSWR<string[]>(props.conversationId ? '/api/content/computer' : null, fetcher);
|
||||
const { data: selectedFiles, error: selectedFilesError } = useSWR(props.conversationId ? `/api/chat/conversation/file-filters/${props.conversationId}` : null, fetcher);
|
||||
const { data: files, error } = useSWR<string[]>(
|
||||
props.conversationId ? "/api/content/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<string[]>([]);
|
||||
const [addedFiles, setAddedFiles] = useState<string[]>([]);
|
||||
@@ -163,11 +185,12 @@ function FilesMenu(props: FilesMenuProps) {
|
||||
|
||||
if (addedFiles) {
|
||||
console.log("addedFiles in useeffect hook", addedFiles);
|
||||
sortedFiles = addedFiles.concat(sortedFiles.filter((filename: string) => !addedFiles.includes(filename)));
|
||||
sortedFiles = addedFiles.concat(
|
||||
sortedFiles.filter((filename: string) => !addedFiles.includes(filename)),
|
||||
);
|
||||
}
|
||||
|
||||
setUnfilteredFiles(sortedFiles);
|
||||
|
||||
}, [files, addedFiles]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -180,18 +203,22 @@ function FilesMenu(props: FilesMenuProps) {
|
||||
if (selectedFiles) {
|
||||
setAddedFiles(selectedFiles);
|
||||
}
|
||||
|
||||
}, [selectedFiles]);
|
||||
|
||||
const removeAllFiles = () => {
|
||||
modifyFileFilterForConversation(props.conversationId, addedFiles, setAddedFiles, 'remove');
|
||||
}
|
||||
modifyFileFilterForConversation(props.conversationId, addedFiles, setAddedFiles, "remove");
|
||||
};
|
||||
|
||||
const addAllFiles = () => {
|
||||
modifyFileFilterForConversation(props.conversationId, unfilteredFiles, setAddedFiles, 'add');
|
||||
}
|
||||
modifyFileFilterForConversation(
|
||||
props.conversationId,
|
||||
unfilteredFiles,
|
||||
setAddedFiles,
|
||||
"add",
|
||||
);
|
||||
};
|
||||
|
||||
if (!props.conversationId) return (<></>);
|
||||
if (!props.conversationId) return <></>;
|
||||
|
||||
if (error) return <div>Failed to load files</div>;
|
||||
if (selectedFilesError) return <div>Failed to load selected files</div>;
|
||||
@@ -203,7 +230,9 @@ function FilesMenu(props: FilesMenuProps) {
|
||||
<Command>
|
||||
<CommandInput placeholder="Find file" />
|
||||
<CommandList>
|
||||
<CommandEmpty>No results found. <Link href="/search">Try advanced search</Link>.</CommandEmpty>
|
||||
<CommandEmpty>
|
||||
No results found. <Link href="/search">Try advanced search</Link>.
|
||||
</CommandEmpty>
|
||||
<CommandGroup heading="Quick">
|
||||
<CommandItem
|
||||
onSelect={() => {
|
||||
@@ -223,36 +252,47 @@ function FilesMenu(props: FilesMenuProps) {
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
<CommandGroup heading="Configure files">
|
||||
{unfilteredFiles.map((filename: string) => (
|
||||
addedFiles && addedFiles.includes(filename) ?
|
||||
{unfilteredFiles.map((filename: string) =>
|
||||
addedFiles && addedFiles.includes(filename) ? (
|
||||
<CommandItem
|
||||
key={filename}
|
||||
value={filename}
|
||||
className="bg-accent text-accent-foreground mb-1"
|
||||
onSelect={(value) => {
|
||||
modifyFileFilterForConversation(props.conversationId, [value], setAddedFiles, 'remove');
|
||||
modifyFileFilterForConversation(
|
||||
props.conversationId,
|
||||
[value],
|
||||
setAddedFiles,
|
||||
"remove",
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Check className="h-4 w-4 mr-2" />
|
||||
<span className="break-all">{filename}</span>
|
||||
</CommandItem>
|
||||
:
|
||||
) : (
|
||||
<CommandItem
|
||||
key={filename}
|
||||
className="mb-1"
|
||||
value={filename}
|
||||
onSelect={(value) => {
|
||||
modifyFileFilterForConversation(props.conversationId, [value], setAddedFiles, 'add');
|
||||
modifyFileFilterForConversation(
|
||||
props.conversationId,
|
||||
[value],
|
||||
setAddedFiles,
|
||||
"add",
|
||||
);
|
||||
}}
|
||||
>
|
||||
<span className="break-all">{filename}</span>
|
||||
</CommandItem>
|
||||
))}
|
||||
),
|
||||
)}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (props.isMobileWidth) {
|
||||
return (
|
||||
@@ -264,7 +304,9 @@ function FilesMenu(props: FilesMenuProps) {
|
||||
<DrawerContent>
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>Files</DrawerTitle>
|
||||
<DrawerDescription>Manage files for this conversation</DrawerDescription>
|
||||
<DrawerDescription>
|
||||
Manage files for this conversation
|
||||
</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<div className={`${styles.panelWrapper}`}>
|
||||
<FilesMenuCommandBox />
|
||||
@@ -282,27 +324,26 @@ function FilesMenu(props: FilesMenuProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Popover
|
||||
open={isOpen}
|
||||
onOpenChange={setIsOpen}>
|
||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<div
|
||||
className="w-auto bg-background border border-muted p-4 drop-shadow-sm rounded-2xl my-8">
|
||||
<div className="w-auto bg-background border border-muted p-4 drop-shadow-sm rounded-2xl my-8">
|
||||
<div className="flex items-center justify-between space-x-4">
|
||||
<h4 className="text-sm font-semibold">
|
||||
Manage Context
|
||||
<p>
|
||||
<span className="text-muted-foreground text-xs">Using {addedFiles.length == 0 ? files.length : addedFiles.length} files</span>
|
||||
<span className="text-muted-foreground text-xs">
|
||||
Using{" "}
|
||||
{addedFiles.length == 0 ? files.length : addedFiles.length}{" "}
|
||||
files
|
||||
</span>
|
||||
</p>
|
||||
</h4>
|
||||
<Button variant="ghost" size="sm" className="w-9 p-0">
|
||||
{
|
||||
isOpen ?
|
||||
<ArrowDown className="h-4 w-4" />
|
||||
:
|
||||
<ArrowRight className="h-4 w-4" />
|
||||
|
||||
}
|
||||
{isOpen ? (
|
||||
<ArrowDown className="h-4 w-4" />
|
||||
) : (
|
||||
<ArrowRight className="h-4 w-4" />
|
||||
)}
|
||||
<span className="sr-only">Toggle</span>
|
||||
</Button>
|
||||
</div>
|
||||
@@ -313,8 +354,7 @@ function FilesMenu(props: FilesMenuProps) {
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</>
|
||||
)
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
interface SessionsAndFilesProps {
|
||||
@@ -333,38 +373,56 @@ function SessionsAndFiles(props: SessionsAndFilesProps) {
|
||||
<>
|
||||
<div>
|
||||
<ScrollArea>
|
||||
<ScrollAreaScrollbar orientation="vertical" className="h-full w-2.5 border-l border-l-transparent p-[1px]" />
|
||||
<ScrollAreaScrollbar
|
||||
orientation="vertical"
|
||||
className="h-full w-2.5 border-l border-l-transparent p-[1px]"
|
||||
/>
|
||||
<div className={styles.sessionsList}>
|
||||
{props.subsetOrganizedData != null && Object.keys(props.subsetOrganizedData).filter(tg => tg !== "All Time").map((timeGrouping) => (
|
||||
<div key={timeGrouping} className={`my-4`}>
|
||||
<div className={`text-muted-foreground text-sm font-bold p-[0.5rem]`}>
|
||||
{timeGrouping}
|
||||
</div>
|
||||
{props.subsetOrganizedData && props.subsetOrganizedData[timeGrouping].map((chatHistory) => (
|
||||
<ChatSession
|
||||
created={chatHistory.created}
|
||||
compressed={true}
|
||||
key={chatHistory.conversation_id}
|
||||
conversation_id={chatHistory.conversation_id}
|
||||
slug={chatHistory.slug}
|
||||
agent_avatar={chatHistory.agent_avatar}
|
||||
agent_name={chatHistory.agent_name}
|
||||
showSidePanel={props.setEnabled}
|
||||
/>
|
||||
{props.subsetOrganizedData != null &&
|
||||
Object.keys(props.subsetOrganizedData)
|
||||
.filter((tg) => tg !== "All Time")
|
||||
.map((timeGrouping) => (
|
||||
<div key={timeGrouping} className={`my-4`}>
|
||||
<div
|
||||
className={`text-muted-foreground text-sm font-bold p-[0.5rem]`}
|
||||
>
|
||||
{timeGrouping}
|
||||
</div>
|
||||
{props.subsetOrganizedData &&
|
||||
props.subsetOrganizedData[timeGrouping].map(
|
||||
(chatHistory) => (
|
||||
<ChatSession
|
||||
created={chatHistory.created}
|
||||
compressed={true}
|
||||
key={chatHistory.conversation_id}
|
||||
conversation_id={
|
||||
chatHistory.conversation_id
|
||||
}
|
||||
slug={chatHistory.slug}
|
||||
agent_avatar={chatHistory.agent_avatar}
|
||||
agent_name={chatHistory.agent_name}
|
||||
showSidePanel={props.setEnabled}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
{
|
||||
(props.data && props.data.length > 5) && (
|
||||
<ChatSessionsModal data={props.organizedData} showSidePanel={props.setEnabled} />
|
||||
)
|
||||
}
|
||||
{props.data && props.data.length > 5 && (
|
||||
<ChatSessionsModal
|
||||
data={props.organizedData}
|
||||
showSidePanel={props.setEnabled}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<FilesMenu conversationId={props.conversationId} uploadedFiles={props.uploadedFiles} isMobileWidth={props.isMobileWidth} />
|
||||
<FilesMenu
|
||||
conversationId={props.conversationId}
|
||||
uploadedFiles={props.uploadedFiles}
|
||||
isMobileWidth={props.isMobileWidth}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
interface ChatSessionActionMenuProps {
|
||||
@@ -373,11 +431,11 @@ interface ChatSessionActionMenuProps {
|
||||
}
|
||||
|
||||
function ChatSessionActionMenu(props: ChatSessionActionMenuProps) {
|
||||
const [renamedTitle, setRenamedTitle] = useState('');
|
||||
const [renamedTitle, setRenamedTitle] = useState("");
|
||||
const [isRenaming, setIsRenaming] = useState(false);
|
||||
const [isSharing, setIsSharing] = useState(false);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const [shareUrl, setShareUrl] = useState('');
|
||||
const [shareUrl, setShareUrl] = useState("");
|
||||
const [showShareUrl, setShowShareUrl] = useState(false);
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
@@ -392,14 +450,13 @@ function ChatSessionActionMenu(props: ChatSessionActionMenuProps) {
|
||||
|
||||
if (isRenaming) {
|
||||
return (
|
||||
<Dialog
|
||||
open={isRenaming}
|
||||
onOpenChange={(open) => setIsRenaming(open)}>
|
||||
<Dialog open={isRenaming} onOpenChange={(open) => setIsRenaming(open)}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Set a new title for the conversation</DialogTitle>
|
||||
<DialogDescription>
|
||||
This will help you identify the conversation easily, and also help you search for it later.
|
||||
This will help you identify the conversation easily, and also help you
|
||||
search for it later.
|
||||
</DialogDescription>
|
||||
<Input
|
||||
value={renamedTitle}
|
||||
@@ -413,11 +470,14 @@ function ChatSessionActionMenu(props: ChatSessionActionMenuProps) {
|
||||
props.setTitle(renamedTitle);
|
||||
setIsRenaming(false);
|
||||
}}
|
||||
type="submit">Rename</Button>
|
||||
type="submit"
|
||||
>
|
||||
Rename
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (isSharing || showShareUrl) {
|
||||
@@ -428,14 +488,16 @@ function ChatSessionActionMenu(props: ChatSessionActionMenuProps) {
|
||||
<Dialog
|
||||
open={isSharing || showShareUrl}
|
||||
onOpenChange={(open) => {
|
||||
setShowShareUrl(open)
|
||||
setIsSharing(open)
|
||||
}}>
|
||||
setShowShareUrl(open);
|
||||
setIsSharing(open);
|
||||
}}
|
||||
>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Conversation Share URL</DialogTitle>
|
||||
<DialogDescription>
|
||||
Sharing this chat session will allow anyone with a link to view the conversation.
|
||||
Sharing this chat session will allow anyone with a link to view the
|
||||
conversation.
|
||||
<Input
|
||||
className="w-full bg-accent text-accent-foreground rounded-md p-2 mt-2"
|
||||
value={shareUrl}
|
||||
@@ -444,40 +506,44 @@ function ChatSessionActionMenu(props: ChatSessionActionMenuProps) {
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
{
|
||||
!showShareUrl &&
|
||||
{!showShareUrl && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
shareConversation(props.conversationId, setShareUrl);
|
||||
setShowShareUrl(true);
|
||||
}}
|
||||
className="bg-orange-500"
|
||||
disabled><Spinner className="mr-2 h-4 w-4 animate-spin" />Sharing</Button>
|
||||
}
|
||||
{
|
||||
showShareUrl &&
|
||||
disabled
|
||||
>
|
||||
<Spinner className="mr-2 h-4 w-4 animate-spin" />
|
||||
Sharing
|
||||
</Button>
|
||||
)}
|
||||
{showShareUrl && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(shareUrl);
|
||||
}}
|
||||
variant={'default'}>Copy</Button>
|
||||
}
|
||||
variant={"default"}
|
||||
>
|
||||
Copy
|
||||
</Button>
|
||||
)}
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (isDeleting) {
|
||||
return (
|
||||
<AlertDialog
|
||||
open={isDeleting}
|
||||
onOpenChange={(open) => setIsDeleting(open)}>
|
||||
<AlertDialog open={isDeleting} onOpenChange={(open) => setIsDeleting(open)}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Delete Conversation</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Are you sure you want to delete this conversation? This action cannot be undone.
|
||||
Are you sure you want to delete this conversation? This action cannot be
|
||||
undone.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
@@ -486,60 +552,88 @@ function ChatSessionActionMenu(props: ChatSessionActionMenuProps) {
|
||||
onClick={() => {
|
||||
deleteConversation(props.conversationId);
|
||||
setIsDeleting(false);
|
||||
var currConversationId = parseInt(new URLSearchParams(window.location.search).get('conversationId') || "0");
|
||||
var currConversationId = parseInt(
|
||||
new URLSearchParams(window.location.search).get(
|
||||
"conversationId",
|
||||
) || "0",
|
||||
);
|
||||
//edge case for deleting current conversation
|
||||
if (currConversationId === parseInt(props.conversationId)) {
|
||||
window.location.href = '/';
|
||||
window.location.href = "/";
|
||||
}
|
||||
//reload side panel
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
}}
|
||||
className="bg-rose-500 hover:bg-rose-600">Delete</AlertDialogAction>
|
||||
className="bg-rose-500 hover:bg-rose-600"
|
||||
>
|
||||
Delete
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenu
|
||||
onOpenChange={(open) => setIsOpen(open)}
|
||||
open={isOpen}>
|
||||
<DropdownMenuTrigger><DotsThreeVertical className="h-4 w-4" /></DropdownMenuTrigger>
|
||||
<DropdownMenu onOpenChange={(open) => setIsOpen(open)} open={isOpen}>
|
||||
<DropdownMenuTrigger>
|
||||
<DotsThreeVertical className="h-4 w-4" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem>
|
||||
<Button className="p-0 text-sm h-auto" variant={'ghost'} onClick={() => setIsRenaming(true)}>
|
||||
<Pencil className="mr-2 h-4 w-4" />Rename
|
||||
<Button
|
||||
className="p-0 text-sm h-auto"
|
||||
variant={"ghost"}
|
||||
onClick={() => setIsRenaming(true)}
|
||||
>
|
||||
<Pencil className="mr-2 h-4 w-4" />
|
||||
Rename
|
||||
</Button>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Button className="p-0 text-sm h-auto" variant={'ghost'} onClick={() => setIsSharing(true)}>
|
||||
<Share className="mr-2 h-4 w-4" />Share
|
||||
<Button
|
||||
className="p-0 text-sm h-auto"
|
||||
variant={"ghost"}
|
||||
onClick={() => setIsSharing(true)}
|
||||
>
|
||||
<Share className="mr-2 h-4 w-4" />
|
||||
Share
|
||||
</Button>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Button className="p-0 text-sm h-auto text-rose-300 hover:text-rose-400" variant={'ghost'} onClick={() => setIsDeleting(true)}>
|
||||
<Trash className="mr-2 h-4 w-4" />Delete
|
||||
<Button
|
||||
className="p-0 text-sm h-auto text-rose-300 hover:text-rose-400"
|
||||
variant={"ghost"}
|
||||
onClick={() => setIsDeleting(true)}
|
||||
>
|
||||
<Trash className="mr-2 h-4 w-4" />
|
||||
Delete
|
||||
</Button>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function ChatSession(props: ChatHistory) {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const [title, setTitle] = useState(props.slug || "New Conversation 🌱");
|
||||
var currConversationId = parseInt(new URLSearchParams(window.location.search).get('conversationId') || "-1");
|
||||
var currConversationId = parseInt(
|
||||
new URLSearchParams(window.location.search).get("conversationId") || "-1",
|
||||
);
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
key={props.conversation_id}
|
||||
className={`${styles.session} ${props.compressed ? styles.compressed : '!max-w-full'} ${isHovered ? `${styles.sessionHover}` : ''} ${currConversationId === parseInt(props.conversation_id) && currConversationId != -1 ? "dark:bg-neutral-800 bg-white" : ""}`}>
|
||||
<Link href={`/chat?conversationId=${props.conversation_id}`} onClick={() => props.showSidePanel(false)}>
|
||||
className={`${styles.session} ${props.compressed ? styles.compressed : "!max-w-full"} ${isHovered ? `${styles.sessionHover}` : ""} ${currConversationId === parseInt(props.conversation_id) && currConversationId != -1 ? "dark:bg-neutral-800 bg-white" : ""}`}
|
||||
>
|
||||
<Link
|
||||
href={`/chat?conversationId=${props.conversation_id}`}
|
||||
onClick={() => props.showSidePanel(false)}
|
||||
>
|
||||
<p className={styles.session}>{title}</p>
|
||||
</Link>
|
||||
<ChatSessionActionMenu conversationId={props.conversation_id} setTitle={setTitle} />
|
||||
@@ -555,34 +649,38 @@ interface ChatSessionsModalProps {
|
||||
function ChatSessionsModal({ data, showSidePanel }: ChatSessionsModalProps) {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger
|
||||
className="flex text-left text-medium text-gray-500 hover:text-gray-300 cursor-pointer my-4 text-sm p-[0.5rem]">
|
||||
<span className="mr-2">See All <ArrowRight className="inline h-4 w-4" weight="bold" /></span>
|
||||
<DialogTrigger className="flex text-left text-medium text-gray-500 hover:text-gray-300 cursor-pointer my-4 text-sm p-[0.5rem]">
|
||||
<span className="mr-2">
|
||||
See All <ArrowRight className="inline h-4 w-4" weight="bold" />
|
||||
</span>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>All Conversations</DialogTitle>
|
||||
<DialogDescription
|
||||
className="p-0">
|
||||
<DialogDescription className="p-0">
|
||||
<ScrollArea className="h-[500px] py-4">
|
||||
{data && Object.keys(data).map((timeGrouping) => (
|
||||
<div key={timeGrouping}>
|
||||
<div className={`text-muted-foreground text-sm font-bold p-[0.5rem] `}>
|
||||
{timeGrouping}
|
||||
{data &&
|
||||
Object.keys(data).map((timeGrouping) => (
|
||||
<div key={timeGrouping}>
|
||||
<div
|
||||
className={`text-muted-foreground text-sm font-bold p-[0.5rem] `}
|
||||
>
|
||||
{timeGrouping}
|
||||
</div>
|
||||
{data[timeGrouping].map((chatHistory) => (
|
||||
<ChatSession
|
||||
created={chatHistory.created}
|
||||
compressed={false}
|
||||
key={chatHistory.conversation_id}
|
||||
conversation_id={chatHistory.conversation_id}
|
||||
slug={chatHistory.slug}
|
||||
agent_avatar={chatHistory.agent_avatar}
|
||||
agent_name={chatHistory.agent_name}
|
||||
showSidePanel={showSidePanel}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{data[timeGrouping].map((chatHistory) => (
|
||||
<ChatSession
|
||||
created={chatHistory.created}
|
||||
compressed={false}
|
||||
key={chatHistory.conversation_id}
|
||||
conversation_id={chatHistory.conversation_id}
|
||||
slug={chatHistory.slug}
|
||||
agent_avatar={chatHistory.agent_avatar}
|
||||
agent_name={chatHistory.agent_name}
|
||||
showSidePanel={showSidePanel} />
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</ScrollArea>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
@@ -593,9 +691,9 @@ function ChatSessionsModal({ data, showSidePanel }: ChatSessionsModalProps) {
|
||||
|
||||
const fetchChatHistory = async (url: string) => {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
method: "GET",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
return response.json();
|
||||
@@ -617,7 +715,6 @@ interface SidePanelProps {
|
||||
isMobileWidth: boolean;
|
||||
}
|
||||
|
||||
|
||||
export default function SidePanel(props: SidePanelProps) {
|
||||
const [data, setData] = useState<ChatHistory[] | null>(null);
|
||||
const [organizedData, setOrganizedData] = useState<GroupedChatHistory | null>(null);
|
||||
@@ -625,7 +722,9 @@ export default function SidePanel(props: SidePanelProps) {
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
|
||||
const authenticatedData = useAuthenticatedData();
|
||||
const { data: chatSessions } = useChatSessionsFetchRequest(authenticatedData ? `/api/chat/sessions` : '');
|
||||
const { data: chatSessions } = useChatSessionsFetchRequest(
|
||||
authenticatedData ? `/api/chat/sessions` : "",
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (chatSessions) {
|
||||
@@ -642,7 +741,8 @@ export default function SidePanel(props: SidePanelProps) {
|
||||
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';
|
||||
const timeGrouping =
|
||||
diffDays < 7 ? "Recent" : diffDays < 30 ? "Last Month" : "All Time";
|
||||
if (!groupedData[timeGrouping]) {
|
||||
groupedData[timeGrouping] = [];
|
||||
}
|
||||
@@ -664,80 +764,98 @@ export default function SidePanel(props: SidePanelProps) {
|
||||
}, [chatSessions]);
|
||||
|
||||
return (
|
||||
<div className={`${styles.panel} ${enabled ? styles.expanded : styles.collapsed} ${props.isMobileWidth ? 'mt-0' : 'mt-1'}`}>
|
||||
<div
|
||||
className={`${styles.panel} ${enabled ? styles.expanded : styles.collapsed} ${props.isMobileWidth ? "mt-0" : "mt-1"}`}
|
||||
>
|
||||
<div className={`flex justify-between flex-row`}>
|
||||
{
|
||||
props.isMobileWidth ?
|
||||
<Drawer open={enabled} onOpenChange={(open) => {
|
||||
{props.isMobileWidth ? (
|
||||
<Drawer
|
||||
open={enabled}
|
||||
onOpenChange={(open) => {
|
||||
if (!enabled) setEnabled(false);
|
||||
setEnabled(open);
|
||||
}
|
||||
}>
|
||||
<DrawerTrigger><Sidebar className="h-8 w-8 mx-2" weight="thin" /></DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>Sessions and Files</DrawerTitle>
|
||||
<DrawerDescription>View all conversation sessions and manage conversation file filters</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
{
|
||||
authenticatedData ?
|
||||
<div className={`${styles.panelWrapper}`}>
|
||||
<SessionsAndFiles
|
||||
setEnabled={setEnabled}
|
||||
subsetOrganizedData={subsetOrganizedData}
|
||||
organizedData={organizedData}
|
||||
data={data}
|
||||
uploadedFiles={props.uploadedFiles}
|
||||
userProfile={authenticatedData}
|
||||
conversationId={props.conversationId}
|
||||
isMobileWidth={props.isMobileWidth}
|
||||
/>
|
||||
</div>
|
||||
:
|
||||
<div className={`${styles.panelWrapper}`}>
|
||||
<Link href={`/login?next=${encodeURIComponent(window.location.pathname)}`} className="text-center"> {/* Redirect to login page */}
|
||||
<Button variant="default"><UserCirclePlus className="h-4 w-4 mr-1" />Sign Up</Button>
|
||||
</Link>
|
||||
</div>
|
||||
}
|
||||
<DrawerFooter>
|
||||
<DrawerClose>
|
||||
<Button variant="outline">Done</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
:
|
||||
<div className={`grid grid-flow-col gap-4 w-fit`}>
|
||||
<Link href='/' className="content-center">
|
||||
<KhojLogoType />
|
||||
}}
|
||||
>
|
||||
<DrawerTrigger>
|
||||
<Sidebar className="h-8 w-8 mx-2" weight="thin" />
|
||||
</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<DrawerHeader>
|
||||
<DrawerTitle>Sessions and Files</DrawerTitle>
|
||||
<DrawerDescription>
|
||||
View all conversation sessions and manage conversation file
|
||||
filters
|
||||
</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
{authenticatedData ? (
|
||||
<div className={`${styles.panelWrapper}`}>
|
||||
<SessionsAndFiles
|
||||
setEnabled={setEnabled}
|
||||
subsetOrganizedData={subsetOrganizedData}
|
||||
organizedData={organizedData}
|
||||
data={data}
|
||||
uploadedFiles={props.uploadedFiles}
|
||||
userProfile={authenticatedData}
|
||||
conversationId={props.conversationId}
|
||||
isMobileWidth={props.isMobileWidth}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className={`${styles.panelWrapper}`}>
|
||||
<Link
|
||||
href={`/login?next=${encodeURIComponent(window.location.pathname)}`}
|
||||
className="text-center"
|
||||
>
|
||||
{" "}
|
||||
{/* Redirect to login page */}
|
||||
<Button variant="default">
|
||||
<UserCirclePlus className="h-4 w-4 mr-1" />
|
||||
Sign Up
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
<DrawerFooter>
|
||||
<DrawerClose>
|
||||
<Button variant="outline">Done</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
) : (
|
||||
<div className={`grid grid-flow-col gap-4 w-fit`}>
|
||||
<Link href="/" className="content-center">
|
||||
<KhojLogoType />
|
||||
</Link>
|
||||
<div className="grid grid-flow-col gap-2 items-center">
|
||||
<Link className="mx-4" href="/">
|
||||
{enabled ? (
|
||||
<NotePencil className="h-6 w-6" weight="fill" />
|
||||
) : (
|
||||
<NotePencil className="h-6 w-6" color="gray" />
|
||||
)}
|
||||
</Link>
|
||||
<div className='grid grid-flow-col gap-2 items-center'>
|
||||
<Link className='mx-4' href="/">
|
||||
{enabled ? <NotePencil className="h-6 w-6" weight="fill" /> : <NotePencil className="h-6 w-6" color="gray" />}
|
||||
</Link>
|
||||
<button className={styles.button} onClick={() => setEnabled(!enabled)}>
|
||||
{enabled ? <Sidebar className="h-6 w-6" weight="fill" /> : <Sidebar className="h-6 w-6" color="gray" />}
|
||||
</button>
|
||||
</div>
|
||||
<div className="fixed right-0 top-[0.9rem] w-fit h-fit">
|
||||
<NavMenu />
|
||||
</div>
|
||||
<button className={styles.button} onClick={() => setEnabled(!enabled)}>
|
||||
{enabled ? (
|
||||
<Sidebar className="h-6 w-6" weight="fill" />
|
||||
) : (
|
||||
<Sidebar className="h-6 w-6" color="gray" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
{
|
||||
props.isMobileWidth &&
|
||||
<Link href='/' className="content-center">
|
||||
<div className="fixed right-0 top-[0.9rem] w-fit h-fit">
|
||||
<NavMenu />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{props.isMobileWidth && (
|
||||
<Link href="/" className="content-center">
|
||||
<KhojLogoType />
|
||||
</Link>
|
||||
}
|
||||
{
|
||||
props.isMobileWidth &&
|
||||
<NavMenu />
|
||||
}
|
||||
)}
|
||||
{props.isMobileWidth && <NavMenu />}
|
||||
</div>
|
||||
{
|
||||
authenticatedData && !props.isMobileWidth && enabled &&
|
||||
{authenticatedData && !props.isMobileWidth && enabled && (
|
||||
<div className={`${styles.panelWrapper}`}>
|
||||
<SessionsAndFiles
|
||||
setEnabled={setEnabled}
|
||||
@@ -750,19 +868,29 @@ export default function SidePanel(props: SidePanelProps) {
|
||||
isMobileWidth={props.isMobileWidth}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
{
|
||||
!authenticatedData && enabled && !props.isMobileWidth &&
|
||||
)}
|
||||
{!authenticatedData && enabled && !props.isMobileWidth && (
|
||||
<div className={`${styles.panelWrapper}`}>
|
||||
<Link href="/" className="flex flex-col content-start items-start no-underline">
|
||||
<Button variant="ghost"><House className="h-4 w-4 mr-1" />Home</Button>
|
||||
<Button variant="ghost"><StackPlus className="h-4 w-4 mr-1" />New Conversation</Button>
|
||||
<Button variant="ghost">
|
||||
<House className="h-4 w-4 mr-1" />
|
||||
Home
|
||||
</Button>
|
||||
<Button variant="ghost">
|
||||
<StackPlus className="h-4 w-4 mr-1" />
|
||||
New Conversation
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href={`/login?next=${encodeURIComponent(window.location.pathname)}`}> {/* Redirect to login page */}
|
||||
<Button variant="default"><UserCirclePlus className="h-4 w-4 mr-1" />Sign Up</Button>
|
||||
<Link href={`/login?next=${encodeURIComponent(window.location.pathname)}`}>
|
||||
{" "}
|
||||
{/* Redirect to login page */}
|
||||
<Button variant="default">
|
||||
<UserCirclePlus className="h-4 w-4 mr-1" />
|
||||
Sign Up
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user