mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-10-10 13:15:18 +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:
@@ -7,6 +7,7 @@ import { FiBookmark, FiCpu, FiInfo, FiX, FiZoomIn } from "react-icons/fi";
|
|||||||
import { HoverPopup } from "@/components/HoverPopup";
|
import { HoverPopup } from "@/components/HoverPopup";
|
||||||
import { Modal } from "@/components/Modal";
|
import { Modal } from "@/components/Modal";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { FaCaretDown, FaCaretRight } from "react-icons/fa";
|
||||||
import { Logo } from "@/components/Logo";
|
import { Logo } from "@/components/Logo";
|
||||||
|
|
||||||
const MAX_PERSONAS_TO_DISPLAY = 4;
|
const MAX_PERSONAS_TO_DISPLAY = 4;
|
||||||
@@ -37,6 +38,8 @@ export function ChatIntro({
|
|||||||
}) {
|
}) {
|
||||||
const availableSourceMetadata = getSourceMetadataForSources(availableSources);
|
const availableSourceMetadata = getSourceMetadataForSources(availableSources);
|
||||||
|
|
||||||
|
const [displaySources, setDisplaySources] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex justify-center items-center h-full">
|
<div className="flex justify-center items-center h-full">
|
||||||
@@ -90,12 +93,13 @@ export function ChatIntro({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{availableSources.length > 0 && (
|
{availableSources.length > 0 && (
|
||||||
<div className="mt-2">
|
<div className="mt-1">
|
||||||
<p className="font-bold mb-1 mt-4 text-emphasis">
|
<p className="font-bold mb-1 mt-4 text-emphasis">
|
||||||
Connected Sources:{" "}
|
Connected Sources:{" "}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className={`flex flex-wrap gap-2`}>
|
||||||
{availableSourceMetadata.map((sourceMetadata) => (
|
{availableSourceMetadata.map((sourceMetadata) => (
|
||||||
<span
|
<span
|
||||||
key={sourceMetadata.internalName}
|
key={sourceMetadata.internalName}
|
||||||
|
@@ -45,7 +45,6 @@ import { useDocumentSelection } from "./useDocumentSelection";
|
|||||||
import { useFilters, useLlmOverride } from "@/lib/hooks";
|
import { useFilters, useLlmOverride } from "@/lib/hooks";
|
||||||
import { computeAvailableFilters } from "@/lib/filters";
|
import { computeAvailableFilters } from "@/lib/filters";
|
||||||
import { FeedbackType } from "./types";
|
import { FeedbackType } from "./types";
|
||||||
import ResizableSection from "@/components/resizable/ResizableSection";
|
|
||||||
import { DocumentSidebar } from "./documentSidebar/DocumentSidebar";
|
import { DocumentSidebar } from "./documentSidebar/DocumentSidebar";
|
||||||
import { DanswerInitializingLoader } from "@/components/DanswerInitializingLoader";
|
import { DanswerInitializingLoader } from "@/components/DanswerInitializingLoader";
|
||||||
import { FeedbackModal } from "./modal/FeedbackModal";
|
import { FeedbackModal } from "./modal/FeedbackModal";
|
||||||
@@ -74,6 +73,10 @@ import { v4 as uuidv4 } from "uuid";
|
|||||||
import { orderAssistantsForUser } from "@/lib/assistants/orderAssistants";
|
import { orderAssistantsForUser } from "@/lib/assistants/orderAssistants";
|
||||||
import { ChatPopup } from "./ChatPopup";
|
import { ChatPopup } from "./ChatPopup";
|
||||||
import { ChatBanner } from "./ChatBanner";
|
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 MAX_INPUT_HEIGHT = 200;
|
||||||
const TEMP_USER_MESSAGE_ID = -1;
|
const TEMP_USER_MESSAGE_ID = -1;
|
||||||
@@ -256,6 +259,19 @@ export function ChatPage({
|
|||||||
initialSessionFetch();
|
initialSessionFetch();
|
||||||
}, [existingChatSessionId]);
|
}, [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>(
|
const [chatSessionId, setChatSessionId] = useState<number | null>(
|
||||||
existingChatSessionId
|
existingChatSessionId
|
||||||
);
|
);
|
||||||
@@ -472,6 +488,7 @@ export function ChatPage({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
adjustDocumentSidebarWidth(); // Adjust the width on initial render
|
adjustDocumentSidebarWidth(); // Adjust the width on initial render
|
||||||
window.addEventListener("resize", adjustDocumentSidebarWidth); // Add resize event listener
|
window.addEventListener("resize", adjustDocumentSidebarWidth); // Add resize event listener
|
||||||
@@ -865,12 +882,26 @@ export function ChatPage({
|
|||||||
router.push("/search");
|
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 retrievalDisabled = !personaIncludesRetrieval(livePersona);
|
||||||
|
const sidebarElementRef = useRef<HTMLDivElement>(null);
|
||||||
|
const innerSidebarElementRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* <div className="absolute top-0 z-40 w-full">
|
|
||||||
<Header user={user} />
|
|
||||||
</div> */}
|
|
||||||
<HealthCheckBanner />
|
<HealthCheckBanner />
|
||||||
<InstantSSRAutoRefresh />
|
<InstantSSRAutoRefresh />
|
||||||
|
|
||||||
@@ -886,7 +917,7 @@ export function ChatPage({
|
|||||||
openedFolders={openedFolders}
|
openedFolders={openedFolders}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex w-full overflow-x-hidden" ref={masterFlexboxRef}>
|
<div ref={masterFlexboxRef} className="flex w-full overflow-x-hidden">
|
||||||
{popup}
|
{popup}
|
||||||
{currentFeedback && (
|
{currentFeedback && (
|
||||||
<FeedbackModal
|
<FeedbackModal
|
||||||
@@ -939,7 +970,10 @@ export function ChatPage({
|
|||||||
<div
|
<div
|
||||||
className={`w-full sm:relative h-screen ${
|
className={`w-full sm:relative h-screen ${
|
||||||
retrievalDisabled ? "pb-[111px]" : "pb-[140px]"
|
retrievalDisabled ? "pb-[111px]" : "pb-[140px]"
|
||||||
}`}
|
}
|
||||||
|
flex-auto transition-margin duration-300
|
||||||
|
overflow-x-auto
|
||||||
|
`}
|
||||||
{...getRootProps()}
|
{...getRootProps()}
|
||||||
>
|
>
|
||||||
{/* <input {...getInputProps()} /> */}
|
{/* <input {...getInputProps()} /> */}
|
||||||
@@ -963,7 +997,7 @@ export function ChatPage({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="ml-auto mr-8 flex">
|
<div className="ml-auto mr-6 flex">
|
||||||
{chatSessionId !== null && (
|
{chatSessionId !== null && (
|
||||||
<div
|
<div
|
||||||
onClick={() => setSharingModalVisible(true)}
|
onClick={() => setSharingModalVisible(true)}
|
||||||
@@ -979,8 +1013,16 @@ export function ChatPage({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="ml-4 my-auto">
|
<div className="ml-4 flex my-auto">
|
||||||
<UserDropdown user={user} />
|
<UserDropdown user={user} />
|
||||||
|
{!retrievalDisabled && !showDocSidebar && (
|
||||||
|
<button
|
||||||
|
className="ml-4 mt-auto"
|
||||||
|
onClick={() => toggleSidebar()}
|
||||||
|
>
|
||||||
|
<TbLayoutSidebarRightExpand size={24} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1047,7 +1089,6 @@ export function ChatPage({
|
|||||||
newCompleteMessageMap
|
newCompleteMessageMap
|
||||||
);
|
);
|
||||||
setSelectedMessageForDocDisplay(messageId);
|
setSelectedMessageForDocDisplay(messageId);
|
||||||
|
|
||||||
// set message as latest so we can edit this message
|
// set message as latest so we can edit this message
|
||||||
// and so it sticks around on page reload
|
// and so it sticks around on page reload
|
||||||
setMessageAsLatest(messageId);
|
setMessageAsLatest(messageId);
|
||||||
@@ -1076,9 +1117,7 @@ export function ChatPage({
|
|||||||
citedDocuments={getCitedDocumentsFromMessage(
|
citedDocuments={getCitedDocumentsFromMessage(
|
||||||
message
|
message
|
||||||
)}
|
)}
|
||||||
toolCall={
|
toolCall={message?.toolCalls?.[0]}
|
||||||
message.toolCalls && message.toolCalls[0]
|
|
||||||
}
|
|
||||||
isComplete={
|
isComplete={
|
||||||
i !== messageHistory.length - 1 ||
|
i !== messageHistory.length - 1 ||
|
||||||
!isStreaming
|
!isStreaming
|
||||||
@@ -1273,12 +1312,21 @@ export function ChatPage({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!retrievalDisabled ? (
|
{!retrievalDisabled ? (
|
||||||
|
<div
|
||||||
|
ref={sidebarElementRef}
|
||||||
|
className={`relative flex-none overflow-y-hidden sidebar bg-background-weak h-screen`}
|
||||||
|
style={{ width: showDocSidebar ? usedSidebarWidth : 0 }}
|
||||||
|
>
|
||||||
<ResizableSection
|
<ResizableSection
|
||||||
intialWidth={documentSidebarInitialWidth as number}
|
updateSidebarWidth={updateSidebarWidth}
|
||||||
minWidth={400}
|
intialWidth={usedSidebarWidth}
|
||||||
|
minWidth={300}
|
||||||
maxWidth={maxDocumentSidebarWidth || undefined}
|
maxWidth={maxDocumentSidebarWidth || undefined}
|
||||||
>
|
>
|
||||||
<DocumentSidebar
|
<DocumentSidebar
|
||||||
|
initialWidth={showDocSidebar ? usedSidebarWidth : 0}
|
||||||
|
ref={innerSidebarElementRef}
|
||||||
|
closeSidebar={() => toggleSidebar()}
|
||||||
selectedMessage={aiMessage}
|
selectedMessage={aiMessage}
|
||||||
selectedDocuments={selectedDocuments}
|
selectedDocuments={selectedDocuments}
|
||||||
toggleDocumentSelection={toggleDocumentSelection}
|
toggleDocumentSelection={toggleDocumentSelection}
|
||||||
@@ -1288,6 +1336,7 @@ export function ChatPage({
|
|||||||
isLoading={isFetchingChatMessages}
|
isLoading={isFetchingChatMessages}
|
||||||
/>
|
/>
|
||||||
</ResizableSection>
|
</ResizableSection>
|
||||||
|
</div>
|
||||||
) : // Another option is to use a div with the width set to the initial width, so that the
|
) : // 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
|
// chat section appears in the same place as before
|
||||||
// <div style={documentSidebarInitialWidth ? {width: documentSidebarInitialWidth} : {}}></div>
|
// <div style={documentSidebarInitialWidth ? {width: documentSidebarInitialWidth} : {}}></div>
|
||||||
|
@@ -130,8 +130,8 @@ export function ChatPersonaSelector({
|
|||||||
</div>
|
</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="select-none text-xl text-strong font-bold flex px-2 rounded cursor-pointer hover:bg-hover-light">
|
||||||
<div className="my-auto">
|
<div className="mt-auto">
|
||||||
{currentlySelectedPersona?.name || "Default"}
|
{currentlySelectedPersona?.name || "Default"}
|
||||||
</div>
|
</div>
|
||||||
<FiChevronDown className="my-auto ml-1" />
|
<FiChevronDown className="my-auto ml-1" />
|
||||||
|
@@ -7,33 +7,41 @@ import { SelectedDocumentDisplay } from "./SelectedDocumentDisplay";
|
|||||||
import { removeDuplicateDocs } from "@/lib/documentUtils";
|
import { removeDuplicateDocs } from "@/lib/documentUtils";
|
||||||
import { BasicSelectable } from "@/components/BasicClickable";
|
import { BasicSelectable } from "@/components/BasicClickable";
|
||||||
import { Message, RetrievalType } from "../interfaces";
|
import { Message, RetrievalType } from "../interfaces";
|
||||||
|
import { SIDEBAR_WIDTH } from "@/lib/constants";
|
||||||
import { HoverPopup } from "@/components/HoverPopup";
|
import { HoverPopup } from "@/components/HoverPopup";
|
||||||
import { HEADER_PADDING } from "@/lib/constants";
|
import { TbLayoutSidebarLeftExpand } from "react-icons/tb";
|
||||||
|
import { ForwardedRef, forwardRef } from "react";
|
||||||
|
|
||||||
function SectionHeader({
|
function SectionHeader({
|
||||||
name,
|
name,
|
||||||
icon,
|
icon,
|
||||||
|
closeHeader,
|
||||||
}: {
|
}: {
|
||||||
name: string;
|
name: string;
|
||||||
icon: React.FC<{ className: string }>;
|
icon: React.FC<{ className: string }>;
|
||||||
|
closeHeader?: () => void;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="text-lg text-emphasis font-medium flex pb-0.5 mb-3.5 mt-2 font-bold">
|
<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" })}
|
{icon({ className: "my-auto mr-1" })}
|
||||||
{name}
|
{name}
|
||||||
|
</p>
|
||||||
|
{closeHeader && (
|
||||||
|
<button onClick={() => closeHeader()}>
|
||||||
|
<TbLayoutSidebarLeftExpand size={24} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DocumentSidebar({
|
interface DocumentSidebarProps {
|
||||||
selectedMessage,
|
closeSidebar: () => void;
|
||||||
selectedDocuments,
|
|
||||||
toggleDocumentSelection,
|
|
||||||
clearSelectedDocuments,
|
|
||||||
selectedDocumentTokens,
|
|
||||||
maxTokens,
|
|
||||||
isLoading,
|
|
||||||
}: {
|
|
||||||
selectedMessage: Message | null;
|
selectedMessage: Message | null;
|
||||||
selectedDocuments: DanswerDocument[] | null;
|
selectedDocuments: DanswerDocument[] | null;
|
||||||
toggleDocumentSelection: (document: DanswerDocument) => void;
|
toggleDocumentSelection: (document: DanswerDocument) => void;
|
||||||
@@ -41,7 +49,24 @@ export function DocumentSidebar({
|
|||||||
selectedDocumentTokens: number;
|
selectedDocumentTokens: number;
|
||||||
maxTokens: number;
|
maxTokens: number;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
}) {
|
initialWidth: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DocumentSidebar = forwardRef<HTMLDivElement, DocumentSidebarProps>(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
closeSidebar,
|
||||||
|
selectedMessage,
|
||||||
|
selectedDocuments,
|
||||||
|
toggleDocumentSelection,
|
||||||
|
clearSelectedDocuments,
|
||||||
|
selectedDocumentTokens,
|
||||||
|
maxTokens,
|
||||||
|
isLoading,
|
||||||
|
initialWidth,
|
||||||
|
},
|
||||||
|
ref: ForwardedRef<HTMLDivElement>
|
||||||
|
) => {
|
||||||
const { popup, setPopup } = usePopup();
|
const { popup, setPopup } = usePopup();
|
||||||
|
|
||||||
const selectedMessageRetrievalType = selectedMessage?.retrievalType || null;
|
const selectedMessageRetrievalType = selectedMessage?.retrievalType || null;
|
||||||
@@ -58,22 +83,23 @@ export function DocumentSidebar({
|
|||||||
// start of the document (since title + metadata + misc overhead) takes up
|
// start of the document (since title + metadata + misc overhead) takes up
|
||||||
// space
|
// space
|
||||||
const tokenLimitReached = selectedDocumentTokens > maxTokens - 75;
|
const tokenLimitReached = selectedDocumentTokens > maxTokens - 75;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`
|
style={{ width: initialWidth }}
|
||||||
flex-initial
|
ref={ref}
|
||||||
|
className={`sidebar absolute right-0 h-screen border-l border-l-border`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="w-full flex-initial
|
||||||
overflow-y-hidden
|
overflow-y-hidden
|
||||||
flex
|
flex
|
||||||
flex-col
|
flex-col h-screen"
|
||||||
w-full
|
|
||||||
h-screen
|
|
||||||
`}
|
|
||||||
id="document-sidebar"
|
|
||||||
>
|
>
|
||||||
{popup}
|
{popup}
|
||||||
|
|
||||||
<div className="h-4/6 flex flex-col mt-4">
|
<div className="h-4/6 flex flex-col">
|
||||||
<div className="px-3 mb-3 flex border-b border-border">
|
<div className="pl-3 pr-6 mb-3 flex border-b border-border">
|
||||||
<SectionHeader
|
<SectionHeader
|
||||||
name={
|
name={
|
||||||
selectedMessageRetrievalType === RetrievalType.SelectedDocs
|
selectedMessageRetrievalType === RetrievalType.SelectedDocs
|
||||||
@@ -81,6 +107,7 @@ export function DocumentSidebar({
|
|||||||
: "Retrieved Documents"
|
: "Retrieved Documents"
|
||||||
}
|
}
|
||||||
icon={FiFileText}
|
icon={FiFileText}
|
||||||
|
closeHeader={closeSidebar}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -108,7 +135,8 @@ export function DocumentSidebar({
|
|||||||
handleSelect={(documentId) => {
|
handleSelect={(documentId) => {
|
||||||
toggleDocumentSelection(
|
toggleDocumentSelection(
|
||||||
dedupedDocuments.find(
|
dedupedDocuments.find(
|
||||||
(document) => document.document_id === documentId
|
(document) =>
|
||||||
|
document.document_id === documentId
|
||||||
)!
|
)!
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
@@ -127,8 +155,8 @@ export function DocumentSidebar({
|
|||||||
!isLoading && (
|
!isLoading && (
|
||||||
<div className="ml-4 mr-3">
|
<div className="ml-4 mr-3">
|
||||||
<Text>
|
<Text>
|
||||||
When you run ask a question, the retrieved documents will show
|
When you run ask a question, the retrieved documents will
|
||||||
up here!
|
show up here!
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -139,7 +167,6 @@ export function DocumentSidebar({
|
|||||||
<div className="flex border-b border-border px-3">
|
<div className="flex border-b border-border px-3">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<SectionHeader name="Selected Documents" icon={FiFileText} />
|
<SectionHeader name="Selected Documents" icon={FiFileText} />
|
||||||
|
|
||||||
{tokenLimitReached && (
|
{tokenLimitReached && (
|
||||||
<div className="ml-2 my-auto">
|
<div className="ml-2 my-auto">
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
@@ -156,13 +183,15 @@ export function DocumentSidebar({
|
|||||||
<i>{selectedDocumentTokens - maxTokens} tokens</i>
|
<i>{selectedDocumentTokens - maxTokens} tokens</i>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
{selectedDocuments && selectedDocuments.length > 0 && (
|
{selectedDocuments &&
|
||||||
|
selectedDocuments.length > 0 && (
|
||||||
<>
|
<>
|
||||||
Truncating: "
|
Truncating: "
|
||||||
<i>
|
<i>
|
||||||
{
|
{
|
||||||
selectedDocuments[selectedDocuments.length - 1]
|
selectedDocuments[
|
||||||
.semantic_identifier
|
selectedDocuments.length - 1
|
||||||
|
].semantic_identifier
|
||||||
}
|
}
|
||||||
</i>
|
</i>
|
||||||
"
|
"
|
||||||
@@ -176,10 +205,14 @@ export function DocumentSidebar({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{selectedDocuments && selectedDocuments.length > 0 && (
|
{selectedDocuments && selectedDocuments.length > 0 && (
|
||||||
<div className="ml-auto my-auto" onClick={clearSelectedDocuments}>
|
<div
|
||||||
<BasicSelectable selected={false}>De-Select All</BasicSelectable>
|
className="ml-auto my-auto"
|
||||||
|
onClick={clearSelectedDocuments}
|
||||||
|
>
|
||||||
|
<BasicSelectable selected={false}>
|
||||||
|
De-Select All
|
||||||
|
</BasicSelectable>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -210,5 +243,9 @@ export function DocumentSidebar({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
DocumentSidebar.displayName = "DocumentSidebar";
|
||||||
|
@@ -26,7 +26,7 @@ export function DocumentPreview({
|
|||||||
flex
|
flex
|
||||||
items-center
|
items-center
|
||||||
p-2
|
p-2
|
||||||
bg-hover-light
|
bg-hover
|
||||||
border
|
border
|
||||||
border-border
|
border-border
|
||||||
rounded-md
|
rounded-md
|
||||||
|
@@ -84,9 +84,10 @@ export function ChatInputBar({
|
|||||||
flex
|
flex
|
||||||
flex-col
|
flex-col
|
||||||
border
|
border
|
||||||
border-border
|
border-border-medium
|
||||||
rounded-lg
|
rounded-lg
|
||||||
bg-background
|
overflow-hidden
|
||||||
|
bg-background-weak
|
||||||
[&:has(textarea:focus)]::ring-1
|
[&:has(textarea:focus)]::ring-1
|
||||||
[&:has(textarea:focus)]::ring-black
|
[&:has(textarea:focus)]::ring-black
|
||||||
"
|
"
|
||||||
@@ -118,13 +119,14 @@ export function ChatInputBar({
|
|||||||
shrink
|
shrink
|
||||||
resize-none
|
resize-none
|
||||||
border-0
|
border-0
|
||||||
bg-transparent
|
bg-background-weak
|
||||||
${
|
${
|
||||||
textAreaRef.current &&
|
textAreaRef.current &&
|
||||||
textAreaRef.current.scrollHeight > MAX_INPUT_HEIGHT
|
textAreaRef.current.scrollHeight > MAX_INPUT_HEIGHT
|
||||||
? "overflow-y-auto mt-2"
|
? "overflow-y-auto mt-2"
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
|
overflow-hidden
|
||||||
whitespace-normal
|
whitespace-normal
|
||||||
break-word
|
break-word
|
||||||
overscroll-contain
|
overscroll-contain
|
||||||
|
@@ -497,6 +497,7 @@ export const HumanMessage = ({
|
|||||||
placeholder-gray-400
|
placeholder-gray-400
|
||||||
resize-none
|
resize-none
|
||||||
pl-4
|
pl-4
|
||||||
|
overflow-y-auto
|
||||||
pr-12
|
pr-12
|
||||||
py-4`}
|
py-4`}
|
||||||
aria-multiline
|
aria-multiline
|
||||||
@@ -505,7 +506,6 @@ export const HumanMessage = ({
|
|||||||
style={{ scrollbarWidth: "thin" }}
|
style={{ scrollbarWidth: "thin" }}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setEditedContent(e.target.value);
|
setEditedContent(e.target.value);
|
||||||
e.target.style.height = "auto";
|
|
||||||
e.target.style.height = `${e.target.scrollHeight}px`;
|
e.target.style.height = `${e.target.scrollHeight}px`;
|
||||||
}}
|
}}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
@@ -514,6 +514,10 @@ export const HumanMessage = ({
|
|||||||
setEditedContent(content);
|
setEditedContent(content);
|
||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
}
|
}
|
||||||
|
// Submit edit if "Command Enter" is pressed, like in ChatGPT
|
||||||
|
if (e.key === "Enter" && e.metaKey) {
|
||||||
|
handleEditSubmit();
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
// ref={(textarea) => {
|
// ref={(textarea) => {
|
||||||
// if (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" />
|
<div className="absolute bottom-0 right-0 top-0 bg-gradient-to-l to-transparent from-hover w-20 from-60% rounded" />
|
||||||
)}
|
)}
|
||||||
{!isSelected && !delayedSkipGradient && (
|
{!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>
|
</BasicSelectable>
|
||||||
|
@@ -7,10 +7,12 @@ import Image from "next/image";
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { BasicClickable, BasicSelectable } from "@/components/BasicClickable";
|
import { BasicClickable, BasicSelectable } from "@/components/BasicClickable";
|
||||||
import { ChatSession } from "../interfaces";
|
import { ChatSession } from "../interfaces";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA,
|
|
||||||
NEXT_PUBLIC_DO_NOT_USE_TOGGLE_OFF_DANSWER_POWERED,
|
NEXT_PUBLIC_DO_NOT_USE_TOGGLE_OFF_DANSWER_POWERED,
|
||||||
|
NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA,
|
||||||
} from "@/lib/constants";
|
} from "@/lib/constants";
|
||||||
|
|
||||||
import { ChatTab } from "./ChatTab";
|
import { ChatTab } from "./ChatTab";
|
||||||
import { Folder } from "../folders/interfaces";
|
import { Folder } from "../folders/interfaces";
|
||||||
import { createFolder } from "../folders/FolderManagement";
|
import { createFolder } from "../folders/FolderManagement";
|
||||||
@@ -56,8 +58,10 @@ export const ChatSidebar = ({
|
|||||||
{popup}
|
{popup}
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
flex-none
|
|
||||||
w-64
|
w-64
|
||||||
|
flex
|
||||||
|
flex-none
|
||||||
|
bg-background-weak
|
||||||
3xl:w-72
|
3xl:w-72
|
||||||
border-r
|
border-r
|
||||||
border-border
|
border-border
|
||||||
|
@@ -20,8 +20,9 @@ export function BasicClickable({
|
|||||||
text-sm
|
text-sm
|
||||||
p-1
|
p-1
|
||||||
h-full
|
h-full
|
||||||
|
bg-background
|
||||||
select-none
|
select-none
|
||||||
hover:bg-hover
|
hover:bg-hover-light
|
||||||
${fullWidth ? "w-full" : ""}`}
|
${fullWidth ? "w-full" : ""}`}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@@ -65,11 +66,13 @@ export function BasicSelectable({
|
|||||||
selected,
|
selected,
|
||||||
hasBorder,
|
hasBorder,
|
||||||
fullWidth = false,
|
fullWidth = false,
|
||||||
|
padding = true,
|
||||||
}: {
|
}: {
|
||||||
children: string | JSX.Element;
|
children: string | JSX.Element;
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
hasBorder?: boolean;
|
hasBorder?: boolean;
|
||||||
fullWidth?: boolean;
|
fullWidth?: boolean;
|
||||||
|
padding?: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -78,7 +81,7 @@ export function BasicSelectable({
|
|||||||
font-medium
|
font-medium
|
||||||
text-emphasis
|
text-emphasis
|
||||||
text-sm
|
text-sm
|
||||||
p-1
|
${padding && "p-1"}
|
||||||
select-none
|
select-none
|
||||||
${hasBorder ? "border border-border" : ""}
|
${hasBorder ? "border border-border" : ""}
|
||||||
${selected ? "bg-hover" : "hover:bg-hover"}
|
${selected ? "bg-hover" : "hover:bg-hover"}
|
||||||
|
@@ -49,12 +49,12 @@ export function UserDropdown({
|
|||||||
open={userInfoVisible}
|
open={userInfoVisible}
|
||||||
onOpenChange={setUserInfoVisible}
|
onOpenChange={setUserInfoVisible}
|
||||||
content={
|
content={
|
||||||
<BasicSelectable selected={false}>
|
<BasicSelectable padding={false} selected={false}>
|
||||||
<div
|
<div
|
||||||
onClick={() => setUserInfoVisible(!userInfoVisible)}
|
onClick={() => setUserInfoVisible(!userInfoVisible)}
|
||||||
className="flex cursor-pointer"
|
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"}
|
{user && user.email ? user.email[0].toUpperCase() : "A"}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -69,7 +69,7 @@ export async function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
<Header user={user} />
|
<Header user={user} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex h-full pt-16">
|
<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
|
<AdminSidebar
|
||||||
collections={[
|
collections={[
|
||||||
{
|
{
|
||||||
|
@@ -23,7 +23,7 @@ export function AdminSidebar({ collections }: { collections: Collection[] }) {
|
|||||||
</h2>
|
</h2>
|
||||||
{collection.items.map((item) => (
|
{collection.items.map((item) => (
|
||||||
<Link key={item.link} href={item.link}>
|
<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>
|
<div className="">{item.name}</div>
|
||||||
</button>
|
</button>
|
||||||
</Link>
|
</Link>
|
||||||
|
@@ -20,11 +20,13 @@ function applyMinAndMax(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ResizableSection({
|
export function ResizableSection({
|
||||||
|
updateSidebarWidth,
|
||||||
children,
|
children,
|
||||||
intialWidth,
|
intialWidth,
|
||||||
minWidth,
|
minWidth,
|
||||||
maxWidth,
|
maxWidth,
|
||||||
}: {
|
}: {
|
||||||
|
updateSidebarWidth?: (newWidth: number) => void;
|
||||||
children: JSX.Element;
|
children: JSX.Element;
|
||||||
intialWidth: number;
|
intialWidth: number;
|
||||||
minWidth: number;
|
minWidth: number;
|
||||||
@@ -56,6 +58,9 @@ export function ResizableSection({
|
|||||||
const delta = mouseMoveEvent.clientX - startX;
|
const delta = mouseMoveEvent.clientX - startX;
|
||||||
let newWidth = applyMinAndMax(width - delta, minWidth, maxWidth);
|
let newWidth = applyMinAndMax(width - delta, minWidth, maxWidth);
|
||||||
setWidth(newWidth);
|
setWidth(newWidth);
|
||||||
|
if (updateSidebarWidth) {
|
||||||
|
updateSidebarWidth(newWidth);
|
||||||
|
}
|
||||||
Cookies.set(DOCUMENT_SIDEBAR_WIDTH_COOKIE_NAME, newWidth.toString(), {
|
Cookies.set(DOCUMENT_SIDEBAR_WIDTH_COOKIE_NAME, newWidth.toString(), {
|
||||||
path: "/",
|
path: "/",
|
||||||
});
|
});
|
||||||
|
@@ -1,6 +1,9 @@
|
|||||||
export type AuthType = "disabled" | "basic" | "google_oauth" | "oidc" | "saml";
|
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 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 INTERNAL_URL = process.env.INTERNAL_URL || "http://127.0.0.1:8080";
|
||||||
export const NEXT_PUBLIC_DISABLE_STREAMING =
|
export const NEXT_PUBLIC_DISABLE_STREAMING =
|
||||||
process.env.NEXT_PUBLIC_DISABLE_STREAMING?.toLowerCase() === "true";
|
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 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 =
|
export const LOGOUT_DISABLED =
|
||||||
process.env.NEXT_PUBLIC_DISABLE_LOGOUT?.toLowerCase() === "true";
|
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).
|
// it will not be accurate (will always be false).
|
||||||
export const SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED =
|
export const SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED =
|
||||||
process.env.ENABLE_PAID_ENTERPRISE_EDITION_FEATURES?.toLowerCase() === "true";
|
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
|
export const CUSTOM_ANALYTICS_ENABLED = process.env.CUSTOM_ANALYTICS_SECRET_KEY
|
||||||
? true
|
? true
|
||||||
|
@@ -43,6 +43,8 @@ module.exports = {
|
|||||||
"background-strong": "#eaecef",
|
"background-strong": "#eaecef",
|
||||||
"background-search": "#ffffff",
|
"background-search": "#ffffff",
|
||||||
"background-custom-header": "#f3f4f6",
|
"background-custom-header": "#f3f4f6",
|
||||||
|
"background-inverted": "#000000",
|
||||||
|
"background-weak": "#f3f4f6", // gray-100
|
||||||
|
|
||||||
// text or icons
|
// text or icons
|
||||||
link: "#3b82f6", // blue-500
|
link: "#3b82f6", // blue-500
|
||||||
@@ -60,6 +62,7 @@ module.exports = {
|
|||||||
// borders
|
// borders
|
||||||
border: "#e5e7eb", // gray-200
|
border: "#e5e7eb", // gray-200
|
||||||
"border-light": "#f3f4f6", // gray-100
|
"border-light": "#f3f4f6", // gray-100
|
||||||
|
"border-medium": "#d1d5db", // gray-300
|
||||||
"border-strong": "#9ca3af", // gray-400
|
"border-strong": "#9ca3af", // gray-400
|
||||||
|
|
||||||
// hover
|
// hover
|
||||||
@@ -73,13 +76,6 @@ module.exports = {
|
|||||||
text: "#fef9c3", // yellow-100
|
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
|
||||||
scrollbar: {
|
scrollbar: {
|
||||||
track: "#f9fafb",
|
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
|
// light mode
|
||||||
tremor: {
|
tremor: {
|
||||||
brand: {
|
brand: {
|
||||||
|
Reference in New Issue
Block a user