diff --git a/ui/litellm-dashboard/src/components/CreateUserButton.test.tsx b/ui/litellm-dashboard/src/components/CreateUserButton.test.tsx
index 9555dd28e1e..6ab857e6b61 100644
--- a/ui/litellm-dashboard/src/components/CreateUserButton.test.tsx
+++ b/ui/litellm-dashboard/src/components/CreateUserButton.test.tsx
@@ -833,5 +833,70 @@ describe("CreateUserButton", () => {
});
expect(mockUserCreateCall.mock.calls[0][2]).not.toHaveProperty("model_max_budget");
});
+
+ it("clears the per-model budget rows after a user is created in standalone mode", async () => {
+ const user = userEvent.setup({ pointerEventsCheck: PointerEventsCheckLevel.Never });
+ vi.mocked(networking.modelAvailableCall).mockResolvedValue({ data: [{ id: "gpt-4" }] } as any);
+ mockUserCreateCall.mockResolvedValue({ data: { user_id: "budget-user" } });
+ mockInvitationCreateCall.mockResolvedValue({
+ id: "inv-budget",
+ user_id: "budget-user",
+ has_user_setup_sso: false,
+ } as any);
+
+ renderWithProviders(
+ ,
+ );
+ const dialog = await openStandaloneModal(user);
+
+ await user.type(within(dialog).getByLabelText(/user email/i), "budget@example.com");
+ await user.click(within(dialog).getByRole("combobox", { name: /global proxy role/i }));
+ await user.click(screen.getByText("User"));
+ await user.click(within(dialog).getByRole("button", { name: /Add Model Budget/i }));
+ await chooseSelectOption(user, within(dialog).getByPlaceholderText("Select model"), "gpt-4");
+ fireEvent.change(within(dialog).getByPlaceholderText("Max spend ($)"), { target: { value: "3" } });
+ await user.click(within(dialog).getByRole("button", { name: /invite user/i }));
+
+ await waitFor(() => {
+ expect(mockUserCreateCall).toHaveBeenCalled();
+ });
+ expect(within(dialog).queryByPlaceholderText("Max spend ($)")).not.toBeInTheDocument();
+ expect(within(dialog).getByRole("button", { name: /Add Model Budget/i, hidden: true })).toBeInTheDocument();
+ });
+
+ it("clears the per-model budget rows after a user is created in embedded mode", async () => {
+ const user = userEvent.setup({ pointerEventsCheck: PointerEventsCheckLevel.Never });
+ const onUserCreated = vi.fn();
+ vi.mocked(networking.modelAvailableCall).mockResolvedValue({ data: [{ id: "gpt-4" }] } as any);
+ mockUserCreateCall.mockResolvedValue({ data: { user_id: "embedded-budget-user" } });
+
+ renderWithProviders(
+ ,
+ );
+
+ await user.type(screen.getByLabelText(/user email/i), "embedded-budget@example.com");
+ await user.click(screen.getByRole("combobox", { name: /user role/i }));
+ await user.click(screen.getByText("User"));
+ await user.click(screen.getByRole("button", { name: /Add Model Budget/i }));
+ await chooseSelectOption(user, screen.getByPlaceholderText("Select model"), "gpt-4");
+ fireEvent.change(screen.getByPlaceholderText("Max spend ($)"), { target: { value: "3" } });
+ await user.click(screen.getByRole("button", { name: /create user/i }));
+
+ await waitFor(() => {
+ expect(mockUserCreateCall).toHaveBeenCalled();
+ });
+ expect(screen.queryByPlaceholderText("Max spend ($)")).not.toBeInTheDocument();
+ expect(screen.getByRole("button", { name: /Add Model Budget/i })).toBeInTheDocument();
+ });
});
});
diff --git a/ui/litellm-dashboard/src/components/CreateUserButton.tsx b/ui/litellm-dashboard/src/components/CreateUserButton.tsx
index 8f2ccc1a2b6..88b96e8b540 100644
--- a/ui/litellm-dashboard/src/components/CreateUserButton.tsx
+++ b/ui/litellm-dashboard/src/components/CreateUserButton.tsx
@@ -166,6 +166,7 @@ export const CreateUserButton: React.FC = ({
const [apiuser, setApiuser] = useState(false);
const [userModels, setUserModels] = useState([]);
const [modelMaxBudget, setModelMaxBudget] = useState({});
+ const [modelMaxBudgetKey, setModelMaxBudgetKey] = useState(0);
const [isPersonalKeyOpen, setIsPersonalKeyOpen] = useState(false);
const [isInvitationLinkModalVisible, setIsInvitationLinkModalVisible] = useState(false);
const [invitationLinkData, setInvitationLinkData] = useState(null);
@@ -198,11 +199,16 @@ export const CreateUserButton: React.FC = ({
fetchData();
}, []);
+ const resetModelMaxBudget = () => {
+ setModelMaxBudget({});
+ setModelMaxBudgetKey((prev) => prev + 1);
+ };
+
const handleCancel = () => {
setIsModalVisible(false);
setApiuser(false);
form.reset(defaultValues);
- setModelMaxBudget({});
+ resetModelMaxBudget();
};
const handleCreate = async (formValues: CreateUserFormValues) => {
@@ -223,7 +229,7 @@ export const CreateUserButton: React.FC = ({
if (onUserCreated && isEmbedded) {
onUserCreated(user_id);
form.reset(defaultValues);
- setModelMaxBudget({});
+ resetModelMaxBudget();
return;
}
@@ -240,7 +246,7 @@ export const CreateUserButton: React.FC = ({
toast.success("API user Created");
form.reset(defaultValues);
- setModelMaxBudget({});
+ resetModelMaxBudget();
localStorage.removeItem("userData" + userID);
} catch (error: any) {
const errorMessage = error.response?.data?.detail || error?.message || "Error creating the user";
@@ -290,6 +296,7 @@ export const CreateUserButton: React.FC = ({
const modelMaxBudgetField = (