Skip to content

Commit

Permalink
Add user dropdown seed-able list (#2308)
Browse files Browse the repository at this point in the history
* add user dropdown seedable list

* minor cleanup

* fix build issue

* minor type update

* remove log

* quick update to divider logic (squash)

* tiny icon updates
  • Loading branch information
pablonyx authored Sep 3, 2024
1 parent 5da6d79 commit 32359d2
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 8 deletions.
12 changes: 12 additions & 0 deletions backend/ee/danswer/server/enterprise_settings/models.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -10,6 +19,9 @@ class EnterpriseSettings(BaseModel):
use_custom_logo: bool = False
use_custom_logotype: bool = False

# custom navigation
custom_nav_items: List[NavigationItem] = Field(default_factory=list)

# custom Chat components
two_lines_for_chat_header: bool | None = None
custom_lower_disclaimer_content: str | None = None
Expand Down
9 changes: 9 additions & 0 deletions web/src/app/admin/settings/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 1 addition & 2 deletions web/src/app/chat/message/Messages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,6 @@ import { SettingsContext } from "@/components/settings/SettingsProvider";
import GeneratingImageDisplay from "../tools/GeneratingImageDisplay";
import RegenerateOption from "../RegenerateOption";
import { LlmOverride } from "@/lib/hooks";
import ExceptionTraceModal from "@/components/modals/ExceptionTraceModal";
import { EmphasizedClickable } from "@/components/BasicClickable";
import { ContinueGenerating } from "./ContinueMessage";

const TOOLS_WITH_CUSTOM_HANDLING = [
Expand Down Expand Up @@ -742,6 +740,7 @@ export const HumanMessage = ({
<div className="xl:ml-8">
<div className="flex flex-col mr-4">
<FileDisplay alignBubble files={files || []} />

<div className="flex justify-end">
<div className="w-full ml-8 flex w-full w-[800px] break-words">
{isEditing ? (
Expand Down
1 change: 1 addition & 0 deletions web/src/app/ee/admin/whitelabeling/WhitelabelingForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
35 changes: 31 additions & 4 deletions web/src/components/UserDropdown.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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;
Expand Down Expand Up @@ -55,6 +57,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;
}
Expand Down Expand Up @@ -126,6 +136,20 @@ export function UserDropdown({
overscroll-contain
`}
>
{customNavItems.map((item, i) => (
<DropdownOption
key={i}
href={item.link}
icon={
<DynamicFaIcon
name={item.icon}
className="h-4 w-4 my-auto mr-2"
/>
}
label={item.title}
/>
))}

{showAdminPanel ? (
<DropdownOption
href="/admin/indexing/status"
Expand All @@ -142,9 +166,12 @@ export function UserDropdown({
)
)}

{showLogout && (showCuratorPanel || showAdminPanel) && (
<div className="border-t border-border my-1" />
)}
{showLogout &&
(showCuratorPanel ||
showAdminPanel ||
customNavItems.length > 0) && (
<div className="border-t border-border my-1" />
)}

{showLogout && (
<DropdownOption
Expand Down
4 changes: 2 additions & 2 deletions web/src/components/admin/connectors/AdminSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ export function AdminSidebar({ collections }: { collections: Collection[] }) {
</div>
<div className="flex w-full justify-center">
<Link href={"/chat"}>
<button className="text-sm block w-52 py-2.5 flex px-2 text-left bg-background-200 hover:bg-background-200/80 cursor-pointer rounded">
<BackIcon size={20} />
<button className="text-sm flex items-center block w-52 py-2.5 flex px-2 text-left bg-background-200 hover:bg-background-200/80 cursor-pointer rounded">
<BackIcon className="my-auto" size={18} />
<p className="ml-1 break-words line-clamp-2 ellipsis leading-none">
Back to{" "}
{combinedSettings.enterpriseSettings?.application_name ||
Expand Down
44 changes: 44 additions & 0 deletions web/src/components/icons/DynamicFaIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from "react";
import { IconBaseProps, IconType } from "react-icons";
import { FaQuestion } from "react-icons/fa";

interface DynamicIconProps extends IconBaseProps {
name: string;
}

// Renders a FontAwesome icon dynamically based on the provided name
const DynamicFaIcon: React.FC<DynamicIconProps> = ({ name, ...props }) => {
const IconComponent = getPreloadedIcon(name);
return IconComponent ? (
<IconComponent className="h-4 w-4" {...props} />
) : (
<FaQuestion className="h-4 w-4" {...props} />
);
};

// Cache for storing preloaded icons
const iconCache: Record<string, IconType> = {};

// Preloads icons asynchronously and stores them in the cache
export async function preloadIcons(iconNames: string[]): Promise<void> {
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;

0 comments on commit 32359d2

Please sign in to comment.