mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-05-05 17:30:26 +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 { 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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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>
|
||||||
|
@ -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!
|
||||||
|
@ -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"
|
||||||
|
@ -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">
|
||||||
|
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",
|
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",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user