refactor(ui): codemod every toast call site onto lib/toast and delete the antd-era facades (#37253)

MessageManager and NotificationManager were thin facades over lib/toast since #37207. This
rewrites their ~750 call sites (226 files) to import { toast } from @/lib/toast directly:
success/info/warning/error keep their names, fromBackend becomes fromError, destroy/clear
become dismiss. The one config-object caller (CreateMCPServer's admin-review branch) becomes
an explicit toast.success(message, { description }). Behaviour is unchanged: no production
caller passed a duration, so every toast keeps the same kind, title and default duration.

Tests: the global vitest mock now targets @/lib/toast (toast.test.ts opts back out with
vi.unmock), so the per-file vi.mock boilerplate for the facades is deleted and assertions read
toast.success / toast.fromError. The two facade files, their test and their filename-case
suppressions are removed, along with the commented-out facade calls left in networking.tsx
This commit is contained in:
ryan-crabbe-berri 2026-08-18 11:24:49 -07:00 • committed by GitHub
parent 8159f240c4
commit 83ae623733
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
246 changed files with 1042 additions and 1679 deletions

View file

@ -2497,16 +2497,6 @@
"count": 1
}
},
"src/components/molecules/message_manager.tsx": {
"local/filename-pascal-case": {
"count": 1
}
},
"src/components/molecules/notifications_manager.tsx": {
"local/filename-pascal-case": {
"count": 1
}
},
"src/components/navbar.tsx": {
"local/filename-pascal-case": {
"count": 1

View file

@ -1,6 +1,6 @@
import React, { useEffect } from "react";
import { Modal, Form } from "antd";
import MessageManager from "@/components/molecules/message_manager";
import { toast } from "@/lib/toast";
import { AccessGroupBaseForm, AccessGroupFormValues } from "./AccessGroupBaseForm";
import { useEditAccessGroup, AccessGroupUpdateParams } from "@/app/(dashboard)/hooks/accessGroups/useEditAccessGroup";
import { AccessGroupResponse } from "@/app/(dashboard)/hooks/accessGroups/useAccessGroups";
@ -45,7 +45,7 @@ export function AccessGroupEditModal({ visible, accessGroup, onCancel, onSuccess
{ accessGroupId: accessGroup.access_group_id, params },
{
onSuccess: () => {
MessageManager.success("Access group updated successfully");
toast.success("Access group updated successfully");
onSuccess?.();
onCancel();
},

View file

@ -4,10 +4,6 @@ import userEvent from "@testing-library/user-event";
import React from "react";
import { describe, expect, it, vi } from "vitest";
vi.mock("@/components/molecules/notifications_manager", () => ({
__esModule: true,
default: { success: vi.fn(), fromBackend: vi.fn() },
}));
vi.mock("@/components/ModelSelect/ModelSelect", () => ({
ModelSelect: ({ onChange }: { onChange: (values: string[]) => void }) => (
<button type="button" onClick={() => onChange(["gpt-5.2"])}>

View file

@ -8,7 +8,7 @@ import { accessGroupKeys } from "@/app/(dashboard)/hooks/accessGroups/useAccessG
import { useAgents } from "@/app/(dashboard)/hooks/agents/useAgents";
import { useMCPServers } from "@/app/(dashboard)/hooks/mcpServers/useMCPServers";
import { ModelSelect } from "@/components/ModelSelect/ModelSelect";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { FieldGroup } from "@/components/shared/form/field";
import { FormField } from "@/components/shared/form/FormField";
import { Button } from "@/components/ui/button";
@ -113,12 +113,12 @@ export const AccessGroupCreateDialog = ({
const mutation = useMutation({
mutationFn: (body: AccessGroupCreateBody) => createAccessGroup(body),
onSuccess: () => {
NotificationsManager.success("Access group created successfully");
toast.success("Access group created successfully");
queryClient.invalidateQueries({ queryKey: accessGroupKeys.all });
closeAndReset();
},
onError: (error: unknown) =>
NotificationsManager.fromBackend(error instanceof Error ? error.message : "Failed to create access group"),
toast.fromError(error instanceof Error ? error.message : "Failed to create access group"),
});
const handleOpenChange = (nextOpen: boolean) => {

View file

@ -18,7 +18,7 @@ import { Alert, Button as Button2, Form, Input, Modal, Space, Tabs, Typography }
import React, { useEffect, useState } from "react";
import NewBadge from "@/components/common_components/NewBadge";
import { useBaseUrl } from "@/components/constants";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { addAllowedIP, deleteAllowedIP, getAllowedIPs, getSSOSettings } from "@/components/networking";
import SCIMConfig from "@/components/SCIM";
import LoggingSettings from "@/components/Settings/AdminSettings/LoggingSettings/LoggingSettings";
@ -79,9 +79,7 @@ const AdminPanel: React.FC<AdminPanelProps> = ({ proxySettings }) => {
const handleShowAllowedIPs = async () => {
try {
if (premiumUser !== true) {
NotificationsManager.fromBackend(
"This feature is only available for premium users. Please upgrade your account.",
);
toast.fromError("This feature is only available for premium users. Please upgrade your account.");
return;
}
if (accessToken) {
@ -92,7 +90,7 @@ const AdminPanel: React.FC<AdminPanelProps> = ({ proxySettings }) => {
}
} catch (error) {
console.error("Error fetching allowed IPs:", error);
NotificationsManager.fromBackend(`Failed to fetch allowed IPs ${error}`);
toast.fromError(`Failed to fetch allowed IPs ${error}`);
setAllowedIPs([all_ip_address_allowed]);
} finally {
if (premiumUser === true) {
@ -108,11 +106,11 @@ const AdminPanel: React.FC<AdminPanelProps> = ({ proxySettings }) => {
// Fetch the updated list of IPs
const updatedIPs = await getAllowedIPs(accessToken);
setAllowedIPs(updatedIPs);
NotificationsManager.success("IP address added successfully");
toast.success("IP address added successfully");
}
} catch (error) {
console.error("Error adding IP:", error);
NotificationsManager.fromBackend(`Failed to add IP address ${error}`);
toast.fromError(`Failed to add IP address ${error}`);
} finally {
setIsAddIPModalVisible(false);
}
@ -130,10 +128,10 @@ const AdminPanel: React.FC<AdminPanelProps> = ({ proxySettings }) => {
// Fetch the updated list of IPs
const updatedIPs = await getAllowedIPs(accessToken);
setAllowedIPs(updatedIPs.length > 0 ? updatedIPs : [all_ip_address_allowed]);
NotificationsManager.success("IP address deleted successfully");
toast.success("IP address deleted successfully");
} catch (error) {
console.error("Error deleting IP:", error);
NotificationsManager.fromBackend(`Failed to delete IP address ${error}`);
toast.fromError(`Failed to delete IP address ${error}`);
} finally {
setIsDeleteIPModalVisible(false);
setIPToDelete(null);
@ -229,7 +227,7 @@ const AdminPanel: React.FC<AdminPanelProps> = ({ proxySettings }) => {
onClick={() =>
premiumUser === true
? setIsUIAccessControlModalVisible(true)
: NotificationsManager.fromBackend("Only premium users can configure UI access control")
: toast.fromError("Only premium users can configure UI access control")
}
>
UI Access Control
@ -335,7 +333,7 @@ const AdminPanel: React.FC<AdminPanelProps> = ({ proxySettings }) => {
accessToken={accessToken}
onSuccess={() => {
handleUIAccessControlOk();
NotificationsManager.success("UI Access Control settings updated successfully");
toast.success("UI Access Control settings updated successfully");
}}
/>
</Modal>

View file

@ -5,7 +5,7 @@ import AddAgentForm from "./add_agent_form";
import { isAdminRole } from "@/utils/roles";
import AgentInfoView from "./agent_info";
import AgentsTable from "./AgentsTable";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { Agent } from "@/components/agents/types";
import { Team } from "@/components/key_team_helpers/key_list";
import { Alert, AlertDescription, AlertTitle } from "@/components/shared/Alert";
@ -120,11 +120,11 @@ const AgentsPanel: React.FC<AgentsPanelProps> = ({ accessToken, userRole, teams
setIsDeleting(true);
try {
await deleteAgentCall(accessToken, agentToDelete.id);
NotificationsManager.success(`Agent "${agentToDelete.name}" deleted successfully`);
toast.success(`Agent "${agentToDelete.name}" deleted successfully`);
await refetchAgents(healthCheckEnabled);
} catch (error) {
console.error("Error deleting agent:", error);
NotificationsManager.fromBackend("Failed to delete agent");
toast.fromError("Failed to delete agent");
} finally {
setIsDeleting(false);
setAgentToDelete(null);

View file

@ -1,6 +1,6 @@
import React, { useState, useEffect } from "react";
import { Modal, Form, Select, Input, Steps, Radio, Tag, Divider, Switch, InputNumber } from "antd";
import MessageManager from "@/components/molecules/message_manager";
import { toast } from "@/lib/toast";
import { Logo } from "@/components/molecules/logo/Logo";
import { Button } from "@tremor/react";
import { CheckCircleFilled, KeyOutlined, RobotOutlined, AppstoreOutlined, InfoCircleOutlined } from "@ant-design/icons";
@ -241,7 +241,7 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
const handleCreateAgent = async () => {
if (!accessToken) {
MessageManager.error("No access token available");
toast.error("No access token available");
return;
}
@ -251,7 +251,7 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
const values = { ...form.getFieldsValue(true) };
const agentData = buildAgentData(values);
if (!agentData) {
MessageManager.error("Failed to build agent data");
toast.error("Failed to build agent data");
setIsSubmitting(false);
return;
}
@ -327,7 +327,7 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
setCreatedKeyValue(keyResponse.key || null);
} else if (keyAssignOption === "existing_key") {
if (!selectedExistingKey) {
MessageManager.error("Please select an existing key to assign");
toast.error("Please select an existing key to assign");
setIsSubmitting(false);
return;
}
@ -344,7 +344,7 @@ const AddAgentForm: React.FC<AddAgentFormProps> = ({ visible, onClose, accessTok
} catch (error) {
console.error("Error creating agent:", error);
const errorMessage = error instanceof Error ? error.message : String(error);
MessageManager.error(errorMessage ? `Failed to create agent: ${errorMessage}` : "Failed to create agent");
toast.error(errorMessage ? `Failed to create agent: ${errorMessage}` : "Failed to create agent");
} finally {
setIsSubmitting(false);
}

View file

@ -1,7 +1,7 @@
import React, { useState, useEffect, useMemo } from "react";
import { Card, Title, Text, Button as TremorButton, Tab, TabGroup, TabList, TabPanel, TabPanels } from "@tremor/react";
import { Form, Input, InputNumber, Button as AntButton, Spin, Descriptions, Divider } from "antd";
import MessageManager from "@/components/molecules/message_manager";
import { toast } from "@/lib/toast";
import { ArrowLeftIcon } from "@heroicons/react/outline";
import { getAgentInfo, patchAgentCall, getAgentCreateMetadata, AgentCreateInfo } from "@/components/networking";
import { Agent } from "@/components/agents/types";
@ -80,7 +80,7 @@ const AgentInfoView: React.FC<AgentInfoViewProps> = ({ agentId, onClose, accessT
}
} catch (error) {
console.error("Error fetching agent info:", error);
MessageManager.error("Failed to load agent information");
toast.error("Failed to load agent information");
} finally {
setIsLoading(false);
}
@ -160,12 +160,12 @@ const AgentInfoView: React.FC<AgentInfoViewProps> = ({ agentId, onClose, accessT
}
await patchAgentCall(accessToken, agentId, updateData);
MessageManager.success("Agent updated successfully");
toast.success("Agent updated successfully");
setIsEditing(false);
fetchAgentInfo();
} catch (error) {
console.error("Error updating agent:", error);
MessageManager.error("Failed to update agent");
toast.error("Failed to update agent");
} finally {
setIsSaving(false);
}

View file

@ -4,7 +4,7 @@ import { Modal } from "antd";
import { z } from "zod/v4";
import { useCreateBudget } from "@/app/(dashboard)/hooks/budgets/useBudgets";
import { applyBudgetPrecision } from "./budgetPrecision";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { FieldGroup } from "@/components/shared/form/field";
import { FormField } from "@/components/shared/form/FormField";
import { Button } from "@/components/ui/button";
@ -52,18 +52,18 @@ const BudgetModal: React.FC<BudgetModalProps> = ({ isModalVisible, setIsModalVis
const handleCreate = async (formValues: BudgetFormValues) => {
try {
NotificationsManager.info("Making API Call");
toast.info("Making API Call");
await createBudget.mutateAsync(
applyBudgetPrecision(
optionalSettingsOpen ? formValues : { ...formValues, max_budget: undefined, budget_duration: undefined },
),
);
NotificationsManager.success("Budget Created");
toast.success("Budget Created");
form.reset();
setIsModalVisible(false);
} catch (error) {
console.error("Error creating the budget:", error);
NotificationsManager.fromBackend(`Error creating the budget: ${error}`);
toast.fromError(`Error creating the budget: ${error}`);
}
};

View file

@ -25,10 +25,6 @@ vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
default: () => ({ accessToken: "sk-test", userRole: "Admin", userId: "u1" }),
}));
vi.mock("@/components/molecules/notifications_manager", () => ({
default: { success: vi.fn(), info: vi.fn(), fromBackend: vi.fn() },
}));
interface BudgetSeed {
budget_id: string;
max_budget: number | null;

View file

@ -11,7 +11,7 @@ import { ToolbarSeparator } from "@/components/shared/ToolbarSeparator";
import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import DeleteResourceModal from "@/components/common_components/DeleteResourceModal";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { useBudgetList, useDeleteBudget, budgetItem } from "@/app/(dashboard)/hooks/budgets/useBudgets";
import BudgetModal from "./budget_modal";
import BudgetTable from "./BudgetTable";
@ -60,14 +60,10 @@ const BudgetPanel: React.FC<BudgetSettingsPageProps> = ({ accessToken }) => {
}
try {
await deleteBudget.mutateAsync(selectedBudget.budget_id);
NotificationsManager.success("Budget deleted.");
toast.success("Budget deleted.");
} catch (error) {
console.error("Error deleting budget:", error);
if (typeof NotificationsManager.fromBackend === "function") {
NotificationsManager.fromBackend("Failed to delete budget");
} else {
NotificationsManager.info("Failed to delete budget");
}
toast.fromError("Failed to delete budget");
} finally {
setIsDeleteModalVisible(false);
setSelectedBudget(null);

View file

@ -5,7 +5,7 @@ import { useForm } from "react-hook-form";
import { useUpdateBudget } from "@/app/(dashboard)/hooks/budgets/useBudgets";
import { budgetItem } from "@/app/(dashboard)/hooks/budgets/useBudgets";
import { applyBudgetPrecision } from "./budgetPrecision";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { FieldGroup } from "@/components/shared/form/field";
import { FormField } from "@/components/shared/form/FormField";
import { Button } from "@/components/ui/button";
@ -58,18 +58,18 @@ const EditBudgetModal: React.FC<EditBudgetModalProps> = ({ isModalVisible, setIs
const handleUpdate = async (formValues: EditBudgetFormValues) => {
try {
NotificationsManager.info("Making API Call");
toast.info("Making API Call");
await updateBudget.mutateAsync(
applyBudgetPrecision(
optionalSettingsOpen ? formValues : { ...formValues, max_budget: undefined, budget_duration: undefined },
),
);
NotificationsManager.success("Budget Updated");
toast.success("Budget Updated");
form.reset();
setIsModalVisible(false);
} catch (error) {
console.error("Error updating the budget:", error);
NotificationsManager.fromBackend(`Error updating the budget: ${error}`);
toast.fromError(`Error updating the budget: ${error}`);
}
};

View file

@ -1,6 +1,6 @@
import type { DateRangePickerValue } from "@/components/shared/date_picker_types";
import React, { useEffect, useState } from "react";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import UsageDatePicker from "@/components/shared/usage_date_picker";
import { BarChart } from "@/components/shared/charts";
import { Button } from "@/components/ui/button";
@ -104,7 +104,7 @@ const CacheDashboard: React.FC<CachePageProps> = ({ accessToken, token, userRole
const runCachingHealthCheck = async () => {
try {
NotificationsManager.info("Running cache health check...");
toast.info("Running cache health check...");
setHealthCheckResponse("");
const response = await cachingHealthCheckCall(accessToken !== null ? accessToken : "");
setHealthCheckResponse(response);

View file

@ -3,7 +3,7 @@ import { Button, Accordion, AccordionHeader, AccordionBody } from "@tremor/react
import { Form } from "antd";
import { getCacheSettingsCall, testCacheConnectionCall, updateCacheSettingsCall } from "@/components/networking";
import { fetchAvailableModels, ModelGroup } from "@/components/llm_calls/fetch_models";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import RedisTypeSelector from "./RedisTypeSelector";
import CacheFieldSection from "./CacheFieldSection";
import { EmbeddingModelOption } from "./CacheFormField";
@ -39,7 +39,7 @@ const CacheSettings: React.FC<CacheSettingsProps> = ({ accessToken }) => {
setRedisType(toRedisType(currentValues.redis_type));
} catch (error) {
console.error("Failed to load cache settings:", error);
NotificationsManager.fromBackend("Failed to load cache settings");
toast.fromError("Failed to load cache settings");
}
}, [accessToken, form]);
@ -86,15 +86,13 @@ const CacheSettings: React.FC<CacheSettingsProps> = ({ accessToken }) => {
buildCachePayload(redisType, values, { forTesting: true }),
);
if (result.status === "success") {
NotificationsManager.success("Cache connection test successful!");
toast.success("Cache connection test successful!");
} else {
NotificationsManager.fromBackend(`Connection test failed: ${result.message || result.error}`);
toast.fromError(`Connection test failed: ${result.message || result.error}`);
}
} catch (error) {
console.error("Test connection error:", error);
NotificationsManager.fromBackend(
`Connection test failed: ${error instanceof Error ? error.message : "Unknown error"}`,
);
toast.fromError(`Connection test failed: ${error instanceof Error ? error.message : "Unknown error"}`);
} finally {
setIsTesting(false);
}
@ -112,11 +110,11 @@ const CacheSettings: React.FC<CacheSettingsProps> = ({ accessToken }) => {
setIsSaving(true);
try {
await updateCacheSettingsCall(accessToken, buildCachePayload(redisType, values, { forTesting: false }));
NotificationsManager.success("Cache settings updated successfully");
toast.success("Cache settings updated successfully");
await loadCacheSettings();
} catch (error) {
console.error("Failed to save cache settings:", error);
NotificationsManager.fromBackend("Failed to update cache settings");
toast.fromError("Failed to update cache settings");
} finally {
setIsSaving(false);
}

View file

@ -5,7 +5,7 @@ import userEvent from "@testing-library/user-event";
import CoordinationRedisSettings from "./index";
import { REDACTED_VALUE } from "./types";
import * as networking from "@/components/networking";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
vi.mock("@/components/networking", () => ({
getCoordinationRedisSettingsCall: vi.fn(),
@ -17,14 +17,10 @@ vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
default: () => ({ accessToken: "sk-test" }),
}));
vi.mock("@/components/molecules/notifications_manager", () => ({
default: { success: vi.fn(), fromBackend: vi.fn() },
}));
const getSettings = vi.mocked(networking.getCoordinationRedisSettingsCall);
const updateSettings = vi.mocked(networking.updateCoordinationRedisSettingsCall);
const testConnection = vi.mocked(networking.testCoordinationRedisConnectionCall);
const notifications = vi.mocked(NotificationsManager);
const notifications = vi.mocked(toast);
const settingsResponse = (
values: Record<string, unknown>,
@ -238,7 +234,7 @@ describe("CoordinationRedisSettings", () => {
await user.click(screen.getByRole("button", { name: /test connection/i }));
await waitFor(() =>
expect(notifications.fromBackend).toHaveBeenCalledWith(expect.stringContaining("connection refused")),
expect(notifications.fromError).toHaveBeenCalledWith(expect.stringContaining("connection refused")),
);
expect(notifications.success).not.toHaveBeenCalled();
});

View file

@ -1,6 +1,6 @@
import React, { useEffect, useMemo, useState } from "react";
import { Button, Form } from "antd";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { StatusBadge } from "@/components/shared/table_cells/status_badge";
import {
useCoordinationRedisSettings,
@ -37,7 +37,7 @@ const CoordinationRedisSettings: React.FC = () => {
useEffect(() => {
if (isError) {
NotificationsManager.fromBackend("Failed to load coordination Redis settings");
toast.fromError("Failed to load coordination Redis settings");
}
}, [isError]);
@ -58,14 +58,12 @@ const CoordinationRedisSettings: React.FC = () => {
try {
const result = await testConnection.mutateAsync(buildCoordinationPayload(redisType, values));
if (result.status === "healthy") {
NotificationsManager.success("Coordination Redis connection test successful!");
toast.success("Coordination Redis connection test successful!");
} else {
NotificationsManager.fromBackend(`Connection test failed: ${result.error ?? "Unknown error"}`);
toast.fromError(`Connection test failed: ${result.error ?? "Unknown error"}`);
}
} catch (error) {
NotificationsManager.fromBackend(
`Connection test failed: ${error instanceof Error ? error.message : "Unknown error"}`,
);
toast.fromError(`Connection test failed: ${error instanceof Error ? error.message : "Unknown error"}`);
}
};
@ -77,9 +75,9 @@ const CoordinationRedisSettings: React.FC = () => {
try {
await updateSettings.mutateAsync(buildCoordinationPayload(redisType, values));
NotificationsManager.success("Coordination Redis settings saved. Restart the proxy to apply them.");
toast.success("Coordination Redis settings saved. Restart the proxy to apply them.");
} catch {
NotificationsManager.fromBackend("Failed to update coordination Redis settings");
toast.fromError("Failed to update coordination Redis settings");
}
};

View file

@ -3,7 +3,7 @@
import React, { useCallback, useEffect, useState } from "react";
import { getGeneralSettingsCall } from "@/components/networking";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import {
PromptCachingPanel,
generalSettingsItem,
@ -27,7 +27,7 @@ const PromptCachingTab: React.FC<PromptCachingTabProps> = ({ accessToken, activi
.then((data: generalSettingsItem[]) => setSettings(data))
.catch((error) => {
console.error("Failed to load prompt caching settings:", error);
NotificationsManager.fromBackend("Failed to load prompt caching settings");
toast.fromError("Failed to load prompt caching settings");
});
}, [accessToken]);

View file

@ -5,7 +5,7 @@ import { Button, Form, Input, Switch } from "antd";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { createGuardrailCall, getGuardrailsList } from "@/components/networking";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import {
buildCompressionGuardrailPayload,
compressionGuardrailsOf,
@ -37,7 +37,7 @@ const PromptCompressionTab: React.FC<PromptCompressionTabProps> = ({ accessToken
.then((response) => setGuardrails(compressionGuardrailsOf(response as GuardrailListResponse)))
.catch((error) => {
console.error("Failed to load compression guardrails:", error);
NotificationsManager.fromBackend("Failed to load compression guardrails");
toast.fromError("Failed to load compression guardrails");
})
.finally(() => setIsLoading(false));
}, [accessToken]);
@ -60,12 +60,12 @@ const PromptCompressionTab: React.FC<PromptCompressionTabProps> = ({ accessToken
defaultOn: values.defaultOn ?? true,
}),
);
NotificationsManager.success("Compression guardrail created");
toast.success("Compression guardrail created");
form.resetFields();
await loadGuardrails();
} catch (error) {
console.error("Failed to create compression guardrail:", error);
NotificationsManager.fromBackend("Failed to create compression guardrail");
toast.fromError("Failed to create compression guardrail");
} finally {
setIsSaving(false);
}

View file

@ -1,7 +1,6 @@
import { describe, expect, it, vi } from "vitest";
vi.mock("@/lib/http/api", () => ({ $api: { useQuery: vi.fn() }, fetchClient: { POST: vi.fn() } }));
vi.mock("@/components/molecules/notifications_manager", () => ({ default: { fromBackend: vi.fn() } }));
import { shadowEvalListPollMs, shadowEvalPollMs } from "./useShadowEval";

View file

@ -1,7 +1,7 @@
import { useMutation, useQueryClient, type QueryClient } from "@tanstack/react-query";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { $api, fetchClient } from "@/lib/http/api";
import type { components } from "@/lib/http/schema";
@ -60,7 +60,7 @@ const useShadowEvalMutation = <TVariables>(mutationFn: (variables: TVariables) =
return useMutation({
mutationFn,
onSuccess: () => invalidateShadowEval(queryClient),
onError: (error: unknown) => NotificationsManager.fromBackend(error),
onError: (error: unknown) => toast.fromError(error),
});
};

View file

@ -1,7 +1,7 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderHook, act } from "@testing-library/react";
import { useDiscountConfig } from "./use_discount_config";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
vi.mock("@/components/networking", () => ({
getProxyBaseUrl: vi.fn(() => ""),
@ -70,7 +70,7 @@ describe("useDiscountConfig", () => {
await result.current.fetchDiscountConfig();
});
expect(NotificationsManager.fromBackend).toHaveBeenCalledWith(expect.stringMatching(/failed to fetch/i));
expect(toast.fromError).toHaveBeenCalledWith(expect.stringMatching(/failed to fetch/i));
});
});
@ -84,9 +84,7 @@ describe("useDiscountConfig", () => {
});
expect(success!).toBe(false);
expect(NotificationsManager.fromBackend).toHaveBeenCalledWith(
"Please select a provider and enter discount percentage",
);
expect(toast.fromError).toHaveBeenCalledWith("Please select a provider and enter discount percentage");
});
it("should return false and notify when no discount is provided", async () => {
@ -98,9 +96,7 @@ describe("useDiscountConfig", () => {
});
expect(success!).toBe(false);
expect(NotificationsManager.fromBackend).toHaveBeenCalledWith(
"Please select a provider and enter discount percentage",
);
expect(toast.fromError).toHaveBeenCalledWith("Please select a provider and enter discount percentage");
});
it("should return false and notify when the discount exceeds 100", async () => {
@ -112,7 +108,7 @@ describe("useDiscountConfig", () => {
});
expect(success!).toBe(false);
expect(NotificationsManager.fromBackend).toHaveBeenCalledWith(expect.stringMatching(/0%.*100%/i));
expect(toast.fromError).toHaveBeenCalledWith(expect.stringMatching(/0%.*100%/i));
});
it("should return false and notify when the provider already exists in the config", async () => {
@ -135,7 +131,7 @@ describe("useDiscountConfig", () => {
});
expect(success!).toBe(false);
expect(NotificationsManager.fromBackend).toHaveBeenCalledWith(expect.stringMatching(/already exists/i));
expect(toast.fromError).toHaveBeenCalledWith(expect.stringMatching(/already exists/i));
});
it("should save the config and return true on a valid new provider", async () => {
@ -156,7 +152,7 @@ describe("useDiscountConfig", () => {
});
expect(success!).toBe(true);
expect(NotificationsManager.success).toHaveBeenCalledWith("Discount configuration updated successfully");
expect(toast.success).toHaveBeenCalledWith("Discount configuration updated successfully");
});
});

View file

@ -1,6 +1,6 @@
import { useState, useCallback } from "react";
import { getProxyBaseUrl, getGlobalLitellmHeaderName } from "@/components/networking";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { DiscountConfig } from "./types";
import { getProviderBackendValue } from "./provider_display_helpers";
import { Providers } from "@/components/provider_info_helpers";
@ -43,7 +43,7 @@ export function useDiscountConfig({ accessToken }: UseDiscountConfigProps): UseD
}
} catch (error) {
console.error("Error fetching discount config:", error);
NotificationsManager.fromBackend("Failed to fetch discount configuration");
toast.fromError("Failed to fetch discount configuration");
}
}, [accessToken]);
@ -63,16 +63,16 @@ export function useDiscountConfig({ accessToken }: UseDiscountConfigProps): UseD
});
if (response.ok) {
NotificationsManager.success("Discount configuration updated successfully");
toast.success("Discount configuration updated successfully");
await fetchDiscountConfig();
} else {
const errorData = await response.json();
const errorMessage = errorData.detail?.error || errorData.detail || "Failed to update settings";
NotificationsManager.fromBackend(errorMessage);
toast.fromError(errorMessage);
}
} catch (error) {
console.error("Error updating discount config:", error);
NotificationsManager.fromBackend("Failed to update discount configuration");
toast.fromError("Failed to update discount configuration");
}
},
[accessToken, fetchDiscountConfig],
@ -81,25 +81,25 @@ export function useDiscountConfig({ accessToken }: UseDiscountConfigProps): UseD
const handleAddProvider = useCallback(
async (selectedProvider: string | undefined, newDiscount: string): Promise<boolean> => {
if (!selectedProvider || !newDiscount) {
NotificationsManager.fromBackend("Please select a provider and enter discount percentage");
toast.fromError("Please select a provider and enter discount percentage");
return false;
}
const percentageValue = parseFloat(newDiscount);
if (isNaN(percentageValue) || percentageValue < 0 || percentageValue > 100) {
NotificationsManager.fromBackend("Discount must be between 0% and 100%");
toast.fromError("Discount must be between 0% and 100%");
return false;
}
const providerValue = getProviderBackendValue(selectedProvider);
if (!providerValue) {
NotificationsManager.fromBackend("Invalid provider selected");
toast.fromError("Invalid provider selected");
return false;
}
if (discountConfig[providerValue]) {
NotificationsManager.fromBackend(
toast.fromError(
`Discount for ${Providers[selectedProvider as keyof typeof Providers]} already exists. Edit it in the table above.`,
);
return false;

View file

@ -1,7 +1,7 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderHook, act } from "@testing-library/react";
import { useMarginConfig } from "./use_margin_config";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
vi.mock("@/components/networking", () => ({
getProxyBaseUrl: vi.fn(() => ""),
@ -70,7 +70,7 @@ describe("useMarginConfig", () => {
await result.current.fetchMarginConfig();
});
expect(NotificationsManager.fromBackend).toHaveBeenCalledWith(expect.stringMatching(/failed to fetch/i));
expect(toast.fromError).toHaveBeenCalledWith(expect.stringMatching(/failed to fetch/i));
});
});
@ -89,7 +89,7 @@ describe("useMarginConfig", () => {
});
expect(success!).toBe(false);
expect(NotificationsManager.fromBackend).toHaveBeenCalledWith("Please select a provider");
expect(toast.fromError).toHaveBeenCalledWith("Please select a provider");
});
it("should return false and notify when percentage is out of range", async () => {
@ -106,7 +106,7 @@ describe("useMarginConfig", () => {
});
expect(success!).toBe(false);
expect(NotificationsManager.fromBackend).toHaveBeenCalledWith(expect.stringMatching(/0%.*1000%/i));
expect(toast.fromError).toHaveBeenCalledWith(expect.stringMatching(/0%.*1000%/i));
});
it("should return false when the provider already has a margin configured", async () => {
@ -134,7 +134,7 @@ describe("useMarginConfig", () => {
});
expect(success!).toBe(false);
expect(NotificationsManager.fromBackend).toHaveBeenCalledWith(expect.stringMatching(/already exists/i));
expect(toast.fromError).toHaveBeenCalledWith(expect.stringMatching(/already exists/i));
});
it("should save a percentage margin and return true for a valid new provider", async () => {
@ -160,7 +160,7 @@ describe("useMarginConfig", () => {
});
expect(success!).toBe(true);
expect(NotificationsManager.success).toHaveBeenCalledWith("Margin configuration updated successfully");
expect(toast.success).toHaveBeenCalledWith("Margin configuration updated successfully");
});
it("should save a fixed amount margin and return true for a valid new provider", async () => {
@ -189,7 +189,7 @@ describe("useMarginConfig", () => {
});
expect(success!).toBe(true);
expect(NotificationsManager.success).toHaveBeenCalledWith("Margin configuration updated successfully");
expect(toast.success).toHaveBeenCalledWith("Margin configuration updated successfully");
});
it("should accept the global provider without provider_map lookup", async () => {

View file

@ -1,6 +1,6 @@
import { useState, useCallback } from "react";
import { getProxyBaseUrl, getGlobalLitellmHeaderName } from "@/components/networking";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { MarginConfig } from "./types";
import { getProviderBackendValue } from "./provider_display_helpers";
import { Providers } from "@/components/provider_info_helpers";
@ -53,7 +53,7 @@ export function useMarginConfig({ accessToken }: UseMarginConfigProps): UseMargi
}
} catch (error) {
console.error("Error fetching margin config:", error);
NotificationsManager.fromBackend("Failed to fetch margin configuration");
toast.fromError("Failed to fetch margin configuration");
}
}, [accessToken]);
@ -73,16 +73,16 @@ export function useMarginConfig({ accessToken }: UseMarginConfigProps): UseMargi
});
if (response.ok) {
NotificationsManager.success("Margin configuration updated successfully");
toast.success("Margin configuration updated successfully");
await fetchMarginConfig();
} else {
const errorData = await response.json();
const errorMessage = errorData.detail?.error || errorData.detail || "Failed to update settings";
NotificationsManager.fromBackend(errorMessage);
toast.fromError(errorMessage);
}
} catch (error) {
console.error("Error updating margin config:", error);
NotificationsManager.fromBackend("Failed to update margin configuration");
toast.fromError("Failed to update margin configuration");
}
},
[accessToken, fetchMarginConfig],
@ -93,7 +93,7 @@ export function useMarginConfig({ accessToken }: UseMarginConfigProps): UseMargi
const { selectedProvider, marginType, percentageValue, fixedAmountValue } = params;
if (!selectedProvider) {
NotificationsManager.fromBackend("Please select a provider");
toast.fromError("Please select a provider");
return false;
}
@ -103,7 +103,7 @@ export function useMarginConfig({ accessToken }: UseMarginConfigProps): UseMargi
} else {
const backendValue = getProviderBackendValue(selectedProvider);
if (!backendValue) {
NotificationsManager.fromBackend("Invalid provider selected");
toast.fromError("Invalid provider selected");
return false;
}
providerValue = backendValue;
@ -112,7 +112,7 @@ export function useMarginConfig({ accessToken }: UseMarginConfigProps): UseMargi
if (marginConfig[providerValue]) {
const displayName =
providerValue === "global" ? "Global" : Providers[selectedProvider as keyof typeof Providers];
NotificationsManager.fromBackend(`Margin for ${displayName} already exists. Edit it in the table above.`);
toast.fromError(`Margin for ${displayName} already exists. Edit it in the table above.`);
return false;
}
@ -120,14 +120,14 @@ export function useMarginConfig({ accessToken }: UseMarginConfigProps): UseMargi
if (marginType === "percentage") {
const percentValue = parseFloat(percentageValue);
if (isNaN(percentValue) || percentValue < 0 || percentValue > 1000) {
NotificationsManager.fromBackend("Percentage must be between 0% and 1000%");
toast.fromError("Percentage must be between 0% and 1000%");
return false;
}
marginValue = percentValue / 100;
} else {
const fixedValue = parseFloat(fixedAmountValue);
if (isNaN(fixedValue) || fixedValue < 0) {
NotificationsManager.fromBackend("Fixed amount must be non-negative");
toast.fromError("Fixed amount must be non-negative");
return false;
}
marginValue = { fixed_amount: fixedValue };

View file

@ -1,6 +1,6 @@
import React, { useState } from "react";
import { Copy, Info } from "lucide-react";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
@ -45,14 +45,14 @@ export function GuardrailTestPanel({
const handleSubmit = () => {
if (!inputText.trim()) {
NotificationsManager.fromBackend("Please enter text to test");
toast.fromError("Please enter text to test");
return;
}
const { metadata, error } = parseMetadata(metadataText);
if (error) {
setMetadataError(error);
NotificationsManager.fromBackend(`Metadata: ${error}`);
toast.fromError(`Metadata: ${error}`);
return;
}
setMetadataError(null);
@ -98,9 +98,9 @@ export function GuardrailTestPanel({
const handleCopyInput = async () => {
const success = await copyToClipboard(inputText);
if (success) {
NotificationsManager.success("Input copied to clipboard");
toast.success("Input copied to clipboard");
} else {
NotificationsManager.fromBackend("Failed to copy input");
toast.fromError("Failed to copy input");
}
};

View file

@ -2,7 +2,7 @@ import React, { useState } from "react";
import { FlaskConical, Search } from "lucide-react";
import GuardrailTestPanel from "./GuardrailTestPanel";
import { applyGuardrail } from "@/components/networking";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { Card, CardContent } from "@/components/ui/card";
import { InputGroup, InputGroupAddon, InputGroupInput } from "@/components/ui/input-group";
import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner";
@ -105,10 +105,10 @@ const GuardrailTestPlayground: React.FC<GuardrailTestPlaygroundProps> = ({
setIsTesting(false);
if (results.length > 0) {
NotificationsManager.success(`${results.length} guardrail${results.length > 1 ? "s" : ""} applied successfully`);
toast.success(`${results.length} guardrail${results.length > 1 ? "s" : ""} applied successfully`);
}
if (errors.length > 0) {
NotificationsManager.fromBackend(`${errors.length} guardrail${errors.length > 1 ? "s" : ""} failed`);
toast.fromError(`${errors.length} guardrail${errors.length > 1 ? "s" : ""} failed`);
}
};

View file

@ -1,6 +1,6 @@
import React, { useState } from "react";
import { Check, ChevronDown, ChevronRight, Clock, Copy } from "lucide-react";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
@ -102,9 +102,9 @@ export function GuardrailTestResults({ results, errors }: GuardrailTestResultsPr
onClick={async () => {
const success = await copyToClipboard(result.response_text);
if (success) {
NotificationsManager.success("Result copied to clipboard");
toast.success("Result copied to clipboard");
} else {
NotificationsManager.fromBackend("Failed to copy result");
toast.fromError("Failed to copy result");
}
}}
>

View file

@ -15,7 +15,7 @@ import GuardrailTable from "./guardrail_table";
import { isAdminRole } from "@/utils/roles";
import GuardrailInfoView from "./guardrail_info";
import GuardrailTestPlayground from "./GuardrailTestPlayground";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { Guardrail } from "@/components/guardrails/types";
import DeleteResourceModal from "@/components/common_components/DeleteResourceModal";
import { getGuardrailLogoAndName } from "./guardrail_info_helpers";
@ -101,11 +101,11 @@ const GuardrailsPanel: React.FC<GuardrailsPanelProps> = ({ accessToken, userRole
setIsDeleting(true);
try {
await deleteGuardrailCall(accessToken, guardrailToDelete.guardrail_id);
NotificationsManager.success(`Guardrail "${guardrailToDelete.guardrail_name}" deleted successfully`);
toast.success(`Guardrail "${guardrailToDelete.guardrail_name}" deleted successfully`);
await fetchGuardrails();
} catch (error) {
console.error("Error deleting guardrail:", error);
NotificationsManager.fromBackend("Failed to delete guardrail");
toast.fromError("Failed to delete guardrail");
} finally {
setIsDeleting(false);
setIsDeleteModalOpen(false);

View file

@ -24,7 +24,7 @@ import {
updateGuardrailCall,
type GuardrailSubmissionItem,
} from "@/components/networking";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import TeamDropdown from "@/components/common_components/team_dropdown";
import { useRegisterGuardrail } from "@/app/(dashboard)/hooks/guardrails/useRegisterGuardrail";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
@ -814,9 +814,9 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
litellm_params: { forward_api_key: newValue },
});
setGuardrails((prev) => prev.map((x) => (x.id === id ? { ...x, forwardKey: newValue } : x)));
NotificationsManager.success(newValue ? "Forward API key enabled" : "Forward API key disabled");
toast.success(newValue ? "Forward API key enabled" : "Forward API key disabled");
} catch {
NotificationsManager.fromBackend("Failed to update forward API key");
toast.fromError("Failed to update forward API key");
}
}
@ -840,9 +840,9 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
: x,
),
);
NotificationsManager.success("Static headers updated");
toast.success("Static headers updated");
} catch {
NotificationsManager.fromBackend("Failed to update static headers");
toast.fromError("Failed to update static headers");
}
}
@ -853,9 +853,9 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
litellm_params: { extra_headers: extraHeaders },
});
setGuardrails((prev) => prev.map((x) => (x.id === id ? { ...x, extraHeaders } : x)));
NotificationsManager.success("Forward client headers updated");
toast.success("Forward client headers updated");
} catch {
NotificationsManager.fromBackend("Failed to update forward client headers");
toast.fromError("Failed to update forward client headers");
}
}
@ -866,9 +866,9 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
setConfirmAction(null);
if (selectedId === id) setSelectedId(null);
await fetchSubmissions();
NotificationsManager.success("Guardrail approved");
toast.success("Guardrail approved");
} catch {
NotificationsManager.fromBackend("Failed to approve guardrail");
toast.fromError("Failed to approve guardrail");
}
}
@ -879,9 +879,9 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
setConfirmAction(null);
if (selectedId === id) setSelectedId(null);
await fetchSubmissions();
NotificationsManager.success("Guardrail rejected");
toast.success("Guardrail rejected");
} catch {
NotificationsManager.fromBackend("Failed to reject guardrail");
toast.fromError("Failed to reject guardrail");
}
}
@ -1011,7 +1011,7 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
litellm_params,
guardrail_info: values.guardrail_info ? JSON.parse(values.guardrail_info) : undefined,
});
NotificationsManager.success("Guardrail submitted for review");
toast.success("Guardrail submitted for review");
setIsSubmitModalOpen(false);
submitForm.resetFields();
fetchSubmissions();

View file

@ -1,6 +1,6 @@
import { Form, Input, Modal, Select, Tag, Button } from "antd";
import React, { useEffect, useMemo, useState } from "react";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import {
createGuardrailCall,
getGuardrailProviderSpecificParams,
@ -214,7 +214,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
populateGuardrailProviderMap(providerParamsResp);
} catch (error) {
console.error("Error fetching guardrail data:", error);
NotificationsManager.fromBackend("Failed to load guardrail configuration");
toast.fromError("Failed to load guardrail configuration");
}
};
@ -344,7 +344,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
// Validate configuration steps
if (currentStep === 1) {
if (shouldRenderPIIConfigSettings(selectedProvider) && selectedEntities.length === 0) {
NotificationsManager.fromBackend("Please select at least one PII entity to continue");
toast.fromError("Please select at least one PII entity to continue");
return;
}
}
@ -449,7 +449,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
const hasContentFilterSelections =
selectedPatterns.length > 0 || blockedWords.length > 0 || selectedContentCategories.length > 0;
if (!hasContentFilterSelections && !hasCompetitorIntent) {
NotificationsManager.fromBackend(
toast.fromError(
"Please configure at least one content filter setting (category, pattern, keyword, or competitor intent)",
);
setLoading(false);
@ -505,7 +505,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
// For some guardrails, the config values need to be in litellm_params
guardrailData.guardrail_info = configObj;
} catch (error) {
NotificationsManager.fromBackend("Invalid JSON in configuration");
toast.fromError("Invalid JSON in configuration");
setLoading(false);
return;
}
@ -514,13 +514,13 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
if (guardrailProvider === "llm_as_a_judge") {
const criteria: JudgeCriterion[] = values.criteria || [];
if (criteria.length === 0) {
NotificationsManager.fromBackend("Add at least one evaluation criterion");
toast.fromError("Add at least one evaluation criterion");
setLoading(false);
return;
}
const weightTotal = criteria.reduce((sum, c) => sum + (Number(c?.weight) || 0), 0);
if (weightTotal !== 100) {
NotificationsManager.fromBackend(`Criterion weights must sum to 100% (currently ${weightTotal}%)`);
toast.fromError(`Criterion weights must sum to 100% (currently ${weightTotal}%)`);
setLoading(false);
return;
}
@ -536,7 +536,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
if (guardrailProvider === "tool_permission") {
if (toolPermissionConfig.rules.length === 0) {
NotificationsManager.fromBackend("Add at least one tool permission rule");
toast.fromError("Add at least one tool permission rule");
setLoading(false);
return;
}
@ -611,7 +611,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
await createGuardrailCall(accessToken, guardrailData);
NotificationsManager.success("Guardrail created successfully");
toast.success("Guardrail created successfully");
// Reset form and close modal
resetForm();
@ -619,9 +619,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
onClose();
} catch (error) {
console.error("Failed to create guardrail:", error);
NotificationsManager.fromBackend(
"Failed to create guardrail: " + (error instanceof Error ? error.message : String(error)),
);
toast.fromError("Failed to create guardrail: " + (error instanceof Error ? error.message : String(error)));
} finally {
setLoading(false);
}

View file

@ -8,10 +8,6 @@ vi.mock("@/components/networking", () => ({
getCategoryYaml: vi.fn(),
}));
vi.mock("@/components/molecules/notifications_manager", () => ({
default: { success: vi.fn(), error: vi.fn(), fromBackend: vi.fn() },
}));
const PREBUILT = [
{ name: "us_ssn", display_name: "US Social Security Number", category: "PII Patterns", description: "d" },
];

View file

@ -1,7 +1,7 @@
import React, { useRef, useState } from "react";
import { Plus, Upload } from "lucide-react";
import { validateBlockedWordsFile } from "@/components/networking";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner";
@ -119,7 +119,7 @@ const ContentFilterConfiguration: React.FC<ContentFilterConfigurationProps> = ({
const handleAddPrebuiltPattern = () => {
if (!selectedPatternName) {
NotificationsManager.error("Please select a pattern");
toast.error("Please select a pattern");
return;
}
@ -140,7 +140,7 @@ const ContentFilterConfiguration: React.FC<ContentFilterConfigurationProps> = ({
const handleAddCustomPattern = () => {
if (!customPatternName || !customPatternRegex) {
NotificationsManager.error("Please provide pattern name and regex");
toast.error("Please provide pattern name and regex");
return;
}
@ -160,7 +160,7 @@ const ContentFilterConfiguration: React.FC<ContentFilterConfigurationProps> = ({
const handleAddKeyword = () => {
if (!newKeyword) {
NotificationsManager.error("Please enter a keyword");
toast.error("Please enter a keyword");
return;
}
@ -188,14 +188,14 @@ const ContentFilterConfiguration: React.FC<ContentFilterConfigurationProps> = ({
if (onFileUpload) {
onFileUpload(content);
}
NotificationsManager.success(result.message || "File uploaded successfully");
toast.success(result.message || "File uploaded successfully");
} else {
const errorMessage = result.error || (result.errors && result.errors.join(", ")) || "Invalid file";
NotificationsManager.error(`Validation failed: ${errorMessage}`);
toast.error(`Validation failed: ${errorMessage}`);
}
}
} catch (error) {
NotificationsManager.error(`Failed to upload file: ${error}`);
toast.error(`Failed to upload file: ${error}`);
} finally {
setUploadValidating(false);
}

View file

@ -10,10 +10,6 @@ vi.mock("@/components/networking", () => ({
testCustomCodeGuardrail: vi.fn(),
}));
vi.mock("@/components/molecules/notifications_manager", () => ({
default: { success: vi.fn(), error: vi.fn(), fromBackend: vi.fn() },
}));
const mockCreate = vi.mocked(createGuardrailCall);
const mockUpdate = vi.mocked(updateGuardrailCall);
const mockTest = vi.mocked(testCustomCodeGuardrail);

View file

@ -1,7 +1,7 @@
import React, { useState, useRef, useEffect } from "react";
import { CheckCircle2, ChevronRight, Code, ExternalLink, PlayCircle, Save, Users, XCircle } from "lucide-react";
import { createGuardrailCall, updateGuardrailCall, testCustomCodeGuardrail } from "@/components/networking";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { Button } from "@/components/ui/button";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import {
@ -364,15 +364,15 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
// Save guardrail (create or update)
const handleSave = async () => {
if (!guardrailName.trim()) {
NotificationsManager.fromBackend("Please enter a guardrail name");
toast.fromError("Please enter a guardrail name");
return;
}
if (!code.trim()) {
NotificationsManager.fromBackend("Please enter custom code");
toast.fromError("Please enter custom code");
return;
}
if (!accessToken) {
NotificationsManager.fromBackend("No access token available");
toast.fromError("No access token available");
return;
}
@ -400,7 +400,7 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
}
await updateGuardrailCall(accessToken, editData.guardrail_id, updateData);
NotificationsManager.success("Custom code guardrail updated successfully");
toast.success("Custom code guardrail updated successfully");
} else {
// Create new guardrail
const guardrailData = {
@ -415,13 +415,13 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({ visible, onClose, onS
};
await createGuardrailCall(accessToken, guardrailData);
NotificationsManager.success("Custom code guardrail created successfully");
toast.success("Custom code guardrail created successfully");
}
onSuccess();
onClose();
} catch (error) {
console.error("Failed to save guardrail:", error);
NotificationsManager.fromBackend(
toast.fromError(
`Failed to ${isEditMode ? "update" : "create"} guardrail: ` +
(error instanceof Error ? error.message : String(error)),
);

View file

@ -11,7 +11,7 @@ import { Badge, Card, Grid, Tab, TabGroup, TabList, TabPanel, TabPanels, Text, T
import { Button, Divider, Form, Input, Select, Tooltip } from "antd";
import { CheckIcon, CopyIcon } from "lucide-react";
import React, { useCallback, useEffect, useState } from "react";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { Logo } from "@/components/molecules/logo/Logo";
import ContentFilterManager, { formatContentFilterDataForAPI } from "./content_filter/ContentFilterManager";
import CustomCodeModal, { EditGuardrailData } from "./custom_code/CustomCodeModal";
@ -147,7 +147,7 @@ const GuardrailInfoView: React.FC<GuardrailInfoProps> = ({ guardrailId, onClose,
setSelectedPiiActions({});
}
} catch (error) {
NotificationsManager.fromBackend("Failed to load guardrail information");
toast.fromError("Failed to load guardrail information");
console.error("Error fetching guardrail info:", error);
} finally {
setLoading(false);
@ -421,19 +421,19 @@ const GuardrailInfoView: React.FC<GuardrailInfoProps> = ({ guardrailId, onClose,
// Only proceed with update if there are actual changes
if (Object.keys(updateData).length === 0) {
NotificationsManager.info("No changes detected");
toast.info("No changes detected");
setIsEditing(false);
return;
}
await updateGuardrailCall(accessToken, guardrailId, updateData);
NotificationsManager.success("Guardrail updated successfully");
toast.success("Guardrail updated successfully");
setHasUnsavedContentFilterChanges(false);
fetchGuardrailInfo();
setIsEditing(false);
} catch (error) {
console.error("Error updating guardrail:", error);
NotificationsManager.fromBackend("Failed to update guardrail");
toast.fromError("Failed to update guardrail");
}
};

View file

@ -44,7 +44,7 @@ import MCPLogoSelector from "./MCPLogoSelector";
import EnvVarsSection from "./EnvVarsSection";
import { isAdminRole } from "@/utils/roles";
import { validateMCPServerUrl, validateMCPServerName } from "./utils";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { useMcpOAuthFlow } from "@/hooks/useMcpOAuthFlow";
import { useTestMCPConnection } from "@/hooks/useTestMCPConnection";
import mcpLogo from "../../../../../public/assets/logos/mcp_logo.png";
@ -222,7 +222,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
// which would persist it as server-level credentials on the created server row. Mirrors the
// edit form's onTokenReceived early return.
setAuthorizedIdentity(getOAuthAuthorizationIdentity(form.getFieldsValue(true)));
NotificationsManager.success(
toast.success(
"Token held for this browser session. Tools can now be previewed and configured; the token is not saved to LiteLLM.",
);
return;
@ -255,9 +255,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
// its own credential write.
setAuthorizedIdentity(getOAuthAuthorizationIdentity(form.getFieldsValue(true)));
NotificationsManager.success(
"OAuth authorization successful! Please click 'Create MCP Server' to save the configuration.",
);
toast.success("OAuth authorization successful! Please click 'Create MCP Server' to save the configuration.");
},
onBeforeRedirect: persistCreateUiState,
flowSource: "create",
@ -398,7 +396,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
dcrClient: dcrClientRef.current,
});
if (built.kind !== "ok") {
NotificationsManager.fromBackend(payloadErrorMessage(built));
toast.fromError(payloadErrorMessage(built));
return;
}
const payload = built.payload;
@ -439,14 +437,13 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
}
}
NotificationsManager.success(
isAdmin
? "MCP Server created successfully"
: {
message: "MCP Server submitted for admin review",
description: "Once an admin approves it, the server will appear in your MCP Servers list.",
},
);
if (isAdmin) {
toast.success("MCP Server created successfully");
} else {
toast.success("MCP Server submitted for admin review", {
description: "Once an admin approves it, the server will appear in your MCP Servers list.",
});
}
form.resetFields();
setCostConfig({});
clearTools();
@ -459,9 +456,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
}
} catch (error) {
const reason = error instanceof Error ? error.message : String(error);
NotificationsManager.fromBackend(
isAdmin ? `Error creating MCP Server: ${reason}` : `Error submitting MCP Server: ${reason}`,
);
toast.fromError(isAdmin ? `Error creating MCP Server: ${reason}` : `Error submitting MCP Server: ${reason}`);
} finally {
setIsLoading(false);
}

View file

@ -20,7 +20,7 @@ import {
} from "@/components/networking";
import { MCPServer, MCPSubmissionsSummary } from "@/components/mcp_tools/types";
import { FIELD_GROUPS, MCP_REQUIRED_FIELD_DEFS, SETTINGS_KEY } from "./MCPStandardsSettings";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
type MCPStatus = "active" | "pending_review" | "rejected";
@ -496,9 +496,9 @@ export function MCPSubmissionsTab({ accessToken }: MCPSubmissionsTabProps) {
setIsSavingRules(true);
try {
await updateConfigFieldSetting(accessToken, SETTINGS_KEY, requiredFields);
NotificationsManager.success("Submission rules saved");
toast.success("Submission rules saved");
} catch {
NotificationsManager.fromBackend("Failed to save submission rules");
toast.fromError("Failed to save submission rules");
} finally {
setIsSavingRules(false);
}
@ -520,9 +520,9 @@ export function MCPSubmissionsTab({ accessToken }: MCPSubmissionsTabProps) {
try {
await approveMCPServer(accessToken, serverId);
await fetchData();
NotificationsManager.success(`MCP server "${serverName}" approved`);
toast.success(`MCP server "${serverName}" approved`);
} catch {
NotificationsManager.fromBackend("Failed to approve MCP server");
toast.fromError("Failed to approve MCP server");
} finally {
setConfirmAction(null);
}
@ -533,9 +533,9 @@ export function MCPSubmissionsTab({ accessToken }: MCPSubmissionsTabProps) {
try {
await rejectMCPServer(accessToken, serverId, reviewNotes);
await fetchData();
NotificationsManager.success(`MCP server "${serverName}" rejected`);
toast.success(`MCP server "${serverName}" rejected`);
} catch {
NotificationsManager.fromBackend("Failed to reject MCP server");
toast.fromError("Failed to reject MCP server");
} finally {
setConfirmAction(null);
}

View file

@ -5,16 +5,6 @@ import { describe, expect, it, vi } from "vitest";
import { ToolTestPanel } from "./ToolTestPanel";
import { InputSchema, MCPTool } from "@/components/mcp_tools/types";
vi.mock("@/components/molecules/notifications_manager", () => ({
default: {
success: vi.fn(),
fromBackend: vi.fn(),
info: vi.fn(),
warning: vi.fn(),
error: vi.fn(),
},
}));
const buildTool = (schema: InputSchema | string): MCPTool => ({
name: "demo-tool",
description: "demo",

View file

@ -4,7 +4,7 @@ import { MCPTool, InputSchema, InputSchemaProperty } from "@/components/mcp_tool
import { resolveLogoSrc } from "@/lib/assetPaths";
import { Form, Select, Tooltip } from "antd";
import { InfoCircleOutlined } from "@ant-design/icons";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
const isPlainObject = (value: unknown): value is Record<string, any> =>
typeof value === "object" && value !== null && !Array.isArray(value);
@ -279,18 +279,18 @@ export function ToolTestPanel({
const handleCopyResult = async () => {
const success = await copyToClipboard(JSON.stringify(result, null, 2));
if (success) {
NotificationsManager.success("Result copied to clipboard");
toast.success("Result copied to clipboard");
} else {
NotificationsManager.fromBackend("Failed to copy result");
toast.fromError("Failed to copy result");
}
};
const handleCopyToolName = async () => {
const success = await copyToClipboard(tool.name);
if (success) {
NotificationsManager.success("Tool name copied to clipboard");
toast.success("Tool name copied to clipboard");
} else {
NotificationsManager.fromBackend("Failed to copy tool name");
toast.fromError("Failed to copy tool name");
}
};

View file

@ -3,7 +3,7 @@ import { Modal, Form, Input, Button, Alert, Spin, Tag, Typography } from "antd";
import { useMutation, useQuery } from "@tanstack/react-query";
import { MCPServer, MCPUserEnvVarsStatus } from "@/components/mcp_tools/types";
import { getMCPUserEnvVars, storeMCPUserEnvVars } from "@/components/networking";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
const { Text, Title } = Typography;
@ -38,12 +38,12 @@ const UserEnvVarsModal: React.FC<UserEnvVarsModalProps> = ({ server, open, acces
const saveMutation = useMutation({
mutationFn: (values: Record<string, string>) => storeMCPUserEnvVars(accessToken!, server!.server_id, values),
onSuccess: (saved) => {
NotificationsManager.success("Credentials saved");
toast.success("Credentials saved");
onSaved?.(saved);
onClose();
},
onError: (err) => {
NotificationsManager.fromBackend(`Failed to save env vars: ${err instanceof Error ? err.message : String(err)}`);
toast.fromError(`Failed to save env vars: ${err instanceof Error ? err.message : String(err)}`);
},
});

View file

@ -5,7 +5,7 @@ import userEvent from "@testing-library/user-event";
import MCPServerEdit, { EDIT_OAUTH_UI_STATE_KEY } from "./mcp_server_edit";
import { setSecureItem } from "@/utils/secureStorage";
import * as networking from "@/components/networking";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { selectAntOption } from "./testUtils";
vi.mock("@/components/networking", () => ({
@ -15,13 +15,6 @@ vi.mock("@/components/networking", () => ({
testMCPToolsListRequest: vi.fn().mockResolvedValue({ tools: [], error: null }),
}));
vi.mock("@/components/molecules/notifications_manager", () => ({
default: {
success: vi.fn(),
fromBackend: vi.fn(),
},
}));
const mockOauth: {
tokenResponse: any;
getTemporaryPayload: (() => Record<string, unknown> | null) | null;
@ -1390,10 +1383,8 @@ describe("MCPServerEdit (OAuth token persistence on save)", () => {
await waitFor(() => {
expect(networking.storeMCPOAuthUserCredential).toHaveBeenCalled();
});
expect(NotificationsManager.fromBackend).toHaveBeenCalledWith(
"MCP Server updated, but failed to persist OAuth token: write failed",
);
expect(NotificationsManager.success).not.toHaveBeenCalledWith("MCP Server updated successfully");
expect(toast.fromError).toHaveBeenCalledWith("MCP Server updated, but failed to persist OAuth token: write failed");
expect(toast.success).not.toHaveBeenCalledWith("MCP Server updated successfully");
expect(onSuccess).not.toHaveBeenCalled();
});

View file

@ -47,7 +47,7 @@ import {
normalizeToolOverrideMap,
TOOL_DISPLAY_NAME_PATTERN,
} from "./utils";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { useMcpOAuthFlow } from "@/hooks/useMcpOAuthFlow";
import { getSecureItem, setSecureItem } from "@/utils/secureStorage";
@ -221,7 +221,7 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
token_type: token.token_type,
};
setToken(mcpServer.server_id, browserHeldToken, userID);
NotificationsManager.success(
toast.success(
"Token held for this browser session. Tools can now be loaded and configured; the token is not saved to LiteLLM.",
);
return;
@ -242,9 +242,7 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
// Re-capture after writing credentials so the token is not invalidated by its own credential write.
authorizedIdentityRef.current = getOAuthAuthorizationIdentity(form.getFieldsValue(true));
NotificationsManager.success(
"OAuth authorization successful! Please click 'Update MCP Server' to save the credentials.",
);
toast.success("OAuth authorization successful! Please click 'Update MCP Server' to save the credentials.");
},
onBeforeRedirect: persistEditUiState,
flowSource: "edit",
@ -677,7 +675,7 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
([, displayName]) => displayName && !TOOL_DISPLAY_NAME_PATTERN.test(displayName),
);
if (invalidDisplayName) {
NotificationsManager.fromBackend(
toast.fromError(
`Tool display name "${invalidDisplayName[1]}" is invalid. Only letters, digits, underscores, and hyphens are allowed (no spaces).`,
);
return;
@ -777,11 +775,11 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
};
if (!stdioFields.command) {
NotificationsManager.fromBackend("Stdio configuration must include a command");
toast.fromError("Stdio configuration must include a command");
return;
}
} catch {
NotificationsManager.fromBackend("Invalid JSON in stdio configuration");
toast.fromError("Invalid JSON in stdio configuration");
return;
}
} else {
@ -798,7 +796,7 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
}, {});
}
} catch {
NotificationsManager.fromBackend("Invalid JSON in stdio env configuration");
toast.fromError("Invalid JSON in stdio env configuration");
return;
}
}
@ -808,7 +806,7 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
const parsedCommand = rawCommand ? String(rawCommand).trim() : "";
if (!parsedCommand) {
NotificationsManager.fromBackend("Stdio transport requires a command");
toast.fromError("Stdio transport requires a command");
return;
}
@ -831,7 +829,7 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
try {
tokenValidation = JSON.parse(rawTokenValidationJson);
} catch {
NotificationsManager.fromBackend("Invalid JSON in Token Validation Rules");
toast.fromError("Invalid JSON in Token Validation Rules");
return;
}
}
@ -982,18 +980,16 @@ const MCPServerEdit: React.FC<MCPServerEditProps> = ({
}
} catch (error: unknown) {
const message = error instanceof Error ? error.message : "";
NotificationsManager.fromBackend(
"MCP Server updated, but failed to persist OAuth token" + (message ? `: ${message}` : ""),
);
toast.fromError("MCP Server updated, but failed to persist OAuth token" + (message ? `: ${message}` : ""));
return;
}
}
NotificationsManager.success("MCP Server updated successfully");
toast.success("MCP Server updated successfully");
setAppMayNotMatchUpstream(false);
onSuccess(updated);
} catch (error: any) {
NotificationsManager.fromBackend("Failed to update MCP Server" + (error?.message ? `: ${error.message}` : ""));
toast.fromError("Failed to update MCP Server" + (error?.message ? `: ${error.message}` : ""));
}
};

View file

@ -19,14 +19,6 @@ vi.mock("@/components/networking", () => ({
listMCPUserEnvVarStatus: vi.fn().mockResolvedValue([]),
}));
// Mock NotificationsManager
vi.mock("@/components/molecules/notifications_manager", () => ({
default: {
success: vi.fn(),
fromBackend: vi.fn(),
},
}));
const createQueryClient = () =>
new QueryClient({
defaultOptions: {

View file

@ -20,7 +20,7 @@ import React, { useEffect, useState, useMemo, useCallback } from "react";
import { useQuery } from "@tanstack/react-query";
import { useMCPServers } from "@/app/(dashboard)/hooks/mcpServers/useMCPServers";
import { useMCPServerHealth } from "@/app/(dashboard)/hooks/mcpServers/useMCPServerHealth";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { deleteMCPServer } from "@/components/networking";
import { MCPSubmissionsTab } from "./MCPSubmissionsTab";
import { MCPToolsetsTab } from "./MCPToolsetsTab";
@ -350,7 +350,7 @@ const MCPServers: React.FC<MCPServerProps> = ({ accessToken, userRole, userID })
try {
setIsDeletingServer(true);
await deleteMCPServer(accessToken, serverIdToDelete);
NotificationsManager.success("Deleted MCP Server successfully");
toast.success("Deleted MCP Server successfully");
// If the user is currently viewing the detail page of the server they
// just deleted, return them to the All Servers list. Otherwise the
// detail view would stay mounted, fall back to an empty stub server,

View file

@ -8,7 +8,7 @@ import React, { useCallback, useMemo, useState } from "react";
import { MemoryRow, createMemory, deleteMemory, fetchMemoryList, updateMemory } from "@/components/networking";
import DeleteResourceModal from "@/components/common_components/DeleteResourceModal";
import MessageManager from "@/components/molecules/message_manager";
import { toast } from "@/lib/toast";
import { Button } from "@/components/ui/button";
import { DEBOUNCE_WAIT_MS } from "@/utils/debounceConstants";
@ -61,7 +61,7 @@ export const MemoryView: React.FC<MemoryViewProps> = ({ accessToken }) => {
// All three write endpoints share the same success/error plumbing:
// - on success: invalidate the list query so every cached page
// refetches from scratch (pagination + filter-aware).
// - on error: surface the message via `MessageManager.error`.
// - on error: surface the message via `toast.error`.
const invalidateList = useCallback(
() => queryClient.invalidateQueries({ queryKey: [MEMORY_LIST_KEY] }),
@ -74,11 +74,11 @@ export const MemoryView: React.FC<MemoryViewProps> = ({ accessToken }) => {
return createMemory(accessToken, args);
},
onSuccess: (row) => {
MessageManager.success(`Created ${row.key}`);
toast.success(`Created ${row.key}`);
invalidateList();
},
onError: (err: Error) => {
MessageManager.error(`Save failed: ${err.message}`);
toast.error(`Save failed: ${err.message}`);
},
});
@ -89,11 +89,11 @@ export const MemoryView: React.FC<MemoryViewProps> = ({ accessToken }) => {
return updateMemory(accessToken, key, payload);
},
onSuccess: (row) => {
MessageManager.success(`Updated ${row.key}`);
toast.success(`Updated ${row.key}`);
invalidateList();
},
onError: (err: Error) => {
MessageManager.error(`Save failed: ${err.message}`);
toast.error(`Save failed: ${err.message}`);
},
});
@ -103,11 +103,11 @@ export const MemoryView: React.FC<MemoryViewProps> = ({ accessToken }) => {
return deleteMemory(accessToken, key).then(() => key);
},
onSuccess: (key) => {
MessageManager.success(`Deleted ${key}`);
toast.success(`Deleted ${key}`);
invalidateList();
},
onError: (err: Error) => {
MessageManager.error(`Delete failed: ${err.message}`);
toast.error(`Delete failed: ${err.message}`);
},
});
@ -149,7 +149,7 @@ export const MemoryView: React.FC<MemoryViewProps> = ({ accessToken }) => {
try {
metadataPayload = JSON.parse(metadataText);
} catch {
MessageManager.error("Metadata must be valid JSON (or leave empty).");
toast.error("Metadata must be valid JSON (or leave empty).");
return false;
}
}

View file

@ -13,10 +13,6 @@ vi.mock("@/components/networking", () => ({
modelPatchUpdateCall: (...args: unknown[]) => mockModelPatchUpdateCall(...args),
}));
vi.mock("@/components/molecules/notifications_manager", () => ({
default: { success: vi.fn(), fromBackend: vi.fn() },
}));
vi.mock("@/components/model_dashboard/ModelSettingsModal/ModelSettingsModal", () => ({
default: function ModelSettingsModalMock({ isVisible }: { isVisible: boolean }) {
return isVisible ? <div data-testid="model-settings-modal" /> : null;

View file

@ -6,7 +6,7 @@ import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import DeleteResourceModal from "@/components/common_components/DeleteResourceModal";
import ModelSettingsModal from "@/components/model_dashboard/ModelSettingsModal/ModelSettingsModal";
import { ModelData } from "@/components/model_dashboard/types";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { modelDeleteCall, modelPatchUpdateCall } from "@/components/networking";
import { useQueryClient } from "@tanstack/react-query";
import { useDebouncedCallback } from "@tanstack/react-pacer/debouncer";
@ -213,12 +213,12 @@ const AllModelsTab = ({
try {
setDeleteLoading(true);
await modelDeleteCall(accessToken, deleteModalModelId);
NotificationsManager.success("Model deleted successfully");
toast.success("Model deleted successfully");
queryClient.invalidateQueries({ queryKey: ["models", "list"] });
refetchModels();
} catch (error) {
console.error("Error deleting model:", error);
NotificationsManager.fromBackend(error);
toast.fromError(error);
} finally {
setDeleteLoading(false);
setDeleteModalModelId(null);
@ -231,13 +231,13 @@ const AllModelsTab = ({
try {
setPausingModelId(modelId);
await modelPatchUpdateCall(accessToken, { blocked }, modelId);
NotificationsManager.success(blocked ? "Model paused" : "Model resumed");
toast.success(blocked ? "Model paused" : "Model resumed");
// invalidateQueries already schedules a refetch for active observers
// on this key — no need to also call refetchModels() (would double-fetch).
queryClient.invalidateQueries({ queryKey: ["models", "list"] });
} catch (error) {
console.error("Error toggling model pause state:", error);
NotificationsManager.fromBackend(error);
toast.fromError(error);
} finally {
setPausingModelId(null);
}

View file

@ -6,10 +6,6 @@ import { ModelData } from "@/components/model_dashboard/types";
import { AllModelsTable } from "./AllModelsTable";
vi.mock("@/components/molecules/notifications_manager", () => ({
default: { success: vi.fn(), fromBackend: vi.fn() },
}));
const makeModel = (overrides: Partial<ModelData> = {}): ModelData =>
({
model_name: "gpt-4-public",

View file

@ -7,7 +7,7 @@ import { useAutoRouters, useInvalidateAutoRouters } from "@/app/(dashboard)/hook
import { useModelDetailRouting } from "@/app/(dashboard)/models-and-endpoints/detailNavigation";
import AddAutoRouterTab from "@/components/add_model/add_auto_router_tab";
import DeleteResourceModal from "@/components/common_components/DeleteResourceModal";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { modelDeleteCall } from "@/components/networking";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
@ -53,11 +53,11 @@ export function AutoRoutersPanel({ accessToken, userRole, userID, teams, createS
setIsDeleting(true);
try {
await modelDeleteCall(accessToken, deletingRouter.id);
NotificationsManager.success(`Deleted auto router: ${deletingRouter.name}`);
toast.success(`Deleted auto router: ${deletingRouter.name}`);
setDeletingRouter(null);
await invalidateAutoRouters();
} catch (error) {
NotificationsManager.fromBackend(`Failed to delete auto router: ${error}`);
toast.fromError(`Failed to delete auto router: ${error}`);
} finally {
setIsDeleting(false);
}

View file

@ -6,7 +6,7 @@ import { useQueryClient } from "@tanstack/react-query";
import AddModelForm from "@/components/add_model/AddModelForm";
import { handleAddModelSubmit } from "@/components/add_model/handle_add_model_submit";
import { Providers, getPlaceholder, getProviderModels } from "@/components/provider_info_helpers";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { useModelCostMap } from "@/app/(dashboard)/hooks/models/useModelCostMap";
import { useCredentials } from "@/app/(dashboard)/hooks/credentials/useCredentials";
import { useTeams } from "@/app/(dashboard)/hooks/teams/useTeams";
@ -34,7 +34,7 @@ export default function AddModelPanel() {
const errorMessages =
error.errorFields?.map((field: any) => `${field.name.join(".")}: ${field.errors.join(", ")}`).join(" | ") ||
"Unknown validation error";
NotificationsManager.fromBackend(`Please fill in the following required fields: ${errorMessages}`);
toast.fromError(`Please fill in the following required fields: ${errorMessages}`);
}
};

View file

@ -6,7 +6,7 @@ import { getCallbacksCall } from "@/components/networking";
import { useUpdateRetryPolicy } from "@/app/(dashboard)/hooks/routerSettings/useUpdateRetryPolicy";
import { useModelDashboardData } from "@/app/(dashboard)/models-and-endpoints/useModelDashboardData";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
interface RetryPolicyObject {
[key: string]: { [retryPolicyKey: string]: number } | undefined;
@ -69,7 +69,7 @@ export default function ModelRetrySettingsPanel() {
{ retry_policy: globalRetryPolicy, model_group_retry_policy: modelGroupRetryPolicy },
{
onSuccess: () => {
NotificationsManager.success("Retry settings saved successfully");
toast.success("Retry settings saved successfully");
void fetchRetrySettings().then((routerSettings) => {
if (routerSettings) {
applyRetrySettings(routerSettings);
@ -77,7 +77,7 @@ export default function ModelRetrySettingsPanel() {
});
},
onError: () => {
NotificationsManager.fromBackend("Failed to save retry settings");
toast.fromError("Failed to save retry settings");
},
},
);

View file

@ -1,14 +1,10 @@
import { waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { vertexCredentialsUploadProps } from "./vertexCredentialsUpload";
vi.mock("@/components/molecules/notifications_manager", () => ({
default: { success: vi.fn(), fromBackend: vi.fn() },
}));
const makeForm = () => ({ setFieldsValue: vi.fn() });
describe("vertexCredentialsUploadProps", () => {
@ -45,7 +41,7 @@ describe("vertexCredentialsUploadProps", () => {
props.onChange?.({ file: { name: "vertex.json", status: "done" } } as never);
props.onChange?.({ file: { name: "vertex.json", status: "error" } } as never);
expect(NotificationsManager.success).toHaveBeenCalledWith("vertex.json file uploaded successfully");
expect(NotificationsManager.fromBackend).toHaveBeenCalledWith("vertex.json file upload failed.");
expect(toast.success).toHaveBeenCalledWith("vertex.json file uploaded successfully");
expect(toast.fromError).toHaveBeenCalledWith("vertex.json file upload failed.");
});
});

View file

@ -1,6 +1,6 @@
import type { ComponentProps } from "react";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import type CredentialsPanel from "@/components/model_add/CredentialsPanel";
interface VertexCredentialsForm {
@ -28,9 +28,9 @@ export function vertexCredentialsUploadProps(form: VertexCredentialsForm): Uploa
},
onChange(info) {
if (info.file.status === "done") {
NotificationsManager.success(`${info.file.name} file uploaded successfully`);
toast.success(`${info.file.name} file uploaded successfully`);
} else if (info.file.status === "error") {
NotificationsManager.fromBackend(`${info.file.name} file upload failed.`);
toast.fromError(`${info.file.name} file upload failed.`);
}
},
};

View file

@ -5,7 +5,7 @@ import { useQueryClient } from "@tanstack/react-query";
import { parseAsString, useQueryState } from "nuqs";
import React, { useState } from "react";
import DeleteResourceModal from "@/components/common_components/DeleteResourceModal";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { organizationDeleteCall } from "@/components/networking";
import { OrgCreateDialog } from "@/components/organization/org-create/OrgCreateDialog";
import OrganizationInfoView from "@/components/organization/organization_view";
@ -61,7 +61,7 @@ const OrganizationsPanel: React.FC<OrganizationsPanelProps> = ({ userRole, acces
try {
setIsDeleting(true);
await organizationDeleteCall(accessToken, orgToDelete);
NotificationsManager.success("Organization deleted successfully");
toast.success("Organization deleted successfully");
setIsDeleteModalOpen(false);
setOrgToDelete(null);

View file

@ -34,10 +34,6 @@ vi.mock("@/components/CodeBlock", () => ({
default: ({ code }: { code: string }) => <pre data-testid="code-block">{code}</pre>,
}));
vi.mock("@/components/molecules/notifications_manager", () => ({
default: { success: vi.fn(), fromBackend: vi.fn() },
}));
const StatefulPanel = ({ label }: { label: string }) => {
const [draft, setDraft] = useState("");
return <input aria-label={label} value={draft} onChange={(event) => setDraft(event.target.value)} />;

View file

@ -20,7 +20,7 @@ import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner";
import { MultiSelect } from "@/components/shared/MultiSelect";
import { useVisitedTabs } from "@/hooks/useVisitedTabs";
import CodeBlock from "@/components/CodeBlock";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import {
keyCreateCall,
modelCreateCall,
@ -240,7 +240,7 @@ export default function AgentBuilderView({
return list;
} catch (e) {
console.error(e);
NotificationsManager.fromBackend("Failed to load agents");
toast.fromError("Failed to load agents");
return [];
} finally {
setLoadingAgents(false);
@ -333,7 +333,7 @@ export default function AgentBuilderView({
const handleSaveAgent = async () => {
if (!accessToken || !draftName?.trim() || !draftUnderlyingModel) {
NotificationsManager.fromBackend("Name and underlying model are required");
toast.fromError("Name and underlying model are required");
return;
}
setSaving(true);
@ -360,7 +360,7 @@ export default function AgentBuilderView({
setSelectedId(created ? getAgentSelectionKey(created) : list[0] ? getAgentSelectionKey(list[0]) : null);
goToTab("chat");
} catch (e) {
NotificationsManager.fromBackend("Failed to save agent");
toast.fromError("Failed to save agent");
} finally {
setSaving(false);
}
@ -368,7 +368,7 @@ export default function AgentBuilderView({
const handleUpdateAgent = async () => {
if (!accessToken || !selectedAgent || !selectedAgentModelId || !draftName?.trim() || !draftUnderlyingModel) {
NotificationsManager.fromBackend("Name and underlying model are required");
toast.fromError("Name and underlying model are required");
return;
}
setSaving(true);
@ -388,13 +388,13 @@ export default function AgentBuilderView({
},
selectedAgentModelId,
);
NotificationsManager.success("Agent updated successfully");
toast.success("Agent updated successfully");
const list = await loadAgents();
const stillSelected = list.find((a) => getAgentModelId(a) === selectedAgentModelId);
const target = stillSelected ?? list[0];
setSelectedId(target ? getAgentSelectionKey(target) : null);
} catch (e) {
NotificationsManager.fromBackend("Failed to update agent");
toast.fromError("Failed to update agent");
} finally {
setSaving(false);
}
@ -412,12 +412,12 @@ export default function AgentBuilderView({
const keyValue = response?.key ?? null;
if (keyValue) {
setCreatedKeyValue(keyValue);
NotificationsManager.success("Virtual key created. Use it in the curl example below.");
toast.success("Virtual key created. Use it in the curl example below.");
} else {
NotificationsManager.fromBackend("Key created but value not returned");
toast.fromError("Key created but value not returned");
}
} catch (e) {
NotificationsManager.fromBackend("Failed to create key for agent");
toast.fromError("Failed to create key for agent");
} finally {
setCreatingKey(false);
}
@ -433,12 +433,12 @@ export default function AgentBuilderView({
setDeleting(true);
try {
await modelDeleteCall(accessToken, selectedAgentModelId);
NotificationsManager.success("Agent deleted");
toast.success("Agent deleted");
const list = await loadAgents();
const remaining = list.filter((a) => getAgentModelId(a) !== selectedAgentModelId);
setSelectedId(remaining.length > 0 ? getAgentSelectionKey(remaining[0]) : null);
} catch (e) {
NotificationsManager.fromBackend("Failed to delete agent");
toast.fromError("Failed to delete agent");
} finally {
setDeleting(false);
setConfirmingDelete(false);

View file

@ -1,6 +1,6 @@
import React, { useId, useRef } from "react";
import { Paperclip } from "lucide-react";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { CHAT_ATTACHMENT_ACCEPT, validateChatAttachment } from "./uploadValidation";
@ -29,7 +29,7 @@ const ChatImageUpload: React.FC<ChatImageUploadProps> = ({ chatUploadedImage, on
}
const result = validateChatAttachment(file);
if (!result.ok) {
NotificationsManager.error(result.error);
toast.error(result.error);
return;
}
onImageUpload(file);

View file

@ -28,7 +28,7 @@ import PolicySelector from "@/components/policies/PolicySelector";
import MCPToolArgumentsForm, { MCPToolArgumentsFormRef } from "@/components/mcp_tools/MCPToolArgumentsForm";
import { MCPServer } from "@/components/mcp_tools/types";
import { ByokCredentialModal } from "@/components/mcp_tools/ByokCredentialModal";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { callMCPTool, fetchMCPServers, fetchMCPToolsets, listMCPTools } from "@/components/networking";
import { MCPTool, MCPToolset } from "@/components/mcp_tools/types";
import TagSelector from "@/components/tag_management/TagSelector";
@ -512,7 +512,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
abortControllerRef.current.abort();
abortControllerRef.current = null;
setIsLoading(false);
NotificationsManager.info("Request cancelled");
toast.info("Request cancelled");
}
};
@ -528,7 +528,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
for (const file of files) {
const result = validateImageEditFile(file, nextCount);
if (!result.ok) {
NotificationsManager.error(result.error);
toast.error(result.error);
continue;
}
accepted.push(file);
@ -565,7 +565,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
const handleResponsesImageUpload = (file: File): void => {
const result = validateChatAttachment(file);
if (!result.ok) {
NotificationsManager.error(result.error);
toast.error(result.error);
return;
}
setResponsesUploadedImage(file);
@ -583,7 +583,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
const handleChatImageUpload = (file: File): void => {
const result = validateChatAttachment(file);
if (!result.ok) {
NotificationsManager.error(result.error);
toast.error(result.error);
return;
}
setChatUploadedImage(file);
@ -601,7 +601,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
const handleAudioUpload = (file: File): void => {
const result = validateAudioFile(file);
if (!result.ok) {
NotificationsManager.error(result.error);
toast.error(result.error);
return;
}
setUploadedAudio(file);
@ -706,19 +706,19 @@ const ChatUI: React.FC<ChatUIProps> = ({
// For image edits, require both image and prompt
if (endpointType === EndpointType.IMAGE_EDITS && uploadedImages.length === 0) {
NotificationsManager.fromBackend("Please upload at least one image for editing");
toast.fromError("Please upload at least one image for editing");
return;
}
// For audio transcriptions, require audio file
if (endpointType === EndpointType.TRANSCRIPTION && !uploadedAudio) {
NotificationsManager.fromBackend("Please upload an audio file for transcription");
toast.fromError("Please upload an audio file for transcription");
return;
}
// For A2A agents, require agent selection
if (endpointType === EndpointType.A2A_AGENTS && !selectedAgent) {
NotificationsManager.fromBackend("Please select an agent to send a message");
toast.fromError("Please select an agent to send a message");
return;
}
@ -728,11 +728,11 @@ const ChatUI: React.FC<ChatUIProps> = ({
const rawSelected =
selectedMCPServers.length === 1 && selectedMCPServers[0] !== "__all__" ? selectedMCPServers[0] : null;
if (!rawSelected) {
NotificationsManager.fromBackend("Please select an MCP server to test");
toast.fromError("Please select an MCP server to test");
return;
}
if (!selectedMCPDirectTool) {
NotificationsManager.fromBackend("Please select an MCP tool to call");
toast.fromError("Please select an MCP tool to call");
return;
}
// For toolsets, find the tool in the servers that back this toolset
@ -750,13 +750,13 @@ const ChatUI: React.FC<ChatUIProps> = ({
}
const mcpTool = searchPool.find((t: any) => t.name === selectedMCPDirectTool);
if (!mcpTool) {
NotificationsManager.fromBackend("Please wait for tool schema to load");
toast.fromError("Please wait for tool schema to load");
return;
}
try {
mcpToolArguments = (await mcpToolArgsFormRef.current?.getSubmitValues()) ?? {};
} catch (err) {
NotificationsManager.fromBackend(err instanceof Error ? err.message : "Please fill in all required parameters");
toast.fromError(err instanceof Error ? err.message : "Please fill in all required parameters");
return;
}
}
@ -775,7 +775,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
];
if (modelRequiredEndpoints.includes(endpointType as EndpointType) && !selectedModel) {
NotificationsManager.fromBackend("Please select a model before sending a request");
toast.fromError("Please select a model before sending a request");
return;
}
@ -786,7 +786,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
const effectiveApiKey = simplified ? accessToken : apiKeySource === "session" ? accessToken : apiKey;
if (!effectiveApiKey) {
NotificationsManager.fromBackend("Please provide a Virtual Key or select Current UI Session");
toast.fromError("Please provide a Virtual Key or select Current UI Session");
return;
}
@ -802,7 +802,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
try {
newUserMessage = await createMultimodalMessage(inputMessage, responsesUploadedImage);
} catch (error) {
NotificationsManager.fromBackend("Failed to process image. Please try again.");
toast.fromError("Failed to process image. Please try again.");
return;
}
}
@ -811,7 +811,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
try {
newUserMessage = await createChatMultimodalMessage(inputMessage, chatUploadedImage);
} catch (error) {
NotificationsManager.fromBackend("Failed to process image. Please try again.");
toast.fromError("Failed to process image. Please try again.");
return;
}
} else {
@ -1139,7 +1139,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
handleRemoveResponsesImage();
handleRemoveChatImage();
handleRemoveAudio();
NotificationsManager.success("Chat history cleared.");
toast.success("Chat history cleared.");
};
const onModelChange = (value: string) => {
@ -2028,7 +2028,7 @@ const ChatUI: React.FC<ChatUIProps> = ({
onToggle={() => {
codeInterpreter.toggle();
if (!codeInterpreter.enabled) {
NotificationsManager.success("Code Interpreter enabled!");
toast.success("Code Interpreter enabled!");
}
}}
/>
@ -2098,8 +2098,8 @@ const ChatUI: React.FC<ChatUIProps> = ({
size="sm"
onClick={() => {
void navigator.clipboard.writeText(generatedCode).then(
() => NotificationsManager.success("Copied to clipboard!"),
() => NotificationsManager.error("Unable to copy to clipboard"),
() => toast.success("Copied to clipboard!"),
() => toast.error("Unable to copy to clipboard"),
);
}}
>

View file

@ -1,5 +1,5 @@
import React from "react";
import MessageManager from "@/components/molecules/message_manager";
import { toast } from "@/lib/toast";
import { Code, Info, TriangleAlert } from "lucide-react";
import { Switch } from "@/components/ui/switch";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
@ -39,7 +39,7 @@ const CodeInterpreterTool: React.FC<CodeInterpreterToolProps> = ({
const handleToggle = (checked: boolean) => {
if (checked && !isOpenAI) {
MessageManager.warning("Code Interpreter is only available for OpenAI models");
toast.warning("Code Interpreter is only available for OpenAI models");
return;
}
onEnabledChange(checked);

View file

@ -1,6 +1,6 @@
import React, { useId, useRef } from "react";
import { Paperclip } from "lucide-react";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { CHAT_ATTACHMENT_ACCEPT, validateChatAttachment } from "./uploadValidation";
@ -33,7 +33,7 @@ const ResponsesImageUpload: React.FC<ResponsesImageUploadProps> = ({
}
const result = validateChatAttachment(file);
if (!result.ok) {
NotificationsManager.error(result.error);
toast.error(result.error);
return;
}
onImageUpload(file);

View file

@ -1,7 +1,7 @@
import React from "react";
import { Copy, Info } from "lucide-react";
import { EndpointType } from "@/components/chat_ui/mode_endpoint_mapping";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
@ -27,9 +27,9 @@ const SessionManagement: React.FC<SessionManagementProps> = ({
if (responsesSessionId) {
try {
await navigator.clipboard.writeText(responsesSessionId);
NotificationsManager.success("Response ID copied to clipboard!");
toast.success("Response ID copied to clipboard!");
} catch {
NotificationsManager.error("Unable to copy response ID");
toast.error("Unable to copy response ID");
}
}
};

View file

@ -1,6 +1,6 @@
"use client";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { DEBOUNCE_WAIT_MS } from "@/utils/debounceConstants";
import { Eraser, FileText, Plus, Trash2 } from "lucide-react";
import { useDebouncedValue } from "@tanstack/react-pacer/debouncer";
@ -482,7 +482,7 @@ export default function CompareUI({ accessToken, disabledPersonalKeyCreation }:
return;
}
if (!effectiveApiKey) {
NotificationsManager.fromBackend("Please provide a Virtual Key or select Current UI Session");
toast.fromError("Please provide a Virtual Key or select Current UI Session");
return;
}
const targetComparisons = comparisons;
@ -491,7 +491,7 @@ export default function CompareUI({ accessToken, disabledPersonalKeyCreation }:
}
// Validate selection based on endpoint type
if (targetComparisons.some((comparison) => !hasValidSelection(comparison, selectedEndpoint))) {
NotificationsManager.fromBackend(endpointConfig.validationMessage);
toast.fromError(endpointConfig.validationMessage);
return;
}
@ -628,7 +628,7 @@ export default function CompareUI({ accessToken, disabledPersonalKeyCreation }:
.catch((error) => {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error("CompareUI: failed to fetch response", error);
NotificationsManager.fromBackend(errorMessage);
toast.fromError(errorMessage);
setComparisons((prev) =>
prev.map((comparison) => {
if (comparison.id !== prepared.id) {

View file

@ -4,7 +4,7 @@ import { TokenUsage } from "@/components/chat_ui/ResponseMetrics";
import { buildMcpToolBlocks } from "@/components/llm_calls/mcp_tool_blocks";
import { MCPServer, MCPToolset } from "@/components/mcp_tools/types";
import { getProxyBaseUrl } from "@/components/networking";
import NotificationManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { extractPromptCacheTokens } from "@/utils/promptCacheUsage";
export async function makeAnthropicMessagesRequest(
@ -118,9 +118,7 @@ export async function makeAnthropicMessagesRequest(
} catch (error) {
if (signal?.aborted) {
} else {
NotificationManager.fromBackend(
`Error occurred while generating model response. Please try again. Error: ${error}`,
);
toast.fromError(`Error occurred while generating model response. Please try again. Error: ${error}`);
}
throw error;
}

View file

@ -1,6 +1,6 @@
import openai from "openai";
import { getProxyBaseUrl } from "@/components/networking";
import NotificationManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import type { OpenAIVoice } from "../components/chat_ui/chatConstants";
export async function makeOpenAIAudioSpeechRequest(
@ -49,7 +49,7 @@ export async function makeOpenAIAudioSpeechRequest(
} catch (error) {
if (signal?.aborted) {
} else {
NotificationManager.fromBackend(`Error occurred while generating speech. Please try again. Error: ${error}`);
toast.fromError(`Error occurred while generating speech. Please try again. Error: ${error}`);
}
throw error; // Re-throw to allow the caller to handle the error
}

View file

@ -1,6 +1,6 @@
import openai from "openai";
import { getProxyBaseUrl } from "@/components/networking";
import NotificationManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
export async function makeOpenAIAudioTranscriptionRequest(
audioFile: File,
@ -45,7 +45,7 @@ export async function makeOpenAIAudioTranscriptionRequest(
// The response is a transcription object with a text field
if (response && response.text) {
updateUI(response.text, selectedModel);
NotificationManager.success(`Audio transcribed successfully`);
toast.success(`Audio transcribed successfully`);
} else {
throw new Error("No transcription text in response");
}
@ -62,7 +62,7 @@ export async function makeOpenAIAudioTranscriptionRequest(
errorMessage = error.message;
}
NotificationManager.fromBackend(`Audio transcription failed: ${errorMessage}`);
toast.fromError(`Audio transcription failed: ${errorMessage}`);
}
throw error; // Re-throw to allow the caller to handle the error
}

View file

@ -1,4 +1,4 @@
import NotificationManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { getProxyBaseUrl, getGlobalLitellmHeaderName } from "@/components/networking";
export async function makeOpenAIEmbeddingsRequest(
@ -57,9 +57,7 @@ export async function makeOpenAIEmbeddingsRequest(
updateEmbeddingsUI(JSON.stringify(embedding), responseData?.model ?? selectedModel);
} catch (error: unknown) {
NotificationManager.fromBackend(
`Error occurred while making embeddings request. Please try again. Error: ${error}`,
);
toast.fromError(`Error occurred while making embeddings request. Please try again. Error: ${error}`);
throw error; // Re-throw to allow the caller to handle the error
}

View file

@ -1,6 +1,6 @@
import openai from "openai";
import { getProxyBaseUrl } from "@/components/networking";
import NotificationManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
export async function makeOpenAIImageEditsRequest(
imageFiles: File | File[],
@ -63,7 +63,7 @@ export async function makeOpenAIImageEditsRequest(
}
if (results.length > 1) {
NotificationManager.success(`Successfully processed ${results.length} images`);
toast.success(`Successfully processed ${results.length} images`);
}
} catch (error: any) {
console.error("Error making image edit request:", error);
@ -78,7 +78,7 @@ export async function makeOpenAIImageEditsRequest(
errorMessage = error.message;
}
NotificationManager.fromBackend(`Image edit failed: ${errorMessage}`);
toast.fromError(`Image edit failed: ${errorMessage}`);
}
throw error; // Re-throw to allow the caller to handle the error
}

View file

@ -1,6 +1,6 @@
import openai from "openai";
import { getProxyBaseUrl } from "@/components/networking";
import NotificationManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
export async function makeOpenAIImageGenerationRequest(
prompt: string,
@ -51,7 +51,7 @@ export async function makeOpenAIImageGenerationRequest(
} catch (error) {
if (signal?.aborted) {
} else {
NotificationManager.fromBackend(`Error occurred while generating image. Please try again. Error: ${error}`);
toast.fromError(`Error occurred while generating image. Please try again. Error: ${error}`);
}
throw error; // Re-throw to allow the caller to handle the error
}

View file

@ -1,4 +1,4 @@
import NotificationManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { getGlobalLitellmHeaderName, getProxyBaseUrl } from "@/components/networking";
export async function makeInteractionsRequest(
@ -115,7 +115,7 @@ export async function makeInteractionsRequest(
if (signal?.aborted) {
throw error;
}
NotificationManager.fromBackend(`Error occurred while making Interactions API request. Error: ${error}`);
toast.fromError(`Error occurred while making Interactions API request. Error: ${error}`);
throw error;
}
}

View file

@ -9,10 +9,6 @@ import { Policy } from "@/components/policies/types";
vi.mock("@/components/networking");
vi.mock("@/components/molecules/notifications_manager", () => ({
default: { success: vi.fn(), fromBackend: vi.fn(), error: vi.fn(), info: vi.fn() },
}));
vi.mock("./impact_preview_alert", () => ({
default: ({ impactResult }: { impactResult: any }) =>
React.createElement("div", { "data-testid": "impact-preview" }, `${impactResult.affected_keys_count} keys`),

View file

@ -3,7 +3,7 @@ import { Modal, Form, Select, Radio, Divider, Typography } from "antd";
import { Button } from "@tremor/react";
import { Policy } from "@/components/policies/types";
import { teamListCall, keyListCall, modelAvailableCall, estimateAttachmentImpactCall } from "@/components/networking";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import { buildAttachmentData } from "./build_attachment_data";
import { getInvalidTeamEntries } from "./scope_validation";
@ -162,11 +162,11 @@ const AddAttachmentForm: React.FC<AddAttachmentFormProps> = ({
const failed = results.filter((r) => r.status === "rejected") as PromiseRejectedResult[];
if (successCount > 0 && failed.length === 0) {
NotificationsManager.success(
toast.success(
successCount === 1 ? "Attachment created successfully" : `${successCount} attachments created successfully`,
);
} else if (successCount > 0 && failed.length > 0) {
NotificationsManager.fromBackend(`${successCount} attachments created, ${failed.length} failed`);
toast.fromError(`${successCount} attachments created, ${failed.length} failed`);
} else {
throw new Error(failed[0]?.reason instanceof Error ? failed[0].reason.message : "Failed to create attachments");
}
@ -176,9 +176,7 @@ const AddAttachmentForm: React.FC<AddAttachmentFormProps> = ({
onClose();
} catch (error) {
console.error("Failed to create attachment:", error);
NotificationsManager.fromBackend(
"Failed to create attachment: " + (error instanceof Error ? error.message : String(error)),
);
toast.fromError("Failed to create attachment: " + (error instanceof Error ? error.message : String(error)));
} finally {
setIsSubmitting(false);
}

View file

@ -4,7 +4,7 @@ import { Button, TextInput, Textarea } from "@tremor/react";
import { Policy, PolicyCreateRequest, PolicyUpdateRequest } from "@/components/policies/types";
import { Guardrail } from "@/components/guardrails/types";
import { getResolvedGuardrails, modelAvailableCall } from "@/components/networking";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
const { Text } = Typography;
@ -322,10 +322,10 @@ const AddPolicyForm: React.FC<AddPolicyFormProps> = ({
if (isEditing && editingPolicy) {
await updatePolicy(accessToken, editingPolicy.policy_id, data as PolicyUpdateRequest);
NotificationsManager.success("Policy updated successfully");
toast.success("Policy updated successfully");
} else {
await createPolicy(accessToken, data as PolicyCreateRequest);
NotificationsManager.success("Policy created successfully");
toast.success("Policy created successfully");
}
resetForm();
@ -333,9 +333,7 @@ const AddPolicyForm: React.FC<AddPolicyFormProps> = ({
onClose();
} catch (error) {
console.error("Failed to save policy:", error);
NotificationsManager.fromBackend(
"Failed to save policy: " + (error instanceof Error ? error.message : String(error)),
);
toast.fromError("Failed to save policy: " + (error instanceof Error ? error.message : String(error)));
} finally {
setIsSubmitting(false);
}

View file

@ -3,7 +3,7 @@ import { Alert, AlertDescription, AlertTitle, AlertAction } from "@/components/s
import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import MessageManager from "@/components/molecules/message_manager";
import { toast } from "@/lib/toast";
import { Info, TriangleAlert, X } from "lucide-react";
import { isAdminRole } from "@/utils/roles";
import PolicyTable from "./PolicyTable";
@ -126,7 +126,7 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
setPoliciesList(response.policies || []);
} catch (error) {
console.error("Error fetching policies:", error);
MessageManager.error("Failed to fetch policies");
toast.error("Failed to fetch policies");
} finally {
setIsLoading(false);
}
@ -141,7 +141,7 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
setAttachmentsList(response.attachments || []);
} catch (error) {
console.error("Error fetching attachments:", error);
MessageManager.error("Failed to fetch attachments");
toast.error("Failed to fetch attachments");
} finally {
setIsAttachmentsLoading(false);
}
@ -194,11 +194,11 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
setIsDeleting(true);
try {
await deletePolicyCall(accessToken, policyToDelete.policy_id);
MessageManager.success(`Policy "${policyToDelete.policy_name}" deleted successfully`);
toast.success(`Policy "${policyToDelete.policy_name}" deleted successfully`);
await fetchPolicies();
} catch (error) {
console.error("Error deleting policy:", error);
MessageManager.error("Failed to delete policy");
toast.error("Failed to delete policy");
} finally {
setIsDeleting(false);
setIsDeleteModalOpen(false);
@ -243,7 +243,7 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
const handleUseTemplate = async (template: any) => {
if (!accessToken) {
MessageManager.error("Authentication required");
toast.error("Authentication required");
return;
}
@ -271,7 +271,7 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
setIsGuardrailSelectionModalOpen(true);
} catch (error) {
console.error("Error fetching guardrails:", error);
MessageManager.error("Failed to load guardrails. Please try again.");
toast.error("Failed to load guardrails. Please try again.");
}
};
@ -321,7 +321,7 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
await proceedWithTemplate(enrichedTemplate);
} catch (error) {
console.error("Error enriching template:", error);
MessageManager.error("Failed to configure template. Please try again.");
toast.error("Failed to configure template. Please try again.");
setIsEnrichingTemplate(false);
}
};
@ -367,15 +367,15 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
// Show success message
if (createdGuardrails.length > 0) {
MessageManager.success(
toast.success(
`Created ${createdGuardrails.length} guardrail${createdGuardrails.length > 1 ? "s" : ""}! Complete the policy form to save.`,
);
} else {
MessageManager.success("Template ready! Complete the policy form to save.");
toast.success("Template ready! Complete the policy form to save.");
}
if (failedGuardrails.length > 0) {
MessageManager.warning(
toast.warning(
`Failed to create ${failedGuardrails.length} guardrail(s): ${failedGuardrails.join(", ")}. You may need to create them manually.`,
);
}
@ -395,7 +395,7 @@ const PoliciesPanel: React.FC<PoliciesPanelProps> = ({ accessToken, userRole })
setTemplateQueue([]);
setTemplateQueueProgress(null);
console.error("Error creating guardrails:", error);
MessageManager.error("Failed to create guardrails. Please try again.");
toast.error("Failed to create guardrails. Please try again.");
}
};

View file

@ -1,5 +1,5 @@
import React, { useState } from "react";
import MessageManager from "@/components/molecules/message_manager";
import { toast } from "@/lib/toast";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
@ -21,7 +21,6 @@ import {
createPolicyVersion,
updatePolicyVersionStatus,
} from "@/components/networking";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { getComplianceDatasetPrompts, getFrameworks } from "@/data/compliancePrompts";
import type { CompliancePrompt } from "@/data/compliancePrompts";
@ -1412,14 +1411,12 @@ export const FlowBuilderPage: React.FC<FlowBuilderPageProps> = ({
setIsCreatingVersion(true);
try {
const newPolicy = await createPolicyVersion(accessToken, editingPolicy.policy_name);
NotificationsManager.success("New draft version created");
toast.success("New draft version created");
onVersionCreated?.(newPolicy);
const list = await listPolicyVersions(accessToken, editingPolicy.policy_name);
setVersions(list.versions ?? []);
} catch (error) {
NotificationsManager.fromBackend(
"Failed to create version: " + (error instanceof Error ? error.message : String(error)),
);
toast.fromError("Failed to create version: " + (error instanceof Error ? error.message : String(error)));
} finally {
setIsCreatingVersion(false);
}
@ -1434,16 +1431,14 @@ export const FlowBuilderPage: React.FC<FlowBuilderPageProps> = ({
setIsUpdatingStatus(true);
try {
const updated = await updatePolicyVersionStatus(accessToken, editingPolicy.policy_id, "published");
NotificationsManager.success(
toast.success(
"Version published. You can test it in the Playground by selecting this version in the Policies dropdown.",
);
const list = await listPolicyVersions(accessToken, editingPolicy.policy_name ?? "");
setVersions(list.versions ?? []);
onVersionStatusUpdated?.(updated);
} catch (error) {
NotificationsManager.fromBackend(
"Failed to publish: " + (error instanceof Error ? error.message : String(error)),
);
toast.fromError("Failed to publish: " + (error instanceof Error ? error.message : String(error)));
} finally {
setIsUpdatingStatus(false);
}
@ -1454,14 +1449,12 @@ export const FlowBuilderPage: React.FC<FlowBuilderPageProps> = ({
setIsUpdatingStatus(true);
try {
const updated = await updatePolicyVersionStatus(accessToken, editingPolicy.policy_id, "production");
NotificationsManager.success("Version promoted to production");
toast.success("Version promoted to production");
const list = await listPolicyVersions(accessToken, editingPolicy.policy_name ?? "");
setVersions(list.versions ?? []);
onVersionStatusUpdated?.(updated);
} catch (error) {
NotificationsManager.fromBackend(
"Failed to promote to production: " + (error instanceof Error ? error.message : String(error)),
);
toast.fromError("Failed to promote to production: " + (error instanceof Error ? error.message : String(error)));
} finally {
setIsUpdatingStatus(false);
}
@ -1469,17 +1462,17 @@ export const FlowBuilderPage: React.FC<FlowBuilderPageProps> = ({
const handleSave = async () => {
if (!policyName.trim()) {
MessageManager.error("Please enter a policy name");
toast.error("Please enter a policy name");
return;
}
if (!accessToken) {
MessageManager.error("No access token available");
toast.error("No access token available");
return;
}
const emptySteps = pipeline.steps.filter((s) => !s.guardrail);
if (emptySteps.length > 0) {
MessageManager.error("Please select a guardrail for all steps");
toast.error("Please select a guardrail for all steps");
return;
}
@ -1497,19 +1490,17 @@ export const FlowBuilderPage: React.FC<FlowBuilderPageProps> = ({
if (isEditing && editingPolicy) {
await updatePolicy(accessToken, editingPolicy.policy_id, data as PolicyUpdateRequest);
NotificationsManager.success("Policy updated successfully");
toast.success("Policy updated successfully");
onSuccess();
} else {
await createPolicy(accessToken, data as PolicyCreateRequest);
NotificationsManager.success("Policy created successfully");
toast.success("Policy created successfully");
onSuccess();
onBack();
}
} catch (error) {
console.error("Failed to save policy:", error);
NotificationsManager.fromBackend(
"Failed to save policy: " + (error instanceof Error ? error.message : String(error)),
);
toast.fromError("Failed to save policy: " + (error instanceof Error ? error.message : String(error)));
} finally {
setIsSubmitting(false);
}

View file

@ -4,7 +4,7 @@ import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox";
import { Skeleton } from "@/components/ui/skeleton";
import MessageManager from "@/components/molecules/message_manager";
import { toast } from "@/lib/toast";
import { ShieldCheck, ShieldAlert, FlaskConical, CircleDollarSign, CheckCircle2 } from "lucide-react";
import { getPolicyTemplates } from "@/components/networking";
@ -159,7 +159,7 @@ const PolicyTemplates: React.FC<PolicyTemplatesProps> = ({
onTemplatesLoaded?.(data);
} catch (error) {
console.error("Error fetching policy templates:", error);
MessageManager.error("Failed to fetch policy templates");
toast.error("Failed to fetch policy templates");
} finally {
setIsLoading(false);
}

View file

@ -1,6 +1,6 @@
import { Modal, Form, Button, Typography } from "antd";
import { FolderAddOutlined } from "@ant-design/icons";
import MessageManager from "@/components/molecules/message_manager";
import { toast } from "@/lib/toast";
import { useCreateProject, ProjectCreateParams } from "@/app/(dashboard)/hooks/projects/useCreateProject";
import { ProjectBaseForm, ProjectFormValues } from "./ProjectBaseForm";
import { buildProjectApiParams } from "./projectFormUtils";
@ -24,12 +24,12 @@ export function CreateProjectModal({ isOpen, onClose }: CreateProjectModalProps)
createMutation.mutate(params, {
onSuccess: () => {
MessageManager.success("Project created successfully");
toast.success("Project created successfully");
form.resetFields();
onClose();
},
onError: (error) => {
MessageManager.error(error.message || "Failed to create project");
toast.error(error.message || "Failed to create project");
},
});
} catch (error) {

View file

@ -1,7 +1,7 @@
import { useEffect } from "react";
import { Modal, Form, Button, Typography } from "antd";
import { SaveOutlined } from "@ant-design/icons";
import MessageManager from "@/components/molecules/message_manager";
import { toast } from "@/lib/toast";
import { ProjectResponse } from "@/app/(dashboard)/hooks/projects/useProjects";
import { useUpdateProject, ProjectUpdateParams } from "@/app/(dashboard)/hooks/projects/useUpdateProject";
import { ProjectBaseForm, ProjectFormValues } from "./ProjectBaseForm";
@ -72,12 +72,12 @@ export function EditProjectModal({ isOpen, project, onClose, onSuccess }: EditPr
{ projectId: project.project_id, params },
{
onSuccess: () => {
MessageManager.success("Project updated successfully");
toast.success("Project updated successfully");
onSuccess?.();
onClose();
},
onError: (error) => {
MessageManager.error(error.message || "Failed to update project");
toast.error(error.message || "Failed to update project");
},
},
);

View file

@ -4,7 +4,7 @@ import { TextInput } from "@tremor/react";
import { UploadOutlined } from "@ant-design/icons";
import type { UploadFile, UploadProps } from "antd";
import { convertPromptFileToJson, createPromptCall } from "@/components/networking";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
const { Option } = Select;
@ -33,12 +33,12 @@ const AddPromptForm: React.FC<AddPromptFormProps> = ({ visible, onClose, accessT
const values = await form.validateFields();
if (!accessToken) {
NotificationsManager.fromBackend("Access token is required");
toast.fromError("Access token is required");
return;
}
if (promptIntegration === "dotprompt" && fileList.length === 0) {
NotificationsManager.fromBackend("Please upload a .prompt file");
toast.fromError("Please upload a .prompt file");
return;
}
@ -67,7 +67,7 @@ const AddPromptForm: React.FC<AddPromptFormProps> = ({ visible, onClose, accessT
};
} catch (conversionError) {
console.error("Error converting prompt file:", conversionError);
NotificationsManager.fromBackend("Failed to convert prompt file to JSON");
toast.fromError("Failed to convert prompt file to JSON");
setLoading(false);
return;
}
@ -76,12 +76,12 @@ const AddPromptForm: React.FC<AddPromptFormProps> = ({ visible, onClose, accessT
// Create the prompt
try {
await createPromptCall(accessToken, promptData);
NotificationsManager.success("Prompt created successfully!");
toast.success("Prompt created successfully!");
handleCancel();
onSuccess();
} catch (createError) {
console.error("Error creating prompt:", createError);
NotificationsManager.fromBackend("Failed to create prompt");
toast.fromError("Failed to create prompt");
}
} catch (error) {
console.error("Form validation error:", error);
@ -93,7 +93,7 @@ const AddPromptForm: React.FC<AddPromptFormProps> = ({ visible, onClose, accessT
const uploadProps: UploadProps = {
beforeUpload: (file) => {
if (!file.name.endsWith(".prompt")) {
NotificationsManager.fromBackend("Please upload a .prompt file");
toast.fromError("Please upload a .prompt file");
return false;
}
return false; // Prevent automatic upload

View file

@ -6,7 +6,7 @@ import PromptTable from "./PromptTable";
import PromptInfoView from "./prompt_info";
import AddPromptForm from "./add_prompt_form";
import PromptEditorView from "./prompt_editor_view";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { isProxyAdminRole } from "@/utils/roles";
import { Button } from "@/components/ui/button";
import {
@ -121,11 +121,11 @@ const PromptsPanel: React.FC<PromptsProps> = ({ accessToken, userRole }) => {
setIsDeleting(true);
try {
await deletePromptCall(accessToken, promptToDelete.id);
NotificationsManager.success(`Prompt "${promptToDelete.name}" deleted successfully`);
toast.success(`Prompt "${promptToDelete.name}" deleted successfully`);
fetchPrompts(); // Refresh the list
} catch (error) {
console.error("Error deleting prompt:", error);
NotificationsManager.fromBackend("Failed to delete prompt");
toast.fromError("Failed to delete prompt");
} finally {
setIsDeleting(false);
setPromptToDelete(null);

View file

@ -2,7 +2,7 @@ import React, { useState } from "react";
import { CodeIcon, CopyIcon } from "lucide-react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { coy } from "react-syntax-highlighter/dist/esm/styles/prism";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
@ -269,7 +269,7 @@ main();`;
variant="outline"
onClick={() => {
navigator.clipboard.writeText(generatedCode);
NotificationsManager.success("Copied to clipboard!");
toast.success("Copied to clipboard!");
}}
>
<CopyIcon />

View file

@ -1,5 +1,5 @@
import { useState, useRef, useEffect } from "react";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { TokenUsage } from "@/components/chat_ui/ResponseMetrics";
import { Message } from "./types";
import { convertToDotPrompt, extractVariables } from "../utils";
@ -37,12 +37,12 @@ export const useConversation = (prompt: any, accessToken: string | null) => {
const handleSendMessage = async () => {
if (!accessToken) {
NotificationsManager.fromBackend("Access token is required");
toast.fromError("Access token is required");
return;
}
if (extractedVariables.length > 0 && !allVariablesFilled) {
NotificationsManager.fromBackend("Please fill in all template variables");
toast.fromError("Please fill in all template variables");
return;
}
@ -198,14 +198,14 @@ export const useConversation = (prompt: any, accessToken: string | null) => {
abortController.abort();
setAbortController(null);
setIsLoading(false);
NotificationsManager.info("Request cancelled");
toast.info("Request cancelled");
}
};
const handleClearConversation = () => {
setMessages([]);
setVariablesFilled(false);
NotificationsManager.success("Chat history cleared.");
toast.success("Chat history cleared.");
};
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {

View file

@ -1,6 +1,6 @@
import React, { useState } from "react";
import ToolModal from "../tool_modal";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { createPromptCall, updatePromptCall, getPromptInfo } from "@/components/networking";
import { PromptType, PromptEditorViewProps, Tool } from "./types";
import { convertToDotPrompt, parseExistingPrompt } from "./utils";
@ -21,7 +21,7 @@ const PromptEditorView: React.FC<PromptEditorViewProps> = ({ onClose, onSuccess,
return parseExistingPrompt(initialPromptData);
} catch (error) {
console.error("Error parsing existing prompt:", error);
NotificationsManager.fromBackend("Failed to parse prompt data");
toast.fromError("Failed to parse prompt data");
}
}
return {
@ -142,7 +142,7 @@ const PromptEditorView: React.FC<PromptEditorViewProps> = ({ onClose, onSuccess,
setShowToolModal(false);
setEditingToolIndex(null);
} catch (error) {
NotificationsManager.fromBackend("Invalid JSON format");
toast.fromError("Invalid JSON format");
}
};
@ -169,10 +169,9 @@ const PromptEditorView: React.FC<PromptEditorViewProps> = ({ onClose, onSuccess,
// Store the version number or construct versioned ID for tracking
const versionNum = versionData.version || 1;
setActiveVersionId(`${versionData.prompt_id}.v${versionNum}`);
// NotificationsManager.success(`Loaded version v${versionNum}`);
} catch (error) {
console.error("Error loading version:", error);
NotificationsManager.fromBackend("Failed to load prompt version");
toast.fromError("Failed to load prompt version");
}
};
@ -186,12 +185,12 @@ const PromptEditorView: React.FC<PromptEditorViewProps> = ({ onClose, onSuccess,
const handleSave = async () => {
if (!accessToken) {
NotificationsManager.fromBackend("Access token is required");
toast.fromError("Access token is required");
return;
}
if (!prompt.name || prompt.name.trim() === "") {
NotificationsManager.fromBackend("Please enter a valid prompt name");
toast.fromError("Please enter a valid prompt name");
return;
}
@ -215,16 +214,16 @@ const PromptEditorView: React.FC<PromptEditorViewProps> = ({ onClose, onSuccess,
if (editMode && initialPromptData?.prompt_spec?.prompt_id) {
await updatePromptCall(accessToken, initialPromptData.prompt_spec.prompt_id, promptData);
NotificationsManager.success("Prompt updated successfully!");
toast.success("Prompt updated successfully!");
} else {
await createPromptCall(accessToken, promptData);
NotificationsManager.success("Prompt created successfully!");
toast.success("Prompt created successfully!");
}
onSuccess();
onClose();
} catch (error) {
console.error("Error saving prompt:", error);
NotificationsManager.fromBackend(editMode ? "Failed to update prompt" : "Failed to save prompt");
toast.fromError(editMode ? "Failed to update prompt" : "Failed to save prompt");
} finally {
setIsSaving(false);
setShowNameModal(false);

View file

@ -29,7 +29,7 @@ import {
} from "@/components/networking";
import { copyToClipboard as utilCopyToClipboard } from "@/utils/dataUtils";
import { CheckIcon, CopyIcon } from "lucide-react";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import PromptCodeSnippets from "./prompt_editor_view/PromptCodeSnippets";
import { extractModel, extractTemplateVariables, getBasePromptId, getCurrentVersion } from "./prompt_utils";
@ -77,7 +77,7 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
}
setSelectedVersion(response.prompt_spec.version || null);
} catch (error) {
NotificationsManager.fromBackend("Failed to load prompt information");
toast.fromError("Failed to load prompt information");
console.error("Error fetching prompt info:", error);
} finally {
setLoading(false);
@ -156,12 +156,12 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
setIsDeleting(true);
try {
await deletePromptCall(accessToken, basePromptId);
NotificationsManager.success(`Prompt "${basePromptId}" deleted successfully`);
toast.success(`Prompt "${basePromptId}" deleted successfully`);
onDelete?.();
onClose();
} catch (error) {
console.error("Error deleting prompt:", error);
NotificationsManager.fromBackend("Failed to delete prompt");
toast.fromError("Failed to delete prompt");
} finally {
setIsDeleting(false);
setShowDeleteConfirm(false);
@ -184,7 +184,7 @@ const PromptInfoView: React.FC<PromptInfoProps> = ({ promptId, onClose, accessTo
setPromptTemplate(response.raw_prompt_template);
setRawApiResponse(response);
} catch {
NotificationsManager.fromBackend(`Failed to load version v${versionNum}`);
toast.fromError(`Failed to load version v${versionNum}`);
}
};

View file

@ -5,7 +5,7 @@ import { Button, TextInput } from "@tremor/react";
import { Form, Input, Modal, Select, Tooltip, Typography } from "antd";
import React, { useState } from "react";
import { Logo } from "@/components/molecules/logo/Logo";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { createSearchTool, fetchAvailableSearchProviders } from "@/components/networking";
import SearchConnectionTest from "./SearchConnectionTest";
import { AvailableSearchProvider, SearchTool } from "./types";
@ -98,14 +98,14 @@ const CreateSearchTool: React.FC<CreateSearchToolProps> = ({
if (accessToken != null) {
const response = await createSearchTool(accessToken, payload);
NotificationsManager.success("Search tool created successfully");
toast.success("Search tool created successfully");
form.resetFields();
setFormValues({});
setModalVisible(false);
onCreateSuccess(response);
}
} catch (error) {
NotificationsManager.error("Error creating search tool: " + error);
toast.error("Error creating search tool: " + error);
} finally {
setIsLoading(false);
}
@ -128,7 +128,7 @@ const CreateSearchTool: React.FC<CreateSearchToolProps> = ({
// Show the modal with the fresh test
setIsTestModalVisible(true);
} catch (error) {
NotificationsManager.error("Please fill in Search Provider and API Key before testing");
toast.error("Please fill in Search Provider and API Key before testing");
}
};

View file

@ -3,7 +3,7 @@ import userEvent from "@testing-library/user-event";
import { beforeEach, describe, expect, it, vi } from "vitest";
import SearchConnectionTest from "./SearchConnectionTest";
import * as networking from "@/components/networking";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
vi.mock("@/components/networking", () => ({
testSearchToolConnection: vi.fn(),
@ -68,7 +68,7 @@ describe("SearchConnectionTest", () => {
render(<SearchConnectionTest {...defaultProps} onTestComplete={onTestComplete} />);
await waitFor(() => {
expect(NotificationsManager.success).toHaveBeenCalledWith("Connection test successful!");
expect(toast.success).toHaveBeenCalledWith("Connection test successful!");
});
expect(onTestComplete).toHaveBeenCalledTimes(1);
});

View file

@ -1,6 +1,6 @@
import { AlertTriangle, CheckCircle2, Info } from "lucide-react";
import React, { useEffect, useState } from "react";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { testSearchToolConnection } from "@/components/networking";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
@ -30,7 +30,7 @@ const SearchConnectionTest: React.FC<SearchConnectionTestProps> = ({ litellmPara
const result = await testSearchToolConnection(accessToken, litellmParams);
setTestResult(result);
if (result.status === "success") {
NotificationsManager.success("Connection test successful!");
toast.success("Connection test successful!");
}
} catch (error) {
setTestResult({

View file

@ -3,7 +3,7 @@ import userEvent from "@testing-library/user-event";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { SearchToolTester } from "./SearchToolTester";
import * as networking from "@/components/networking";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
vi.mock("@/components/networking", () => ({
searchToolQueryCall: vi.fn(),
@ -293,7 +293,7 @@ describe("SearchToolTester", () => {
const searchButton = screen.getByRole("button", { name: /search/i });
await user.click(searchButton);
await waitFor(() => {
expect(NotificationsManager.fromBackend).toHaveBeenCalledWith("Failed to query search tool");
expect(toast.fromError).toHaveBeenCalledWith("Failed to query search tool");
});
consoleSpy.mockRestore();
});
@ -360,7 +360,7 @@ describe("SearchToolTester", () => {
});
const clearButton = screen.getByRole("button", { name: /clear all/i });
await user.click(clearButton);
expect(NotificationsManager.success).toHaveBeenCalledWith("Search history cleared");
expect(toast.success).toHaveBeenCalledWith("Search history cleared");
expect(screen.queryByText("Previous Searches")).not.toBeInTheDocument();
});

View file

@ -1,8 +1,7 @@
import React, { useState } from "react";
import { ExternalLink, Search } from "lucide-react";
import MessageManager from "@/components/molecules/message_manager";
import { toast } from "@/lib/toast";
import { searchToolQueryCall } from "@/components/networking";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
@ -39,7 +38,7 @@ export const SearchToolTester: React.FC<SearchToolTesterProps> = ({ searchToolNa
const handleSearch = async () => {
if (!query.trim()) {
MessageManager.warning("Please enter a search query");
toast.warning("Please enter a search query");
return;
}
@ -61,7 +60,7 @@ export const SearchToolTester: React.FC<SearchToolTesterProps> = ({ searchToolNa
setSearchHistory((prev) => [historyEntry, ...prev]);
} catch (error) {
console.error("Error querying search tool:", error);
NotificationsManager.fromBackend("Failed to query search tool");
toast.fromError("Failed to query search tool");
} finally {
setIsLoading(false);
}
@ -74,7 +73,7 @@ export const SearchToolTester: React.FC<SearchToolTesterProps> = ({ searchToolNa
const clearHistory = () => {
setSearchHistory([]);
setExpandedResults({});
NotificationsManager.success("Search history cleared");
toast.success("Search history cleared");
};
const toggleResultExpansion = (historyIndex: number, resultIndex: number) => {

View file

@ -4,7 +4,7 @@ import { Button, Text, Title } from "@tremor/react";
import { Form, Input, Modal, Select } from "antd";
import React, { useState } from "react";
import DeleteResourceModal from "@/components/common_components/DeleteResourceModal";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import {
deleteSearchTool,
fetchAvailableSearchProviders,
@ -93,13 +93,13 @@ const SearchTools: React.FC<SearchToolsProps> = ({ accessToken, userRole, userID
setIsDeleting(true);
try {
await deleteSearchTool(accessToken, toolIdToDelete);
NotificationsManager.success("Deleted search tool successfully");
toast.success("Deleted search tool successfully");
setIsDeleteModalOpen(false);
setToolToDelete(null);
refetch();
} catch (error) {
console.error("Error deleting the search tool:", error);
NotificationsManager.error("Failed to delete search tool");
toast.error("Failed to delete search tool");
} finally {
setIsDeleting(false);
}
@ -142,14 +142,14 @@ const SearchTools: React.FC<SearchToolsProps> = ({ accessToken, userRole, userID
};
await updateSearchTool(accessToken, selectedToolId, searchToolData);
NotificationsManager.success("Search tool updated successfully");
toast.success("Search tool updated successfully");
setEditModalVisible(false);
form.resetFields();
setSelectedToolId(null);
refetch();
} catch (error) {
console.error("Failed to update search tool:", error);
NotificationsManager.error("Failed to update search tool");
toast.error("Failed to update search tool");
}
};

View file

@ -14,7 +14,7 @@ import AddPluginForm from "./add_plugin_form";
import PluginTable from "./PluginTable";
import SkillDetail from "@/components/claude_code_plugins/skill_detail";
import { isAdminRole } from "@/utils/roles";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import { Plugin, ListPluginsResponse } from "@/components/claude_code_plugins/types";
interface ClaudeCodePluginsPanelProps {
@ -66,11 +66,11 @@ const ClaudeCodePluginsPanel: React.FC<ClaudeCodePluginsPanelProps> = ({ accessT
setIsDeleting(true);
try {
await deleteClaudeCodePlugin(accessToken, pluginToDelete.name);
NotificationsManager.success(`Skill "${pluginToDelete.displayName}" deleted successfully`);
toast.success(`Skill "${pluginToDelete.displayName}" deleted successfully`);
fetchPlugins();
} catch (error) {
console.error("Error deleting skill:", error);
NotificationsManager.error("Failed to delete skill");
toast.error("Failed to delete skill");
} finally {
setIsDeleting(false);
setPluginToDelete(null);

View file

@ -4,18 +4,14 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderWithProviders } from "@/../tests/test-utils";
import AddPluginForm from "./add_plugin_form";
import { registerClaudeCodePlugin } from "@/components/networking";
import MessageManager from "@/components/molecules/message_manager";
import { toast } from "@/lib/toast";
vi.mock("@/components/networking", () => ({
registerClaudeCodePlugin: vi.fn().mockResolvedValue({ status: "success" }),
}));
vi.mock("@/components/molecules/message_manager", () => ({
default: { error: vi.fn(), success: vi.fn() },
}));
const mockRegister = vi.mocked(registerClaudeCodePlugin);
const mockMessageError = vi.mocked(MessageManager.error);
const mockMessageError = vi.mocked(toast.error);
const DEFAULT_PROPS = {
visible: true,

View file

@ -2,7 +2,7 @@ import React, { useState } from "react";
import { Modal } from "antd";
import { CircleHelp } from "lucide-react";
import { z } from "zod/v4";
import MessageManager from "@/components/molecules/message_manager";
import { toast } from "@/lib/toast";
import { registerClaudeCodePlugin } from "@/components/networking";
import { FieldGroup } from "@/components/shared/form/field";
import { FormField } from "@/components/shared/form/FormField";
@ -146,34 +146,34 @@ const AddPluginForm: React.FC<AddPluginFormProps> = ({ visible, onClose, accessT
const handleSubmit = async (values: AddPluginFormValues) => {
if (!accessToken) {
MessageManager.error("No access token available");
toast.error("No access token available");
return;
}
if (!urlPreview) {
MessageManager.error("Please enter a valid repository URL");
toast.error("Please enter a valid repository URL");
return;
}
if (!validatePluginName(values.name)) {
MessageManager.error("Skill name must be kebab-case (lowercase letters, numbers, and hyphens only)");
toast.error("Skill name must be kebab-case (lowercase letters, numbers, and hyphens only)");
return;
}
if (values.version && !isValidSemanticVersion(values.version)) {
MessageManager.error("Version must be in semantic versioning format (e.g., 1.0.0)");
toast.error("Version must be in semantic versioning format (e.g., 1.0.0)");
return;
}
if (values.authorEmail && !isValidEmail(values.authorEmail)) {
MessageManager.error("Invalid email format");
toast.error("Invalid email format");
return;
}
setIsSubmitting(true);
try {
await registerClaudeCodePlugin(accessToken, buildRegisterRequest(values, urlPreview.parsed));
MessageManager.success("Skill registered successfully");
toast.success("Skill registered successfully");
form.reset(EMPTY_VALUES);
setUrlPreview(null);
setUrlEncodesSubdir(false);
@ -181,7 +181,7 @@ const AddPluginForm: React.FC<AddPluginFormProps> = ({ visible, onClose, accessT
onClose();
} catch (error) {
console.error("Error registering skill:", error);
MessageManager.error(error instanceof Error && error.message ? error.message : "Failed to register skill");
toast.error(error instanceof Error && error.message ? error.message : "Failed to register skill");
} finally {
setIsSubmitting(false);
}

View file

@ -13,14 +13,6 @@ vi.mock("@/components/networking", () => ({
modelInfoCall: vi.fn(),
}));
vi.mock("@/components/molecules/notifications_manager", () => ({
__esModule: true,
default: {
success: vi.fn(),
fromBackend: vi.fn(),
},
}));
vi.mock("./TagTable", () => ({
__esModule: true,
default: ({ isLoading, onDelete }: { isLoading?: boolean; onDelete: (tagName: string) => void }) => (

View file

@ -6,7 +6,7 @@ import { modelInfoCall } from "@/components/networking";
import { tagCreateCall, tagListCall, tagDeleteCall } from "@/components/networking";
import { Tag } from "@/components/tag_management/types";
import TagTable from "./TagTable";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import DeleteResourceModal from "@/components/common_components/DeleteResourceModal";
import CreateTagModal from "./components/CreateTagModal";
@ -48,7 +48,7 @@ const TagManagement: React.FC<TagProps> = ({ accessToken, userID, userRole }) =>
setTags(Object.values(response));
} catch (error) {
console.error("Error fetching tags:", error);
NotificationsManager.fromBackend("Error fetching tags: " + error);
toast.fromError("Error fetching tags: " + error);
} finally {
setIsLoadingTags(false);
}
@ -73,12 +73,12 @@ const TagManagement: React.FC<TagProps> = ({ accessToken, userID, userRole }) =>
rpm_limit: formValues.rpm_limit,
budget_duration: formValues.budget_duration,
});
NotificationsManager.success("Tag created successfully");
toast.success("Tag created successfully");
setIsCreateModalVisible(false);
fetchTags();
} catch (error) {
console.error("Error creating tag:", error);
NotificationsManager.fromBackend("Error creating tag: " + error);
toast.fromError("Error creating tag: " + error);
}
};
@ -92,11 +92,11 @@ const TagManagement: React.FC<TagProps> = ({ accessToken, userID, userRole }) =>
setIsDeleting(true);
try {
await tagDeleteCall(accessToken, tagToDelete);
NotificationsManager.success("Tag deleted successfully");
toast.success("Tag deleted successfully");
fetchTags();
} catch (error) {
console.error("Error deleting tag:", error);
NotificationsManager.fromBackend("Error deleting tag: " + error);
toast.fromError("Error deleting tag: " + error);
} finally {
setIsDeleting(false);
setIsDeleteModalOpen(false);
@ -114,7 +114,7 @@ const TagManagement: React.FC<TagProps> = ({ accessToken, userID, userRole }) =>
}
} catch (error) {
console.error("Error fetching models:", error);
NotificationsManager.fromBackend("Error fetching models: " + error);
toast.fromError("Error fetching models: " + error);
}
};
fetchModels();

View file

@ -21,11 +21,6 @@ vi.mock("@/components/organisms/create_key_button", () => ({
),
}));
vi.mock("@/components/molecules/notifications_manager", () => ({
__esModule: true,
default: { success: vi.fn(), fromBackend: vi.fn() },
}));
const mockTagInfoCall = vi.mocked(tagInfoCall);
const mockTagUpdateCall = vi.mocked(tagUpdateCall);

View file

@ -8,7 +8,7 @@ import { fetchUserModels } from "@/components/organisms/create_key_button";
import { getModelDisplayName } from "@/components/key_team_helpers/fetch_available_models_team_key";
import { tagInfoCall, tagUpdateCall } from "@/components/networking";
import { Tag, TagUpdateRequest } from "@/components/tag_management/types";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import NumericalInput from "@/components/shared/numerical_input";
import BudgetDurationDropdown from "@/components/common_components/budget_duration_dropdown";
import { FieldGroup } from "@/components/shared/form/field";
@ -175,7 +175,7 @@ const TagInfoView: React.FC<TagInfoViewProps> = ({ tagId, onClose, accessToken,
}
} catch (error) {
console.error("Error fetching tag details:", error);
NotificationsManager.fromBackend("Error fetching tag details: " + error);
toast.fromError("Error fetching tag details: " + error);
}
};
@ -203,12 +203,12 @@ const TagInfoView: React.FC<TagInfoViewProps> = ({ tagId, onClose, accessToken,
rpm_limit: undefined,
budget_duration: values.budget_duration,
});
NotificationsManager.success("Tag updated successfully");
toast.success("Tag updated successfully");
setIsEditing(false);
fetchTagDetails();
} catch (error) {
console.error("Error updating tag:", error);
NotificationsManager.fromBackend("Error updating tag: " + error);
toast.fromError("Error updating tag: " + error);
}
};

View file

@ -3,22 +3,14 @@ import userEvent from "@testing-library/user-event";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import TransformRequestPanel from "./TransformRequestPanel";
import { transformRequestCall } from "@/components/networking";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
vi.mock("@/components/networking", () => ({
transformRequestCall: vi.fn(),
}));
vi.mock("@/components/molecules/notifications_manager", () => ({
default: {
success: vi.fn(),
info: vi.fn(),
fromBackend: vi.fn(),
},
}));
const transformRequestCallMock = vi.mocked(transformRequestCall);
const notify = vi.mocked(NotificationsManager);
const notify = vi.mocked(toast);
const ACCESS_TOKEN = "sk-test-token";
@ -109,7 +101,7 @@ describe("TransformRequestPanel", () => {
await user.type(textarea, "not json");
await user.click(getTransformButton());
await waitFor(() => expect(notify.fromBackend).toHaveBeenCalledWith("Invalid JSON in request body"));
await waitFor(() => expect(notify.fromError).toHaveBeenCalledWith("Invalid JSON in request body"));
expect(transformRequestCallMock).not.toHaveBeenCalled();
});
@ -120,7 +112,7 @@ describe("TransformRequestPanel", () => {
await user.click(getTransformButton());
await waitFor(() => expect(notify.fromBackend).toHaveBeenCalledWith("No access token found"));
await waitFor(() => expect(notify.fromError).toHaveBeenCalledWith("No access token found"));
expect(transformRequestCallMock).not.toHaveBeenCalled();
});
@ -133,7 +125,7 @@ describe("TransformRequestPanel", () => {
await user.click(getTransformButton());
await waitFor(() => expect(notify.fromBackend).toHaveBeenCalledWith("Failed to transform request"));
await waitFor(() => expect(notify.fromError).toHaveBeenCalledWith("Failed to transform request"));
expect(screen.getByText(/https:\/\/api\.openai\.com\/v1\/chat\/completions/)).toBeInTheDocument();
});

View file

@ -5,7 +5,7 @@ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle }
import { Textarea } from "@/components/ui/textarea";
import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner";
import { transformRequestCall } from "@/components/networking";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
interface TransformRequestPanelProps {
accessToken: string | null;
@ -69,7 +69,7 @@ ${formattedBody}
try {
requestBody = JSON.parse(originalRequestJSON);
} catch (e) {
NotificationsManager.fromBackend("Invalid JSON in request body");
toast.fromError("Invalid JSON in request body");
setIsLoading(false);
return;
}
@ -82,7 +82,7 @@ ${formattedBody}
// Make the API call using fetch
if (!accessToken) {
NotificationsManager.fromBackend("No access token found");
toast.fromError("No access token found");
setIsLoading(false);
return;
}
@ -100,17 +100,17 @@ ${formattedBody}
// Update state with the formatted curl command
setTransformedResponse(formattedCurl);
NotificationsManager.success("Request transformed successfully");
toast.success("Request transformed successfully");
} else {
// Handle the case where the API returns a different format
// Try to extract the parts from a string response if needed
const rawText = typeof data === "string" ? data : JSON.stringify(data);
setTransformedResponse(rawText);
NotificationsManager.info("Transformed request received in unexpected format");
toast.info("Transformed request received in unexpected format");
}
} catch (err) {
console.error("Error transforming request:", err);
NotificationsManager.fromBackend("Failed to transform request");
toast.fromError("Failed to transform request");
} finally {
setIsLoading(false);
}
@ -191,7 +191,7 @@ ${formattedBody}
className="absolute top-2 right-2"
onClick={() => {
navigator.clipboard.writeText(transformedResponse || "");
NotificationsManager.success("Copied to clipboard");
toast.success("Copied to clipboard");
}}
>
<Copy />

View file

@ -2,7 +2,7 @@ import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
import UIThemeSettings from "./UIThemeSettings";
@ -18,11 +18,6 @@ vi.mock("@/components/networking", () => ({
getGlobalLitellmHeaderName: () => "Authorization",
}));
vi.mock("@/components/molecules/notifications_manager", () => ({
__esModule: true,
default: { success: vi.fn(), fromBackend: vi.fn() },
}));
const LOGO_PLACEHOLDER = "https://example.com/logo.png";
const FAVICON_PLACEHOLDER = "https://example.com/favicon.ico";
@ -83,9 +78,7 @@ describe("UIThemeSettings", () => {
logo_url: "https://a.test/logo.png",
favicon_url: "https://a.test/fav.ico",
});
await waitFor(() =>
expect(NotificationsManager.success).toHaveBeenCalledWith("Theme settings updated successfully!"),
);
await waitFor(() => expect(toast.success).toHaveBeenCalledWith("Theme settings updated successfully!"));
});
it("should surface a backend failure when saving fails", async () => {
@ -97,10 +90,8 @@ describe("UIThemeSettings", () => {
await user.click(screen.getByRole("button", { name: "Save Changes" }));
await waitFor(() =>
expect(NotificationsManager.fromBackend).toHaveBeenCalledWith("Failed to update theme settings"),
);
expect(NotificationsManager.success).not.toHaveBeenCalled();
await waitFor(() => expect(toast.fromError).toHaveBeenCalledWith("Failed to update theme settings"));
expect(toast.success).not.toHaveBeenCalled();
});
it("should clear both inputs and persist nulls when resetting to default", async () => {
@ -123,6 +114,6 @@ describe("UIThemeSettings", () => {
expect(screen.getByPlaceholderText(FAVICON_PLACEHOLDER)).toHaveValue("");
expect(setLogoUrl).toHaveBeenLastCalledWith(null);
expect(setFaviconUrl).toHaveBeenLastCalledWith(null);
await waitFor(() => expect(NotificationsManager.success).toHaveBeenCalledWith("Theme settings reset to default!"));
await waitFor(() => expect(toast.success).toHaveBeenCalledWith("Theme settings reset to default!"));
});
});

View file

@ -6,7 +6,7 @@ import { Label } from "@/components/ui/label";
import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner";
import { useTheme } from "@/contexts/ThemeContext";
import { getProxyBaseUrl, getGlobalLitellmHeaderName } from "@/components/networking";
import NotificationsManager from "@/components/molecules/notifications_manager";
import { toast } from "@/lib/toast";
interface UIThemeSettingsProps {
userID: string | null;
@ -66,7 +66,7 @@ const UIThemeSettings: React.FC<UIThemeSettingsProps> = ({ userID, userRole, acc
}),
});
if (response.ok) {
NotificationsManager.success("Theme settings updated successfully!");
toast.success("Theme settings updated successfully!");
setLogoUrl(logoUrlInput || null);
setFaviconUrl(faviconUrlInput || null);
} else {
@ -74,7 +74,7 @@ const UIThemeSettings: React.FC<UIThemeSettingsProps> = ({ userID, userRole, acc
}
} catch (error) {
console.error("Error updating theme settings:", error);
NotificationsManager.fromBackend("Failed to update theme settings");
toast.fromError("Failed to update theme settings");
} finally {
setLoading(false);
}
@ -98,13 +98,13 @@ const UIThemeSettings: React.FC<UIThemeSettingsProps> = ({ userID, userRole, acc
body: JSON.stringify({ logo_url: null, favicon_url: null }),
});
if (response.ok) {
NotificationsManager.success("Theme settings reset to default!");
toast.success("Theme settings reset to default!");
} else {
throw new Error("Failed to reset");
}
} catch (error) {
console.error("Error resetting theme settings:", error);
NotificationsManager.fromBackend("Failed to reset theme settings");
toast.fromError("Failed to reset theme settings");
} finally {
setLoading(false);
}

Some files were not shown because too many files have changed in this diff Show more