diff --git a/backend/danswer/chat/process_message.py b/backend/danswer/chat/process_message.py
index f09d3b27c..2c3a0a332 100644
--- a/backend/danswer/chat/process_message.py
+++ b/backend/danswer/chat/process_message.py
@@ -694,6 +694,7 @@ def stream_chat_message_objects(
except Exception as e:
error_msg = str(e)
+
logger.exception(f"Failed to process chat message: {error_msg}")
if "Illegal header value b'Bearer '" in error_msg:
diff --git a/backend/ee/danswer/server/auth_check.py b/backend/ee/danswer/server/auth_check.py
index d0ba3ffe4..49353abf8 100644
--- a/backend/ee/danswer/server/auth_check.py
+++ b/backend/ee/danswer/server/auth_check.py
@@ -8,6 +8,7 @@ EE_PUBLIC_ENDPOINT_SPECS = PUBLIC_ENDPOINT_SPECS + [
# needs to be accessible prior to user login
("/enterprise-settings", {"GET"}),
("/enterprise-settings/logo", {"GET"}),
+ ("/enterprise-settings/logotype", {"GET"}),
("/enterprise-settings/custom-analytics-script", {"GET"}),
# oidc
("/auth/oidc/authorize", {"GET"}),
diff --git a/backend/ee/danswer/server/enterprise_settings/api.py b/backend/ee/danswer/server/enterprise_settings/api.py
index 85f43ca55..736296517 100644
--- a/backend/ee/danswer/server/enterprise_settings/api.py
+++ b/backend/ee/danswer/server/enterprise_settings/api.py
@@ -12,6 +12,7 @@ from danswer.file_store.file_store import get_default_file_store
from ee.danswer.server.enterprise_settings.models import AnalyticsScriptUpload
from ee.danswer.server.enterprise_settings.models import EnterpriseSettings
from ee.danswer.server.enterprise_settings.store import _LOGO_FILENAME
+from ee.danswer.server.enterprise_settings.store import _LOGOTYPE_FILENAME
from ee.danswer.server.enterprise_settings.store import load_analytics_script
from ee.danswer.server.enterprise_settings.store import load_settings
from ee.danswer.server.enterprise_settings.store import store_analytics_script
@@ -41,22 +42,38 @@ def fetch_settings() -> EnterpriseSettings:
@admin_router.put("/logo")
def put_logo(
file: UploadFile,
+ is_logotype: bool = False,
db_session: Session = Depends(get_session),
_: User | None = Depends(current_admin_user),
) -> None:
- upload_logo(file=file, db_session=db_session)
+ upload_logo(file=file, db_session=db_session, is_logotype=is_logotype)
-@basic_router.get("/logo")
-def fetch_logo(db_session: Session = Depends(get_session)) -> Response:
+def fetch_logo_or_logotype(is_logotype: bool, db_session: Session) -> Response:
try:
file_store = get_default_file_store(db_session)
- file_io = file_store.read_file(_LOGO_FILENAME, mode="b")
+ filename = _LOGOTYPE_FILENAME if is_logotype else _LOGO_FILENAME
+ file_io = file_store.read_file(filename, mode="b")
# NOTE: specifying "image/jpeg" here, but it still works for pngs
# TODO: do this properly
return Response(content=file_io.read(), media_type="image/jpeg")
except Exception:
- raise HTTPException(status_code=404, detail="No logo file found")
+ raise HTTPException(
+ status_code=404,
+ detail=f"No {'logotype' if is_logotype else 'logo'} file found",
+ )
+
+
+@basic_router.get("/logotype")
+def fetch_logotype(db_session: Session = Depends(get_session)) -> Response:
+ return fetch_logo_or_logotype(is_logotype=True, db_session=db_session)
+
+
+@basic_router.get("/logo")
+def fetch_logo(
+ is_logotype: bool = False, db_session: Session = Depends(get_session)
+) -> Response:
+ return fetch_logo_or_logotype(is_logotype=is_logotype, db_session=db_session)
@admin_router.put("/custom-analytics-script")
diff --git a/backend/ee/danswer/server/enterprise_settings/models.py b/backend/ee/danswer/server/enterprise_settings/models.py
index c2142c641..c9831d87a 100644
--- a/backend/ee/danswer/server/enterprise_settings/models.py
+++ b/backend/ee/danswer/server/enterprise_settings/models.py
@@ -8,8 +8,10 @@ class EnterpriseSettings(BaseModel):
application_name: str | None = None
use_custom_logo: bool = False
+ use_custom_logotype: bool = False
# custom Chat components
+ custom_lower_disclaimer_content: str | None = None
custom_header_content: str | None = None
custom_popup_header: str | None = None
custom_popup_content: str | None = None
diff --git a/backend/ee/danswer/server/enterprise_settings/store.py b/backend/ee/danswer/server/enterprise_settings/store.py
index e1418f022..128661cfd 100644
--- a/backend/ee/danswer/server/enterprise_settings/store.py
+++ b/backend/ee/danswer/server/enterprise_settings/store.py
@@ -63,6 +63,7 @@ def store_analytics_script(analytics_script_upload: AnalyticsScriptUpload) -> No
_LOGO_FILENAME = "__logo__"
+_LOGOTYPE_FILENAME = "__logotype__"
def is_valid_file_type(filename: str) -> bool:
@@ -79,8 +80,7 @@ def guess_file_type(filename: str) -> str:
def upload_logo(
- db_session: Session,
- file: UploadFile | str,
+ db_session: Session, file: UploadFile | str, is_logotype: bool = False
) -> bool:
content: IO[Any]
@@ -111,7 +111,7 @@ def upload_logo(
file_store = get_default_file_store(db_session)
file_store.save_file(
- file_name=_LOGO_FILENAME,
+ file_name=_LOGOTYPE_FILENAME if is_logotype else _LOGO_FILENAME,
content=content,
display_name=display_name,
file_origin=FileOrigin.OTHER,
diff --git a/web/src/app/admin/settings/interfaces.ts b/web/src/app/admin/settings/interfaces.ts
index b6e2b2bd7..910403f1a 100644
--- a/web/src/app/admin/settings/interfaces.ts
+++ b/web/src/app/admin/settings/interfaces.ts
@@ -8,8 +8,10 @@ export interface Settings {
export interface EnterpriseSettings {
application_name: string | null;
use_custom_logo: boolean;
+ use_custom_logotype: boolean;
// custom Chat components
+ custom_lower_disclaimer_content: string | null;
custom_header_content: string | null;
custom_popup_header: string | null;
custom_popup_content: string | null;
diff --git a/web/src/app/chat/ChatBanner.tsx b/web/src/app/chat/ChatBanner.tsx
index 3ce245581..bdd53617f 100644
--- a/web/src/app/chat/ChatBanner.tsx
+++ b/web/src/app/chat/ChatBanner.tsx
@@ -7,6 +7,7 @@ import remarkGfm from "remark-gfm";
import { Popover } from "@/components/popover/Popover";
import { ChevronDownIcon } from "@/components/icons/icons";
import { Divider } from "@tremor/react";
+import { MinimalMarkdown } from "@/components/chat_search/MinimalMarkdown";
export function ChatBanner() {
const settings = useContext(SettingsContext);
@@ -79,14 +80,19 @@ export function ChatBanner() {
ref={contentRef}
className="line-clamp-2 text-center w-full overflow-hidden pr-8"
>
- {renderMarkdown("")}
+
-
- {renderMarkdown("")}
+
{isOverflowing && (
@@ -104,7 +110,12 @@ export function ChatBanner() {
popover={
Banner Content
- {renderMarkdown("max-h-96 overflow-y-auto")}
+
}
side="bottom"
diff --git a/web/src/app/chat/ChatPage.tsx b/web/src/app/chat/ChatPage.tsx
index 8f76c4a59..90f56a9d7 100644
--- a/web/src/app/chat/ChatPage.tsx
+++ b/web/src/app/chat/ChatPage.tsx
@@ -1,4 +1,5 @@
"use client";
+import ReactMarkdown from "react-markdown";
import { redirect, useRouter, useSearchParams } from "next/navigation";
import {
@@ -82,6 +83,8 @@ import FixedLogo from "./shared_chat_search/FixedLogo";
import { getSecondsUntilExpiration } from "@/lib/time";
import { SetDefaultModelModal } from "./modal/SetDefaultModelModal";
import { DeleteChatModal } from "./modal/DeleteChatModal";
+import remarkGfm from "remark-gfm";
+import { MinimalMarkdown } from "@/components/chat_search/MinimalMarkdown";
const TEMP_USER_MESSAGE_ID = -1;
const TEMP_ASSISTANT_MESSAGE_ID = -2;
@@ -289,7 +292,7 @@ export function ChatPage({
}
return;
}
-
+ clearSelectedDocuments();
setIsFetchingChatMessages(true);
const response = await fetch(
`/api/chat/get-chat-session/${existingChatSessionId}`
@@ -1110,6 +1113,7 @@ export function ChatPage({
// settings are passed in via Context and therefore aren't
// available in server-side components
const settings = useContext(SettingsContext);
+ const enterpriseSettings = settings?.enterpriseSettings;
if (settings?.settings?.chat_page_enabled === false) {
router.push("/search");
}
@@ -1694,7 +1698,7 @@ export function ChatPage({
ref={inputRef}
className="absolute bottom-0 z-10 w-full"
>
-
+
{aboveHorizon && (
+
+ {enterpriseSettings &&
+ enterpriseSettings.custom_lower_disclaimer_content && (
+
+ )}
+
+ {enterpriseSettings &&
+ enterpriseSettings.use_custom_logotype && (
+
+

+
+ )}
@@ -1750,7 +1783,7 @@ export function ChatPage({
)}
-
+
{
const [isExpanded, setIsExpanded] = useState(isInitiallyExpanded);
- const [isEditing, setIsEditing] = useState(false);
+ const [isEditing, setIsEditing] = useState(initiallySelected);
const [editedFolderName, setEditedFolderName] = useState(
folder.folder_name
);
@@ -135,6 +134,14 @@ const FolderItem = ({
};
}, []);
+ const inputRef = useRef(null);
+
+ useEffect(() => {
+ if (initiallySelected && inputRef.current) {
+ inputRef.current.focus();
+ }
+ }, [initiallySelected]);
+
const handleDrop = async (event: React.DragEvent) => {
event.preventDefault();
setIsDragOver(false);
@@ -170,31 +177,6 @@ const FolderItem = ({
isDragOver ? "bg-hover" : ""
}`}
>
- {showDeleteConfirm && (
-
-
- Are you sure you want to delete {folder.folder_name}? All the
- content inside this folder will also be deleted
-
-
-
-
-
-
- )}
setIsHovering(true)}
@@ -214,6 +196,7 @@ const FolderItem = ({
{isEditing ? (
+ }
+ popover={
+
+
+ Are you sure you want to delete{" "}
+ {folder.folder_name}? All the content inside
+ this folder will also be deleted.
+
+
+
+
+
+
+ }
+ side="top"
+ align="center"
+ />
)}
@@ -285,22 +299,25 @@ export const FolderList = ({
folders,
currentChatId,
openedFolders,
+ newFolderId,
}: {
folders: Folder[];
currentChatId?: number;
openedFolders?: { [key: number]: boolean };
+ newFolderId: number | null;
}) => {
if (folders.length === 0) {
return null;
}
return (
-
+
{folders.map((folder) => (
{
throw new Error("Failed to create folder");
}
const data = await response.json();
- return data.folder_id;
+ return data;
}
// Function to add a chat session to a folder
diff --git a/web/src/app/chat/input/ChatInputBar.tsx b/web/src/app/chat/input/ChatInputBar.tsx
index 9c1b64aba..4204b9c5d 100644
--- a/web/src/app/chat/input/ChatInputBar.tsx
+++ b/web/src/app/chat/input/ChatInputBar.tsx
@@ -268,14 +268,14 @@ export function ChatInputBar({
return (
-
+
@@ -528,6 +528,7 @@ export function ChatInputBar({
mobilePosition="top-right"
>
void;
size?: number;
tooltipContent?: React.ReactNode;
- options?: { name: string; value: number; onClick?: () => void }[];
flexPriority?: "shrink" | "stiff" | "second";
+ toggle?: boolean;
}
export const ChatInputOption: React.FC = ({
@@ -19,9 +16,9 @@ export const ChatInputOption: React.FC = ({
Icon,
// icon: Icon,
size = 16,
- options,
flexPriority,
tooltipContent,
+ toggle,
onClick,
}) => {
const [isDropupVisible, setDropupVisible] = useState(false);
@@ -76,7 +73,11 @@ export const ChatInputOption: React.FC = ({
onClick={onClick}
>
- {name && {name}}
+
+ {name && {name}}
+ {toggle && }
+
+
{isTooltipVisible && tooltipContent && (
{
if (scrollableDivRef?.current && !isStreaming) {
- if (scrollDist.current < distance) {
+ if (scrollDist.current < distance - 50) {
scrollableDivRef?.current?.scrollBy({
left: 0,
top: Math.max(scrollDist.current + 600, 0),
diff --git a/web/src/app/chat/sessionSidebar/ChatSessionDisplay.tsx b/web/src/app/chat/sessionSidebar/ChatSessionDisplay.tsx
index 77376dc22..8844d9ff4 100644
--- a/web/src/app/chat/sessionSidebar/ChatSessionDisplay.tsx
+++ b/web/src/app/chat/sessionSidebar/ChatSessionDisplay.tsx
@@ -3,7 +3,11 @@
import { useRouter } from "next/navigation";
import { ChatSession } from "../interfaces";
import { useState, useEffect, useContext } from "react";
-import { deleteChatSession, renameChatSession } from "../lib";
+import {
+ deleteChatSession,
+ getChatRetentionInfo,
+ renameChatSession,
+} from "../lib";
import { DeleteChatModal } from "../modal/DeleteChatModal";
import { BasicSelectable } from "@/components/BasicClickable";
import Link from "next/link";
@@ -20,6 +24,8 @@ import { Popover } from "@/components/popover/Popover";
import { ShareChatSessionModal } from "../modal/ShareChatSessionModal";
import { CHAT_SESSION_ID_KEY, FOLDER_ID_KEY } from "@/lib/drag/constants";
import { SettingsContext } from "@/components/settings/SettingsProvider";
+import { WarningCircle } from "@phosphor-icons/react";
+import { CustomTooltip } from "@/components/tooltip/CustomTooltip";
export function ChatSessionDisplay({
chatSession,
@@ -41,13 +47,13 @@ export function ChatSessionDisplay({
showDeleteModal?: (chatSession: ChatSession) => void;
}) {
const router = useRouter();
- const [isDeletionModalVisible, setIsDeletionModalVisible] = useState(false);
const [isRenamingChat, setIsRenamingChat] = useState(false);
const [isMoreOptionsDropdownOpen, setIsMoreOptionsDropdownOpen] =
useState(false);
const [isShareModalVisible, setIsShareModalVisible] = useState(false);
const [chatName, setChatName] = useState(chatSession.name);
const [delayedSkipGradient, setDelayedSkipGradient] = useState(skipGradient);
+ const settings = useContext(SettingsContext);
useEffect(() => {
if (skipGradient) {
@@ -69,7 +75,15 @@ export function ChatSessionDisplay({
alert("Failed to rename chat session");
}
};
- const settings = useContext(SettingsContext);
+
+ if (!settings) {
+ return <>>;
+ }
+
+ const { daysUntilExpiration, showRetentionWarning } = getChatRetentionInfo(
+ chatSession,
+ settings?.settings
+ );
return (
<>
@@ -120,7 +134,7 @@ export function ChatSessionDisplay({
event.preventDefault();
}
}}
- className="-my-px px-1 mr-2 w-full rounded"
+ className="-my-px px-1 mr-1 w-full rounded"
/>
) : (
@@ -134,10 +148,10 @@ export function ChatSessionDisplay({
{isSelected &&
(isRenamingChat ? (
-
+
@@ -152,7 +166,25 @@ export function ChatSessionDisplay({
) : (
-
+
+ {!showShareModal && showRetentionWarning && (
+
+ This chat will expire{" "}
+ {daysUntilExpiration < 1
+ ? "today"
+ : `in ${daysUntilExpiration} day${daysUntilExpiration !== 1 ? "s" : ""}`}
+
+ }
+ >
+
+
+
+
+ )}
+
{
@@ -160,7 +192,7 @@ export function ChatSessionDisplay({
!isMoreOptionsDropdownOpen
);
}}
- className={"-m-1"}
+ className={"-my-1"}
>
showDeleteModal(chatSession)}
- className={`hover:bg-black/10 p-1 -m-1 rounded ml-2`}
+ className={`hover:bg-black/10 p-1 -m-1 rounded ml-1`}
>
diff --git a/web/src/app/chat/sessionSidebar/HistorySidebar.tsx b/web/src/app/chat/sessionSidebar/HistorySidebar.tsx
index 10a4fc052..28bb18531 100644
--- a/web/src/app/chat/sessionSidebar/HistorySidebar.tsx
+++ b/web/src/app/chat/sessionSidebar/HistorySidebar.tsx
@@ -1,7 +1,13 @@
"use client";
import { FiEdit, FiFolderPlus } from "react-icons/fi";
-import { ForwardedRef, forwardRef, useContext, useEffect } from "react";
+import {
+ ForwardedRef,
+ forwardRef,
+ useContext,
+ useEffect,
+ useState,
+} from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { ChatSession } from "../interfaces";
@@ -56,6 +62,9 @@ export const HistorySidebar = forwardRef
(
const router = useRouter();
const { popup, setPopup } = usePopup();
+ // For determining intial focus state
+ const [newFolderId, setNewFolderId] = useState(null);
+
const currentChatId = currentChatSession?.id;
// prevent the NextJS Router cache from causing the chat sidebar to not
@@ -69,8 +78,6 @@ export const HistorySidebar = forwardRef(
return null;
}
- const enterpriseSettings = combinedSettings.enterpriseSettings;
-
const handleNewChat = () => {
reset();
const newChatUrl =
@@ -133,8 +140,8 @@ export const HistorySidebar = forwardRef(
onClick={() =>
createFolder("New Folder")
.then((folderId) => {
- console.log(`Folder created with ID: ${folderId}`);
router.refresh();
+ setNewFolderId(folderId);
})
.catch((error) => {
console.error("Failed to create folder:", error);
@@ -172,6 +179,7 @@ export const HistorySidebar = forwardRef(
)}
void;
+ newFolderId: number | null;
showShareModal?: (chatSession: ChatSession) => void;
showDeleteModal?: (chatSession: ChatSession) => void;
}) {
@@ -69,8 +71,10 @@ export function PagesTab({
Chat Folders
+ {newFolderId ? newFolderId : "Hi"}
null,
+}: {
+ toggleSidebar?: () => void;
+}) {
const combinedSettings = useContext(SettingsContext);
const settings = combinedSettings?.settings;
const enterpriseSettings = combinedSettings?.enterpriseSettings;
return (
<>
-
+
diff --git a/web/src/app/chat/shared_chat_search/FunctionalWrapper.tsx b/web/src/app/chat/shared_chat_search/FunctionalWrapper.tsx
index 86fab2eaa..4ef22ef4e 100644
--- a/web/src/app/chat/shared_chat_search/FunctionalWrapper.tsx
+++ b/web/src/app/chat/shared_chat_search/FunctionalWrapper.tsx
@@ -80,7 +80,10 @@ export default function FunctionalWrapper({
initiallyToggled,
content,
}: {
- content: (toggledSidebar: boolean, toggle: () => void) => ReactNode;
+ content: (
+ toggledSidebar: boolean,
+ toggle: (toggled?: boolean) => void
+ ) => ReactNode;
initiallyToggled: boolean;
}) {
const router = useRouter();
@@ -123,11 +126,9 @@ export default function FunctionalWrapper({
const [toggledSidebar, setToggledSidebar] = useState(initiallyToggled);
const toggle = (value?: boolean) => {
- if (value !== undefined) {
- setToggledSidebar(value);
- } else {
- setToggledSidebar((prevState) => !prevState);
- }
+ setToggledSidebar((toggledSidebar) =>
+ value !== undefined ? value : !toggledSidebar
+ );
};
return (
diff --git a/web/src/app/ee/admin/whitelabeling/WhitelabelingForm.tsx b/web/src/app/ee/admin/whitelabeling/WhitelabelingForm.tsx
index 6b4b7147d..2c8259c0b 100644
--- a/web/src/app/ee/admin/whitelabeling/WhitelabelingForm.tsx
+++ b/web/src/app/ee/admin/whitelabeling/WhitelabelingForm.tsx
@@ -16,7 +16,8 @@ import { ImageUpload } from "./ImageUpload";
export function WhitelabelingForm() {
const router = useRouter();
- const [selectedFile, setSelectedFile] = useState
(null);
+ const [selectedLogo, setSelectedLogo] = useState(null);
+ const [selectedLogotype, setSelectedLogotype] = useState(null);
const settings = useContext(SettingsContext);
if (!settings) {
@@ -49,27 +50,33 @@ export function WhitelabelingForm() {
initialValues={{
application_name: enterpriseSettings?.application_name || null,
use_custom_logo: enterpriseSettings?.use_custom_logo || false,
+ use_custom_logotype: enterpriseSettings?.use_custom_logotype || false,
+
custom_header_content:
enterpriseSettings?.custom_header_content || "",
custom_popup_header: enterpriseSettings?.custom_popup_header || "",
custom_popup_content: enterpriseSettings?.custom_popup_content || "",
+ custom_lower_disclaimer_content:
+ enterpriseSettings?.custom_lower_disclaimer_content || "",
}}
validationSchema={Yup.object().shape({
application_name: Yup.string().nullable(),
use_custom_logo: Yup.boolean().required(),
+ use_custom_logotype: Yup.boolean().required(),
custom_header_content: Yup.string().nullable(),
custom_popup_header: Yup.string().nullable(),
custom_popup_content: Yup.string().nullable(),
+ custom_lower_disclaimer_content: Yup.string().nullable(),
})}
onSubmit={async (values, formikHelpers) => {
formikHelpers.setSubmitting(true);
- if (selectedFile) {
+ if (selectedLogo) {
values.use_custom_logo = true;
const formData = new FormData();
- formData.append("file", selectedFile);
- setSelectedFile(null);
+ formData.append("file", selectedLogo);
+ setSelectedLogo(null);
const response = await fetch(
"/api/admin/enterprise-settings/logo",
{
@@ -85,6 +92,27 @@ export function WhitelabelingForm() {
}
}
+ if (selectedLogotype) {
+ values.use_custom_logotype = true;
+
+ const formData = new FormData();
+ formData.append("file", selectedLogotype);
+ setSelectedLogo(null);
+ const response = await fetch(
+ "/api/admin/enterprise-settings/logo?is_logotype=true",
+ {
+ method: "PUT",
+ body: formData,
+ }
+ );
+ if (!response.ok) {
+ const errorMsg = (await response.json()).detail;
+ alert(`Failed to upload logo. ${errorMsg}`);
+ formikHelpers.setSubmitting(false);
+ return;
+ }
+ }
+
formikHelpers.setValues(values);
await updateEnterpriseSettings(values);
}}
@@ -138,10 +166,53 @@ export function WhitelabelingForm() {
Specify your own logo to replace the standard Danswer logo.
)}
-
+
+
+
+ {values.use_custom_logotype ? (
+
+
Current Custom Logotype:
+

+
+
+
+
+ Override the current custom logotype by uploading a new image
+ below and clicking the Update button. This logotype is the
+ text-based logo that will be rendered at the bottom right of
+ the chat screen.
+
+
+ ) : (
+ Specify your own logotype
+ )}
+
@@ -182,6 +253,17 @@ export function WhitelabelingForm() {
/>
+
+
+
+
diff --git a/web/src/components/chat_search/Header.tsx b/web/src/components/chat_search/Header.tsx
index d1fb68ccf..48a0ba42c 100644
--- a/web/src/components/chat_search/Header.tsx
+++ b/web/src/components/chat_search/Header.tsx
@@ -30,9 +30,6 @@ export default function FunctionalHeader({
setSharingModalVisible?: (value: SetStateAction
) => void;
toggleSidebar?: () => void;
}) {
- const combinedSettings = useContext(SettingsContext);
- const enterpriseSettings = combinedSettings?.enterpriseSettings;
-
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.metaKey || event.ctrlKey) {
@@ -69,7 +66,7 @@ export default function FunctionalHeader({
};
return (
-
+
= ({
+ content,
+ className = "",
+}) => {
+ return (
+ (
+
+ ),
+ p: ({ node, ...props }) => (
+
+ ),
+ }}
+ remarkPlugins={[remarkGfm]}
+ >
+ {content}
+
+ );
+};
diff --git a/web/src/components/chat_search/hooks.ts b/web/src/components/chat_search/hooks.ts
index 2e42978a3..f07234e54 100644
--- a/web/src/components/chat_search/hooks.ts
+++ b/web/src/components/chat_search/hooks.ts
@@ -52,7 +52,12 @@ export const useSidebarVisibility = ({
!isWithinSidebar &&
!toggledSidebar
) {
- setShowDocSidebar(false);
+ setTimeout(() => {
+ setShowDocSidebar((showDocSidebar) => {
+ // Account for possition as point in time of
+ return !(xPosition.current > sidebarRect.right);
+ });
+ }, 200);
} else if (currentXPosition < 100 && !showDocSidebar) {
if (!mobile) {
setShowDocSidebar(true);
diff --git a/web/src/components/header/LogoType.tsx b/web/src/components/header/LogoType.tsx
index 5942b132c..b1ea82c45 100644
--- a/web/src/components/header/LogoType.tsx
+++ b/web/src/components/header/LogoType.tsx
@@ -6,7 +6,7 @@ import {
NEXT_PUBLIC_DO_NOT_USE_TOGGLE_OFF_DANSWER_POWERED,
NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA,
} from "@/lib/constants";
-import { LefToLineIcon, NewChatIcon, RightToLineIcon } from "../icons/icons";
+import { LeftToLineIcon, NewChatIcon, RightToLineIcon } from "../icons/icons";
import { Tooltip } from "../tooltip/Tooltip";
import { pageType } from "@/app/chat/sessionSidebar/types";
import { Logo } from "../Logo";
@@ -50,7 +50,7 @@ export default function LogoType({
)}
{enterpriseSettings && enterpriseSettings.application_name ? (
@@ -100,7 +100,7 @@ export default function LogoType({
{!toggled && !combinedSettings?.isMobile ? (
) : (
-
+
)}
diff --git a/web/src/components/icons/icons.tsx b/web/src/components/icons/icons.tsx
index 709ca9467..70fa5bb6a 100644
--- a/web/src/components/icons/icons.tsx
+++ b/web/src/components/icons/icons.tsx
@@ -71,6 +71,7 @@ import slackIcon from "../../../public/Slack.png";
import s3Icon from "../../../public/S3.png";
import r2Icon from "../../../public/r2.png";
import salesforceIcon from "../../../public/Salesforce.png";
+
import sharepointIcon from "../../../public/Sharepoint.png";
import teamsIcon from "../../../public/Teams.png";
import mediawikiIcon from "../../../public/MediaWiki.svg";
@@ -82,8 +83,7 @@ import cohereIcon from "../../../public/Cohere.svg";
import voyageIcon from "../../../public/Voyage.png";
import googleIcon from "../../../public/Google.webp";
-import { FaRobot, FaSlack } from "react-icons/fa";
-import { IconType } from "react-icons";
+import { FaRobot } from "react-icons/fa";
export interface IconProps {
size?: number;
@@ -291,7 +291,7 @@ export const AnthropicIcon = ({
);
};
-export const LefToLineIcon = ({
+export const LeftToLineIcon = ({
size = 16,
className = defaultTailwindCSS,
}: IconProps) => {
@@ -2489,3 +2489,95 @@ export const ClosedBookIcon = ({
);
};
+
+export const PinIcon = ({
+ size = 16,
+ className = defaultTailwindCSS,
+}: IconProps) => {
+ return (
+
+ );
+};
+
+export const TwoRightArrowIcons = ({
+ size = 16,
+ className = defaultTailwindCSS,
+}: IconProps) => {
+ return (
+
+ );
+};
+
+export const PlusIcon = ({
+ size = 16,
+ className = defaultTailwindCSS,
+}: IconProps) => {
+ return (
+
+ );
+};
+
+export const MinusIcon = ({
+ size = 16,
+ className = defaultTailwindCSS,
+}: IconProps) => {
+ return (
+
+ );
+};
diff --git a/web/src/components/search/SearchSection.tsx b/web/src/components/search/SearchSection.tsx
index b41dd4237..eedc74a16 100644
--- a/web/src/components/search/SearchSection.tsx
+++ b/web/src/components/search/SearchSection.tsx
@@ -644,7 +644,7 @@ export const SearchSection = ({
{
{!settings?.isMobile &&
diff --git a/web/src/components/search/filtering/Filters.tsx b/web/src/components/search/filtering/Filters.tsx
index 1d2f4a2da..feb2b3452 100644
--- a/web/src/components/search/filtering/Filters.tsx
+++ b/web/src/components/search/filtering/Filters.tsx
@@ -1,7 +1,14 @@
import React, { useState } from "react";
import { DocumentSet, Tag, ValidSources } from "@/lib/types";
import { SourceMetadata } from "@/lib/search/interfaces";
-import { InfoIcon, defaultTailwindCSS } from "../../icons/icons";
+import {
+ GearIcon,
+ InfoIcon,
+ MinusIcon,
+ PlusCircleIcon,
+ PlusIcon,
+ defaultTailwindCSS,
+} from "../../icons/icons";
import { HoverPopup } from "../../HoverPopup";
import {
FiBook,
@@ -75,6 +82,19 @@ export function SourceSelector({
});
};
+ let allSourcesSelected = selectedSources.length > 0;
+
+ const toggleAllSources = () => {
+ if (allSourcesSelected) {
+ setSelectedSources([]);
+ } else {
+ const allSources = listSourceMetadata().filter((source) =>
+ existingSources.includes(source.internalName)
+ );
+ setSelectedSources(allSources);
+ }
+ };
+
return (
0 && (
-
Sources
+
{listSourceMetadata()
.filter((source) => existingSources.includes(source.internalName))
diff --git a/web/src/components/tooltip/CustomTooltip.tsx b/web/src/components/tooltip/CustomTooltip.tsx
index 9bedf6a19..c8a18795b 100644
--- a/web/src/components/tooltip/CustomTooltip.tsx
+++ b/web/src/components/tooltip/CustomTooltip.tsx
@@ -6,6 +6,7 @@ import React, {
createContext,
useContext,
} from "react";
+import { createPortal } from "react-dom";
// Create a context for the tooltip group
const TooltipGroupContext = createContext<{
@@ -52,13 +53,14 @@ export const CustomTooltip = ({
light?: boolean;
showTick?: boolean;
delay?: number;
-
wrap?: boolean;
citation?: boolean;
position?: "top" | "bottom";
}) => {
const [isVisible, setIsVisible] = useState(false);
+ const [tooltipPosition, setTooltipPosition] = useState({ top: 0, left: 0 });
const timeoutRef = useRef
(null);
+ const triggerRef = useRef(null);
const { groupHovered, setGroupHovered, hoverCountRef } =
useContext(TooltipGroupContext);
@@ -69,6 +71,7 @@ export const CustomTooltip = ({
timeoutRef.current = setTimeout(() => {
setIsVisible(true);
setGroupHovered(true);
+ updateTooltipPosition();
}, showDelay);
};
@@ -85,6 +88,16 @@ export const CustomTooltip = ({
}, 100);
};
+ const updateTooltipPosition = () => {
+ if (triggerRef.current) {
+ const rect = triggerRef.current.getBoundingClientRect();
+ setTooltipPosition({
+ top: position === "top" ? rect.top - 10 : rect.bottom + 10,
+ left: rect.left + rect.width / 2,
+ });
+ }
+ };
+
useEffect(() => {
return () => {
if (timeoutRef.current) {
@@ -94,47 +107,60 @@ export const CustomTooltip = ({
}, []);
return (
-
+ <>
{children}
- {isVisible && (
-
- {showTick && (
-
- )}
+ {isVisible &&
+ createPortal(
- {content}
-
-
- )}
-
+ {showTick && (
+
+ )}
+
+ {content}
+
+ ,
+ document.body
+ )}
+ >
);
};
diff --git a/web/tailwind-themes/tailwind.config.js b/web/tailwind-themes/tailwind.config.js
index e43d22043..90be59590 100644
--- a/web/tailwind-themes/tailwind.config.js
+++ b/web/tailwind-themes/tailwind.config.js
@@ -90,6 +90,9 @@ module.exports = {
"background-200": "#e5e5e5", // neutral-200
"background-300": "#d4d4d4", // neutral-300
"background-400": "#a3a3a3", // neutral-400
+ "background-500": "#737373", // neutral-400
+ "background-600": "#525252", // neutral-400
+ "background-700": "#404040", // neutral-400
"background-800": "#262626", // neutral-800
"background-900": "#111827", // gray-900
"background-inverted": "#000000", // black