Compare commits

...

26 Commits

Author SHA1 Message Date
Raunak Bhagat
9bd0b01abf Remove sidebar toggling from ChatPage 2025-07-25 11:54:29 -07:00
Raunak Bhagat
5ba90d402e Merge branch 'main' into sidebar-cleanup 2025-07-25 11:48:01 -07:00
Raunak Bhagat
718d911790 Edit z-index 2025-07-25 11:46:58 -07:00
Raunak Bhagat
dc35205824 Finish separation 2025-07-25 10:46:14 -07:00
Raunak Bhagat
0951c9a732 Update structure 2025-07-25 10:16:09 -07:00
Raunak Bhagat
a2ca9019d1 More fixes to sidebar 2025-07-25 09:28:36 -07:00
Raunak Bhagat
4a1fc769ea Add SidebarProvider 2025-07-24 13:34:30 -07:00
Raunak Bhagat
b3a5dd4a8b Rename props 2025-07-24 08:32:48 -07:00
Raunak Bhagat
cd92e49c18 Address comments 2025-07-24 08:29:47 -07:00
Raunak Bhagat
915a4c48b2 Merge remote-tracking branch 'origin/sidebar-cleanup' into sidebar-cleanup 2025-07-24 08:20:42 -07:00
Raunak Bhagat
2ddf3e2cbe Remove Header from ChatPage 2025-07-24 08:20:07 -07:00
Raunak Bhagat
a24050a776 Remove redundant router push 2025-07-24 07:56:01 -07:00
Raunak Bhagat
5121943a9f Update web/src/components/sidebar/Sidebar.tsx
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2025-07-24 07:52:37 -07:00
Raunak Bhagat
915764250c Revert old files 2025-07-24 07:50:52 -07:00
Raunak Bhagat
dd9789296b Remove import 2025-07-24 07:38:48 -07:00
Raunak Bhagat
672ef5333f Merge branch 'main' into search 2025-07-24 07:29:43 -07:00
Raunak Bhagat
e6a53bfcaf Update name of sidebar pinned and sidebar visible 2025-07-24 07:20:06 -07:00
Raunak Bhagat
a38f4acebe Perform more separation of state 2025-07-23 18:18:06 -07:00
Raunak Bhagat
063b88f453 Fix bug in how the cookies were set 2025-07-23 13:09:15 -07:00
Raunak Bhagat
7ca53ccca5 Remove AppModeProvider 2025-07-23 12:56:36 -07:00
Raunak Bhagat
9d86e6a345 Change name of sessionSidebar to sidebar 2025-07-23 12:51:56 -07:00
Raunak Bhagat
95901b10a0 Merge branch 'main' into update-sidebar 2025-07-23 12:44:19 -07:00
Raunak Bhagat
8ea8558a3f Move sessionSidebar to be inside of components instead of app/chat 2025-07-23 12:40:17 -07:00
Raunak Bhagat
0341d45aa7 Remove unused component file 2025-07-23 11:53:46 -07:00
Raunak Bhagat
8032f91b81 Add new AppProvider 2025-07-23 11:35:42 -07:00
Raunak Bhagat
b8ac698a66 Use props instead of inline type def 2025-07-22 16:39:17 -07:00
23 changed files with 795 additions and 835 deletions

View File

@@ -1,152 +0,0 @@
"use client";
import Cookies from "js-cookie";
import { SIDEBAR_TOGGLED_COOKIE_NAME } from "@/components/resizable/constants";
import { ReactNode, useCallback, useContext, useRef, useState } from "react";
import { useSidebarVisibility } from "@/components/chat/hooks";
import FunctionalHeader from "@/components/chat/Header";
import { useRouter } from "next/navigation";
import FixedLogo from "../../components/logo/FixedLogo";
import { SettingsContext } from "@/components/settings/SettingsProvider";
import { useChatContext } from "@/components/context/ChatContext";
import { HistorySidebar } from "@/components/sidebar/HistorySidebar";
import { useAssistants } from "@/components/context/AssistantsContext";
import AssistantModal from "./mine/AssistantModal";
import { useSidebarShortcut } from "@/lib/browserUtilities";
import { UserSettingsModal } from "../chat/modal/UserSettingsModal";
import { usePopup } from "@/components/admin/connectors/Popup";
import { useUser } from "@/components/user/UserProvider";
interface SidebarWrapperProps<T extends object> {
size?: "sm" | "lg";
children: ReactNode;
}
export default function SidebarWrapper<T extends object>({
size = "sm",
children,
}: SidebarWrapperProps<T>) {
const { sidebarInitiallyVisible: initiallyToggled } = useChatContext();
const [sidebarVisible, setSidebarVisible] = useState(initiallyToggled);
const [showDocSidebar, setShowDocSidebar] = useState(false); // State to track if sidebar is open
// Used to maintain a "time out" for history sidebar so our existing refs can have time to process change
const [untoggled, setUntoggled] = useState(false);
const toggleSidebar = useCallback(() => {
Cookies.set(
SIDEBAR_TOGGLED_COOKIE_NAME,
String(!sidebarVisible).toLocaleLowerCase(),
{ path: "/" }
);
setSidebarVisible((sidebarVisible) => !sidebarVisible);
}, [sidebarVisible]);
const sidebarElementRef = useRef<HTMLDivElement>(null);
const { folders, openedFolders, chatSessions } = useChatContext();
const { assistants } = useAssistants();
const explicitlyUntoggle = () => {
setShowDocSidebar(false);
setUntoggled(true);
setTimeout(() => {
setUntoggled(false);
}, 200);
};
const { popup, setPopup } = usePopup();
const settings = useContext(SettingsContext);
useSidebarVisibility({
sidebarVisible,
sidebarElementRef,
showDocSidebar,
setShowDocSidebar,
mobile: settings?.isMobile,
});
const { user } = useUser();
const [showAssistantsModal, setShowAssistantsModal] = useState(false);
const router = useRouter();
const [userSettingsToggled, setUserSettingsToggled] = useState(false);
const { llmProviders } = useChatContext();
useSidebarShortcut(router, toggleSidebar);
return (
<div className="flex relative overflow-x-hidden overscroll-contain flex-col w-full h-screen">
{popup}
{showAssistantsModal && (
<AssistantModal hideModal={() => setShowAssistantsModal(false)} />
)}
<div
ref={sidebarElementRef}
className={`
flex-none
fixed
left-0
z-30
bg-background-100
h-screen
transition-all
bg-opacity-80
duration-300
ease-in-out
${
!untoggled && (showDocSidebar || sidebarVisible)
? "opacity-100 w-[250px] translate-x-0"
: "opacity-0 w-[200px] pointer-events-none -translate-x-10"
}`}
>
<div className="w-full relative">
{" "}
<HistorySidebar
setShowAssistantsModal={setShowAssistantsModal}
page={"chat"}
explicitlyUntoggle={explicitlyUntoggle}
ref={sidebarElementRef}
toggleSidebar={toggleSidebar}
toggled={sidebarVisible}
existingChats={chatSessions}
currentChatSession={null}
folders={folders}
/>
</div>
</div>
{userSettingsToggled && (
<UserSettingsModal
setPopup={setPopup}
llmProviders={llmProviders}
onClose={() => setUserSettingsToggled(false)}
defaultModel={user?.preferences?.default_model!}
/>
)}
<div className="absolute px-2 left-0 w-full top-0">
<FunctionalHeader
removeHeight={true}
toggleUserSettings={() => setUserSettingsToggled(true)}
sidebarToggled={sidebarVisible}
toggleSidebar={toggleSidebar}
page="chat"
/>
<div className="w-full flex">
<div
style={{ transition: "width 0.30s ease-out" }}
className={`flex-none
overflow-y-hidden
bg-background-100
h-full
transition-all
bg-opacity-80
duration-300
ease-in-out
${sidebarVisible ? "w-[250px]" : "w-[0px]"}`}
/>
<div className={` w-full mx-auto`}>{children}</div>
</div>
</div>
<FixedLogo backgroundToggled={sidebarVisible || showDocSidebar} />
</div>
);
}

View File

@@ -1,12 +1,13 @@
"use client";
import React, { useMemo, useState } from "react";
import React, { useMemo, useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import AssistantCard from "./AssistantCard";
import { useAssistants } from "@/components/context/AssistantsContext";
import { useUser } from "@/components/user/UserProvider";
import { FilterIcon, XIcon } from "lucide-react";
import { checkUserOwnsAssistant } from "@/lib/assistants/checkOwnership";
import ReactDOM from "react-dom";
export const AssistantBadgeSelector = ({
text,
@@ -63,13 +64,21 @@ interface AssistantModalProps {
hideModal: () => void;
}
export function AssistantModal({ hideModal }: AssistantModalProps) {
export default function AssistantModal({ hideModal }: AssistantModalProps) {
const { assistants, pinnedAssistants } = useAssistants();
const { assistantFilters, toggleAssistantFilter } = useAssistantFilter();
const router = useRouter();
const { user } = useUser();
const [searchQuery, setSearchQuery] = useState("");
const [isSearchFocused, setIsSearchFocused] = useState(false);
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
setIsMounted(true);
return () => {
setIsMounted(false);
};
}, []);
const memoizedCurrentlyVisibleAssistants = useMemo(() => {
return assistants.filter((assistant) => {
@@ -110,7 +119,7 @@ export function AssistantModal({ hideModal }: AssistantModalProps) {
(assistant) => !assistant.is_default_persona
);
return (
const modalContent = (
<div
onClick={hideModal}
className="fixed inset-0 bg-neutral-950/80 bg-opacity-50 flex items-center justify-center z-50"
@@ -286,5 +295,6 @@ export function AssistantModal({ hideModal }: AssistantModalProps) {
</div>
</div>
);
return isMounted ? ReactDOM.createPortal(modalContent, document.body) : null;
}
export default AssistantModal;

View File

@@ -28,7 +28,6 @@ import {
import Prism from "prismjs";
import Cookies from "js-cookie";
import { HistorySidebar } from "@/components/sidebar/HistorySidebar";
import { MinimalPersonaSnapshot } from "../admin/assistants/interfaces";
import { HealthCheckBanner } from "@/components/health/healthcheck";
import {
@@ -96,20 +95,16 @@ import {
import { ChatInputBar } from "./input/ChatInputBar";
import { useChatContext } from "@/components/context/ChatContext";
import { ChatPopup } from "./ChatPopup";
import FunctionalHeader from "@/components/chat/Header";
import { FederatedOAuthModal } from "@/components/chat/FederatedOAuthModal";
import { useFederatedOAuthStatus } from "@/lib/hooks/useFederatedOAuthStatus";
import { useSidebarVisibility } from "@/components/chat/hooks";
import {
PRO_SEARCH_TOGGLED_COOKIE_NAME,
SIDEBAR_TOGGLED_COOKIE_NAME,
} from "@/components/resizable/constants";
import FixedLogo from "@/components/logo/FixedLogo";
import ExceptionTraceModal from "@/components/modals/ExceptionTraceModal";
import { SEARCH_TOOL_ID, SEARCH_TOOL_NAME } from "./tools/constants";
import { useUser } from "@/components/user/UserProvider";
import { ApiKeyModal } from "@/components/llm/ApiKeyModal";
import BlurBackground from "../../components/chat/BlurBackground";
import { NoAssistantModal } from "@/components/modals/NoAssistantModal";
import { useAssistants } from "@/components/context/AssistantsContext";
import TextView from "@/components/chat/TextView";
@@ -124,7 +119,6 @@ import { getSourceMetadata } from "@/lib/sources";
import { UserSettingsModal } from "./modal/UserSettingsModal";
import { AgenticMessage } from "./message/AgenticMessage";
import AssistantModal from "../assistants/mine/AssistantModal";
import { useSidebarShortcut } from "@/lib/browserUtilities";
import { FilePickerModal } from "./my-documents/components/FilePicker";
import { SourceMetadata } from "@/lib/search/interfaces";
@@ -139,7 +133,6 @@ import { ErrorBanner } from "./message/Resubmit";
import MinimalMarkdown from "@/components/chat/MinimalMarkdown";
import { WelcomeModal } from "@/components/initialSetup/welcome/WelcomeModal";
import { useFederatedConnectors } from "@/lib/hooks";
import { Button } from "@/components/ui/button";
const TEMP_USER_MESSAGE_ID = -1;
const TEMP_ASSISTANT_MESSAGE_ID = -2;
@@ -151,9 +144,7 @@ export enum UploadIntent {
}
type ChatPageProps = {
toggle: (toggled?: boolean) => void;
documentSidebarInitialWidth?: number;
sidebarVisible: boolean;
firstMessage?: string;
initialFolders?: any;
initialFiles?: any;
@@ -174,9 +165,7 @@ type ChatPageProps = {
// ---
export function ChatPage({
toggle,
documentSidebarInitialWidth,
sidebarVisible,
firstMessage,
initialFolders,
initialFiles,
@@ -374,23 +363,21 @@ export function ChatPage({
const slackChatId = searchParams?.get("slackChatId");
const existingChatIdRaw = searchParams?.get("chatId");
const [showHistorySidebar, setShowHistorySidebar] = useState(false);
const existingChatSessionId = existingChatIdRaw ? existingChatIdRaw : null;
const selectedChatSession = chatSessions.find(
(chatSession) => chatSession.id === existingChatSessionId
);
useEffect(() => {
if (user?.is_anonymous_user) {
Cookies.set(
SIDEBAR_TOGGLED_COOKIE_NAME,
String(!sidebarVisible).toLocaleLowerCase()
);
toggle(false);
}
}, [user]);
// useEffect(() => {
// if (user?.is_anonymous_user) {
// Cookies.set(
// SIDEBAR_TOGGLED_COOKIE_NAME,
// String(!sidebarVisible).toLocaleLowerCase()
// );
// toggle(false);
// }
// }, [user]);
const processSearchParamsAndSubmitMessage = (searchParamsString: string) => {
const newSearchParams = new URLSearchParams(searchParamsString);
@@ -2175,44 +2162,20 @@ export function ChatPage({
};
// Used to maintain a "time out" for history sidebar so our existing refs can have time to process change
const [untoggled, setUntoggled] = useState(false);
const [loadingError, setLoadingError] = useState<string | null>(null);
const explicitlyUntoggle = () => {
setShowHistorySidebar(false);
setUntoggled(true);
setTimeout(() => {
setUntoggled(false);
}, 200);
};
const toggleSidebar = () => {
if (user?.is_anonymous_user) {
return;
}
Cookies.set(
SIDEBAR_TOGGLED_COOKIE_NAME,
String(!sidebarVisible).toLocaleLowerCase()
);
toggle();
};
const removeToggle = () => {
setShowHistorySidebar(false);
toggle(false);
};
// const toggleSidebar = () => {
// if (user?.is_anonymous_user) {
// return;
// }
// Cookies.set(
// SIDEBAR_TOGGLED_COOKIE_NAME,
// String(!sidebarVisible).toLocaleLowerCase()
// );
// toggle();
// };
const waitForScrollRef = useRef(false);
const sidebarElementRef = useRef<HTMLDivElement>(null);
useSidebarVisibility({
sidebarVisible,
sidebarElementRef,
showDocSidebar: showHistorySidebar,
setShowDocSidebar: setShowHistorySidebar,
setToggled: removeToggle,
mobile: settings?.isMobile,
isAnonymousUser: user?.is_anonymous_user,
});
// Virtualization + Scrolling related effects and functions
const scrollInitialized = useRef(false);
@@ -2379,8 +2342,6 @@ export function ChatPage({
calculateTokensAndUpdateSearchMode();
}, [selectedFiles, selectedFolders, llmManager.currentLlm]);
useSidebarShortcut(router, toggleSidebar);
const [sharedChatSession, setSharedChatSession] =
useState<ChatSession | null>();
@@ -2625,70 +2586,8 @@ export function ChatPage({
<AssistantModal hideModal={() => setShowAssistantsModal(false)} />
)}
<div className="fixed inset-0 flex flex-col text-text-dark">
<div className="flex flex-col text-text-dark">
<div className="h-[100dvh] overflow-y-hidden">
<div className="w-full">
<div
ref={sidebarElementRef}
className={`
flex-none
fixed
left-0
z-40
bg-neutral-200
h-screen
transition-all
bg-opacity-80
duration-300
ease-in-out
${
!untoggled && (showHistorySidebar || sidebarVisible)
? "opacity-100 w-[250px] translate-x-0"
: "opacity-0 w-[250px] pointer-events-none -translate-x-10"
}`}
>
<div className="w-full relative">
<HistorySidebar
toggleChatSessionSearchModal={() =>
setIsChatSearchModalOpen((open) => !open)
}
liveAssistant={liveAssistant}
setShowAssistantsModal={setShowAssistantsModal}
explicitlyUntoggle={explicitlyUntoggle}
reset={reset}
page="chat"
ref={innerSidebarElementRef}
toggleSidebar={toggleSidebar}
toggled={sidebarVisible}
existingChats={chatSessions}
currentChatSession={selectedChatSession}
folders={folders}
removeToggle={removeToggle}
showShareModal={showShareModal}
/>
</div>
<div
className={`
flex-none
fixed
left-0
z-40
bg-background-100
h-screen
transition-all
bg-opacity-80
duration-300
ease-in-out
${
documentSidebarVisible &&
!settings?.isMobile &&
"opacity-100 w-[350px]"
}`}
></div>
</div>
</div>
<div
style={{ transition: "width 0.30s ease-out" }}
className={`
@@ -2738,12 +2637,6 @@ export function ChatPage({
isOpen={documentSidebarVisible && !settings?.isMobile}
/>
</div>
<BlurBackground
visible={!untoggled && (showHistorySidebar || sidebarVisible)}
onClick={() => toggleSidebar()}
/>
<div
ref={masterFlexboxRef}
className="flex h-full w-full overflow-x-hidden"
@@ -2752,26 +2645,6 @@ export function ChatPage({
id="scrollableContainer"
className="flex h-full relative px-2 flex-col w-full"
>
{liveAssistant && (
<FunctionalHeader
toggleUserSettings={() => setUserSettingsToggled(true)}
sidebarToggled={sidebarVisible}
reset={() => setMessage("")}
page="chat"
setSharingModalVisible={
chatSessionIdRef.current !== null
? setSharingModalVisible
: undefined
}
documentSidebarVisible={
documentSidebarVisible && !settings?.isMobile
}
toggleSidebar={toggleSidebar}
currentChatSession={selectedChatSession}
hideUserDropdown={user?.is_anonymous_user}
/>
)}
{documentSidebarInitialWidth !== undefined && isReady ? (
<Dropzone
key={currentSessionId()}
@@ -2782,51 +2655,15 @@ export function ChatPage({
>
{({ getRootProps }) => (
<div className="flex h-full w-full">
{!settings?.isMobile && (
<div
style={{ transition: "width 0.30s ease-out" }}
className={`
flex-none
overflow-y-hidden
bg-transparent
transition-all
bg-opacity-80
duration-300
ease-in-out
h-full
${sidebarVisible ? "w-[200px]" : "w-[0px]"}
`}
></div>
)}
<div
className={`h-full w-full relative flex-auto transition-margin duration-300 overflow-x-auto mobile:pb-12 desktop:pb-[100px]`}
{...getRootProps()}
>
<div
onScroll={handleScroll}
className={`w-full h-[calc(100vh-160px)] flex flex-col default-scrollbar overflow-y-auto overflow-x-hidden relative`}
className={`w-full h-[calc(100vh-130px)] flex flex-col default-scrollbar overflow-y-auto overflow-x-hidden relative`}
ref={scrollableDivRef}
>
{liveAssistant && (
<div className="z-20 fixed top-0 pointer-events-none left-0 w-full flex justify-center overflow-visible">
{!settings?.isMobile && (
<div
style={{ transition: "width 0.30s ease-out" }}
className={`
flex-none
overflow-y-hidden
transition-all
pointer-events-none
duration-300
ease-in-out
h-full
${sidebarVisible ? "w-[200px]" : "w-[0px]"}
`}
/>
)}
</div>
)}
{/* ChatBanner is a custom banner that displays a admin-specified message at
the top of the chat page. Oly used in the EE version of the app. */}
{messageHistory.length === 0 &&
@@ -3544,15 +3381,6 @@ export function ChatPage({
</Dropzone>
) : (
<div className="mx-auto h-full flex">
<div
style={{ transition: "width 0.30s ease-out" }}
className={`flex-none bg-transparent transition-all bg-opacity-80 duration-300 ease-in-out h-full
${
sidebarVisible && !settings?.isMobile
? "w-[250px] "
: "w-[0px]"
}`}
/>
<div className="my-auto">
<OnyxInitializingLoader />
</div>
@@ -3560,7 +3388,6 @@ export function ChatPage({
)}
</div>
</div>
<FixedLogo backgroundToggled={sidebarVisible || showHistorySidebar} />
</div>
</div>
</>

View File

@@ -1,29 +0,0 @@
"use client";
import { useChatContext } from "@/components/context/ChatContext";
import { ChatPage } from "./ChatPage";
import FunctionalWrapper from "../../components/chat/FunctionalWrapper";
export default function WrappedChat({
firstMessage,
defaultSidebarOff,
}: {
firstMessage?: string;
// This is required for the chrome extension side panel
// we don't want to show the sidebar by default when the user opens the side panel
defaultSidebarOff?: boolean;
}) {
const { sidebarInitiallyVisible } = useChatContext();
return (
<FunctionalWrapper
initiallyVisible={sidebarInitiallyVisible && !defaultSidebarOff}
content={(sidebarVisible, toggle) => (
<ChatPage
toggle={toggle}
sidebarVisible={sidebarVisible}
firstMessage={firstMessage}
/>
)}
/>
);
}

View File

@@ -2,12 +2,14 @@ import { redirect } from "next/navigation";
import { unstable_noStore as noStore } from "next/cache";
import { fetchChatData } from "@/lib/chat/fetchChatData";
import { ChatProvider } from "@/components/context/ChatContext";
import { SidebarProvider } from "@/components/context/SidebarProvider";
import MainPageFrame from "@/components/frames/MainPageFrame";
export default async function Layout({
children,
}: {
type LayoutProps = {
children: React.ReactNode;
}) {
};
export default async function Layout({ children }: LayoutProps) {
noStore();
// Ensure searchParams is an object, even if it's empty
@@ -40,28 +42,28 @@ export default async function Layout({
} = data;
return (
<>
<ChatProvider
value={{
proSearchToggled,
inputPrompts,
chatSessions,
sidebarInitiallyVisible,
availableSources,
ccPairs,
documentSets,
tags,
availableDocumentSets: documentSets,
availableTags: tags,
llmProviders,
folders,
openedFolders,
shouldShowWelcomeModal,
defaultAssistantId,
}}
>
{children}
</ChatProvider>
</>
<ChatProvider
value={{
proSearchToggled,
inputPrompts,
chatSessions,
sidebarInitiallyVisible,
availableSources,
ccPairs,
documentSets,
tags,
availableDocumentSets: documentSets,
availableTags: tags,
llmProviders,
folders,
openedFolders,
shouldShowWelcomeModal,
defaultAssistantId,
}}
>
<SidebarProvider>
<MainPageFrame>{children}</MainPageFrame>
</SidebarProvider>
</ChatProvider>
);
}

View File

@@ -34,6 +34,17 @@ import { getSourceMetadata } from "@/lib/sources";
type SettingsSection = "settings" | "password" | "connectors";
type UserSettingsModalProps = {
setPopup: (popupSpec: PopupSpec | null) => void;
llmProviders: LLMProviderDescriptor[];
setCurrentLlm?: (newLlm: LlmDescriptor) => void;
onClose: () => void;
defaultModel: string | null;
ccPairs?: CCPairBasicInfo[];
federatedConnectors?: FederatedConnectorOAuthStatus[];
refetchFederatedConnectors?: () => void;
};
export function UserSettingsModal({
setPopup,
llmProviders,
@@ -43,16 +54,7 @@ export function UserSettingsModal({
ccPairs,
federatedConnectors,
refetchFederatedConnectors,
}: {
setPopup: (popupSpec: PopupSpec | null) => void;
llmProviders: LLMProviderDescriptor[];
setCurrentLlm?: (newLlm: LlmDescriptor) => void;
onClose: () => void;
defaultModel: string | null;
ccPairs?: CCPairBasicInfo[];
federatedConnectors?: FederatedConnectorOAuthStatus[];
refetchFederatedConnectors?: () => void;
}) {
}: UserSettingsModalProps) {
const {
refreshUser,
user,

View File

@@ -1,21 +0,0 @@
"use client";
import MyDocuments from "./MyDocuments";
import { BackButton } from "@/components/BackButton";
import { useRouter } from "next/navigation";
export default function WrappedUserDocuments() {
const router = useRouter();
return (
<div className="mx-auto w-full">
<div className="absolute top-4 left-4">
<BackButton
behaviorOverride={() => {
router.push("/chat");
}}
/>
</div>
<MyDocuments />
</div>
);
}

View File

@@ -1,12 +1,12 @@
import WrappedDocuments from "./WrappedDocuments";
import { DocumentsProvider } from "./DocumentsContext";
import { DocumentsProvider } from "@/app/chat/my-documents/DocumentsContext";
import MyDocuments from "@/app/chat/my-documents/MyDocuments";
export default async function GalleryPage(props: {
searchParams: Promise<{ [key: string]: string }>;
}) {
return (
<DocumentsProvider>
<WrappedDocuments />
<MyDocuments />
</DocumentsProvider>
);
}

View File

@@ -1,6 +1,6 @@
import { DocumentsProvider } from "./my-documents/DocumentsContext";
import { SEARCH_PARAMS } from "@/lib/extension/constants";
import WrappedChat from "./WrappedChat";
import { ChatPage } from "./ChatPage";
export default async function Page(props: {
searchParams: Promise<{ [key: string]: string }>;
@@ -12,10 +12,7 @@ export default async function Page(props: {
return (
<DocumentsProvider>
<WrappedChat
firstMessage={firstMessage}
defaultSidebarOff={defaultSidebarOff}
/>
<ChatPage firstMessage={firstMessage} />
</DocumentsProvider>
);
}

View File

@@ -1,16 +0,0 @@
"use client";
import SidebarWrapper from "@/app/assistants/SidebarWrapper";
import { AssistantStats } from "./AssistantStats";
export default function WrappedAssistantsStats({
assistantId,
}: {
assistantId: number;
}) {
return (
<SidebarWrapper>
<AssistantStats assistantId={assistantId} />
</SidebarWrapper>
);
}

View File

@@ -61,12 +61,7 @@ export default function FunctionalHeader({
const handleNewChat = () => {
reset();
const newChatUrl =
`/${page}` +
(currentChatSession
? `?assistantId=${currentChatSession.persona_id}`
: "");
router.push(newChatUrl);
router.push("/chat");
};
return (
<div
@@ -76,11 +71,9 @@ export default function FunctionalHeader({
>
<div className="items-end flex mt-2 text-text-700 relative flex w-full">
<LogoWithText
assistantId={currentChatSession?.persona_id}
page={page}
toggleSidebar={toggleSidebar}
toggled={false}
handleNewChat={handleNewChat}
/>
<div className="mt-1 items-center flex w-full h-8">
<div
@@ -138,7 +131,6 @@ export default function FunctionalHeader({
page={page}
toggled={sidebarToggled}
toggleSidebar={toggleSidebar}
handleNewChat={handleNewChat}
/>
</div>
@@ -161,12 +153,13 @@ export default function FunctionalHeader({
</div>
<Link
className="desktop:hidden ml-2 my-auto"
href={
`/${page}` +
(currentChatSession
? `?assistantId=${currentChatSession.persona_id}`
: "")
}
href="/chat"
onClick={(e) => {
if (e.metaKey || e.ctrlKey) {
return;
}
handleNewChat();
}}
>
<div className=" cursor-pointer ml-2 mr-4 flex-none text-text-700 hover:text-text-600 transition-colors duration-300">
<NewChatIcon size={24} />

View File

@@ -1,94 +0,0 @@
import { Dispatch, SetStateAction, useEffect, useRef } from "react";
interface UseSidebarVisibilityProps {
sidebarVisible: boolean;
sidebarElementRef: React.RefObject<HTMLElement>;
showDocSidebar: boolean;
setShowDocSidebar: Dispatch<SetStateAction<boolean>>;
mobile?: boolean;
setToggled?: () => void;
isAnonymousUser?: boolean;
}
export const useSidebarVisibility = ({
sidebarVisible,
sidebarElementRef,
setShowDocSidebar,
setToggled,
showDocSidebar,
mobile,
isAnonymousUser,
}: UseSidebarVisibilityProps) => {
const xPosition = useRef(0);
useEffect(() => {
const handleEvent = (event: MouseEvent) => {
if (isAnonymousUser) {
return;
}
const currentXPosition = event.clientX;
xPosition.current = currentXPosition;
const sidebarRect = sidebarElementRef.current?.getBoundingClientRect();
if (sidebarRect && sidebarElementRef.current) {
const isWithinSidebar =
currentXPosition >= sidebarRect.left &&
currentXPosition <= sidebarRect.right &&
event.clientY >= sidebarRect.top &&
event.clientY <= sidebarRect.bottom;
const sidebarStyle = window.getComputedStyle(sidebarElementRef.current);
const isVisible = sidebarStyle.opacity !== "0";
if (isWithinSidebar && isVisible) {
if (!mobile) {
setShowDocSidebar(true);
}
}
if (mobile && !isWithinSidebar && setToggled) {
setToggled();
return;
}
if (
currentXPosition > 100 &&
showDocSidebar &&
!isWithinSidebar &&
!sidebarVisible
) {
setTimeout(() => {
setShowDocSidebar((showDocSidebar) => {
// Account for possition as point in time of
return !(xPosition.current > sidebarRect.right);
});
}, 200);
} else if (currentXPosition < 100 && !showDocSidebar) {
if (!mobile) {
setShowDocSidebar(true);
}
}
}
};
const handleMouseLeave = () => {
if (!mobile) {
setShowDocSidebar(false);
}
};
if (!mobile) {
document.addEventListener("mousemove", handleEvent);
document.addEventListener("mouseleave", handleMouseLeave);
}
return () => {
if (!mobile) {
document.removeEventListener("mousemove", handleEvent);
document.removeEventListener("mouseleave", handleMouseLeave);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [showDocSidebar, sidebarVisible, sidebarElementRef, mobile]);
return { showDocSidebar };
};

View File

@@ -0,0 +1,176 @@
"use client";
import React, {
createContext,
useState,
useContext,
useCallback,
useRef,
useEffect,
ReactNode,
} from "react";
// private hook
interface UseSidebarVisibilityProps {
sidebarElementRef: React.RefObject<HTMLElement>;
// pinned
sidebarPinned: boolean;
setSidebarPinned: (pinned: boolean) => void;
// pinned
sidebarAreaMouseHover: boolean;
setSidebarAreaMouseHover: (hover: boolean) => void;
mobile?: boolean;
isAnonymousUser?: boolean;
}
function useSidebarVisibility({
sidebarElementRef,
sidebarPinned,
sidebarAreaMouseHover,
setSidebarAreaMouseHover,
mobile,
isAnonymousUser,
}: UseSidebarVisibilityProps) {
const xPosition = useRef(0);
useEffect(() => {
function handleEvent(event: MouseEvent) {
if (isAnonymousUser) {
return;
}
const currentXPosition = event.clientX;
xPosition.current = currentXPosition;
const sidebarRect = sidebarElementRef.current?.getBoundingClientRect();
if (sidebarRect && sidebarElementRef.current) {
const isWithinSidebar =
currentXPosition >= sidebarRect.left &&
currentXPosition <= sidebarRect.right &&
event.clientY >= sidebarRect.top &&
event.clientY <= sidebarRect.bottom;
const sidebarStyle = window.getComputedStyle(sidebarElementRef.current);
const isVisible = sidebarStyle.opacity !== "0";
if (isWithinSidebar && isVisible) {
if (!mobile) {
setSidebarAreaMouseHover(true);
}
}
if (
currentXPosition > 100 &&
sidebarAreaMouseHover &&
!isWithinSidebar &&
!sidebarPinned
) {
setTimeout(() => {
setSidebarAreaMouseHover(!(xPosition.current > sidebarRect.right));
}, 200);
} else if (currentXPosition < 100 && !sidebarAreaMouseHover) {
if (!mobile) {
setSidebarAreaMouseHover(true);
}
}
}
}
function handleMouseLeave() {
if (!mobile) {
setSidebarAreaMouseHover(false);
}
}
if (!mobile) {
document.addEventListener("mousemove", handleEvent);
document.addEventListener("mouseleave", handleMouseLeave);
}
return function () {
if (!mobile) {
document.removeEventListener("mousemove", handleEvent);
document.removeEventListener("mouseleave", handleMouseLeave);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [sidebarAreaMouseHover, sidebarPinned, sidebarElementRef, mobile]);
}
// context
interface SidebarContextProps {
sidebarElementRef: React.RefObject<HTMLDivElement>;
// pinning
sidebarPinned: boolean;
toggleSidebarPinned: () => void;
// visibility
sidebarVisible: boolean;
sidebarAreaMouseHover: boolean;
setSidebarAreaMouseHover: (show: boolean) => void;
}
const SidebarContext = createContext<SidebarContextProps | undefined>(
undefined
);
// public provider
interface SidebarProviderProps {
children: ReactNode;
mobile?: boolean;
isAnonymousUser?: boolean;
}
export function SidebarProvider({
children,
mobile = false,
isAnonymousUser = false,
}: SidebarProviderProps) {
const [sidebarPinned, setSidebarPinned] = useState(true);
const [sidebarAreaMouseHover, setSidebarAreaMouseHover] = useState(false);
const sidebarElementRef = useRef<HTMLDivElement>(null);
useSidebarVisibility({
sidebarElementRef,
sidebarPinned,
setSidebarPinned,
sidebarAreaMouseHover,
setSidebarAreaMouseHover,
mobile,
isAnonymousUser,
});
const sidebarVisible = sidebarPinned || sidebarAreaMouseHover;
return (
<SidebarContext.Provider
value={{
sidebarVisible,
sidebarElementRef,
sidebarPinned,
toggleSidebarPinned: () => setSidebarPinned((prev) => !prev),
sidebarAreaMouseHover,
setSidebarAreaMouseHover,
}}
>
{children}
</SidebarContext.Provider>
);
}
// public hook
export function useSidebar(): SidebarContextProps {
const context = useContext(SidebarContext);
if (context === undefined) {
throw new Error("useSidebar must be used within a SidebarProvider");
}
return context;
}

View File

@@ -0,0 +1,172 @@
"use client";
import React, {
createContext,
useState,
useContext,
useCallback,
useRef,
useEffect,
ReactNode,
} from "react";
// private hook
interface UseSidebarVisibilityProps {
sidebarElementRef: React.RefObject<HTMLElement>;
sidebarPinned: boolean;
showSidebar: boolean;
setShowSidebar: (show: boolean) => void;
mobile?: boolean;
isAnonymousUser?: boolean;
}
function useSidebarVisibility({
sidebarElementRef,
sidebarPinned,
showSidebar,
setShowSidebar,
mobile,
isAnonymousUser,
}: UseSidebarVisibilityProps) {
const xPosition = useRef(0);
useEffect(() => {
function handleEvent(event: MouseEvent) {
if (isAnonymousUser) {
return;
}
const currentXPosition = event.clientX;
xPosition.current = currentXPosition;
const sidebarRect = sidebarElementRef.current?.getBoundingClientRect();
if (sidebarRect && sidebarElementRef.current) {
const isWithinSidebar =
currentXPosition >= sidebarRect.left &&
currentXPosition <= sidebarRect.right &&
event.clientY >= sidebarRect.top &&
event.clientY <= sidebarRect.bottom;
const sidebarStyle = window.getComputedStyle(sidebarElementRef.current);
const isVisible = sidebarStyle.opacity !== "0";
if (isWithinSidebar && isVisible) {
if (!mobile) {
setShowSidebar(true);
}
}
if (
currentXPosition > 100 &&
showSidebar &&
!isWithinSidebar &&
!sidebarPinned
) {
setTimeout(() => {
setShowSidebar(!(xPosition.current > sidebarRect.right));
}, 200);
} else if (currentXPosition < 100 && !showSidebar) {
if (!mobile) {
setShowSidebar(true);
}
}
}
}
function handleMouseLeave() {
if (!mobile) {
setShowSidebar(false);
}
}
if (!mobile) {
document.addEventListener("mousemove", handleEvent);
document.addEventListener("mouseleave", handleMouseLeave);
}
return function () {
if (!mobile) {
document.removeEventListener("mousemove", handleEvent);
document.removeEventListener("mouseleave", handleMouseLeave);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [showSidebar, sidebarPinned, sidebarElementRef, mobile]);
}
// context
interface SidebarContextProps {
sidebarPinned: boolean;
sidebarVisible: boolean;
showSidebar: boolean;
toggleSidebarPinned: () => void;
setShowSidebar: (show: boolean) => void;
sidebarElementRef: React.RefObject<HTMLDivElement>;
}
const SidebarContext = createContext<SidebarContextProps | undefined>(
undefined
);
// public provider
interface SidebarProviderProps {
children: ReactNode;
mobile?: boolean;
isAnonymousUser?: boolean;
}
export function SidebarProvider({
children,
mobile = false,
isAnonymousUser = false,
}: SidebarProviderProps) {
const [sidebarPinned, setSidebarPinned] = useState(true);
const [showSidebar, setShowSidebar] = useState(false);
const sidebarElementRef = useRef<HTMLDivElement>(null);
const toggleSidebarPinned = useCallback(() => {
setSidebarPinned((prev) => !prev);
}, []);
const setShowSidebarCallback = useCallback((show: boolean) => {
setShowSidebar(show);
}, []);
useSidebarVisibility({
sidebarElementRef,
sidebarPinned,
showSidebar,
setShowSidebar: setShowSidebarCallback,
mobile,
isAnonymousUser,
});
const sidebarVisible = sidebarPinned || showSidebar;
const contextValue: SidebarContextProps = {
sidebarPinned,
showSidebar,
sidebarVisible,
toggleSidebarPinned,
setShowSidebar: setShowSidebarCallback,
sidebarElementRef,
};
return (
<SidebarContext.Provider value={contextValue}>
{children}
</SidebarContext.Provider>
);
}
// public hook
export function useSidebar(): SidebarContextProps {
const context = useContext(SidebarContext);
if (context === undefined) {
throw new Error("useSidebar must be used within a SidebarProvider");
}
return context;
}

View File

@@ -0,0 +1,51 @@
"use client";
import React, { useState } from "react";
import { UserDropdown } from "@/components/UserDropdown";
import { UserSettingsModal } from "@/app/chat/modal/UserSettingsModal";
import { useChatContext } from "@/components/context/ChatContext";
import { useUser } from "@/components/user/UserProvider";
import { usePopup } from "@/components/admin/connectors/Popup";
import { useFederatedOAuthStatus } from "@/lib/hooks/useFederatedOAuthStatus";
import { useLlmManager } from "@/lib/hooks";
export function Header() {
const [userSettingsOpen, setUserSettingsOpen] = useState(false);
const { llmProviders, ccPairs } = useChatContext();
const { user } = useUser();
const { popup, setPopup } = usePopup();
const {
connectors: federatedConnectors,
refetch: refetchFederatedConnectors,
} = useFederatedOAuthStatus();
const llmManager = useLlmManager(llmProviders, undefined, undefined);
const toggleUserSettings = () => {
setUserSettingsOpen(!userSettingsOpen);
};
return (
<>
<div className="flex items-center justify-end gap-2 pr-[16px] pt-[16px] z-50">
<UserDropdown
hideUserDropdown={false}
toggleUserSettings={toggleUserSettings}
/>
</div>
{userSettingsOpen && (
<UserSettingsModal
setPopup={setPopup}
setCurrentLlm={(newLlm) => llmManager.updateCurrentLlm(newLlm)}
defaultModel={user?.preferences.default_model!}
llmProviders={llmProviders}
ccPairs={ccPairs}
federatedConnectors={federatedConnectors}
refetchFederatedConnectors={refetchFederatedConnectors}
onClose={() => setUserSettingsOpen(false)}
/>
)}
{popup}
</>
);
}

View File

@@ -0,0 +1,92 @@
"use client";
import React from "react";
import { Sidebar } from "@/components/sidebar/Sidebar";
import { useChatContext } from "@/components/context/ChatContext";
import { useSidebar } from "@/components/context/SidebarProvider";
import { useSidebarShortcut } from "@/lib/browserUtilities";
import { NewChatIcon } from "@/components/icons/icons";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import Link from "next/link";
import CollapsibleLogo from "../../logo/CollapsibleLogo";
import { Header } from "./Header";
interface MainPageFrameProps {
children: React.ReactNode;
}
export default function MainPageFrame({ children }: MainPageFrameProps) {
const { chatSessions, folders } = useChatContext();
const {
sidebarElementRef,
sidebarPinned,
sidebarVisible,
toggleSidebarPinned,
} = useSidebar();
useSidebarShortcut(toggleSidebarPinned);
return (
<div className="flex relative overflow-x-hidden overscroll-contain flex-col w-full h-screen">
<div className="flex flex-1 w-full">
{/* Header framing
Interestingly enough, the CollapsibleLogo is not a part of the Header.
This is so that it can be a part of the seamless animation when opening/closing the sidebar.
*/}
<div className="fixed top-[12px] left-[16px] z-30">
<Link href="chat">
<CollapsibleLogo />
</Link>
</div>
<div className={`fixed top-0 w-full flex flex-1 justify-end z-10`}>
<Header />
</div>
</div>
{/* Sidebar framing */}
<div
ref={sidebarElementRef}
className={`
flex-none
fixed
left-0
z-20
bg-background-100
h-screen
transition-all
bg-opacity-80
duration-300
ease-in-out
${
sidebarVisible
? "opacity-100 w-[250px] translate-x-0"
: "opacity-0 w-[200px] pointer-events-none -translate-x-10"
}
`}
>
<div className="w-full relative">
<Sidebar
existingChats={chatSessions}
currentChatSession={null}
folders={folders}
sidebarVisible={sidebarVisible}
sidebarPinned={sidebarPinned}
toggleSidebarPinned={toggleSidebarPinned}
/>
</div>
</div>
{/* Main content framing */}
<main
className={`flex flex-col h-full transition-all duration-300 ease-in-out ${sidebarPinned ? "pl-[250px]" : ""}`}
>
{children}
</main>
</div>
);
}

View File

@@ -2,35 +2,23 @@
import { useContext } from "react";
import { FiSidebar } from "react-icons/fi";
import { SettingsContext } from "../settings/SettingsProvider";
import { LeftToLineIcon, NewChatIcon, RightToLineIcon } from "../icons/icons";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { pageType } from "@/components/sidebar/types";
import { Logo } from "../logo/Logo";
import Link from "next/link";
import { LogoComponent } from "@/components/logo/FixedLogo";
export default function LogoWithText({
toggleSidebar,
hideOnMobile,
handleNewChat,
page,
toggled,
showArrow,
assistantId,
explicitlyUntoggle = () => null,
}: {
hideOnMobile?: boolean;
toggleSidebar?: () => void;
handleNewChat?: () => void;
page: pageType;
toggled?: boolean;
showArrow?: boolean;
assistantId?: number;
explicitlyUntoggle?: () => void;
}) {
const combinedSettings = useContext(SettingsContext);
@@ -81,67 +69,7 @@ export default function LogoWithText({
/>
</div>
)}
{page == "chat" && !showArrow && (
<TooltipProvider delayDuration={1000}>
<Tooltip>
<TooltipTrigger asChild>
<Link
className="my-auto mobile:hidden"
href={
`/${page}` +
(assistantId ? `?assistantId=${assistantId}` : "")
}
onClick={(e) => {
if (e.metaKey || e.ctrlKey) {
return;
}
if (handleNewChat) {
handleNewChat();
}
}}
>
<NewChatIcon
className="ml-2 flex-none text-text-700 hover:text-text-600 "
size={24}
/>
</Link>
</TooltipTrigger>
<TooltipContent>New Chat</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
<div className="flex ml-auto gap-x-4">
{showArrow && toggleSidebar && (
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<button
className="mr-2 my-auto"
onClick={() => {
toggleSidebar();
if (toggled) {
explicitlyUntoggle();
}
}}
>
{!toggled && !combinedSettings?.isMobile ? (
<RightToLineIcon className="mobile:hidden text-sidebar-toggle" />
) : (
<LeftToLineIcon className="mobile:hidden text-sidebar-toggle" />
)}
<FiSidebar
size={20}
className="hidden mobile:block text-text-mobile-sidebar"
/>
</button>
</TooltipTrigger>
<TooltipContent className="!border-none">
{toggled ? `Unpin sidebar` : "Pin sidebar"}
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div>
<div className="flex ml-auto gap-x-4"></div>
</div>
);
}

View File

@@ -0,0 +1,18 @@
"use client";
import React from "react";
import { Logo } from "@/components/logo/Logo";
import { LogoComponent } from "@/components/logo/FixedLogo";
export default function CollapsibleLogo(
props: React.ComponentProps<typeof LogoComponent>
) {
return (
<>
<div className="block mobile:hidden">
<LogoComponent {...props} />
</div>
<Logo className="block desktop:hidden" size="small" {...props} />
</>
);
}

View File

@@ -18,7 +18,7 @@ export const LogoComponent = memo(function LogoComponent({
show,
isAdmin,
}: {
enterpriseSettings: EnterpriseSettings | null;
enterpriseSettings?: EnterpriseSettings;
backgroundToggled?: boolean;
show?: boolean;
isAdmin?: boolean;
@@ -28,9 +28,7 @@ export const LogoComponent = memo(function LogoComponent({
return (
<div
onClick={isAdmin ? () => router.push("/chat") : () => {}}
className={`max-w-[200px]
${!show && "mobile:hidden"}
flex text-text-900 items-center gap-x-1`}
className={`max-w-[200px] ${!!show && "mobile:hidden"} flex text-text-900 items-center gap-x-1`}
>
{enterpriseSettings && enterpriseSettings.application_name ? (
<>

View File

@@ -57,11 +57,7 @@ export function Logo({
);
}
export function LogoType({
size = "default",
}: {
size?: "small" | "default" | "large";
}) {
export function LogoType() {
return (
<OnyxLogoTypeIcon
size={115}

View File

@@ -5,6 +5,7 @@ import React, {
forwardRef,
useContext,
useCallback,
useState,
} from "react";
import Link from "next/link";
import {
@@ -23,10 +24,11 @@ import {
DocumentIcon2,
KnowledgeGroupIcon,
NewChatIcon,
LeftToLineIcon,
RightToLineIcon,
} from "@/components/icons/icons";
import { PagesTab } from "./PagesTab";
import { pageType } from "./types";
import LogoWithText from "@/components/header/LogoWithText";
import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces";
import { DragEndEvent } from "@dnd-kit/core";
import { useAssistants } from "@/components/context/AssistantsContext";
@@ -54,23 +56,8 @@ import { CSS } from "@dnd-kit/utilities";
import { CircleX, PinIcon } from "lucide-react";
import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
import { TruncatedText } from "@/components/ui/truncatedText";
interface HistorySidebarProps {
liveAssistant?: MinimalPersonaSnapshot | null;
page: pageType;
existingChats?: ChatSession[];
currentChatSession?: ChatSession | null | undefined;
folders?: Folder[];
toggleSidebar?: () => void;
toggled?: boolean;
removeToggle?: () => void;
reset?: () => void;
showShareModal?: (chatSession: ChatSession) => void;
showDeleteModal?: (chatSession: ChatSession) => void;
explicitlyUntoggle: () => void;
setShowAssistantsModal: (show: boolean) => void;
toggleChatSessionSearchModal?: () => void;
}
import { FiSidebar } from "react-icons/fi";
import AssistantModal from "@/app/assistants/mine/AssistantModal";
interface SortableAssistantProps {
assistant: MinimalPersonaSnapshot;
@@ -169,26 +156,44 @@ const SortableAssistant: React.FC<SortableAssistantProps> = ({
);
};
export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
interface SidebarProps {
liveAssistant?: MinimalPersonaSnapshot | null;
existingChats?: ChatSession[];
currentChatSession?: ChatSession | null | undefined;
folders?: Folder[];
removeToggle?: () => void;
reset?: () => void;
showShareModal?: (chatSession: ChatSession) => void;
showDeleteModal?: (chatSession: ChatSession) => void;
toggleChatSessionSearchModal?: () => void;
// sidebar-visibility related
sidebarVisible: boolean;
sidebarPinned: boolean;
toggleSidebarPinned: () => void;
}
export const Sidebar = forwardRef<HTMLDivElement, SidebarProps>(
(
{
liveAssistant,
reset = () => null,
setShowAssistantsModal = () => null,
toggled,
page,
existingChats,
currentChatSession,
folders,
explicitlyUntoggle,
toggleSidebar,
removeToggle,
showShareModal,
toggleChatSessionSearchModal,
showDeleteModal,
// sidebar-visibility related
sidebarVisible,
sidebarPinned,
toggleSidebarPinned,
},
ref: ForwardedRef<HTMLDivElement>
) => {
const [showAssistantsModal, setShowAssistantsModal] = useState(false);
const searchParams = useSearchParams();
const router = useRouter();
const { user, toggleAssistantPinnedStatus } = useUser();
@@ -243,23 +248,20 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
return null;
}
function newChatUrl(): string {
return `/chat${currentChatSession ? `?assistantId=${currentChatSession.persona_id}` : ""}`;
}
const handleNewChat = () => {
reset();
console.log("currentChatSession", currentChatSession);
const newChatUrl =
`/${page}` +
(currentChatSession
? `?assistantId=${currentChatSession.persona_id}`
: "");
router.push(newChatUrl);
router.push(newChatUrl());
};
return (
<>
<div
ref={ref}
className={`
<div
ref={ref}
className={`
flex
flex-none
gap-y-4
@@ -276,157 +278,165 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
pt-2
transition-transform
`}
>
<div className="px-4 pl-2">
<LogoWithText
showArrow={true}
toggled={toggled}
page={page}
toggleSidebar={toggleSidebar}
explicitlyUntoggle={explicitlyUntoggle}
/>
</div>
{page == "chat" && (
<div className="px-4 px-1 -mx-2 gap-y-1 flex-col text-text-history-sidebar-button flex gap-x-1.5 items-center items-center">
<Link
className="w-full px-2 py-1 group rounded-md items-center hover:bg-accent-background-hovered cursor-pointer transition-all duration-150 flex gap-x-2"
href={
`/${page}` +
(currentChatSession
? `?assistantId=${currentChatSession?.persona_id}`
: "")
}
onClick={(e) => {
if (e.metaKey || e.ctrlKey) {
return;
}
if (handleNewChat) {
handleNewChat();
}
}}
>
<NewChatIcon size={20} className="flex-none" />
<p className="my-auto flex font-normal items-center ">
New Chat
</p>
</Link>
<Link
className="w-full px-2 py-1 rounded-md items-center hover:bg-hover cursor-pointer transition-all duration-150 flex gap-x-2"
href="/chat/my-documents"
>
<KnowledgeGroupIcon
size={20}
className="flex-none text-text-history-sidebar-button"
/>
<p className="my-auto flex font-normal items-center text-base">
My Documents
</p>
</Link>
{user?.preferences?.shortcut_enabled && (
<Link
className="w-full px-2 py-1 rounded-md items-center hover:bg-accent-background-hovered cursor-pointer transition-all duration-150 flex gap-x-2"
href="/chat/input-prompts"
>
<DocumentIcon2
>
<div className="px-4 h-[40px] flex flex-row justify-end items-center w-full">
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<button onClick={toggleSidebarPinned}>
{sidebarPinned && !combinedSettings?.isMobile ? (
<LeftToLineIcon className="mobile:hidden text-sidebar-toggle" />
) : (
<RightToLineIcon className="mobile:hidden text-sidebar-toggle" />
)}
<FiSidebar
size={20}
className="flex-none text-text-history-sidebar-button"
className="hidden mobile:block text-text-mobile-sidebar"
/>
<p className="my-auto flex font-normal items-center text-base">
Prompt Shortcuts
</p>
</Link>
)}
</div>
)}
<div className="h-full relative overflow-x-hidden overflow-y-auto">
<div className="flex px-4 font-normal text-sm gap-x-2 leading-normal text-text-500/80 dark:text-[#D4D4D4] items-center font-normal leading-normal">
Assistants
</div>
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
modifiers={[restrictToVerticalAxis]}
</button>
</TooltipTrigger>
<TooltipContent className="!border-none">
{sidebarVisible ? `Unpin sidebar` : "Pin sidebar"}
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<div className="px-4 -mx-2 gap-y-1 flex-col text-text-history-sidebar-button flex gap-x-1.5 items-center">
<Link
className="w-full px-2 py-1 group rounded-md items-center hover:bg-accent-background-hovered cursor-pointer transition-all duration-150 flex gap-x-2"
href={newChatUrl()}
onClick={(e) => {
if (e.metaKey || e.ctrlKey) {
return;
}
if (handleNewChat) {
handleNewChat();
}
}}
>
<NewChatIcon size={20} className="flex-none" />
<p className="my-auto flex font-normal items-center ">New Chat</p>
</Link>
<Link
className="w-full px-2 py-1 rounded-md items-center hover:bg-hover cursor-pointer transition-all duration-150 flex gap-x-2"
href="/chat/my-documents"
>
<KnowledgeGroupIcon
size={20}
className="flex-none text-text-history-sidebar-button"
/>
<p className="my-auto flex font-normal items-center text-base">
My Documents
</p>
</Link>
{user?.preferences?.shortcut_enabled && (
<Link
className="w-full px-2 py-1 rounded-md items-center hover:bg-accent-background-hovered cursor-pointer transition-all duration-150 flex gap-x-2"
href="/chat/input-prompts"
>
<SortableContext
items={pinnedAssistants.map((a) =>
a.id === 0 ? "assistant-0" : a.id
)}
strategy={verticalListSortingStrategy}
>
<div className="flex px-0 mr-4 flex-col gap-y-1 mt-1">
{pinnedAssistants.map((assistant: MinimalPersonaSnapshot) => (
<SortableAssistant
key={assistant.id === 0 ? "assistant-0" : assistant.id}
assistant={assistant}
active={assistant.id === liveAssistant?.id}
onClick={() => {
router.push(
buildChatUrl(searchParams, null, assistant.id)
);
}}
onPinAction={async (e: React.MouseEvent) => {
e.stopPropagation();
await toggleAssistantPinnedStatus(
pinnedAssistants.map((a) => a.id),
assistant.id,
false
);
await refreshAssistants();
}}
/>
))}
</div>
</SortableContext>
</DndContext>
{!pinnedAssistants.some((a) => a.id === liveAssistant?.id) &&
liveAssistant && (
<div className="w-full mt-1 pr-4">
<DocumentIcon2
size={20}
className="flex-none text-text-history-sidebar-button"
/>
<p className="my-auto flex font-normal items-center text-base">
Prompt Shortcuts
</p>
</Link>
)}
</div>
<div className="h-full relative overflow-x-hidden overflow-y-auto">
<div className="flex px-4 font-normal text-sm gap-x-2 leading-normal text-text-500/80 dark:text-[#D4D4D4] items-center">
Assistants
</div>
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
modifiers={[restrictToVerticalAxis]}
>
<SortableContext
items={pinnedAssistants.map((a) =>
a.id === 0 ? "assistant-0" : a.id
)}
strategy={verticalListSortingStrategy}
>
<div className="flex px-0 mr-4 flex-col gap-y-1 mt-1">
{pinnedAssistants.map((assistant: MinimalPersonaSnapshot) => (
<SortableAssistant
pinned={false}
assistant={liveAssistant}
active={liveAssistant.id === liveAssistant?.id}
key={assistant.id === 0 ? "assistant-0" : assistant.id}
assistant={assistant}
active={assistant.id === liveAssistant?.id}
onClick={() => {
router.push(
buildChatUrl(searchParams, null, liveAssistant.id)
buildChatUrl(searchParams, null, assistant.id)
);
}}
onPinAction={async (e: React.MouseEvent) => {
e.stopPropagation();
await toggleAssistantPinnedStatus(
[...pinnedAssistants.map((a) => a.id)],
liveAssistant.id,
true
pinnedAssistants.map((a) => a.id),
assistant.id,
false
);
await refreshAssistants();
}}
/>
</div>
)}
))}
</div>
</SortableContext>
</DndContext>
{!pinnedAssistants.some((a) => a.id === liveAssistant?.id) &&
liveAssistant && (
<div className="w-full mt-1 pr-4">
<SortableAssistant
pinned={false}
assistant={liveAssistant}
active={liveAssistant.id === liveAssistant?.id}
onClick={() => {
router.push(
buildChatUrl(searchParams, null, liveAssistant.id)
);
}}
onPinAction={async (e: React.MouseEvent) => {
e.stopPropagation();
await toggleAssistantPinnedStatus(
[...pinnedAssistants.map((a) => a.id)],
liveAssistant.id,
true
);
await refreshAssistants();
}}
/>
</div>
)}
<div className="w-full px-4">
<button
aria-label="Explore Assistants"
onClick={() => setShowAssistantsModal(true)}
className="w-full cursor-pointer text-base text-black dark:text-[#D4D4D4] hover:bg-background-chat-hover flex items-center gap-x-2 py-1 px-2 rounded-md"
>
Explore Assistants
</button>
</div>
<PagesTab
toggleChatSessionSearchModal={toggleChatSessionSearchModal}
showDeleteModal={showDeleteModal}
showShareModal={showShareModal}
closeSidebar={removeToggle}
existingChats={existingChats}
currentChatId={currentChatId}
folders={folders}
/>
<div className="w-full px-4">
<button
aria-label="Explore Assistants"
onClick={() => setShowAssistantsModal(true)}
className="w-full cursor-pointer text-base text-black dark:text-[#D4D4D4] hover:bg-background-chat-hover flex items-center gap-x-2 py-1 px-2 rounded-md"
>
Explore Assistants
</button>
</div>
<PagesTab
toggleChatSessionSearchModal={toggleChatSessionSearchModal}
showDeleteModal={showDeleteModal}
showShareModal={showShareModal}
closeSidebar={removeToggle}
existingChats={existingChats}
currentChatId={currentChatId}
folders={folders}
/>
</div>
</>
{showAssistantsModal && (
<AssistantModal hideModal={() => setShowAssistantsModal(false)} />
)}
</div>
);
}
);
HistorySidebar.displayName = "HistorySidebar";
Sidebar.displayName = "Sidebar";

View File

@@ -9,7 +9,7 @@ export enum OperatingSystem {
Other = "Other",
}
export const useOperatingSystem = (): OperatingSystem => {
export function useOperatingSystem(): OperatingSystem {
const [os, setOS] = useState<OperatingSystem>(OperatingSystem.Other);
useEffect(() => {
@@ -22,16 +22,16 @@ export const useOperatingSystem = (): OperatingSystem => {
}, []);
return os;
};
}
// Use this to handle the sidebar shortcut for the chat page
// The shortcut is Ctrl+E on Windows/Linux and Cmd+E on Mac
// This hook handles the keyboard event and toggles the sidebar
export const useSidebarShortcut = (router: any, toggleSidebar: () => void) => {
// Use this to handle the sidebar shortcut for the chat page.
// The shortcut is Ctrl+E on Windows/Linux and Cmd+E on Mac.
// This hook handles the keyboard event and toggles the sidebar.
export function useSidebarShortcut(toggleSidebar: () => void) {
const os = useOperatingSystem();
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
function handleKeyDown(event: KeyboardEvent) {
const isMac = os === OperatingSystem.Mac;
const modifierKey = isMac ? event.metaKey : event.ctrlKey;
@@ -39,16 +39,16 @@ export const useSidebarShortcut = (router: any, toggleSidebar: () => void) => {
event.preventDefault();
toggleSidebar();
}
};
}
window.addEventListener("keydown", handleKeyDown);
return () => {
return function () {
window.removeEventListener("keydown", handleKeyDown);
};
}, [router, toggleSidebar, os]);
};
}, [toggleSidebar, os]);
}
const KeyboardSymbol = () => {
function KeyboardSymbol() {
const os = useOperatingSystem();
if (os === OperatingSystem.Windows) {
@@ -56,6 +56,6 @@ const KeyboardSymbol = () => {
} else {
return <MacIcon size={12} />;
}
};
}
export default KeyboardSymbol;

View File

@@ -1,4 +1,5 @@
"use client";
import {
ConnectorIndexingStatus,
DocumentBoostStatus,
@@ -24,7 +25,6 @@ import {
} from "@/app/admin/assistants/interfaces";
import { LLMProviderDescriptor } from "@/app/admin/configuration/llm/interfaces";
import { isAnthropic } from "@/app/admin/configuration/llm/utils";
import { getSourceMetadata } from "./sources";
import { AuthType, NEXT_PUBLIC_CLOUD_ENABLED } from "./constants";
import { useUser } from "@/components/user/UserProvider";
import { SEARCH_TOOL_ID } from "@/app/chat/tools/constants";