diff --git a/backend/danswer/background/indexing/run_indexing.py b/backend/danswer/background/indexing/run_indexing.py index 4b38d0d95..fa684f020 100644 --- a/backend/danswer/background/indexing/run_indexing.py +++ b/backend/danswer/background/indexing/run_indexing.py @@ -304,9 +304,7 @@ def _prepare_index_attempt(db_session: Session, index_attempt_id: int) -> IndexA return attempt -def run_indexing_entrypoint( - index_attempt_id: int, is_ee: bool = False -) -> None: +def run_indexing_entrypoint(index_attempt_id: int, is_ee: bool = False) -> None: """Entrypoint for indexing run when using dask distributed. Wraps the actual logic in a `try` block so that we can catch any exceptions and mark the attempt as failed.""" diff --git a/backend/danswer/background/update.py b/backend/danswer/background/update.py index 93ab45909..d5ae2bd65 100755 --- a/backend/danswer/background/update.py +++ b/backend/danswer/background/update.py @@ -35,10 +35,10 @@ from danswer.db.models import IndexModelStatus from danswer.db.swap_index import check_index_swap from danswer.search.search_nlp_models import warm_up_encoders from danswer.utils.logger import setup_logger +from danswer.utils.variable_functionality import global_version from shared_configs.configs import INDEXING_MODEL_SERVER_HOST from shared_configs.configs import LOG_LEVEL from shared_configs.configs import MODEL_SERVER_PORT -from danswer.utils.variable_functionality import global_version logger = setup_logger() diff --git a/backend/ee/danswer/server/enterprise_settings/models.py b/backend/ee/danswer/server/enterprise_settings/models.py index 8cd3dacd4..00b2c995c 100644 --- a/backend/ee/danswer/server/enterprise_settings/models.py +++ b/backend/ee/danswer/server/enterprise_settings/models.py @@ -9,5 +9,10 @@ class EnterpriseSettings(BaseModel): application_name: str | None = None use_custom_logo: bool = False + # custom Chat components + custom_header_content: str | None = None + custom_popup_header: str | None = None + custom_popup_content: str | None = None + def check_validity(self) -> None: return diff --git a/web/src/app/chat/Chat.tsx b/web/src/app/chat/Chat.tsx index 8c9052186..0d5a32756 100644 --- a/web/src/app/chat/Chat.tsx +++ b/web/src/app/chat/Chat.tsx @@ -41,13 +41,14 @@ import { usePopup } from "@/components/admin/connectors/Popup"; import { ResizableSection } from "@/components/resizable/ResizableSection"; import { DanswerInitializingLoader } from "@/components/DanswerInitializingLoader"; import { ChatIntro } from "./ChatIntro"; -import { HEADER_PADDING } from "@/lib/constants"; import { computeAvailableFilters } from "@/lib/filters"; import { useDocumentSelection } from "./useDocumentSelection"; import { StarterMessage } from "./StarterMessage"; import { ShareChatSessionModal } from "./modal/ShareChatSessionModal"; import { SEARCH_PARAM_NAMES, shouldSubmitOnLoad } from "./searchParams"; import { Persona } from "../admin/assistants/interfaces"; +import { ChatBanner } from "./ChatBanner"; +import { HEADER_PADDING } from "@/lib/constants"; const MAX_INPUT_HEIGHT = 200; @@ -594,6 +595,10 @@ export const Chat = ({ className={`w-full h-full ${HEADER_PADDING} flex flex-col overflow-y-auto overflow-x-hidden relative`} ref={scrollableDivRef} > + {/* ChatBanner is a custom banner that displays a admin-specified message at + the top of the chat page. Only used in the EE version of the app. */} + + {livePersona && (
diff --git a/web/src/app/chat/ChatBanner.tsx b/web/src/app/chat/ChatBanner.tsx new file mode 100644 index 000000000..b4d933019 --- /dev/null +++ b/web/src/app/chat/ChatBanner.tsx @@ -0,0 +1,48 @@ +"use client"; + +import ReactMarkdown from "react-markdown"; +import { SettingsContext } from "@/components/settings/SettingsProviderClientSideHelper"; +import { useContext } from "react"; +import remarkGfm from "remark-gfm"; + +export function ChatBanner() { + const settings = useContext(SettingsContext); + if (!settings?.enterpriseSettings?.custom_header_content) { + return null; + } + + return ( +
+
+ +
+
+ ); +} diff --git a/web/src/app/chat/ChatPage.tsx b/web/src/app/chat/ChatPage.tsx index 78114c5df..bf8869734 100644 --- a/web/src/app/chat/ChatPage.tsx +++ b/web/src/app/chat/ChatPage.tsx @@ -72,6 +72,7 @@ import { useChatContext } from "@/components/context/ChatContext"; import { UserDropdown } from "@/components/UserDropdown"; import { v4 as uuidv4 } from "uuid"; import { orderAssistantsForUser } from "@/lib/assistants/orderAssistants"; +import { ChatPopup } from "./ChatPopup"; const MAX_INPUT_HEIGHT = 200; const TEMP_USER_MESSAGE_ID = -1; @@ -872,6 +873,10 @@ export function ChatPage({ + {/* ChatPopup is a custom popup that displays a admin-specified message on initial user visit. + Only used in the EE version of the app. */} + +
{ + setCompletedFlow( + localStorage.getItem(ALL_USERS_INITIAL_POPUP_FLOW_COMPLETED) === "true" + ); + }); + + const settings = useContext(SettingsContext); + if (!settings?.enterpriseSettings?.custom_popup_content || completedFlow) { + return null; + } + + let popupTitle = settings.enterpriseSettings.custom_popup_header; + if (!popupTitle) { + popupTitle = `Welcome to ${ + settings.enterpriseSettings.application_name || "Danswer" + }!`; + } + + return ( + + <> + ( + + ), + p: ({ node, ...props }) =>

, + }} + remarkPlugins={[remarkGfm]} + > + {settings.enterpriseSettings.custom_popup_content} + + +

+ +
+ + + ); +} diff --git a/web/src/app/chat/documentSidebar/DocumentSidebar.tsx b/web/src/app/chat/documentSidebar/DocumentSidebar.tsx index a7f4170b5..c5913d406 100644 --- a/web/src/app/chat/documentSidebar/DocumentSidebar.tsx +++ b/web/src/app/chat/documentSidebar/DocumentSidebar.tsx @@ -7,8 +7,8 @@ import { SelectedDocumentDisplay } from "./SelectedDocumentDisplay"; import { removeDuplicateDocs } from "@/lib/documentUtils"; import { BasicSelectable } from "@/components/BasicClickable"; import { Message, RetrievalType } from "../interfaces"; -import { HEADER_PADDING } from "@/lib/constants"; import { HoverPopup } from "@/components/HoverPopup"; +import { HEADER_PADDING } from "@/lib/constants"; function SectionHeader({ name, diff --git a/web/src/app/ee/admin/whitelabeling/WhitelabelingForm.tsx b/web/src/app/ee/admin/whitelabeling/WhitelabelingForm.tsx index dab202ff0..9025e30b2 100644 --- a/web/src/app/ee/admin/whitelabeling/WhitelabelingForm.tsx +++ b/web/src/app/ee/admin/whitelabeling/WhitelabelingForm.tsx @@ -11,7 +11,7 @@ import { SubLabel, TextFormField, } from "@/components/admin/connectors/Field"; -import { Button } from "@tremor/react"; +import { Button, Divider } from "@tremor/react"; import { ImageUpload } from "./ImageUpload"; export function WhitelabelingForm() { @@ -42,7 +42,6 @@ export function WhitelabelingForm() { alert(`Failed to update settings. ${errorMsg}`); } } - console.log(enterpriseSettings); return (
@@ -50,10 +49,17 @@ export function WhitelabelingForm() { initialValues={{ application_name: enterpriseSettings?.application_name || null, use_custom_logo: enterpriseSettings?.use_custom_logo || false, + custom_header_content: + enterpriseSettings?.custom_header_content || "", + custom_popup_header: enterpriseSettings?.custom_popup_header || "", + custom_popup_content: enterpriseSettings?.custom_popup_content || "", }} validationSchema={Yup.object().shape({ - application_name: Yup.string(), + application_name: Yup.string().nullable(), use_custom_logo: Yup.boolean().required(), + custom_header_content: Yup.string().nullable(), + custom_popup_header: Yup.string().nullable(), + custom_popup_content: Yup.string().nullable(), })} onSubmit={async (values, formikHelpers) => { formikHelpers.setSubmitting(true); @@ -138,6 +144,44 @@ export function WhitelabelingForm() { setSelectedFile={setSelectedFile} /> + + +
+ +
+ + + +
+ +
+ +
+ +
+ diff --git a/web/src/app/search/page.tsx b/web/src/app/search/page.tsx index d84bb7926..c87573b72 100644 --- a/web/src/app/search/page.tsx +++ b/web/src/app/search/page.tsx @@ -23,6 +23,7 @@ import { personaComparator } from "../admin/assistants/lib"; import { FullEmbeddingModelResponse } from "../admin/models/embedding/embeddingModels"; import { NoSourcesModal } from "@/components/initialSetup/search/NoSourcesModal"; import { NoCompleteSourcesModal } from "@/components/initialSetup/search/NoCompleteSourceModal"; +import { ChatPopup } from "../chat/ChatPopup"; export default async function Home() { // Disable caching so we always get the up to date connector / document set / persona info @@ -163,6 +164,10 @@ export default async function Home() { )} + {/* ChatPopup is a custom popup that displays a admin-specified message on initial user visit. + Only used in the EE version of the app. */} + +
diff --git a/web/src/lib/constants.ts b/web/src/lib/constants.ts index 5a51c7121..e4eba107e 100644 --- a/web/src/lib/constants.ts +++ b/web/src/lib/constants.ts @@ -15,7 +15,7 @@ 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 HEADER_PADDING = `pt-[64px]`; export const LOGOUT_DISABLED = process.env.NEXT_PUBLIC_DISABLE_LOGOUT?.toLowerCase() === "true"; diff --git a/web/tailwind-themes/tailwind.config.js b/web/tailwind-themes/tailwind.config.js index b36e323f5..12fc3f339 100644 --- a/web/tailwind-themes/tailwind.config.js +++ b/web/tailwind-themes/tailwind.config.js @@ -42,9 +42,11 @@ module.exports = { "background-emphasis": "#f6f7f8", "background-strong": "#eaecef", "background-search": "#ffffff", + "background-custom-header": "#f3f4f6", // text or icons link: "#3b82f6", // blue-500 + "link-hover": "#1d4ed8", // blue-700 subtle: "#6b7280", // gray-500 default: "#4b5563", // gray-600 emphasis: "#374151", // gray-700