Add user dropdown seed-able list (#2308)

* add user dropdown seedable list

* minor cleanup

* fix build issue

* minor type update

* remove log

* quick update to divider logic (squash)

* tiny icon updates
This commit is contained in:
pablodanswer
2024-09-03 12:24:50 -07:00
committed by GitHub
parent 5da6d792de
commit 32359d2dff
7 changed files with 100 additions and 8 deletions

View File

@ -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] = Field(default_factory=list)
# custom Chat components
two_lines_for_chat_header: bool | None = None
custom_lower_disclaimer_content: str | None = None

View File

@ -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;

View File

@ -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 = [
@ -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 ? (

View File

@ -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(),

View File

@ -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;
@ -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;
}
@ -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"
@ -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

View File

@ -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 ||

View File

@ -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;