Add initial mobile support (#1962)

This commit is contained in:
pablodanswer
2024-07-30 17:13:50 -07:00
committed by GitHub
parent d151082871
commit eb9bb56829
34 changed files with 837 additions and 676 deletions

View File

@@ -19,4 +19,5 @@ export interface CombinedSettings {
settings: Settings;
enterpriseSettings: EnterpriseSettings | null;
customAnalyticsScript: string | null;
isMobile?: boolean;
}

View File

@@ -7,12 +7,13 @@ import { Folder } from "@/app/chat/folders/interfaces";
import { User } from "@/lib/types";
import Cookies from "js-cookie";
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 FunctionalHeader from "@/components/chat_search/Header";
import { useRouter } from "next/navigation";
import { pageType } from "../chat/sessionSidebar/types";
import FixedLogo from "../chat/shared_chat_search/FixedLogo";
import { SettingsContext } from "@/components/settings/SettingsProvider";
interface SidebarWrapperProps<T extends object> {
chatSessions?: ChatSession[];
@@ -55,11 +56,13 @@ export default function SidebarWrapper<T extends object>({
const sidebarElementRef = useRef<HTMLDivElement>(null);
const settings = useContext(SettingsContext);
useSidebarVisibility({
toggledSidebar,
sidebarElementRef,
showDocSidebar,
setShowDocSidebar,
mobile: settings?.isMobile,
});
const innerSidebarElementRef = useRef<HTMLDivElement>(null);
@@ -90,9 +93,7 @@ export default function SidebarWrapper<T extends object>({
flex-none
fixed
left-0
z-20
overflow-y-hidden
sidebar
z-30
bg-background-100
h-screen
transition-all
@@ -119,10 +120,10 @@ export default function SidebarWrapper<T extends object>({
</div>
</div>
<div className="absolute left-0 w-full top-0">
<div className="absolute h-svh left-0 w-full top-0">
<FunctionalHeader
toggleSidebar={toggleSidebar}
page="assistants"
showSidebar={showDocSidebar}
user={headerProps.user}
/>
<div className="w-full flex">
@@ -138,6 +139,7 @@ export default function SidebarWrapper<T extends object>({
ease-in-out
${toggledSidebar ? "w-[250px]" : "w-[0px]"}`}
/>
<div className="mt-4 w-full max-w-3xl mx-auto">
{content(contentProps)}
</div>

View File

@@ -47,7 +47,7 @@ export function AssistantTools({
hovered?: boolean;
}) {
return (
<div className="relative text-xs flex text-subtle">
<div className="relative text-xs overflow-x-hidden flex text-subtle">
<span
className={`${assistant.tools.length > 0 && "py-1"} ${!list ? "font-semibold" : "text-subtle text-sm"}`}
>

View File

@@ -287,7 +287,7 @@ export function AssistantsList({ user, assistants }: AssistantsListProps) {
return (
<>
{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>
<div className="grid grid-cols-2 gap-4 mt-3">

View File

@@ -38,7 +38,7 @@ export function ChatIntro({
return (
<>
<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="mx-auto">
<div className="m-auto text-3xl font-strong font-bold text-strong w-fit">

View File

@@ -85,7 +85,7 @@ export function ChatPage({
defaultSelectedAssistantId,
toggledSidebar,
}: {
toggle: () => void;
toggle: (toggled?: boolean) => void;
documentSidebarInitialWidth?: number;
defaultSelectedAssistantId?: number;
toggledSidebar: boolean;
@@ -524,15 +524,6 @@ export function ChatPage({
const distance = 500; // distance that should "engage" the scroll
const debounce = 100; // time for debouncing
useScrollonStream({
isStreaming,
scrollableDivRef,
scrollDist,
endDivRef,
distance,
debounce,
});
const [hasPerformedInitialScroll, setHasPerformedInitialScroll] = useState(
existingChatSessionId === null
);
@@ -644,7 +635,6 @@ export function ChatPage({
alternativeAssistantOverride?: Persona | null;
} = {}) => {
setAlternativeGeneratingAssistant(alternativeAssistantOverride);
clientScrollToBottom();
let currChatSessionId: number;
let isNewSession = chatSessionIdRef.current === null;
@@ -1059,6 +1049,10 @@ export function ChatPage({
toggle();
};
const removeToggle = () => {
setShowDocSidebar(false);
toggle(false);
};
const sidebarElementRef = useRef<HTMLDivElement>(null);
@@ -1067,6 +1061,17 @@ export function ChatPage({
sidebarElementRef,
showDocSidebar,
setShowDocSidebar,
setToggled: removeToggle,
mobile: settings?.isMobile,
});
useScrollonStream({
isStreaming,
scrollableDivRef,
scrollDist,
endDivRef,
distance,
debounce,
});
useEffect(() => {
@@ -1119,20 +1124,48 @@ export function ChatPage({
<>
<HealthCheckBanner secondsUntilExpiration={secondsUntilExpiration} />
<InstantSSRAutoRefresh />
{/* ChatPopup is a custom popup that displays a admin-specified message on initial user visit.
Only used in the EE version of the app. */}
<ChatPopup />
<div className="flex relative bg-background text-default ">
{currentFeedback && (
<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
ref={sidebarElementRef}
className={`
flex-none
absolute
fixed
left-0
z-20
sidebar
z-30
bg-background-100
h-screen
transition-all
@@ -1150,48 +1183,24 @@ export function ChatPage({
page="chat"
ref={innerSidebarElementRef}
toggleSidebar={toggleSidebar}
toggled={toggledSidebar}
toggled={toggledSidebar && !settings?.isMobile}
existingChats={chatSessions}
currentChatSession={selectedChatSession}
folders={folders}
openedFolders={openedFolders}
removeToggle={removeToggle}
/>
</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}
{currentFeedback && (
<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="flex h-[calc(100dvh)] flex-col w-full">
<div className="flex h-full flex-col w-full">
{liveAssistant && (
<FunctionalHeader
page="chat"
@@ -1200,7 +1209,7 @@ export function ChatPage({
? setSharingModalVisible
: undefined
}
showSidebar={showDocSidebar}
toggleSidebar={toggleSidebar}
user={user}
currentChatSession={selectedChatSession}
/>
@@ -1226,6 +1235,7 @@ export function ChatPage({
<Dropzone onDrop={handleImageUpload} noClick>
{({ getRootProps }) => (
<div className="flex h-full w-full">
{!settings?.isMobile && (
<div
style={{ transition: "width 0.30s ease-out" }}
className={`
@@ -1240,8 +1250,10 @@ export function ChatPage({
${toggledSidebar ? "w-[250px]" : "w-[0px]"}
`}
></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()}
>
{/* <input {...getInputProps()} /> */}
@@ -1263,12 +1275,13 @@ export function ChatPage({
<div
className={
"mt-4 -ml-4 w-full mx-auto " +
"absolute top-12 left-0 " +
"absolute mobile:top-0 desktop:top-12 left-0" +
(hasPerformedInitialScroll ? "" : "invisible")
}
>
{messageHistory.map((message, i) => {
const messageMap = completeMessageDetail.messageMap;
const messageMap =
completeMessageDetail.messageMap;
const messageReactComponentKey = `${i}-${completeMessageDetail.sessionId}`;
if (message.type === "user") {
const parentMessage = message.parentMessageId
@@ -1375,7 +1388,8 @@ export function ChatPage({
message
)}
toolCall={
message.toolCalls && message.toolCalls[0]
message.toolCalls &&
message.toolCalls[0]
}
isComplete={
i !== messageHistory.length - 1 ||
@@ -1409,7 +1423,8 @@ export function ChatPage({
}
if (
previousMessage.messageId === null
previousMessage.messageId ===
null
) {
setPopup({
type: "error",
@@ -1524,10 +1539,6 @@ export function ChatPage({
</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.starter_messages &&
currentPersona.starter_messages.length > 0 &&
@@ -1566,6 +1577,8 @@ export function ChatPage({
)}
</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>
</div>
@@ -1627,9 +1640,12 @@ export function ChatPage({
</div>
)}
</div>
</div>
<FixedLogo />
</div>
</div>
<DocumentSidebar
initialWidth={390}
initialWidth={350}
ref={innerSidebarElementRef}
closeSidebar={() => setDocumentSelection(false)}
selectedMessage={aiMessage}
@@ -1641,9 +1657,6 @@ export function ChatPage({
isLoading={isFetchingChatMessages}
isOpen={documentSelection}
/>
</div>
<FixedLogo />
</div>
</>
);
}

View File

@@ -2,44 +2,10 @@ import { DanswerDocument } from "@/lib/search/interfaces";
import { Divider, Text } from "@tremor/react";
import { ChatDocumentDisplay } from "./ChatDocumentDisplay";
import { usePopup } from "@/components/admin/connectors/Popup";
import { FiAlertTriangle, FiFileText } from "react-icons/fi";
import { SelectedDocumentDisplay } from "./SelectedDocumentDisplay";
import { removeDuplicateDocs } from "@/lib/documentUtils";
import { BasicSelectable } from "@/components/BasicClickable";
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";
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 {
closeSidebar: () => void;
selectedMessage: Message | null;
@@ -71,8 +37,6 @@ export const DocumentSidebar = forwardRef<HTMLDivElement, DocumentSidebarProps>(
) => {
const { popup, setPopup } = usePopup();
const selectedMessageRetrievalType = selectedMessage?.retrievalType || null;
const selectedDocumentIds =
selectedDocuments?.map((document) => document.document_id) || [];
@@ -88,9 +52,7 @@ export const DocumentSidebar = forwardRef<HTMLDivElement, DocumentSidebarProps>(
return (
<div
className={`fixed inset-0 transition-opacity duration-300 z-50 bg-black/80 ${
isOpen ? "opacity-100" : "opacity-0 pointer-events-none"
}`}
className={`fixed inset-0 transition-opacity duration-300 z-50 bg-black/80 ${isOpen ? "opacity-100" : "opacity-0 pointer-events-none"}`}
onClick={(e) => {
if (e.target === e.currentTarget) {
closeSidebar();

View File

@@ -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 { ChatInputOption } from "./ChatInputOption";
import { Persona } from "@/app/admin/assistants/interfaces";
@@ -29,6 +29,7 @@ import { DanswerDocument } from "@/lib/search/interfaces";
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
import { Tooltip } from "@/components/tooltip/Tooltip";
import { Hoverable } from "@/components/Hoverable";
import { SettingsContext } from "@/components/settings/SettingsProvider";
const MAX_INPUT_HEIGHT = 200;
export function ChatInputBar({
@@ -103,6 +104,7 @@ export function ChatInputBar({
}
}
};
const settings = useContext(SettingsContext);
const { llmProviders } = useChatContext();
const [_, llmName] = getFinalLLM(llmProviders, selectedAssistant, null);
@@ -213,9 +215,8 @@ export function ChatInputBar({
className="
w-[90%]
shrink
bg-background
relative
px-4
desktop:px-4
max-w-searchbar-max
mx-auto
"
@@ -390,7 +391,7 @@ export function ChatInputBar({
style={{ scrollbarWidth: "thin" }}
role="textarea"
aria-multiline
placeholder="Send a message or @ to tag an assistant..."
placeholder={`Send a message ${!settings?.isMobile ? "or @ to tag an assistant..." : ""}`}
value={message}
onKeyDown={(event) => {
if (
@@ -420,7 +421,9 @@ export function ChatInputBar({
}}
/>
)}
flexPriority="shrink"
position="top"
mobilePosition="top-right"
>
<ChatInputOption
flexPriority="shrink"
@@ -450,16 +453,21 @@ export function ChatInputBar({
/>
)}
position="top"
// flexPriority="second"
>
<ChatInputOption
flexPriority="second"
name={getDisplayNameForModel(
name={
settings?.isMobile
? undefined
: getDisplayNameForModel(
llmOverrideManager.llmOverride.modelName ||
(selectedAssistant
? selectedAssistant.llm_model_version_override ||
llmName
: llmName)
)}
)
}
Icon={CpuIconSkeleton}
/>
</Popup>
@@ -484,7 +492,7 @@ export function ChatInputBar({
}}
/>
</div>
<div className="absolute bottom-2.5 right-10">
<div className="absolute bottom-2.5 mobile:right-4 desktop:right-10">
<div
className="cursor-pointer"
onClick={() => {

View File

@@ -5,7 +5,7 @@ import { Popover } from "../../../components/popover/Popover";
import { IconProps } from "@/components/icons/icons";
interface ChatInputOptionProps {
name: string;
name?: string;
Icon: ({ size, className }: IconProps) => JSX.Element;
onClick?: () => void;
size?: number;
@@ -61,7 +61,7 @@ export const ChatInputOption: React.FC<ChatInputOptionProps> = ({
rounded-md
${
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" &&
@@ -76,7 +76,7 @@ export const ChatInputOption: React.FC<ChatInputOptionProps> = ({
onClick={onClick}
>
<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 && (
<div
className="absolute z-10 p-2 text-sm text-white bg-black rounded shadow-lg"

View File

@@ -610,6 +610,7 @@ export async function useScrollonStream({
endDivRef: RefObject<HTMLDivElement>;
distance: number;
debounce: number;
mobile?: boolean;
}) {
const preventScrollInterference = useRef<boolean>(false);
const preventScroll = useRef<boolean>(false);

View File

@@ -13,7 +13,7 @@ import {
FiGlobe,
} from "react-icons/fi";
import { FeedbackType } from "../types";
import { useEffect, useRef, useState } from "react";
import { useContext, useEffect, useRef, useState } from "react";
import ReactMarkdown from "react-markdown";
import {
DanswerDocument,
@@ -58,6 +58,7 @@ import { ValidSources } from "@/lib/types";
import { Tooltip } from "@/components/tooltip/Tooltip";
import { useMouseTracking } from "./hooks";
import { InternetSearchIcon } from "@/components/InternetSearchIcon";
import { SettingsContext } from "@/components/settings/SettingsProvider";
const TOOLS_WITH_CUSTOM_HANDLING = [
SEARCH_TOOL_NAME,
@@ -162,6 +163,7 @@ export const AIMessage = ({
const { isHovering, trackedElementRef, hoverElementRef } = useMouseTracking();
const settings = useContext(SettingsContext);
// this is needed to give Prism a chance to load
if (!isReady) {
return <div />;
@@ -226,7 +228,7 @@ export const AIMessage = ({
return (
<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="xl:ml-8">
<div className=" mobile:ml-4 xl:ml-8">
<div className="flex">
<AssistantIcon
size="small"
@@ -430,7 +432,8 @@ export const AIMessage = ({
<div className="mt-2 -mx-8 w-full mb-4 flex relative">
<div className="w-full">
<div className="px-8 flex gap-x-2">
{filteredDocs.length > 0 &&
{!settings?.isMobile &&
filteredDocs.length > 0 &&
filteredDocs.slice(0, 2).map((doc, ind) => (
<div
key={doc.document_id}
@@ -530,10 +533,10 @@ export const AIMessage = ({
<div
ref={hoverElementRef}
className={`
absolute -bottom-2
invisible ${isHovering && "!visible"}
opacity-0 ${isHovering && "!opacity-100"}
translate-y-2 ${isHovering && "!translate-y-0"}
absolute -bottom-4
invisible ${(isHovering || settings?.isMobile) && "!visible"}
opacity-0 ${(isHovering || settings?.isMobile) && "!opacity-100"}
translate-y-2 ${(isHovering || settings?.isMobile) && "!translate-y-0"}
transition-transform duration-300 ease-in-out
flex md:flex-row gap-x-0.5 bg-background-125/40 p-1.5 rounded-lg
`}

View File

@@ -28,9 +28,12 @@ export function SkippedSearch({
}) {
return (
<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">
<span className="mobile:hidden">
The AI decided this query didn&apos;t need a search
</span>
<span className="desktop:hidden">No search</span>
</div>
<div className="ml-auto my-auto" onClick={handleForceSearch}>

View File

@@ -43,15 +43,14 @@ export default async function Page({
return (
<>
<InstantSSRAutoRefresh />
{shouldShowWelcomeModal && <WelcomeModal user={user} />}
{/* <InstantSSRAutoRefresh /> */}
{/* {shouldShowWelcomeModal && <WelcomeModal user={user} />}
{!shouldShowWelcomeModal && !shouldDisplaySourcesIncompleteModal && (
<ApiKeyModal user={user} />
)}
{shouldDisplaySourcesIncompleteModal && (
<NoCompleteSourcesModal ccPairs={ccPairs} />
)}
)} */}
<ChatProvider
value={{
user,

View File

@@ -2,7 +2,7 @@
import { useRouter } from "next/navigation";
import { ChatSession } from "../interfaces";
import { useState, useEffect } from "react";
import { useState, useEffect, useContext } from "react";
import { deleteChatSession, renameChatSession } from "../lib";
import { DeleteChatModal } from "../modal/DeleteChatModal";
import { BasicSelectable } from "@/components/BasicClickable";
@@ -19,12 +19,14 @@ import { DefaultDropdownElement } from "@/components/Dropdown";
import { Popover } from "@/components/popover/Popover";
import { ShareChatSessionModal } from "../modal/ShareChatSessionModal";
import { CHAT_SESSION_ID_KEY, FOLDER_ID_KEY } from "@/lib/drag/constants";
import { SettingsContext } from "@/components/settings/SettingsProvider";
export function ChatSessionDisplay({
chatSession,
search,
isSelected,
skipGradient,
closeSidebar,
}: {
chatSession: ChatSession;
isSelected: boolean;
@@ -32,6 +34,7 @@ export function ChatSessionDisplay({
// needed when the parent is trying to apply some background effect
// if not set, the gradient will still be applied and cause weirdness
skipGradient?: boolean;
closeSidebar?: () => void;
}) {
const router = useRouter();
const [isDeletionModalVisible, setIsDeletionModalVisible] = useState(false);
@@ -62,6 +65,7 @@ export function ChatSessionDisplay({
alert("Failed to rename chat session");
}
};
const settings = useContext(SettingsContext);
return (
<>
@@ -92,6 +96,11 @@ export function ChatSessionDisplay({
<Link
className="flex my-1 group relative"
key={chatSession.id}
onClick={() => {
if (settings?.isMobile && closeSidebar) {
closeSidebar();
}
}}
href={
search
? `/search?searchId=${chatSession.id}`

View File

@@ -48,6 +48,7 @@ interface HistorySidebarProps {
openedFolders?: { [key: number]: boolean };
toggleSidebar?: () => void;
toggled?: boolean;
removeToggle?: () => void;
}
export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
@@ -60,6 +61,7 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
folders,
openedFolders,
toggleSidebar,
removeToggle,
},
ref: ForwardedRef<HTMLDivElement>
) => {
@@ -101,11 +103,11 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
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="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} />
</div>
<div className="invisible">
<div className="desktop:invisible">
{enterpriseSettings && enterpriseSettings.application_name ? (
<div>
<HeaderTitle>
@@ -119,13 +121,18 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
<HeaderTitle>Danswer</HeaderTitle>
)}
</div>
{toggleSidebar && (
<Tooltip
delayDuration={0}
content={toggled ? `Unpin sidebar` : "Pin sidebar"}
>
<button className="my-auto ml-auto" onClick={toggleSidebar}>
{!toggled ? <RightToLineIcon /> : <LefToLineIcon />}
{!toggled && !combinedSettings.isMobile ? (
<RightToLineIcon />
) : (
<LefToLineIcon />
)}
</button>
</Tooltip>
)}
@@ -182,6 +189,7 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
<div className="border-b border-border pb-4 mx-3" />
<PagesTab
closeSidebar={removeToggle}
page={page}
existingChats={existingChats}
currentChatId={currentChatId}

View File

@@ -16,12 +16,14 @@ export function PagesTab({
currentChatId,
folders,
openedFolders,
closeSidebar,
}: {
page: pageType;
existingChats?: ChatSession[];
currentChatId?: number;
folders?: Folder[];
openedFolders?: { [key: number]: boolean };
closeSidebar?: () => void;
}) {
const groupedChatSessions = existingChats
? groupSessionsByDateRange(existingChats)
@@ -58,7 +60,7 @@ export function PagesTab({
const isHistoryEmpty = !existingChats || existingChats.length === 0;
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 && (
<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">
@@ -115,6 +117,7 @@ export function PagesTab({
return (
<div key={`${chat.id}-${chat.name}`}>
<ChatSessionDisplay
closeSidebar={closeSidebar}
search={page == "search"}
chatSession={chat}
isSelected={isSelected}

View File

@@ -14,8 +14,8 @@ export default function FixedLogo() {
return (
<>
<div className="absolute flex z-40 left-2.5 top-2">
<div className="max-w-[200px] flex items-center gap-x-1 my-auto">
<div className="fixed pointer-events-none flex z-40 left-2.5 top-2">
<div className="max-w-[200px] mobile:hidden flex items-center gap-x-1 my-auto">
<div className="flex-none my-auto">
<Logo height={24} width={24} />
</div>
@@ -33,7 +33,7 @@ export default function FixedLogo() {
</div>
</div>
</div>
<div className="absolute left-2.5 bottom-4">
<div className="mobile:hidden fixed left-2.5 bottom-4">
<FiSidebar />
</div>
</>

View File

@@ -10,6 +10,7 @@ const ToggleSwitch = () => {
const commandSymbol = KeyboardSymbol();
const pathname = usePathname();
const router = useRouter();
const settings = useContext(SettingsContext);
const [activeTab, setActiveTab] = useState(() => {
return pathname == "/search" ? "search" : "chat";
@@ -27,13 +28,17 @@ const ToggleSwitch = () => {
const handleTabChange = (tab: string) => {
setActiveTab(tab);
localStorage.setItem("activeTab", tab);
if (settings?.isMobile) {
window.location.href = tab;
} else {
router.push(tab === "search" ? "/search" : "/chat");
}
};
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
className={`absolute top-1 bottom-1 ${
className={`absolute mobile:mt-8 top-1 bottom-1 ${
activeTab === "chat" ? "w-[45%]" : "w-[50%]"
} bg-white rounded-full shadow ${
isInitialLoad ? "" : "transition-transform duration-300 ease-in-out"
@@ -116,15 +121,19 @@ export default function FunctionalWrapper({
const [toggledSidebar, setToggledSidebar] = useState(initiallyToggled);
const toggle = () => {
setToggledSidebar((toggledSidebar) => !toggledSidebar);
const toggle = (value?: boolean) => {
if (value !== undefined) {
setToggledSidebar(value);
} else {
setToggledSidebar((prevState) => !prevState);
}
};
return (
<>
{(!settings ||
(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
style={{ transition: "width 0.30s ease-out" }}
className={`flex-none overflow-y-hidden bg-background-100 transition-all bg-opacity-80 duration-300 ease-in-out h-full
@@ -136,7 +145,7 @@ export default function FunctionalWrapper({
</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)}
</div>
</>

View File

@@ -111,6 +111,10 @@
overflow: hidden;
}
.prevent-scroll {
overscroll-behavior-y: none;
}
body {
overscroll-behavior-y: none;
}

View File

@@ -6,6 +6,7 @@ import { SettingsProvider } from "@/components/settings/SettingsProvider";
import { Metadata } from "next";
import { buildClientUrl } from "@/lib/utilsSS";
import { Inter } from "next/font/google";
import Head from "next/head";
const inter = Inter({
subsets: ["latin"],
@@ -41,6 +42,13 @@ export default async function RootLayout({
return (
<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 && (
<head>
<script
@@ -51,7 +59,9 @@ export default async function RootLayout({
/>
</head>
)}
<body
<body className="relative">
<div
className={`${inter.variable} font-sans text-default bg-background ${
// TODO: remove this once proper dark mode exists
process.env.THEME_IS_DARK?.toLowerCase() === "true" ? "dark" : ""
@@ -60,6 +70,7 @@ export default async function RootLayout({
<SettingsProvider settings={combinedSettings}>
{children}
</SettingsProvider>
</div>
</body>
</html>
);

View File

@@ -187,7 +187,6 @@ export default async function Home() {
return (
<>
<Header user={user} />
<HealthCheckBanner secondsUntilExpiration={secondsUntilExpiration} />
{shouldShowWelcomeModal && <WelcomeModal user={user} />}

View File

@@ -54,7 +54,7 @@ export function UserDropdown({
onClick={() => setUserInfoVisible(!userInfoVisible)}
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"}
</div>
</div>
@@ -84,9 +84,10 @@ export function UserDropdown({
<>
<Link
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
</Link>
</>

View File

@@ -18,20 +18,19 @@ import { SettingsContext } from "../settings/SettingsProvider";
import { pageType } from "@/app/chat/sessionSidebar/types";
export default function FunctionalHeader({
showSidebar,
user,
page,
currentChatSession,
setSharingModalVisible,
toggleSidebar,
}: {
page: pageType;
showSidebar: boolean;
user: User | null;
currentChatSession?: ChatSession | null | undefined;
setSharingModalVisible?: (value: SetStateAction<boolean>) => void;
toggleSidebar: () => void;
}) {
const combinedSettings = useContext(SettingsContext);
const settings = combinedSettings?.settings;
const enterpriseSettings = combinedSettings?.enterpriseSettings;
const commandSymbol = KeyboardSymbol();
@@ -60,12 +59,15 @@ export default function FunctionalHeader({
};
}, []);
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="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} />
</div>
</button>
<div className="invisible break-words inline-block w-fit ml-2 text-text-700 text-xl">
<div className="max-w-[200px]">
{enterpriseSettings && enterpriseSettings.application_name ? (
@@ -86,7 +88,7 @@ export default function FunctionalHeader({
{page == "chat" && (
<Tooltip delayDuration={1000} content={`${commandSymbol}U`}>
<Link
className="my-auto"
className="mobile:hidden my-auto"
href={
`/${page}` +
(NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA &&
@@ -107,15 +109,29 @@ export default function FunctionalHeader({
{setSharingModalVisible && (
<div
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" />
</div>
)}
<div className="flex my-auto">
<div className="mobile:hidden flex my-auto">
<UserDropdown user={user} />
</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>

View File

@@ -5,18 +5,22 @@ interface UseSidebarVisibilityProps {
sidebarElementRef: React.RefObject<HTMLElement>;
showDocSidebar: boolean;
setShowDocSidebar: Dispatch<SetStateAction<boolean>>;
mobile?: boolean;
setToggled?: () => void;
}
export const useSidebarVisibility = ({
toggledSidebar,
sidebarElementRef,
setShowDocSidebar,
setToggled,
showDocSidebar,
mobile,
}: UseSidebarVisibilityProps) => {
const xPosition = useRef(0);
useEffect(() => {
const handleMouseMove = (event: MouseEvent) => {
const handleEvent = (event: MouseEvent) => {
const currentXPosition = event.clientX;
xPosition.current = currentXPosition;
@@ -28,11 +32,20 @@ export const useSidebarVisibility = ({
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 &&
@@ -41,17 +54,19 @@ export const useSidebarVisibility = ({
) {
setShowDocSidebar(false);
} else if (currentXPosition < 100 && !showDocSidebar) {
if (!mobile) {
setShowDocSidebar(true);
}
}
}
};
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mousemove", handleEvent);
return () => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mousemove", handleEvent);
};
}, [showDocSidebar, toggledSidebar, sidebarElementRef]);
}, [showDocSidebar, toggledSidebar, sidebarElementRef, mobile]);
return { showDocSidebar };
};

View File

@@ -3,7 +3,6 @@
import { User } from "@/lib/types";
import Link from "next/link";
import React, { useContext } from "react";
import { FiMessageSquare, FiSearch } from "react-icons/fi";
import { HeaderWrapper } from "./HeaderWrapper";
import { SettingsContext } from "../settings/SettingsProvider";
import { UserDropdown } from "../UserDropdown";

View File

@@ -6,4 +6,5 @@
.PopoverContent[data-side="top"] {
transform: translateY(var(--radix-popover-trigger-height) px);
font-family: sans-serif;
}

View File

@@ -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 {
children: JSX.Element;
@@ -6,9 +14,11 @@ interface PopupProps {
close: () => void,
ref?: React.RefObject<HTMLDivElement>
) => ReactNode;
position?: "top" | "bottom" | "left" | "right";
position?: "top" | "bottom" | "left" | "right" | "top-right";
removePadding?: boolean;
tab?: boolean;
flexPriority?: "shrink" | "stiff" | "second";
mobilePosition?: "top" | "bottom" | "left" | "right" | "top-right";
}
const Popup: React.FC<PopupProps> = ({
@@ -17,6 +27,8 @@ const Popup: React.FC<PopupProps> = ({
tab,
removePadding,
position = "top",
flexPriority,
mobilePosition,
}) => {
const [isOpen, setIsOpen] = useState<boolean>(false);
const triggerRef = useRef<HTMLDivElement>(null);
@@ -51,6 +63,8 @@ const Popup: React.FC<PopupProps> = ({
};
}, []);
const settings = useContext(SettingsContext);
const getPopupStyle = (): React.CSSProperties => {
if (!triggerRef.current) return {};
@@ -60,7 +74,11 @@ const Popup: React.FC<PopupProps> = ({
zIndex: 10,
};
switch (position) {
const currentPosition = settings?.isMobile
? mobilePosition || position
: position;
switch (currentPosition) {
case "bottom":
popupStyle.top = `${triggerRect.height + 5}px`;
popupStyle.left = "50%";
@@ -76,9 +94,14 @@ const Popup: React.FC<PopupProps> = ({
popupStyle.left = `${triggerRect.width + 5}px`;
popupStyle.transform = "translateY(-50%)";
break;
case "top-right":
popupStyle.bottom = `${triggerRect.height + 5}px`;
popupStyle.right = "0";
popupStyle.right = "auto";
break;
default: // top
popupStyle.bottom = `${triggerRect.height + 5}px`;
popupStyle.left = `${triggerRect.width + 50}px`;
popupStyle.left = "50%";
popupStyle.transform = "translateX(-50%)";
}
@@ -86,7 +109,19 @@ const Popup: React.FC<PopupProps> = ({
};
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">
{children}
</div>
@@ -96,7 +131,7 @@ const Popup: React.FC<PopupProps> = ({
ref={popupRef}
className={`absolute bg-white border border-gray-200 rounded-lg shadow-lg ${
!removePadding && "p-4"
} ${tab ? " w-[400px] " : "min-w-[400px]"}`}
} ${!settings?.isMobile ? (tab ? "w-[400px] " : "min-w-[400px]") : "w-[250px]"}`}
style={getPopupStyle()}
>
{content(closePopup, contentRef)}

View File

@@ -1,3 +1,5 @@
"use client";
import {
DanswerDocument,
DocumentRelevance,
@@ -5,7 +7,7 @@ import {
SearchDanswerDocument,
} from "@/lib/search/interfaces";
import { DocumentFeedbackBlock } from "./DocumentFeedbackBlock";
import { useState } from "react";
import { useContext, useState } from "react";
import { PopupSpec } from "../admin/connectors/Popup";
import { DocumentUpdatedAtBadge } from "./DocumentUpdatedAtBadge";
import { SourceIcon } from "../SourceIcon";
@@ -15,6 +17,7 @@ import { BookIcon, CheckmarkIcon, LightBulbIcon, XIcon } from "../icons/icons";
import { FaStar } from "react-icons/fa";
import { FiTag } from "react-icons/fi";
import { DISABLE_AGENTIC_SEARCH } from "@/lib/constants";
import { SettingsContext } from "../settings/SettingsProvider";
export const buildDocumentSummaryDisplay = (
matchHighlights: string[],
@@ -178,11 +181,12 @@ export const DocumentDisplay = ({
const [alternativeToggled, setAlternativeToggled] = useState(false);
const relevance_explanation =
document.relevance_explanation ?? additional_relevance?.content;
const settings = useContext(SettingsContext);
return (
<div
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 ? "pt-3" : "border-transparent"} relative`}
onMouseEnter={() => setIsHovered(true)}
@@ -234,7 +238,7 @@ export const DocumentDisplay = ({
{(contentEnriched || additional_relevance) &&
relevance_explanation &&
(isHovered || alternativeToggled) && (
(isHovered || alternativeToggled || settings?.isMobile) && (
<button
onClick={() =>
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>
)}
</div>
@@ -286,7 +292,7 @@ export const AgenticDocumentDisplay = ({
return (
<div
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 ? "py-3" : "border-transparent"} relative`}
onMouseEnter={() => setIsHovered(true)}

View File

@@ -1,4 +1,4 @@
import React, { KeyboardEvent, ChangeEvent } from "react";
import React, { KeyboardEvent, ChangeEvent, useContext } from "react";
import { searchState } from "./SearchSection";
import { MagnifyingGlass } from "@phosphor-icons/react";
@@ -17,6 +17,7 @@ import { SendIcon } from "../icons/icons";
import { Divider } from "@tremor/react";
import { CustomTooltip } from "../tooltip/CustomTooltip";
import KeyboardSymbol from "@/lib/browserUtilities";
import { SettingsContext } from "../settings/SettingsProvider";
export const AnimatedToggle = ({
isOn,
@@ -139,6 +140,8 @@ export const FullSearchBar = ({
}
};
const settings = useContext(SettingsContext);
return (
<div
className="
@@ -198,13 +201,17 @@ export const FullSearchBar = ({
{searchState == "reading" && (
<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>
)}
{searchState == "analyzing" && (
<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>
)}

View File

@@ -23,7 +23,7 @@ import { useFilters, useObjectState } from "@/lib/hooks";
import { questionValidationStreamed } from "@/lib/search/streamingQuestionValidation";
import { Persona } from "@/app/admin/assistants/interfaces";
import { computeAvailableFilters } from "@/lib/filters";
import { useRouter, useSearchParams } from "next/navigation";
import { redirect, useRouter, useSearchParams } from "next/navigation";
import { SettingsContext } from "../settings/SettingsProvider";
import { HistorySidebar } from "@/app/chat/sessionSidebar/HistorySidebar";
import { ChatSession, SearchSession } from "@/app/chat/interfaces";
@@ -339,7 +339,6 @@ export const SearchSection = ({
if ((overrideMessage || query) == "") {
return;
}
setSearchResponse;
setAgenticResults(agentic!);
resetInput();
setContentEnriched(false);
@@ -456,6 +455,12 @@ export const SearchSection = ({
};
}, [router]);
useEffect(() => {
if (settings?.isMobile) {
router.push("/chat");
}
}, [settings?.isMobile, router]);
const handleTransitionEnd = (e: React.TransitionEvent<HTMLDivElement>) => {
if (e.propertyName === "opacity" && !firstSearch) {
const target = e.target as HTMLDivElement;
@@ -474,20 +479,19 @@ export const SearchSection = ({
sidebarElementRef,
showDocSidebar,
setShowDocSidebar,
mobile: settings?.isMobile,
});
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
ref={sidebarElementRef}
className={`
flex-none
fixed -8
fixed
left-0
z-20
overflow-y-hidden
sidebar
z-30
bg-background-100
h-screen
transition-all
@@ -514,7 +518,7 @@ export const SearchSection = ({
<div className="absolute left-0 w-full top-0">
<FunctionalHeader
showSidebar={showDocSidebar}
toggleSidebar={toggleSidebar}
page="search"
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="absolute z-10 top-12 left-0 hidden 2xl:block w-52 3xl:w-64">
{(ccPairs.length > 0 || documentSets.length > 0) && (
<div className="desktop:px-24 w-full pt-10 relative max-w-[2000px] xl:max-w-[1430px] mx-auto">
<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">
{!settings?.isMobile &&
(ccPairs.length > 0 || documentSets.length > 0) && (
<SourceSelector
{...filterManager}
showDocSidebar={showDocSidebar || toggledSidebar}
@@ -549,6 +554,28 @@ export const SearchSection = ({
</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">
{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
className={`transition-all duration-500 ease-in-out overflow-hidden
${
@@ -568,9 +595,10 @@ export const SearchSection = ({
</div>
</div>
</div>
<FullSearchBar
toggleAgentic={disabledAgentic ? undefined : toggleAgentic}
toggleAgentic={
disabledAgentic ? undefined : toggleAgentic
}
agentic={agentic}
searchState={searchState}
query={query}
@@ -580,7 +608,9 @@ export const SearchSection = ({
await onSearch({ agentic, offset: 0 });
}}
/>
</div>
{!settings?.isMobile && (
<div className="mt-6">
{!(agenticResults && isFetching) || disabledAgentic ? (
<SearchResultsDisplay
@@ -598,6 +628,7 @@ export const SearchSection = ({
<></>
)}
</div>
)}
</div>
</div>
}

View File

@@ -1,7 +1,7 @@
"use client";
import { CombinedSettings } from "@/app/admin/settings/interfaces";
import { createContext } from "react";
import { createContext, useEffect, useState } from "react";
export const SettingsContext = createContext<CombinedSettings | null>(null);
@@ -12,8 +12,20 @@ export function SettingsProvider({
children: React.ReactNode | JSX.Element;
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 (
<SettingsContext.Provider value={settings}>
<SettingsContext.Provider value={{ ...settings, isMobile }}>
{children}
</SettingsContext.Provider>
);

View File

@@ -36,6 +36,7 @@ export interface CombinedSettings {
settings: Settings;
enterpriseSettings: EnterpriseSettings | null;
customAnalyticsScript: string | null;
isMobile?: boolean;
}
let cachedSettings: CombinedSettings;

View File

@@ -45,6 +45,8 @@ module.exports = {
"2xl": "1420px",
"3xl": "1700px",
"4xl": "2000px",
mobile: { max: "767px" },
desktop: "768px",
},
fontFamily: {
sans: ["var(--font-inter)"],