From 02ac8bce1b637d4fce0b4c795d63e2c20489df32 Mon Sep 17 00:00:00 2001 From: pablodanswer Date: Mon, 2 Sep 2024 18:30:48 -0700 Subject: [PATCH 1/7] add user dropdown seedable list --- .../server/enterprise_settings/models.py | 22 ++++++++++ backend/ee/danswer/server/seeding.py | 1 + web/src/app/admin/settings/interfaces.ts | 9 ++++ web/src/app/chat/ChatPage.tsx | 2 + web/src/app/chat/message/Messages.tsx | 3 ++ web/src/components/UserDropdown.tsx | 26 ++++++++++- web/src/components/icons/DynamicFaIcon.tsx | 44 +++++++++++++++++++ 7 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 web/src/components/icons/DynamicFaIcon.tsx diff --git a/backend/ee/danswer/server/enterprise_settings/models.py b/backend/ee/danswer/server/enterprise_settings/models.py index b4cf91a4a3e..70883ec5ae8 100644 --- a/backend/ee/danswer/server/enterprise_settings/models.py +++ b/backend/ee/danswer/server/enterprise_settings/models.py @@ -1,4 +1,13 @@ +from typing import List + from pydantic import BaseModel +from pydantic import Field + + +class NavigationItem(BaseModel): + link: str + icon: str + title: str class EnterpriseSettings(BaseModel): @@ -10,6 +19,9 @@ class EnterpriseSettings(BaseModel): use_custom_logo: bool = False use_custom_logotype: bool = False + # custom navigation + custom_nav_items: List[NavigationItem] = [] + # custom Chat components two_lines_for_chat_header: bool | None = None custom_lower_disclaimer_content: str | None = None @@ -21,6 +33,16 @@ def check_validity(self) -> None: return +class EnterpriseSettingsModification(EnterpriseSettings): + """ + This class extends EnterpriseSettings to allow for modifications. + It inherits all fields from EnterpriseSettings but excludes custom_nav_items + from being modified directly through this class. + """ + + custom_nav_items: List[NavigationItem] = Field(default=[], exclude=True) + + class AnalyticsScriptUpload(BaseModel): script: str secret_key: str diff --git a/backend/ee/danswer/server/seeding.py b/backend/ee/danswer/server/seeding.py index bbca5acc20a..1b8380dbae6 100644 --- a/backend/ee/danswer/server/seeding.py +++ b/backend/ee/danswer/server/seeding.py @@ -39,6 +39,7 @@ class SeedConfiguration(BaseModel): def _parse_env() -> SeedConfiguration | None: seed_config_str = os.getenv(_SEED_CONFIG_ENV_VAR_NAME) + print(seed_config_str) if not seed_config_str: return None seed_config = SeedConfiguration.parse_raw(seed_config_str) diff --git a/web/src/app/admin/settings/interfaces.ts b/web/src/app/admin/settings/interfaces.ts index 5c3f7a72564..1986da9ab53 100644 --- a/web/src/app/admin/settings/interfaces.ts +++ b/web/src/app/admin/settings/interfaces.ts @@ -15,11 +15,20 @@ export interface Notification { first_shown: string; } +export interface NavigationItem { + link: string; + icon: string; + title: string; +} + export interface EnterpriseSettings { application_name: string | null; use_custom_logo: boolean; use_custom_logotype: boolean; + // custom navigation + custom_nav_items: NavigationItem[]; + // custom Chat components custom_lower_disclaimer_content: string | null; custom_header_content: string | null; diff --git a/web/src/app/chat/ChatPage.tsx b/web/src/app/chat/ChatPage.tsx index 57c663af24e..6a99786998a 100644 --- a/web/src/app/chat/ChatPage.tsx +++ b/web/src/app/chat/ChatPage.tsx @@ -100,6 +100,7 @@ import ExceptionTraceModal from "@/components/modals/ExceptionTraceModal"; import { SEARCH_TOOL_NAME } from "./tools/constants"; import { useUser } from "@/components/user/UserProvider"; import { Stop } from "@phosphor-icons/react"; +import DynamicFaIcon from "@/components/icons/DynamicFaIcon"; const TEMP_USER_MESSAGE_ID = -1; const TEMP_ASSISTANT_MESSAGE_ID = -2; @@ -1601,6 +1602,7 @@ export function ChatPage({ }} /> )} + {settingsToggled && (
+ +
{isEditing ? ( diff --git a/web/src/components/UserDropdown.tsx b/web/src/components/UserDropdown.tsx index 22baa391d9c..8b942c0a997 100644 --- a/web/src/components/UserDropdown.tsx +++ b/web/src/components/UserDropdown.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useRef, useContext } from "react"; +import { useState, useRef, useContext, useEffect } from "react"; import { FiLogOut } from "react-icons/fi"; import Link from "next/link"; import { useRouter } from "next/navigation"; @@ -15,6 +15,8 @@ import { UsersIcon, } from "./icons/icons"; import { pageType } from "@/app/chat/sessionSidebar/types"; +import { NavigationItem } from "@/app/admin/settings/interfaces"; +import DynamicFaIcon, { preloadIcons } from "./icons/DynamicFaIcon"; interface DropdownOptionProps { href?: string; @@ -29,6 +31,7 @@ const DropdownOption: React.FC = ({ icon, label, }) => { + console.log(href); const content = (
{icon} @@ -55,6 +58,14 @@ export function UserDropdown({ const router = useRouter(); const combinedSettings = useContext(SettingsContext); + const customNavItems: NavigationItem[] = + combinedSettings?.enterpriseSettings?.custom_nav_items || []; + + useEffect(() => { + const iconNames = customNavItems.map((item) => item.icon); + preloadIcons(iconNames); + }, [customNavItems]); + if (!combinedSettings) { return null; } @@ -126,6 +137,19 @@ export function UserDropdown({ overscroll-contain `} > + {customNavItems.map((item) => ( + + } + label={item.title} + /> + ))} + {showAdminPanel ? ( = ({ name, ...props }) => { + const IconComponent = getPreloadedIcon(name); + return IconComponent ? ( + + ) : ( + + ); +}; + +// Cache for storing preloaded icons +const iconCache: Record = {}; + +// Preloads icons asynchronously and stores them in the cache +export async function preloadIcons(iconNames: string[]): Promise { + const promises = iconNames.map(async (name) => { + try { + const iconModule = await import("react-icons/fa"); + const iconName = + `Fa${name.charAt(0).toUpperCase() + name.slice(1)}` as keyof typeof iconModule; + iconCache[name] = (iconModule[iconName] as IconType) || FaQuestion; + } catch (error) { + console.error(`Failed to load icon: ${name}`, error); + iconCache[name] = FaQuestion; + } + }); + + await Promise.all(promises); +} + +// Retrieves a preloaded icon from the cache +export function getPreloadedIcon(name: string): IconType | undefined { + return iconCache[name] || FaQuestion; +} + +export default DynamicFaIcon; From 4a5d99a3c5dbb1102a853676fc9afe08b8259ee6 Mon Sep 17 00:00:00 2001 From: pablodanswer Date: Mon, 2 Sep 2024 18:32:53 -0700 Subject: [PATCH 2/7] minor cleanup --- .../ee/danswer/server/enterprise_settings/models.py | 11 ----------- backend/ee/danswer/server/seeding.py | 1 - web/src/app/chat/ChatPage.tsx | 3 --- web/src/app/chat/message/Messages.tsx | 4 ---- 4 files changed, 19 deletions(-) diff --git a/backend/ee/danswer/server/enterprise_settings/models.py b/backend/ee/danswer/server/enterprise_settings/models.py index 70883ec5ae8..2a8d442a973 100644 --- a/backend/ee/danswer/server/enterprise_settings/models.py +++ b/backend/ee/danswer/server/enterprise_settings/models.py @@ -1,7 +1,6 @@ from typing import List from pydantic import BaseModel -from pydantic import Field class NavigationItem(BaseModel): @@ -33,16 +32,6 @@ def check_validity(self) -> None: return -class EnterpriseSettingsModification(EnterpriseSettings): - """ - This class extends EnterpriseSettings to allow for modifications. - It inherits all fields from EnterpriseSettings but excludes custom_nav_items - from being modified directly through this class. - """ - - custom_nav_items: List[NavigationItem] = Field(default=[], exclude=True) - - class AnalyticsScriptUpload(BaseModel): script: str secret_key: str diff --git a/backend/ee/danswer/server/seeding.py b/backend/ee/danswer/server/seeding.py index 1b8380dbae6..bbca5acc20a 100644 --- a/backend/ee/danswer/server/seeding.py +++ b/backend/ee/danswer/server/seeding.py @@ -39,7 +39,6 @@ class SeedConfiguration(BaseModel): def _parse_env() -> SeedConfiguration | None: seed_config_str = os.getenv(_SEED_CONFIG_ENV_VAR_NAME) - print(seed_config_str) if not seed_config_str: return None seed_config = SeedConfiguration.parse_raw(seed_config_str) diff --git a/web/src/app/chat/ChatPage.tsx b/web/src/app/chat/ChatPage.tsx index 6a99786998a..0c63ad2164e 100644 --- a/web/src/app/chat/ChatPage.tsx +++ b/web/src/app/chat/ChatPage.tsx @@ -99,8 +99,6 @@ import ExceptionTraceModal from "@/components/modals/ExceptionTraceModal"; import { SEARCH_TOOL_NAME } from "./tools/constants"; import { useUser } from "@/components/user/UserProvider"; -import { Stop } from "@phosphor-icons/react"; -import DynamicFaIcon from "@/components/icons/DynamicFaIcon"; const TEMP_USER_MESSAGE_ID = -1; const TEMP_ASSISTANT_MESSAGE_ID = -2; @@ -1602,7 +1600,6 @@ export function ChatPage({ }} /> )} - {settingsToggled && (
-
From 25f62d017e446f99701348714b5e249108389bf0 Mon Sep 17 00:00:00 2001 From: pablodanswer Date: Mon, 2 Sep 2024 18:37:44 -0700 Subject: [PATCH 3/7] fix build issue --- web/src/components/UserDropdown.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/src/components/UserDropdown.tsx b/web/src/components/UserDropdown.tsx index 8b942c0a997..3eb0abfaf65 100644 --- a/web/src/components/UserDropdown.tsx +++ b/web/src/components/UserDropdown.tsx @@ -137,8 +137,9 @@ export function UserDropdown({ overscroll-contain `} > - {customNavItems.map((item) => ( + {customNavItems.map((item, i) => ( Date: Mon, 2 Sep 2024 18:39:04 -0700 Subject: [PATCH 4/7] minor type update --- web/src/app/ee/admin/whitelabeling/WhitelabelingForm.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/web/src/app/ee/admin/whitelabeling/WhitelabelingForm.tsx b/web/src/app/ee/admin/whitelabeling/WhitelabelingForm.tsx index 9195d9362d0..814e37058aa 100644 --- a/web/src/app/ee/admin/whitelabeling/WhitelabelingForm.tsx +++ b/web/src/app/ee/admin/whitelabeling/WhitelabelingForm.tsx @@ -64,6 +64,7 @@ export function WhitelabelingForm() { custom_popup_content: enterpriseSettings?.custom_popup_content || "", custom_lower_disclaimer_content: enterpriseSettings?.custom_lower_disclaimer_content || "", + custom_nav_items: enterpriseSettings?.custom_nav_items || [], }} validationSchema={Yup.object().shape({ application_name: Yup.string().nullable(), From fdbc2f350a7cbfa905879d514d78a3a9838467c9 Mon Sep 17 00:00:00 2001 From: pablodanswer Date: Mon, 2 Sep 2024 18:39:38 -0700 Subject: [PATCH 5/7] remove log --- web/src/components/UserDropdown.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/web/src/components/UserDropdown.tsx b/web/src/components/UserDropdown.tsx index 3eb0abfaf65..df1797a5606 100644 --- a/web/src/components/UserDropdown.tsx +++ b/web/src/components/UserDropdown.tsx @@ -31,7 +31,6 @@ const DropdownOption: React.FC = ({ icon, label, }) => { - console.log(href); const content = (
{icon} From fd60ff8527eee6dbe2c0139a73878227d81dff09 Mon Sep 17 00:00:00 2001 From: pablodanswer Date: Mon, 2 Sep 2024 18:44:19 -0700 Subject: [PATCH 6/7] quick update to divider logic (squash) --- web/src/components/UserDropdown.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/web/src/components/UserDropdown.tsx b/web/src/components/UserDropdown.tsx index df1797a5606..305acdf94f4 100644 --- a/web/src/components/UserDropdown.tsx +++ b/web/src/components/UserDropdown.tsx @@ -166,9 +166,12 @@ export function UserDropdown({ ) )} - {showLogout && (showCuratorPanel || showAdminPanel) && ( -
- )} + {showLogout && + (showCuratorPanel || + showAdminPanel || + customNavItems.length > 0) && ( +
+ )} {showLogout && ( Date: Tue, 3 Sep 2024 11:52:46 -0700 Subject: [PATCH 7/7] tiny icon updates --- backend/ee/danswer/server/enterprise_settings/models.py | 3 ++- web/src/components/UserDropdown.tsx | 2 +- web/src/components/admin/connectors/AdminSidebar.tsx | 4 ++-- web/src/components/icons/DynamicFaIcon.tsx | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/backend/ee/danswer/server/enterprise_settings/models.py b/backend/ee/danswer/server/enterprise_settings/models.py index 2a8d442a973..b5f8165f541 100644 --- a/backend/ee/danswer/server/enterprise_settings/models.py +++ b/backend/ee/danswer/server/enterprise_settings/models.py @@ -1,6 +1,7 @@ from typing import List from pydantic import BaseModel +from pydantic import Field class NavigationItem(BaseModel): @@ -19,7 +20,7 @@ class EnterpriseSettings(BaseModel): use_custom_logotype: bool = False # custom navigation - custom_nav_items: List[NavigationItem] = [] + custom_nav_items: List[NavigationItem] = Field(default_factory=list) # custom Chat components two_lines_for_chat_header: bool | None = None diff --git a/web/src/components/UserDropdown.tsx b/web/src/components/UserDropdown.tsx index 305acdf94f4..3bc18b5241b 100644 --- a/web/src/components/UserDropdown.tsx +++ b/web/src/components/UserDropdown.tsx @@ -143,7 +143,7 @@ export function UserDropdown({ icon={ } label={item.title} diff --git a/web/src/components/admin/connectors/AdminSidebar.tsx b/web/src/components/admin/connectors/AdminSidebar.tsx index 56aa08176c7..5b1a8adc831 100644 --- a/web/src/components/admin/connectors/AdminSidebar.tsx +++ b/web/src/components/admin/connectors/AdminSidebar.tsx @@ -74,8 +74,8 @@ export function AdminSidebar({ collections }: { collections: Collection[] }) {
-