mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-10-10 21:26:01 +02:00
Add initial mobile support (#1962)
This commit is contained in:
@@ -19,4 +19,5 @@ export interface CombinedSettings {
|
|||||||
settings: Settings;
|
settings: Settings;
|
||||||
enterpriseSettings: EnterpriseSettings | null;
|
enterpriseSettings: EnterpriseSettings | null;
|
||||||
customAnalyticsScript: string | null;
|
customAnalyticsScript: string | null;
|
||||||
|
isMobile?: boolean;
|
||||||
}
|
}
|
||||||
|
@@ -7,12 +7,13 @@ import { Folder } from "@/app/chat/folders/interfaces";
|
|||||||
import { User } from "@/lib/types";
|
import { User } from "@/lib/types";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import { SIDEBAR_TOGGLED_COOKIE_NAME } from "@/components/resizable/constants";
|
import { SIDEBAR_TOGGLED_COOKIE_NAME } from "@/components/resizable/constants";
|
||||||
import { ReactNode, useEffect, useRef, useState } from "react";
|
import { ReactNode, useContext, useEffect, useRef, useState } from "react";
|
||||||
import { useSidebarVisibility } from "@/components/chat_search/hooks";
|
import { useSidebarVisibility } from "@/components/chat_search/hooks";
|
||||||
import FunctionalHeader from "@/components/chat_search/Header";
|
import FunctionalHeader from "@/components/chat_search/Header";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { pageType } from "../chat/sessionSidebar/types";
|
import { pageType } from "../chat/sessionSidebar/types";
|
||||||
import FixedLogo from "../chat/shared_chat_search/FixedLogo";
|
import FixedLogo from "../chat/shared_chat_search/FixedLogo";
|
||||||
|
import { SettingsContext } from "@/components/settings/SettingsProvider";
|
||||||
|
|
||||||
interface SidebarWrapperProps<T extends object> {
|
interface SidebarWrapperProps<T extends object> {
|
||||||
chatSessions?: ChatSession[];
|
chatSessions?: ChatSession[];
|
||||||
@@ -55,11 +56,13 @@ export default function SidebarWrapper<T extends object>({
|
|||||||
|
|
||||||
const sidebarElementRef = useRef<HTMLDivElement>(null);
|
const sidebarElementRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const settings = useContext(SettingsContext);
|
||||||
useSidebarVisibility({
|
useSidebarVisibility({
|
||||||
toggledSidebar,
|
toggledSidebar,
|
||||||
sidebarElementRef,
|
sidebarElementRef,
|
||||||
showDocSidebar,
|
showDocSidebar,
|
||||||
setShowDocSidebar,
|
setShowDocSidebar,
|
||||||
|
mobile: settings?.isMobile,
|
||||||
});
|
});
|
||||||
|
|
||||||
const innerSidebarElementRef = useRef<HTMLDivElement>(null);
|
const innerSidebarElementRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -90,9 +93,7 @@ export default function SidebarWrapper<T extends object>({
|
|||||||
flex-none
|
flex-none
|
||||||
fixed
|
fixed
|
||||||
left-0
|
left-0
|
||||||
z-20
|
z-30
|
||||||
overflow-y-hidden
|
|
||||||
sidebar
|
|
||||||
bg-background-100
|
bg-background-100
|
||||||
h-screen
|
h-screen
|
||||||
transition-all
|
transition-all
|
||||||
@@ -119,10 +120,10 @@ export default function SidebarWrapper<T extends object>({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="absolute left-0 w-full top-0">
|
<div className="absolute h-svh left-0 w-full top-0">
|
||||||
<FunctionalHeader
|
<FunctionalHeader
|
||||||
|
toggleSidebar={toggleSidebar}
|
||||||
page="assistants"
|
page="assistants"
|
||||||
showSidebar={showDocSidebar}
|
|
||||||
user={headerProps.user}
|
user={headerProps.user}
|
||||||
/>
|
/>
|
||||||
<div className="w-full flex">
|
<div className="w-full flex">
|
||||||
@@ -138,6 +139,7 @@ export default function SidebarWrapper<T extends object>({
|
|||||||
ease-in-out
|
ease-in-out
|
||||||
${toggledSidebar ? "w-[250px]" : "w-[0px]"}`}
|
${toggledSidebar ? "w-[250px]" : "w-[0px]"}`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="mt-4 w-full max-w-3xl mx-auto">
|
<div className="mt-4 w-full max-w-3xl mx-auto">
|
||||||
{content(contentProps)}
|
{content(contentProps)}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -47,7 +47,7 @@ export function AssistantTools({
|
|||||||
hovered?: boolean;
|
hovered?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="relative text-xs flex text-subtle">
|
<div className="relative text-xs overflow-x-hidden flex text-subtle">
|
||||||
<span
|
<span
|
||||||
className={`${assistant.tools.length > 0 && "py-1"} ${!list ? "font-semibold" : "text-subtle text-sm"}`}
|
className={`${assistant.tools.length > 0 && "py-1"} ${!list ? "font-semibold" : "text-subtle text-sm"}`}
|
||||||
>
|
>
|
||||||
|
@@ -287,7 +287,7 @@ export function AssistantsList({ user, assistants }: AssistantsListProps) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{popup}
|
{popup}
|
||||||
<div className="mx-auto w-searchbar-xs 2xl:w-searchbar-sm 3xl:w-searchbar">
|
<div className="mx-auto mobile:w-[90%] desktop:w-searchbar-xs 2xl:w-searchbar-sm 3xl:w-searchbar">
|
||||||
<AssistantsPageTitle>My Assistants</AssistantsPageTitle>
|
<AssistantsPageTitle>My Assistants</AssistantsPageTitle>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4 mt-3">
|
<div className="grid grid-cols-2 gap-4 mt-3">
|
||||||
|
@@ -38,7 +38,7 @@ export function ChatIntro({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex justify-center items-center h-full">
|
<div className="flex justify-center items-center h-full">
|
||||||
<div className="w-message-xs 2xl:w-message-sm 3xl:w-message">
|
<div className="mobile:w-[90%] mobile:px-4 w-message-xs 2xl:w-message-sm 3xl:w-message">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="mx-auto">
|
<div className="mx-auto">
|
||||||
<div className="m-auto text-3xl font-strong font-bold text-strong w-fit">
|
<div className="m-auto text-3xl font-strong font-bold text-strong w-fit">
|
||||||
|
@@ -85,7 +85,7 @@ export function ChatPage({
|
|||||||
defaultSelectedAssistantId,
|
defaultSelectedAssistantId,
|
||||||
toggledSidebar,
|
toggledSidebar,
|
||||||
}: {
|
}: {
|
||||||
toggle: () => void;
|
toggle: (toggled?: boolean) => void;
|
||||||
documentSidebarInitialWidth?: number;
|
documentSidebarInitialWidth?: number;
|
||||||
defaultSelectedAssistantId?: number;
|
defaultSelectedAssistantId?: number;
|
||||||
toggledSidebar: boolean;
|
toggledSidebar: boolean;
|
||||||
@@ -524,15 +524,6 @@ export function ChatPage({
|
|||||||
const distance = 500; // distance that should "engage" the scroll
|
const distance = 500; // distance that should "engage" the scroll
|
||||||
const debounce = 100; // time for debouncing
|
const debounce = 100; // time for debouncing
|
||||||
|
|
||||||
useScrollonStream({
|
|
||||||
isStreaming,
|
|
||||||
scrollableDivRef,
|
|
||||||
scrollDist,
|
|
||||||
endDivRef,
|
|
||||||
distance,
|
|
||||||
debounce,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [hasPerformedInitialScroll, setHasPerformedInitialScroll] = useState(
|
const [hasPerformedInitialScroll, setHasPerformedInitialScroll] = useState(
|
||||||
existingChatSessionId === null
|
existingChatSessionId === null
|
||||||
);
|
);
|
||||||
@@ -644,7 +635,6 @@ export function ChatPage({
|
|||||||
alternativeAssistantOverride?: Persona | null;
|
alternativeAssistantOverride?: Persona | null;
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
setAlternativeGeneratingAssistant(alternativeAssistantOverride);
|
setAlternativeGeneratingAssistant(alternativeAssistantOverride);
|
||||||
|
|
||||||
clientScrollToBottom();
|
clientScrollToBottom();
|
||||||
let currChatSessionId: number;
|
let currChatSessionId: number;
|
||||||
let isNewSession = chatSessionIdRef.current === null;
|
let isNewSession = chatSessionIdRef.current === null;
|
||||||
@@ -1059,6 +1049,10 @@ export function ChatPage({
|
|||||||
|
|
||||||
toggle();
|
toggle();
|
||||||
};
|
};
|
||||||
|
const removeToggle = () => {
|
||||||
|
setShowDocSidebar(false);
|
||||||
|
toggle(false);
|
||||||
|
};
|
||||||
|
|
||||||
const sidebarElementRef = useRef<HTMLDivElement>(null);
|
const sidebarElementRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -1067,6 +1061,17 @@ export function ChatPage({
|
|||||||
sidebarElementRef,
|
sidebarElementRef,
|
||||||
showDocSidebar,
|
showDocSidebar,
|
||||||
setShowDocSidebar,
|
setShowDocSidebar,
|
||||||
|
setToggled: removeToggle,
|
||||||
|
mobile: settings?.isMobile,
|
||||||
|
});
|
||||||
|
|
||||||
|
useScrollonStream({
|
||||||
|
isStreaming,
|
||||||
|
scrollableDivRef,
|
||||||
|
scrollDist,
|
||||||
|
endDivRef,
|
||||||
|
distance,
|
||||||
|
debounce,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -1119,20 +1124,48 @@ export function ChatPage({
|
|||||||
<>
|
<>
|
||||||
<HealthCheckBanner secondsUntilExpiration={secondsUntilExpiration} />
|
<HealthCheckBanner secondsUntilExpiration={secondsUntilExpiration} />
|
||||||
<InstantSSRAutoRefresh />
|
<InstantSSRAutoRefresh />
|
||||||
|
|
||||||
{/* ChatPopup is a custom popup that displays a admin-specified message on initial user visit.
|
{/* ChatPopup is a custom popup that displays a admin-specified message on initial user visit.
|
||||||
Only used in the EE version of the app. */}
|
Only used in the EE version of the app. */}
|
||||||
<ChatPopup />
|
<ChatPopup />
|
||||||
|
{currentFeedback && (
|
||||||
<div className="flex relative bg-background text-default ">
|
<FeedbackModal
|
||||||
|
feedbackType={currentFeedback[0]}
|
||||||
|
onClose={() => setCurrentFeedback(null)}
|
||||||
|
onSubmit={({ message, predefinedFeedback }) => {
|
||||||
|
onFeedback(
|
||||||
|
currentFeedback[1],
|
||||||
|
currentFeedback[0],
|
||||||
|
message,
|
||||||
|
predefinedFeedback
|
||||||
|
);
|
||||||
|
setCurrentFeedback(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{sharingModalVisible && chatSessionIdRef.current !== null && (
|
||||||
|
<ShareChatSessionModal
|
||||||
|
chatSessionId={chatSessionIdRef.current}
|
||||||
|
existingSharedStatus={chatSessionSharedStatus}
|
||||||
|
onClose={() => setSharingModalVisible(false)}
|
||||||
|
onShare={(shared) =>
|
||||||
|
setChatSessionSharedStatus(
|
||||||
|
shared
|
||||||
|
? ChatSessionSharedStatus.Public
|
||||||
|
: ChatSessionSharedStatus.Private
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="fixed inset-0 flex flex-col text-default">
|
||||||
|
<div className="h-[100dvh] overflow-y-hidden">
|
||||||
|
<div className="w-full">
|
||||||
<div
|
<div
|
||||||
ref={sidebarElementRef}
|
ref={sidebarElementRef}
|
||||||
className={`
|
className={`
|
||||||
flex-none
|
flex-none
|
||||||
absolute
|
fixed
|
||||||
left-0
|
left-0
|
||||||
z-20
|
z-30
|
||||||
sidebar
|
|
||||||
bg-background-100
|
bg-background-100
|
||||||
h-screen
|
h-screen
|
||||||
transition-all
|
transition-all
|
||||||
@@ -1150,48 +1183,24 @@ export function ChatPage({
|
|||||||
page="chat"
|
page="chat"
|
||||||
ref={innerSidebarElementRef}
|
ref={innerSidebarElementRef}
|
||||||
toggleSidebar={toggleSidebar}
|
toggleSidebar={toggleSidebar}
|
||||||
toggled={toggledSidebar}
|
toggled={toggledSidebar && !settings?.isMobile}
|
||||||
existingChats={chatSessions}
|
existingChats={chatSessions}
|
||||||
currentChatSession={selectedChatSession}
|
currentChatSession={selectedChatSession}
|
||||||
folders={folders}
|
folders={folders}
|
||||||
openedFolders={openedFolders}
|
openedFolders={openedFolders}
|
||||||
|
removeToggle={removeToggle}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ref={masterFlexboxRef} className="flex w-full overflow-x-hidden">
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
ref={masterFlexboxRef}
|
||||||
|
className="flex h-full w-full overflow-x-hidden"
|
||||||
|
>
|
||||||
{popup}
|
{popup}
|
||||||
{currentFeedback && (
|
|
||||||
<FeedbackModal
|
|
||||||
feedbackType={currentFeedback[0]}
|
|
||||||
onClose={() => setCurrentFeedback(null)}
|
|
||||||
onSubmit={({ message, predefinedFeedback }) => {
|
|
||||||
onFeedback(
|
|
||||||
currentFeedback[1],
|
|
||||||
currentFeedback[0],
|
|
||||||
message,
|
|
||||||
predefinedFeedback
|
|
||||||
);
|
|
||||||
setCurrentFeedback(null);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{sharingModalVisible && chatSessionIdRef.current !== null && (
|
<div className="flex h-full flex-col w-full">
|
||||||
<ShareChatSessionModal
|
|
||||||
chatSessionId={chatSessionIdRef.current}
|
|
||||||
existingSharedStatus={chatSessionSharedStatus}
|
|
||||||
onClose={() => setSharingModalVisible(false)}
|
|
||||||
onShare={(shared) =>
|
|
||||||
setChatSessionSharedStatus(
|
|
||||||
shared
|
|
||||||
? ChatSessionSharedStatus.Public
|
|
||||||
: ChatSessionSharedStatus.Private
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex h-[calc(100dvh)] flex-col w-full">
|
|
||||||
{liveAssistant && (
|
{liveAssistant && (
|
||||||
<FunctionalHeader
|
<FunctionalHeader
|
||||||
page="chat"
|
page="chat"
|
||||||
@@ -1200,7 +1209,7 @@ export function ChatPage({
|
|||||||
? setSharingModalVisible
|
? setSharingModalVisible
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
showSidebar={showDocSidebar}
|
toggleSidebar={toggleSidebar}
|
||||||
user={user}
|
user={user}
|
||||||
currentChatSession={selectedChatSession}
|
currentChatSession={selectedChatSession}
|
||||||
/>
|
/>
|
||||||
@@ -1226,6 +1235,7 @@ export function ChatPage({
|
|||||||
<Dropzone onDrop={handleImageUpload} noClick>
|
<Dropzone onDrop={handleImageUpload} noClick>
|
||||||
{({ getRootProps }) => (
|
{({ getRootProps }) => (
|
||||||
<div className="flex h-full w-full">
|
<div className="flex h-full w-full">
|
||||||
|
{!settings?.isMobile && (
|
||||||
<div
|
<div
|
||||||
style={{ transition: "width 0.30s ease-out" }}
|
style={{ transition: "width 0.30s ease-out" }}
|
||||||
className={`
|
className={`
|
||||||
@@ -1240,8 +1250,10 @@ export function ChatPage({
|
|||||||
${toggledSidebar ? "w-[250px]" : "w-[0px]"}
|
${toggledSidebar ? "w-[250px]" : "w-[0px]"}
|
||||||
`}
|
`}
|
||||||
></div>
|
></div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`h-full w-full relative flex-auto transition-margin duration-300 overflow-x-auto pb-[140px]`}
|
className={`h-full w-full relative flex-auto transition-margin duration-300 overflow-x-auto mobile:pb-12 desktop:pb-[140px]`}
|
||||||
{...getRootProps()}
|
{...getRootProps()}
|
||||||
>
|
>
|
||||||
{/* <input {...getInputProps()} /> */}
|
{/* <input {...getInputProps()} /> */}
|
||||||
@@ -1263,12 +1275,13 @@ export function ChatPage({
|
|||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
"mt-4 -ml-4 w-full mx-auto " +
|
"mt-4 -ml-4 w-full mx-auto " +
|
||||||
"absolute top-12 left-0 " +
|
"absolute mobile:top-0 desktop:top-12 left-0" +
|
||||||
(hasPerformedInitialScroll ? "" : "invisible")
|
(hasPerformedInitialScroll ? "" : "invisible")
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{messageHistory.map((message, i) => {
|
{messageHistory.map((message, i) => {
|
||||||
const messageMap = completeMessageDetail.messageMap;
|
const messageMap =
|
||||||
|
completeMessageDetail.messageMap;
|
||||||
const messageReactComponentKey = `${i}-${completeMessageDetail.sessionId}`;
|
const messageReactComponentKey = `${i}-${completeMessageDetail.sessionId}`;
|
||||||
if (message.type === "user") {
|
if (message.type === "user") {
|
||||||
const parentMessage = message.parentMessageId
|
const parentMessage = message.parentMessageId
|
||||||
@@ -1375,7 +1388,8 @@ export function ChatPage({
|
|||||||
message
|
message
|
||||||
)}
|
)}
|
||||||
toolCall={
|
toolCall={
|
||||||
message.toolCalls && message.toolCalls[0]
|
message.toolCalls &&
|
||||||
|
message.toolCalls[0]
|
||||||
}
|
}
|
||||||
isComplete={
|
isComplete={
|
||||||
i !== messageHistory.length - 1 ||
|
i !== messageHistory.length - 1 ||
|
||||||
@@ -1409,7 +1423,8 @@ export function ChatPage({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
previousMessage.messageId === null
|
previousMessage.messageId ===
|
||||||
|
null
|
||||||
) {
|
) {
|
||||||
setPopup({
|
setPopup({
|
||||||
type: "error",
|
type: "error",
|
||||||
@@ -1524,10 +1539,6 @@ export function ChatPage({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Some padding at the bottom so the search bar has space at the bottom to not cover the last message*/}
|
|
||||||
<div ref={endPaddingRef} className="h-[95px]" />
|
|
||||||
<div ref={endDivRef}></div>
|
|
||||||
|
|
||||||
{currentPersona &&
|
{currentPersona &&
|
||||||
currentPersona.starter_messages &&
|
currentPersona.starter_messages &&
|
||||||
currentPersona.starter_messages.length > 0 &&
|
currentPersona.starter_messages.length > 0 &&
|
||||||
@@ -1566,6 +1577,8 @@ export function ChatPage({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{/* Some padding at the bottom so the search bar has space at the bottom to not cover the last message*/}
|
||||||
|
<div ref={endPaddingRef} className="h-[95px]" />
|
||||||
<div ref={endDivRef} />
|
<div ref={endDivRef} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1627,9 +1640,12 @@ export function ChatPage({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<FixedLogo />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<DocumentSidebar
|
<DocumentSidebar
|
||||||
initialWidth={390}
|
initialWidth={350}
|
||||||
ref={innerSidebarElementRef}
|
ref={innerSidebarElementRef}
|
||||||
closeSidebar={() => setDocumentSelection(false)}
|
closeSidebar={() => setDocumentSelection(false)}
|
||||||
selectedMessage={aiMessage}
|
selectedMessage={aiMessage}
|
||||||
@@ -1641,9 +1657,6 @@ export function ChatPage({
|
|||||||
isLoading={isFetchingChatMessages}
|
isLoading={isFetchingChatMessages}
|
||||||
isOpen={documentSelection}
|
isOpen={documentSelection}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<FixedLogo />
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -2,44 +2,10 @@ import { DanswerDocument } from "@/lib/search/interfaces";
|
|||||||
import { Divider, Text } from "@tremor/react";
|
import { Divider, Text } from "@tremor/react";
|
||||||
import { ChatDocumentDisplay } from "./ChatDocumentDisplay";
|
import { ChatDocumentDisplay } from "./ChatDocumentDisplay";
|
||||||
import { usePopup } from "@/components/admin/connectors/Popup";
|
import { usePopup } from "@/components/admin/connectors/Popup";
|
||||||
import { FiAlertTriangle, FiFileText } from "react-icons/fi";
|
|
||||||
import { SelectedDocumentDisplay } from "./SelectedDocumentDisplay";
|
|
||||||
import { removeDuplicateDocs } from "@/lib/documentUtils";
|
import { removeDuplicateDocs } from "@/lib/documentUtils";
|
||||||
import { BasicSelectable } from "@/components/BasicClickable";
|
|
||||||
import { Message, RetrievalType } from "../interfaces";
|
import { Message, RetrievalType } from "../interfaces";
|
||||||
import { SIDEBAR_WIDTH } from "@/lib/constants";
|
|
||||||
import { HoverPopup } from "@/components/HoverPopup";
|
|
||||||
import { TbLayoutSidebarLeftExpand } from "react-icons/tb";
|
|
||||||
import { ForwardedRef, forwardRef } from "react";
|
import { ForwardedRef, forwardRef } from "react";
|
||||||
|
|
||||||
function SectionHeader({
|
|
||||||
name,
|
|
||||||
icon,
|
|
||||||
closeHeader,
|
|
||||||
}: {
|
|
||||||
name: string;
|
|
||||||
icon: React.FC<{ className: string }>;
|
|
||||||
closeHeader?: () => void;
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`w-full mt-3 flex text-lg text-emphasis font-medium flex mb-3.5 font-bold flex items-end`}
|
|
||||||
>
|
|
||||||
<div className="flex mt-auto justify-between w-full">
|
|
||||||
<p className="flex">
|
|
||||||
{icon({ className: "my-auto mr-1" })}
|
|
||||||
{name}
|
|
||||||
</p>
|
|
||||||
{closeHeader && (
|
|
||||||
<button onClick={() => closeHeader()}>
|
|
||||||
<TbLayoutSidebarLeftExpand size={24} />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DocumentSidebarProps {
|
interface DocumentSidebarProps {
|
||||||
closeSidebar: () => void;
|
closeSidebar: () => void;
|
||||||
selectedMessage: Message | null;
|
selectedMessage: Message | null;
|
||||||
@@ -71,8 +37,6 @@ export const DocumentSidebar = forwardRef<HTMLDivElement, DocumentSidebarProps>(
|
|||||||
) => {
|
) => {
|
||||||
const { popup, setPopup } = usePopup();
|
const { popup, setPopup } = usePopup();
|
||||||
|
|
||||||
const selectedMessageRetrievalType = selectedMessage?.retrievalType || null;
|
|
||||||
|
|
||||||
const selectedDocumentIds =
|
const selectedDocumentIds =
|
||||||
selectedDocuments?.map((document) => document.document_id) || [];
|
selectedDocuments?.map((document) => document.document_id) || [];
|
||||||
|
|
||||||
@@ -88,9 +52,7 @@ export const DocumentSidebar = forwardRef<HTMLDivElement, DocumentSidebarProps>(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`fixed inset-0 transition-opacity duration-300 z-50 bg-black/80 ${
|
className={`fixed inset-0 transition-opacity duration-300 z-50 bg-black/80 ${isOpen ? "opacity-100" : "opacity-0 pointer-events-none"}`}
|
||||||
isOpen ? "opacity-100" : "opacity-0 pointer-events-none"
|
|
||||||
}`}
|
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
if (e.target === e.currentTarget) {
|
if (e.target === e.currentTarget) {
|
||||||
closeSidebar();
|
closeSidebar();
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useContext, useEffect, useRef, useState } from "react";
|
||||||
import { FiPlusCircle, FiPlus, FiInfo, FiX } from "react-icons/fi";
|
import { FiPlusCircle, FiPlus, FiInfo, FiX } from "react-icons/fi";
|
||||||
import { ChatInputOption } from "./ChatInputOption";
|
import { ChatInputOption } from "./ChatInputOption";
|
||||||
import { Persona } from "@/app/admin/assistants/interfaces";
|
import { Persona } from "@/app/admin/assistants/interfaces";
|
||||||
@@ -29,6 +29,7 @@ import { DanswerDocument } from "@/lib/search/interfaces";
|
|||||||
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
|
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
|
||||||
import { Tooltip } from "@/components/tooltip/Tooltip";
|
import { Tooltip } from "@/components/tooltip/Tooltip";
|
||||||
import { Hoverable } from "@/components/Hoverable";
|
import { Hoverable } from "@/components/Hoverable";
|
||||||
|
import { SettingsContext } from "@/components/settings/SettingsProvider";
|
||||||
const MAX_INPUT_HEIGHT = 200;
|
const MAX_INPUT_HEIGHT = 200;
|
||||||
|
|
||||||
export function ChatInputBar({
|
export function ChatInputBar({
|
||||||
@@ -103,6 +104,7 @@ export function ChatInputBar({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const settings = useContext(SettingsContext);
|
||||||
|
|
||||||
const { llmProviders } = useChatContext();
|
const { llmProviders } = useChatContext();
|
||||||
const [_, llmName] = getFinalLLM(llmProviders, selectedAssistant, null);
|
const [_, llmName] = getFinalLLM(llmProviders, selectedAssistant, null);
|
||||||
@@ -213,9 +215,8 @@ export function ChatInputBar({
|
|||||||
className="
|
className="
|
||||||
w-[90%]
|
w-[90%]
|
||||||
shrink
|
shrink
|
||||||
bg-background
|
|
||||||
relative
|
relative
|
||||||
px-4
|
desktop:px-4
|
||||||
max-w-searchbar-max
|
max-w-searchbar-max
|
||||||
mx-auto
|
mx-auto
|
||||||
"
|
"
|
||||||
@@ -390,7 +391,7 @@ export function ChatInputBar({
|
|||||||
style={{ scrollbarWidth: "thin" }}
|
style={{ scrollbarWidth: "thin" }}
|
||||||
role="textarea"
|
role="textarea"
|
||||||
aria-multiline
|
aria-multiline
|
||||||
placeholder="Send a message or @ to tag an assistant..."
|
placeholder={`Send a message ${!settings?.isMobile ? "or @ to tag an assistant..." : ""}`}
|
||||||
value={message}
|
value={message}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
if (
|
if (
|
||||||
@@ -420,7 +421,9 @@ export function ChatInputBar({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
flexPriority="shrink"
|
||||||
position="top"
|
position="top"
|
||||||
|
mobilePosition="top-right"
|
||||||
>
|
>
|
||||||
<ChatInputOption
|
<ChatInputOption
|
||||||
flexPriority="shrink"
|
flexPriority="shrink"
|
||||||
@@ -450,16 +453,21 @@ export function ChatInputBar({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
position="top"
|
position="top"
|
||||||
|
// flexPriority="second"
|
||||||
>
|
>
|
||||||
<ChatInputOption
|
<ChatInputOption
|
||||||
flexPriority="second"
|
flexPriority="second"
|
||||||
name={getDisplayNameForModel(
|
name={
|
||||||
|
settings?.isMobile
|
||||||
|
? undefined
|
||||||
|
: getDisplayNameForModel(
|
||||||
llmOverrideManager.llmOverride.modelName ||
|
llmOverrideManager.llmOverride.modelName ||
|
||||||
(selectedAssistant
|
(selectedAssistant
|
||||||
? selectedAssistant.llm_model_version_override ||
|
? selectedAssistant.llm_model_version_override ||
|
||||||
llmName
|
llmName
|
||||||
: llmName)
|
: llmName)
|
||||||
)}
|
)
|
||||||
|
}
|
||||||
Icon={CpuIconSkeleton}
|
Icon={CpuIconSkeleton}
|
||||||
/>
|
/>
|
||||||
</Popup>
|
</Popup>
|
||||||
@@ -484,7 +492,7 @@ export function ChatInputBar({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute bottom-2.5 right-10">
|
<div className="absolute bottom-2.5 mobile:right-4 desktop:right-10">
|
||||||
<div
|
<div
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@@ -5,7 +5,7 @@ import { Popover } from "../../../components/popover/Popover";
|
|||||||
import { IconProps } from "@/components/icons/icons";
|
import { IconProps } from "@/components/icons/icons";
|
||||||
|
|
||||||
interface ChatInputOptionProps {
|
interface ChatInputOptionProps {
|
||||||
name: string;
|
name?: string;
|
||||||
Icon: ({ size, className }: IconProps) => JSX.Element;
|
Icon: ({ size, className }: IconProps) => JSX.Element;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
size?: number;
|
size?: number;
|
||||||
@@ -61,7 +61,7 @@ export const ChatInputOption: React.FC<ChatInputOptionProps> = ({
|
|||||||
rounded-md
|
rounded-md
|
||||||
${
|
${
|
||||||
flexPriority === "shrink" &&
|
flexPriority === "shrink" &&
|
||||||
"flex-shrink-[10000] flex-grow-0 flex-basis-auto min-w-[30px] whitespace-nowrap overflow-hidden"
|
"flex-shrink-100 flex-grow-0 flex-basis-auto min-w-[30px] whitespace-nowrap overflow-hidden"
|
||||||
}
|
}
|
||||||
${
|
${
|
||||||
flexPriority === "second" &&
|
flexPriority === "second" &&
|
||||||
@@ -76,7 +76,7 @@ export const ChatInputOption: React.FC<ChatInputOptionProps> = ({
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<Icon size={size} className="flex-none" />
|
<Icon size={size} className="flex-none" />
|
||||||
<span className="text-sm break-all line-clamp-1">{name}</span>
|
{name && <span className="text-sm break-all line-clamp-1">{name}</span>}
|
||||||
{isTooltipVisible && tooltipContent && (
|
{isTooltipVisible && tooltipContent && (
|
||||||
<div
|
<div
|
||||||
className="absolute z-10 p-2 text-sm text-white bg-black rounded shadow-lg"
|
className="absolute z-10 p-2 text-sm text-white bg-black rounded shadow-lg"
|
||||||
|
@@ -610,6 +610,7 @@ export async function useScrollonStream({
|
|||||||
endDivRef: RefObject<HTMLDivElement>;
|
endDivRef: RefObject<HTMLDivElement>;
|
||||||
distance: number;
|
distance: number;
|
||||||
debounce: number;
|
debounce: number;
|
||||||
|
mobile?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const preventScrollInterference = useRef<boolean>(false);
|
const preventScrollInterference = useRef<boolean>(false);
|
||||||
const preventScroll = useRef<boolean>(false);
|
const preventScroll = useRef<boolean>(false);
|
||||||
|
@@ -13,7 +13,7 @@ import {
|
|||||||
FiGlobe,
|
FiGlobe,
|
||||||
} from "react-icons/fi";
|
} from "react-icons/fi";
|
||||||
import { FeedbackType } from "../types";
|
import { FeedbackType } from "../types";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useContext, useEffect, useRef, useState } from "react";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import {
|
import {
|
||||||
DanswerDocument,
|
DanswerDocument,
|
||||||
@@ -58,6 +58,7 @@ import { ValidSources } from "@/lib/types";
|
|||||||
import { Tooltip } from "@/components/tooltip/Tooltip";
|
import { Tooltip } from "@/components/tooltip/Tooltip";
|
||||||
import { useMouseTracking } from "./hooks";
|
import { useMouseTracking } from "./hooks";
|
||||||
import { InternetSearchIcon } from "@/components/InternetSearchIcon";
|
import { InternetSearchIcon } from "@/components/InternetSearchIcon";
|
||||||
|
import { SettingsContext } from "@/components/settings/SettingsProvider";
|
||||||
|
|
||||||
const TOOLS_WITH_CUSTOM_HANDLING = [
|
const TOOLS_WITH_CUSTOM_HANDLING = [
|
||||||
SEARCH_TOOL_NAME,
|
SEARCH_TOOL_NAME,
|
||||||
@@ -162,6 +163,7 @@ export const AIMessage = ({
|
|||||||
|
|
||||||
const { isHovering, trackedElementRef, hoverElementRef } = useMouseTracking();
|
const { isHovering, trackedElementRef, hoverElementRef } = useMouseTracking();
|
||||||
|
|
||||||
|
const settings = useContext(SettingsContext);
|
||||||
// this is needed to give Prism a chance to load
|
// this is needed to give Prism a chance to load
|
||||||
if (!isReady) {
|
if (!isReady) {
|
||||||
return <div />;
|
return <div />;
|
||||||
@@ -226,7 +228,7 @@ export const AIMessage = ({
|
|||||||
return (
|
return (
|
||||||
<div ref={trackedElementRef} className={"py-5 px-2 lg:px-5 relative flex "}>
|
<div ref={trackedElementRef} className={"py-5 px-2 lg:px-5 relative flex "}>
|
||||||
<div className="mx-auto w-[90%] max-w-message-max">
|
<div className="mx-auto w-[90%] max-w-message-max">
|
||||||
<div className="xl:ml-8">
|
<div className=" mobile:ml-4 xl:ml-8">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<AssistantIcon
|
<AssistantIcon
|
||||||
size="small"
|
size="small"
|
||||||
@@ -430,7 +432,8 @@ export const AIMessage = ({
|
|||||||
<div className="mt-2 -mx-8 w-full mb-4 flex relative">
|
<div className="mt-2 -mx-8 w-full mb-4 flex relative">
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="px-8 flex gap-x-2">
|
<div className="px-8 flex gap-x-2">
|
||||||
{filteredDocs.length > 0 &&
|
{!settings?.isMobile &&
|
||||||
|
filteredDocs.length > 0 &&
|
||||||
filteredDocs.slice(0, 2).map((doc, ind) => (
|
filteredDocs.slice(0, 2).map((doc, ind) => (
|
||||||
<div
|
<div
|
||||||
key={doc.document_id}
|
key={doc.document_id}
|
||||||
@@ -530,10 +533,10 @@ export const AIMessage = ({
|
|||||||
<div
|
<div
|
||||||
ref={hoverElementRef}
|
ref={hoverElementRef}
|
||||||
className={`
|
className={`
|
||||||
absolute -bottom-2
|
absolute -bottom-4
|
||||||
invisible ${isHovering && "!visible"}
|
invisible ${(isHovering || settings?.isMobile) && "!visible"}
|
||||||
opacity-0 ${isHovering && "!opacity-100"}
|
opacity-0 ${(isHovering || settings?.isMobile) && "!opacity-100"}
|
||||||
translate-y-2 ${isHovering && "!translate-y-0"}
|
translate-y-2 ${(isHovering || settings?.isMobile) && "!translate-y-0"}
|
||||||
transition-transform duration-300 ease-in-out
|
transition-transform duration-300 ease-in-out
|
||||||
flex md:flex-row gap-x-0.5 bg-background-125/40 p-1.5 rounded-lg
|
flex md:flex-row gap-x-0.5 bg-background-125/40 p-1.5 rounded-lg
|
||||||
`}
|
`}
|
||||||
|
@@ -28,9 +28,12 @@ export function SkippedSearch({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="flex text-sm !pt-0 p-1">
|
<div className="flex text-sm !pt-0 p-1">
|
||||||
<FiBook className="my-auto mr-2" size={14} />
|
<FiBook className="my-auto flex-none mr-2" size={14} />
|
||||||
<div className="my-auto cursor-default">
|
<div className="my-auto cursor-default">
|
||||||
|
<span className="mobile:hidden">
|
||||||
The AI decided this query didn't need a search
|
The AI decided this query didn't need a search
|
||||||
|
</span>
|
||||||
|
<span className="desktop:hidden">No search</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="ml-auto my-auto" onClick={handleForceSearch}>
|
<div className="ml-auto my-auto" onClick={handleForceSearch}>
|
||||||
|
@@ -43,15 +43,14 @@ export default async function Page({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<InstantSSRAutoRefresh />
|
{/* <InstantSSRAutoRefresh /> */}
|
||||||
{shouldShowWelcomeModal && <WelcomeModal user={user} />}
|
{/* {shouldShowWelcomeModal && <WelcomeModal user={user} />}
|
||||||
{!shouldShowWelcomeModal && !shouldDisplaySourcesIncompleteModal && (
|
{!shouldShowWelcomeModal && !shouldDisplaySourcesIncompleteModal && (
|
||||||
<ApiKeyModal user={user} />
|
<ApiKeyModal user={user} />
|
||||||
)}
|
)}
|
||||||
{shouldDisplaySourcesIncompleteModal && (
|
{shouldDisplaySourcesIncompleteModal && (
|
||||||
<NoCompleteSourcesModal ccPairs={ccPairs} />
|
<NoCompleteSourcesModal ccPairs={ccPairs} />
|
||||||
)}
|
)} */}
|
||||||
|
|
||||||
<ChatProvider
|
<ChatProvider
|
||||||
value={{
|
value={{
|
||||||
user,
|
user,
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { ChatSession } from "../interfaces";
|
import { ChatSession } from "../interfaces";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, useContext } from "react";
|
||||||
import { deleteChatSession, renameChatSession } from "../lib";
|
import { deleteChatSession, renameChatSession } from "../lib";
|
||||||
import { DeleteChatModal } from "../modal/DeleteChatModal";
|
import { DeleteChatModal } from "../modal/DeleteChatModal";
|
||||||
import { BasicSelectable } from "@/components/BasicClickable";
|
import { BasicSelectable } from "@/components/BasicClickable";
|
||||||
@@ -19,12 +19,14 @@ import { DefaultDropdownElement } from "@/components/Dropdown";
|
|||||||
import { Popover } from "@/components/popover/Popover";
|
import { Popover } from "@/components/popover/Popover";
|
||||||
import { ShareChatSessionModal } from "../modal/ShareChatSessionModal";
|
import { ShareChatSessionModal } from "../modal/ShareChatSessionModal";
|
||||||
import { CHAT_SESSION_ID_KEY, FOLDER_ID_KEY } from "@/lib/drag/constants";
|
import { CHAT_SESSION_ID_KEY, FOLDER_ID_KEY } from "@/lib/drag/constants";
|
||||||
|
import { SettingsContext } from "@/components/settings/SettingsProvider";
|
||||||
|
|
||||||
export function ChatSessionDisplay({
|
export function ChatSessionDisplay({
|
||||||
chatSession,
|
chatSession,
|
||||||
search,
|
search,
|
||||||
isSelected,
|
isSelected,
|
||||||
skipGradient,
|
skipGradient,
|
||||||
|
closeSidebar,
|
||||||
}: {
|
}: {
|
||||||
chatSession: ChatSession;
|
chatSession: ChatSession;
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
@@ -32,6 +34,7 @@ export function ChatSessionDisplay({
|
|||||||
// needed when the parent is trying to apply some background effect
|
// needed when the parent is trying to apply some background effect
|
||||||
// if not set, the gradient will still be applied and cause weirdness
|
// if not set, the gradient will still be applied and cause weirdness
|
||||||
skipGradient?: boolean;
|
skipGradient?: boolean;
|
||||||
|
closeSidebar?: () => void;
|
||||||
}) {
|
}) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [isDeletionModalVisible, setIsDeletionModalVisible] = useState(false);
|
const [isDeletionModalVisible, setIsDeletionModalVisible] = useState(false);
|
||||||
@@ -62,6 +65,7 @@ export function ChatSessionDisplay({
|
|||||||
alert("Failed to rename chat session");
|
alert("Failed to rename chat session");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const settings = useContext(SettingsContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -92,6 +96,11 @@ export function ChatSessionDisplay({
|
|||||||
<Link
|
<Link
|
||||||
className="flex my-1 group relative"
|
className="flex my-1 group relative"
|
||||||
key={chatSession.id}
|
key={chatSession.id}
|
||||||
|
onClick={() => {
|
||||||
|
if (settings?.isMobile && closeSidebar) {
|
||||||
|
closeSidebar();
|
||||||
|
}
|
||||||
|
}}
|
||||||
href={
|
href={
|
||||||
search
|
search
|
||||||
? `/search?searchId=${chatSession.id}`
|
? `/search?searchId=${chatSession.id}`
|
||||||
|
@@ -48,6 +48,7 @@ interface HistorySidebarProps {
|
|||||||
openedFolders?: { [key: number]: boolean };
|
openedFolders?: { [key: number]: boolean };
|
||||||
toggleSidebar?: () => void;
|
toggleSidebar?: () => void;
|
||||||
toggled?: boolean;
|
toggled?: boolean;
|
||||||
|
removeToggle?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
|
export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
|
||||||
@@ -60,6 +61,7 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
|
|||||||
folders,
|
folders,
|
||||||
openedFolders,
|
openedFolders,
|
||||||
toggleSidebar,
|
toggleSidebar,
|
||||||
|
removeToggle,
|
||||||
},
|
},
|
||||||
ref: ForwardedRef<HTMLDivElement>
|
ref: ForwardedRef<HTMLDivElement>
|
||||||
) => {
|
) => {
|
||||||
@@ -101,11 +103,11 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
|
|||||||
transition-transform`}
|
transition-transform`}
|
||||||
>
|
>
|
||||||
<div className="max-w-full ml-3 mr-3 mt-2 flex flex gap-x-1 items-center my-auto text-text-700 text-xl">
|
<div className="max-w-full ml-3 mr-3 mt-2 flex flex gap-x-1 items-center my-auto text-text-700 text-xl">
|
||||||
<div className="mr-1 invisible mb-auto h-6 w-6">
|
<div className="mr-1 desktop:invisible mb-auto h-6 w-6">
|
||||||
<Logo height={24} width={24} />
|
<Logo height={24} width={24} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="invisible">
|
<div className="desktop:invisible">
|
||||||
{enterpriseSettings && enterpriseSettings.application_name ? (
|
{enterpriseSettings && enterpriseSettings.application_name ? (
|
||||||
<div>
|
<div>
|
||||||
<HeaderTitle>
|
<HeaderTitle>
|
||||||
@@ -119,13 +121,18 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
|
|||||||
<HeaderTitle>Danswer</HeaderTitle>
|
<HeaderTitle>Danswer</HeaderTitle>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{toggleSidebar && (
|
{toggleSidebar && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
delayDuration={0}
|
delayDuration={0}
|
||||||
content={toggled ? `Unpin sidebar` : "Pin sidebar"}
|
content={toggled ? `Unpin sidebar` : "Pin sidebar"}
|
||||||
>
|
>
|
||||||
<button className="my-auto ml-auto" onClick={toggleSidebar}>
|
<button className="my-auto ml-auto" onClick={toggleSidebar}>
|
||||||
{!toggled ? <RightToLineIcon /> : <LefToLineIcon />}
|
{!toggled && !combinedSettings.isMobile ? (
|
||||||
|
<RightToLineIcon />
|
||||||
|
) : (
|
||||||
|
<LefToLineIcon />
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
@@ -182,6 +189,7 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
|
|||||||
<div className="border-b border-border pb-4 mx-3" />
|
<div className="border-b border-border pb-4 mx-3" />
|
||||||
|
|
||||||
<PagesTab
|
<PagesTab
|
||||||
|
closeSidebar={removeToggle}
|
||||||
page={page}
|
page={page}
|
||||||
existingChats={existingChats}
|
existingChats={existingChats}
|
||||||
currentChatId={currentChatId}
|
currentChatId={currentChatId}
|
||||||
|
@@ -16,12 +16,14 @@ export function PagesTab({
|
|||||||
currentChatId,
|
currentChatId,
|
||||||
folders,
|
folders,
|
||||||
openedFolders,
|
openedFolders,
|
||||||
|
closeSidebar,
|
||||||
}: {
|
}: {
|
||||||
page: pageType;
|
page: pageType;
|
||||||
existingChats?: ChatSession[];
|
existingChats?: ChatSession[];
|
||||||
currentChatId?: number;
|
currentChatId?: number;
|
||||||
folders?: Folder[];
|
folders?: Folder[];
|
||||||
openedFolders?: { [key: number]: boolean };
|
openedFolders?: { [key: number]: boolean };
|
||||||
|
closeSidebar?: () => void;
|
||||||
}) {
|
}) {
|
||||||
const groupedChatSessions = existingChats
|
const groupedChatSessions = existingChats
|
||||||
? groupSessionsByDateRange(existingChats)
|
? groupSessionsByDateRange(existingChats)
|
||||||
@@ -58,7 +60,7 @@ export function PagesTab({
|
|||||||
const isHistoryEmpty = !existingChats || existingChats.length === 0;
|
const isHistoryEmpty = !existingChats || existingChats.length === 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-1 ml-3 relative miniscroll overflow-y-auto h-full">
|
<div className="mb-1 ml-3 relative miniscroll mobile:pb-40 overflow-y-auto h-full">
|
||||||
{folders && folders.length > 0 && (
|
{folders && folders.length > 0 && (
|
||||||
<div className="py-2 border-b border-border">
|
<div className="py-2 border-b border-border">
|
||||||
<div className="text-xs text-subtle flex pb-0.5 mb-1.5 mt-2 font-bold">
|
<div className="text-xs text-subtle flex pb-0.5 mb-1.5 mt-2 font-bold">
|
||||||
@@ -115,6 +117,7 @@ export function PagesTab({
|
|||||||
return (
|
return (
|
||||||
<div key={`${chat.id}-${chat.name}`}>
|
<div key={`${chat.id}-${chat.name}`}>
|
||||||
<ChatSessionDisplay
|
<ChatSessionDisplay
|
||||||
|
closeSidebar={closeSidebar}
|
||||||
search={page == "search"}
|
search={page == "search"}
|
||||||
chatSession={chat}
|
chatSession={chat}
|
||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
|
@@ -14,8 +14,8 @@ export default function FixedLogo() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="absolute flex z-40 left-2.5 top-2">
|
<div className="fixed pointer-events-none flex z-40 left-2.5 top-2">
|
||||||
<div className="max-w-[200px] flex items-center gap-x-1 my-auto">
|
<div className="max-w-[200px] mobile:hidden flex items-center gap-x-1 my-auto">
|
||||||
<div className="flex-none my-auto">
|
<div className="flex-none my-auto">
|
||||||
<Logo height={24} width={24} />
|
<Logo height={24} width={24} />
|
||||||
</div>
|
</div>
|
||||||
@@ -33,7 +33,7 @@ export default function FixedLogo() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute left-2.5 bottom-4">
|
<div className="mobile:hidden fixed left-2.5 bottom-4">
|
||||||
<FiSidebar />
|
<FiSidebar />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@@ -10,6 +10,7 @@ const ToggleSwitch = () => {
|
|||||||
const commandSymbol = KeyboardSymbol();
|
const commandSymbol = KeyboardSymbol();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const settings = useContext(SettingsContext);
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState(() => {
|
const [activeTab, setActiveTab] = useState(() => {
|
||||||
return pathname == "/search" ? "search" : "chat";
|
return pathname == "/search" ? "search" : "chat";
|
||||||
@@ -27,13 +28,17 @@ const ToggleSwitch = () => {
|
|||||||
const handleTabChange = (tab: string) => {
|
const handleTabChange = (tab: string) => {
|
||||||
setActiveTab(tab);
|
setActiveTab(tab);
|
||||||
localStorage.setItem("activeTab", tab);
|
localStorage.setItem("activeTab", tab);
|
||||||
|
if (settings?.isMobile) {
|
||||||
|
window.location.href = tab;
|
||||||
|
} else {
|
||||||
router.push(tab === "search" ? "/search" : "/chat");
|
router.push(tab === "search" ? "/search" : "/chat");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-gray-100 flex rounded-full p-1">
|
<div className="bg-gray-100 mobile:mt-8 flex rounded-full p-1">
|
||||||
<div
|
<div
|
||||||
className={`absolute top-1 bottom-1 ${
|
className={`absolute mobile:mt-8 top-1 bottom-1 ${
|
||||||
activeTab === "chat" ? "w-[45%]" : "w-[50%]"
|
activeTab === "chat" ? "w-[45%]" : "w-[50%]"
|
||||||
} bg-white rounded-full shadow ${
|
} bg-white rounded-full shadow ${
|
||||||
isInitialLoad ? "" : "transition-transform duration-300 ease-in-out"
|
isInitialLoad ? "" : "transition-transform duration-300 ease-in-out"
|
||||||
@@ -116,18 +121,22 @@ export default function FunctionalWrapper({
|
|||||||
|
|
||||||
const [toggledSidebar, setToggledSidebar] = useState(initiallyToggled);
|
const [toggledSidebar, setToggledSidebar] = useState(initiallyToggled);
|
||||||
|
|
||||||
const toggle = () => {
|
const toggle = (value?: boolean) => {
|
||||||
setToggledSidebar((toggledSidebar) => !toggledSidebar);
|
if (value !== undefined) {
|
||||||
|
setToggledSidebar(value);
|
||||||
|
} else {
|
||||||
|
setToggledSidebar((prevState) => !prevState);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{(!settings ||
|
{(!settings ||
|
||||||
(settings.search_page_enabled && settings.chat_page_enabled)) && (
|
(settings.search_page_enabled && settings.chat_page_enabled)) && (
|
||||||
<div className="z-[40] flex fixed top-4 left-1/2 transform -translate-x-1/2">
|
<div className="mobile:hidden z-30 flex fixed top-4 left-1/2 transform -translate-x-1/2">
|
||||||
<div
|
<div
|
||||||
style={{ transition: "width 0.30s ease-out" }}
|
style={{ transition: "width 0.30s ease-out" }}
|
||||||
className={`flex-none overflow-y-hidden bg-background-100 transition-all bg-opacity-80duration-300 ease-in-out h-full
|
className={`flex-none overflow-y-hidden bg-background-100 transition-all bg-opacity-80 duration-300 ease-in-out h-full
|
||||||
${toggledSidebar ? "w-[250px] " : "w-[0px]"}`}
|
${toggledSidebar ? "w-[250px] " : "w-[0px]"}`}
|
||||||
/>
|
/>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
@@ -136,7 +145,7 @@ export default function FunctionalWrapper({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="absolute left-0 top-0 w-full h-full">
|
<div className="overscroll-y-contain overflow-y-scroll overscroll-contain left-0 top-0 w-full h-svh">
|
||||||
{content(toggledSidebar, toggle)}
|
{content(toggledSidebar, toggle)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@@ -111,6 +111,10 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.prevent-scroll {
|
||||||
|
overscroll-behavior-y: none;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
overscroll-behavior-y: none;
|
overscroll-behavior-y: none;
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,7 @@ import { SettingsProvider } from "@/components/settings/SettingsProvider";
|
|||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
import { buildClientUrl } from "@/lib/utilsSS";
|
import { buildClientUrl } from "@/lib/utilsSS";
|
||||||
import { Inter } from "next/font/google";
|
import { Inter } from "next/font/google";
|
||||||
|
import Head from "next/head";
|
||||||
|
|
||||||
const inter = Inter({
|
const inter = Inter({
|
||||||
subsets: ["latin"],
|
subsets: ["latin"],
|
||||||
@@ -41,6 +42,13 @@ export default async function RootLayout({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
<Head>
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, interactive-widget=resizes-content"
|
||||||
|
/>
|
||||||
|
</Head>
|
||||||
|
|
||||||
{CUSTOM_ANALYTICS_ENABLED && combinedSettings.customAnalyticsScript && (
|
{CUSTOM_ANALYTICS_ENABLED && combinedSettings.customAnalyticsScript && (
|
||||||
<head>
|
<head>
|
||||||
<script
|
<script
|
||||||
@@ -51,7 +59,9 @@ export default async function RootLayout({
|
|||||||
/>
|
/>
|
||||||
</head>
|
</head>
|
||||||
)}
|
)}
|
||||||
<body
|
|
||||||
|
<body className="relative">
|
||||||
|
<div
|
||||||
className={`${inter.variable} font-sans text-default bg-background ${
|
className={`${inter.variable} font-sans text-default bg-background ${
|
||||||
// TODO: remove this once proper dark mode exists
|
// TODO: remove this once proper dark mode exists
|
||||||
process.env.THEME_IS_DARK?.toLowerCase() === "true" ? "dark" : ""
|
process.env.THEME_IS_DARK?.toLowerCase() === "true" ? "dark" : ""
|
||||||
@@ -60,6 +70,7 @@ export default async function RootLayout({
|
|||||||
<SettingsProvider settings={combinedSettings}>
|
<SettingsProvider settings={combinedSettings}>
|
||||||
{children}
|
{children}
|
||||||
</SettingsProvider>
|
</SettingsProvider>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
@@ -187,7 +187,6 @@ export default async function Home() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header user={user} />
|
|
||||||
<HealthCheckBanner secondsUntilExpiration={secondsUntilExpiration} />
|
<HealthCheckBanner secondsUntilExpiration={secondsUntilExpiration} />
|
||||||
{shouldShowWelcomeModal && <WelcomeModal user={user} />}
|
{shouldShowWelcomeModal && <WelcomeModal user={user} />}
|
||||||
|
|
||||||
|
@@ -54,7 +54,7 @@ export function UserDropdown({
|
|||||||
onClick={() => setUserInfoVisible(!userInfoVisible)}
|
onClick={() => setUserInfoVisible(!userInfoVisible)}
|
||||||
className="flex cursor-pointer"
|
className="flex cursor-pointer"
|
||||||
>
|
>
|
||||||
<div className="my-auto bg-background-strong ring-2 ring-transparent group-hover:ring-background-300/50 transition-ring duration-150 rounded-lg inline-block flex-none px-2 text-base font-normal">
|
<div className="my-auto bg-background-strong ring-2 ring-transparent group-hover:ring-background-300/50 transition-ring duration-150 rounded-lg inline-block flex-none px-2 text-base ">
|
||||||
{user && user.email ? user.email[0].toUpperCase() : "A"}
|
{user && user.email ? user.email[0].toUpperCase() : "A"}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,9 +84,10 @@ export function UserDropdown({
|
|||||||
<>
|
<>
|
||||||
<Link
|
<Link
|
||||||
href="/admin/indexing/status"
|
href="/admin/indexing/status"
|
||||||
className="flex py-3 px-4 cursor-pointer rounded hover:bg-hover-light"
|
className="flex py-3 px-4 cursor-pointer !
|
||||||
|
rounded hover:bg-hover-light"
|
||||||
>
|
>
|
||||||
<LightSettingsIcon className="h-5 w-5 text-text-200est0 my-auto mr-2" />
|
<LightSettingsIcon className="h-5 w-5 my-auto mr-2" />
|
||||||
Admin Panel
|
Admin Panel
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
|
@@ -18,20 +18,19 @@ import { SettingsContext } from "../settings/SettingsProvider";
|
|||||||
import { pageType } from "@/app/chat/sessionSidebar/types";
|
import { pageType } from "@/app/chat/sessionSidebar/types";
|
||||||
|
|
||||||
export default function FunctionalHeader({
|
export default function FunctionalHeader({
|
||||||
showSidebar,
|
|
||||||
user,
|
user,
|
||||||
page,
|
page,
|
||||||
currentChatSession,
|
currentChatSession,
|
||||||
setSharingModalVisible,
|
setSharingModalVisible,
|
||||||
|
toggleSidebar,
|
||||||
}: {
|
}: {
|
||||||
page: pageType;
|
page: pageType;
|
||||||
showSidebar: boolean;
|
|
||||||
user: User | null;
|
user: User | null;
|
||||||
currentChatSession?: ChatSession | null | undefined;
|
currentChatSession?: ChatSession | null | undefined;
|
||||||
setSharingModalVisible?: (value: SetStateAction<boolean>) => void;
|
setSharingModalVisible?: (value: SetStateAction<boolean>) => void;
|
||||||
|
toggleSidebar: () => void;
|
||||||
}) {
|
}) {
|
||||||
const combinedSettings = useContext(SettingsContext);
|
const combinedSettings = useContext(SettingsContext);
|
||||||
const settings = combinedSettings?.settings;
|
|
||||||
const enterpriseSettings = combinedSettings?.enterpriseSettings;
|
const enterpriseSettings = combinedSettings?.enterpriseSettings;
|
||||||
|
|
||||||
const commandSymbol = KeyboardSymbol();
|
const commandSymbol = KeyboardSymbol();
|
||||||
@@ -60,12 +59,15 @@ export default function FunctionalHeader({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
<div className="pb-6 left-0 sticky top-0 z-10 w-full relative flex">
|
<div className="pb-6 left-0 sticky top-0 z-20 w-full relative flex">
|
||||||
<div className="mt-2 mx-4 text-text-700 flex w-full">
|
<div className="mt-2 mx-4 text-text-700 flex w-full">
|
||||||
<div className="absolute z-[100] my-auto flex items-center text-xl font-bold">
|
<div className="absolute z-[100] my-auto flex items-center text-xl font-bold">
|
||||||
<div className="pt-[2px] invisible mb-auto">
|
<button
|
||||||
|
onClick={() => toggleSidebar()}
|
||||||
|
className="pt-[2px] desktop:invisible mb-auto"
|
||||||
|
>
|
||||||
<FiSidebar size={20} />
|
<FiSidebar size={20} />
|
||||||
</div>
|
</button>
|
||||||
<div className="invisible break-words inline-block w-fit ml-2 text-text-700 text-xl">
|
<div className="invisible break-words inline-block w-fit ml-2 text-text-700 text-xl">
|
||||||
<div className="max-w-[200px]">
|
<div className="max-w-[200px]">
|
||||||
{enterpriseSettings && enterpriseSettings.application_name ? (
|
{enterpriseSettings && enterpriseSettings.application_name ? (
|
||||||
@@ -86,7 +88,7 @@ export default function FunctionalHeader({
|
|||||||
{page == "chat" && (
|
{page == "chat" && (
|
||||||
<Tooltip delayDuration={1000} content={`${commandSymbol}U`}>
|
<Tooltip delayDuration={1000} content={`${commandSymbol}U`}>
|
||||||
<Link
|
<Link
|
||||||
className="my-auto"
|
className="mobile:hidden my-auto"
|
||||||
href={
|
href={
|
||||||
`/${page}` +
|
`/${page}` +
|
||||||
(NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA &&
|
(NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA &&
|
||||||
@@ -107,15 +109,29 @@ export default function FunctionalHeader({
|
|||||||
{setSharingModalVisible && (
|
{setSharingModalVisible && (
|
||||||
<div
|
<div
|
||||||
onClick={() => setSharingModalVisible(true)}
|
onClick={() => setSharingModalVisible(true)}
|
||||||
className="my-auto rounded cursor-pointer hover:bg-hover-light"
|
className="mobile:hidden my-auto rounded cursor-pointer hover:bg-hover-light"
|
||||||
>
|
>
|
||||||
<FiShare2 size="18" />
|
<FiShare2 size="18" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex my-auto">
|
<div className="mobile:hidden flex my-auto">
|
||||||
<UserDropdown user={user} />
|
<UserDropdown user={user} />
|
||||||
</div>
|
</div>
|
||||||
|
<Link
|
||||||
|
className="desktop:hidden my-auto"
|
||||||
|
href={
|
||||||
|
`/${page}` +
|
||||||
|
(NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA &&
|
||||||
|
currentChatSession
|
||||||
|
? `?assistantId=${currentChatSession.persona_id}`
|
||||||
|
: "")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="cursor-pointer ml-2 flex-none text-text-700 hover:text-text-600 transition-colors duration-300">
|
||||||
|
<NewChatIcon size={20} />
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -5,18 +5,22 @@ interface UseSidebarVisibilityProps {
|
|||||||
sidebarElementRef: React.RefObject<HTMLElement>;
|
sidebarElementRef: React.RefObject<HTMLElement>;
|
||||||
showDocSidebar: boolean;
|
showDocSidebar: boolean;
|
||||||
setShowDocSidebar: Dispatch<SetStateAction<boolean>>;
|
setShowDocSidebar: Dispatch<SetStateAction<boolean>>;
|
||||||
|
mobile?: boolean;
|
||||||
|
setToggled?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSidebarVisibility = ({
|
export const useSidebarVisibility = ({
|
||||||
toggledSidebar,
|
toggledSidebar,
|
||||||
sidebarElementRef,
|
sidebarElementRef,
|
||||||
setShowDocSidebar,
|
setShowDocSidebar,
|
||||||
|
setToggled,
|
||||||
showDocSidebar,
|
showDocSidebar,
|
||||||
|
mobile,
|
||||||
}: UseSidebarVisibilityProps) => {
|
}: UseSidebarVisibilityProps) => {
|
||||||
const xPosition = useRef(0);
|
const xPosition = useRef(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleMouseMove = (event: MouseEvent) => {
|
const handleEvent = (event: MouseEvent) => {
|
||||||
const currentXPosition = event.clientX;
|
const currentXPosition = event.clientX;
|
||||||
xPosition.current = currentXPosition;
|
xPosition.current = currentXPosition;
|
||||||
|
|
||||||
@@ -28,11 +32,20 @@ export const useSidebarVisibility = ({
|
|||||||
currentXPosition <= sidebarRect.right &&
|
currentXPosition <= sidebarRect.right &&
|
||||||
event.clientY >= sidebarRect.top &&
|
event.clientY >= sidebarRect.top &&
|
||||||
event.clientY <= sidebarRect.bottom;
|
event.clientY <= sidebarRect.bottom;
|
||||||
|
|
||||||
const sidebarStyle = window.getComputedStyle(sidebarElementRef.current);
|
const sidebarStyle = window.getComputedStyle(sidebarElementRef.current);
|
||||||
const isVisible = sidebarStyle.opacity !== "0";
|
const isVisible = sidebarStyle.opacity !== "0";
|
||||||
if (isWithinSidebar && isVisible) {
|
if (isWithinSidebar && isVisible) {
|
||||||
|
if (!mobile) {
|
||||||
setShowDocSidebar(true);
|
setShowDocSidebar(true);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mobile && !isWithinSidebar && setToggled) {
|
||||||
|
setToggled();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
currentXPosition > 100 &&
|
currentXPosition > 100 &&
|
||||||
showDocSidebar &&
|
showDocSidebar &&
|
||||||
@@ -41,17 +54,19 @@ export const useSidebarVisibility = ({
|
|||||||
) {
|
) {
|
||||||
setShowDocSidebar(false);
|
setShowDocSidebar(false);
|
||||||
} else if (currentXPosition < 100 && !showDocSidebar) {
|
} else if (currentXPosition < 100 && !showDocSidebar) {
|
||||||
|
if (!mobile) {
|
||||||
setShowDocSidebar(true);
|
setShowDocSidebar(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("mousemove", handleMouseMove);
|
document.addEventListener("mousemove", handleEvent);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("mousemove", handleMouseMove);
|
document.removeEventListener("mousemove", handleEvent);
|
||||||
};
|
};
|
||||||
}, [showDocSidebar, toggledSidebar, sidebarElementRef]);
|
}, [showDocSidebar, toggledSidebar, sidebarElementRef, mobile]);
|
||||||
|
|
||||||
return { showDocSidebar };
|
return { showDocSidebar };
|
||||||
};
|
};
|
||||||
|
@@ -3,7 +3,6 @@
|
|||||||
import { User } from "@/lib/types";
|
import { User } from "@/lib/types";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import React, { useContext } from "react";
|
import React, { useContext } from "react";
|
||||||
import { FiMessageSquare, FiSearch } from "react-icons/fi";
|
|
||||||
import { HeaderWrapper } from "./HeaderWrapper";
|
import { HeaderWrapper } from "./HeaderWrapper";
|
||||||
import { SettingsContext } from "../settings/SettingsProvider";
|
import { SettingsContext } from "../settings/SettingsProvider";
|
||||||
import { UserDropdown } from "../UserDropdown";
|
import { UserDropdown } from "../UserDropdown";
|
||||||
|
@@ -6,4 +6,5 @@
|
|||||||
|
|
||||||
.PopoverContent[data-side="top"] {
|
.PopoverContent[data-side="top"] {
|
||||||
transform: translateY(var(--radix-popover-trigger-height) px);
|
transform: translateY(var(--radix-popover-trigger-height) px);
|
||||||
|
font-family: sans-serif;
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,12 @@
|
|||||||
import React, { useState, useRef, useEffect, ReactNode } from "react";
|
"use client";
|
||||||
|
import React, {
|
||||||
|
useState,
|
||||||
|
useRef,
|
||||||
|
useEffect,
|
||||||
|
ReactNode,
|
||||||
|
useContext,
|
||||||
|
} from "react";
|
||||||
|
import { SettingsContext } from "../settings/SettingsProvider";
|
||||||
|
|
||||||
interface PopupProps {
|
interface PopupProps {
|
||||||
children: JSX.Element;
|
children: JSX.Element;
|
||||||
@@ -6,9 +14,11 @@ interface PopupProps {
|
|||||||
close: () => void,
|
close: () => void,
|
||||||
ref?: React.RefObject<HTMLDivElement>
|
ref?: React.RefObject<HTMLDivElement>
|
||||||
) => ReactNode;
|
) => ReactNode;
|
||||||
position?: "top" | "bottom" | "left" | "right";
|
position?: "top" | "bottom" | "left" | "right" | "top-right";
|
||||||
removePadding?: boolean;
|
removePadding?: boolean;
|
||||||
tab?: boolean;
|
tab?: boolean;
|
||||||
|
flexPriority?: "shrink" | "stiff" | "second";
|
||||||
|
mobilePosition?: "top" | "bottom" | "left" | "right" | "top-right";
|
||||||
}
|
}
|
||||||
|
|
||||||
const Popup: React.FC<PopupProps> = ({
|
const Popup: React.FC<PopupProps> = ({
|
||||||
@@ -17,6 +27,8 @@ const Popup: React.FC<PopupProps> = ({
|
|||||||
tab,
|
tab,
|
||||||
removePadding,
|
removePadding,
|
||||||
position = "top",
|
position = "top",
|
||||||
|
flexPriority,
|
||||||
|
mobilePosition,
|
||||||
}) => {
|
}) => {
|
||||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||||
const triggerRef = useRef<HTMLDivElement>(null);
|
const triggerRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -51,6 +63,8 @@ const Popup: React.FC<PopupProps> = ({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const settings = useContext(SettingsContext);
|
||||||
|
|
||||||
const getPopupStyle = (): React.CSSProperties => {
|
const getPopupStyle = (): React.CSSProperties => {
|
||||||
if (!triggerRef.current) return {};
|
if (!triggerRef.current) return {};
|
||||||
|
|
||||||
@@ -60,7 +74,11 @@ const Popup: React.FC<PopupProps> = ({
|
|||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (position) {
|
const currentPosition = settings?.isMobile
|
||||||
|
? mobilePosition || position
|
||||||
|
: position;
|
||||||
|
|
||||||
|
switch (currentPosition) {
|
||||||
case "bottom":
|
case "bottom":
|
||||||
popupStyle.top = `${triggerRect.height + 5}px`;
|
popupStyle.top = `${triggerRect.height + 5}px`;
|
||||||
popupStyle.left = "50%";
|
popupStyle.left = "50%";
|
||||||
@@ -76,9 +94,14 @@ const Popup: React.FC<PopupProps> = ({
|
|||||||
popupStyle.left = `${triggerRect.width + 5}px`;
|
popupStyle.left = `${triggerRect.width + 5}px`;
|
||||||
popupStyle.transform = "translateY(-50%)";
|
popupStyle.transform = "translateY(-50%)";
|
||||||
break;
|
break;
|
||||||
|
case "top-right":
|
||||||
|
popupStyle.bottom = `${triggerRect.height + 5}px`;
|
||||||
|
popupStyle.right = "0";
|
||||||
|
popupStyle.right = "auto";
|
||||||
|
break;
|
||||||
default: // top
|
default: // top
|
||||||
popupStyle.bottom = `${triggerRect.height + 5}px`;
|
popupStyle.bottom = `${triggerRect.height + 5}px`;
|
||||||
popupStyle.left = `${triggerRect.width + 50}px`;
|
popupStyle.left = "50%";
|
||||||
popupStyle.transform = "translateX(-50%)";
|
popupStyle.transform = "translateX(-50%)";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +109,19 @@ const Popup: React.FC<PopupProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative inline-block">
|
<div
|
||||||
|
className={`relative inline-block
|
||||||
|
${
|
||||||
|
flexPriority === "shrink" &&
|
||||||
|
"flex-shrink-100 flex-grow-0 flex-basis-auto min-w-[30px] whitespace-nowrap "
|
||||||
|
}
|
||||||
|
${
|
||||||
|
flexPriority === "second" &&
|
||||||
|
"flex-shrink flex-basis-0 min-w-[30px] whitespace-nowrap "
|
||||||
|
}
|
||||||
|
${flexPriority === "stiff" && "flex-none whitespace-nowrap "}
|
||||||
|
`}
|
||||||
|
>
|
||||||
<div ref={triggerRef} onClick={togglePopup} className="cursor-pointer">
|
<div ref={triggerRef} onClick={togglePopup} className="cursor-pointer">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
@@ -96,7 +131,7 @@ const Popup: React.FC<PopupProps> = ({
|
|||||||
ref={popupRef}
|
ref={popupRef}
|
||||||
className={`absolute bg-white border border-gray-200 rounded-lg shadow-lg ${
|
className={`absolute bg-white border border-gray-200 rounded-lg shadow-lg ${
|
||||||
!removePadding && "p-4"
|
!removePadding && "p-4"
|
||||||
} ${tab ? " w-[400px] " : "min-w-[400px]"}`}
|
} ${!settings?.isMobile ? (tab ? "w-[400px] " : "min-w-[400px]") : "w-[250px]"}`}
|
||||||
style={getPopupStyle()}
|
style={getPopupStyle()}
|
||||||
>
|
>
|
||||||
{content(closePopup, contentRef)}
|
{content(closePopup, contentRef)}
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DanswerDocument,
|
DanswerDocument,
|
||||||
DocumentRelevance,
|
DocumentRelevance,
|
||||||
@@ -5,7 +7,7 @@ import {
|
|||||||
SearchDanswerDocument,
|
SearchDanswerDocument,
|
||||||
} from "@/lib/search/interfaces";
|
} from "@/lib/search/interfaces";
|
||||||
import { DocumentFeedbackBlock } from "./DocumentFeedbackBlock";
|
import { DocumentFeedbackBlock } from "./DocumentFeedbackBlock";
|
||||||
import { useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
import { PopupSpec } from "../admin/connectors/Popup";
|
import { PopupSpec } from "../admin/connectors/Popup";
|
||||||
import { DocumentUpdatedAtBadge } from "./DocumentUpdatedAtBadge";
|
import { DocumentUpdatedAtBadge } from "./DocumentUpdatedAtBadge";
|
||||||
import { SourceIcon } from "../SourceIcon";
|
import { SourceIcon } from "../SourceIcon";
|
||||||
@@ -15,6 +17,7 @@ import { BookIcon, CheckmarkIcon, LightBulbIcon, XIcon } from "../icons/icons";
|
|||||||
import { FaStar } from "react-icons/fa";
|
import { FaStar } from "react-icons/fa";
|
||||||
import { FiTag } from "react-icons/fi";
|
import { FiTag } from "react-icons/fi";
|
||||||
import { DISABLE_AGENTIC_SEARCH } from "@/lib/constants";
|
import { DISABLE_AGENTIC_SEARCH } from "@/lib/constants";
|
||||||
|
import { SettingsContext } from "../settings/SettingsProvider";
|
||||||
|
|
||||||
export const buildDocumentSummaryDisplay = (
|
export const buildDocumentSummaryDisplay = (
|
||||||
matchHighlights: string[],
|
matchHighlights: string[],
|
||||||
@@ -178,11 +181,12 @@ export const DocumentDisplay = ({
|
|||||||
const [alternativeToggled, setAlternativeToggled] = useState(false);
|
const [alternativeToggled, setAlternativeToggled] = useState(false);
|
||||||
const relevance_explanation =
|
const relevance_explanation =
|
||||||
document.relevance_explanation ?? additional_relevance?.content;
|
document.relevance_explanation ?? additional_relevance?.content;
|
||||||
|
const settings = useContext(SettingsContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={document.semantic_identifier}
|
key={document.semantic_identifier}
|
||||||
className={`text-sm border-b border-border transition-all duration-500
|
className={`text-sm mobile:ml-4 border-b border-border transition-all duration-500
|
||||||
${hide ? "transform translate-x-full opacity-0" : ""}
|
${hide ? "transform translate-x-full opacity-0" : ""}
|
||||||
${!hide ? "pt-3" : "border-transparent"} relative`}
|
${!hide ? "pt-3" : "border-transparent"} relative`}
|
||||||
onMouseEnter={() => setIsHovered(true)}
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
@@ -234,7 +238,7 @@ export const DocumentDisplay = ({
|
|||||||
|
|
||||||
{(contentEnriched || additional_relevance) &&
|
{(contentEnriched || additional_relevance) &&
|
||||||
relevance_explanation &&
|
relevance_explanation &&
|
||||||
(isHovered || alternativeToggled) && (
|
(isHovered || alternativeToggled || settings?.isMobile) && (
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setAlternativeToggled(
|
setAlternativeToggled(
|
||||||
@@ -242,7 +246,9 @@ export const DocumentDisplay = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<LightBulbIcon className="text-blue-400 h-4 w-4 cursor-pointer" />
|
<LightBulbIcon
|
||||||
|
className={`${settings?.isMobile && alternativeToggled ? "text-green-600" : "text-blue-600"} h-4 w-4 cursor-pointer`}
|
||||||
|
/>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -286,7 +292,7 @@ export const AgenticDocumentDisplay = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={document.semantic_identifier}
|
key={document.semantic_identifier}
|
||||||
className={`text-sm border-b border-border transition-all duration-500
|
className={`text-sm mobile:ml-4 border-b border-border transition-all duration-500
|
||||||
${!hide ? "transform translate-x-full opacity-0" : ""}
|
${!hide ? "transform translate-x-full opacity-0" : ""}
|
||||||
${hide ? "py-3" : "border-transparent"} relative`}
|
${hide ? "py-3" : "border-transparent"} relative`}
|
||||||
onMouseEnter={() => setIsHovered(true)}
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import React, { KeyboardEvent, ChangeEvent } from "react";
|
import React, { KeyboardEvent, ChangeEvent, useContext } from "react";
|
||||||
import { searchState } from "./SearchSection";
|
import { searchState } from "./SearchSection";
|
||||||
|
|
||||||
import { MagnifyingGlass } from "@phosphor-icons/react";
|
import { MagnifyingGlass } from "@phosphor-icons/react";
|
||||||
@@ -17,6 +17,7 @@ import { SendIcon } from "../icons/icons";
|
|||||||
import { Divider } from "@tremor/react";
|
import { Divider } from "@tremor/react";
|
||||||
import { CustomTooltip } from "../tooltip/CustomTooltip";
|
import { CustomTooltip } from "../tooltip/CustomTooltip";
|
||||||
import KeyboardSymbol from "@/lib/browserUtilities";
|
import KeyboardSymbol from "@/lib/browserUtilities";
|
||||||
|
import { SettingsContext } from "../settings/SettingsProvider";
|
||||||
|
|
||||||
export const AnimatedToggle = ({
|
export const AnimatedToggle = ({
|
||||||
isOn,
|
isOn,
|
||||||
@@ -139,6 +140,8 @@ export const FullSearchBar = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const settings = useContext(SettingsContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="
|
className="
|
||||||
@@ -198,13 +201,17 @@ export const FullSearchBar = ({
|
|||||||
|
|
||||||
{searchState == "reading" && (
|
{searchState == "reading" && (
|
||||||
<div key={"Reading"} className="mr-auto relative inline-block">
|
<div key={"Reading"} className="mr-auto relative inline-block">
|
||||||
<span className="loading-text">Reading Documents...</span>
|
<span className="loading-text">
|
||||||
|
Reading{settings?.isMobile ? "" : " Documents"}...
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{searchState == "analyzing" && (
|
{searchState == "analyzing" && (
|
||||||
<div key={"Generating"} className="mr-auto relative inline-block">
|
<div key={"Generating"} className="mr-auto relative inline-block">
|
||||||
<span className="loading-text">Generating Analysis...</span>
|
<span className="loading-text">
|
||||||
|
Generating{settings?.isMobile ? "" : " Analysis"}...
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@@ -23,7 +23,7 @@ import { useFilters, useObjectState } from "@/lib/hooks";
|
|||||||
import { questionValidationStreamed } from "@/lib/search/streamingQuestionValidation";
|
import { questionValidationStreamed } from "@/lib/search/streamingQuestionValidation";
|
||||||
import { Persona } from "@/app/admin/assistants/interfaces";
|
import { Persona } from "@/app/admin/assistants/interfaces";
|
||||||
import { computeAvailableFilters } from "@/lib/filters";
|
import { computeAvailableFilters } from "@/lib/filters";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { redirect, useRouter, useSearchParams } from "next/navigation";
|
||||||
import { SettingsContext } from "../settings/SettingsProvider";
|
import { SettingsContext } from "../settings/SettingsProvider";
|
||||||
import { HistorySidebar } from "@/app/chat/sessionSidebar/HistorySidebar";
|
import { HistorySidebar } from "@/app/chat/sessionSidebar/HistorySidebar";
|
||||||
import { ChatSession, SearchSession } from "@/app/chat/interfaces";
|
import { ChatSession, SearchSession } from "@/app/chat/interfaces";
|
||||||
@@ -339,7 +339,6 @@ export const SearchSection = ({
|
|||||||
if ((overrideMessage || query) == "") {
|
if ((overrideMessage || query) == "") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setSearchResponse;
|
|
||||||
setAgenticResults(agentic!);
|
setAgenticResults(agentic!);
|
||||||
resetInput();
|
resetInput();
|
||||||
setContentEnriched(false);
|
setContentEnriched(false);
|
||||||
@@ -456,6 +455,12 @@ export const SearchSection = ({
|
|||||||
};
|
};
|
||||||
}, [router]);
|
}, [router]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (settings?.isMobile) {
|
||||||
|
router.push("/chat");
|
||||||
|
}
|
||||||
|
}, [settings?.isMobile, router]);
|
||||||
|
|
||||||
const handleTransitionEnd = (e: React.TransitionEvent<HTMLDivElement>) => {
|
const handleTransitionEnd = (e: React.TransitionEvent<HTMLDivElement>) => {
|
||||||
if (e.propertyName === "opacity" && !firstSearch) {
|
if (e.propertyName === "opacity" && !firstSearch) {
|
||||||
const target = e.target as HTMLDivElement;
|
const target = e.target as HTMLDivElement;
|
||||||
@@ -474,20 +479,19 @@ export const SearchSection = ({
|
|||||||
sidebarElementRef,
|
sidebarElementRef,
|
||||||
showDocSidebar,
|
showDocSidebar,
|
||||||
setShowDocSidebar,
|
setShowDocSidebar,
|
||||||
|
mobile: settings?.isMobile,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex relative w-full pr-[8px] h-full text-default overflow-x-hidden">
|
<div className="flex relative w-full pr-[8px] h-full text-default">
|
||||||
<div
|
<div
|
||||||
ref={sidebarElementRef}
|
ref={sidebarElementRef}
|
||||||
className={`
|
className={`
|
||||||
flex-none
|
flex-none
|
||||||
fixed -8
|
fixed
|
||||||
left-0
|
left-0
|
||||||
z-20
|
z-30
|
||||||
overflow-y-hidden
|
|
||||||
sidebar
|
|
||||||
bg-background-100
|
bg-background-100
|
||||||
h-screen
|
h-screen
|
||||||
transition-all
|
transition-all
|
||||||
@@ -514,7 +518,7 @@ export const SearchSection = ({
|
|||||||
|
|
||||||
<div className="absolute left-0 w-full top-0">
|
<div className="absolute left-0 w-full top-0">
|
||||||
<FunctionalHeader
|
<FunctionalHeader
|
||||||
showSidebar={showDocSidebar}
|
toggleSidebar={toggleSidebar}
|
||||||
page="search"
|
page="search"
|
||||||
user={user}
|
user={user}
|
||||||
/>
|
/>
|
||||||
@@ -535,9 +539,10 @@ export const SearchSection = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
<div className="px-24 w-full pt-10 relative max-w-[2000px] xl:max-w-[1430px] mx-auto">
|
<div className="desktop:px-24 w-full pt-10 relative max-w-[2000px] xl:max-w-[1430px] mx-auto">
|
||||||
<div className="absolute z-10 top-12 left-0 hidden 2xl:block w-52 3xl:w-64">
|
<div className="absolute z-10 mobile:px-4 mobile:max-w-searchbar-max mobile:w-[90%] top-12 desktop:left-0 hidden 2xl:block mobile:left-1/2 mobile:transform mobile:-translate-x-1/2 desktop:w-52 3xl:w-64">
|
||||||
{(ccPairs.length > 0 || documentSets.length > 0) && (
|
{!settings?.isMobile &&
|
||||||
|
(ccPairs.length > 0 || documentSets.length > 0) && (
|
||||||
<SourceSelector
|
<SourceSelector
|
||||||
{...filterManager}
|
{...filterManager}
|
||||||
showDocSidebar={showDocSidebar || toggledSidebar}
|
showDocSidebar={showDocSidebar || toggledSidebar}
|
||||||
@@ -549,6 +554,28 @@ export const SearchSection = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="absolute left-0 hidden 2xl:block w-52 3xl:w-64"></div>
|
<div className="absolute left-0 hidden 2xl:block w-52 3xl:w-64"></div>
|
||||||
<div className="max-w-searchbar-max w-[90%] mx-auto">
|
<div className="max-w-searchbar-max w-[90%] mx-auto">
|
||||||
|
{settings?.isMobile && (
|
||||||
|
<div className="mt-6">
|
||||||
|
{!(agenticResults && isFetching) || disabledAgentic ? (
|
||||||
|
<SearchResultsDisplay
|
||||||
|
disabledAgentic={disabledAgentic}
|
||||||
|
contentEnriched={contentEnriched}
|
||||||
|
comments={comments}
|
||||||
|
sweep={sweep}
|
||||||
|
agenticResults={agenticResults && !disabledAgentic}
|
||||||
|
performSweep={performSweep}
|
||||||
|
searchResponse={searchResponse}
|
||||||
|
isFetching={isFetching}
|
||||||
|
defaultOverrides={defaultOverrides}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className={`mobile:fixed mobile:left-1/2 mobile:transform mobile:-translate-x-1/2 mobile:max-w-search-bar-max mobile:w-[90%] mobile:z-100 mobile:bottom-12`}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className={`transition-all duration-500 ease-in-out overflow-hidden
|
className={`transition-all duration-500 ease-in-out overflow-hidden
|
||||||
${
|
${
|
||||||
@@ -568,9 +595,10 @@ export const SearchSection = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FullSearchBar
|
<FullSearchBar
|
||||||
toggleAgentic={disabledAgentic ? undefined : toggleAgentic}
|
toggleAgentic={
|
||||||
|
disabledAgentic ? undefined : toggleAgentic
|
||||||
|
}
|
||||||
agentic={agentic}
|
agentic={agentic}
|
||||||
searchState={searchState}
|
searchState={searchState}
|
||||||
query={query}
|
query={query}
|
||||||
@@ -580,7 +608,9 @@ export const SearchSection = ({
|
|||||||
await onSearch({ agentic, offset: 0 });
|
await onSearch({ agentic, offset: 0 });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!settings?.isMobile && (
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
{!(agenticResults && isFetching) || disabledAgentic ? (
|
{!(agenticResults && isFetching) || disabledAgentic ? (
|
||||||
<SearchResultsDisplay
|
<SearchResultsDisplay
|
||||||
@@ -598,6 +628,7 @@ export const SearchSection = ({
|
|||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { CombinedSettings } from "@/app/admin/settings/interfaces";
|
import { CombinedSettings } from "@/app/admin/settings/interfaces";
|
||||||
import { createContext } from "react";
|
import { createContext, useEffect, useState } from "react";
|
||||||
|
|
||||||
export const SettingsContext = createContext<CombinedSettings | null>(null);
|
export const SettingsContext = createContext<CombinedSettings | null>(null);
|
||||||
|
|
||||||
@@ -12,8 +12,20 @@ export function SettingsProvider({
|
|||||||
children: React.ReactNode | JSX.Element;
|
children: React.ReactNode | JSX.Element;
|
||||||
settings: CombinedSettings;
|
settings: CombinedSettings;
|
||||||
}) {
|
}) {
|
||||||
|
const [isMobile, setIsMobile] = useState<boolean | undefined>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const checkMobile = () => {
|
||||||
|
setIsMobile(window.innerWidth < 768);
|
||||||
|
};
|
||||||
|
|
||||||
|
checkMobile();
|
||||||
|
window.addEventListener("resize", checkMobile);
|
||||||
|
return () => window.removeEventListener("resize", checkMobile);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsContext.Provider value={settings}>
|
<SettingsContext.Provider value={{ ...settings, isMobile }}>
|
||||||
{children}
|
{children}
|
||||||
</SettingsContext.Provider>
|
</SettingsContext.Provider>
|
||||||
);
|
);
|
||||||
|
@@ -36,6 +36,7 @@ export interface CombinedSettings {
|
|||||||
settings: Settings;
|
settings: Settings;
|
||||||
enterpriseSettings: EnterpriseSettings | null;
|
enterpriseSettings: EnterpriseSettings | null;
|
||||||
customAnalyticsScript: string | null;
|
customAnalyticsScript: string | null;
|
||||||
|
isMobile?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let cachedSettings: CombinedSettings;
|
let cachedSettings: CombinedSettings;
|
||||||
|
@@ -45,6 +45,8 @@ module.exports = {
|
|||||||
"2xl": "1420px",
|
"2xl": "1420px",
|
||||||
"3xl": "1700px",
|
"3xl": "1700px",
|
||||||
"4xl": "2000px",
|
"4xl": "2000px",
|
||||||
|
mobile: { max: "767px" },
|
||||||
|
desktop: "768px",
|
||||||
},
|
},
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: ["var(--font-inter)"],
|
sans: ["var(--font-inter)"],
|
||||||
|
Reference in New Issue
Block a user