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 LoginPrompt from "../components/loginPrompt/loginPrompt";
import { InlineLoading } from "../components/loading/loading";
import SidePanel from "../components/sidePanel/chatHistorySidePanel";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { useIsMobileWidth } from "../common/utils";
import {
@@ -30,6 +29,8 @@ import {
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
import { AppSidebar } from "../components/appSidebar/appSidebar";
export interface AgentData {
slug: string;
@@ -276,107 +277,113 @@ export default function Agents() {
);
return (
<main className={`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={`pt-6 md:pt-8 flex justify-between`}>
<h1 className="text-3xl flex items-center">Agents</h1>
<div className="ml-auto float-right border p-2 pt-3 rounded-xl font-bold hover:bg-stone-100 dark:hover:bg-neutral-900">
<CreateAgentCard
data={{
slug: "",
name: "",
persona: "",
color: "",
icon: "",
privacy_level: "private",
managed_by_admin: false,
chat_model: "",
input_tools: [],
output_modes: [],
}}
userProfile={authenticatedData}
<SidebarProvider>
<AppSidebar conversationId={""} />
<SidebarTrigger />
<main className={`w-full mx-auto`}>
<div className={`grid w-full mx-auto`}>
<div className={`${styles.pageLayout} w-full`}>
<div className={`pt-6 md:pt-8 flex justify-between`}>
<h1 className="text-3xl flex items-center">Agents</h1>
<div className="ml-auto float-right border p-2 pt-3 rounded-xl font-bold hover:bg-stone-100 dark:hover:bg-neutral-900">
<CreateAgentCard
data={{
slug: "",
name: "",
persona: "",
color: "",
icon: "",
privacy_level: "private",
managed_by_admin: false,
chat_model: "",
input_tools: [],
output_modes: [],
}}
userProfile={authenticatedData}
isMobileWidth={isMobileWidth}
filesOptions={filesData || []}
modelOptions={userConfig?.chat_model_options || []}
selectedChatModelOption={defaultModelOption?.name || ""}
isSubscribed={isSubscribed}
setAgentChangeTriggered={setAgentChangeTriggered}
inputToolOptions={agentConfigurationOptions?.input_tools || {}}
outputModeOptions={
agentConfigurationOptions?.output_modes || {}
}
/>
</div>
</div>
{showLoginPrompt && (
<LoginPrompt
loginRedirectMessage="Sign in to start chatting with a specialized agent"
onOpenChange={setShowLoginPrompt}
isMobileWidth={isMobileWidth}
filesOptions={filesData || []}
modelOptions={userConfig?.chat_model_options || []}
selectedChatModelOption={defaultModelOption?.name || ""}
isSubscribed={isSubscribed}
setAgentChangeTriggered={setAgentChangeTriggered}
inputToolOptions={agentConfigurationOptions?.input_tools || {}}
outputModeOptions={agentConfigurationOptions?.output_modes || {}}
/>
</div>
</div>
{showLoginPrompt && (
<LoginPrompt
loginRedirectMessage="Sign in to start chatting with a specialized agent"
onOpenChange={setShowLoginPrompt}
isMobileWidth={isMobileWidth}
/>
)}
<Alert className="bg-secondary border-none my-4">
<AlertDescription>
<Lightning weight={"fill"} className="h-4 w-4 text-purple-400 inline" />
<span className="font-bold">How it works</span> Use any of these
specialized personas to tune your conversation to your needs.
</AlertDescription>
</Alert>
<div className="pt-6 md:pt-8">
<div className={`${styles.agentList}`}>
{personalAgents.map((agent) => (
<AgentCard
key={agent.slug}
data={agent}
userProfile={authenticatedData}
isMobileWidth={isMobileWidth}
filesOptions={filesData ?? []}
selectedChatModelOption={defaultModelOption?.name || ""}
isSubscribed={isSubscribed}
setAgentChangeTriggered={setAgentChangeTriggered}
modelOptions={userConfig?.chat_model_options || []}
editCard={true}
agentSlug={agentSlug || ""}
inputToolOptions={agentConfigurationOptions?.input_tools || {}}
outputModeOptions={
agentConfigurationOptions?.output_modes || {}
}
)}
<Alert className="bg-secondary border-none my-4">
<AlertDescription>
<Lightning
weight={"fill"}
className="h-4 w-4 text-purple-400 inline"
/>
))}
<span className="font-bold">How it works</span> Use any of these
specialized personas to tune your conversation to your needs.
</AlertDescription>
</Alert>
<div className="pt-6 md:pt-8">
<div className={`${styles.agentList}`}>
{personalAgents.map((agent) => (
<AgentCard
key={agent.slug}
data={agent}
userProfile={authenticatedData}
isMobileWidth={isMobileWidth}
filesOptions={filesData ?? []}
selectedChatModelOption={defaultModelOption?.name || ""}
isSubscribed={isSubscribed}
setAgentChangeTriggered={setAgentChangeTriggered}
modelOptions={userConfig?.chat_model_options || []}
editCard={true}
agentSlug={agentSlug || ""}
inputToolOptions={
agentConfigurationOptions?.input_tools || {}
}
outputModeOptions={
agentConfigurationOptions?.output_modes || {}
}
/>
))}
</div>
</div>
</div>
<div className="pt-6 md:pt-8">
<h2 className="text-2xl">Explore</h2>
<div className={`${styles.agentList}`}>
{publicAgents.map((agent) => (
<AgentCard
key={agent.slug}
data={agent}
userProfile={authenticatedData}
isMobileWidth={isMobileWidth}
editCard={false}
filesOptions={filesData ?? []}
selectedChatModelOption={defaultModelOption?.name || ""}
isSubscribed={isSubscribed}
setAgentChangeTriggered={setAgentChangeTriggered}
modelOptions={userConfig?.chat_model_options || []}
agentSlug={agentSlug || ""}
inputToolOptions={agentConfigurationOptions?.input_tools || {}}
outputModeOptions={
agentConfigurationOptions?.output_modes || {}
}
/>
))}
<div className="pt-6 md:pt-8">
<h2 className="text-2xl">Explore</h2>
<div className={`${styles.agentList}`}>
{publicAgents.map((agent) => (
<AgentCard
key={agent.slug}
data={agent}
userProfile={authenticatedData}
isMobileWidth={isMobileWidth}
editCard={false}
filesOptions={filesData ?? []}
selectedChatModelOption={defaultModelOption?.name || ""}
isSubscribed={isSubscribed}
setAgentChangeTriggered={setAgentChangeTriggered}
modelOptions={userConfig?.chat_model_options || []}
agentSlug={agentSlug || ""}
inputToolOptions={
agentConfigurationOptions?.input_tools || {}
}
outputModeOptions={
agentConfigurationOptions?.output_modes || {}
}
/>
))}
</div>
</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 { ToastAction } from "@/components/ui/toast";
import { Alert, AlertDescription } from "@/components/ui/alert";
import SidePanel from "../components/sidePanel/chatHistorySidePanel";
import { Drawer, DrawerContent, DrawerTitle, DrawerTrigger } from "@/components/ui/drawer";
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
import { AppSidebar } from "../components/appSidebar/appSidebar";
const automationsFetcher = () =>
window
@@ -1023,94 +1024,106 @@ export default function Automations() {
return <InlineLoading message="Oops, something went wrong. Please refresh the page." />;
return (
<main className={`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="pt-6 md:pt-8 grid gap-1 md:flex md:justify-between">
<h1 className="text-3xl flex items-center">Automations</h1>
<div className="flex flex-wrap gap-2 items-center justify-start">
<SidebarProvider>
<AppSidebar conversationId={""} />
<SidebarTrigger />
<main className={`w-full mx-auto`}>
<div className={`grid w-full mx-auto`}>
<div className={`${styles.pageLayout} w-full`}>
<div className="pt-6 md:pt-8 grid gap-1 md:flex md:justify-between">
<h1 className="text-3xl flex items-center">Automations</h1>
<div className="flex flex-wrap gap-2 items-center justify-start">
{authenticatedData ? (
<span className="rounded-lg text-sm border-secondary border p-1 flex items-center shadow-sm dark:bg-muted">
<Envelope className="h-4 w-4 mr-2 inline text-orange-500 shadow-sm" />
{authenticatedData.email}
</span>
) : null}
{locationData && (
<span className="rounded-lg text-sm border-secondary border p-1 flex items-center shadow-sm dark:bg-muted">
<MapPinSimple className="h-4 w-4 mr-2 inline text-purple-500" />
{locationData
? `${locationData.city}, ${locationData.country}`
: "Unknown"}
</span>
)}
{locationData && (
<span className="rounded-lg text-sm border-secondary border p-1 flex items-center shadow-sm dark:bg-muted">
<Clock className="h-4 w-4 mr-2 inline text-green-500" />
{locationData ? `${locationData.timezone}` : "Unknown"}
</span>
)}
</div>
</div>
{showLoginPrompt && (
<LoginPrompt
loginRedirectMessage={
"Create an account to make your own automation"
}
onOpenChange={setShowLoginPrompt}
isMobileWidth={isMobileWidth}
/>
)}
<Alert className="bg-secondary border-none my-4">
<AlertDescription>
<Lightning
weight={"fill"}
className="h-4 w-4 text-purple-400 inline"
/>
<span className="font-bold">How it works</span> Automations help you
structure your time by automating tasks you do regularly. Build your
own, or try out our presets. Get results straight to your inbox.
</AlertDescription>
</Alert>
<div className="flex justify-between items-center py-4">
{authenticatedData ? (
<span className="rounded-lg text-sm border-secondary border p-1 flex items-center shadow-sm dark:bg-muted">
<Envelope className="h-4 w-4 mr-2 inline text-orange-500 shadow-sm" />
{authenticatedData.email}
</span>
) : null}
{locationData && (
<span className="rounded-lg text-sm border-secondary border p-1 flex items-center shadow-sm dark:bg-muted">
<MapPinSimple className="h-4 w-4 mr-2 inline text-purple-500" />
{locationData
? `${locationData.city}, ${locationData.country}`
: "Unknown"}
</span>
)}
{locationData && (
<span className="rounded-lg text-sm border-secondary border p-1 flex items-center shadow-sm dark:bg-muted">
<Clock className="h-4 w-4 mr-2 inline text-green-500" />
{locationData ? `${locationData.timezone}` : "Unknown"}
</span>
<AutomationComponentWrapper
isMobileWidth={isMobileWidth}
callToAction="Create Automation"
createNew={true}
setIsCreating={setIsCreating}
setShowLoginPrompt={setShowLoginPrompt}
setNewAutomationData={setNewAutomationData}
authenticatedData={authenticatedData}
isCreating={isCreating}
ipLocationData={locationData}
/>
) : (
<Button
className="shadow-sm"
onClick={() => setShowLoginPrompt(true)}
variant={"outline"}
>
<Plus className="h-4 w-4 mr-2" />
Create Automation
</Button>
)}
</div>
</div>
{showLoginPrompt && (
<LoginPrompt
loginRedirectMessage={"Create an account to make your own automation"}
onOpenChange={setShowLoginPrompt}
isMobileWidth={isMobileWidth}
/>
)}
<Alert className="bg-secondary border-none my-4">
<AlertDescription>
<Lightning weight={"fill"} className="h-4 w-4 text-purple-400 inline" />
<span className="font-bold">How it works</span> Automations help you
structure your time by automating tasks you do regularly. Build your
own, or try out our presets. Get results straight to your inbox.
</AlertDescription>
</Alert>
<div className="flex justify-between items-center py-4">
{authenticatedData ? (
<AutomationComponentWrapper
<Suspense>
<SharedAutomationCard
isMobileWidth={isMobileWidth}
callToAction="Create Automation"
createNew={true}
setIsCreating={setIsCreating}
authenticatedData={authenticatedData}
locationData={locationData}
isLoggedIn={authenticatedData ? true : false}
setShowLoginPrompt={setShowLoginPrompt}
setNewAutomationData={setNewAutomationData}
authenticatedData={authenticatedData}
isCreating={isCreating}
ipLocationData={locationData}
/>
) : (
<Button
className="shadow-sm"
onClick={() => setShowLoginPrompt(true)}
variant={"outline"}
>
<Plus className="h-4 w-4 mr-2" />
Create Automation
</Button>
)}
</div>
<Suspense>
<SharedAutomationCard
isMobileWidth={isMobileWidth}
authenticatedData={authenticatedData}
locationData={locationData}
isLoggedIn={authenticatedData ? true : false}
setShowLoginPrompt={setShowLoginPrompt}
setNewAutomationData={setNewAutomationData}
/>
</Suspense>
{isLoading && <InlineLoading message="booting up your automations" />}
<div className={`${styles.automationsLayout}`}>
{personalAutomations &&
personalAutomations.map((automation) => (
</Suspense>
{isLoading && <InlineLoading message="booting up your automations" />}
<div className={`${styles.automationsLayout}`}>
{personalAutomations &&
personalAutomations.map((automation) => (
<AutomationsCard
isMobileWidth={isMobileWidth}
key={automation.id}
authenticatedData={authenticatedData}
automation={automation}
locationData={locationData}
isLoggedIn={authenticatedData ? true : false}
setShowLoginPrompt={setShowLoginPrompt}
/>
))}
{allNewAutomations.map((automation) => (
<AutomationsCard
isMobileWidth={isMobileWidth}
key={automation.id}
@@ -1121,36 +1134,26 @@ export default function Automations() {
setShowLoginPrompt={setShowLoginPrompt}
/>
))}
{allNewAutomations.map((automation) => (
<AutomationsCard
isMobileWidth={isMobileWidth}
key={automation.id}
authenticatedData={authenticatedData}
automation={automation}
locationData={locationData}
isLoggedIn={authenticatedData ? true : false}
setShowLoginPrompt={setShowLoginPrompt}
/>
))}
</div>
<h3 className="text-xl py-4">Explore</h3>
<div className={`${styles.automationsLayout}`}>
{suggestedAutomations.map((automation) => (
<AutomationsCard
isMobileWidth={isMobileWidth}
setNewAutomationData={setNewAutomationData}
key={automation.id}
authenticatedData={authenticatedData}
automation={automation}
locationData={locationData}
isLoggedIn={authenticatedData ? true : false}
setShowLoginPrompt={setShowLoginPrompt}
suggestedCard={true}
/>
))}
</div>
<h3 className="text-xl py-4">Explore</h3>
<div className={`${styles.automationsLayout}`}>
{suggestedAutomations.map((automation) => (
<AutomationsCard
isMobileWidth={isMobileWidth}
setNewAutomationData={setNewAutomationData}
key={automation.id}
authenticatedData={authenticatedData}
automation={automation}
locationData={locationData}
isLoggedIn={authenticatedData ? true : false}
setShowLoginPrompt={setShowLoginPrompt}
suggestedCard={true}
/>
))}
</div>
</div>
</div>
</div>
</main>
</main>
</SidebarProvider>
);
}

View File

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

View File

@@ -3,7 +3,6 @@
import styles from "./chat.module.css";
import React, { Suspense, useEffect, useRef, useState } from "react";
import SidePanel, { ChatSessionActionMenu } from "../components/sidePanel/chatHistorySidePanel";
import ChatHistory from "../components/chatHistory/chatHistory";
import { useSearchParams } from "next/navigation";
import Loading from "../components/loading/loading";
@@ -26,6 +25,9 @@ import {
} from "../components/chatInputArea/chatInputArea";
import { useAuthenticatedData } from "../common/auth";
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 {
chatOptionsData: ChatOptions | null;
@@ -382,54 +384,51 @@ export default function Chat() {
if (isLoading) return <Loading />;
return (
<div className={`${styles.main} ${styles.chatLayout}`}>
<title>
{`${defaultTitle}${!!title && title !== defaultTitle ? `: ${title}` : ""}`}
</title>
<div className={isMobileWidth ? "h-1" : "h-auto"}>
<SidePanel
conversationId={conversationId}
uploadedFiles={[]}
isMobileWidth={isMobileWidth}
/>
</div>
<div className={styles.chatBox}>
<div className={styles.chatBoxBody}>
{conversationId && (
<div
className={`${styles.chatTitleWrapper} text-nowrap text-ellipsis overflow-hidden max-w-screen-md grid items-top font-bold mx-2 md:mr-8 md:pt-6 col-auto h-fit`}
>
{title && (
<h2
className={`text-lg text-ellipsis whitespace-nowrap overflow-x-hidden`}
>
{title}
</h2>
)}
<ChatSessionActionMenu
conversationId={conversationId}
<SidebarProvider>
<AppSidebar conversationId={conversationId || ""} />
<SidebarTrigger />
<div className={`${styles.main} ${styles.chatLayout}`}>
<title>
{`${defaultTitle}${!!title && title !== defaultTitle ? `: ${title}` : ""}`}
</title>
<div className={styles.chatBox}>
<div className={styles.chatBoxBody}>
{conversationId && (
<div
className={`${styles.chatTitleWrapper} text-nowrap text-ellipsis overflow-hidden max-w-screen-md grid items-top font-bold mx-2 md:mr-8 md:pt-6 col-auto h-fit`}
>
{title && (
<h2
className={`text-lg text-ellipsis whitespace-nowrap overflow-x-hidden`}
>
{title}
</h2>
)}
<ChatSessionActionMenu
conversationId={conversationId}
setTitle={setTitle}
sizing={isMobileWidth ? "sm" : "md"}
/>
</div>
)}
<Suspense fallback={<Loading />}>
<ChatBodyData
isLoggedIn={authenticatedData !== null}
streamedMessages={messages}
setStreamedMessages={setMessages}
chatOptionsData={chatOptionsData}
setTitle={setTitle}
sizing={isMobileWidth ? "sm" : "md"}
setQueryToProcess={setQueryToProcess}
setUploadedFiles={setUploadedFiles}
isMobileWidth={isMobileWidth}
onConversationIdChange={handleConversationIdChange}
setImages={setImages}
setTriggeredAbort={setTriggeredAbort}
/>
</div>
)}
<Suspense fallback={<Loading />}>
<ChatBodyData
isLoggedIn={authenticatedData !== null}
streamedMessages={messages}
setStreamedMessages={setMessages}
chatOptionsData={chatOptionsData}
setTitle={setTitle}
setQueryToProcess={setQueryToProcess}
setUploadedFiles={setUploadedFiles}
isMobileWidth={isMobileWidth}
onConversationIdChange={handleConversationIdChange}
setImages={setImages}
setTriggeredAbort={setTriggeredAbort}
/>
</Suspense>
</Suspense>
</div>
</div>
</div>
</div>
</SidebarProvider>
);
}

View File

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

View File

@@ -74,6 +74,14 @@ p.session {
font-size: small;
}
p.compressed {
width: 12rem;
}
p.expanded {
max-width: 20rem;
}
div.header {
display: grid;
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 LoginPrompt from "../loginPrompt/loginPrompt";
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 }) {
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 [darkMode, setDarkMode] = useState(false);
const [initialLoadDone, setInitialLoadDone] = useState(false);
@@ -89,31 +95,34 @@ export default function NavMenu() {
}
return (
<div className={styles.titleBar}>
{showLoginPrompt && (
<LoginPrompt
onOpenChange={setShowLoginPrompt}
isMobileWidth={isMobileWidth}
loginRedirectMessage={"Login to your second brain"}
/>
)}
{isMobileWidth ? (
<SidebarMenu className="border-none p-0 m-0">
<SidebarMenuItem className="p-0 m-0">
<DropdownMenu>
<DropdownMenuTrigger>
{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="h-10 w-10" />
)}
<DropdownMenuTrigger asChild>
<SidebarMenuButton className="p-0 m-0 rounded-lg" asChild>
{userData ? (
<span className="flex items-center gap-2">
<Avatar
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" />
<AvatarFallback className="bg-transparent hover:bg-muted">
{userData?.username[0].toUpperCase()}
</AvatarFallback>
</Avatar>
{sideBarIsOpen && (
<>
<p>{userData?.username}</p>
<ChevronUp className="w-6 h-6" />
</>
)}
</span>
) : (
<UserCircle className="w-10 h-10" />
)}
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent className="gap-2">
<DropdownMenuContent align="end" className="rounded-xl gap-2">
<DropdownMenuItem className="w-full">
<div className="flex flex-col">
<p className="font-semibold">{userData?.email}</p>
@@ -123,10 +132,10 @@ export default function NavMenu() {
)}
</div>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuSeparator className="dark:bg-white height-[2px] bg-black" />
<DropdownMenuItem
onClick={() => setDarkMode(!darkMode)}
className="w-full cursor-pointer"
className="w-full hover:cursor-pointer"
>
<div className="flex flex-rows">
{darkMode ? (
@@ -140,220 +149,51 @@ export default function NavMenu() {
</div>
</DropdownMenuItem>
<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>
<Question className="w-6 h-6" />
<p className="ml-3 font-semibold">Help</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)}
className="w-full hover:cursor-pointer"
<Link
href="https://github.com/khoj-ai/khoj/releases"
className="no-underline w-full"
>
<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>
<Code className="w-6 h-6" />
<p className="ml-3 font-semibold">Releases</p>
</div>
</MenubarItem>
<MenubarItem>
<Link href="/agents" className="no-underline w-full">
</Link>
</DropdownMenuItem>
{userData ? (
<DropdownMenuItem>
<Link href="/auth/logout" className="no-underline w-full">
<div className="flex flex-rows">
<KhojAgentLogo className="w-6 h-6" />
<p className="ml-3 font-semibold">Agents</p>
<ArrowRight className="w-6 h-6" />
<p className="ml-3 font-semibold">Logout</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>
</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>
</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">
<Question className="w-6 h-6" />
<p className="ml-3 font-semibold">Help</p>
</div>
</Link>
</MenubarItem>
<MenubarItem>
<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>
</MenubarItem>
{userData ? (
<MenubarItem>
<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>
</MenubarItem>
) : (
<MenubarItem>
<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>
</MenubarItem>
)}
</>
</MenubarContent>
</MenubarMenu>
</Menubar>
)}
</div>
</Button>
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
);
}

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,6 @@
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 Loading from "../../components/loading/loading";
@@ -19,6 +18,8 @@ import {
} from "@/app/components/chatInputArea/chatInputArea";
import { StreamMessage } from "@/app/components/chatMessage/chatMessage";
import { AgentData } from "@/app/agents/page";
import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
import { AppSidebar } from "@/app/components/appSidebar/appSidebar";
interface ChatBodyDataProps {
chatOptionsData: ChatOptions | null;
@@ -184,47 +185,43 @@ export default function SharedChat() {
}
return (
<div className={`${styles.main} ${styles.chatLayout}`}>
<title>{title}</title>
<div className={styles.sidePanel}>
<SidePanel
conversationId={conversationId ?? null}
uploadedFiles={[]}
isMobileWidth={isMobileWidth}
/>
</div>
<div className={styles.chatBox}>
<div className={styles.chatBoxBody}>
{!isMobileWidth && title && (
<div
className={`${styles.chatTitleWrapper} text-nowrap text-ellipsis overflow-hidden max-w-screen-md grid items-top font-bold mr-8 pt-6 col-auto h-fit`}
>
{title && (
<h2
className={`text-lg text-ellipsis whitespace-nowrap overflow-x-hidden`}
>
{title}
</h2>
)}
</div>
)}
<Suspense fallback={<Loading />}>
<ChatBodyData
conversationId={conversationId}
streamedMessages={messages}
setQueryToProcess={setQueryToProcess}
isLoggedIn={authenticatedData !== null}
publicConversationSlug={paramSlug}
chatOptionsData={chatOptionsData}
setTitle={setTitle}
setUploadedFiles={setUploadedFiles}
isMobileWidth={isMobileWidth}
setImages={setImages}
/>
</Suspense>
<SidebarProvider>
<AppSidebar conversationId={conversationId || ""} />
<SidebarTrigger />
<div className={`${styles.main} ${styles.chatLayout}`}>
<title>{title}</title>
<div className={styles.chatBox}>
<div className={styles.chatBoxBody}>
{!isMobileWidth && title && (
<div
className={`${styles.chatTitleWrapper} text-nowrap text-ellipsis overflow-hidden max-w-screen-md grid items-top font-bold mr-8 pt-6 col-auto h-fit`}
>
{title && (
<h2
className={`text-lg text-ellipsis whitespace-nowrap overflow-x-hidden`}
>
{title}
</h2>
)}
</div>
)}
<Suspense fallback={<Loading />}>
<ChatBodyData
conversationId={conversationId}
streamedMessages={messages}
setQueryToProcess={setQueryToProcess}
isLoggedIn={authenticatedData !== null}
publicConversationSlug={paramSlug}
chatOptionsData={chatOptionsData}
setTitle={setTitle}
setUploadedFiles={setUploadedFiles}
isMobileWidth={isMobileWidth}
setImages={setImages}
/>
</Suspense>
</div>
</div>
</div>
</div>
</SidebarProvider>
);
}

View File

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

View File

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