mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-09-20 13:05:49 +02:00
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:
@@ -1,4 +1,13 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from pydantic import Field
|
||||||
|
|
||||||
|
|
||||||
|
class NavigationItem(BaseModel):
|
||||||
|
link: str
|
||||||
|
icon: str
|
||||||
|
title: str
|
||||||
|
|
||||||
|
|
||||||
class EnterpriseSettings(BaseModel):
|
class EnterpriseSettings(BaseModel):
|
||||||
@@ -10,6 +19,9 @@ class EnterpriseSettings(BaseModel):
|
|||||||
use_custom_logo: bool = False
|
use_custom_logo: bool = False
|
||||||
use_custom_logotype: bool = False
|
use_custom_logotype: bool = False
|
||||||
|
|
||||||
|
# custom navigation
|
||||||
|
custom_nav_items: List[NavigationItem] = Field(default_factory=list)
|
||||||
|
|
||||||
# custom Chat components
|
# custom Chat components
|
||||||
two_lines_for_chat_header: bool | None = None
|
two_lines_for_chat_header: bool | None = None
|
||||||
custom_lower_disclaimer_content: str | None = None
|
custom_lower_disclaimer_content: str | None = None
|
||||||
|
@@ -15,11 +15,20 @@ export interface Notification {
|
|||||||
first_shown: string;
|
first_shown: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface NavigationItem {
|
||||||
|
link: string;
|
||||||
|
icon: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface EnterpriseSettings {
|
export interface EnterpriseSettings {
|
||||||
application_name: string | null;
|
application_name: string | null;
|
||||||
use_custom_logo: boolean;
|
use_custom_logo: boolean;
|
||||||
use_custom_logotype: boolean;
|
use_custom_logotype: boolean;
|
||||||
|
|
||||||
|
// custom navigation
|
||||||
|
custom_nav_items: NavigationItem[];
|
||||||
|
|
||||||
// custom Chat components
|
// custom Chat components
|
||||||
custom_lower_disclaimer_content: string | null;
|
custom_lower_disclaimer_content: string | null;
|
||||||
custom_header_content: string | null;
|
custom_header_content: string | null;
|
||||||
|
@@ -64,8 +64,6 @@ import { SettingsContext } from "@/components/settings/SettingsProvider";
|
|||||||
import GeneratingImageDisplay from "../tools/GeneratingImageDisplay";
|
import GeneratingImageDisplay from "../tools/GeneratingImageDisplay";
|
||||||
import RegenerateOption from "../RegenerateOption";
|
import RegenerateOption from "../RegenerateOption";
|
||||||
import { LlmOverride } from "@/lib/hooks";
|
import { LlmOverride } from "@/lib/hooks";
|
||||||
import ExceptionTraceModal from "@/components/modals/ExceptionTraceModal";
|
|
||||||
import { EmphasizedClickable } from "@/components/BasicClickable";
|
|
||||||
import { ContinueGenerating } from "./ContinueMessage";
|
import { ContinueGenerating } from "./ContinueMessage";
|
||||||
|
|
||||||
const TOOLS_WITH_CUSTOM_HANDLING = [
|
const TOOLS_WITH_CUSTOM_HANDLING = [
|
||||||
@@ -742,6 +740,7 @@ export const HumanMessage = ({
|
|||||||
<div className="xl:ml-8">
|
<div className="xl:ml-8">
|
||||||
<div className="flex flex-col mr-4">
|
<div className="flex flex-col mr-4">
|
||||||
<FileDisplay alignBubble files={files || []} />
|
<FileDisplay alignBubble files={files || []} />
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<div className="w-full ml-8 flex w-full w-[800px] break-words">
|
<div className="w-full ml-8 flex w-full w-[800px] break-words">
|
||||||
{isEditing ? (
|
{isEditing ? (
|
||||||
|
@@ -64,6 +64,7 @@ export function WhitelabelingForm() {
|
|||||||
custom_popup_content: enterpriseSettings?.custom_popup_content || "",
|
custom_popup_content: enterpriseSettings?.custom_popup_content || "",
|
||||||
custom_lower_disclaimer_content:
|
custom_lower_disclaimer_content:
|
||||||
enterpriseSettings?.custom_lower_disclaimer_content || "",
|
enterpriseSettings?.custom_lower_disclaimer_content || "",
|
||||||
|
custom_nav_items: enterpriseSettings?.custom_nav_items || [],
|
||||||
}}
|
}}
|
||||||
validationSchema={Yup.object().shape({
|
validationSchema={Yup.object().shape({
|
||||||
application_name: Yup.string().nullable(),
|
application_name: Yup.string().nullable(),
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useRef, useContext } from "react";
|
import { useState, useRef, useContext, useEffect } from "react";
|
||||||
import { FiLogOut } from "react-icons/fi";
|
import { FiLogOut } from "react-icons/fi";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
@@ -15,6 +15,8 @@ import {
|
|||||||
UsersIcon,
|
UsersIcon,
|
||||||
} from "./icons/icons";
|
} from "./icons/icons";
|
||||||
import { pageType } from "@/app/chat/sessionSidebar/types";
|
import { pageType } from "@/app/chat/sessionSidebar/types";
|
||||||
|
import { NavigationItem } from "@/app/admin/settings/interfaces";
|
||||||
|
import DynamicFaIcon, { preloadIcons } from "./icons/DynamicFaIcon";
|
||||||
|
|
||||||
interface DropdownOptionProps {
|
interface DropdownOptionProps {
|
||||||
href?: string;
|
href?: string;
|
||||||
@@ -55,6 +57,14 @@ export function UserDropdown({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const combinedSettings = useContext(SettingsContext);
|
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) {
|
if (!combinedSettings) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -126,6 +136,20 @@ export function UserDropdown({
|
|||||||
overscroll-contain
|
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 ? (
|
{showAdminPanel ? (
|
||||||
<DropdownOption
|
<DropdownOption
|
||||||
href="/admin/indexing/status"
|
href="/admin/indexing/status"
|
||||||
@@ -142,9 +166,12 @@ export function UserDropdown({
|
|||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showLogout && (showCuratorPanel || showAdminPanel) && (
|
{showLogout &&
|
||||||
<div className="border-t border-border my-1" />
|
(showCuratorPanel ||
|
||||||
)}
|
showAdminPanel ||
|
||||||
|
customNavItems.length > 0) && (
|
||||||
|
<div className="border-t border-border my-1" />
|
||||||
|
)}
|
||||||
|
|
||||||
{showLogout && (
|
{showLogout && (
|
||||||
<DropdownOption
|
<DropdownOption
|
||||||
|
@@ -74,8 +74,8 @@ export function AdminSidebar({ collections }: { collections: Collection[] }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex w-full justify-center">
|
<div className="flex w-full justify-center">
|
||||||
<Link href={"/chat"}>
|
<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">
|
<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 size={20} />
|
<BackIcon className="my-auto" size={18} />
|
||||||
<p className="ml-1 break-words line-clamp-2 ellipsis leading-none">
|
<p className="ml-1 break-words line-clamp-2 ellipsis leading-none">
|
||||||
Back to{" "}
|
Back to{" "}
|
||||||
{combinedSettings.enterpriseSettings?.application_name ||
|
{combinedSettings.enterpriseSettings?.application_name ||
|
||||||
|
44
web/src/components/icons/DynamicFaIcon.tsx
Normal file
44
web/src/components/icons/DynamicFaIcon.tsx
Normal 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;
|
Reference in New Issue
Block a user