From 4cbceb565b0e2cabb0187fbec36f9ae01a10df76 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Tue, 18 Aug 2026 15:29:35 -0700 Subject: [PATCH] fix(ui): show select labels on the trigger instead of raw values Base UI's Select.Value resolves an option's label only when the root carries an `items` prop or the Value has a child. `resolveSelectedLabel` in @base-ui/react/internals/resolveValueLabel.js falls through every branch to `stringifyAsLabel(value)` otherwise, and `state.items` is written only from the root's `items` prop, so the `` children rendered inside `` never populate it. A self-closing `` on a root without `items` therefore renders the raw value once something is selected. The placeholder branch still works, so the trigger looked right until the user picked an option and then showed `development` for Development, `LiteLLM_VerificationToken` for Keys, `all` for All Actions, and `24h` for Daily. Pass `items` at the 20 affected sites, using the array form the other 52 call sites already use. Where a literal option sat alongside mapped ones, build one array and map the options over it so the labels and `items` cannot drift. The record-map form is avoided deliberately: `items[value]` on an object literal reaches Object.prototype, so a dynamic value named `toString` would resolve to a function and React would throw on it. The array form matches with `.find` and has no prototype lookup, which matters where the values are user-supplied model groups, team ids and key aliases. Also replace the option lookup in CompetitorIntentConfiguration's test helper, which searched by text and clicked the last match. That match is now ambiguous because the trigger carries the label too, and the helper already flaked roughly one run in six before this change. --- .../TeamGuardrailsTab.integration.test.tsx | 13 +++++ .../_components/TeamGuardrailsTab.tsx | 2 +- .../CompetitorIntentConfiguration.test.tsx | 23 ++++++++- .../CompetitorIntentConfiguration.tsx | 3 ++ .../components/ModelRetrySettingsTab.test.tsx | 12 +++++ .../components/ModelRetrySettingsTab.tsx | 12 +++-- .../components/chat_ui/ChatUI.test.tsx | 49 +++++++++++++++++++ .../playground/components/chat_ui/ChatUI.tsx | 24 +++++++-- .../PromptCodeSnippets.test.tsx | 24 +++++++++ .../prompt_editor_view/PromptCodeSnippets.tsx | 15 ++++-- .../PromptEditorHeader.test.tsx | 21 ++++++++ .../prompt_editor_view/PromptEditorHeader.tsx | 20 ++++++-- .../PromptMessagesCard.test.tsx | 18 +++++++ .../prompt_editor_view/PromptMessagesCard.tsx | 15 ++++-- .../_components/CreateVectorStore.test.tsx | 13 +++++ .../_components/CreateVectorStore.tsx | 28 +++++------ .../UserBannerSettings.test.tsx | 11 +++++ .../UserBannerSettings/UserBannerSettings.tsx | 12 +++-- .../ToolPolicies/ToolPoliciesTable.test.tsx | 14 ++++++ .../ToolPolicies/ToolPoliciesTable.tsx | 28 +++++++++++ .../common_components/DurationSelect.test.tsx | 26 ++++++++++ .../common_components/DurationSelect.tsx | 1 + .../src/components/ui/select.test.tsx | 45 +++++++++++++++++ .../view_logs/AuditLogsTable.test.tsx | 27 ++++++++++ .../components/view_logs/AuditLogsTable.tsx | 12 +++++ .../view_logs/RequestLogsFilters.test.tsx | 10 ++++ .../view_logs/RequestLogsFilters.tsx | 15 ++++-- 27 files changed, 452 insertions(+), 41 deletions(-) create mode 100644 ui/litellm-dashboard/src/components/ui/select.test.tsx diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/TeamGuardrailsTab.integration.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/TeamGuardrailsTab.integration.test.tsx index 8eefa6dcf81..5c34eca9804 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/TeamGuardrailsTab.integration.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/TeamGuardrailsTab.integration.test.tsx @@ -145,6 +145,19 @@ describe("TeamGuardrailsTab submit payload", () => { expect(registeredPayload().litellm_params.mode).toBe("during_call"); }); + it("shows the mode by its human label on the trigger", async () => { + const user = userEvent.setup(); + await openSubmitModal(user); + + const mode = screen.getAllByRole("combobox")[1]; + expect(mode).toHaveTextContent("Pre Call"); + + await user.click(mode); + await user.click(await screen.findByRole("option", { name: "During Call" })); + + expect(screen.getAllByRole("combobox")[1]).toHaveTextContent("During Call"); + }); + it("blocks an empty submit and reports every required field", async () => { const user = userEvent.setup(); await openSubmitModal(user); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/TeamGuardrailsTab.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/TeamGuardrailsTab.tsx index 3909857cc8d..fc77562e932 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/TeamGuardrailsTab.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/TeamGuardrailsTab.tsx @@ -1090,7 +1090,7 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) { {({ id, value, onChange, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy }) => ( - onChange.mock.calls[onChange.mo const chooseOption = async (user: ReturnType, index: number, optionText: string) => { await user.click(screen.getAllByRole("combobox")[index]); - const options = await screen.findAllByText(optionText); - await user.click(options[options.length - 1]); + await user.click(await screen.findByRole("option", { name: optionText })); }; describe("CompetitorIntentConfiguration reported config", () => { @@ -175,4 +174,24 @@ describe("CompetitorIntentConfiguration reported config", () => { expect(screen.queryAllByRole("combobox")).toHaveLength(0); expect(screen.queryAllByRole("spinbutton")).toHaveLength(0); }); + + it.each([ + ["Type", "Airline (auto-load competitors from IATA)"], + ["Policy: Competitor comparison", "Refuse (block request)"], + ["Policy: Possible competitor comparison", "Reframe (suggest alternative to backend LLM)"], + ])("shows the human label on the %s trigger", (name, label) => { + render(); + + expect(screen.getByRole("combobox", { name })).toHaveTextContent(label); + }); + + it("shows the human label on the Type trigger after switching to generic", async () => { + const user = userEvent.setup(); + render(); + + await user.click(screen.getByRole("combobox", { name: "Type" })); + await user.click(await screen.findByRole("option", { name: "Generic (specify competitors manually)" })); + + expect(screen.getByRole("combobox", { name: "Type" })).toHaveTextContent("Generic (specify competitors manually)"); + }); }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/content_filter/CompetitorIntentConfiguration.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/content_filter/CompetitorIntentConfiguration.tsx index 48ef20cd35b..d77dd216c2a 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/content_filter/CompetitorIntentConfiguration.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/content_filter/CompetitorIntentConfiguration.tsx @@ -187,6 +187,7 @@ const CompetitorIntentConfiguration: React.FC Type v !== null && handlePolicyChange("competitor_comparison", v)} > @@ -281,6 +283,7 @@ const CompetitorIntentConfiguration: React.FC setSelectedModelGroup(value)} > @@ -89,10 +94,9 @@ const ModelRetrySettingsTab = ({ - Global Default - {availableModelGroups.map((group) => ( - - {group} + {scopeItems.map((item) => ( + + {item.label} ))} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.test.tsx index 9d9c5dc86ab..5d61714ee97 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.test.tsx @@ -96,6 +96,55 @@ describe("ChatUI", () => { }); }); + it("should show the SDK type by its human label rather than its wire value", async () => { + const user = userEvent.setup(); + render( + , + ); + + await waitFor(() => { + expect(screen.getByText("Test Key")).toBeInTheDocument(); + }); + + await user.click(screen.getByRole("button", { name: /get code/i })); + + const sdkTrigger = await screen.findByLabelText("SDK Type"); + expect(sdkTrigger).toHaveTextContent("OpenAI SDK"); + + await user.click(sdkTrigger); + await user.click(await screen.findByRole("option", { name: "Azure SDK" })); + + expect(await screen.findByLabelText("SDK Type")).toHaveTextContent("Azure SDK"); + }); + + it("should show the voice by its human label rather than its wire value", async () => { + render( + , + ); + + await waitFor(() => { + expect(screen.getByText("Test Key")).toBeInTheDocument(); + }); + + await selectComboboxOption("Select an endpoint", "/v1/audio/speech"); + + await waitFor(() => { + expect(screen.getByLabelText("Voice")).toHaveTextContent("Alloy - Professional and confident"); + }); + }); + it("should allow the user to select a model", async () => { render( = ({