Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add user dropdown seed-able list #2308

Merged
merged 7 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
1 change: 0 additions & 1 deletion web/src/app/chat/ChatPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +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";

const TEMP_USER_MESSAGE_ID = -1;
const TEMP_ASSISTANT_MESSAGE_ID = -2;
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;
Loading