diff --git a/ui/litellm-dashboard/src/app/layout.tsx b/ui/litellm-dashboard/src/app/layout.tsx index 95c485fe2f0..1233da9046f 100644 --- a/ui/litellm-dashboard/src/app/layout.tsx +++ b/ui/litellm-dashboard/src/app/layout.tsx @@ -2,6 +2,8 @@ import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; +import AntdGlobalProvider from "@/contexts/AntdGlobalProvider"; + const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { @@ -17,7 +19,9 @@ export default function RootLayout({ }>) { return ( - {children} + + {children} + ); } diff --git a/ui/litellm-dashboard/src/components/molecules/notifications_manager.tsx b/ui/litellm-dashboard/src/components/molecules/notifications_manager.tsx index 71b14bee885..59b048b412c 100644 --- a/ui/litellm-dashboard/src/components/molecules/notifications_manager.tsx +++ b/ui/litellm-dashboard/src/components/molecules/notifications_manager.tsx @@ -1,8 +1,18 @@ import React from "react"; -import { notification } from "antd"; +import { notification as staticNotification } from "antd"; +import type { NotificationInstance } from "antd/es/notification/interface"; import { parseErrorMessage } from "../shared/errorUtils"; import { ArgsProps } from "antd/es/notification"; +let notificationInstance: NotificationInstance | null = null; + +export const setNotificationInstance = (instance: NotificationInstance) => { + notificationInstance = instance; +}; + +// Helper to get the best available notification instance +const getNotification = () => notificationInstance || staticNotification; + type Placement = "top" | "topLeft" | "topRight" | "bottom" | "bottomLeft" | "bottomRight"; type NotificationConfig = { @@ -251,7 +261,7 @@ function looksErrorPayload(input: any, status?: number): boolean { const NotificationManager = { error(input: string | NotificationConfig) { const cfg = normalize(input, "Error"); - notification.error({ + getNotification().error({ ...COMMON_NOTIFICATION_PROPS, ...cfg, placement: cfg.placement ?? defaultPlacement(), @@ -261,7 +271,7 @@ const NotificationManager = { warning(input: string | NotificationConfig) { const cfg = normalize(input, "Warning"); - notification.warning({ + getNotification().warning({ ...COMMON_NOTIFICATION_PROPS, ...cfg, placement: cfg.placement ?? defaultPlacement(), @@ -271,7 +281,7 @@ const NotificationManager = { info(input: string | NotificationConfig) { const cfg = normalize(input, "Info"); - notification.info({ + getNotification().info({ ...COMMON_NOTIFICATION_PROPS, ...cfg, placement: cfg.placement ?? defaultPlacement(), @@ -281,7 +291,7 @@ const NotificationManager = { success(input: string | React.ReactNode | NotificationConfig) { if (React.isValidElement(input)) { - notification.success({ + getNotification().success({ ...COMMON_NOTIFICATION_PROPS, message: "Success", description: input, @@ -291,7 +301,7 @@ const NotificationManager = { return; } const cfg = normalize(input as string | NotificationConfig, "Success"); - notification.success({ + getNotification().success({ ...COMMON_NOTIFICATION_PROPS, ...cfg, placement: cfg.placement ?? defaultPlacement(), @@ -316,11 +326,11 @@ const NotificationManager = { title === "Content Blocked" || title === "Integration Error" ) { - notification.warning({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 7 }); + getNotification().warning({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 7 }); return; } if (title === "Server Error") { - notification.error({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 8 }); + getNotification().error({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 8 }); return; } if ( @@ -331,10 +341,10 @@ const NotificationManager = { title === "Error" || title === "Already Exists" ) { - notification.error({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 6 }); + getNotification().error({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 6 }); return; } - notification.info({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 4 }); + getNotification().info({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 4 }); return; } @@ -343,18 +353,18 @@ const NotificationManager = { const payload = { ...base, message: cls?.title ?? "Info" }; if (cls?.kind === "success") { - notification.success({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 3.5 }); + getNotification().success({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 3.5 }); return; } if (cls?.kind === "warning") { - notification.warning({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 6 }); + getNotification().warning({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 6 }); return; } - notification.info({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 4 }); + getNotification().info({ ...COMMON_NOTIFICATION_PROPS, ...payload, duration: extra?.duration ?? 4 }); }, clear() { - notification.destroy(); + getNotification().destroy(); }, }; diff --git a/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx b/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx new file mode 100644 index 00000000000..5b5c1036fa9 --- /dev/null +++ b/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx @@ -0,0 +1,24 @@ +"use client"; + +import React, { useEffect, useRef } from "react"; +import { notification } from "antd"; +import { setNotificationInstance } from "@/components/molecules/notifications_manager"; + +export default function AntdGlobalProvider({ children }: { children: React.ReactNode }) { + const [api, contextHolder] = notification.useNotification(); + const initialized = useRef(false); + + useEffect(() => { + if (!initialized.current) { + setNotificationInstance(api); + initialized.current = true; + } + }, [api]); + + return ( + <> + {contextHolder} + {children} + + ); +}