Allow re-sizing of document sidebar + make central chat smaller on small screens (#832)

This commit is contained in:
Chris Weaver 2023-12-17 18:17:43 -08:00 committed by GitHub
parent a099f8e296
commit c7a91b1819
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 402 additions and 205 deletions

View File

@ -35,6 +35,8 @@ import { buildFilters } from "@/lib/search/utils";
import { QA, SearchTypeSelector } from "./modifiers/SearchTypeSelector"; import { QA, SearchTypeSelector } from "./modifiers/SearchTypeSelector";
import { SelectedDocuments } from "./modifiers/SelectedDocuments"; import { SelectedDocuments } from "./modifiers/SelectedDocuments";
import { usePopup } from "@/components/admin/connectors/Popup"; import { usePopup } from "@/components/admin/connectors/Popup";
import { ResizableSection } from "@/components/resizable/ResizableSection";
import { DanswerInitializingLoader } from "@/components/DanswerInitializingLoader";
const MAX_INPUT_HEIGHT = 200; const MAX_INPUT_HEIGHT = 200;
@ -45,6 +47,7 @@ export const Chat = ({
availableSources, availableSources,
availableDocumentSets, availableDocumentSets,
availablePersonas, availablePersonas,
documentSidebarInitialWidth,
shouldhideBeforeScroll, shouldhideBeforeScroll,
}: { }: {
existingChatSessionId: number | null; existingChatSessionId: number | null;
@ -53,6 +56,7 @@ export const Chat = ({
availableSources: ValidSources[]; availableSources: ValidSources[];
availableDocumentSets: DocumentSet[]; availableDocumentSets: DocumentSet[];
availablePersonas: Persona[]; availablePersonas: Persona[];
documentSidebarInitialWidth?: number;
shouldhideBeforeScroll?: boolean; shouldhideBeforeScroll?: boolean;
}) => { }) => {
const router = useRouter(); const router = useRouter();
@ -113,7 +117,6 @@ export const Chat = ({
}); });
// scroll to bottom initially // scroll to bottom initially
console.log(shouldhideBeforeScroll);
const [hasPerformedInitialScroll, setHasPerformedInitialScroll] = useState( const [hasPerformedInitialScroll, setHasPerformedInitialScroll] = useState(
shouldhideBeforeScroll !== true shouldhideBeforeScroll !== true
); );
@ -140,6 +143,35 @@ export const Chat = ({
} }
}, [message]); }, [message]);
// used for resizing of the document sidebar
const masterFlexboxRef = useRef<HTMLDivElement>(null);
const [maxDocumentSidebarWidth, setMaxDocumentSidebarWidth] = useState<
number | null
>(null);
const adjustDocumentSidebarWidth = () => {
if (masterFlexboxRef.current && document.documentElement.clientWidth) {
// numbers below are based on the actual width the center section for different
// screen sizes. `1700` corresponds to the custom "3xl" tailwind breakpoint
// NOTE: some buffer is needed to account for scroll bars
setMaxDocumentSidebarWidth(
masterFlexboxRef.current.clientWidth -
(document.documentElement.clientWidth > 1700 ? 950 : 810)
);
}
};
useEffect(() => {
adjustDocumentSidebarWidth(); // Adjust the width on initial render
window.addEventListener("resize", adjustDocumentSidebarWidth); // Add resize event listener
return () => {
window.removeEventListener("resize", adjustDocumentSidebarWidth); // Cleanup the event listener
};
}, []);
if (!documentSidebarInitialWidth && maxDocumentSidebarWidth) {
documentSidebarInitialWidth = Math.min(700, maxDocumentSidebarWidth);
}
const onSubmit = async (messageOverride?: string) => { const onSubmit = async (messageOverride?: string) => {
let currChatSessionId: number; let currChatSessionId: number;
let isNewSession = chatSessionId === null; let isNewSession = chatSessionId === null;
@ -301,7 +333,7 @@ export const Chat = ({
}; };
return ( return (
<div className="flex w-full overflow-x-hidden"> <div className="flex w-full overflow-x-hidden" ref={masterFlexboxRef}>
{popup} {popup}
{currentFeedback && ( {currentFeedback && (
<FeedbackModal <FeedbackModal
@ -314,154 +346,156 @@ export const Chat = ({
/> />
)} )}
<div className="w-full sm:relative"> {documentSidebarInitialWidth !== undefined ? (
<div <>
className="w-full h-screen flex flex-col overflow-y-auto relative" <div className="w-full sm:relative">
ref={scrollableDivRef} <div
> className="w-full h-screen flex flex-col overflow-y-auto relative"
{selectedPersona && ( ref={scrollableDivRef}
<div className="sticky top-0 left-80 z-10 w-full bg-background/90"> >
<div className="ml-2 p-1 rounded mt-2 w-fit"> {selectedPersona && (
<ChatPersonaSelector <div className="sticky top-0 left-80 z-10 w-full bg-background/90">
personas={availablePersonas} <div className="ml-2 p-1 rounded mt-2 w-fit">
selectedPersonaId={selectedPersona?.id} <ChatPersonaSelector
onPersonaChange={(persona) => { personas={availablePersonas}
if (persona) { selectedPersonaId={selectedPersona?.id}
setSelectedPersona(persona); onPersonaChange={(persona) => {
} if (persona) {
}} setSelectedPersona(persona);
/>
</div>
</div>
)}
{messageHistory.length === 0 && !isStreaming && (
<div className="flex justify-center items-center h-full">
<div>
<div className="flex">
<div className="mx-auto h-[80px] w-[80px]">
<Image
src="/logo.png"
alt="Logo"
width="1419"
height="1520"
/>
</div>
</div>
<div className="text-2xl font-bold text-strong p-4">
What are you looking for today?
</div>
</div>
</div>
)}
<div
className={
"mt-4 pt-12 sm:pt-0 mx-8" +
(hasPerformedInitialScroll ? "" : " invisible")
}
>
{messageHistory.map((message, i) => {
if (message.type === "user") {
return (
<div key={i}>
<HumanMessage content={message.message} />
</div>
);
} else if (message.type === "assistant") {
const isShowingRetrieved =
(selectedMessageForDocDisplay !== null &&
selectedMessageForDocDisplay === message.messageId) ||
(selectedMessageForDocDisplay === -1 &&
i === messageHistory.length - 1);
return (
<div key={i}>
<AIMessage
messageId={message.messageId}
content={message.message}
query={messageHistory[i]?.query || undefined}
citedDocuments={getCitedDocumentsFromMessage(message)}
isComplete={
i !== messageHistory.length - 1 || !isStreaming
}
hasDocs={
(message.documents && message.documents.length > 0) ===
true
}
handleFeedback={
i === messageHistory.length - 1 && isStreaming
? undefined
: (feedbackType) =>
setCurrentFeedback([
feedbackType,
message.messageId as number,
])
}
isCurrentlyShowingRetrieved={isShowingRetrieved}
handleShowRetrieved={(messageNumber) => {
if (isShowingRetrieved) {
setSelectedMessageForDocDisplay(null);
} else {
if (messageNumber !== null) {
setSelectedMessageForDocDisplay(messageNumber);
} else {
setSelectedMessageForDocDisplay(-1);
}
} }
}} }}
/> />
</div> </div>
);
} else {
return (
<div key={i}>
<AIMessage
messageId={message.messageId}
content={
<p className="text-red-700 text-sm my-auto">
{message.message}
</p>
}
/>
</div>
);
}
})}
{isStreaming &&
messageHistory.length &&
messageHistory[messageHistory.length - 1].type === "user" && (
<div key={messageHistory.length}>
<AIMessage
messageId={null}
content={
<div className="text-sm my-auto">
<ThreeDots
height="30"
width="50"
color="#3b82f6"
ariaLabel="grid-loading"
radius="12.5"
wrapperStyle={{}}
wrapperClass=""
visible={true}
/>
</div>
}
/>
</div> </div>
)} )}
{/* Some padding at the bottom so the search bar has space at the bottom to not cover the last message*/} {messageHistory.length === 0 && !isStreaming && (
<div className={`min-h-[200px] w-full`}></div> <div className="flex justify-center items-center h-full">
<div className="px-8 w-searchbar-small 3xl:w-searchbar">
<div className="flex">
<div className="mx-auto h-[80px] w-[80px]">
<Image
src="/logo.png"
alt="Logo"
width="1419"
height="1520"
/>
</div>
</div>
<div className="mx-auto text-2xl font-bold text-strong p-4 w-fit">
What are you looking for today?
</div>
</div>
</div>
)}
<div ref={endDivRef} /> <div
</div> className={
</div> "mt-4 pt-12 sm:pt-0 mx-8" +
(hasPerformedInitialScroll ? "" : " invisible")
}
>
{messageHistory.map((message, i) => {
if (message.type === "user") {
return (
<div key={i}>
<HumanMessage content={message.message} />
</div>
);
} else if (message.type === "assistant") {
const isShowingRetrieved =
(selectedMessageForDocDisplay !== null &&
selectedMessageForDocDisplay === message.messageId) ||
(selectedMessageForDocDisplay === -1 &&
i === messageHistory.length - 1);
return (
<div key={i}>
<AIMessage
messageId={message.messageId}
content={message.message}
query={messageHistory[i]?.query || undefined}
citedDocuments={getCitedDocumentsFromMessage(message)}
isComplete={
i !== messageHistory.length - 1 || !isStreaming
}
hasDocs={
(message.documents &&
message.documents.length > 0) === true
}
handleFeedback={
i === messageHistory.length - 1 && isStreaming
? undefined
: (feedbackType) =>
setCurrentFeedback([
feedbackType,
message.messageId as number,
])
}
isCurrentlyShowingRetrieved={isShowingRetrieved}
handleShowRetrieved={(messageNumber) => {
if (isShowingRetrieved) {
setSelectedMessageForDocDisplay(null);
} else {
if (messageNumber !== null) {
setSelectedMessageForDocDisplay(messageNumber);
} else {
setSelectedMessageForDocDisplay(-1);
}
}
}}
/>
</div>
);
} else {
return (
<div key={i}>
<AIMessage
messageId={message.messageId}
content={
<p className="text-red-700 text-sm my-auto">
{message.message}
</p>
}
/>
</div>
);
}
})}
<div className="absolute bottom-0 z-10 w-full bg-background border-t border-border"> {isStreaming &&
<div className="w-full pb-4 pt-2"> messageHistory.length &&
{/* {(isStreaming || messageHistory.length > 0) && ( messageHistory[messageHistory.length - 1].type === "user" && (
<div key={messageHistory.length}>
<AIMessage
messageId={null}
content={
<div className="text-sm my-auto">
<ThreeDots
height="30"
width="50"
color="#3b82f6"
ariaLabel="grid-loading"
radius="12.5"
wrapperStyle={{}}
wrapperClass=""
visible={true}
/>
</div>
}
/>
</div>
)}
{/* Some padding at the bottom so the search bar has space at the bottom to not cover the last message*/}
<div className={`min-h-[200px] w-full`}></div>
<div ref={endDivRef} />
</div>
</div>
<div className="absolute bottom-0 z-10 w-full bg-background border-t border-border">
<div className="w-full pb-4 pt-2">
{/* {(isStreaming || messageHistory.length > 0) && (
<div className="flex justify-center w-full"> <div className="flex justify-center w-full">
<div className="w-[800px] flex"> <div className="w-[800px] flex">
<div className="cursor-pointer flex w-fit p-2 rounded border border-neutral-400 text-sm hover:bg-neutral-200 ml-auto mr-4"> <div className="cursor-pointer flex w-fit p-2 rounded border border-neutral-400 text-sm hover:bg-neutral-200 ml-auto mr-4">
@ -491,33 +525,35 @@ export const Chat = ({
</div> </div>
)} */} )} */}
<div className="flex"> <div className="flex">
<div className="w-searchbar mx-auto px-4 pt-1 flex"> <div className="w-searchbar-small 3xl:w-searchbar mx-auto px-4 pt-1 flex">
<div className="mr-3"> <div className="mr-3">
<SearchTypeSelector <SearchTypeSelector
selectedSearchType={selectedSearchType} selectedSearchType={selectedSearchType}
setSelectedSearchType={setSelectedSearchType} setSelectedSearchType={setSelectedSearchType}
/> />
</div>
{selectedDocuments.length > 0 ? (
<SelectedDocuments
selectedDocuments={selectedDocuments}
/>
) : (
<ChatFilters
{...filterManager}
existingSources={availableSources}
availableDocumentSets={availableDocumentSets}
/>
)}
</div>
</div> </div>
{selectedDocuments.length > 0 ? ( <div className="flex justify-center py-2 max-w-screen-lg mx-auto mb-2">
<SelectedDocuments selectedDocuments={selectedDocuments} /> <div className="w-full shrink relative px-4 w-searchbar-small 3xl:w-searchbar mx-auto">
) : ( <textarea
<ChatFilters ref={textareaRef}
{...filterManager} autoFocus
existingSources={availableSources} className={`
availableDocumentSets={availableDocumentSets}
/>
)}
</div>
</div>
<div className="flex justify-center py-2 max-w-screen-lg mx-auto mb-2">
<div className="w-full shrink relative px-4 w-searchbar mx-auto">
<textarea
ref={textareaRef}
autoFocus
className={`
opacity-100 opacity-100
w-full w-full
shrink shrink
@ -542,42 +578,59 @@ export const Chat = ({
overscroll-contain overscroll-contain
resize-none resize-none
`} `}
style={{ scrollbarWidth: "thin" }} style={{ scrollbarWidth: "thin" }}
role="textarea" role="textarea"
aria-multiline aria-multiline
placeholder="Ask me anything..." placeholder="Ask me anything..."
value={message} value={message}
onChange={(e) => setMessage(e.target.value)} onChange={(e) => setMessage(e.target.value)}
onKeyDown={(event) => { onKeyDown={(event) => {
if (event.key === "Enter" && !event.shiftKey) { if (event.key === "Enter" && !event.shiftKey) {
onSubmit(); onSubmit();
event.preventDefault(); event.preventDefault();
} }
}} }}
suppressContentEditableWarning={true} suppressContentEditableWarning={true}
/>
<div className="absolute bottom-4 right-10">
<div className={"cursor-pointer"} onClick={() => onSubmit()}>
<FiSend
size={18}
className={
"text-emphasis w-9 h-9 p-2 rounded-lg " +
(message ? "bg-blue-200" : "")
}
/> />
<div className="absolute bottom-4 right-10">
<div
className={"cursor-pointer"}
onClick={() => onSubmit()}
>
<FiSend
size={18}
className={
"text-emphasis w-9 h-9 p-2 rounded-lg " +
(message ? "bg-blue-200" : "")
}
/>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
<DocumentSidebar <ResizableSection
selectedMessage={aiMessage} intialWidth={documentSidebarInitialWidth}
selectedDocuments={selectedDocuments} minWidth={400}
setSelectedDocuments={setSelectedDocuments} maxWidth={maxDocumentSidebarWidth || undefined}
/> >
<DocumentSidebar
selectedMessage={aiMessage}
selectedDocuments={selectedDocuments}
setSelectedDocuments={setSelectedDocuments}
/>
</ResizableSection>
</>
) : (
<div className="mx-auto h-full flex flex-col">
<div className="my-auto">
<DanswerInitializingLoader />
</div>
</div>
)}
</div> </div>
); );
}; };

View File

@ -15,6 +15,8 @@ import { Persona } from "../admin/personas/interfaces";
import { InstantSSRAutoRefresh } from "@/components/SSRAutoRefresh"; import { InstantSSRAutoRefresh } from "@/components/SSRAutoRefresh";
import { WelcomeModal } from "@/components/WelcomeModal"; import { WelcomeModal } from "@/components/WelcomeModal";
import { ApiKeyModal } from "@/components/openai/ApiKeyModal"; import { ApiKeyModal } from "@/components/openai/ApiKeyModal";
import { cookies } from "next/headers";
import { DOCUMENT_SIDEBAR_WIDTH_COOKIE_NAME } from "@/components/resizable/contants";
export default async function ChatPage({ export default async function ChatPage({
chatId, chatId,
@ -175,6 +177,13 @@ export default async function ChatPage({
); );
} }
const documentSidebarCookieInitialWidth = cookies().get(
DOCUMENT_SIDEBAR_WIDTH_COOKIE_NAME
);
const finalDocumentSidebarInitialWidth = documentSidebarCookieInitialWidth
? parseInt(documentSidebarCookieInitialWidth.value)
: undefined;
return ( return (
<> <>
<InstantSSRAutoRefresh /> <InstantSSRAutoRefresh />
@ -196,6 +205,7 @@ export default async function ChatPage({
availableSources={availableSources} availableSources={availableSources}
availableDocumentSets={documentSets} availableDocumentSets={documentSets}
availablePersonas={personas} availablePersonas={personas}
documentSidebarInitialWidth={finalDocumentSidebarInitialWidth}
shouldhideBeforeScroll={shouldhideBeforeScroll} shouldhideBeforeScroll={shouldhideBeforeScroll}
/> />
</div> </div>

View File

@ -45,13 +45,11 @@ export function DocumentSidebar({
<div <div
className={` className={`
flex-initial flex-initial
w-document-sidebar
border-l
border-border
overflow-y-hidden overflow-y-hidden
flex flex
flex-col flex-col
pt-4 pt-4
w-full
`} `}
id="document-sidebar" id="document-sidebar"
> >
@ -117,7 +115,7 @@ export function DocumentSidebar({
</div> </div>
</div> </div>
) : ( ) : (
<div className="ml-4"> <div className="ml-4 mr-3">
<Text> <Text>
When you run ask a question, the retrieved documents will show up When you run ask a question, the retrieved documents will show up
here! here!

View File

@ -52,7 +52,7 @@ export const AIMessage = ({
const [copyClicked, setCopyClicked] = useState(false); const [copyClicked, setCopyClicked] = useState(false);
return ( return (
<div className={"py-5 px-5 flex -mr-6 w-full"}> <div className={"py-5 px-5 flex -mr-6 w-full"}>
<div className="mx-auto w-searchbar relative"> <div className="mx-auto w-searchbar-small 3xl:w-searchbar relative">
<div className="ml-8"> <div className="ml-8">
<div className="flex"> <div className="flex">
<div className="p-1 bg-ai rounded-lg h-fit my-auto"> <div className="p-1 bg-ai rounded-lg h-fit my-auto">
@ -67,7 +67,7 @@ export const AIMessage = ({
hasDocs && hasDocs &&
handleShowRetrieved !== undefined && handleShowRetrieved !== undefined &&
isCurrentlyShowingRetrieved !== undefined && ( isCurrentlyShowingRetrieved !== undefined && (
<div className="flex w-message-default absolute ml-8"> <div className="flex w-message-small 3xl:w-message-default absolute ml-8">
<div className="ml-auto"> <div className="ml-auto">
<ShowHideDocsButton <ShowHideDocsButton
messageId={messageId} messageId={messageId}
@ -79,7 +79,7 @@ export const AIMessage = ({
)} )}
</div> </div>
<div className="w-message-default break-words mt-1 ml-8"> <div className="w-message-small 3xl:w-message-default break-words mt-1 ml-8">
{query !== undefined && {query !== undefined &&
handleShowRetrieved !== undefined && handleShowRetrieved !== undefined &&
isCurrentlyShowingRetrieved !== undefined && ( isCurrentlyShowingRetrieved !== undefined && (
@ -194,7 +194,7 @@ export const HumanMessage = ({
}) => { }) => {
return ( return (
<div className="py-5 px-5 flex -mr-6 w-full"> <div className="py-5 px-5 flex -mr-6 w-full">
<div className="mx-auto w-searchbar"> <div className="mx-auto w-searchbar-small 3xl:w-searchbar">
<div className="ml-8"> <div className="ml-8">
<div className="flex"> <div className="flex">
<div className="p-1 bg-user rounded-lg h-fit"> <div className="p-1 bg-user rounded-lg h-fit">
@ -205,8 +205,8 @@ export const HumanMessage = ({
<div className="font-bold text-emphasis ml-2 my-auto">You</div> <div className="font-bold text-emphasis ml-2 my-auto">You</div>
</div> </div>
<div className="mx-auto mt-1 ml-8 w-full w-message-default flex flex-wrap"> <div className="mx-auto mt-1 ml-8 w-message-small 3xl:w-message-default flex flex-wrap">
<div className="w-full sm:w-full w-message-default break-words"> <div className="w-full sm:w-full w-message-small 3xl:w-message-default break-words">
{typeof content === "string" ? ( {typeof content === "string" ? (
<ReactMarkdown <ReactMarkdown
className="prose max-w-full" className="prose max-w-full"

View File

@ -53,6 +53,7 @@ export function ChatSessionDisplay({
className="flex my-1" className="flex my-1"
key={chatSession.id} key={chatSession.id}
href={`/chat/${chatSession.id}`} href={`/chat/${chatSession.id}`}
scroll={false}
> >
<BasicSelectable fullWidth selected={isSelected}> <BasicSelectable fullWidth selected={isSelected}>
<div className="flex"> <div className="flex">

View File

@ -0,0 +1,13 @@
import { Bold } from "@tremor/react";
import Image from "next/image";
export function DanswerInitializingLoader() {
return (
<div className="mx-auto animate-pulse">
<div className="h-24 w-24 mx-auto mb-3">
<Image src="/logo.png" alt="Logo" width="1419" height="1520" />
</div>
<Bold>Initializing Danswer</Bold>
</div>
);
}

View File

@ -0,0 +1,116 @@
"use client";
import React, { useEffect, useState } from "react";
import Cookies from "js-cookie";
import { DOCUMENT_SIDEBAR_WIDTH_COOKIE_NAME } from "./contants";
function applyMinAndMax(
width: number,
minWidth: number | undefined,
maxWidth: number | undefined
) {
let newWidth = width;
if (minWidth) {
newWidth = Math.max(width, minWidth); // Ensure the width doesn't go below a minimum value
}
if (maxWidth) {
newWidth = Math.min(newWidth, maxWidth); // Ensure the width doesn't exceed a maximum value
}
return newWidth;
}
export function ResizableSection({
children,
intialWidth,
minWidth,
maxWidth,
}: {
children: JSX.Element;
intialWidth: number;
minWidth: number;
maxWidth?: number;
}) {
const [width, setWidth] = useState<number>(intialWidth);
const [isResizing, setIsResizing] = useState<boolean>(false);
useEffect(() => {
const newWidth = applyMinAndMax(width, minWidth, maxWidth);
setWidth(newWidth);
Cookies.set(DOCUMENT_SIDEBAR_WIDTH_COOKIE_NAME, newWidth.toString(), {
path: "/",
});
}, [minWidth, maxWidth]);
const startResizing = (mouseDownEvent: React.MouseEvent<HTMLDivElement>) => {
setIsResizing(true);
// Disable text selection
document.body.style.userSelect = "none";
document.body.style.cursor = "col-resize";
// Record the initial position of the mouse
const startX = mouseDownEvent.clientX;
const handleMouseMove = (mouseMoveEvent: MouseEvent) => {
// Calculate the change in position
const delta = mouseMoveEvent.clientX - startX;
let newWidth = applyMinAndMax(width - delta, minWidth, maxWidth);
setWidth(newWidth);
Cookies.set(DOCUMENT_SIDEBAR_WIDTH_COOKIE_NAME, newWidth.toString(), {
path: "/",
});
};
const stopResizing = () => {
// Re-enable text selection
document.body.style.userSelect = "";
document.body.style.cursor = "";
// Remove the event listeners
window.removeEventListener("mousemove", handleMouseMove);
window.removeEventListener("mouseup", stopResizing);
setIsResizing(false);
};
window.addEventListener("mousemove", handleMouseMove);
window.addEventListener("mouseup", stopResizing);
};
return (
<div className="flex h-full">
<div
className={`
-mr-1
pr-1
z-40
h-full
`}
>
<div
onMouseDown={startResizing}
className={`
cursor-col-resize
border-l
border-border
h-full
w-full
transition-all duration-300 ease-in hover:border-border-strong hover:border-l-2
${
isResizing
? "transition-all duration-300 ease-in border-border-strong border-l-2"
: ""
}
`}
></div>
</div>
<div
style={{ width: `${width}px` }}
className={`resize-section h-full flex`}
>
{children}
</div>
</div>
);
}
export default ResizableSection;

View File

@ -0,0 +1 @@
export const DOCUMENT_SIDEBAR_WIDTH_COOKIE_NAME = "documentSidebarWidth";

View File

@ -16,11 +16,16 @@ module.exports = {
transparent: "transparent", transparent: "transparent",
current: "currentColor", current: "currentColor",
extend: { extend: {
screens: {
"3xl": "1700px",
},
fontFamily: { fontFamily: {
sans: ["var(--font-inter)"], sans: ["var(--font-inter)"],
}, },
width: { width: {
"message-small": "600px",
"message-default": "740px", "message-default": "740px",
"searchbar-small": "710px",
searchbar: "850px", searchbar: "850px",
"document-sidebar": "800px", "document-sidebar": "800px",
"document-sidebar-large": "1000px", "document-sidebar-large": "1000px",