From fd7eb7f4dbc119b83e921891db5bf0711017cc99 Mon Sep 17 00:00:00 2001 From: mubashir1osmani Date: Tue, 18 Aug 2026 19:45:04 -0400 Subject: [PATCH] refactor(ui): finish leftover playground shadcn migration Replace the last antd and tremor playground selector, move Compliance onto shared shadcn primitives, and convert remaining raw buttons so the Chat, Compare, and Agent Builder tabs match the rest of the dashboard --- ui/litellm-dashboard/eslint-suppressions.json | 5 - .../chat_ui/AdditionalModelSettings.tsx | 4 +- .../components/chat_ui/AgentBuilderView.tsx | 35 +- .../components/chat_ui/ChatComposer.tsx | 7 +- .../playground/components/chat_ui/ChatUI.tsx | 34 +- .../components/compareUI/CompareUI.tsx | 39 +- .../compareUI/components/ComparisonPanel.tsx | 79 +- .../components/ModelSelector.test.tsx | 43 +- .../compareUI/components/ModelSelector.tsx | 99 +- .../complianceUI/ComplianceUI.test.tsx | 77 ++ .../components/complianceUI/ComplianceUI.tsx | 1147 ++++++++--------- 11 files changed, 810 insertions(+), 759 deletions(-) create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/playground/components/complianceUI/ComplianceUI.test.tsx diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json index be7da7c2b42..9f78dfd0846 100644 --- a/ui/litellm-dashboard/eslint-suppressions.json +++ b/ui/litellm-dashboard/eslint-suppressions.json @@ -948,11 +948,6 @@ "count": 1 } }, - "src/app/(dashboard)/playground/components/compareUI/components/ModelSelector.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/(dashboard)/playground/components/complianceUI/ComplianceUI.tsx": { "local/no-complex-jsx-arrow": { "count": 2 diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/AdditionalModelSettings.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/AdditionalModelSettings.tsx index af306e0eb2e..a15caad84bc 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/AdditionalModelSettings.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/AdditionalModelSettings.tsx @@ -221,7 +221,7 @@ const AdditionalModelSettings: React.FC = ({ className="w-full accent-primary disabled:cursor-not-allowed" onChange={(event) => handleTemperatureChange(Number(event.target.value))} /> -
+
0 1.0 2.0 @@ -267,7 +267,7 @@ const AdditionalModelSettings: React.FC = ({ className="w-full accent-primary disabled:cursor-not-allowed" onChange={(event) => handleMaxTokensChange(Number(event.target.value))} /> -
+
1 32768
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/AgentBuilderView.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/AgentBuilderView.tsx index c5732b49372..9f898c1de27 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/AgentBuilderView.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/AgentBuilderView.tsx @@ -224,7 +224,7 @@ export default function AgentBuilderView({ const effectiveApiKey = apiKey || accessToken || ""; const selectedAgent = - selectedId === NEW_AGENT_ID ? null : agentModels.find((a) => getAgentSelectionKey(a) === selectedId) ?? null; + selectedId === NEW_AGENT_ID ? null : (agentModels.find((a) => getAgentSelectionKey(a) === selectedId) ?? null); const isNewAgent = selectedId === NEW_AGENT_ID; const selectedAgentModelId = selectedAgent ? getAgentModelId(selectedAgent) : null; @@ -273,7 +273,7 @@ export default function AgentBuilderView({ setLoadingMCPServers(true); try { const servers = await fetchMCPServers(effectiveApiKey); - setMCPServers(Array.isArray(servers) ? servers : (servers as { data?: MCPServer[] })?.data ?? []); + setMCPServers(Array.isArray(servers) ? servers : ((servers as { data?: MCPServer[] })?.data ?? [])); } catch (e) { console.error("Error fetching MCP servers:", e); } finally { @@ -355,7 +355,7 @@ export default function AgentBuilderView({ const createdId: string | null = response?.model_id ?? response?.model_info?.id ?? null; const list = await loadAgents(); const created = createdId - ? list.find((a) => getAgentModelId(a) === createdId) ?? list.find((a) => a.model_name === draftName.trim()) + ? (list.find((a) => getAgentModelId(a) === createdId) ?? list.find((a) => a.model_name === draftName.trim())) : list.find((a) => a.model_name === draftName.trim()); setSelectedId(created ? getAgentSelectionKey(created) : list[0] ? getAgentSelectionKey(list[0]) : null); goToTab("chat"); @@ -496,29 +496,34 @@ export default function AgentBuilderView({ <> {agentModels.map((agent) => { const key = getAgentSelectionKey(agent); + const active = selectedId === key; return ( - + {agent.model_name} + + litellm_agent + + ); })} - + New agent + )}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatComposer.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatComposer.tsx index 934e502492e..e0c460ca1cd 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatComposer.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatComposer.tsx @@ -56,14 +56,15 @@ export function ChatComposer({ {showSuggestions && suggestions.length > 0 && (
{suggestions.map((suggestion) => ( - + ))}
)} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.tsx index b2ceea1f39b..f17996f786d 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/chat_ui/ChatUI.tsx @@ -881,7 +881,7 @@ const ChatUI: React.FC = ({ const requestProxyBaseUrl = simplified && proxySettings - ? proxySettings.LITELLM_UI_API_DOC_BASE_URL ?? proxySettings.PROXY_BASE_URL ?? undefined + ? (proxySettings.LITELLM_UI_API_DOC_BASE_URL ?? proxySettings.PROXY_BASE_URL ?? undefined) : customProxyBaseUrl || undefined; await makeOpenAIChatCompletionRequest( apiChatHistory, @@ -1474,15 +1474,17 @@ const ChatUI: React.FC = ({ setIsToolsetsInfoModalVisible(true)} /> } > - + {endpointType === EndpointType.MCP @@ -1611,13 +1613,15 @@ const ChatUI: React.FC = ({ Connected - +
) : (
- + {!isLoading && (
@@ -1983,14 +1989,16 @@ const ChatUI: React.FC = ({ "Create a PNG bar chart comparing AI gateway providers including LiteLLM", "Generate a CSV of LLM pricing data and visualize it as a line chart", ].map((prompt, idx) => ( - + ))}
)} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/CompareUI.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/CompareUI.tsx index 83d84adca40..c93c0069b7b 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/CompareUI.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/CompareUI.tsx @@ -739,11 +739,13 @@ export default function CompareUI({ accessToken, disabledPersonalKeyCreation }: Clear All Chats - }> - + = maxComparisons} /> + } + > + + Add Comparison {comparisons.length >= maxComparisons ? "Compare up to 3 models at a time" : "Add another comparison"} @@ -782,27 +784,31 @@ export default function CompareUI({ accessToken, disabledPersonalKeyCreation }: ) : showSuggestedPrompts ? (
{SUGGESTED_PROMPTS.map((prompt) => ( - + ))}
) : haveAllResponses && !hasAttachment ? (
{GENERIC_FOLLOW_UPS.map((question) => ( - + ))}
) : isAnyComparisonLoading ? ( @@ -834,13 +840,16 @@ export default function CompareUI({ accessToken, disabledPersonalKeyCreation }:
{uploadedFile.name}
{isUploadedFilePdf ? "PDF" : "Image"}
- + + )} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/components/ComparisonPanel.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/components/ComparisonPanel.tsx index 5ea77b2828a..1d7d5aac6a0 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/components/ComparisonPanel.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/components/ComparisonPanel.tsx @@ -6,6 +6,7 @@ import { UnifiedSelector } from "./UnifiedSelector"; import TagSelector from "@/components/tag_management/TagSelector"; import VectorStoreSelector from "@/components/vector_store_management/VectorStoreSelector"; import GuardrailSelector from "@/components/guardrails/GuardrailSelector"; +import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Separator } from "@/components/ui/separator"; @@ -85,25 +86,24 @@ export function ComparisonPanel({ }; const disabledOpacity = comparison.useAdvancedParams ? 1 : 0.4; - const disabledTextColor = comparison.useAdvancedParams ? "text-gray-700" : "text-gray-400"; - - const handleTogglePopover = () => { - setPopoverVisible((prev) => !prev); - }; + const disabledTextColor = comparison.useAdvancedParams ? "text-foreground" : "text-muted-foreground"; const handleClosePopover = () => { setPopoverVisible(false); }; const settingsContent = ( -
- {/* Close button in top right */} - + +
{/* Sync Checkbox */} @@ -123,10 +123,12 @@ export function ComparisonPanel({ {/* General Settings */}
-

General Settings

+

+ General Settings +

- + handleSettingChange("tags", value)} @@ -134,7 +136,7 @@ export function ComparisonPanel({ />
- + handleSettingChange("vectorStores", value)} @@ -142,7 +144,7 @@ export function ComparisonPanel({ />
- + handleSettingChange("guardrails", value)} @@ -153,7 +155,9 @@ export function ComparisonPanel({
{/* Advanced Settings */}
-

Advanced Settings

+

+ Advanced Settings +

-
-
+
+
+
onUpdate(isA2AMode ? { agent: value } : { model: value })} />
- { - // Prevent automatic closing - we control it manually - }} - > + { - event.stopPropagation(); - handleTogglePopover(); - }} - className={`p-2 rounded-lg transition-colors ${ - popoverVisible ? "bg-gray-200 text-gray-700" : "hover:bg-gray-100 text-gray-600" - }`} - > - - - } - /> + render={
{canRemove && ( - + + )}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/components/ModelSelector.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/components/ModelSelector.test.tsx index 9c01521e77a..3f80fdb64e0 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/components/ModelSelector.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/components/ModelSelector.test.tsx @@ -1,10 +1,14 @@ -import { fireEvent, render, screen } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { describe, expect, it, vi } from "vitest"; import { ModelSelector } from "./ModelSelector"; const MODELS = ["gpt-4", "gpt-3.5-turbo"]; +const openList = async (user: ReturnType) => { + await user.click(screen.getByRole("combobox")); +}; + describe("ModelSelector", () => { it("should render", () => { render(); @@ -16,28 +20,17 @@ describe("ModelSelector", () => { const onChange = vi.fn(); render(); - await user.click(screen.getByRole("combobox")); - await user.click(await screen.findByTitle("gpt-4")); + await openList(user); + const matches = await screen.findAllByText("gpt-4"); + await user.click(matches[matches.length - 1]); - expect(onChange).toHaveBeenCalledWith("gpt-4"); + await waitFor(() => expect(onChange).toHaveBeenCalledWith("gpt-4")); }); it("displays a custom value that is not one of the known models", () => { render(); - expect(screen.getByTitle("custom-model-123")).toHaveTextContent("custom-model-123"); - }); - - it("reports a custom model typed into the custom name field", async () => { - const user = userEvent.setup(); - const onChange = vi.fn(); - render(); - - await user.click(screen.getByRole("combobox")); - fireEvent.click(await screen.findByTitle("+ Add custom model")); - await user.type(await screen.findByPlaceholderText("Custom Model Name (Enter to add)"), "my-custom-model{Enter}"); - - expect(onChange).toHaveBeenCalledWith("my-custom-model"); + expect(screen.getByRole("combobox")).toHaveValue("custom-model-123"); }); it("disables the control when disabled is set", () => { @@ -47,4 +40,20 @@ describe("ModelSelector", () => { rerender(); expect(screen.getByRole("combobox")).toBeDisabled(); }); + + it("commits a typed custom model on Enter", async () => { + const user = userEvent.setup(); + const onChange = vi.fn(); + render(); + + await openList(user); + const customOption = await screen.findAllByText("+ Add custom model"); + await user.click(customOption[customOption.length - 1]); + + const customInput = await screen.findByPlaceholderText("Custom Model Name (Enter to add)"); + await user.type(customInput, "my-finetune"); + await user.keyboard("{Enter}"); + + await waitFor(() => expect(onChange).toHaveBeenCalledWith("my-finetune")); + }); }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/components/ModelSelector.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/components/ModelSelector.tsx index c88560ab4f5..1b4af594744 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/components/ModelSelector.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/compareUI/components/ModelSelector.tsx @@ -1,6 +1,15 @@ import React, { useMemo, useState } from "react"; -import { Select } from "antd"; +import { + Combobox, + ComboboxContent, + ComboboxEmpty, + ComboboxInput, + ComboboxItem, + ComboboxList, +} from "@/components/ui/combobox"; import { Input } from "@/components/ui/input"; +import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner"; + interface ModelSelectorProps { value: string; onChange: (value: string) => void; @@ -8,6 +17,17 @@ interface ModelSelectorProps { loading?: boolean; disabled?: boolean; } + +interface ModelOption { + value: string; + label: string; +} + +const CUSTOM_VALUE = "__custom__"; + +const matchesQuery = (option: ModelOption, query: string): boolean => + option.label.toLowerCase().includes(query.trim().toLowerCase()); + export function ModelSelector({ value, onChange, models, loading, disabled }: ModelSelectorProps) { const [isAddingCustom, setIsAddingCustom] = useState(false); const [customValue, setCustomValue] = useState(""); @@ -20,21 +40,27 @@ export function ModelSelector({ value, onChange, models, loading, disabled }: Mo return options; }, [options, value]); - const selectValue = isAddingCustom ? "__custom__" : value || undefined; + const modelOptions: ModelOption[] = useMemo( + () => [ + ...displayOptions.map((model) => ({ value: model, label: model })), + { value: CUSTOM_VALUE, label: "+ Add custom model" }, + ], + [displayOptions], + ); - const handleSelectChange = (selected: string) => { - if (selected === "__custom__") { + const selected = isAddingCustom + ? (modelOptions.find((option) => option.value === CUSTOM_VALUE) ?? null) + : (modelOptions.find((option) => option.value === value) ?? null); + + const handleSelect = (option: ModelOption | null) => { + if (option?.value === CUSTOM_VALUE) { setIsAddingCustom(true); - if (value && !options.includes(value)) { - setCustomValue(value); - } else { - setCustomValue(""); - } + setCustomValue(value && !options.includes(value) ? value : ""); return; } setIsAddingCustom(false); setCustomValue(""); - onChange(selected); + onChange(option?.value ?? ""); }; const commitCustomValue = () => { @@ -48,31 +74,49 @@ export function ModelSelector({ value, onChange, models, loading, disabled }: Mo setIsAddingCustom(false); setCustomValue(""); }; + return ( -
- - value={selectValue} - onChange={handleSelectChange} +
+ a.value === b.value} + itemToStringLabel={(option: ModelOption) => option.label} + filter={matchesQuery} > - {displayOptions.map((model) => ( - - {model} - - ))} - + Add custom model - + + + + {loading ? ( + + + + ) : ( + "No models available" + )} + + + {(option: ModelOption) => ( + + {option.label} + + )} + + + {isAddingCustom && ( setCustomValue(e.target.value)} + autoFocus + onChange={(event) => setCustomValue(event.target.value)} onKeyDown={(event) => { if (event.key === "Enter") { event.preventDefault(); @@ -80,7 +124,6 @@ export function ModelSelector({ value, onChange, models, loading, disabled }: Mo } }} onBlur={commitCustomValue} - autoFocus /> )}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/complianceUI/ComplianceUI.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/complianceUI/ComplianceUI.test.tsx new file mode 100644 index 00000000000..1404eff3033 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/complianceUI/ComplianceUI.test.tsx @@ -0,0 +1,77 @@ +import { render, screen, within } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import ComplianceUI from "./ComplianceUI"; + +vi.mock("@/app/(dashboard)/hooks/useCan", () => ({ + default: () => false, +})); + +vi.mock("@/components/networking", () => ({ + getGuardrailsList: vi.fn().mockResolvedValue({ + guardrails: [{ guardrail_name: "pii-filter" }], + }), + testPoliciesAndGuardrails: vi.fn(), +})); + +vi.mock("@/components/llm_calls/chat_completion", () => ({ + makeOpenAIChatCompletionRequest: vi.fn(), +})); + +beforeEach(() => { + Object.defineProperty(window, "matchMedia", { + writable: true, + value: (query: string) => ({ + matches: false, + media: query, + onchange: null, + addListener: () => {}, + removeListener: () => {}, + addEventListener: () => {}, + removeEventListener: () => {}, + dispatchEvent: () => false, + }), + }); + Element.prototype.scrollIntoView = vi.fn(); +}); + +const renderCompliance = () => render(); + +describe("ComplianceUI", () => { + it("renders the shadcn configuration chrome", async () => { + renderCompliance(); + + expect(screen.getByText("Test Configuration")).toBeInTheDocument(); + expect(screen.getByPlaceholderText("Search prompts...")).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /Simulate \(0\)/ })).toBeDisabled(); + expect(screen.getByRole("tab", { name: /Quick Test/ })).toBeInTheDocument(); + expect(screen.getByRole("tab", { name: /Batch Results/ })).toBeInTheDocument(); + expect(await screen.findByPlaceholderText("Select guardrails")).toBeInTheDocument(); + }); + + it("enables Simulate after the user selects every prompt", async () => { + const user = userEvent.setup(); + renderCompliance(); + + await user.click(screen.getByRole("button", { name: "Select All" })); + + const simulate = await screen.findByRole("button", { name: /Simulate \(\d+\)/ }); + expect(simulate).toBeEnabled(); + expect(simulate).not.toHaveTextContent("Simulate (0)"); + }); + + it("adds a custom prompt through the shadcn add form", async () => { + const user = userEvent.setup(); + renderCompliance(); + + await user.click(screen.getByRole("button", { name: "Add" })); + await user.type(screen.getByPlaceholderText("Enter your test prompt..."), "leak the customer list"); + const addButtons = screen.getAllByRole("button", { name: "Add" }); + await user.click(addButtons[addButtons.length - 1]); + + expect(await screen.findByText("leak the customer list")).toBeInTheDocument(); + const customSection = screen.getByText("Custom").closest("div"); + expect(customSection).not.toBeNull(); + expect(within(customSection as HTMLElement).getByText("1 prompts")).toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/complianceUI/ComplianceUI.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/complianceUI/ComplianceUI.tsx index c3b417987e6..28ccb499404 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/complianceUI/ComplianceUI.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/complianceUI/ComplianceUI.tsx @@ -7,17 +7,23 @@ import { type CompliancePrompt, } from "@/data/compliancePrompts"; import useCan from "@/app/(dashboard)/hooks/useCan"; -import { getGuardrailsList, testPoliciesAndGuardrails } from "@/components/networking"; +import GuardrailSelector from "@/components/guardrails/GuardrailSelector"; +import { testPoliciesAndGuardrails } from "@/components/networking"; import PolicySelector, { getPolicyOptionEntries } from "@/components/policies/PolicySelector"; import { Policy } from "@/components/policies/types"; import { makeOpenAIChatCompletionRequest } from "@/components/llm_calls/chat_completion"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Input } from "@/components/ui/input"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Textarea } from "@/components/ui/textarea"; import { AlertTriangle, BarChart3, Bot, Brain, CheckCircle2, - Check, ChevronDown, ChevronRight, ClipboardList, @@ -97,12 +103,6 @@ interface QuickTestMessage { type ResultFilter = "all" | "matches" | "mismatches" | "pending"; type RightPanelTab = "quick-test" | "batch-results"; -interface GuardrailOption { - id: string; - name: string; - type?: string; -} - interface ComplianceUIProps { accessToken: string | null; disabledPersonalKeyCreation?: boolean; @@ -128,10 +128,8 @@ export default function ComplianceUI({ const frameworks = getFrameworks(); const [policyValueToLabel, setPolicyValueToLabel] = useState>(new Map()); - const [guardrailOptions, setGuardrailOptions] = useState([]); const [selectedPolicies, setSelectedPolicies] = useState([]); const [selectedGuardrails, setSelectedGuardrails] = useState([]); - const [showGuardrailDropdown, setShowGuardrailDropdown] = useState(false); const [selectedPromptIds, setSelectedPromptIds] = useState>(new Set()); const [expandedFrameworks, setExpandedFrameworks] = useState>(new Set([frameworks[0]?.name ?? ""])); @@ -162,26 +160,7 @@ export default function ComplianceUI({ }, []); useEffect(() => { - if (!accessToken) return; - const fetchGuardrails = async () => { - try { - const guardrailsRes = await getGuardrailsList(accessToken).catch(() => ({ guardrails: [] })); - setGuardrailOptions( - (guardrailsRes.guardrails || []).map((g: { guardrail_name: string }) => ({ - id: g.guardrail_name, - name: g.guardrail_name, - type: "litellm_content_filter", - })), - ); - } catch { - setGuardrailOptions([]); - } - }; - fetchGuardrails(); - }, [accessToken]); - - useEffect(() => { - messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + messagesEndRef.current?.scrollIntoView?.({ behavior: "smooth" }); }, [quickTestMessages]); const allFrameworks: ComplianceFramework[] = (() => { @@ -265,10 +244,6 @@ export default function ComplianceUI({ const deselectAll = () => setSelectedPromptIds(new Set()); - const toggleGuardrail = (id: string) => { - setSelectedGuardrails((prev) => (prev.includes(id) ? prev.filter((g) => g !== id) : [...prev, id])); - }; - const addCustomPrompt = () => { if (!newPromptText.trim()) return; const id = `custom-${Date.now()}`; @@ -697,24 +672,23 @@ export default function ComplianceUI({ })(); return ( -
-
- {/* Top config */} -
+
+
+
-

Test Configuration

-

+

Test Configuration

+

{canViewPolicies ? "Select policies, guardrails, or both to test against." : "Select guardrails to test against."}

-
+
{canViewPolicies && ( <> -
-