mirror of
https://github.com/onyx-dot-app/onyx.git
synced 2026-03-25 01:22:45 +00:00
Compare commits
26 Commits
v3.0.0-clo
...
sidebar-cl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bd0b01abf | ||
|
|
5ba90d402e | ||
|
|
718d911790 | ||
|
|
dc35205824 | ||
|
|
0951c9a732 | ||
|
|
a2ca9019d1 | ||
|
|
4a1fc769ea | ||
|
|
b3a5dd4a8b | ||
|
|
cd92e49c18 | ||
|
|
915a4c48b2 | ||
|
|
2ddf3e2cbe | ||
|
|
a24050a776 | ||
|
|
5121943a9f | ||
|
|
915764250c | ||
|
|
dd9789296b | ||
|
|
672ef5333f | ||
|
|
e6a53bfcaf | ||
|
|
a38f4acebe | ||
|
|
063b88f453 | ||
|
|
7ca53ccca5 | ||
|
|
9d86e6a345 | ||
|
|
95901b10a0 | ||
|
|
8ea8558a3f | ||
|
|
0341d45aa7 | ||
|
|
8032f91b81 | ||
|
|
b8ac698a66 |
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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} />
|
||||
|
||||
@@ -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 };
|
||||
};
|
||||
176
web/src/components/context/SidebarContext.tsx
Normal file
176
web/src/components/context/SidebarContext.tsx
Normal 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;
|
||||
}
|
||||
172
web/src/components/context/SidebarProvider.tsx
Normal file
172
web/src/components/context/SidebarProvider.tsx
Normal 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;
|
||||
}
|
||||
51
web/src/components/frames/MainPageFrame/Header.tsx
Normal file
51
web/src/components/frames/MainPageFrame/Header.tsx
Normal 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}
|
||||
</>
|
||||
);
|
||||
}
|
||||
92
web/src/components/frames/MainPageFrame/index.tsx
Normal file
92
web/src/components/frames/MainPageFrame/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
18
web/src/components/logo/CollapsibleLogo.tsx
Normal file
18
web/src/components/logo/CollapsibleLogo.tsx
Normal 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} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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 ? (
|
||||
<>
|
||||
|
||||
@@ -57,11 +57,7 @@ export function Logo({
|
||||
);
|
||||
}
|
||||
|
||||
export function LogoType({
|
||||
size = "default",
|
||||
}: {
|
||||
size?: "small" | "default" | "large";
|
||||
}) {
|
||||
export function LogoType() {
|
||||
return (
|
||||
<OnyxLogoTypeIcon
|
||||
size={115}
|
||||
|
||||
@@ -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";
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in New Issue
Block a user