From 34d954f8cb9d02c5c4176368a9baf0dc0dbb5e8d Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Thu, 19 Mar 2026 23:29:38 -0700 Subject: [PATCH] [Fix] UI: Migrate AntD message API to use context-based MessageManager AntD v5 static message API doesn't render without an App wrapper or useMessage() context holder. Mirrors the existing notification pattern by adding message.useMessage() to AntdGlobalProvider and routing all calls through a new MessageManager module. Co-Authored-By: Claude Opus 4.6 --- .../AccessGroupCreateModal.tsx | 5 ++- .../AccessGroupEditModal.tsx | 5 ++- .../src/components/BulkEditUsers.tsx | 4 +- .../CloudZeroCreateModal.tsx | 9 +++-- .../CloudZeroIntegrationSettings.tsx | 15 ++++---- .../CloudZeroUpdateModal.tsx | 9 +++-- .../ProjectModals/CreateProjectModal.tsx | 7 ++-- .../ProjectModals/EditProjectModal.tsx | 7 ++-- .../SearchTools/SearchToolTester.tsx | 5 ++- .../RouterSettings/Fallbacks/AddFallbacks.tsx | 5 ++- .../Fallbacks/FallbackSelectionForm.tsx | 5 ++- .../src/components/agents/add_agent_form.tsx | 11 +++--- .../src/components/agents/agent_info.tsx | 9 +++-- .../src/components/chat/ChatPage.tsx | 5 ++- .../src/components/chat/MCPAppsPanel.tsx | 6 +-- .../src/components/chat/MCPConnectPicker.tsx | 7 ++-- .../src/components/chat/MCPCredentialsTab.tsx | 5 ++- .../claude_code_plugins/add_plugin_form.tsx | 17 +++++---- .../mcp_tools/ByokCredentialModal.tsx | 9 +++-- .../components/molecules/message_manager.tsx | 38 +++++++++++++++++++ .../src/components/networking.tsx | 4 +- .../organisms/create_key_button.tsx | 2 +- .../chat_ui/CodeInterpreterTool.tsx | 5 ++- .../src/components/policies/index.tsx | 29 +++++++------- .../policies/pipeline_flow_builder.tsx | 9 +++-- .../components/policies/policy_templates.tsx | 5 ++- .../shared/CreatedKeyDisplay.test.tsx | 14 +++---- .../components/shared/CreatedKeyDisplay.tsx | 5 ++- .../src/components/team/TeamInfo.tsx | 7 ++-- .../CreateVectorStore.tsx | 19 +++++----- .../DocumentsTable.tsx | 5 ++- .../VectorStoreTester.tsx | 5 ++- .../view_logs/LogDetailsDrawer/InputCard.tsx | 4 +- .../view_logs/LogDetailsDrawer/OutputCard.tsx | 5 ++- .../src/contexts/AntdGlobalProvider.tsx | 14 ++++--- 35 files changed, 190 insertions(+), 125 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/molecules/message_manager.tsx diff --git a/ui/litellm-dashboard/src/components/AccessGroups/AccessGroupsModal/AccessGroupCreateModal.tsx b/ui/litellm-dashboard/src/components/AccessGroups/AccessGroupsModal/AccessGroupCreateModal.tsx index b51fbd5bd9..5ec9f04f3b 100644 --- a/ui/litellm-dashboard/src/components/AccessGroups/AccessGroupsModal/AccessGroupCreateModal.tsx +++ b/ui/litellm-dashboard/src/components/AccessGroups/AccessGroupsModal/AccessGroupCreateModal.tsx @@ -1,5 +1,6 @@ import React from "react"; -import { Modal, Form, message } from "antd"; +import { Modal, Form } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; import { AccessGroupBaseForm, AccessGroupFormValues, @@ -37,7 +38,7 @@ export function AccessGroupCreateModal({ createMutation.mutate(params, { onSuccess: () => { - message.success("Access group created successfully"); + MessageManager.success("Access group created successfully"); form.resetFields(); onSuccess?.(); onCancel(); diff --git a/ui/litellm-dashboard/src/components/AccessGroups/AccessGroupsModal/AccessGroupEditModal.tsx b/ui/litellm-dashboard/src/components/AccessGroups/AccessGroupsModal/AccessGroupEditModal.tsx index 919295b6f7..f05edb3fe9 100644 --- a/ui/litellm-dashboard/src/components/AccessGroups/AccessGroupsModal/AccessGroupEditModal.tsx +++ b/ui/litellm-dashboard/src/components/AccessGroups/AccessGroupsModal/AccessGroupEditModal.tsx @@ -1,5 +1,6 @@ import React, { useEffect } from "react"; -import { Modal, Form, message } from "antd"; +import { Modal, Form } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; import { AccessGroupBaseForm, AccessGroupFormValues, @@ -55,7 +56,7 @@ export function AccessGroupEditModal({ { accessGroupId: accessGroup.access_group_id, params }, { onSuccess: () => { - message.success("Access group updated successfully"); + MessageManager.success("Access group updated successfully"); onSuccess?.(); onCancel(); }, diff --git a/ui/litellm-dashboard/src/components/BulkEditUsers.tsx b/ui/litellm-dashboard/src/components/BulkEditUsers.tsx index 2f3e57ff2a..2ca69065bc 100644 --- a/ui/litellm-dashboard/src/components/BulkEditUsers.tsx +++ b/ui/litellm-dashboard/src/components/BulkEditUsers.tsx @@ -3,7 +3,6 @@ import { Modal, Typography, Divider, - message, Table, Select, InputNumber, @@ -14,6 +13,7 @@ import { import { userBulkUpdateUserCall, teamBulkMemberAddCall, Member } from "./networking"; import { UserEditView } from "./user_edit_view"; import NotificationsManager from "./molecules/notifications_manager"; +import MessageManager from "@/components/molecules/message_manager"; const { Text, Title } = Typography; @@ -188,7 +188,7 @@ const BulkEditUserModal: React.FC = ({ } if (failedTeams.length > 0) { - message.warning(`Failed to add users to ${failedTeams.length} team(s)`); + MessageManager.warning(`Failed to add users to ${failedTeams.length} team(s)`); } } diff --git a/ui/litellm-dashboard/src/components/CloudZeroCostTracking/CloudZeroCreateModal.tsx b/ui/litellm-dashboard/src/components/CloudZeroCostTracking/CloudZeroCreateModal.tsx index feb00fc040..5f460cb7cb 100644 --- a/ui/litellm-dashboard/src/components/CloudZeroCostTracking/CloudZeroCreateModal.tsx +++ b/ui/litellm-dashboard/src/components/CloudZeroCostTracking/CloudZeroCreateModal.tsx @@ -1,4 +1,5 @@ -import { Form, Modal, Input, message } from "antd"; +import { Form, Modal, Input } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; import { useEffect } from "react"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { useCloudZeroCreate } from "@/app/(dashboard)/hooks/cloudzero/useCloudZeroCreate"; @@ -31,7 +32,7 @@ export default function CloudZeroCreationModal({ open, onOk, onCancel }: CloudZe }, { onSuccess: () => { - message.success("CloudZero integration created successfully"); + MessageManager.success("CloudZero integration created successfully"); form.resetFields(); onOk(); }, @@ -39,7 +40,7 @@ export default function CloudZeroCreationModal({ open, onOk, onCancel }: CloudZe if (error?.errorFields) { return; } - message.error(error?.message || "Failed to create CloudZero integration"); + MessageManager.error(error?.message || "Failed to create CloudZero integration"); }, }, ); @@ -47,7 +48,7 @@ export default function CloudZeroCreationModal({ open, onOk, onCancel }: CloudZe if (error?.errorFields) { return; } - message.error(error?.message || "Failed to create CloudZero integration"); + MessageManager.error(error?.message || "Failed to create CloudZero integration"); } }; diff --git a/ui/litellm-dashboard/src/components/CloudZeroCostTracking/CloudZeroIntegrationSettings.tsx b/ui/litellm-dashboard/src/components/CloudZeroCostTracking/CloudZeroIntegrationSettings.tsx index c161d241f7..62709dac52 100644 --- a/ui/litellm-dashboard/src/components/CloudZeroCostTracking/CloudZeroIntegrationSettings.tsx +++ b/ui/litellm-dashboard/src/components/CloudZeroCostTracking/CloudZeroIntegrationSettings.tsx @@ -3,7 +3,8 @@ import { useCloudZeroExport } from "@/app/(dashboard)/hooks/cloudzero/useCloudZe import { useCloudZeroDeleteSettings } from "@/app/(dashboard)/hooks/cloudzero/useCloudZeroSettings"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import DeleteResourceModal from "@/components/common_components/DeleteResourceModal"; -import { Alert, Button, Card, Descriptions, Divider, message, Popconfirm, Tag } from "antd"; +import { Alert, Button, Card, Descriptions, Divider, Popconfirm, Tag } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; import { CheckCircle, Edit, Play, Trash2, Upload } from "lucide-react"; import { useState } from "react"; import CloudZeroUpdateModal from "./CloudZeroUpdateModal"; @@ -30,10 +31,10 @@ export function CloudZeroIntegrationSettings({ settings, onSettingsUpdated }: Cl { limit: 10 }, { onSuccess: (data) => { - message.success("Dry run completed successfully"); + MessageManager.success("Dry run completed successfully"); }, onError: (error) => { - message.error(error?.message || "Failed to perform dry run"); + MessageManager.error(error?.message || "Failed to perform dry run"); }, }, ); @@ -48,10 +49,10 @@ export function CloudZeroIntegrationSettings({ settings, onSettingsUpdated }: Cl { operation: "replace_hourly" }, { onSuccess: () => { - message.success("Data successfully exported to CloudZero"); + MessageManager.success("Data successfully exported to CloudZero"); }, onError: (error) => { - message.error(error?.message || "Failed to export data"); + MessageManager.error(error?.message || "Failed to export data"); }, }, ); @@ -79,12 +80,12 @@ export function CloudZeroIntegrationSettings({ settings, onSettingsUpdated }: Cl deleteMutation.mutate(undefined, { onSuccess: () => { - message.success("CloudZero integration deleted successfully"); + MessageManager.success("CloudZero integration deleted successfully"); setIsDeleteModalOpen(false); onSettingsUpdated(); }, onError: (error) => { - message.error(error?.message || "Failed to delete CloudZero integration"); + MessageManager.error(error?.message || "Failed to delete CloudZero integration"); }, }); }; diff --git a/ui/litellm-dashboard/src/components/CloudZeroCostTracking/CloudZeroUpdateModal.tsx b/ui/litellm-dashboard/src/components/CloudZeroCostTracking/CloudZeroUpdateModal.tsx index 0aca6857b8..a04007897c 100644 --- a/ui/litellm-dashboard/src/components/CloudZeroCostTracking/CloudZeroUpdateModal.tsx +++ b/ui/litellm-dashboard/src/components/CloudZeroCostTracking/CloudZeroUpdateModal.tsx @@ -1,6 +1,7 @@ import { useCloudZeroUpdateSettings } from "@/app/(dashboard)/hooks/cloudzero/useCloudZeroSettings"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; -import { Form, Input, message, Modal } from "antd"; +import { Form, Input, Modal } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; import { useEffect } from "react"; import { CloudZeroSettings } from "./types"; @@ -39,7 +40,7 @@ export default function CloudZeroUpdateModal({ open, onOk, onCancel, settings }: }, { onSuccess: () => { - message.success("CloudZero integration updated successfully"); + MessageManager.success("CloudZero integration updated successfully"); form.resetFields(); onOk(); }, @@ -47,7 +48,7 @@ export default function CloudZeroUpdateModal({ open, onOk, onCancel, settings }: if (error?.errorFields) { return; } - message.error(error?.message || "Failed to update CloudZero integration"); + MessageManager.error(error?.message || "Failed to update CloudZero integration"); }, }, ); @@ -55,7 +56,7 @@ export default function CloudZeroUpdateModal({ open, onOk, onCancel, settings }: if (error?.errorFields) { return; } - message.error(error?.message || "Failed to update CloudZero integration"); + MessageManager.error(error?.message || "Failed to update CloudZero integration"); } }; diff --git a/ui/litellm-dashboard/src/components/Projects/ProjectModals/CreateProjectModal.tsx b/ui/litellm-dashboard/src/components/Projects/ProjectModals/CreateProjectModal.tsx index e490f89303..bbf56e4930 100644 --- a/ui/litellm-dashboard/src/components/Projects/ProjectModals/CreateProjectModal.tsx +++ b/ui/litellm-dashboard/src/components/Projects/ProjectModals/CreateProjectModal.tsx @@ -1,5 +1,6 @@ -import { Modal, Form, Button, Typography, message } from "antd"; +import { Modal, Form, Button, Typography } from "antd"; import { FolderAddOutlined } from "@ant-design/icons"; +import MessageManager from "@/components/molecules/message_manager"; import { useCreateProject, ProjectCreateParams, @@ -32,12 +33,12 @@ export function CreateProjectModal({ createMutation.mutate(params, { onSuccess: () => { - message.success("Project created successfully"); + MessageManager.success("Project created successfully"); form.resetFields(); onClose(); }, onError: (error) => { - message.error(error.message || "Failed to create project"); + MessageManager.error(error.message || "Failed to create project"); }, }); } catch (error) { diff --git a/ui/litellm-dashboard/src/components/Projects/ProjectModals/EditProjectModal.tsx b/ui/litellm-dashboard/src/components/Projects/ProjectModals/EditProjectModal.tsx index 75f56b1373..dc3b43ef73 100644 --- a/ui/litellm-dashboard/src/components/Projects/ProjectModals/EditProjectModal.tsx +++ b/ui/litellm-dashboard/src/components/Projects/ProjectModals/EditProjectModal.tsx @@ -1,6 +1,7 @@ import { useEffect } from "react"; -import { Modal, Form, Button, Typography, message } from "antd"; +import { Modal, Form, Button, Typography } from "antd"; import { SaveOutlined } from "@ant-design/icons"; +import MessageManager from "@/components/molecules/message_manager"; import { ProjectResponse } from "@/app/(dashboard)/hooks/projects/useProjects"; import { useUpdateProject, @@ -80,12 +81,12 @@ export function EditProjectModal({ { projectId: project.project_id, params }, { onSuccess: () => { - message.success("Project updated successfully"); + MessageManager.success("Project updated successfully"); onSuccess?.(); onClose(); }, onError: (error) => { - message.error(error.message || "Failed to update project"); + MessageManager.error(error.message || "Failed to update project"); }, }, ); diff --git a/ui/litellm-dashboard/src/components/SearchTools/SearchToolTester.tsx b/ui/litellm-dashboard/src/components/SearchTools/SearchToolTester.tsx index 28b34b0608..d1cb5077b1 100644 --- a/ui/litellm-dashboard/src/components/SearchTools/SearchToolTester.tsx +++ b/ui/litellm-dashboard/src/components/SearchTools/SearchToolTester.tsx @@ -1,5 +1,6 @@ import React, { useState } from "react"; -import { Button, Input, Typography, Spin, message } from "antd"; +import { Button, Input, Typography, Spin } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; import { SearchOutlined, LoadingOutlined } from "@ant-design/icons"; import { searchToolQueryCall } from "../networking"; import NotificationsManager from "../molecules/notifications_manager"; @@ -39,7 +40,7 @@ export const SearchToolTester: React.FC = ({ searchToolNa const handleSearch = async () => { if (!query.trim()) { - message.warning("Please enter a search query"); + MessageManager.warning("Please enter a search query"); return; } diff --git a/ui/litellm-dashboard/src/components/Settings/RouterSettings/Fallbacks/AddFallbacks.tsx b/ui/litellm-dashboard/src/components/Settings/RouterSettings/Fallbacks/AddFallbacks.tsx index 34f059516a..e55089d27d 100644 --- a/ui/litellm-dashboard/src/components/Settings/RouterSettings/Fallbacks/AddFallbacks.tsx +++ b/ui/litellm-dashboard/src/components/Settings/RouterSettings/Fallbacks/AddFallbacks.tsx @@ -5,8 +5,9 @@ */ import { Button as TremorButton } from "@tremor/react"; -import { Button, message } from "antd"; +import { Button } from "antd"; import React, { useEffect, useState } from "react"; +import MessageManager from "@/components/molecules/message_manager"; import NotificationManager from "../../../molecules/notifications_manager"; import { fetchAvailableModels, ModelGroup } from "../../../playground/llm_calls/fetch_models"; import { AddFallbacksModal } from "./AddFallbacksModal"; @@ -90,7 +91,7 @@ export default function AddFallbacks({ (g) => !g.primaryModel || g.fallbackModels.length === 0, ); if (invalidGroups.length > 0) { - message.error( + MessageManager.error( `Please complete configuration for all groups. ${invalidGroups.length} group(s) incomplete.`, ); return; diff --git a/ui/litellm-dashboard/src/components/Settings/RouterSettings/Fallbacks/FallbackSelectionForm.tsx b/ui/litellm-dashboard/src/components/Settings/RouterSettings/Fallbacks/FallbackSelectionForm.tsx index 08b031c683..0482161bc6 100644 --- a/ui/litellm-dashboard/src/components/Settings/RouterSettings/Fallbacks/FallbackSelectionForm.tsx +++ b/ui/litellm-dashboard/src/components/Settings/RouterSettings/Fallbacks/FallbackSelectionForm.tsx @@ -5,9 +5,10 @@ */ import { Button } from "@tremor/react"; -import { message, Tabs } from "antd"; +import { Tabs } from "antd"; import { Plus } from "lucide-react"; import React, { useEffect, useState } from "react"; +import MessageManager from "@/components/molecules/message_manager"; import { FallbackGroup, FallbackGroupConfig } from "./FallbackGroupConfig"; interface FallbackSelectionFormProps { @@ -60,7 +61,7 @@ export function FallbackSelectionForm({ const handleRemoveGroup = (targetId: string) => { if (groups.length === 1) { - message.warning("At least one group is required"); + MessageManager.warning("At least one group is required"); return; } const newGroups = groups.filter((g) => g.id !== targetId); diff --git a/ui/litellm-dashboard/src/components/agents/add_agent_form.tsx b/ui/litellm-dashboard/src/components/agents/add_agent_form.tsx index c5518596b8..b6d96f0445 100644 --- a/ui/litellm-dashboard/src/components/agents/add_agent_form.tsx +++ b/ui/litellm-dashboard/src/components/agents/add_agent_form.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from "react"; -import { Modal, Form, message, Select, Input, Steps, Radio, Tag, Divider, Switch, InputNumber, Collapse } from "antd"; +import { Modal, Form, Select, Input, Steps, Radio, Tag, Divider, Switch, InputNumber, Collapse } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; import { Button } from "@tremor/react"; import { CheckCircleFilled, KeyOutlined, RobotOutlined, AppstoreOutlined, InfoCircleOutlined } from "@ant-design/icons"; import CreatedKeyDisplay from "../shared/CreatedKeyDisplay"; @@ -216,7 +217,7 @@ const AddAgentForm: React.FC = ({ const handleCreateAgent = async () => { if (!accessToken) { - message.error("No access token available"); + MessageManager.error("No access token available"); return; } @@ -226,7 +227,7 @@ const AddAgentForm: React.FC = ({ const values = { ...form.getFieldsValue(true) }; const agentData = buildAgentData(values); if (!agentData) { - message.error("Failed to build agent data"); + MessageManager.error("Failed to build agent data"); setIsSubmitting(false); return; } @@ -301,7 +302,7 @@ const AddAgentForm: React.FC = ({ setCreatedKeyValue(keyResponse.key || null); } else if (keyAssignOption === "existing_key") { if (!selectedExistingKey) { - message.error("Please select an existing key to assign"); + MessageManager.error("Please select an existing key to assign"); setIsSubmitting(false); return; } @@ -318,7 +319,7 @@ const AddAgentForm: React.FC = ({ } catch (error) { console.error("Error creating agent:", error); const errorMessage = error instanceof Error ? error.message : String(error); - message.error(errorMessage ? `Failed to create agent: ${errorMessage}` : "Failed to create agent"); + MessageManager.error(errorMessage ? `Failed to create agent: ${errorMessage}` : "Failed to create agent"); } finally { setIsSubmitting(false); } diff --git a/ui/litellm-dashboard/src/components/agents/agent_info.tsx b/ui/litellm-dashboard/src/components/agents/agent_info.tsx index b41e318a76..d543be8356 100644 --- a/ui/litellm-dashboard/src/components/agents/agent_info.tsx +++ b/ui/litellm-dashboard/src/components/agents/agent_info.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from "react"; import { Card, Title, Text, Button as TremorButton, Tab, TabGroup, TabList, TabPanel, TabPanels} from "@tremor/react"; -import { Form, Input, InputNumber, Button as AntButton, message, Spin, Descriptions, Divider } from "antd"; +import { Form, Input, InputNumber, Button as AntButton, Spin, Descriptions, Divider } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; import { ArrowLeftIcon } from "@heroicons/react/outline"; import { getAgentInfo, patchAgentCall, getAgentCreateMetadata, AgentCreateInfo } from "../networking"; import { Agent } from "./types"; @@ -72,7 +73,7 @@ const AgentInfoView: React.FC = ({ } } catch (error) { console.error("Error fetching agent info:", error); - message.error("Failed to load agent information"); + MessageManager.error("Failed to load agent information"); } finally { setIsLoading(false); } @@ -111,12 +112,12 @@ const AgentInfoView: React.FC = ({ } await patchAgentCall(accessToken, agentId, updateData); - message.success("Agent updated successfully"); + MessageManager.success("Agent updated successfully"); setIsEditing(false); fetchAgentInfo(); } catch (error) { console.error("Error updating agent:", error); - message.error("Failed to update agent"); + MessageManager.error("Failed to update agent"); } finally { setIsSaving(false); } diff --git a/ui/litellm-dashboard/src/components/chat/ChatPage.tsx b/ui/litellm-dashboard/src/components/chat/ChatPage.tsx index ccf39d2147..b547f74917 100644 --- a/ui/litellm-dashboard/src/components/chat/ChatPage.tsx +++ b/ui/litellm-dashboard/src/components/chat/ChatPage.tsx @@ -1,7 +1,8 @@ "use client"; import React, { useCallback, useEffect, useRef, useState, useLayoutEffect } from "react"; -import { Tooltip, Skeleton, Popover, message } from "antd"; +import { Tooltip, Skeleton, Popover } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; import { SettingOutlined, PlusOutlined, @@ -212,7 +213,7 @@ const ChatPage: React.FC = ({ accessToken, userRole, userId, user localStorage.setItem(LOCALSTORAGE_MODEL_KEY, JSON.stringify([names[0]])); } }) - .catch(() => message.error("Could not load models")) + .catch(() => MessageManager.error("Could not load models")) .finally(() => setIsLoadingModels(false)); }, [accessToken]); diff --git a/ui/litellm-dashboard/src/components/chat/MCPAppsPanel.tsx b/ui/litellm-dashboard/src/components/chat/MCPAppsPanel.tsx index b805cab71e..d25db73ae4 100644 --- a/ui/litellm-dashboard/src/components/chat/MCPAppsPanel.tsx +++ b/ui/litellm-dashboard/src/components/chat/MCPAppsPanel.tsx @@ -5,7 +5,7 @@ import { Spin, Input, Button, Skeleton } from "antd"; import { SearchOutlined, ArrowLeftOutlined, RightOutlined, ToolOutlined, CheckCircleOutlined } from "@ant-design/icons"; import { deleteMCPOAuthUserCredential, fetchMCPServers, getMCPOAuthUserCredentialStatus, listMCPTools } from "../networking"; import { AUTH_TYPE, MCPServer, MCPTool, handleTransport } from "../mcp_tools/types"; -import { message } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; import { useUserMcpOAuthFlow } from "@/hooks/useUserMcpOAuthFlow"; // ── OAuth2 connect button ───────────────────────────────────────────────────── @@ -198,7 +198,7 @@ const MCPAppsPanel: React.FC = ({ accessToken, selectedServers, onChange const idToFetch = serverId ?? serverName; const result = await listMCPTools(accessToken, idToFetch); if (result?.error) { - message.warning(`Could not load tools for ${serverName}`); + MessageManager.warning(`Could not load tools for ${serverName}`); return; } // Use the ref so we read the most up-to-date list; guard against duplicates @@ -207,7 +207,7 @@ const MCPAppsPanel: React.FC = ({ accessToken, selectedServers, onChange onChange([...selectedServersRef.current, serverName]); } } catch { - message.warning(`Could not load tools for ${serverName}`); + MessageManager.warning(`Could not load tools for ${serverName}`); } finally { setTogglingOn((prev) => { const next = new Set(prev); diff --git a/ui/litellm-dashboard/src/components/chat/MCPConnectPicker.tsx b/ui/litellm-dashboard/src/components/chat/MCPConnectPicker.tsx index a52ce18156..6ef3aecc46 100644 --- a/ui/litellm-dashboard/src/components/chat/MCPConnectPicker.tsx +++ b/ui/litellm-dashboard/src/components/chat/MCPConnectPicker.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from "react"; -import { Switch, Spin, message } from "antd"; +import { Switch, Spin } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; import { fetchMCPServers, listMCPTools } from "../networking"; import { MCPServer } from "../mcp_tools/types"; @@ -57,7 +58,7 @@ const MCPConnectPicker: React.FC = ({ accessToken, selectedServers, onCha const result = await listMCPTools(accessToken, serverName); // listMCPTools never throws; it returns { tools, error, message } on failure if (result?.error) { - message.warning( + MessageManager.warning( `Could not load tools for ${serverName} — it will be excluded from this message.` ); // Do not add to selectedServers @@ -65,7 +66,7 @@ const MCPConnectPicker: React.FC = ({ accessToken, selectedServers, onCha } onChange([...selectedServers, serverName]); } catch { - message.warning( + MessageManager.warning( `Could not load tools for ${serverName} — it will be excluded from this message.` ); // Do not add to selectedServers diff --git a/ui/litellm-dashboard/src/components/chat/MCPCredentialsTab.tsx b/ui/litellm-dashboard/src/components/chat/MCPCredentialsTab.tsx index 363ec8c0e4..e2d879b22a 100644 --- a/ui/litellm-dashboard/src/components/chat/MCPCredentialsTab.tsx +++ b/ui/litellm-dashboard/src/components/chat/MCPCredentialsTab.tsx @@ -8,7 +8,8 @@ */ import React, { useCallback, useEffect, useState } from "react"; -import { Spin, message } from "antd"; +import { Spin } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; import { DeleteOutlined, LinkOutlined } from "@ant-design/icons"; import { Badge, Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from "@tremor/react"; import { @@ -77,7 +78,7 @@ const MCPCredentialsTab: React.FC = ({ accessToken }) => { await deleteMCPOAuthUserCredential(accessToken, serverId); setCredentials((prev) => prev.filter((c) => c.server_id !== serverId)); } catch { - message.error("Failed to revoke connection. Please try again."); + MessageManager.error("Failed to revoke connection. Please try again."); } finally { setRevoking((prev) => { const n = new Set(prev); n.delete(serverId); return n; }); } diff --git a/ui/litellm-dashboard/src/components/claude_code_plugins/add_plugin_form.tsx b/ui/litellm-dashboard/src/components/claude_code_plugins/add_plugin_form.tsx index 217851f128..d5e417a9a8 100644 --- a/ui/litellm-dashboard/src/components/claude_code_plugins/add_plugin_form.tsx +++ b/ui/litellm-dashboard/src/components/claude_code_plugins/add_plugin_form.tsx @@ -1,5 +1,6 @@ import React, { useState } from "react"; -import { Modal, Form, Input, Select, message } from "antd"; +import { Modal, Form, Input, Select } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; import { Button } from "@tremor/react"; import { registerClaudeCodePlugin } from "../networking"; import { @@ -43,13 +44,13 @@ const AddPluginForm: React.FC = ({ const handleSubmit = async (values: any) => { if (!accessToken) { - message.error("No access token available"); + MessageManager.error("No access token available"); return; } // Validate plugin name if (!validatePluginName(values.name)) { - message.error( + MessageManager.error( "Plugin name must be kebab-case (lowercase letters, numbers, and hyphens only)" ); return; @@ -57,7 +58,7 @@ const AddPluginForm: React.FC = ({ // Validate semantic version if provided if (values.version && !isValidSemanticVersion(values.version)) { - message.error( + MessageManager.error( "Version must be in semantic versioning format (e.g., 1.0.0)" ); return; @@ -65,13 +66,13 @@ const AddPluginForm: React.FC = ({ // Validate email if provided if (values.authorEmail && !isValidEmail(values.authorEmail)) { - message.error("Invalid email format"); + MessageManager.error("Invalid email format"); return; } // Validate homepage URL if provided if (values.homepage && !isValidUrl(values.homepage)) { - message.error("Invalid homepage URL format"); + MessageManager.error("Invalid homepage URL format"); return; } @@ -119,14 +120,14 @@ const AddPluginForm: React.FC = ({ } await registerClaudeCodePlugin(accessToken, pluginData); - message.success("Plugin registered successfully"); + MessageManager.success("Plugin registered successfully"); form.resetFields(); setSourceType("github"); onSuccess(); onClose(); } catch (error) { console.error("Error registering plugin:", error); - message.error("Failed to register plugin"); + MessageManager.error("Failed to register plugin"); } finally { setIsSubmitting(false); } diff --git a/ui/litellm-dashboard/src/components/mcp_tools/ByokCredentialModal.tsx b/ui/litellm-dashboard/src/components/mcp_tools/ByokCredentialModal.tsx index 473918c126..58c2a965e0 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/ByokCredentialModal.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/ByokCredentialModal.tsx @@ -1,7 +1,8 @@ "use client"; import React, { useState } from "react"; -import { Modal, Input, Switch, message } from "antd"; +import { Modal, Input, Switch } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; import { KeyOutlined, LockOutlined, @@ -46,7 +47,7 @@ export const ByokCredentialModal: React.FC = ({ const handleAuthorize = async () => { if (!apiKey.trim()) { - message.error("Please enter your API key"); + MessageManager.error("Please enter your API key"); return; } setLoading(true); @@ -63,11 +64,11 @@ export const ByokCredentialModal: React.FC = ({ const err = await response.json(); throw new Error(err?.detail?.error || "Failed to save credential"); } - message.success(`Connected to ${serverDisplayName}`); + MessageManager.success(`Connected to ${serverDisplayName}`); onSuccess(server.server_id); handleClose(); } catch (e: any) { - message.error(e.message || "Failed to connect"); + MessageManager.error(e.message || "Failed to connect"); } finally { setLoading(false); } diff --git a/ui/litellm-dashboard/src/components/molecules/message_manager.tsx b/ui/litellm-dashboard/src/components/molecules/message_manager.tsx new file mode 100644 index 0000000000..e4c1552d5e --- /dev/null +++ b/ui/litellm-dashboard/src/components/molecules/message_manager.tsx @@ -0,0 +1,38 @@ +import { message as staticMessage } from "antd"; +import type { MessageInstance } from "antd/es/message/interface"; + +let messageInstance: MessageInstance | null = null; + +export const setMessageInstance = (instance: MessageInstance) => { + messageInstance = instance; +}; + +const getMessageApi = () => messageInstance || staticMessage; + +const MessageManager = { + success(content: string, duration?: number) { + getMessageApi().success(content, duration); + }, + + error(content: string, duration?: number) { + getMessageApi().error(content, duration); + }, + + warning(content: string, duration?: number) { + getMessageApi().warning(content, duration); + }, + + info(content: string, duration?: number) { + getMessageApi().info(content, duration); + }, + + loading(content: string, duration?: number) { + return getMessageApi().loading(content, duration); + }, + + destroy() { + getMessageApi().destroy(); + }, +}; + +export default MessageManager; diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index bb9a0c1e01..956968dfee 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -68,7 +68,7 @@ export const getInProductNudgesCall = async (accessToken: string) => { /** * Helper file for calls being made to proxy */ -import { message } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; import { clearTokenCookies } from "@/utils/cookieUtils"; import { TagNewRequest, TagUpdateRequest, TagListResponse, TagInfoResponse } from "./tag_management/types"; import { Team } from "./key_team_helpers/key_list"; @@ -555,7 +555,7 @@ export const modelCreateCall = async (accessToken: string, formValues: Model) => console.log("API Response:", data); // Close any existing messages before showing new ones - message.destroy(); + MessageManager.destroy(); // Sequential success messages NotificationsManager.success(`Model ${formValues.model_name} created successfully`); diff --git a/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx b/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx index d0a7a909d6..df5b3a4b32 100644 --- a/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx +++ b/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx @@ -8,7 +8,7 @@ import { formatNumberWithCommas } from "@/utils/dataUtils"; import { InfoCircleOutlined } from "@ant-design/icons"; import { useQueryClient } from "@tanstack/react-query"; import { Accordion, AccordionBody, AccordionHeader, Button, Col, Grid, Text, TextInput, Title } from "@tremor/react"; -import { Button as Button2, Form, Input, message, Modal, Radio, Select, Switch, Tag, Tooltip } from "antd"; +import { Button as Button2, Form, Input, Modal, Radio, Select, Switch, Tag, Tooltip } from "antd"; import debounce from "lodash/debounce"; import React, { useCallback, useEffect, useState } from "react"; import { rolesWithWriteAccess } from "../../utils/roles"; diff --git a/ui/litellm-dashboard/src/components/playground/chat_ui/CodeInterpreterTool.tsx b/ui/litellm-dashboard/src/components/playground/chat_ui/CodeInterpreterTool.tsx index 04711ede9f..dd37769556 100644 --- a/ui/litellm-dashboard/src/components/playground/chat_ui/CodeInterpreterTool.tsx +++ b/ui/litellm-dashboard/src/components/playground/chat_ui/CodeInterpreterTool.tsx @@ -1,5 +1,6 @@ import React from "react"; -import { Switch, Tooltip, message } from "antd"; +import { Switch, Tooltip } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; import { CodeOutlined, InfoCircleOutlined, ExclamationCircleOutlined } from "@ant-design/icons"; import { Text } from "@tremor/react"; @@ -38,7 +39,7 @@ const CodeInterpreterTool: React.FC = ({ const handleToggle = (checked: boolean) => { if (checked && !isOpenAI) { - message.warning("Code Interpreter is only available for OpenAI models"); + MessageManager.warning("Code Interpreter is only available for OpenAI models"); return; } onEnabledChange(checked); diff --git a/ui/litellm-dashboard/src/components/policies/index.tsx b/ui/litellm-dashboard/src/components/policies/index.tsx index bd77f3ab9b..f47b8d78b9 100644 --- a/ui/litellm-dashboard/src/components/policies/index.tsx +++ b/ui/litellm-dashboard/src/components/policies/index.tsx @@ -1,6 +1,7 @@ import React, { useState, useEffect, useCallback } from "react"; import { Button, TabGroup, TabList, Tab, TabPanels, TabPanel } from "@tremor/react"; -import { Modal, message, Alert } from "antd"; +import { Modal, Alert } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; import { ExclamationCircleOutlined, InfoCircleOutlined } from "@ant-design/icons"; import { isAdminRole } from "@/utils/roles"; import PolicyTable from "./policy_table"; @@ -80,7 +81,7 @@ const PoliciesPanel: React.FC = ({ setPoliciesList(response.policies || []); } catch (error) { console.error("Error fetching policies:", error); - message.error("Failed to fetch policies"); + MessageManager.error("Failed to fetch policies"); } finally { setIsLoading(false); } @@ -95,7 +96,7 @@ const PoliciesPanel: React.FC = ({ setAttachmentsList(response.attachments || []); } catch (error) { console.error("Error fetching attachments:", error); - message.error("Failed to fetch attachments"); + MessageManager.error("Failed to fetch attachments"); } finally { setIsAttachmentsLoading(false); } @@ -148,11 +149,11 @@ const PoliciesPanel: React.FC = ({ setIsDeleting(true); try { await deletePolicyCall(accessToken, policyToDelete.policy_id); - message.success(`Policy "${policyToDelete.policy_name}" deleted successfully`); + MessageManager.success(`Policy "${policyToDelete.policy_name}" deleted successfully`); await fetchPolicies(); } catch (error) { console.error("Error deleting policy:", error); - message.error("Failed to delete policy"); + MessageManager.error("Failed to delete policy"); } finally { setIsDeleting(false); setIsDeleteModalOpen(false); @@ -177,11 +178,11 @@ const PoliciesPanel: React.FC = ({ if (!accessToken) return; try { await deletePolicyAttachmentCall(accessToken, attachmentId); - message.success("Attachment deleted successfully"); + MessageManager.success("Attachment deleted successfully"); fetchAttachments(); } catch (error) { console.error("Error deleting attachment:", error); - message.error("Failed to delete attachment"); + MessageManager.error("Failed to delete attachment"); } }, }); @@ -193,7 +194,7 @@ const PoliciesPanel: React.FC = ({ const handleUseTemplate = async (template: any) => { if (!accessToken) { - message.error("Authentication required"); + MessageManager.error("Authentication required"); return; } @@ -221,7 +222,7 @@ const PoliciesPanel: React.FC = ({ setIsGuardrailSelectionModalOpen(true); } catch (error) { console.error("Error fetching guardrails:", error); - message.error("Failed to load guardrails. Please try again."); + MessageManager.error("Failed to load guardrails. Please try again."); } }; @@ -271,7 +272,7 @@ const PoliciesPanel: React.FC = ({ await proceedWithTemplate(enrichedTemplate); } catch (error) { console.error("Error enriching template:", error); - message.error("Failed to configure template. Please try again."); + MessageManager.error("Failed to configure template. Please try again."); setIsEnrichingTemplate(false); } }; @@ -318,15 +319,15 @@ const PoliciesPanel: React.FC = ({ // Show success message if (createdGuardrails.length > 0) { - message.success( + MessageManager.success( `Created ${createdGuardrails.length} guardrail${createdGuardrails.length > 1 ? "s" : ""}! Complete the policy form to save.` ); } else { - message.success("Template ready! Complete the policy form to save."); + MessageManager.success("Template ready! Complete the policy form to save."); } if (failedGuardrails.length > 0) { - message.warning( + MessageManager.warning( `Failed to create ${failedGuardrails.length} guardrail(s): ${failedGuardrails.join(", ")}. You may need to create them manually.` ); } @@ -348,7 +349,7 @@ const PoliciesPanel: React.FC = ({ setTemplateQueue([]); setTemplateQueueProgress(null); console.error("Error creating guardrails:", error); - message.error("Failed to create guardrails. Please try again."); + MessageManager.error("Failed to create guardrails. Please try again."); } }; diff --git a/ui/litellm-dashboard/src/components/policies/pipeline_flow_builder.tsx b/ui/litellm-dashboard/src/components/policies/pipeline_flow_builder.tsx index 89665774ee..b1768d5b81 100644 --- a/ui/litellm-dashboard/src/components/policies/pipeline_flow_builder.tsx +++ b/ui/litellm-dashboard/src/components/policies/pipeline_flow_builder.tsx @@ -1,5 +1,6 @@ import React, { useState } from "react"; -import { Select, Typography, message, Spin } from "antd"; +import { Select, Typography, Spin } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; import { Button, TextInput } from "@tremor/react"; import { ArrowLeftIcon, PlusIcon } from "@heroicons/react/outline"; import { DotsVerticalIcon } from "@heroicons/react/solid"; @@ -1385,17 +1386,17 @@ export const FlowBuilderPage: React.FC = ({ const handleSave = async () => { if (!policyName.trim()) { - message.error("Please enter a policy name"); + MessageManager.error("Please enter a policy name"); return; } if (!accessToken) { - message.error("No access token available"); + MessageManager.error("No access token available"); return; } const emptySteps = pipeline.steps.filter((s) => !s.guardrail); if (emptySteps.length > 0) { - message.error("Please select a guardrail for all steps"); + MessageManager.error("Please select a guardrail for all steps"); return; } diff --git a/ui/litellm-dashboard/src/components/policies/policy_templates.tsx b/ui/litellm-dashboard/src/components/policies/policy_templates.tsx index 98ba0acb6a..c4ea22651c 100644 --- a/ui/litellm-dashboard/src/components/policies/policy_templates.tsx +++ b/ui/litellm-dashboard/src/components/policies/policy_templates.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect, useMemo } from "react"; -import { Card, Button, Spin, message, Checkbox } from "antd"; +import { Card, Button, Spin, Checkbox } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; import { ShieldCheckIcon, ShieldExclamationIcon, @@ -184,7 +185,7 @@ const PolicyTemplates: React.FC = ({ onUseTemplate, onOpen onTemplatesLoaded?.(data); } catch (error) { console.error("Error fetching policy templates:", error); - message.error("Failed to fetch policy templates"); + MessageManager.error("Failed to fetch policy templates"); } finally { setIsLoading(false); } diff --git a/ui/litellm-dashboard/src/components/shared/CreatedKeyDisplay.test.tsx b/ui/litellm-dashboard/src/components/shared/CreatedKeyDisplay.test.tsx index 013e222c6b..eb6fe5f50c 100644 --- a/ui/litellm-dashboard/src/components/shared/CreatedKeyDisplay.test.tsx +++ b/ui/litellm-dashboard/src/components/shared/CreatedKeyDisplay.test.tsx @@ -3,15 +3,11 @@ import { render, screen, act } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import CreatedKeyDisplay from "./CreatedKeyDisplay"; -vi.mock("antd", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - message: { success: vi.fn() }, - }; -}); +vi.mock("@/components/molecules/message_manager", () => ({ + default: { success: vi.fn(), error: vi.fn(), warning: vi.fn(), info: vi.fn(), loading: vi.fn(), destroy: vi.fn() }, +})); -import { message } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; describe("CreatedKeyDisplay", () => { beforeEach(() => { @@ -52,7 +48,7 @@ describe("CreatedKeyDisplay", () => { await user.click(screen.getByRole("button", { name: /copy virtual key/i })); - expect(message.success).toHaveBeenCalledWith("Key copied to clipboard"); + expect(MessageManager.success).toHaveBeenCalledWith("Key copied to clipboard"); }); it("should revert button text back after 2 seconds", async () => { diff --git a/ui/litellm-dashboard/src/components/shared/CreatedKeyDisplay.tsx b/ui/litellm-dashboard/src/components/shared/CreatedKeyDisplay.tsx index d75c63c646..00ffb8c6e2 100644 --- a/ui/litellm-dashboard/src/components/shared/CreatedKeyDisplay.tsx +++ b/ui/litellm-dashboard/src/components/shared/CreatedKeyDisplay.tsx @@ -1,6 +1,7 @@ import React, { useState } from "react"; import { CopyToClipboard } from "react-copy-to-clipboard"; -import { Button, message } from "antd"; +import { Button } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; interface CreatedKeyDisplayProps { apiKey: string; @@ -15,7 +16,7 @@ const CreatedKeyDisplay: React.FC = ({ apiKey }) => { const handleCopy = () => { setCopied(true); - message.success("Key copied to clipboard"); + MessageManager.success("Key copied to clipboard"); setTimeout(() => setCopied(false), 2000); }; diff --git a/ui/litellm-dashboard/src/components/team/TeamInfo.tsx b/ui/litellm-dashboard/src/components/team/TeamInfo.tsx index d2ce79580d..a4c7ae2bbb 100644 --- a/ui/litellm-dashboard/src/components/team/TeamInfo.tsx +++ b/ui/litellm-dashboard/src/components/team/TeamInfo.tsx @@ -20,7 +20,8 @@ import { isProxyAdminRole } from "@/utils/roles"; import { EditOutlined, InfoCircleOutlined, SaveOutlined } from "@ant-design/icons"; import { ArrowLeftIcon } from "@heroicons/react/outline"; import { Badge, Card, Grid, Text, TextInput, Title } from "@tremor/react"; -import { Button, Form, Input, message, Select, Switch, Tabs, Tooltip } from "antd"; +import { Button, Form, Input, Select, Switch, Tabs, Tooltip } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; import { CheckIcon, CopyIcon } from "lucide-react"; import React, { useEffect, useMemo, useState } from "react"; import { copyToClipboard as utilCopyToClipboard } from "../../utils/dataUtils"; @@ -366,7 +367,7 @@ const TeamInfoView: React.FC = ({ tpm_limit: values.tpm_limit, rpm_limit: values.rpm_limit, }; - message.destroy(); // Remove all existing toasts + MessageManager.destroy(); // Remove all existing toasts await teamMemberUpdateCall(accessToken, teamId, member); @@ -388,7 +389,7 @@ const TeamInfoView: React.FC = ({ } setIsEditMemberModalVisible(false); - message.destroy(); // Remove all existing toasts + MessageManager.destroy(); // Remove all existing toasts NotificationsManager.fromBackend(errMsg); console.error("Error updating team member:", error); diff --git a/ui/litellm-dashboard/src/components/vector_store_management/CreateVectorStore.tsx b/ui/litellm-dashboard/src/components/vector_store_management/CreateVectorStore.tsx index 97162aa132..b189168e1b 100644 --- a/ui/litellm-dashboard/src/components/vector_store_management/CreateVectorStore.tsx +++ b/ui/litellm-dashboard/src/components/vector_store_management/CreateVectorStore.tsx @@ -1,6 +1,7 @@ import React, { useState } from "react"; import { Card, Title, Text } from "@tremor/react"; -import { Upload, Button, Select, Form, message, Alert, Tooltip, Input } from "antd"; +import { Upload, Button, Select, Form, Alert, Tooltip, Input } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; import { InboxOutlined, InfoCircleOutlined } from "@ant-design/icons"; import type { UploadProps } from "antd"; import { ragIngestCall } from "../networking"; @@ -47,13 +48,13 @@ const CreateVectorStore: React.FC = ({ accessToken, onSu ].includes(file.type); if (!isValidType) { - message.error(`${file.name} is not a supported file type. Please upload PDF, TXT, DOCX, or MD files.`); + MessageManager.error(`${file.name} is not a supported file type. Please upload PDF, TXT, DOCX, or MD files.`); return Upload.LIST_IGNORE; } const isLt50M = file.size / 1024 / 1024 < 50; if (!isLt50M) { - message.error(`${file.name} must be smaller than 50MB!`); + MessageManager.error(`${file.name} must be smaller than 50MB!`); return Upload.LIST_IGNORE; } @@ -87,12 +88,12 @@ const CreateVectorStore: React.FC = ({ accessToken, onSu const handleCreateVectorStore = async () => { if (documents.length === 0) { - message.warning("Please upload at least one document"); + MessageManager.warning("Please upload at least one document"); return; } if (!selectedProvider) { - message.warning("Please select a provider"); + MessageManager.warning("Please select a provider"); return; } @@ -100,7 +101,7 @@ const CreateVectorStore: React.FC = ({ accessToken, onSu const requiredFields = getProviderSpecificFields(selectedProvider).filter((field) => field.required); for (const field of requiredFields) { if (!providerParams[field.name]) { - message.warning(`Please provide ${field.label}`); + MessageManager.warning(`Please provide ${field.label}`); return; } } @@ -108,17 +109,17 @@ const CreateVectorStore: React.FC = ({ accessToken, onSu // S3 Vectors specific validation if (selectedProvider === "s3_vectors") { if (providerParams.vector_bucket_name && providerParams.vector_bucket_name.length < 3) { - message.warning("Vector bucket name must be at least 3 characters"); + MessageManager.warning("Vector bucket name must be at least 3 characters"); return; } if (providerParams.index_name && providerParams.index_name.length > 0 && providerParams.index_name.length < 3) { - message.warning("Index name must be at least 3 characters if provided"); + MessageManager.warning("Index name must be at least 3 characters if provided"); return; } } if (!accessToken) { - message.error("No access token available"); + MessageManager.error("No access token available"); return; } diff --git a/ui/litellm-dashboard/src/components/vector_store_management/DocumentsTable.tsx b/ui/litellm-dashboard/src/components/vector_store_management/DocumentsTable.tsx index aeb4240d36..c1ce57ad33 100644 --- a/ui/litellm-dashboard/src/components/vector_store_management/DocumentsTable.tsx +++ b/ui/litellm-dashboard/src/components/vector_store_management/DocumentsTable.tsx @@ -1,5 +1,6 @@ import React from "react"; -import { Table, Badge, Tooltip, message } from "antd"; +import { Table, Badge, Tooltip } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; import { EyeOutlined, CopyOutlined, DeleteOutlined } from "@ant-design/icons"; import { DocumentUpload } from "./types"; @@ -11,7 +12,7 @@ interface DocumentsTableProps { const DocumentsTable: React.FC = ({ documents, onRemove }) => { const handleCopyId = (uid: string) => { navigator.clipboard.writeText(uid); - message.success("Document ID copied to clipboard"); + MessageManager.success("Document ID copied to clipboard"); }; const getStatusBadge = (status: DocumentUpload["status"]) => { diff --git a/ui/litellm-dashboard/src/components/vector_store_management/VectorStoreTester.tsx b/ui/litellm-dashboard/src/components/vector_store_management/VectorStoreTester.tsx index f8b5ada0c3..5c59334fa4 100644 --- a/ui/litellm-dashboard/src/components/vector_store_management/VectorStoreTester.tsx +++ b/ui/litellm-dashboard/src/components/vector_store_management/VectorStoreTester.tsx @@ -1,5 +1,6 @@ import React, { useState } from "react"; -import { Button, Input, Card, Typography, Spin, message, Divider } from "antd"; +import { Button, Input, Card, Typography, Spin, Divider } from "antd"; +import MessageManager from "@/components/molecules/message_manager"; import { SendOutlined, DatabaseOutlined, LoadingOutlined, DownOutlined, RightOutlined } from "@ant-design/icons"; import { vectorStoreSearchCall } from "../networking"; import NotificationsManager from "../molecules/notifications_manager"; @@ -46,7 +47,7 @@ export const VectorStoreTester: React.FC = ({ vectorStor const handleSearch = async () => { if (!query.trim()) { - message.warning("Please enter a search query"); + MessageManager.warning("Please enter a search query"); return; } diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/InputCard.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/InputCard.tsx index 1299f9c168..8fb47274a8 100644 --- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/InputCard.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/InputCard.tsx @@ -4,7 +4,7 @@ */ import { useState } from 'react'; -import { message } from 'antd'; +import MessageManager from "@/components/molecules/message_manager"; import { ParsedMessage } from './prettyMessagesTypes'; import { SectionHeader } from './SectionHeader'; import { CollapsibleMessage } from './CollapsibleMessage'; @@ -33,7 +33,7 @@ export function InputCard({ messages, promptTokens, inputCost }: InputCardProps) const handleCopy = () => { const content = lastMessage?.content || ''; navigator.clipboard.writeText(content); - message.success('Input copied'); + MessageManager.success('Input copied'); }; return ( diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/OutputCard.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/OutputCard.tsx index eff8d83cbd..22f1708ee4 100644 --- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/OutputCard.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/OutputCard.tsx @@ -4,7 +4,8 @@ */ import { useState } from 'react'; -import { Typography, message as antdMessage } from 'antd'; +import { Typography } from 'antd'; +import MessageManager from "@/components/molecules/message_manager"; import { ParsedMessage } from './prettyMessagesTypes'; import { SectionHeader } from './SectionHeader'; import { SimpleMessageBlock } from './SimpleMessageBlock'; @@ -25,7 +26,7 @@ export function OutputCard({ message, completionTokens, outputCost }: OutputCard const content = message.content || ''; navigator.clipboard.writeText(content); - antdMessage.success('Output copied'); + MessageManager.success('Output copied'); }; if (!message) { diff --git a/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx b/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx index 5b5c1036fa..cc182ee36d 100644 --- a/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx +++ b/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx @@ -1,23 +1,27 @@ "use client"; import React, { useEffect, useRef } from "react"; -import { notification } from "antd"; +import { notification, message } from "antd"; import { setNotificationInstance } from "@/components/molecules/notifications_manager"; +import { setMessageInstance } from "@/components/molecules/message_manager"; export default function AntdGlobalProvider({ children }: { children: React.ReactNode }) { - const [api, contextHolder] = notification.useNotification(); + const [notificationApi, notificationContextHolder] = notification.useNotification(); + const [messageApi, messageContextHolder] = message.useMessage(); const initialized = useRef(false); useEffect(() => { if (!initialized.current) { - setNotificationInstance(api); + setNotificationInstance(notificationApi); + setMessageInstance(messageApi); initialized.current = true; } - }, [api]); + }, [notificationApi, messageApi]); return ( <> - {contextHolder} + {notificationContextHolder} + {messageContextHolder} {children} );