diff --git a/src/interface/web/app/agents/agents.module.css b/src/interface/web/app/agents/agents.module.css index fe4c3b85..e60a9758 100644 --- a/src/interface/web/app/agents/agents.module.css +++ b/src/interface/web/app/agents/agents.module.css @@ -22,12 +22,6 @@ div.pageLayout { margin-bottom: 2rem; } -div.sidePanel { - position: fixed; - height: 100%; - z-index: 1; -} - button.infoButton { border: none; background-color: transparent !important; @@ -58,9 +52,4 @@ div.agentList { div.pageLayout { max-width: 90vw; } - - div.sidePanel { - position: relative; - height: 100%; - } } diff --git a/src/interface/web/app/agents/page.tsx b/src/interface/web/app/agents/page.tsx index 58abe7fa..0ec76839 100644 --- a/src/interface/web/app/agents/page.tsx +++ b/src/interface/web/app/agents/page.tsx @@ -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,10 @@ import { import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; +import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"; +import { AppSidebar } from "../components/appSidebar/appSidebar"; +import { Separator } from "@/components/ui/separator"; +import { KhojLogoType } from "../components/logo/khojLogo"; export interface AgentData { slug: string; @@ -276,107 +279,125 @@ export default function Agents() { ); return ( -
-
-
- -
-
-
-

Agents

-
- -
-
- {showLoginPrompt && ( - + + + +
+ + + {isMobileWidth ? ( + + ) : ( +

Agents

)} - - - - How it works Use any of these - specialized personas to tune your conversation to your needs. - - -
-
- {personalAgents.map((agent) => ( - +
+
+
+
+

Agents

+
+ +
+
+ {showLoginPrompt && ( + - ))} + )} + + + + How it works Use any of these + specialized personas to tune your conversation to your needs. + + +
+
+ {personalAgents.map((agent) => ( + + ))} +
+
+
+

Explore

+
+ {publicAgents.map((agent) => ( + + ))} +
+
-
-

Explore

-
- {publicAgents.map((agent) => ( - - ))} -
-
-
-
-
+ + + ); } diff --git a/src/interface/web/app/automations/automations.module.css b/src/interface/web/app/automations/automations.module.css index 30563e6c..b26e7777 100644 --- a/src/interface/web/app/automations/automations.module.css +++ b/src/interface/web/app/automations/automations.module.css @@ -15,12 +15,6 @@ div.pageLayout { margin-bottom: 2rem; } -div.sidePanel { - position: fixed; - height: 100%; - z-index: 1; -} - @media screen and (max-width: 768px) { div.automationsLayout { grid-template-columns: 1fr; @@ -29,9 +23,4 @@ div.sidePanel { div.pageLayout { max-width: 90vw; } - - div.sidePanel { - position: relative; - height: 100%; - } } diff --git a/src/interface/web/app/automations/page.tsx b/src/interface/web/app/automations/page.tsx index d71d96af..f7fbb906 100644 --- a/src/interface/web/app/automations/page.tsx +++ b/src/interface/web/app/automations/page.tsx @@ -66,8 +66,11 @@ 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 { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"; +import { AppSidebar } from "../components/appSidebar/appSidebar"; +import { Separator } from "@/components/ui/separator"; +import { KhojLogoType } from "../components/logo/khojLogo"; const automationsFetcher = () => window @@ -392,13 +395,13 @@ function AutomationsCard(props: AutomationsCardProps) {
-
+
{timeRecurrence}
-
+
{intervalString} @@ -1023,134 +1026,147 @@ export default function Automations() { return ; return ( -
-
-
- -
-
-
-

Automations

-
- {authenticatedData ? ( - - - {authenticatedData.email} - - ) : null} - {locationData && ( - - - {locationData - ? `${locationData.city}, ${locationData.country}` - : "Unknown"} - - )} - {locationData && ( - - - {locationData ? `${locationData.timezone}` : "Unknown"} - - )} -
-
- {showLoginPrompt && ( - + + + +
+ + + {isMobileWidth ? ( + + ) : ( +

Automations

)} - - - - How it works 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. - - -
- {authenticatedData ? ( - - ) : ( - - )} -
- - - - {isLoading && } -
- {personalAutomations && - personalAutomations.map((automation) => ( - +
+
+
+
+

Automations

+
+ {authenticatedData ? ( + + + {authenticatedData.email} + + ) : null} + {locationData && ( + + + {locationData + ? `${locationData.city}, ${locationData.country}` + : "Unknown"} + + )} + {locationData && ( + + + {locationData ? `${locationData.timezone}` : "Unknown"} + + )} +
+
+ {showLoginPrompt && ( + + )} + + + + How it works 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. + + +
+ {authenticatedData ? ( + + ) : ( + + )} +
+ + - ))} - {allNewAutomations.map((automation) => ( - - ))} + + {isLoading && } +
+ {personalAutomations && + personalAutomations.map((automation) => ( + + ))} + {allNewAutomations.map((automation) => ( + + ))} +
+

Explore

+
+ {suggestedAutomations.map((automation) => ( + + ))} +
+
-

Explore

-
- {suggestedAutomations.map((automation) => ( - - ))} -
-
-
-
+ + + ); } diff --git a/src/interface/web/app/chat/chat.module.css b/src/interface/web/app/chat/chat.module.css index 69be25b4..9bd95434 100644 --- a/src/interface/web/app/chat/chat.module.css +++ b/src/interface/web/app/chat/chat.module.css @@ -1,6 +1,8 @@ div.main { - height: 100dvh; + height: 100%; color: hsla(var(--foreground)); + margin-left: auto; + margin-right: auto; } .suggestions { @@ -64,6 +66,7 @@ div.chatLayout { display: grid; grid-template-columns: auto 1fr; gap: 1rem; + padding-top: 1rem; } div.chatBox { diff --git a/src/interface/web/app/chat/page.tsx b/src/interface/web/app/chat/page.tsx index ed73e66f..91a7c12b 100644 --- a/src/interface/web/app/chat/page.tsx +++ b/src/interface/web/app/chat/page.tsx @@ -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,11 @@ import { } from "../components/chatInputArea/chatInputArea"; import { useAuthenticatedData } from "../common/auth"; import { AgentData } from "../agents/page"; +import { ChatSessionActionMenu } from "../components/allConversations/allConversations"; +import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"; +import { AppSidebar } from "../components/appSidebar/appSidebar"; +import { Separator } from "@/components/ui/separator"; +import { KhojLogoType } from "../components/logo/khojLogo"; interface ChatBodyDataProps { chatOptionsData: ChatOptions | null; @@ -147,7 +151,7 @@ function ChatBodyData(props: ChatBodyDataProps) { />
; return ( -
- - {`${defaultTitle}${!!title && title !== defaultTitle ? `: ${title}` : ""}`} - -
- -
-
-
+ + + +
+ + {conversationId && (
- {title && ( -

- {title} -

+ {isMobileWidth ? ( + + ) : ( + title && ( + <> +

+ {title} +

+ + + ) )} -
)} - }> - - +
+
+ + {`${defaultTitle}${!!title && title !== defaultTitle ? `: ${title}` : ""}`} + +
+
+ }> + + +
+
-
-
+ + ); } diff --git a/src/interface/web/app/components/sidePanel/chatHistorySidePanel.tsx b/src/interface/web/app/components/allConversations/allConversations.tsx similarity index 78% rename from src/interface/web/app/components/sidePanel/chatHistorySidePanel.tsx rename to src/interface/web/app/components/allConversations/allConversations.tsx index 768917a8..aa129469 100644 --- a/src/interface/web/app/components/sidePanel/chatHistorySidePanel.tsx +++ b/src/interface/web/app/components/allConversations/allConversations.tsx @@ -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 ( - <> -
- {props.data && props.data.length > 5 && ( - - )} +
+ {props.data && props.data.length > 5 && ( + + )} + {props.sideBarOpen && ( -
+
{props.subsetOrganizedData != null && Object.keys(props.subsetOrganizedData) .filter((tg) => tg !== "All Time") .map((timeGrouping) => ( -
-
+
+
{timeGrouping}
{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) { ))}
-
- - + )} +
); } @@ -638,41 +636,37 @@ export function ChatSessionActionMenu(props: ChatSessionActionMenuProps) { )} setIsOpen(open)} open={isOpen}> - - + + {props.sizing === "lg" || props.sizing === "md" ? ( + + ) : ( + + + + )} - - - + {props.sizing === "sm" && ( - - + )} - - + @@ -686,30 +680,36 @@ function ChatSession(props: ChatHistory) { var currConversationId = new URLSearchParams(window.location.search).get("conversationId") || "-1"; return ( -
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" : ""}`} > - props.showSidePanel(false)} - > -

{title}

- + + +

+ {title} +

+ +
-
+ ); } interface ChatSessionsModalProps { data: GroupedChatHistory | null; - showSidePanel: (isEnabled: boolean) => void; + sideBarOpen: boolean; } interface AgentStyle { @@ -717,7 +717,7 @@ interface AgentStyle { icon: string; } -function ChatSessionsModal({ data, showSidePanel }: ChatSessionsModalProps) { +function ChatSessionsModal({ data, sideBarOpen }: ChatSessionsModalProps) { const [agentsFilter, setAgentsFilter] = useState([]); const [agentOptions, setAgentOptions] = useState([]); const [searchQuery, setSearchQuery] = useState(""); @@ -786,10 +786,12 @@ function ChatSessionsModal({ data, showSidePanel }: ChatSessionsModalProps) { return ( - + - Find - Conversation + + {sideBarOpen ? "Find Conversation" : ""} @@ -814,7 +816,6 @@ function ChatSessionsModal({ data, showSidePanel }: ChatSessionsModalProps) { - {/* */} Agents {agentOptions.map((agent) => ( @@ -841,7 +842,6 @@ function ChatSessionsModal({ data, showSidePanel }: ChatSessionsModalProps) {
))} - {/* */}
@@ -865,7 +865,6 @@ function ChatSessionsModal({ data, showSidePanel }: ChatSessionsModalProps) { agent_name={chatHistory.agent_name} agent_color={chatHistory.agent_color} agent_icon={chatHistory.agent_icon} - showSidePanel={showSidePanel} /> ))}
@@ -902,17 +901,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(null); const [organizedData, setOrganizedData] = useState(null); const [subsetOrganizedData, setSubsetOrganizedData] = useState(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 +952,49 @@ export default function SidePanel(props: SidePanelProps) { } }, [chatSessions]); + if (isLoading) { + return ( + + Conversations + {Array.from({ length: 5 }).map((_, index) => ( + + + + ))} + + ); + } + return ( -
- {showLoginPrompt && ( - - )} -
- {props.isMobileWidth ? ( - { - if (!enabled) setEnabled(false); - setEnabled(open); - }} - > - - - - - - Sessions and Files - - View all conversation sessions and manage conversation file - filters - - - {authenticatedData ? ( -
- -
- ) : ( -
- {" "} - {/* Redirect to login page */} - -
- )} - - - - - -
-
- ) : ( -
- - - -
- - {enabled ? ( - - ) : ( - - )} - - + + Conversations +
+ {authenticatedData && ( + <> +
+
-
- -
-
+ {props.sideBarOpen && ( + + )} + )} - {props.isMobileWidth && ( - - - - )} - {props.isMobileWidth && }
- {authenticatedData && !props.isMobileWidth && enabled && ( -
- -
- )} - {!authenticatedData && enabled && !props.isMobileWidth && ( -
- - - - {" "} - -
- )} -
+ ); } diff --git a/src/interface/web/app/components/sidePanel/sidePanel.module.css b/src/interface/web/app/components/allConversations/sidePanel.module.css similarity index 97% rename from src/interface/web/app/components/sidePanel/sidePanel.module.css rename to src/interface/web/app/components/allConversations/sidePanel.module.css index cf412452..5a8a1bd6 100644 --- a/src/interface/web/app/components/sidePanel/sidePanel.module.css +++ b/src/interface/web/app/components/allConversations/sidePanel.module.css @@ -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; diff --git a/src/interface/web/app/components/appSidebar/appSidebar.tsx b/src/interface/web/app/components/appSidebar/appSidebar.tsx new file mode 100644 index 00000000..3023f28c --- /dev/null +++ b/src/interface/web/app/components/appSidebar/appSidebar.tsx @@ -0,0 +1,138 @@ +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 ( + + + + + {open ? ( + + + + + + ) : ( + + + + Khoj + + + )} + + + + + + + + {items.map((item) => ( + + + + + {item.title} + + + + ))} + + + + + + + + + + ); +} diff --git a/src/interface/web/app/components/logo/khojLogo.tsx b/src/interface/web/app/components/logo/khojLogo.tsx index 66cb2396..297a5520 100644 --- a/src/interface/web/app/components/logo/khojLogo.tsx +++ b/src/interface/web/app/components/logo/khojLogo.tsx @@ -1,17 +1,17 @@ export function KhojLogoType({ className }: { className?: string }) { - const classes = className ?? "fill-zinc-950 dark:fill-zinc-300"; + const fillClasses = "fill-zinc-950 dark:fill-zinc-300"; return ( - - - - + + + + - + - - - - - - - - + + + + + + + + diff --git a/src/interface/web/app/components/navMenu/navMenu.tsx b/src/interface/web/app/components/navMenu/navMenu.tsx index 6ad767a1..b90fa951 100644 --- a/src/interface/web/app/components/navMenu/navMenu.tsx +++ b/src/interface/web/app/components/navMenu/navMenu.tsx @@ -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 ( -
- {showLoginPrompt && ( - - )} - {isMobileWidth ? ( + + - - {userData ? ( - - - - {userData?.username[0].toUpperCase()} - - - ) : ( - - )} + + + {userData ? ( + + + + + {userData?.username[0].toUpperCase()} + + + {sideBarIsOpen && ( + <> +

{userData?.username}

+ + + )} +
+ ) : ( + + )} +
- +

{userData?.email}

@@ -123,10 +132,10 @@ export default function NavMenu() { )}
- + setDarkMode(!darkMode)} - className="w-full cursor-pointer" + className="w-full hover:cursor-pointer" >
{darkMode ? ( @@ -140,220 +149,51 @@ export default function NavMenu() {
- +
- -

Agents

+ +

Help

+ - -
- -

Automations

-
- -
- {userData && ( - - -
- -

Search

-
- -
- )} - {userData && ( - - -
- -

Settings

-
- -
- )} - <> - - - -
- -

Help

-
- -
- - -
- -

Releases

-
- -
- {userData ? ( - - -
- -

Logout

-
- -
- ) : ( - - - - )} - -
-
- ) : ( - - - - {userData ? ( - - - - {userData?.username[0].toUpperCase()} - - - ) : ( - - )} - - - -
-

{userData?.email}

- - {userData?.khoj_version && ( - - )} -
-
- - setDarkMode(!darkMode)} - className="w-full hover:cursor-pointer" +
- {darkMode ? ( - - ) : ( - - )} -

- {darkMode ? "Light Mode" : "Dark Mode"} -

+ +

Releases

-
- - + + + {userData ? ( + +
- -

Agents

+ +

Logout

-
- - -
- -

Automations

+ + ) : ( + + - - )} - - - - - )} -
+ + + )} + + +
+
); } diff --git a/src/interface/web/app/layout.tsx b/src/interface/web/app/layout.tsx index 3d2cd4f7..8fb49fc1 100644 --- a/src/interface/web/app/layout.tsx +++ b/src/interface/web/app/layout.tsx @@ -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: diff --git a/src/interface/web/app/page.module.css b/src/interface/web/app/page.module.css index d1041377..faca97c7 100644 --- a/src/interface/web/app/page.module.css +++ b/src/interface/web/app/page.module.css @@ -1,6 +1,8 @@ div.main { - height: 100dvh; + height: 100%; color: hsla(var(--foreground)); + margin-left: auto; + margin-right: auto; } div.suggestions { @@ -84,17 +86,7 @@ div.homeGreetings { grid-template-rows: 1fr 2fr; } -div.sidePanel { - position: fixed; - height: 100%; - z-index: 1; -} - @media screen and (max-width: 768px) { - div.inputBox { - margin-bottom: 0px; - } - div.chatBody { grid-template-columns: 0fr 1fr; } @@ -104,13 +96,9 @@ div.sidePanel { grid-template-rows: auto; } - div.sidePanel { - position: relative; - } - div.chatBox { padding: 0; - height: 100%; + height: 100vh; } div.chatLayout { diff --git a/src/interface/web/app/page.tsx b/src/interface/web/app/page.tsx index bf7f30d1..4571fb87 100644 --- a/src/interface/web/app/page.tsx +++ b/src/interface/web/app/page.tsx @@ -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,10 @@ 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 { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"; +import { AppSidebar } from "./components/appSidebar/appSidebar"; +import { Separator } from "@/components/ui/separator"; +import { KhojLogoType } from "./components/logo/khojLogo"; interface ChatBodyDataProps { chatOptionsData: ChatOptions | null; @@ -366,7 +369,7 @@ function ChatBodyData(props: ChatBodyDataProps) { {props.isMobileWidth && ( <>
@@ -458,28 +461,35 @@ export default function Home() { } return ( -
- Khoj AI - Your Second Brain -
- -
-
-
- + + + +
+ + + {isMobileWidth ? ( + + ) : ( +

Ask Anything

+ )} +
+
+ Khoj AI - Your Second Brain +
+
+ +
+
-
-
+ + ); } diff --git a/src/interface/web/app/search/page.tsx b/src/interface/web/app/search/page.tsx index 5d942819..b700e5c1 100644 --- a/src/interface/web/app/search/page.tsx +++ b/src/interface/web/app/search/page.tsx @@ -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 { @@ -31,6 +29,10 @@ import { Button } from "@/components/ui/button"; import Link from "next/link"; import { getIconFromFilename } from "../common/iconUtils"; import { useIsMobileWidth } from "../common/utils"; +import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"; +import { AppSidebar } from "../components/appSidebar/appSidebar"; +import { Separator } from "@/components/ui/separator"; +import { KhojLogoType } from "../components/logo/khojLogo"; interface AdditionalData { file: string; @@ -90,7 +92,7 @@ function Note(props: NoteResultProps) { const fileIcon = getIconFromFilename(fileName || ".txt", "h-4 w-4 inline mr-2"); return ( - + {getNoteTypeIcon(note.additional.source)} @@ -228,107 +230,122 @@ export default function Search() { }, [searchQuery]); return ( -
-
- -
-
-
-
-
- setSearchQuery(e.currentTarget.value)} - onKeyDown={(e) => e.key === "Enter" && search()} - type="search" - placeholder="Search Documents" - /> - -
- {focusSearchResult && ( -
- - {focusNote(focusSearchResult)} -
- )} - {!focusSearchResult && searchResults && searchResults.length > 0 && ( -
- - {searchResults.map((result, index) => { - return ( - - ); - })} - -
- )} - {searchResults == null && ( - - - - - - - Search across your documents - - - - {exampleQuery} - - - )} - {searchResults && searchResults.length === 0 && ( - - - - - - - No documents found - - - -
- To use search, upload your docs to your account. -
- + + +
+ + + {isMobileWidth ? ( + + ) : ( +

Search

+ )} +
+
+
+
+
+
+ setSearchQuery(e.currentTarget.value)} + onKeyDown={(e) => e.key === "Enter" && search()} + type="search" + placeholder="Search Documents" + /> + +
+ {focusSearchResult && ( +
+ + {focusNote(focusSearchResult)} +
+ )} + {!focusSearchResult && + searchResults && + searchResults.length > 0 && ( +
+ + {searchResults.map((result, index) => { + return ( + + ); + })} +
- - - - )} + )} + {searchResults == null && ( + + + + + + + Search across your documents + + + + {exampleQuery} + + + )} + {searchResults && searchResults.length === 0 && ( + + + + + + + No documents found + + + +
+ To use search, upload your docs to your account. +
+ +
+ Learn More +
+ +
+
+ )} +
+
-
-
+ + ); } diff --git a/src/interface/web/app/search/search.module.css b/src/interface/web/app/search/search.module.css index 1a60d3bf..dc507324 100644 --- a/src/interface/web/app/search/search.module.css +++ b/src/interface/web/app/search/search.module.css @@ -2,22 +2,11 @@ div.searchLayout { display: grid; grid-template-columns: 1fr; gap: 1rem; - height: 100vh; -} - -div.sidePanel { - position: fixed; height: 100%; - z-index: 1; } @media screen and (max-width: 768px) { div.searchLayout { gap: 0; } - - div.sidePanel { - position: relative; - height: 100%; - } } diff --git a/src/interface/web/app/settings/page.tsx b/src/interface/web/app/settings/page.tsx index d4785bb1..3178824b 100644 --- a/src/interface/web/app/settings/page.tsx +++ b/src/interface/web/app/settings/page.tsx @@ -62,7 +62,6 @@ import { Waveform, } from "@phosphor-icons/react"; -import SidePanel from "../components/sidePanel/chatHistorySidePanel"; import Loading from "../components/loading/loading"; import IntlTelInput from "intl-tel-input/react"; @@ -77,6 +76,10 @@ import { } from "@/components/ui/alert-dialog"; import { Progress } from "@/components/ui/progress"; import Link from "next/link"; +import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"; +import { AppSidebar } from "../components/appSidebar/appSidebar"; +import { Separator } from "@/components/ui/separator"; +import { KhojLogoType } from "../components/logo/khojLogo"; const ManageFilesModal: React.FC<{ onClose: () => void }> = ({ onClose }) => { const [syncedFiles, setSyncedFiles] = useState([]); @@ -854,121 +857,79 @@ export default function SettingsView() { if (!userConfig) return ; return ( -
- {title} -
- -
-
-
- }> -
-
-
Profile
-
- - - - Name - - -

- What should Khoj refer to you as? -

- setName(e.target.value)} - value={name} - className="w-full border border-gray-300 rounded-lg p-4 py-6" - /> -
- - - -
- - - - Subscription - - -

Current Plan

- {(userConfig.subscription_state === "trial" && ( - <> -

- Futurist (Trial) + + + +

+ + + {isMobileWidth ? ( + + ) : ( +

Settings

+ )} +
+
+ {title} +
+
+ }> +
+
+
Profile
+
+ + + + Name + + +

+ What should Khoj refer to you as?

-

- You are on a{" "} - {userConfig.length_of_free_trial} day trial - of the Khoj Futurist plan. Your trial ends - on {userConfig.subscription_renewal_date}. - Check{" "} - - pricing page - {" "} - to compare plans. -

- - )) || - (userConfig.subscription_state === "subscribed" && ( - <> -

- Futurist -

-

- Subscription renews on{" "} - - { - userConfig.subscription_renewal_date - } - -

- - )) || - (userConfig.subscription_state === - "unsubscribed" && ( - <> -

Futurist

-

- Subscription ends on{" "} - - { - userConfig.subscription_renewal_date - } - -

- - )) || - (userConfig.subscription_state === "expired" && ( - <> -

Humanist

- {(userConfig.subscription_renewal_date && ( -

- Subscription expired on{" "} - - { - userConfig.subscription_renewal_date - } - + setName(e.target.value)} + value={name} + className="w-full border border-gray-300 rounded-lg p-4 py-6" + /> + + + + + + + + + Subscription + + +

Current Plan

+ {(userConfig.subscription_state === "trial" && ( + <> +

+ Futurist (Trial)

- )) || (

- Check{" "} + You are on a{" "} + {userConfig.length_of_free_trial}{" "} + day trial of the Khoj Futurist plan. + Your trial ends on{" "} + { + userConfig.subscription_renewal_date + } + . Check{" "} {" "} to compare plans.

- )} - - ))} -
- - {(userConfig.subscription_state == "subscribed" && ( - - )) || - (userConfig.subscription_state == - "unsubscribed" && ( - - )) || - (userConfig.subscription_enabled_trial_at && ( - - )) || ( - - )} - -
-
-
- {isManageFilesModalOpen && ( - setIsManageFilesModalOpen(false)} - /> - )} -
-
Content
-
- - - - Files - {userConfig.enabled_content_source.computer && ( - - )} - - - Manage your synced files - - - - - - - - - - Github - - - Set Github repositories to index - - - - - - - - - - Notion - {userConfig.enabled_content_source.notion && ( - - )} - - -

- Sync your Notion workspace. -

- {!userConfig.notion_oauth_url && ( - setNotionToken(e.target.value)} - value={notionToken || ""} - placeholder="Enter API Key of your Khoj integration on Notion" - className="w-full border border-gray-300 rounded-lg px-4 py-6" - /> - )} -
- - { - /* Show connect to notion button if notion oauth url setup and user disconnected*/ - userConfig.notion_oauth_url && - !userConfig.enabled_content_source.notion ? ( - - ) : /* Show sync button if user connected to notion and API key unchanged */ - userConfig.enabled_content_source.notion && - notionToken === userConfig.notion_token ? ( - - ) : /* Show set API key button notion oauth url not set setup */ - !userConfig.notion_oauth_url ? ( - - ) : ( - <> - ) - } - - -
-
-
-
-
Models
-
- {userConfig.chat_model_options.length > 0 && ( - - - - Chat - - -

- Pick the chat model to generate text responses -

- -
- - {!userConfig.is_active && ( -

- Subscribe to switch model -

- )} -
-
- )} - {userConfig.paint_model_options.length > 0 && ( - - - - Paint - - -

- Pick the paint model to generate image responses -

- -
- - {!userConfig.is_active && ( -

- Subscribe to switch model -

- )} -
-
- )} - {userConfig.voice_model_options.length > 0 && ( - - - - Voice - - -

- Pick the voice model to generate speech - responses -

- -
- - {!userConfig.is_active && ( -

- Subscribe to switch model -

- )} -
-
- )} -
-
-
-
- Clients -
-
- {!userConfig.anonymous_mode && ( - - - - - API Keys - - - - -

- Access Khoj from the{" "} - - Desktop - - ,{" "} - - Obsidian - - ,{" "} - - Emacs - {" "} - apps and more. -

- - - {apiKeys.map((key) => ( - - - {key.name} - - - - {`${key.token.slice(0, 6)}...${key.token.slice(-4)}`} - -
- { - toast({ - title: `🔑 Copied API Key: ${key.name}`, - description: `Set this API key in the Khoj apps you want to connect to this Khoj account`, - }); - copyAPIKey( - key.token, - ); - }} - /> - { - toast({ - title: `🔑 Deleted API Key: ${key.name}`, - description: `Apps using this API key will no longer connect to this Khoj account`, - }); - deleteAPIKey( - key.token, - ); - }} - /> -
-
-
+ + )) || + (userConfig.subscription_state === + "subscribed" && ( + <> +

+ Futurist +

+

+ Subscription renews on{" "} + + { + userConfig.subscription_renewal_date + } + +

+ + )) || + (userConfig.subscription_state === + "unsubscribed" && ( + <> +

Futurist

+

+ Subscription ends on{" "} + + { + userConfig.subscription_renewal_date + } + +

+ + )) || + (userConfig.subscription_state === + "expired" && ( + <> +

Humanist

+ {(userConfig.subscription_renewal_date && ( +

+ Subscription expired{" "} + on{" "} + + { + userConfig.subscription_renewal_date + } + +

+ )) || ( +

+ Check{" "} + + pricing page + {" "} + to compare plans. +

+ )} + ))} -
-
- - - - )} - - - - Chat on Whatsapp - {(numberValidationState === - PhoneNumberValidationState.Verified && ( - - )) || - (numberValidationState !== - PhoneNumberValidationState.Setup && ( - - ))} - - -

- Connect your number to chat with Khoj on WhatsApp. - Learn more about the integration{" "} - - here - - . -

-
- - {numberValidationState === - PhoneNumberValidationState.VerifyOTP && ( - <> -

{`Enter the OTP sent to your number: ${phoneNumber}`}

- - setNumberValidationState( - PhoneNumberValidationState.VerifyOTP, - ) + + + {(userConfig.subscription_state == + "subscribed" && ( +
-
- - {(numberValidationState === - PhoneNumberValidationState.VerifyOTP && ( - - )) || ( - + )) || + (userConfig.subscription_state == + "unsubscribed" && ( + + )) || + (userConfig.subscription_enabled_trial_at && ( + + )) || ( + + )} + +
+
+
+ {isManageFilesModalOpen && ( + setIsManageFilesModalOpen(false)} + /> + )} +
+
Content
+
+ + + + Files + {userConfig.enabled_content_source.computer && ( + )} - + + + Manage your synced files + + + + + + + + + + Github + + + Set Github repositories to index + + + + + + + + + + Notion + {userConfig.enabled_content_source.notion && ( + + )} + + +

+ Sync your Notion workspace. +

+ {!userConfig.notion_oauth_url && ( + + setNotionToken(e.target.value) + } + value={notionToken || ""} + placeholder="Enter API Key of your Khoj integration on Notion" + className="w-full border border-gray-300 rounded-lg px-4 py-6" + /> + )} +
+ + { + /* Show connect to notion button if notion oauth url setup and user disconnected*/ + userConfig.notion_oauth_url && + !userConfig.enabled_content_source + .notion ? ( + + ) : /* Show sync button if user connected to notion and API key unchanged */ + userConfig.enabled_content_source.notion && + notionToken === + userConfig.notion_token ? ( + + ) : /* Show set API key button notion oauth url not set setup */ + !userConfig.notion_oauth_url ? ( + + ) : ( + <> + ) + } + + +
+
+
+
+
Models
+
+ {userConfig.chat_model_options.length > 0 && ( + + + + Chat + + +

+ Pick the chat model to generate text + responses +

+ +
+ + {!userConfig.is_active && ( +

+ Subscribe to switch model +

+ )} +
+
)} - {numberValidationState === - PhoneNumberValidationState.Verified && ( - + {userConfig.paint_model_options.length > 0 && ( + + + + Paint + + +

+ Pick the paint model to generate image + responses +

+ +
+ + {!userConfig.is_active && ( +

+ Subscribe to switch model +

+ )} +
+
)} - - + {userConfig.voice_model_options.length > 0 && ( + + + + Voice + + +

+ Pick the voice model to generate speech + responses +

+ +
+ + {!userConfig.is_active && ( +

+ Subscribe to switch model +

+ )} +
+
+ )} +
+
+
+
+ Clients +
+
+ {!userConfig.anonymous_mode && ( + + + + + API Keys + + + + +

+ Access Khoj from the{" "} + + Desktop + + ,{" "} + + Obsidian + + ,{" "} + + Emacs + {" "} + apps and more. +

+ + + {apiKeys.map((key) => ( + + + {key.name} + + + + {`${key.token.slice(0, 6)}...${key.token.slice(-4)}`} + +
+ { + toast({ + title: `🔑 Copied API Key: ${key.name}`, + description: `Set this API key in the Khoj apps you want to connect to this Khoj account`, + }); + copyAPIKey( + key.token, + ); + }} + /> + { + toast({ + title: `🔑 Deleted API Key: ${key.name}`, + description: `Apps using this API key will no longer connect to this Khoj account`, + }); + deleteAPIKey( + key.token, + ); + }} + /> +
+
+
+ ))} +
+
+
+ +
+ )} + + + + Chat on Whatsapp + {(numberValidationState === + PhoneNumberValidationState.Verified && ( + + )) || + (numberValidationState !== + PhoneNumberValidationState.Setup && ( + + ))} + + +

+ Connect your number to chat with Khoj on + WhatsApp. Learn more about the integration{" "} + + here + + . +

+
+ + {numberValidationState === + PhoneNumberValidationState.VerifyOTP && ( + <> +

{`Enter the OTP sent to your number: ${phoneNumber}`}

+ + setNumberValidationState( + PhoneNumberValidationState.VerifyOTP, + ) + } + > + + + + + + + + + + + )} +
+
+ + {(numberValidationState === + PhoneNumberValidationState.VerifyOTP && ( + + )) || ( + + )} + {numberValidationState === + PhoneNumberValidationState.Verified && ( + + )} + +
+
+
-
+
- +
-
-
+ + ); } diff --git a/src/interface/web/app/settings/settings.module.css b/src/interface/web/app/settings/settings.module.css index 099a9739..20317590 100644 --- a/src/interface/web/app/settings/settings.module.css +++ b/src/interface/web/app/settings/settings.module.css @@ -16,12 +16,6 @@ div.phoneInput { padding: 0rem; } -div.sidePanel { - position: fixed; - height: 100%; - z-index: 1; -} - div.phoneInput input { width: 100%; padding: 0.5rem; @@ -30,11 +24,6 @@ div.phoneInput input { } @media screen and (max-width: 768px) { - div.sidePanel { - position: relative; - height: 100%; - } - div.contentBody { margin-left: 0; margin-top: 0; diff --git a/src/interface/web/app/share/chat/page.tsx b/src/interface/web/app/share/chat/page.tsx index 06a75303..c64d8d3a 100644 --- a/src/interface/web/app/share/chat/page.tsx +++ b/src/interface/web/app/share/chat/page.tsx @@ -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,10 @@ import { } from "@/app/components/chatInputArea/chatInputArea"; import { StreamMessage } from "@/app/components/chatMessage/chatMessage"; import { AgentData } from "@/app/agents/page"; +import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"; +import { AppSidebar } from "@/app/components/appSidebar/appSidebar"; +import { Separator } from "@/components/ui/separator"; +import { KhojLogoType } from "@/app/components/logo/khojLogo"; interface ChatBodyDataProps { chatOptionsData: ChatOptions | null; @@ -89,7 +92,7 @@ function ChatBodyData(props: ChatBodyDataProps) { />
- {title} -
- -
- -
-
- {!isMobileWidth && title && ( + + + +
+ + + {paramSlug && (
- {title && ( -

- {title} -

+ {isMobileWidth ? ( + + ) : ( + title && ( + <> +

+ {title} +

+ + ) )}
)} - }> - - +
+
+ {title} +
+
+ }> + + +
+
-
-
+ + ); } diff --git a/src/interface/web/app/share/chat/sharedChat.module.css b/src/interface/web/app/share/chat/sharedChat.module.css index c1d15fe0..81c422c7 100644 --- a/src/interface/web/app/share/chat/sharedChat.module.css +++ b/src/interface/web/app/share/chat/sharedChat.module.css @@ -1,6 +1,8 @@ div.main { - height: 100vh; + height: 100%; color: hsla(var(--foreground)); + margin-left: auto; + margin-right: auto; } .suggestions { @@ -94,11 +96,6 @@ div.agentIndicator { div.chatBody { grid-template-columns: 0fr 1fr; } - - div.chatBox { - padding: 0; - height: min-content; - } } @media screen and (max-width: 768px) { @@ -110,11 +107,6 @@ div.agentIndicator { width: 100%; } - div.chatBox { - padding: 0; - height: min-content; - } - div.chatLayout { gap: 0; grid-template-columns: 1fr;