From 37645756c79a18da9c9331e0e699c1087829695e Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Sat, 9 May 2026 11:34:17 -0700 Subject: [PATCH] feat(dashboard): refine navbar zones and Agent Platform notice Restructure the admin navbar for production users: clear product vs community vs personal columns with vertical dividers, icon-only Slack/GitHub in a shared chip, and Docs/Blog typography aligned on an 8px rhythm. Add a notifications bell with popover linking to the LiteLLM Agent Platform repo and optional mark-as-read persistence. Promote the account control with initials avatar, single-line display name, and navDisplayName mapping for placeholder user ids (e.g. default_user_id). Co-authored-by: Cursor --- .../Navbar/BlogDropdown/BlogDropdown.tsx | 9 +- .../CommunityEngagementButtons.tsx | 53 +++++----- .../NotificationsBell.test.tsx | 46 +++++++++ .../NotificationsBell/NotificationsBell.tsx | 66 +++++++++++++ .../Navbar/UserDropdown/UserDropdown.test.tsx | 62 +++++++----- .../Navbar/UserDropdown/UserDropdown.tsx | 89 ++++++++++++----- .../components/Navbar/navDisplayName.test.ts | 17 ++++ .../src/components/Navbar/navDisplayName.ts | 20 ++++ .../components/Navbar/navProductLinkClass.ts | 3 + .../src/components/navbar.test.tsx | 17 ++-- .../src/components/navbar.tsx | 98 ++++++++++++------- 11 files changed, 360 insertions(+), 120 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/Navbar/NotificationsBell/NotificationsBell.test.tsx create mode 100644 ui/litellm-dashboard/src/components/Navbar/NotificationsBell/NotificationsBell.tsx create mode 100644 ui/litellm-dashboard/src/components/Navbar/navDisplayName.test.ts create mode 100644 ui/litellm-dashboard/src/components/Navbar/navDisplayName.ts create mode 100644 ui/litellm-dashboard/src/components/Navbar/navProductLinkClass.ts diff --git a/ui/litellm-dashboard/src/components/Navbar/BlogDropdown/BlogDropdown.tsx b/ui/litellm-dashboard/src/components/Navbar/BlogDropdown/BlogDropdown.tsx index ddb2a33cdaa..da0a03800a2 100644 --- a/ui/litellm-dashboard/src/components/Navbar/BlogDropdown/BlogDropdown.tsx +++ b/ui/litellm-dashboard/src/components/Navbar/BlogDropdown/BlogDropdown.tsx @@ -1,6 +1,7 @@ import { useDisableBlogPosts } from "@/app/(dashboard)/hooks/useDisableBlogPosts"; import { useBlogPosts, type BlogPost } from "@/app/(dashboard)/hooks/blogPosts/useBlogPosts"; -import { LoadingOutlined } from "@ant-design/icons"; +import { NAV_PRODUCT_LINK_CLASS } from "@/components/Navbar/navProductLinkClass"; +import { DownOutlined, LoadingOutlined } from "@ant-design/icons"; import { Button, Dropdown, Space, Typography } from "antd"; import type { MenuProps } from "antd"; import React from "react"; @@ -74,9 +75,13 @@ export const BlogDropdown: React.FC = () => { ]; } + // Blog opens a post list; Docs is a single outbound link — navbar adds a layout-only chevron there for alignment. return ( - + ); }; diff --git a/ui/litellm-dashboard/src/components/Navbar/CommunityEngagementButtons/CommunityEngagementButtons.tsx b/ui/litellm-dashboard/src/components/Navbar/CommunityEngagementButtons/CommunityEngagementButtons.tsx index 649bcc0b589..f6a43196a32 100644 --- a/ui/litellm-dashboard/src/components/Navbar/CommunityEngagementButtons/CommunityEngagementButtons.tsx +++ b/ui/litellm-dashboard/src/components/Navbar/CommunityEngagementButtons/CommunityEngagementButtons.tsx @@ -1,36 +1,45 @@ import { useDisableShowPrompts } from "@/app/(dashboard)/hooks/useDisableShowPrompts"; import { GithubOutlined, SlackOutlined } from "@ant-design/icons"; -import { Button } from "antd"; +import { Tooltip } from "antd"; import React from "react"; +const iconBtnClass = + "inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-md border-0 bg-transparent text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-700 cursor-pointer"; + export const CommunityEngagementButtons: React.FC = () => { const disableShowPrompts = useDisableShowPrompts(); - // Hide buttons if prompts are disabled if (disableShowPrompts) { return null; } return ( - <> - - - +
+ + + + + + + + + + +
); }; diff --git a/ui/litellm-dashboard/src/components/Navbar/NotificationsBell/NotificationsBell.test.tsx b/ui/litellm-dashboard/src/components/Navbar/NotificationsBell/NotificationsBell.test.tsx new file mode 100644 index 00000000000..19333bf1ff5 --- /dev/null +++ b/ui/litellm-dashboard/src/components/Navbar/NotificationsBell/NotificationsBell.test.tsx @@ -0,0 +1,46 @@ +import { renderWithProviders, screen } from "../../../../tests/test-utils"; +import { NotificationsBell, AGENT_PLATFORM_URL } from "./NotificationsBell"; +import React from "react"; +import userEvent from "@testing-library/user-event"; + +describe("NotificationsBell", () => { + beforeEach(() => { + localStorage.clear(); + }); + + it("should open notifications with Agent Platform details and GitHub link", async () => { + const user = userEvent.setup(); + renderWithProviders(); + await user.click(screen.getByRole("button", { name: /^notifications$/i })); + expect(screen.getByText(/LiteLLM Agent Platform/i)).toBeInTheDocument(); + const githubBtn = screen.getByRole("link", { name: /^GitHub$/i }); + expect(githubBtn).toHaveAttribute("href", AGENT_PLATFORM_URL); + expect(githubBtn).toHaveAttribute("target", "_blank"); + expect(githubBtn).toHaveAttribute("rel", "noopener noreferrer"); + }); + + it("should offer mark as read when announcement is unread", async () => { + const user = userEvent.setup(); + renderWithProviders(); + await user.click(screen.getByRole("button", { name: /^notifications$/i })); + expect(screen.getByRole("button", { name: /^mark as read$/i })).toBeInTheDocument(); + }); + + it("should hide mark as read and persist after marking read", async () => { + const user = userEvent.setup(); + renderWithProviders(); + await user.click(screen.getByRole("button", { name: /^notifications$/i })); + await user.click(screen.getByRole("button", { name: /^mark as read$/i })); + expect(localStorage.getItem("litellmHideAgentPlatformBanner")).toBe("true"); + await user.click(screen.getByRole("button", { name: /^notifications$/i })); + expect(screen.queryByRole("button", { name: /^mark as read$/i })).not.toBeInTheDocument(); + }); + + it("should not show mark as read when previously dismissed", async () => { + localStorage.setItem("litellmHideAgentPlatformBanner", "true"); + const user = userEvent.setup(); + renderWithProviders(); + await user.click(screen.getByRole("button", { name: /^notifications$/i })); + expect(screen.queryByRole("button", { name: /^mark as read$/i })).not.toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/Navbar/NotificationsBell/NotificationsBell.tsx b/ui/litellm-dashboard/src/components/Navbar/NotificationsBell/NotificationsBell.tsx new file mode 100644 index 00000000000..5f755b21d61 --- /dev/null +++ b/ui/litellm-dashboard/src/components/Navbar/NotificationsBell/NotificationsBell.tsx @@ -0,0 +1,66 @@ +"use client"; + +import { BellOutlined } from "@ant-design/icons"; +import { Badge, Button, Popover, Typography } from "antd"; +import React, { useEffect, useState } from "react"; + +const STORAGE_KEY = "litellmHideAgentPlatformBanner"; +export const AGENT_PLATFORM_URL = "https://github.com/BerriAI/litellm-agent-platform"; + +export const NotificationsBell: React.FC = () => { + const [hasUnread, setHasUnread] = useState(false); + const [open, setOpen] = useState(false); + + useEffect(() => { + try { + setHasUnread(localStorage.getItem(STORAGE_KEY) !== "true"); + } catch { + setHasUnread(true); + } + }, []); + + const markDismissed = () => { + try { + localStorage.setItem(STORAGE_KEY, "true"); + } catch { + /* ignore */ + } + setHasUnread(false); + setOpen(false); + }; + + const content = ( +
+ + LiteLLM Agent Platform + + + Open-source agent infra — sandboxes, durable sessions, and workers on AWS Fargate. + +
+ + {hasUnread ? ( + + ) : null} +
+
+ ); + + return ( + + + + ); +}; diff --git a/ui/litellm-dashboard/src/components/Navbar/UserDropdown/UserDropdown.test.tsx b/ui/litellm-dashboard/src/components/Navbar/UserDropdown/UserDropdown.test.tsx index de853303c15..31ddae31798 100644 --- a/ui/litellm-dashboard/src/components/Navbar/UserDropdown/UserDropdown.test.tsx +++ b/ui/litellm-dashboard/src/components/Navbar/UserDropdown/UserDropdown.test.tsx @@ -37,6 +37,8 @@ vi.mock("@/utils/localStorageUtils", () => ({ describe("UserDropdown", () => { const mockOnLogout = vi.fn(); + const getAccountTrigger = () => screen.getByRole("button", { name: /account menu/i }); + beforeEach(() => { vi.clearAllMocks(); mockUseAuthorizedImpl = () => ({ @@ -55,22 +57,23 @@ describe("UserDropdown", () => { it("should render", () => { renderWithProviders(); - expect(screen.getByRole("button")).toBeInTheDocument(); + expect(getAccountTrigger()).toBeInTheDocument(); }); - it("should display user button with User text", () => { + it("should surface initials and account menu affordance", () => { renderWithProviders(); - expect(screen.getByText("User")).toBeInTheDocument(); + expect(getAccountTrigger()).toBeInTheDocument(); + expect(screen.getByText("TE")).toBeInTheDocument(); }); it("should show user email when dropdown is opened", async () => { const user = userEvent.setup(); renderWithProviders(); - await user.click(screen.getByText("User")); + await user.click(getAccountTrigger()); await waitFor(() => { - expect(screen.getByText("test@example.com")).toBeInTheDocument(); + expect(screen.getAllByText("test@example.com").length).toBeGreaterThan(0); }); }); @@ -78,7 +81,7 @@ describe("UserDropdown", () => { const user = userEvent.setup(); renderWithProviders(); - await user.click(screen.getByText("User")); + await user.click(getAccountTrigger()); await waitFor(() => { expect(screen.getByText("test-user-id")).toBeInTheDocument(); @@ -89,10 +92,10 @@ describe("UserDropdown", () => { const user = userEvent.setup(); renderWithProviders(); - await user.click(screen.getByText("User")); + await user.click(getAccountTrigger()); await waitFor(() => { - expect(screen.getByText("Admin")).toBeInTheDocument(); + expect(screen.getAllByText("Admin").length).toBeGreaterThan(0); }); }); @@ -100,7 +103,7 @@ describe("UserDropdown", () => { const user = userEvent.setup(); renderWithProviders(); - await user.click(screen.getByText("User")); + await user.click(getAccountTrigger()); await waitFor(() => { expect(screen.getByText("Standard")).toBeInTheDocument(); @@ -118,7 +121,7 @@ describe("UserDropdown", () => { renderWithProviders(); - await user.click(screen.getByText("User")); + await user.click(getAccountTrigger()); await waitFor(() => { expect(screen.getByText("Premium")).toBeInTheDocument(); @@ -129,10 +132,10 @@ describe("UserDropdown", () => { const user = userEvent.setup(); renderWithProviders(); - await user.click(screen.getByText("User")); + await user.click(getAccountTrigger()); await waitFor(() => { - expect(screen.getByText("test@example.com")).toBeInTheDocument(); + expect(screen.getAllByText("test@example.com").length).toBeGreaterThan(0); }); await user.click(screen.getByText("Logout")); @@ -144,10 +147,10 @@ describe("UserDropdown", () => { const user = userEvent.setup(); renderWithProviders(); - await user.click(screen.getByText("User")); + await user.click(getAccountTrigger()); await waitFor(() => { - expect(screen.getByText("test@example.com")).toBeInTheDocument(); + expect(screen.getAllByText("test@example.com").length).toBeGreaterThan(0); }); const toggle = screen.getByLabelText("Toggle hide new feature indicators"); @@ -169,10 +172,10 @@ describe("UserDropdown", () => { renderWithProviders(); - await user.click(screen.getByText("User")); + await user.click(getAccountTrigger()); await waitFor(() => { - expect(screen.getByText("test@example.com")).toBeInTheDocument(); + expect(screen.getAllByText("test@example.com").length).toBeGreaterThan(0); }); const toggle = screen.getByLabelText("Toggle hide new feature indicators"); @@ -189,10 +192,10 @@ describe("UserDropdown", () => { const user = userEvent.setup(); renderWithProviders(); - await user.click(screen.getByText("User")); + await user.click(getAccountTrigger()); await waitFor(() => { - expect(screen.getByText("test@example.com")).toBeInTheDocument(); + expect(screen.getAllByText("test@example.com").length).toBeGreaterThan(0); }); const toggle = screen.getByLabelText("Toggle hide all prompts"); @@ -215,10 +218,10 @@ describe("UserDropdown", () => { renderWithProviders(); - await user.click(screen.getByText("User")); + await user.click(getAccountTrigger()); await waitFor(() => { - expect(screen.getByText("test@example.com")).toBeInTheDocument(); + expect(screen.getAllByText("test@example.com").length).toBeGreaterThan(0); }); const toggle = screen.getByLabelText("Toggle hide all prompts"); @@ -231,6 +234,17 @@ describe("UserDropdown", () => { expect(localStorageUtils.emitLocalStorageChange).toHaveBeenCalledWith("disableShowPrompts"); }); + it("should show Account in the trigger when user id is the default placeholder", () => { + mockUseAuthorizedImpl = () => ({ + userId: "default_user_id", + userEmail: null as any, + userRole: "Admin", + premiumUser: false, + }); + renderWithProviders(); + expect(screen.getByText("Account")).toBeInTheDocument(); + }); + it("should display dash when user email is not available", async () => { const user = userEvent.setup(); mockUseAuthorizedImpl = () => ({ @@ -242,7 +256,7 @@ describe("UserDropdown", () => { renderWithProviders(); - await user.click(screen.getByText("User")); + await user.click(getAccountTrigger()); await waitFor(() => { expect(screen.getByText("-")).toBeInTheDocument(); @@ -260,7 +274,7 @@ describe("UserDropdown", () => { renderWithProviders(); - await user.click(screen.getByText("User")); + await user.click(getAccountTrigger()); await waitFor(() => { const dashElements = screen.getAllByText("-"); @@ -277,10 +291,10 @@ describe("UserDropdown", () => { renderWithProviders(); - await user.click(screen.getByText("User")); + await user.click(getAccountTrigger()); await waitFor(() => { - expect(screen.getByText("test@example.com")).toBeInTheDocument(); + expect(screen.getAllByText("test@example.com").length).toBeGreaterThan(0); }); const toggle = screen.getByLabelText("Toggle hide new feature indicators"); diff --git a/ui/litellm-dashboard/src/components/Navbar/UserDropdown/UserDropdown.tsx b/ui/litellm-dashboard/src/components/Navbar/UserDropdown/UserDropdown.tsx index 6490cd32fa7..50c90cfef3d 100644 --- a/ui/litellm-dashboard/src/components/Navbar/UserDropdown/UserDropdown.tsx +++ b/ui/litellm-dashboard/src/components/Navbar/UserDropdown/UserDropdown.tsx @@ -9,6 +9,7 @@ import { removeLocalStorageItem, setLocalStorageItem, } from "@/utils/localStorageUtils"; +import { navAccountDisplayName } from "@/components/Navbar/navDisplayName"; import { CrownOutlined, DownOutlined, @@ -18,11 +19,44 @@ import { UserOutlined, } from "@ant-design/icons"; import type { MenuProps } from "antd"; -import { Button, Divider, Dropdown, Space, Switch, Tag, Tooltip, Typography } from "antd"; +import { Divider, Dropdown, Space, Switch, Tag, Tooltip, Typography } from "antd"; import React, { useEffect, useState } from "react"; const { Text } = Typography; +function hueFromString(seed: string): number { + let h = 0; + for (let i = 0; i < seed.length; i += 1) { + h = seed.charCodeAt(i) + ((h << 5) - h); + } + return Math.abs(h) % 360; +} + +function initialsFromIdentity(email: string | null, userId: string | null): string { + const local = email?.split("@")[0]?.trim(); + if (local) { + const parts = local + .replace(/[^a-zA-Z0-9]+/g, " ") + .trim() + .split(/\s+/) + .filter(Boolean); + if (parts.length >= 2) { + return `${parts[0]!.charAt(0)}${parts[1]!.charAt(0)}`.toUpperCase(); + } + if (parts.length === 1) { + const p = parts[0]!; + return p.length >= 2 ? p.slice(0, 2).toUpperCase() : `${p.charAt(0)}`.toUpperCase(); + } + } + if (userId && userId.length >= 2) { + return userId.slice(0, 2).toUpperCase(); + } + if (userId && userId.length === 1) { + return `${userId.toUpperCase()}•`; + } + return "?"; +} + interface UserDropdownProps { onLogout: () => void; } @@ -61,19 +95,12 @@ const UserDropdown: React.FC = ({ onLogout }) => { {userEmail || "-"} {premiumUser ? ( - } - color="gold" - > + } color="gold"> Premium ) : ( - } - > - Standard - + }>Standard )} @@ -83,12 +110,7 @@ const UserDropdown: React.FC = ({ onLogout }) => { User ID - + {userId || "-"} @@ -189,13 +211,17 @@ const UserDropdown: React.FC = ({ onLogout }) => { ); + const seed = userEmail || userId || "user"; + const initials = initialsFromIdentity(userEmail, userId); + const hue = hueFromString(seed); + const displayName = navAccountDisplayName(userEmail, userId); + return ( ( -
+
{renderUserInfoSection()} {React.cloneElement(menu as React.ReactElement, { @@ -204,13 +230,24 @@ const UserDropdown: React.FC = ({ onLogout }) => {
)} > - + ); }; diff --git a/ui/litellm-dashboard/src/components/Navbar/navDisplayName.test.ts b/ui/litellm-dashboard/src/components/Navbar/navDisplayName.test.ts new file mode 100644 index 00000000000..96e768c5d31 --- /dev/null +++ b/ui/litellm-dashboard/src/components/Navbar/navDisplayName.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, it } from "vitest"; +import { navAccountDisplayName } from "./navDisplayName"; + +describe("navAccountDisplayName", () => { + it("should prefer email when present", () => { + expect(navAccountDisplayName("x@y.com", "ignored")).toBe("x@y.com"); + }); + + it("should map default_user_id placeholder to Account", () => { + expect(navAccountDisplayName(null, "default_user_id")).toBe("Account"); + expect(navAccountDisplayName(null, "DEFAULT_USER_ID")).toBe("Account"); + }); + + it("should show a sensible token when user id is non-placeholder", () => { + expect(navAccountDisplayName(null, "user-uuid-123")).toBe("user-uuid-123"); + }); +}); diff --git a/ui/litellm-dashboard/src/components/Navbar/navDisplayName.ts b/ui/litellm-dashboard/src/components/Navbar/navDisplayName.ts new file mode 100644 index 00000000000..a09d559b506 --- /dev/null +++ b/ui/litellm-dashboard/src/components/Navbar/navDisplayName.ts @@ -0,0 +1,20 @@ +/** Primary label for the navbar account control — avoids raw placeholder JWT/user IDs in the UI. */ +export function navAccountDisplayName(userEmail: string | null, userId: string | null): string { + const email = userEmail?.trim(); + if (email) { + return email; + } + const id = userId?.trim(); + if (!id) { + return "Account"; + } + const lower = id.toLowerCase(); + if ( + lower === "default_user_id" || + lower === "default-user-id" || + /^default[_\s-]?user[_\s-]?id$/i.test(id) + ) { + return "Account"; + } + return id; +} diff --git a/ui/litellm-dashboard/src/components/Navbar/navProductLinkClass.ts b/ui/litellm-dashboard/src/components/Navbar/navProductLinkClass.ts new file mode 100644 index 00000000000..ca4b2e5d1f3 --- /dev/null +++ b/ui/litellm-dashboard/src/components/Navbar/navProductLinkClass.ts @@ -0,0 +1,3 @@ +/** Shared styling for Docs / Blog in the top nav (product navigation zone). */ +export const NAV_PRODUCT_LINK_CLASS = + "inline-flex h-9 shrink-0 items-center justify-center gap-1 rounded-md px-2 text-sm font-medium leading-none text-gray-800 transition-colors hover:bg-gray-100 hover:text-gray-950"; diff --git a/ui/litellm-dashboard/src/components/navbar.test.tsx b/ui/litellm-dashboard/src/components/navbar.test.tsx index 9a3c781aaab..246604a475b 100644 --- a/ui/litellm-dashboard/src/components/navbar.test.tsx +++ b/ui/litellm-dashboard/src/components/navbar.test.tsx @@ -37,8 +37,8 @@ vi.mock("./Navbar/UserDropdown/UserDropdown", async (importOriginal) => { const [open, setOpen] = useState(false); return (
- {open && (
@@ -146,15 +146,16 @@ describe("Navbar", () => { it("should render without crashing", () => { renderWithProviders(); + expect(screen.getByRole("button", { name: /^notifications$/i })).toBeInTheDocument(); expect(screen.getByText("Docs")).toBeInTheDocument(); - expect(screen.getByText("User")).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /open account menu/i })).toBeInTheDocument(); }); it("should display user information in dropdown", async () => { const user = userEvent.setup(); renderWithProviders(); - await user.click(screen.getByText("User")); + await user.click(screen.getByRole("button", { name: /open account menu/i })); await waitFor(() => { expect(screen.getByText("test-user")).toBeInTheDocument(); @@ -193,7 +194,7 @@ describe("Navbar", () => { }); renderWithProviders(); - await user.click(screen.getByText("User")); + await user.click(screen.getByRole("button", { name: /open account menu/i })); await waitFor(() => { expect(screen.getByText("Premium")).toBeInTheDocument(); @@ -230,7 +231,7 @@ describe("Navbar", () => { const publicPageProps = { ...defaultProps, isPublicPage: true }; renderWithProviders(); - expect(screen.queryByText("User")).not.toBeInTheDocument(); + expect(screen.queryByRole("button", { name: /open account menu/i })).not.toBeInTheDocument(); }); it("should handle hide new features toggle", async () => { @@ -244,7 +245,7 @@ describe("Navbar", () => { renderWithProviders(); - await user.click(screen.getByText("User")); + await user.click(screen.getByRole("button", { name: /open account menu/i })); await waitFor(() => { expect(screen.getByText("test-user")).toBeInTheDocument(); @@ -269,7 +270,7 @@ describe("Navbar", () => { renderWithProviders(); - await user.click(screen.getByText("User")); + await user.click(screen.getByRole("button", { name: /open account menu/i })); await waitFor(() => { expect(screen.getByText("test-user")).toBeInTheDocument(); diff --git a/ui/litellm-dashboard/src/components/navbar.tsx b/ui/litellm-dashboard/src/components/navbar.tsx index 6e0a47a23f9..5213409f8c3 100644 --- a/ui/litellm-dashboard/src/components/navbar.tsx +++ b/ui/litellm-dashboard/src/components/navbar.tsx @@ -1,16 +1,20 @@ -import { useHealthReadiness } from "@/app/(dashboard)/hooks/healthReadiness/useHealthReadiness"; import { useDisableBouncingIcon } from "@/app/(dashboard)/hooks/useDisableBouncingIcon"; +import { useDisableShowPrompts } from "@/app/(dashboard)/hooks/useDisableShowPrompts"; +import { useHealthReadiness } from "@/app/(dashboard)/hooks/healthReadiness/useHealthReadiness"; +import { useWorker } from "@/hooks/useWorker"; import { getProxyBaseUrl } from "@/components/networking"; import { useTheme } from "@/contexts/ThemeContext"; import { clearTokenCookies } from "@/utils/cookieUtils"; import { clearStoredReturnUrl } from "@/utils/returnUrlUtils"; import { fetchProxySettings } from "@/utils/proxyUtils"; -import { MenuFoldOutlined, MenuUnfoldOutlined, MoonOutlined, SunOutlined } from "@ant-design/icons"; -import { Button, Switch, Tag } from "antd"; +import { DownOutlined, MenuFoldOutlined, MenuUnfoldOutlined } from "@ant-design/icons"; +import { Tag } from "antd"; import Link from "next/link"; import React, { useEffect, useState } from "react"; import { BlogDropdown } from "./Navbar/BlogDropdown/BlogDropdown"; import { CommunityEngagementButtons } from "./Navbar/CommunityEngagementButtons/CommunityEngagementButtons"; +import { NAV_PRODUCT_LINK_CLASS } from "./Navbar/navProductLinkClass"; +import { NotificationsBell } from "./Navbar/NotificationsBell/NotificationsBell"; import UserDropdown from "./Navbar/UserDropdown/UserDropdown"; import WorkerDropdown from "./Navbar/WorkerDropdown/WorkerDropdown"; @@ -30,18 +34,12 @@ interface NavbarProps { } const Navbar: React.FC = ({ - userID, - userEmail, - userRole, - premiumUser, proxySettings, setProxySettings, accessToken, isPublicPage = false, sidebarCollapsed = false, onToggleSidebar, - isDarkMode, - toggleDarkMode, }) => { const baseUrl = getProxyBaseUrl(); const [logoutUrl, setLogoutUrl] = useState(""); @@ -49,8 +47,10 @@ const Navbar: React.FC = ({ const { data: healthData } = useHealthReadiness(); const version = healthData?.litellm_version; const disableBouncingIcon = useDisableBouncingIcon(); + const hideCommunityLinks = useDisableShowPrompts(); + const { isControlPlane, selectedWorker } = useWorker(); + const showWorkerSwitch = isControlPlane && selectedWorker !== null; - // Simple logo URL: use custom logo if available, otherwise default const imageUrl = logoUrl || `${baseUrl}/get_image`; useEffect(() => { @@ -87,14 +87,14 @@ const Navbar: React.FC = ({ }; return ( -