mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-05-03 00:10:24 +02:00
Allow re-sizing of document sidebar + make central chat smaller on small screens (#832)
This commit is contained in:
parent
a099f8e296
commit
c7a91b1819
@ -35,6 +35,8 @@ import { buildFilters } from "@/lib/search/utils";
|
||||
import { QA, SearchTypeSelector } from "./modifiers/SearchTypeSelector";
|
||||
import { SelectedDocuments } from "./modifiers/SelectedDocuments";
|
||||
import { usePopup } from "@/components/admin/connectors/Popup";
|
||||
import { ResizableSection } from "@/components/resizable/ResizableSection";
|
||||
import { DanswerInitializingLoader } from "@/components/DanswerInitializingLoader";
|
||||
|
||||
const MAX_INPUT_HEIGHT = 200;
|
||||
|
||||
@ -45,6 +47,7 @@ export const Chat = ({
|
||||
availableSources,
|
||||
availableDocumentSets,
|
||||
availablePersonas,
|
||||
documentSidebarInitialWidth,
|
||||
shouldhideBeforeScroll,
|
||||
}: {
|
||||
existingChatSessionId: number | null;
|
||||
@ -53,6 +56,7 @@ export const Chat = ({
|
||||
availableSources: ValidSources[];
|
||||
availableDocumentSets: DocumentSet[];
|
||||
availablePersonas: Persona[];
|
||||
documentSidebarInitialWidth?: number;
|
||||
shouldhideBeforeScroll?: boolean;
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
@ -113,7 +117,6 @@ export const Chat = ({
|
||||
});
|
||||
|
||||
// scroll to bottom initially
|
||||
console.log(shouldhideBeforeScroll);
|
||||
const [hasPerformedInitialScroll, setHasPerformedInitialScroll] = useState(
|
||||
shouldhideBeforeScroll !== true
|
||||
);
|
||||
@ -140,6 +143,35 @@ export const Chat = ({
|
||||
}
|
||||
}, [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) => {
|
||||
let currChatSessionId: number;
|
||||
let isNewSession = chatSessionId === null;
|
||||
@ -301,7 +333,7 @@ export const Chat = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex w-full overflow-x-hidden">
|
||||
<div className="flex w-full overflow-x-hidden" ref={masterFlexboxRef}>
|
||||
{popup}
|
||||
{currentFeedback && (
|
||||
<FeedbackModal
|
||||
@ -314,154 +346,156 @@ export const Chat = ({
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="w-full sm:relative">
|
||||
<div
|
||||
className="w-full h-screen flex flex-col overflow-y-auto relative"
|
||||
ref={scrollableDivRef}
|
||||
>
|
||||
{selectedPersona && (
|
||||
<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">
|
||||
<ChatPersonaSelector
|
||||
personas={availablePersonas}
|
||||
selectedPersonaId={selectedPersona?.id}
|
||||
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);
|
||||
}
|
||||
{documentSidebarInitialWidth !== undefined ? (
|
||||
<>
|
||||
<div className="w-full sm:relative">
|
||||
<div
|
||||
className="w-full h-screen flex flex-col overflow-y-auto relative"
|
||||
ref={scrollableDivRef}
|
||||
>
|
||||
{selectedPersona && (
|
||||
<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">
|
||||
<ChatPersonaSelector
|
||||
personas={availablePersonas}
|
||||
selectedPersonaId={selectedPersona?.id}
|
||||
onPersonaChange={(persona) => {
|
||||
if (persona) {
|
||||
setSelectedPersona(persona);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</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>
|
||||
)}
|
||||
|
||||
{/* 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>
|
||||
{messageHistory.length === 0 && !isStreaming && (
|
||||
<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>
|
||||
<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>
|
||||
);
|
||||
} 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">
|
||||
<div className="w-full pb-4 pt-2">
|
||||
{/* {(isStreaming || messageHistory.length > 0) && (
|
||||
{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>
|
||||
)}
|
||||
|
||||
{/* 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="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">
|
||||
@ -491,33 +525,35 @@ export const Chat = ({
|
||||
</div>
|
||||
)} */}
|
||||
|
||||
<div className="flex">
|
||||
<div className="w-searchbar mx-auto px-4 pt-1 flex">
|
||||
<div className="mr-3">
|
||||
<SearchTypeSelector
|
||||
selectedSearchType={selectedSearchType}
|
||||
setSelectedSearchType={setSelectedSearchType}
|
||||
/>
|
||||
<div className="flex">
|
||||
<div className="w-searchbar-small 3xl:w-searchbar mx-auto px-4 pt-1 flex">
|
||||
<div className="mr-3">
|
||||
<SearchTypeSelector
|
||||
selectedSearchType={selectedSearchType}
|
||||
setSelectedSearchType={setSelectedSearchType}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{selectedDocuments.length > 0 ? (
|
||||
<SelectedDocuments
|
||||
selectedDocuments={selectedDocuments}
|
||||
/>
|
||||
) : (
|
||||
<ChatFilters
|
||||
{...filterManager}
|
||||
existingSources={availableSources}
|
||||
availableDocumentSets={availableDocumentSets}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedDocuments.length > 0 ? (
|
||||
<SelectedDocuments selectedDocuments={selectedDocuments} />
|
||||
) : (
|
||||
<ChatFilters
|
||||
{...filterManager}
|
||||
existingSources={availableSources}
|
||||
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={`
|
||||
<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-small 3xl:w-searchbar mx-auto">
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
autoFocus
|
||||
className={`
|
||||
opacity-100
|
||||
w-full
|
||||
shrink
|
||||
@ -542,42 +578,59 @@ export const Chat = ({
|
||||
overscroll-contain
|
||||
resize-none
|
||||
`}
|
||||
style={{ scrollbarWidth: "thin" }}
|
||||
role="textarea"
|
||||
aria-multiline
|
||||
placeholder="Ask me anything..."
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
onSubmit();
|
||||
event.preventDefault();
|
||||
}
|
||||
}}
|
||||
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" : "")
|
||||
}
|
||||
style={{ scrollbarWidth: "thin" }}
|
||||
role="textarea"
|
||||
aria-multiline
|
||||
placeholder="Ask me anything..."
|
||||
value={message}
|
||||
onChange={(e) => setMessage(e.target.value)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
onSubmit();
|
||||
event.preventDefault();
|
||||
}
|
||||
}}
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DocumentSidebar
|
||||
selectedMessage={aiMessage}
|
||||
selectedDocuments={selectedDocuments}
|
||||
setSelectedDocuments={setSelectedDocuments}
|
||||
/>
|
||||
<ResizableSection
|
||||
intialWidth={documentSidebarInitialWidth}
|
||||
minWidth={400}
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
@ -15,6 +15,8 @@ import { Persona } from "../admin/personas/interfaces";
|
||||
import { InstantSSRAutoRefresh } from "@/components/SSRAutoRefresh";
|
||||
import { WelcomeModal } from "@/components/WelcomeModal";
|
||||
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({
|
||||
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 (
|
||||
<>
|
||||
<InstantSSRAutoRefresh />
|
||||
@ -196,6 +205,7 @@ export default async function ChatPage({
|
||||
availableSources={availableSources}
|
||||
availableDocumentSets={documentSets}
|
||||
availablePersonas={personas}
|
||||
documentSidebarInitialWidth={finalDocumentSidebarInitialWidth}
|
||||
shouldhideBeforeScroll={shouldhideBeforeScroll}
|
||||
/>
|
||||
</div>
|
||||
|
@ -45,13 +45,11 @@ export function DocumentSidebar({
|
||||
<div
|
||||
className={`
|
||||
flex-initial
|
||||
w-document-sidebar
|
||||
border-l
|
||||
border-border
|
||||
overflow-y-hidden
|
||||
flex
|
||||
flex-col
|
||||
pt-4
|
||||
w-full
|
||||
`}
|
||||
id="document-sidebar"
|
||||
>
|
||||
@ -117,7 +115,7 @@ export function DocumentSidebar({
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="ml-4">
|
||||
<div className="ml-4 mr-3">
|
||||
<Text>
|
||||
When you run ask a question, the retrieved documents will show up
|
||||
here!
|
||||
|
@ -52,7 +52,7 @@ export const AIMessage = ({
|
||||
const [copyClicked, setCopyClicked] = useState(false);
|
||||
return (
|
||||
<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="flex">
|
||||
<div className="p-1 bg-ai rounded-lg h-fit my-auto">
|
||||
@ -67,7 +67,7 @@ export const AIMessage = ({
|
||||
hasDocs &&
|
||||
handleShowRetrieved !== 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">
|
||||
<ShowHideDocsButton
|
||||
messageId={messageId}
|
||||
@ -79,7 +79,7 @@ export const AIMessage = ({
|
||||
)}
|
||||
</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 &&
|
||||
handleShowRetrieved !== undefined &&
|
||||
isCurrentlyShowingRetrieved !== undefined && (
|
||||
@ -194,7 +194,7 @@ export const HumanMessage = ({
|
||||
}) => {
|
||||
return (
|
||||
<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="flex">
|
||||
<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>
|
||||
<div className="mx-auto mt-1 ml-8 w-full w-message-default flex flex-wrap">
|
||||
<div className="w-full sm:w-full w-message-default break-words">
|
||||
<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-small 3xl:w-message-default break-words">
|
||||
{typeof content === "string" ? (
|
||||
<ReactMarkdown
|
||||
className="prose max-w-full"
|
||||
|
@ -53,6 +53,7 @@ export function ChatSessionDisplay({
|
||||
className="flex my-1"
|
||||
key={chatSession.id}
|
||||
href={`/chat/${chatSession.id}`}
|
||||
scroll={false}
|
||||
>
|
||||
<BasicSelectable fullWidth selected={isSelected}>
|
||||
<div className="flex">
|
||||
|
13
web/src/components/DanswerInitializingLoader.tsx
Normal file
13
web/src/components/DanswerInitializingLoader.tsx
Normal 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>
|
||||
);
|
||||
}
|
116
web/src/components/resizable/ResizableSection.tsx
Normal file
116
web/src/components/resizable/ResizableSection.tsx
Normal 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;
|
1
web/src/components/resizable/contants.ts
Normal file
1
web/src/components/resizable/contants.ts
Normal file
@ -0,0 +1 @@
|
||||
export const DOCUMENT_SIDEBAR_WIDTH_COOKIE_NAME = "documentSidebarWidth";
|
@ -16,11 +16,16 @@ module.exports = {
|
||||
transparent: "transparent",
|
||||
current: "currentColor",
|
||||
extend: {
|
||||
screens: {
|
||||
"3xl": "1700px",
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ["var(--font-inter)"],
|
||||
},
|
||||
width: {
|
||||
"message-small": "600px",
|
||||
"message-default": "740px",
|
||||
"searchbar-small": "710px",
|
||||
searchbar: "850px",
|
||||
"document-sidebar": "800px",
|
||||
"document-sidebar-large": "1000px",
|
||||
|
Loading…
x
Reference in New Issue
Block a user