mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-04-07 19:38:19 +02:00
Minor Update to UI (#1692)
New sidebar / chatbar color, hidable right-panel, and many more small tweaks. --------- Co-authored-by: pablodanswer <“pablo@danswer.ai”>
This commit is contained in:
parent
20c4cdbdda
commit
d6e5a98a22
@ -7,6 +7,7 @@ import { FiBookmark, FiCpu, FiInfo, FiX, FiZoomIn } from "react-icons/fi";
|
||||
import { HoverPopup } from "@/components/HoverPopup";
|
||||
import { Modal } from "@/components/Modal";
|
||||
import { useState } from "react";
|
||||
import { FaCaretDown, FaCaretRight } from "react-icons/fa";
|
||||
import { Logo } from "@/components/Logo";
|
||||
|
||||
const MAX_PERSONAS_TO_DISPLAY = 4;
|
||||
@ -37,6 +38,8 @@ export function ChatIntro({
|
||||
}) {
|
||||
const availableSourceMetadata = getSourceMetadataForSources(availableSources);
|
||||
|
||||
const [displaySources, setDisplaySources] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex justify-center items-center h-full">
|
||||
@ -90,12 +93,13 @@ export function ChatIntro({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{availableSources.length > 0 && (
|
||||
<div className="mt-2">
|
||||
<div className="mt-1">
|
||||
<p className="font-bold mb-1 mt-4 text-emphasis">
|
||||
Connected Sources:{" "}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<div className={`flex flex-wrap gap-2`}>
|
||||
{availableSourceMetadata.map((sourceMetadata) => (
|
||||
<span
|
||||
key={sourceMetadata.internalName}
|
||||
|
@ -45,7 +45,6 @@ import { useDocumentSelection } from "./useDocumentSelection";
|
||||
import { useFilters, useLlmOverride } from "@/lib/hooks";
|
||||
import { computeAvailableFilters } from "@/lib/filters";
|
||||
import { FeedbackType } from "./types";
|
||||
import ResizableSection from "@/components/resizable/ResizableSection";
|
||||
import { DocumentSidebar } from "./documentSidebar/DocumentSidebar";
|
||||
import { DanswerInitializingLoader } from "@/components/DanswerInitializingLoader";
|
||||
import { FeedbackModal } from "./modal/FeedbackModal";
|
||||
@ -74,6 +73,10 @@ import { v4 as uuidv4 } from "uuid";
|
||||
import { orderAssistantsForUser } from "@/lib/assistants/orderAssistants";
|
||||
import { ChatPopup } from "./ChatPopup";
|
||||
import { ChatBanner } from "./ChatBanner";
|
||||
import { TbLayoutSidebarRightExpand } from "react-icons/tb";
|
||||
import { SIDEBAR_WIDTH_CONST } from "@/lib/constants";
|
||||
|
||||
import ResizableSection from "@/components/resizable/ResizableSection";
|
||||
|
||||
const MAX_INPUT_HEIGHT = 200;
|
||||
const TEMP_USER_MESSAGE_ID = -1;
|
||||
@ -256,6 +259,19 @@ export function ChatPage({
|
||||
initialSessionFetch();
|
||||
}, [existingChatSessionId]);
|
||||
|
||||
const [usedSidebarWidth, setUsedSidebarWidth] = useState<number>(
|
||||
documentSidebarInitialWidth || parseInt(SIDEBAR_WIDTH_CONST)
|
||||
);
|
||||
|
||||
const updateSidebarWidth = (newWidth: number) => {
|
||||
setUsedSidebarWidth(newWidth);
|
||||
if (sidebarElementRef.current && innerSidebarElementRef.current) {
|
||||
sidebarElementRef.current.style.transition = "";
|
||||
sidebarElementRef.current.style.width = `${newWidth}px`;
|
||||
innerSidebarElementRef.current.style.width = `${newWidth}px`;
|
||||
}
|
||||
};
|
||||
|
||||
const [chatSessionId, setChatSessionId] = useState<number | null>(
|
||||
existingChatSessionId
|
||||
);
|
||||
@ -472,6 +488,7 @@ export function ChatPage({
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
adjustDocumentSidebarWidth(); // Adjust the width on initial render
|
||||
window.addEventListener("resize", adjustDocumentSidebarWidth); // Add resize event listener
|
||||
@ -865,12 +882,26 @@ export function ChatPage({
|
||||
router.push("/search");
|
||||
}
|
||||
|
||||
const [showDocSidebar, setShowDocSidebar] = useState(true); // State to track if sidebar is open
|
||||
|
||||
const toggleSidebar = () => {
|
||||
if (sidebarElementRef.current) {
|
||||
sidebarElementRef.current.style.transition = "width 0.3s ease-in-out";
|
||||
|
||||
sidebarElementRef.current.style.width = showDocSidebar
|
||||
? "0px"
|
||||
: `${usedSidebarWidth}px`;
|
||||
}
|
||||
|
||||
setShowDocSidebar((showDocSidebar) => !showDocSidebar); // Toggle the state which will in turn toggle the class
|
||||
};
|
||||
|
||||
const retrievalDisabled = !personaIncludesRetrieval(livePersona);
|
||||
const sidebarElementRef = useRef<HTMLDivElement>(null);
|
||||
const innerSidebarElementRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <div className="absolute top-0 z-40 w-full">
|
||||
<Header user={user} />
|
||||
</div> */}
|
||||
<HealthCheckBanner />
|
||||
<InstantSSRAutoRefresh />
|
||||
|
||||
@ -886,7 +917,7 @@ export function ChatPage({
|
||||
openedFolders={openedFolders}
|
||||
/>
|
||||
|
||||
<div className="flex w-full overflow-x-hidden" ref={masterFlexboxRef}>
|
||||
<div ref={masterFlexboxRef} className="flex w-full overflow-x-hidden">
|
||||
{popup}
|
||||
{currentFeedback && (
|
||||
<FeedbackModal
|
||||
@ -939,7 +970,10 @@ export function ChatPage({
|
||||
<div
|
||||
className={`w-full sm:relative h-screen ${
|
||||
retrievalDisabled ? "pb-[111px]" : "pb-[140px]"
|
||||
}`}
|
||||
}
|
||||
flex-auto transition-margin duration-300
|
||||
overflow-x-auto
|
||||
`}
|
||||
{...getRootProps()}
|
||||
>
|
||||
{/* <input {...getInputProps()} /> */}
|
||||
@ -963,7 +997,7 @@ export function ChatPage({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="ml-auto mr-8 flex">
|
||||
<div className="ml-auto mr-6 flex">
|
||||
{chatSessionId !== null && (
|
||||
<div
|
||||
onClick={() => setSharingModalVisible(true)}
|
||||
@ -979,8 +1013,16 @@ export function ChatPage({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="ml-4 my-auto">
|
||||
<div className="ml-4 flex my-auto">
|
||||
<UserDropdown user={user} />
|
||||
{!retrievalDisabled && !showDocSidebar && (
|
||||
<button
|
||||
className="ml-4 mt-auto"
|
||||
onClick={() => toggleSidebar()}
|
||||
>
|
||||
<TbLayoutSidebarRightExpand size={24} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1047,7 +1089,6 @@ export function ChatPage({
|
||||
newCompleteMessageMap
|
||||
);
|
||||
setSelectedMessageForDocDisplay(messageId);
|
||||
|
||||
// set message as latest so we can edit this message
|
||||
// and so it sticks around on page reload
|
||||
setMessageAsLatest(messageId);
|
||||
@ -1076,9 +1117,7 @@ export function ChatPage({
|
||||
citedDocuments={getCitedDocumentsFromMessage(
|
||||
message
|
||||
)}
|
||||
toolCall={
|
||||
message.toolCalls && message.toolCalls[0]
|
||||
}
|
||||
toolCall={message?.toolCalls?.[0]}
|
||||
isComplete={
|
||||
i !== messageHistory.length - 1 ||
|
||||
!isStreaming
|
||||
@ -1273,21 +1312,31 @@ export function ChatPage({
|
||||
</div>
|
||||
|
||||
{!retrievalDisabled ? (
|
||||
<ResizableSection
|
||||
intialWidth={documentSidebarInitialWidth as number}
|
||||
minWidth={400}
|
||||
maxWidth={maxDocumentSidebarWidth || undefined}
|
||||
<div
|
||||
ref={sidebarElementRef}
|
||||
className={`relative flex-none overflow-y-hidden sidebar bg-background-weak h-screen`}
|
||||
style={{ width: showDocSidebar ? usedSidebarWidth : 0 }}
|
||||
>
|
||||
<DocumentSidebar
|
||||
selectedMessage={aiMessage}
|
||||
selectedDocuments={selectedDocuments}
|
||||
toggleDocumentSelection={toggleDocumentSelection}
|
||||
clearSelectedDocuments={clearSelectedDocuments}
|
||||
selectedDocumentTokens={selectedDocumentTokens}
|
||||
maxTokens={maxTokens}
|
||||
isLoading={isFetchingChatMessages}
|
||||
/>
|
||||
</ResizableSection>
|
||||
<ResizableSection
|
||||
updateSidebarWidth={updateSidebarWidth}
|
||||
intialWidth={usedSidebarWidth}
|
||||
minWidth={300}
|
||||
maxWidth={maxDocumentSidebarWidth || undefined}
|
||||
>
|
||||
<DocumentSidebar
|
||||
initialWidth={showDocSidebar ? usedSidebarWidth : 0}
|
||||
ref={innerSidebarElementRef}
|
||||
closeSidebar={() => toggleSidebar()}
|
||||
selectedMessage={aiMessage}
|
||||
selectedDocuments={selectedDocuments}
|
||||
toggleDocumentSelection={toggleDocumentSelection}
|
||||
clearSelectedDocuments={clearSelectedDocuments}
|
||||
selectedDocumentTokens={selectedDocumentTokens}
|
||||
maxTokens={maxTokens}
|
||||
isLoading={isFetchingChatMessages}
|
||||
/>
|
||||
</ResizableSection>
|
||||
</div>
|
||||
) : // Another option is to use a div with the width set to the initial width, so that the
|
||||
// chat section appears in the same place as before
|
||||
// <div style={documentSidebarInitialWidth ? {width: documentSidebarInitialWidth} : {}}></div>
|
||||
|
@ -130,8 +130,8 @@ export function ChatPersonaSelector({
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className="select-none text-xl text-strong font-bold flex px-2 py-1.5 rounded cursor-pointer hover:bg-hover-light">
|
||||
<div className="my-auto">
|
||||
<div className="select-none text-xl text-strong font-bold flex px-2 rounded cursor-pointer hover:bg-hover-light">
|
||||
<div className="mt-auto">
|
||||
{currentlySelectedPersona?.name || "Default"}
|
||||
</div>
|
||||
<FiChevronDown className="my-auto ml-1" />
|
||||
|
@ -7,33 +7,41 @@ 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 { HEADER_PADDING } from "@/lib/constants";
|
||||
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="text-lg text-emphasis font-medium flex pb-0.5 mb-3.5 mt-2 font-bold">
|
||||
{icon({ className: "my-auto mr-1" })}
|
||||
{name}
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
export function DocumentSidebar({
|
||||
selectedMessage,
|
||||
selectedDocuments,
|
||||
toggleDocumentSelection,
|
||||
clearSelectedDocuments,
|
||||
selectedDocumentTokens,
|
||||
maxTokens,
|
||||
isLoading,
|
||||
}: {
|
||||
interface DocumentSidebarProps {
|
||||
closeSidebar: () => void;
|
||||
selectedMessage: Message | null;
|
||||
selectedDocuments: DanswerDocument[] | null;
|
||||
toggleDocumentSelection: (document: DanswerDocument) => void;
|
||||
@ -41,174 +49,203 @@ export function DocumentSidebar({
|
||||
selectedDocumentTokens: number;
|
||||
maxTokens: number;
|
||||
isLoading: boolean;
|
||||
}) {
|
||||
const { popup, setPopup } = usePopup();
|
||||
initialWidth: number;
|
||||
}
|
||||
|
||||
const selectedMessageRetrievalType = selectedMessage?.retrievalType || null;
|
||||
export const DocumentSidebar = forwardRef<HTMLDivElement, DocumentSidebarProps>(
|
||||
(
|
||||
{
|
||||
closeSidebar,
|
||||
selectedMessage,
|
||||
selectedDocuments,
|
||||
toggleDocumentSelection,
|
||||
clearSelectedDocuments,
|
||||
selectedDocumentTokens,
|
||||
maxTokens,
|
||||
isLoading,
|
||||
initialWidth,
|
||||
},
|
||||
ref: ForwardedRef<HTMLDivElement>
|
||||
) => {
|
||||
const { popup, setPopup } = usePopup();
|
||||
|
||||
const selectedDocumentIds =
|
||||
selectedDocuments?.map((document) => document.document_id) || [];
|
||||
const selectedMessageRetrievalType = selectedMessage?.retrievalType || null;
|
||||
|
||||
const currentDocuments = selectedMessage?.documents || null;
|
||||
const dedupedDocuments = removeDuplicateDocs(currentDocuments || []);
|
||||
const selectedDocumentIds =
|
||||
selectedDocuments?.map((document) => document.document_id) || [];
|
||||
|
||||
// NOTE: do not allow selection if less than 75 tokens are left
|
||||
// this is to prevent the case where they are able to select the doc
|
||||
// but it basically is unused since it's truncated right at the very
|
||||
// start of the document (since title + metadata + misc overhead) takes up
|
||||
// space
|
||||
const tokenLimitReached = selectedDocumentTokens > maxTokens - 75;
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex-initial
|
||||
overflow-y-hidden
|
||||
flex
|
||||
flex-col
|
||||
w-full
|
||||
h-screen
|
||||
`}
|
||||
id="document-sidebar"
|
||||
>
|
||||
{popup}
|
||||
const currentDocuments = selectedMessage?.documents || null;
|
||||
const dedupedDocuments = removeDuplicateDocs(currentDocuments || []);
|
||||
|
||||
<div className="h-4/6 flex flex-col mt-4">
|
||||
<div className="px-3 mb-3 flex border-b border-border">
|
||||
<SectionHeader
|
||||
name={
|
||||
selectedMessageRetrievalType === RetrievalType.SelectedDocs
|
||||
? "Referenced Documents"
|
||||
: "Retrieved Documents"
|
||||
}
|
||||
icon={FiFileText}
|
||||
/>
|
||||
</div>
|
||||
// NOTE: do not allow selection if less than 75 tokens are left
|
||||
// this is to prevent the case where they are able to select the doc
|
||||
// but it basically is unused since it's truncated right at the very
|
||||
// start of the document (since title + metadata + misc overhead) takes up
|
||||
// space
|
||||
const tokenLimitReached = selectedDocumentTokens > maxTokens - 75;
|
||||
|
||||
{currentDocuments ? (
|
||||
<div className="overflow-y-auto dark-scrollbar flex flex-col">
|
||||
<div>
|
||||
{dedupedDocuments.length > 0 ? (
|
||||
dedupedDocuments.map((document, ind) => (
|
||||
<div
|
||||
key={document.document_id}
|
||||
className={
|
||||
ind === dedupedDocuments.length - 1
|
||||
? "mb-5"
|
||||
: "border-b border-border-light mb-3"
|
||||
}
|
||||
>
|
||||
<ChatDocumentDisplay
|
||||
document={document}
|
||||
setPopup={setPopup}
|
||||
queryEventId={null}
|
||||
isAIPick={false}
|
||||
isSelected={selectedDocumentIds.includes(
|
||||
document.document_id
|
||||
)}
|
||||
handleSelect={(documentId) => {
|
||||
toggleDocumentSelection(
|
||||
dedupedDocuments.find(
|
||||
(document) => document.document_id === documentId
|
||||
)!
|
||||
);
|
||||
}}
|
||||
tokenLimitReached={tokenLimitReached}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="mx-3">
|
||||
<Text>No documents found for the query.</Text>
|
||||
</div>
|
||||
)}
|
||||
return (
|
||||
<div
|
||||
style={{ width: initialWidth }}
|
||||
ref={ref}
|
||||
className={`sidebar absolute right-0 h-screen border-l border-l-border`}
|
||||
>
|
||||
<div
|
||||
className="w-full flex-initial
|
||||
overflow-y-hidden
|
||||
flex
|
||||
flex-col h-screen"
|
||||
>
|
||||
{popup}
|
||||
|
||||
<div className="h-4/6 flex flex-col">
|
||||
<div className="pl-3 pr-6 mb-3 flex border-b border-border">
|
||||
<SectionHeader
|
||||
name={
|
||||
selectedMessageRetrievalType === RetrievalType.SelectedDocs
|
||||
? "Referenced Documents"
|
||||
: "Retrieved Documents"
|
||||
}
|
||||
icon={FiFileText}
|
||||
closeHeader={closeSidebar}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
!isLoading && (
|
||||
<div className="ml-4 mr-3">
|
||||
<Text>
|
||||
When you run ask a question, the retrieved documents will show
|
||||
up here!
|
||||
</Text>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="text-sm mb-4 border-t border-border pt-4 overflow-y-hidden flex flex-col">
|
||||
<div className="flex border-b border-border px-3">
|
||||
<div className="flex">
|
||||
<SectionHeader name="Selected Documents" icon={FiFileText} />
|
||||
|
||||
{tokenLimitReached && (
|
||||
<div className="ml-2 my-auto">
|
||||
<div className="mb-2">
|
||||
<HoverPopup
|
||||
mainContent={
|
||||
<FiAlertTriangle
|
||||
className="text-alert my-auto"
|
||||
size="16"
|
||||
/>
|
||||
}
|
||||
popupContent={
|
||||
<Text className="w-40">
|
||||
Over LLM context length by:{" "}
|
||||
<i>{selectedDocumentTokens - maxTokens} tokens</i>
|
||||
<br />
|
||||
<br />
|
||||
{selectedDocuments && selectedDocuments.length > 0 && (
|
||||
<>
|
||||
Truncating: "
|
||||
<i>
|
||||
{
|
||||
selectedDocuments[selectedDocuments.length - 1]
|
||||
.semantic_identifier
|
||||
}
|
||||
</i>
|
||||
"
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
}
|
||||
direction="left"
|
||||
/>
|
||||
{currentDocuments ? (
|
||||
<div className="overflow-y-auto dark-scrollbar flex flex-col">
|
||||
<div>
|
||||
{dedupedDocuments.length > 0 ? (
|
||||
dedupedDocuments.map((document, ind) => (
|
||||
<div
|
||||
key={document.document_id}
|
||||
className={
|
||||
ind === dedupedDocuments.length - 1
|
||||
? "mb-5"
|
||||
: "border-b border-border-light mb-3"
|
||||
}
|
||||
>
|
||||
<ChatDocumentDisplay
|
||||
document={document}
|
||||
setPopup={setPopup}
|
||||
queryEventId={null}
|
||||
isAIPick={false}
|
||||
isSelected={selectedDocumentIds.includes(
|
||||
document.document_id
|
||||
)}
|
||||
handleSelect={(documentId) => {
|
||||
toggleDocumentSelection(
|
||||
dedupedDocuments.find(
|
||||
(document) =>
|
||||
document.document_id === documentId
|
||||
)!
|
||||
);
|
||||
}}
|
||||
tokenLimitReached={tokenLimitReached}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="mx-3">
|
||||
<Text>No documents found for the query.</Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
!isLoading && (
|
||||
<div className="ml-4 mr-3">
|
||||
<Text>
|
||||
When you run ask a question, the retrieved documents will
|
||||
show up here!
|
||||
</Text>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
|
||||
{selectedDocuments && selectedDocuments.length > 0 && (
|
||||
<div className="ml-auto my-auto" onClick={clearSelectedDocuments}>
|
||||
<BasicSelectable selected={false}>De-Select All</BasicSelectable>
|
||||
<div className="text-sm mb-4 border-t border-border pt-4 overflow-y-hidden flex flex-col">
|
||||
<div className="flex border-b border-border px-3">
|
||||
<div className="flex">
|
||||
<SectionHeader name="Selected Documents" icon={FiFileText} />
|
||||
{tokenLimitReached && (
|
||||
<div className="ml-2 my-auto">
|
||||
<div className="mb-2">
|
||||
<HoverPopup
|
||||
mainContent={
|
||||
<FiAlertTriangle
|
||||
className="text-alert my-auto"
|
||||
size="16"
|
||||
/>
|
||||
}
|
||||
popupContent={
|
||||
<Text className="w-40">
|
||||
Over LLM context length by:{" "}
|
||||
<i>{selectedDocumentTokens - maxTokens} tokens</i>
|
||||
<br />
|
||||
<br />
|
||||
{selectedDocuments &&
|
||||
selectedDocuments.length > 0 && (
|
||||
<>
|
||||
Truncating: "
|
||||
<i>
|
||||
{
|
||||
selectedDocuments[
|
||||
selectedDocuments.length - 1
|
||||
].semantic_identifier
|
||||
}
|
||||
</i>
|
||||
"
|
||||
</>
|
||||
)}
|
||||
</Text>
|
||||
}
|
||||
direction="left"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{selectedDocuments && selectedDocuments.length > 0 && (
|
||||
<div
|
||||
className="ml-auto my-auto"
|
||||
onClick={clearSelectedDocuments}
|
||||
>
|
||||
<BasicSelectable selected={false}>
|
||||
De-Select All
|
||||
</BasicSelectable>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{selectedDocuments && selectedDocuments.length > 0 ? (
|
||||
<div className="flex flex-col gap-y-2 py-3 px-3 overflow-y-auto dark-scrollbar max-h-full">
|
||||
{selectedDocuments.map((document) => (
|
||||
<SelectedDocumentDisplay
|
||||
key={document.document_id}
|
||||
document={document}
|
||||
handleDeselect={(documentId) => {
|
||||
toggleDocumentSelection(
|
||||
dedupedDocuments.find(
|
||||
(document) => document.document_id === documentId
|
||||
)!
|
||||
);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
{selectedDocuments && selectedDocuments.length > 0 ? (
|
||||
<div className="flex flex-col gap-y-2 py-3 px-3 overflow-y-auto dark-scrollbar max-h-full">
|
||||
{selectedDocuments.map((document) => (
|
||||
<SelectedDocumentDisplay
|
||||
key={document.document_id}
|
||||
document={document}
|
||||
handleDeselect={(documentId) => {
|
||||
toggleDocumentSelection(
|
||||
dedupedDocuments.find(
|
||||
(document) => document.document_id === documentId
|
||||
)!
|
||||
);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
!isLoading && (
|
||||
<Text className="mx-3 py-3">
|
||||
Select documents from the retrieved documents section to chat
|
||||
specifically with them!
|
||||
</Text>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
!isLoading && (
|
||||
<Text className="mx-3 py-3">
|
||||
Select documents from the retrieved documents section to chat
|
||||
specifically with them!
|
||||
</Text>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
DocumentSidebar.displayName = "DocumentSidebar";
|
||||
|
@ -26,7 +26,7 @@ export function DocumentPreview({
|
||||
flex
|
||||
items-center
|
||||
p-2
|
||||
bg-hover-light
|
||||
bg-hover
|
||||
border
|
||||
border-border
|
||||
rounded-md
|
||||
|
@ -84,9 +84,10 @@ export function ChatInputBar({
|
||||
flex
|
||||
flex-col
|
||||
border
|
||||
border-border
|
||||
border-border-medium
|
||||
rounded-lg
|
||||
bg-background
|
||||
overflow-hidden
|
||||
bg-background-weak
|
||||
[&:has(textarea:focus)]::ring-1
|
||||
[&:has(textarea:focus)]::ring-black
|
||||
"
|
||||
@ -118,13 +119,14 @@ export function ChatInputBar({
|
||||
shrink
|
||||
resize-none
|
||||
border-0
|
||||
bg-transparent
|
||||
bg-background-weak
|
||||
${
|
||||
textAreaRef.current &&
|
||||
textAreaRef.current.scrollHeight > MAX_INPUT_HEIGHT
|
||||
? "overflow-y-auto mt-2"
|
||||
: ""
|
||||
}
|
||||
overflow-hidden
|
||||
whitespace-normal
|
||||
break-word
|
||||
overscroll-contain
|
||||
@ -157,7 +159,7 @@ export function ChatInputBar({
|
||||
}}
|
||||
suppressContentEditableWarning={true}
|
||||
/>
|
||||
<div className="flex items-center space-x-3 px-4 pb-2">
|
||||
<div className="flex items-center space-x-3 px-4 pb-2">
|
||||
<ChatInputOption
|
||||
name={selectedAssistant ? selectedAssistant.name : "Assistants"}
|
||||
icon={FaBrain}
|
||||
|
@ -497,6 +497,7 @@ export const HumanMessage = ({
|
||||
placeholder-gray-400
|
||||
resize-none
|
||||
pl-4
|
||||
overflow-y-auto
|
||||
pr-12
|
||||
py-4`}
|
||||
aria-multiline
|
||||
@ -505,7 +506,6 @@ export const HumanMessage = ({
|
||||
style={{ scrollbarWidth: "thin" }}
|
||||
onChange={(e) => {
|
||||
setEditedContent(e.target.value);
|
||||
e.target.style.height = "auto";
|
||||
e.target.style.height = `${e.target.scrollHeight}px`;
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
@ -514,6 +514,10 @@ export const HumanMessage = ({
|
||||
setEditedContent(content);
|
||||
setIsEditing(false);
|
||||
}
|
||||
// Submit edit if "Command Enter" is pressed, like in ChatGPT
|
||||
if (e.key === "Enter" && e.metaKey) {
|
||||
handleEditSubmit();
|
||||
}
|
||||
}}
|
||||
// ref={(textarea) => {
|
||||
// if (textarea) {
|
||||
|
@ -198,7 +198,7 @@ export function ChatSessionDisplay({
|
||||
<div className="absolute bottom-0 right-0 top-0 bg-gradient-to-l to-transparent from-hover w-20 from-60% rounded" />
|
||||
)}
|
||||
{!isSelected && !delayedSkipGradient && (
|
||||
<div className="absolute bottom-0 right-0 top-0 bg-gradient-to-l to-transparent from-background w-8 from-0% rounded" />
|
||||
<div className="absolute bottom-0 right-0 top-0 bg-gradient-to-l to-transparent from-background-weak w-8 from-0% rounded" />
|
||||
)}
|
||||
</>
|
||||
</BasicSelectable>
|
||||
|
@ -7,10 +7,12 @@ import Image from "next/image";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { BasicClickable, BasicSelectable } from "@/components/BasicClickable";
|
||||
import { ChatSession } from "../interfaces";
|
||||
|
||||
import {
|
||||
NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA,
|
||||
NEXT_PUBLIC_DO_NOT_USE_TOGGLE_OFF_DANSWER_POWERED,
|
||||
NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA,
|
||||
} from "@/lib/constants";
|
||||
|
||||
import { ChatTab } from "./ChatTab";
|
||||
import { Folder } from "../folders/interfaces";
|
||||
import { createFolder } from "../folders/FolderManagement";
|
||||
@ -56,8 +58,10 @@ export const ChatSidebar = ({
|
||||
{popup}
|
||||
<div
|
||||
className={`
|
||||
flex-none
|
||||
w-64
|
||||
flex
|
||||
flex-none
|
||||
bg-background-weak
|
||||
3xl:w-72
|
||||
border-r
|
||||
border-border
|
||||
|
@ -20,8 +20,9 @@ export function BasicClickable({
|
||||
text-sm
|
||||
p-1
|
||||
h-full
|
||||
bg-background
|
||||
select-none
|
||||
hover:bg-hover
|
||||
hover:bg-hover-light
|
||||
${fullWidth ? "w-full" : ""}`}
|
||||
>
|
||||
{children}
|
||||
@ -65,11 +66,13 @@ export function BasicSelectable({
|
||||
selected,
|
||||
hasBorder,
|
||||
fullWidth = false,
|
||||
padding = true,
|
||||
}: {
|
||||
children: string | JSX.Element;
|
||||
selected: boolean;
|
||||
hasBorder?: boolean;
|
||||
fullWidth?: boolean;
|
||||
padding?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
@ -78,7 +81,7 @@ export function BasicSelectable({
|
||||
font-medium
|
||||
text-emphasis
|
||||
text-sm
|
||||
p-1
|
||||
${padding && "p-1"}
|
||||
select-none
|
||||
${hasBorder ? "border border-border" : ""}
|
||||
${selected ? "bg-hover" : "hover:bg-hover"}
|
||||
|
@ -49,12 +49,12 @@ export function UserDropdown({
|
||||
open={userInfoVisible}
|
||||
onOpenChange={setUserInfoVisible}
|
||||
content={
|
||||
<BasicSelectable selected={false}>
|
||||
<BasicSelectable padding={false} selected={false}>
|
||||
<div
|
||||
onClick={() => setUserInfoVisible(!userInfoVisible)}
|
||||
className="flex cursor-pointer"
|
||||
>
|
||||
<div className="my-auto bg-user rounded-lg px-2 text-base font-normal">
|
||||
<div className="my-auto bg-user hover:bg-user-hover rounded-lg px-2 text-base font-normal">
|
||||
{user && user.email ? user.email[0].toUpperCase() : "A"}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -69,7 +69,7 @@ export async function Layout({ children }: { children: React.ReactNode }) {
|
||||
<Header user={user} />
|
||||
</div>
|
||||
<div className="flex h-full pt-16">
|
||||
<div className="w-80 pt-12 pb-8 h-full border-r border-border overflow-auto">
|
||||
<div className="w-80 bg-background-weak pt-12 pb-8 h-full border-r border-border overflow-auto">
|
||||
<AdminSidebar
|
||||
collections={[
|
||||
{
|
||||
|
@ -23,7 +23,7 @@ export function AdminSidebar({ collections }: { collections: Collection[] }) {
|
||||
</h2>
|
||||
{collection.items.map((item) => (
|
||||
<Link key={item.link} href={item.link}>
|
||||
<button className="text-sm block w-48 py-2 px-2 text-left hover:bg-hover-light rounded">
|
||||
<button className="text-sm block w-48 py-2 px-2 text-left hover:bg-hover rounded">
|
||||
<div className="">{item.name}</div>
|
||||
</button>
|
||||
</Link>
|
||||
|
@ -20,11 +20,13 @@ function applyMinAndMax(
|
||||
}
|
||||
|
||||
export function ResizableSection({
|
||||
updateSidebarWidth,
|
||||
children,
|
||||
intialWidth,
|
||||
minWidth,
|
||||
maxWidth,
|
||||
}: {
|
||||
updateSidebarWidth?: (newWidth: number) => void;
|
||||
children: JSX.Element;
|
||||
intialWidth: number;
|
||||
minWidth: number;
|
||||
@ -56,6 +58,9 @@ export function ResizableSection({
|
||||
const delta = mouseMoveEvent.clientX - startX;
|
||||
let newWidth = applyMinAndMax(width - delta, minWidth, maxWidth);
|
||||
setWidth(newWidth);
|
||||
if (updateSidebarWidth) {
|
||||
updateSidebarWidth(newWidth);
|
||||
}
|
||||
Cookies.set(DOCUMENT_SIDEBAR_WIDTH_COOKIE_NAME, newWidth.toString(), {
|
||||
path: "/",
|
||||
});
|
||||
|
@ -1,6 +1,9 @@
|
||||
export type AuthType = "disabled" | "basic" | "google_oauth" | "oidc" | "saml";
|
||||
|
||||
export const HOST_URL = process.env.WEB_DOMAIN || "http://127.0.0.1:3000";
|
||||
export const HEADER_HEIGHT = "h-16";
|
||||
export const SUB_HEADER = "h-12";
|
||||
|
||||
export const INTERNAL_URL = process.env.INTERNAL_URL || "http://127.0.0.1:8080";
|
||||
export const NEXT_PUBLIC_DISABLE_STREAMING =
|
||||
process.env.NEXT_PUBLIC_DISABLE_STREAMING?.toLowerCase() === "true";
|
||||
@ -20,7 +23,8 @@ export const GOOGLE_DRIVE_AUTH_IS_ADMIN_COOKIE_NAME =
|
||||
|
||||
export const SEARCH_TYPE_COOKIE_NAME = "search_type";
|
||||
|
||||
export const HEADER_PADDING = `pt-[64px]`;
|
||||
export const SIDEBAR_WIDTH_CONST = "350px";
|
||||
export const SIDEBAR_WIDTH = `w-[350px]`;
|
||||
|
||||
export const LOGOUT_DISABLED =
|
||||
process.env.NEXT_PUBLIC_DISABLE_LOGOUT?.toLowerCase() === "true";
|
||||
@ -31,6 +35,12 @@ export const LOGOUT_DISABLED =
|
||||
// it will not be accurate (will always be false).
|
||||
export const SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED =
|
||||
process.env.ENABLE_PAID_ENTERPRISE_EDITION_FEATURES?.toLowerCase() === "true";
|
||||
// NOTE: since this is a `NEXT_PUBLIC_` variable, it will be set at
|
||||
// build-time
|
||||
// TODO: consider moving this to an API call so that the api_server
|
||||
// can be the single source of truth
|
||||
export const EE_ENABLED =
|
||||
process.env.NEXT_PUBLIC_ENABLE_PAID_EE_FEATURES?.toLowerCase() === "true";
|
||||
|
||||
export const CUSTOM_ANALYTICS_ENABLED = process.env.CUSTOM_ANALYTICS_SECRET_KEY
|
||||
? true
|
||||
|
@ -43,6 +43,8 @@ module.exports = {
|
||||
"background-strong": "#eaecef",
|
||||
"background-search": "#ffffff",
|
||||
"background-custom-header": "#f3f4f6",
|
||||
"background-inverted": "#000000",
|
||||
"background-weak": "#f3f4f6", // gray-100
|
||||
|
||||
// text or icons
|
||||
link: "#3b82f6", // blue-500
|
||||
@ -60,6 +62,7 @@ module.exports = {
|
||||
// borders
|
||||
border: "#e5e7eb", // gray-200
|
||||
"border-light": "#f3f4f6", // gray-100
|
||||
"border-medium": "#d1d5db", // gray-300
|
||||
"border-strong": "#9ca3af", // gray-400
|
||||
|
||||
// hover
|
||||
@ -73,13 +76,6 @@ module.exports = {
|
||||
text: "#fef9c3", // yellow-100
|
||||
},
|
||||
|
||||
// bubbles in chat for each "user"
|
||||
user: "#fb7185", // yellow-400
|
||||
ai: "#60a5fa", // blue-400
|
||||
|
||||
// for display documents
|
||||
document: "#ec4899", // pink-500
|
||||
|
||||
// scrollbar
|
||||
scrollbar: {
|
||||
track: "#f9fafb",
|
||||
@ -92,6 +88,13 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
|
||||
// bubbles in chat for each "user"
|
||||
user: "#fb7185", // yellow-400
|
||||
ai: "#60a5fa", // blue-400
|
||||
|
||||
// for display documents
|
||||
document: "#ec4899", // pink-500
|
||||
|
||||
// light mode
|
||||
tremor: {
|
||||
brand: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user