Use the new shadcn sidebar for khoj nav and actions

- Use the sidebar across all pages to quickly navigate through the app, access settings, and go to past chats
- Pending: mobile friendliness
This commit is contained in:
sabaimran
2024-12-19 20:10:03 -08:00
parent 7eb15bf0a9
commit 68af10c805
16 changed files with 1270 additions and 1353 deletions

View File

@@ -19,7 +19,6 @@ import { z } from "zod";
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "@/components/ui/dialog"; import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "@/components/ui/dialog";
import LoginPrompt from "../components/loginPrompt/loginPrompt"; import LoginPrompt from "../components/loginPrompt/loginPrompt";
import { InlineLoading } from "../components/loading/loading"; import { InlineLoading } from "../components/loading/loading";
import SidePanel from "../components/sidePanel/chatHistorySidePanel";
import { Alert, AlertDescription } from "@/components/ui/alert"; import { Alert, AlertDescription } from "@/components/ui/alert";
import { useIsMobileWidth } from "../common/utils"; import { useIsMobileWidth } from "../common/utils";
import { import {
@@ -30,6 +29,8 @@ import {
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
import { AppSidebar } from "../components/appSidebar/appSidebar";
export interface AgentData { export interface AgentData {
slug: string; slug: string;
@@ -276,15 +277,11 @@ export default function Agents() {
); );
return ( return (
<SidebarProvider>
<AppSidebar conversationId={""} />
<SidebarTrigger />
<main className={`w-full mx-auto`}> <main className={`w-full mx-auto`}>
<div className={`grid w-full mx-auto`}> <div className={`grid w-full mx-auto`}>
<div className={`${styles.sidePanel} top-0`}>
<SidePanel
conversationId={null}
uploadedFiles={[]}
isMobileWidth={isMobileWidth}
/>
</div>
<div className={`${styles.pageLayout} w-full`}> <div className={`${styles.pageLayout} w-full`}>
<div className={`pt-6 md:pt-8 flex justify-between`}> <div className={`pt-6 md:pt-8 flex justify-between`}>
<h1 className="text-3xl flex items-center">Agents</h1> <h1 className="text-3xl flex items-center">Agents</h1>
@@ -310,7 +307,9 @@ export default function Agents() {
isSubscribed={isSubscribed} isSubscribed={isSubscribed}
setAgentChangeTriggered={setAgentChangeTriggered} setAgentChangeTriggered={setAgentChangeTriggered}
inputToolOptions={agentConfigurationOptions?.input_tools || {}} inputToolOptions={agentConfigurationOptions?.input_tools || {}}
outputModeOptions={agentConfigurationOptions?.output_modes || {}} outputModeOptions={
agentConfigurationOptions?.output_modes || {}
}
/> />
</div> </div>
</div> </div>
@@ -323,7 +322,10 @@ export default function Agents() {
)} )}
<Alert className="bg-secondary border-none my-4"> <Alert className="bg-secondary border-none my-4">
<AlertDescription> <AlertDescription>
<Lightning weight={"fill"} className="h-4 w-4 text-purple-400 inline" /> <Lightning
weight={"fill"}
className="h-4 w-4 text-purple-400 inline"
/>
<span className="font-bold">How it works</span> Use any of these <span className="font-bold">How it works</span> Use any of these
specialized personas to tune your conversation to your needs. specialized personas to tune your conversation to your needs.
</AlertDescription> </AlertDescription>
@@ -343,7 +345,9 @@ export default function Agents() {
modelOptions={userConfig?.chat_model_options || []} modelOptions={userConfig?.chat_model_options || []}
editCard={true} editCard={true}
agentSlug={agentSlug || ""} agentSlug={agentSlug || ""}
inputToolOptions={agentConfigurationOptions?.input_tools || {}} inputToolOptions={
agentConfigurationOptions?.input_tools || {}
}
outputModeOptions={ outputModeOptions={
agentConfigurationOptions?.output_modes || {} agentConfigurationOptions?.output_modes || {}
} }
@@ -367,7 +371,9 @@ export default function Agents() {
setAgentChangeTriggered={setAgentChangeTriggered} setAgentChangeTriggered={setAgentChangeTriggered}
modelOptions={userConfig?.chat_model_options || []} modelOptions={userConfig?.chat_model_options || []}
agentSlug={agentSlug || ""} agentSlug={agentSlug || ""}
inputToolOptions={agentConfigurationOptions?.input_tools || {}} inputToolOptions={
agentConfigurationOptions?.input_tools || {}
}
outputModeOptions={ outputModeOptions={
agentConfigurationOptions?.output_modes || {} agentConfigurationOptions?.output_modes || {}
} }
@@ -378,5 +384,6 @@ export default function Agents() {
</div> </div>
</div> </div>
</main> </main>
</SidebarProvider>
); );
} }

View File

@@ -66,8 +66,9 @@ import LoginPrompt from "../components/loginPrompt/loginPrompt";
import { useToast } from "@/components/ui/use-toast"; import { useToast } from "@/components/ui/use-toast";
import { ToastAction } from "@/components/ui/toast"; import { ToastAction } from "@/components/ui/toast";
import { Alert, AlertDescription } from "@/components/ui/alert"; import { Alert, AlertDescription } from "@/components/ui/alert";
import SidePanel from "../components/sidePanel/chatHistorySidePanel";
import { Drawer, DrawerContent, DrawerTitle, DrawerTrigger } from "@/components/ui/drawer"; import { Drawer, DrawerContent, DrawerTitle, DrawerTrigger } from "@/components/ui/drawer";
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
import { AppSidebar } from "../components/appSidebar/appSidebar";
const automationsFetcher = () => const automationsFetcher = () =>
window window
@@ -1023,15 +1024,11 @@ export default function Automations() {
return <InlineLoading message="Oops, something went wrong. Please refresh the page." />; return <InlineLoading message="Oops, something went wrong. Please refresh the page." />;
return ( return (
<SidebarProvider>
<AppSidebar conversationId={""} />
<SidebarTrigger />
<main className={`w-full mx-auto`}> <main className={`w-full mx-auto`}>
<div className={`grid w-full mx-auto`}> <div className={`grid w-full mx-auto`}>
<div className={`${styles.sidePanel} top-0`}>
<SidePanel
conversationId={null}
uploadedFiles={[]}
isMobileWidth={isMobileWidth}
/>
</div>
<div className={`${styles.pageLayout} w-full`}> <div className={`${styles.pageLayout} w-full`}>
<div className="pt-6 md:pt-8 grid gap-1 md:flex md:justify-between"> <div className="pt-6 md:pt-8 grid gap-1 md:flex md:justify-between">
<h1 className="text-3xl flex items-center">Automations</h1> <h1 className="text-3xl flex items-center">Automations</h1>
@@ -1060,14 +1057,19 @@ export default function Automations() {
</div> </div>
{showLoginPrompt && ( {showLoginPrompt && (
<LoginPrompt <LoginPrompt
loginRedirectMessage={"Create an account to make your own automation"} loginRedirectMessage={
"Create an account to make your own automation"
}
onOpenChange={setShowLoginPrompt} onOpenChange={setShowLoginPrompt}
isMobileWidth={isMobileWidth} isMobileWidth={isMobileWidth}
/> />
)} )}
<Alert className="bg-secondary border-none my-4"> <Alert className="bg-secondary border-none my-4">
<AlertDescription> <AlertDescription>
<Lightning weight={"fill"} className="h-4 w-4 text-purple-400 inline" /> <Lightning
weight={"fill"}
className="h-4 w-4 text-purple-400 inline"
/>
<span className="font-bold">How it works</span> Automations help you <span className="font-bold">How it works</span> Automations help you
structure your time by automating tasks you do regularly. Build your structure your time by automating tasks you do regularly. Build your
own, or try out our presets. Get results straight to your inbox. own, or try out our presets. Get results straight to your inbox.
@@ -1152,5 +1154,6 @@ export default function Automations() {
</div> </div>
</div> </div>
</main> </main>
</SidebarProvider>
); );
} }

View File

@@ -1,6 +1,8 @@
div.main { div.main {
height: 100dvh; height: 100dvh;
color: hsla(var(--foreground)); color: hsla(var(--foreground));
margin-left: auto;
margin-right: auto;
} }
.suggestions { .suggestions {

View File

@@ -3,7 +3,6 @@
import styles from "./chat.module.css"; import styles from "./chat.module.css";
import React, { Suspense, useEffect, useRef, useState } from "react"; import React, { Suspense, useEffect, useRef, useState } from "react";
import SidePanel, { ChatSessionActionMenu } from "../components/sidePanel/chatHistorySidePanel";
import ChatHistory from "../components/chatHistory/chatHistory"; import ChatHistory from "../components/chatHistory/chatHistory";
import { useSearchParams } from "next/navigation"; import { useSearchParams } from "next/navigation";
import Loading from "../components/loading/loading"; import Loading from "../components/loading/loading";
@@ -26,6 +25,9 @@ import {
} from "../components/chatInputArea/chatInputArea"; } from "../components/chatInputArea/chatInputArea";
import { useAuthenticatedData } from "../common/auth"; import { useAuthenticatedData } from "../common/auth";
import { AgentData } from "../agents/page"; import { AgentData } from "../agents/page";
import { ChatSessionActionMenu } from "../components/allConversations/allConversations";
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
import { AppSidebar } from "../components/appSidebar/appSidebar";
interface ChatBodyDataProps { interface ChatBodyDataProps {
chatOptionsData: ChatOptions | null; chatOptionsData: ChatOptions | null;
@@ -382,17 +384,13 @@ export default function Chat() {
if (isLoading) return <Loading />; if (isLoading) return <Loading />;
return ( return (
<SidebarProvider>
<AppSidebar conversationId={conversationId || ""} />
<SidebarTrigger />
<div className={`${styles.main} ${styles.chatLayout}`}> <div className={`${styles.main} ${styles.chatLayout}`}>
<title> <title>
{`${defaultTitle}${!!title && title !== defaultTitle ? `: ${title}` : ""}`} {`${defaultTitle}${!!title && title !== defaultTitle ? `: ${title}` : ""}`}
</title> </title>
<div className={isMobileWidth ? "h-1" : "h-auto"}>
<SidePanel
conversationId={conversationId}
uploadedFiles={[]}
isMobileWidth={isMobileWidth}
/>
</div>
<div className={styles.chatBox}> <div className={styles.chatBox}>
<div className={styles.chatBoxBody}> <div className={styles.chatBoxBody}>
{conversationId && ( {conversationId && (
@@ -431,5 +429,6 @@ export default function Chat() {
</div> </div>
</div> </div>
</div> </div>
</SidebarProvider>
); );
} }

View File

@@ -60,6 +60,7 @@ import {
NotePencil, NotePencil,
FunnelSimple, FunnelSimple,
MagnifyingGlass, MagnifyingGlass,
ChatsCircle,
} from "@phosphor-icons/react"; } from "@phosphor-icons/react";
interface ChatHistory { interface ChatHistory {
@@ -71,7 +72,6 @@ interface ChatHistory {
compressed: boolean; compressed: boolean;
created: string; created: string;
updated: string; updated: string;
showSidePanel: (isEnabled: boolean) => void;
} }
import { import {
@@ -106,6 +106,15 @@ import { KhojLogoType } from "@/app/components/logo/khojLogo";
import NavMenu from "@/app/components/navMenu/navMenu"; import NavMenu from "@/app/components/navMenu/navMenu";
import { getIconFromIconName } from "@/app/common/iconUtils"; import { getIconFromIconName } from "@/app/common/iconUtils";
import LoginPrompt from "../loginPrompt/loginPrompt"; import LoginPrompt from "../loginPrompt/loginPrompt";
import {
SidebarGroup,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSkeleton,
} from "@/components/ui/sidebar";
// Define a fetcher function // Define a fetcher function
const fetcher = (url: string) => const fetcher = (url: string) =>
@@ -403,7 +412,6 @@ function FilesMenu(props: FilesMenuProps) {
} }
interface SessionsAndFilesProps { interface SessionsAndFilesProps {
setEnabled: (enabled: boolean) => void;
subsetOrganizedData: GroupedChatHistory | null; subsetOrganizedData: GroupedChatHistory | null;
organizedData: GroupedChatHistory | null; organizedData: GroupedChatHistory | null;
data: ChatHistory[] | null; data: ChatHistory[] | null;
@@ -411,32 +419,28 @@ interface SessionsAndFilesProps {
conversationId: string | null; conversationId: string | null;
uploadedFiles: string[]; uploadedFiles: string[];
isMobileWidth: boolean; isMobileWidth: boolean;
sideBarOpen: boolean;
} }
function SessionsAndFiles(props: SessionsAndFilesProps) { function SessionsAndFiles(props: SessionsAndFilesProps) {
return ( return (
<>
<div> <div>
{props.data && props.data.length > 5 && ( {props.data && props.data.length > 5 && (
<ChatSessionsModal <ChatSessionsModal data={props.organizedData} sideBarOpen={props.sideBarOpen} />
data={props.organizedData}
showSidePanel={props.setEnabled}
/>
)} )}
{props.sideBarOpen && (
<ScrollArea> <ScrollArea>
<ScrollAreaScrollbar <ScrollAreaScrollbar
orientation="vertical" orientation="vertical"
className="h-full w-2.5 border-l border-l-transparent p-[1px]" className="h-full w-2.5 border-l border-l-transparent p-[1px]"
/> />
<div className={styles.sessionsList}> <div className="p-0 m-0">
{props.subsetOrganizedData != null && {props.subsetOrganizedData != null &&
Object.keys(props.subsetOrganizedData) Object.keys(props.subsetOrganizedData)
.filter((tg) => tg !== "All Time") .filter((tg) => tg !== "All Time")
.map((timeGrouping) => ( .map((timeGrouping) => (
<div key={timeGrouping} className={`my-4`}> <div key={timeGrouping} className={`my-1`}>
<div <div className={`text-muted-foreground text-xs p-[0.5rem]`}>
className={`text-muted-foreground text-sm font-bold p-[0.5rem]`}
>
{timeGrouping} {timeGrouping}
</div> </div>
{props.subsetOrganizedData && {props.subsetOrganizedData &&
@@ -454,7 +458,6 @@ function SessionsAndFiles(props: SessionsAndFilesProps) {
agent_name={chatHistory.agent_name} agent_name={chatHistory.agent_name}
agent_color={chatHistory.agent_color} agent_color={chatHistory.agent_color}
agent_icon={chatHistory.agent_icon} agent_icon={chatHistory.agent_icon}
showSidePanel={props.setEnabled}
/> />
), ),
)} )}
@@ -462,13 +465,8 @@ function SessionsAndFiles(props: SessionsAndFilesProps) {
))} ))}
</div> </div>
</ScrollArea> </ScrollArea>
)}
</div> </div>
<FilesMenu
conversationId={props.conversationId}
uploadedFiles={props.uploadedFiles}
isMobileWidth={props.isMobileWidth}
/>
</>
); );
} }
@@ -638,41 +636,31 @@ export function ChatSessionActionMenu(props: ChatSessionActionMenuProps) {
</Button> </Button>
)} )}
<DropdownMenu onOpenChange={(open) => setIsOpen(open)} open={isOpen}> <DropdownMenu onOpenChange={(open) => setIsOpen(open)} open={isOpen}>
<DropdownMenuTrigger> <DropdownMenuTrigger asChild>
<SidebarMenuAction>
<DotsThreeVertical className={`${size}`} /> <DotsThreeVertical className={`${size}`} />
</SidebarMenuAction>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuContent side="right" align="start">
<DropdownMenuItem> <DropdownMenuItem onClick={() => setIsRenaming(true)}>
<Button <span className="flex items-center">
className="p-0 text-sm h-auto"
variant={"ghost"}
onClick={() => setIsRenaming(true)}
>
<Pencil className={`mr-2 ${size}`} /> <Pencil className={`mr-2 ${size}`} />
Rename Rename
</Button> </span>
</DropdownMenuItem> </DropdownMenuItem>
{props.sizing === "sm" && ( {props.sizing === "sm" && (
<DropdownMenuItem> <DropdownMenuItem onClick={() => setIsSharing(true)}>
<Button <span className="flex items-center">
className="p-0 text-sm h-auto"
variant={"ghost"}
onClick={() => setIsSharing(true)}
>
<Share className={`mr-2 ${size}`} /> <Share className={`mr-2 ${size}`} />
Share Share
</Button> </span>
</DropdownMenuItem> </DropdownMenuItem>
)} )}
<DropdownMenuItem> <DropdownMenuItem onClick={() => setIsDeleting(true)}>
<Button <span className="flex items-center">
className="p-0 text-sm h-auto text-rose-300 hover:text-rose-400"
variant={"ghost"}
onClick={() => setIsDeleting(true)}
>
<Trash className={`mr-2 ${size}`} /> <Trash className={`mr-2 ${size}`} />
Delete Delete
</Button> </span>
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
@@ -686,30 +674,36 @@ function ChatSession(props: ChatHistory) {
var currConversationId = var currConversationId =
new URLSearchParams(window.location.search).get("conversationId") || "-1"; new URLSearchParams(window.location.search).get("conversationId") || "-1";
return ( return (
<div <SidebarMenuItem
onMouseEnter={() => setIsHovered(true)} onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)} onMouseLeave={() => setIsHovered(false)}
key={props.conversation_id} key={props.conversation_id}
className={`${styles.session} ${props.compressed ? styles.compressed : "!max-w-full"} ${isHovered ? `${styles.sessionHover}` : ""} ${currConversationId === props.conversation_id && currConversationId != "-1" ? "dark:bg-neutral-800 bg-white" : ""}`} className={`${styles.session} ${props.compressed ? styles.compressed : "!max-w-full"} ${isHovered ? `${styles.sessionHover}` : ""} ${currConversationId === props.conversation_id && currConversationId != "-1" ? "dark:bg-neutral-800 bg-white" : ""}`}
> >
<SidebarMenuButton asChild>
<Link <Link
href={`/chat?conversationId=${props.conversation_id}`} href={`/chat?conversationId=${props.conversation_id}`}
onClick={() => props.showSidePanel(false)} className="flex items-center gap-2 no-underline"
> >
<p className={styles.session}>{title}</p> <p
className={`${styles.session} ${props.compressed ? styles.compressed : styles.expanded}`}
>
{title}
</p>
</Link> </Link>
</SidebarMenuButton>
<ChatSessionActionMenu <ChatSessionActionMenu
conversationId={props.conversation_id} conversationId={props.conversation_id}
setTitle={setTitle} setTitle={setTitle}
sizing="sm" sizing="sm"
/> />
</div> </SidebarMenuItem>
); );
} }
interface ChatSessionsModalProps { interface ChatSessionsModalProps {
data: GroupedChatHistory | null; data: GroupedChatHistory | null;
showSidePanel: (isEnabled: boolean) => void; sideBarOpen: boolean;
} }
interface AgentStyle { interface AgentStyle {
@@ -717,7 +711,7 @@ interface AgentStyle {
icon: string; icon: string;
} }
function ChatSessionsModal({ data, showSidePanel }: ChatSessionsModalProps) { function ChatSessionsModal({ data, sideBarOpen }: ChatSessionsModalProps) {
const [agentsFilter, setAgentsFilter] = useState<string[]>([]); const [agentsFilter, setAgentsFilter] = useState<string[]>([]);
const [agentOptions, setAgentOptions] = useState<string[]>([]); const [agentOptions, setAgentOptions] = useState<string[]>([]);
const [searchQuery, setSearchQuery] = useState<string>(""); const [searchQuery, setSearchQuery] = useState<string>("");
@@ -786,10 +780,12 @@ function ChatSessionsModal({ data, showSidePanel }: ChatSessionsModalProps) {
return ( return (
<Dialog> <Dialog>
<DialogTrigger className="flex text-left text-medium text-gray-500 hover:text-gray-300 cursor-pointer my-1 text-sm p-[0.1rem]"> <DialogTrigger
className={`flex text-left text-medium text-gray-500 hover:text-gray-300 cursor-pointer my-1 text-sm ${sideBarOpen ? "p-[0.5rem] " : "p-[0.1rem]"}`}
>
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">
<MagnifyingGlass className="inline h-4 w-4 mr-1" weight="bold" /> Find <ChatsCircle className="inline h-4 w-4 mr-1" />
Conversation {sideBarOpen ? "Find Conversations" : ""}
</span> </span>
</DialogTrigger> </DialogTrigger>
<DialogContent> <DialogContent>
@@ -814,7 +810,6 @@ function ChatSessionsModal({ data, showSidePanel }: ChatSessionsModalProps) {
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuContent>
{/* <ScrollArea className="h-[200px]"> */}
<DropdownMenuLabel>Agents</DropdownMenuLabel> <DropdownMenuLabel>Agents</DropdownMenuLabel>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
{agentOptions.map((agent) => ( {agentOptions.map((agent) => (
@@ -841,7 +836,6 @@ function ChatSessionsModal({ data, showSidePanel }: ChatSessionsModalProps) {
</div> </div>
</DropdownMenuCheckboxItem> </DropdownMenuCheckboxItem>
))} ))}
{/* </ScrollArea> */}
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</div> </div>
@@ -865,7 +859,6 @@ function ChatSessionsModal({ data, showSidePanel }: ChatSessionsModalProps) {
agent_name={chatHistory.agent_name} agent_name={chatHistory.agent_name}
agent_color={chatHistory.agent_color} agent_color={chatHistory.agent_color}
agent_icon={chatHistory.agent_icon} agent_icon={chatHistory.agent_icon}
showSidePanel={showSidePanel}
/> />
))} ))}
</div> </div>
@@ -902,17 +895,17 @@ interface SidePanelProps {
conversationId: string | null; conversationId: string | null;
uploadedFiles: string[]; uploadedFiles: string[];
isMobileWidth: boolean; isMobileWidth: boolean;
sideBarOpen: boolean;
} }
export default function SidePanel(props: SidePanelProps) { export default function AllConversations(props: SidePanelProps) {
const [data, setData] = useState<ChatHistory[] | null>(null); const [data, setData] = useState<ChatHistory[] | null>(null);
const [organizedData, setOrganizedData] = useState<GroupedChatHistory | null>(null); const [organizedData, setOrganizedData] = useState<GroupedChatHistory | null>(null);
const [subsetOrganizedData, setSubsetOrganizedData] = useState<GroupedChatHistory | null>(null); const [subsetOrganizedData, setSubsetOrganizedData] = useState<GroupedChatHistory | null>(null);
const [enabled, setEnabled] = useState(false);
const [showLoginPrompt, setShowLoginPrompt] = useState(false); const [showLoginPrompt, setShowLoginPrompt] = useState(false);
const authenticatedData = useAuthenticatedData(); const authenticatedData = useAuthenticatedData();
const { data: chatSessions } = useChatSessionsFetchRequest( const { data: chatSessions, isLoading } = useChatSessionsFetchRequest(
authenticatedData ? `/api/chat/sessions` : "", authenticatedData ? `/api/chat/sessions` : "",
); );
@@ -953,41 +946,29 @@ export default function SidePanel(props: SidePanelProps) {
} }
}, [chatSessions]); }, [chatSessions]);
if (isLoading) {
return ( return (
<SidebarGroup className="p-0 m-0">
<SidebarGroupLabel className="!p-0 m-0 px-0">Conversations</SidebarGroupLabel>
{Array.from({ length: 5 }).map((_, index) => (
<SidebarMenuItem key={index} className="p-0 list-none m-0">
<SidebarMenuSkeleton showIcon />
</SidebarMenuItem>
))}
</SidebarGroup>
);
}
return (
<SidebarGroup>
<SidebarGroupLabel className="!p-0 m-0 px-0">Conversations</SidebarGroupLabel>
<div className={`flex justify-between flex-col`}>
{authenticatedData && (
<>
<div <div
className={`${styles.panel} ${enabled ? styles.expanded : styles.collapsed} ${props.isMobileWidth ? "mt-0" : "mt-1"}`} className={`${props.sideBarOpen ? "border-l-2 border-light-blue-500 border-opacity-25 " : ""}`}
> >
{showLoginPrompt && (
<LoginPrompt
loginRedirectMessage="Sign in to start chatting"
onOpenChange={setShowLoginPrompt}
isMobileWidth={props.isMobileWidth}
/>
)}
<div className={`flex justify-between flex-row`}>
{props.isMobileWidth ? (
<Drawer
open={enabled}
onOpenChange={(open) => {
if (!enabled) setEnabled(false);
setEnabled(open);
}}
>
<DrawerTrigger>
<Sidebar className="h-6 w-6 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 <SessionsAndFiles
setEnabled={setEnabled}
subsetOrganizedData={subsetOrganizedData} subsetOrganizedData={subsetOrganizedData}
organizedData={organizedData} organizedData={organizedData}
data={data} data={data}
@@ -995,93 +976,19 @@ export default function SidePanel(props: SidePanelProps) {
userProfile={authenticatedData} userProfile={authenticatedData}
conversationId={props.conversationId} conversationId={props.conversationId}
isMobileWidth={props.isMobileWidth} isMobileWidth={props.isMobileWidth}
sideBarOpen={props.sideBarOpen}
/> />
</div> </div>
) : ( {props.sideBarOpen && (
<div className={`${styles.panelWrapper}`}> <FilesMenu
{" "}
{/* Redirect to login page */}
<Button
variant="default"
onClick={() => setShowLoginPrompt(true)}
>
<UserCirclePlus className="h-4 w-4 mr-1" />
Sign Up
</Button>
</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>
<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>
</div>
)}
{props.isMobileWidth && (
<Link href="/" className="content-center h-fit self-center">
<KhojLogoType />
</Link>
)}
{props.isMobileWidth && <NavMenu />}
</div>
{authenticatedData && !props.isMobileWidth && enabled && (
<div className={`${styles.panelWrapper}`}>
<SessionsAndFiles
setEnabled={setEnabled}
subsetOrganizedData={subsetOrganizedData}
organizedData={organizedData}
data={data}
uploadedFiles={props.uploadedFiles}
userProfile={authenticatedData}
conversationId={props.conversationId} conversationId={props.conversationId}
uploadedFiles={props.uploadedFiles}
isMobileWidth={props.isMobileWidth} isMobileWidth={props.isMobileWidth}
/> />
</div>
)} )}
{!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>
</Link>{" "}
<Button variant="default" onClick={() => setShowLoginPrompt(true)}>
<UserCirclePlus className="h-4 w-4 mr-1" />
Sign Up
</Button>
</div>
)} )}
</div> </div>
</SidebarGroup>
); );
} }

View File

@@ -74,6 +74,14 @@ p.session {
font-size: small; font-size: small;
} }
p.compressed {
width: 12rem;
}
p.expanded {
max-width: 20rem;
}
div.header { div.header {
display: grid; display: grid;
grid-template-columns: 1fr auto; grid-template-columns: 1fr auto;

View File

@@ -0,0 +1,130 @@
import { Calendar, Home, Inbox, Search, Settings } from "lucide-react";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "@/components/ui/sidebar";
import Link from "next/link";
import {
KhojAgentLogo,
KhojAutomationLogo,
KhojLogo,
KhojLogoType,
KhojSearchLogo,
} from "../logo/khojLogo";
import { Gear } from "@phosphor-icons/react/dist/ssr";
import { House, Plus } from "@phosphor-icons/react";
import { useEffect, useState } from "react";
import { useAuthenticatedData } from "@/app/common/auth";
import AllConversations from "../allConversations/allConversations";
import NavMenu from "../navMenu/navMenu";
import { useSidebar } from "@/components/ui/sidebar";
// Menu items.
const items = [
{
title: "New",
url: "/",
icon: Plus,
},
{
title: "Agents",
url: "/agents",
icon: KhojAgentLogo,
},
{
title: "Automations",
url: "/automations",
icon: KhojAutomationLogo,
},
{
title: "Search",
url: "/search",
icon: KhojSearchLogo,
},
{
title: "Settings",
url: "/settings",
icon: Gear,
},
];
const SIDEBAR_KEYBOARD_SHORTCUT = "b";
const SIDEBAR_WIDTH = "18rem";
const SIDEBAR_WIDTH_MOBILE = "20rem";
interface AppSidebarProps {
conversationId: string | null;
}
export function AppSidebar(props: AppSidebarProps) {
const [isMobileWidth, setIsMobileWidth] = useState(false);
const { state, open, setOpen, openMobile, setOpenMobile, isMobile, toggleSidebar } =
useSidebar();
useEffect(() => {
const handleResize = () => {
setIsMobileWidth(window.innerWidth < 768);
};
handleResize();
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return (
<Sidebar collapsible={"icon"}>
<SidebarHeader>
<SidebarMenu className="p-0 m-0">
<SidebarMenuItem className="p-0 m-0">
<SidebarMenuButton asChild>
<a className="flex items-center gap-2 no-underline" href="/">
<KhojLogo className="w-14 h-auto" />
<span className="text-2xl">Khoj</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu className="p-0 m-0">
{items.map((item) => (
<SidebarMenuItem key={item.title} className="p-0 list-none m-0">
<SidebarMenuButton asChild>
<a
href={item.url}
className="flex items-center gap-2 no-underline"
>
<item.icon />
<span>{item.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
<AllConversations
isMobileWidth={isMobileWidth}
conversationId={props.conversationId}
uploadedFiles={[]}
sideBarOpen={open}
/>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<NavMenu sideBarIsOpen={open} />
</SidebarFooter>
</Sidebar>
);
}

View File

@@ -27,6 +27,8 @@ import { KhojAgentLogo, KhojAutomationLogo, KhojSearchLogo } from "../logo/khojL
import { useIsMobileWidth } from "@/app/common/utils"; import { useIsMobileWidth } from "@/app/common/utils";
import LoginPrompt from "../loginPrompt/loginPrompt"; import LoginPrompt from "../loginPrompt/loginPrompt";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "@/components/ui/sidebar";
import { ChevronUp } from "lucide-react";
function SubscriptionBadge({ is_active }: { is_active: boolean }) { function SubscriptionBadge({ is_active }: { is_active: boolean }) {
return ( return (
@@ -48,7 +50,11 @@ function VersionBadge({ version }: { version: string }) {
); );
} }
export default function NavMenu() { interface NavMenuProps {
sideBarIsOpen: boolean;
}
export default function NavMenu({ sideBarIsOpen }: NavMenuProps) {
const userData = useAuthenticatedData(); const userData = useAuthenticatedData();
const [darkMode, setDarkMode] = useState(false); const [darkMode, setDarkMode] = useState(false);
const [initialLoadDone, setInitialLoadDone] = useState(false); const [initialLoadDone, setInitialLoadDone] = useState(false);
@@ -89,31 +95,34 @@ export default function NavMenu() {
} }
return ( return (
<div className={styles.titleBar}> <SidebarMenu className="border-none p-0 m-0">
{showLoginPrompt && ( <SidebarMenuItem className="p-0 m-0">
<LoginPrompt
onOpenChange={setShowLoginPrompt}
isMobileWidth={isMobileWidth}
loginRedirectMessage={"Login to your second brain"}
/>
)}
{isMobileWidth ? (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger> <DropdownMenuTrigger asChild>
<SidebarMenuButton className="p-0 m-0 rounded-lg" asChild>
{userData ? ( {userData ? (
<span className="flex items-center gap-2">
<Avatar <Avatar
className={`h-10 w-10 border-2 ${userData.is_active ? "border-yellow-500" : "border-stone-700 dark:border-stone-300"}`} className={`${sideBarIsOpen ? "h-8 w-8" : "h-6 w-6"} border-2 ${userData.is_active ? "border-yellow-500" : "border-stone-700 dark:border-stone-300"}`}
> >
<AvatarImage src={userData?.photo} alt="user profile" /> <AvatarImage src={userData?.photo} alt="user profile" />
<AvatarFallback className="bg-transparent hover:bg-muted"> <AvatarFallback className="bg-transparent hover:bg-muted">
{userData?.username[0].toUpperCase()} {userData?.username[0].toUpperCase()}
</AvatarFallback> </AvatarFallback>
</Avatar> </Avatar>
) : ( {sideBarIsOpen && (
<UserCircle className="h-10 w-10" /> <>
<p>{userData?.username}</p>
<ChevronUp className="w-6 h-6" />
</>
)} )}
</span>
) : (
<UserCircle className="w-10 h-10" />
)}
</SidebarMenuButton>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="gap-2"> <DropdownMenuContent align="end" className="rounded-xl gap-2">
<DropdownMenuItem className="w-full"> <DropdownMenuItem className="w-full">
<div className="flex flex-col"> <div className="flex flex-col">
<p className="font-semibold">{userData?.email}</p> <p className="font-semibold">{userData?.email}</p>
@@ -123,134 +132,8 @@ export default function NavMenu() {
)} )}
</div> </div>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator /> <DropdownMenuSeparator className="dark:bg-white height-[2px] bg-black" />
<DropdownMenuItem <DropdownMenuItem
onClick={() => setDarkMode(!darkMode)}
className="w-full cursor-pointer"
>
<div className="flex flex-rows">
{darkMode ? (
<Sun className="w-6 h-6" />
) : (
<Moon className="w-6 h-6" />
)}
<p className="ml-3 font-semibold">
{darkMode ? "Light Mode" : "Dark Mode"}
</p>
</div>
</DropdownMenuItem>
<DropdownMenuItem>
<Link href="/agents" className="no-underline w-full">
<div className="flex flex-rows">
<KhojAgentLogo className="w-6 h-6" />
<p className="ml-3 font-semibold">Agents</p>
</div>
</Link>
</DropdownMenuItem>
<DropdownMenuItem>
<Link href="/automations" className="no-underline w-full">
<div className="flex flex-rows">
<KhojAutomationLogo className="w-6 h-6" />
<p className="ml-3 font-semibold">Automations</p>
</div>
</Link>
</DropdownMenuItem>
{userData && (
<DropdownMenuItem>
<Link href="/search" className="no-underline w-full">
<div className="flex flex-rows">
<KhojSearchLogo className="w-6 h-6" />
<p className="ml-3 font-semibold">Search</p>
</div>
</Link>
</DropdownMenuItem>
)}
{userData && (
<DropdownMenuItem>
<Link href="/settings" className="no-underline w-full">
<div className="flex flex-rows">
<GearFine className="w-6 h-6" />
<p className="ml-3 font-semibold">Settings</p>
</div>
</Link>
</DropdownMenuItem>
)}
<>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Link href="https://docs.khoj.dev" className="no-underline w-full">
<div className="flex flex-rows">
<Question className="w-6 h-6" />
<p className="ml-3 font-semibold">Help</p>
</div>
</Link>
</DropdownMenuItem>
<DropdownMenuItem>
<Link
href="https://github.com/khoj-ai/khoj/releases"
className="no-underline w-full"
>
<div className="flex flex-rows">
<Code className="w-6 h-6" />
<p className="ml-3 font-semibold">Releases</p>
</div>
</Link>
</DropdownMenuItem>
{userData ? (
<DropdownMenuItem>
<Link href="/auth/logout" className="no-underline w-full">
<div className="flex flex-rows">
<ArrowRight className="w-6 h-6" />
<p className="ml-3 font-semibold">Logout</p>
</div>
</Link>
</DropdownMenuItem>
) : (
<DropdownMenuItem>
<Button
variant={"ghost"}
onClick={() => setShowLoginPrompt(true)}
className="no-underline w-full text-left p-0 content-start justify-start items-start h-fit"
>
<div className="flex flex-rows text-left content-start justify-start items-start p-0">
<ArrowRight className="w-6 h-6" />
<p className="ml-3 font-semibold">Login</p>
</div>
</Button>
</DropdownMenuItem>
)}
</>
</DropdownMenuContent>
</DropdownMenu>
) : (
<Menubar className="border-none">
<MenubarMenu>
<MenubarTrigger>
{userData ? (
<Avatar
className={`h-10 w-10 border-2 ${userData.is_active ? "border-yellow-500" : "border-stone-700 dark:border-stone-300"}`}
>
<AvatarImage src={userData?.photo} alt="user profile" />
<AvatarFallback className="bg-transparent hover:bg-muted">
{userData?.username[0].toUpperCase()}
</AvatarFallback>
</Avatar>
) : (
<UserCircle className="w-10 h-10" />
)}
</MenubarTrigger>
<MenubarContent align="end" className="rounded-xl gap-2">
<MenubarItem className="w-full">
<div className="flex flex-col">
<p className="font-semibold">{userData?.email}</p>
<SubscriptionBadge is_active={userData?.is_active ?? false} />
{userData?.khoj_version && (
<VersionBadge version={userData?.khoj_version} />
)}
</div>
</MenubarItem>
<MenubarSeparator className="dark:bg-white height-[2px] bg-black" />
<MenubarItem
onClick={() => setDarkMode(!darkMode)} onClick={() => setDarkMode(!darkMode)}
className="w-full hover:cursor-pointer" className="w-full hover:cursor-pointer"
> >
@@ -264,58 +147,17 @@ export default function NavMenu() {
{darkMode ? "Light Mode" : "Dark Mode"} {darkMode ? "Light Mode" : "Dark Mode"}
</p> </p>
</div> </div>
</MenubarItem> </DropdownMenuItem>
<MenubarItem> <DropdownMenuItem>
<Link href="/agents" className="no-underline w-full"> <Link href="https://docs.khoj.dev" className="no-underline w-full">
<div className="flex flex-rows">
<KhojAgentLogo className="w-6 h-6" />
<p className="ml-3 font-semibold">Agents</p>
</div>
</Link>
</MenubarItem>
<MenubarItem>
<Link href="/automations" className="no-underline w-full">
<div className="flex flex-rows">
<KhojAutomationLogo className="w-6 h-6" />
<p className="ml-3 font-semibold">Automations</p>
</div>
</Link>
</MenubarItem>
{userData && (
<MenubarItem>
<Link href="/search" className="no-underline w-full">
<div className="flex flex-rows">
<KhojSearchLogo className="w-6 h-6" />
<p className="ml-3 font-semibold">Search</p>
</div>
</Link>
</MenubarItem>
)}
{userData && (
<MenubarItem>
<Link href="/settings" className="no-underline w-full">
<div className="flex flex-rows">
<GearFine className="w-6 h-6" />
<p className="ml-3 font-semibold">Settings</p>
</div>
</Link>
</MenubarItem>
)}
<>
<MenubarSeparator className="dark:bg-white height-[2px] bg-black" />
<MenubarItem>
<Link
href="https://docs.khoj.dev"
className="no-underline w-full"
>
<div className="flex flex-rows"> <div className="flex flex-rows">
<Question className="w-6 h-6" /> <Question className="w-6 h-6" />
<p className="ml-3 font-semibold">Help</p> <p className="ml-3 font-semibold">Help</p>
</div> </div>
</Link> </Link>
</MenubarItem> </DropdownMenuItem>
<MenubarItem> <DropdownMenuItem>
<Link <Link
href="https://github.com/khoj-ai/khoj/releases" href="https://github.com/khoj-ai/khoj/releases"
className="no-underline w-full" className="no-underline w-full"
@@ -325,18 +167,18 @@ export default function NavMenu() {
<p className="ml-3 font-semibold">Releases</p> <p className="ml-3 font-semibold">Releases</p>
</div> </div>
</Link> </Link>
</MenubarItem> </DropdownMenuItem>
{userData ? ( {userData ? (
<MenubarItem> <DropdownMenuItem>
<Link href="/auth/logout" className="no-underline w-full"> <Link href="/auth/logout" className="no-underline w-full">
<div className="flex flex-rows"> <div className="flex flex-rows">
<ArrowRight className="w-6 h-6" /> <ArrowRight className="w-6 h-6" />
<p className="ml-3 font-semibold">Logout</p> <p className="ml-3 font-semibold">Logout</p>
</div> </div>
</Link> </Link>
</MenubarItem> </DropdownMenuItem>
) : ( ) : (
<MenubarItem> <DropdownMenuItem>
<Button <Button
variant={"ghost"} variant={"ghost"}
onClick={() => setShowLoginPrompt(true)} onClick={() => setShowLoginPrompt(true)}
@@ -347,13 +189,11 @@ export default function NavMenu() {
<p className="ml-3 font-semibold">Login</p> <p className="ml-3 font-semibold">Login</p>
</div> </div>
</Button> </Button>
</MenubarItem> </DropdownMenuItem>
)} )}
</> </DropdownMenuContent>
</MenubarContent> </DropdownMenu>
</MenubarMenu> </SidebarMenuItem>
</Menubar> </SidebarMenu>
)}
</div>
); );
} }

View File

@@ -3,6 +3,9 @@ import { noto_sans, noto_sans_arabic } from "@/app/fonts";
import "./globals.css"; import "./globals.css";
import { ContentSecurityPolicy } from "./common/layoutHelper"; import { ContentSecurityPolicy } from "./common/layoutHelper";
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
import { AppSidebar } from "@/app/components/appSidebar/appSidebar";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Khoj AI - Ask Anything", title: "Khoj AI - Ask Anything",
description: description:

View File

@@ -1,6 +1,8 @@
div.main { div.main {
height: 100dvh; height: 100dvh;
color: hsla(var(--foreground)); color: hsla(var(--foreground));
margin-left: auto;
margin-right: auto;
} }
div.suggestions { div.suggestions {
@@ -110,7 +112,7 @@ div.sidePanel {
div.chatBox { div.chatBox {
padding: 0; padding: 0;
height: 100%; height: 100vh;
} }
div.chatLayout { div.chatLayout {

View File

@@ -9,7 +9,6 @@ import { ArrowCounterClockwise } from "@phosphor-icons/react";
import { Card, CardTitle } from "@/components/ui/card"; import { Card, CardTitle } from "@/components/ui/card";
import SuggestionCard from "@/app/components/suggestions/suggestionCard"; import SuggestionCard from "@/app/components/suggestions/suggestionCard";
import SidePanel from "@/app/components/sidePanel/chatHistorySidePanel";
import Loading from "@/app/components/loading/loading"; import Loading from "@/app/components/loading/loading";
import { import {
AttachedFileText, AttachedFileText,
@@ -35,6 +34,8 @@ import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
import { AgentCard } from "@/app/components/agentCard/agentCard"; import { AgentCard } from "@/app/components/agentCard/agentCard";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import LoginPopup from "./components/loginPrompt/loginPopup"; import LoginPopup from "./components/loginPrompt/loginPopup";
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
import { AppSidebar } from "./components/appSidebar/appSidebar";
interface ChatBodyDataProps { interface ChatBodyDataProps {
chatOptionsData: ChatOptions | null; chatOptionsData: ChatOptions | null;
@@ -458,15 +459,18 @@ export default function Home() {
} }
return ( return (
<SidebarProvider>
<AppSidebar conversationId={conversationId} />
<SidebarTrigger />
<div className={`${styles.main} ${styles.chatLayout}`}> <div className={`${styles.main} ${styles.chatLayout}`}>
<title>Khoj AI - Your Second Brain</title> <title>Khoj AI - Your Second Brain</title>
<div className={`${styles.sidePanel}`}> {/* <div className={`${styles.sidePanel}`}>
<SidePanel <SidePanel
conversationId={conversationId} conversationId={conversationId}
uploadedFiles={[]} uploadedFiles={[]}
isMobileWidth={isMobileWidth} isMobileWidth={isMobileWidth}
/> />
</div> </div> */}
<div className={`${styles.chatBox}`}> <div className={`${styles.chatBox}`}>
<div className={`${styles.chatBoxBody}`}> <div className={`${styles.chatBoxBody}`}>
<ChatBodyData <ChatBodyData
@@ -481,5 +485,6 @@ export default function Home() {
</div> </div>
</div> </div>
</div> </div>
</SidebarProvider>
); );
} }

View File

@@ -2,9 +2,7 @@
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { useAuthenticatedData } from "../common/auth";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import SidePanel from "../components/sidePanel/chatHistorySidePanel";
import styles from "./search.module.css"; import styles from "./search.module.css";
import { ScrollArea } from "@/components/ui/scroll-area"; import { ScrollArea } from "@/components/ui/scroll-area";
import { import {
@@ -229,9 +227,6 @@ export default function Search() {
return ( return (
<div> <div>
<div className={`h-full ${styles.sidePanel}`}>
<SidePanel conversationId={null} uploadedFiles={[]} isMobileWidth={isMobileWidth} />
</div>
<div className={`${styles.searchLayout}`}> <div className={`${styles.searchLayout}`}>
<div className="md:w-3/4 sm:w-full mx-auto pt-6 md:pt-8"> <div className="md:w-3/4 sm:w-full mx-auto pt-6 md:pt-8">
<div className="p-4 md:w-3/4 sm:w-full mx-auto"> <div className="p-4 md:w-3/4 sm:w-full mx-auto">

View File

@@ -62,7 +62,6 @@ import {
Waveform, Waveform,
} from "@phosphor-icons/react"; } from "@phosphor-icons/react";
import SidePanel from "../components/sidePanel/chatHistorySidePanel";
import Loading from "../components/loading/loading"; import Loading from "../components/loading/loading";
import IntlTelInput from "intl-tel-input/react"; import IntlTelInput from "intl-tel-input/react";
@@ -77,6 +76,8 @@ import {
} from "@/components/ui/alert-dialog"; } from "@/components/ui/alert-dialog";
import { Progress } from "@/components/ui/progress"; import { Progress } from "@/components/ui/progress";
import Link from "next/link"; import Link from "next/link";
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
import { AppSidebar } from "../components/appSidebar/appSidebar";
const ManageFilesModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { const ManageFilesModal: React.FC<{ onClose: () => void }> = ({ onClose }) => {
const [syncedFiles, setSyncedFiles] = useState<string[]>([]); const [syncedFiles, setSyncedFiles] = useState<string[]>([]);
@@ -854,11 +855,11 @@ export default function SettingsView() {
if (!userConfig) return <Loading />; if (!userConfig) return <Loading />;
return ( return (
<SidebarProvider>
<AppSidebar conversationId={""} />
<SidebarTrigger />
<div className={styles.page}> <div className={styles.page}>
<title>{title}</title> <title>{title}</title>
<div className={styles.sidePanel}>
<SidePanel conversationId={null} uploadedFiles={[]} isMobileWidth={isMobileWidth} />
</div>
<div className={styles.content}> <div className={styles.content}>
<div className={`${styles.contentBody} mx-10 my-2`}> <div className={`${styles.contentBody} mx-10 my-2`}>
<Suspense fallback={<Loading />}> <Suspense fallback={<Loading />}>
@@ -911,9 +912,10 @@ export default function SettingsView() {
</p> </p>
<p className="text-gray-400"> <p className="text-gray-400">
You are on a{" "} You are on a{" "}
{userConfig.length_of_free_trial} day trial {userConfig.length_of_free_trial} day
of the Khoj Futurist plan. Your trial ends trial of the Khoj Futurist plan. Your
on {userConfig.subscription_renewal_date}. trial ends on{" "}
{userConfig.subscription_renewal_date}.
Check{" "} Check{" "}
<a <a
href="https://khoj.dev/#pricing" href="https://khoj.dev/#pricing"
@@ -925,7 +927,8 @@ export default function SettingsView() {
</p> </p>
</> </>
)) || )) ||
(userConfig.subscription_state === "subscribed" && ( (userConfig.subscription_state ===
"subscribed" && (
<> <>
<p className="text-xl text-primary/80"> <p className="text-xl text-primary/80">
Futurist Futurist
@@ -954,7 +957,8 @@ export default function SettingsView() {
</p> </p>
</> </>
)) || )) ||
(userConfig.subscription_state === "expired" && ( (userConfig.subscription_state ===
"expired" && (
<> <>
<p className="text-xl">Humanist</p> <p className="text-xl">Humanist</p>
{(userConfig.subscription_renewal_date && ( {(userConfig.subscription_renewal_date && (
@@ -1136,7 +1140,9 @@ export default function SettingsView() {
</p> </p>
{!userConfig.notion_oauth_url && ( {!userConfig.notion_oauth_url && (
<Input <Input
onChange={(e) => setNotionToken(e.target.value)} onChange={(e) =>
setNotionToken(e.target.value)
}
value={notionToken || ""} value={notionToken || ""}
placeholder="Enter API Key of your Khoj integration on Notion" placeholder="Enter API Key of your Khoj integration on Notion"
className="w-full border border-gray-300 rounded-lg px-4 py-6" className="w-full border border-gray-300 rounded-lg px-4 py-6"
@@ -1178,7 +1184,8 @@ export default function SettingsView() {
size="sm" size="sm"
onClick={saveNotionToken} onClick={saveNotionToken}
disabled={ disabled={
notionToken === userConfig.notion_token notionToken ===
userConfig.notion_token
} }
> >
<FloppyDisk className="h-5 w-5 inline mr-1" /> <FloppyDisk className="h-5 w-5 inline mr-1" />
@@ -1215,11 +1222,14 @@ export default function SettingsView() {
</CardHeader> </CardHeader>
<CardContent className="overflow-hidden pb-12 grid gap-8 h-fit"> <CardContent className="overflow-hidden pb-12 grid gap-8 h-fit">
<p className="text-gray-400"> <p className="text-gray-400">
Pick the chat model to generate text responses Pick the chat model to generate text
responses
</p> </p>
<DropdownComponent <DropdownComponent
items={userConfig.chat_model_options} items={userConfig.chat_model_options}
selected={userConfig.selected_chat_model_config} selected={
userConfig.selected_chat_model_config
}
callbackFunc={updateModel("chat")} callbackFunc={updateModel("chat")}
/> />
</CardContent> </CardContent>
@@ -1240,7 +1250,8 @@ export default function SettingsView() {
</CardHeader> </CardHeader>
<CardContent className="overflow-hidden pb-12 grid gap-8 h-fit"> <CardContent className="overflow-hidden pb-12 grid gap-8 h-fit">
<p className="text-gray-400"> <p className="text-gray-400">
Pick the paint model to generate image responses Pick the paint model to generate image
responses
</p> </p>
<DropdownComponent <DropdownComponent
items={userConfig.paint_model_options} items={userConfig.paint_model_options}
@@ -1306,7 +1317,10 @@ export default function SettingsView() {
className="!mt-0" className="!mt-0"
onClick={generateAPIKey} onClick={generateAPIKey}
> >
<Plus weight="bold" className="h-5 w-5 mr-2" /> <Plus
weight="bold"
className="h-5 w-5 mr-2"
/>
Generate Key Generate Key
</Button> </Button>
</CardHeader> </CardHeader>
@@ -1398,8 +1412,8 @@ export default function SettingsView() {
</CardHeader> </CardHeader>
<CardContent className="grid gap-4"> <CardContent className="grid gap-4">
<p className="text-gray-400"> <p className="text-gray-400">
Connect your number to chat with Khoj on WhatsApp. Connect your number to chat with Khoj on
Learn more about the integration{" "} WhatsApp. Learn more about the integration{" "}
<a href="https://docs.khoj.dev/clients/whatsapp"> <a href="https://docs.khoj.dev/clients/whatsapp">
here here
</a> </a>
@@ -1460,7 +1474,8 @@ export default function SettingsView() {
variant="outline" variant="outline"
disabled={ disabled={
!phoneNumber || !phoneNumber ||
(phoneNumber === userConfig.phone_number && (phoneNumber ===
userConfig.phone_number &&
numberValidationState === numberValidationState ===
PhoneNumberValidationState.Verified) || PhoneNumberValidationState.Verified) ||
!isValidPhoneNumber(phoneNumber) !isValidPhoneNumber(phoneNumber)
@@ -1473,7 +1488,8 @@ export default function SettingsView() {
Setup Whatsapp Setup Whatsapp
</> </>
) : !phoneNumber || ) : !phoneNumber ||
(phoneNumber === userConfig.phone_number && (phoneNumber ===
userConfig.phone_number &&
numberValidationState === numberValidationState ===
PhoneNumberValidationState.Verified) || PhoneNumberValidationState.Verified) ||
!isValidPhoneNumber(phoneNumber) ? ( !isValidPhoneNumber(phoneNumber) ? (
@@ -1511,5 +1527,6 @@ export default function SettingsView() {
</div> </div>
</div> </div>
</div> </div>
</SidebarProvider>
); );
} }

View File

@@ -3,7 +3,6 @@
import styles from "./sharedChat.module.css"; import styles from "./sharedChat.module.css";
import React, { Suspense, useEffect, useRef, useState } from "react"; import React, { Suspense, useEffect, useRef, useState } from "react";
import SidePanel from "../../components/sidePanel/chatHistorySidePanel";
import ChatHistory from "../../components/chatHistory/chatHistory"; import ChatHistory from "../../components/chatHistory/chatHistory";
import Loading from "../../components/loading/loading"; import Loading from "../../components/loading/loading";
@@ -19,6 +18,8 @@ import {
} from "@/app/components/chatInputArea/chatInputArea"; } from "@/app/components/chatInputArea/chatInputArea";
import { StreamMessage } from "@/app/components/chatMessage/chatMessage"; import { StreamMessage } from "@/app/components/chatMessage/chatMessage";
import { AgentData } from "@/app/agents/page"; import { AgentData } from "@/app/agents/page";
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
import { AppSidebar } from "@/app/components/appSidebar/appSidebar";
interface ChatBodyDataProps { interface ChatBodyDataProps {
chatOptionsData: ChatOptions | null; chatOptionsData: ChatOptions | null;
@@ -184,16 +185,11 @@ export default function SharedChat() {
} }
return ( return (
<SidebarProvider>
<AppSidebar conversationId={conversationId || ""} />
<SidebarTrigger />
<div className={`${styles.main} ${styles.chatLayout}`}> <div className={`${styles.main} ${styles.chatLayout}`}>
<title>{title}</title> <title>{title}</title>
<div className={styles.sidePanel}>
<SidePanel
conversationId={conversationId ?? null}
uploadedFiles={[]}
isMobileWidth={isMobileWidth}
/>
</div>
<div className={styles.chatBox}> <div className={styles.chatBox}>
<div className={styles.chatBoxBody}> <div className={styles.chatBoxBody}>
{!isMobileWidth && title && ( {!isMobileWidth && title && (
@@ -226,5 +222,6 @@ export default function SharedChat() {
</div> </div>
</div> </div>
</div> </div>
</SidebarProvider>
); );
} }

View File

@@ -1,6 +1,8 @@
div.main { div.main {
height: 100vh; height: 100vh;
color: hsla(var(--foreground)); color: hsla(var(--foreground));
margin-left: auto;
margin-right: auto;
} }
.suggestions { .suggestions {

View File

@@ -5,8 +5,8 @@ import { Slot } from "@radix-ui/react-slot";
import { VariantProps, cva } from "class-variance-authority"; import { VariantProps, cva } from "class-variance-authority";
import { PanelLeft } from "lucide-react"; import { PanelLeft } from "lucide-react";
import { useIsMobile } from "@/components/hooks/use-mobile"; import { useIsMobile } from "@/hooks/use-mobile";
import { cn } from "@/components/lib/utils"; import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";