From ad4d9b1a92f27ab4e232e935461678ed7ce7e2ef Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sat, 10 Oct 2026 16:30:25 -0700 Subject: [PATCH] feat(ui): replace Reset example with an Example preset dropdown and a Clear button on the Decisions tab (#45848) Backport of #45848 to rc/1.106.0. Cherry-picked from dc7738195639c92dc01a1b5ed5ede96be980083f (main). --- .../SystemOneUI.integration.test.tsx | 87 ++++++++- .../components/systemOneUI/SystemOneUI.tsx | 58 ++++-- .../systemOneUI/lib/example.test.ts | 60 ++++++ .../components/systemOneUI/lib/example.ts | 180 +++++++++++++++--- 4 files changed, 340 insertions(+), 45 deletions(-) create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/lib/example.test.ts diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.integration.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.integration.test.tsx index ab0a67dbbab..c5c6023cf27 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.integration.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.integration.test.tsx @@ -71,6 +71,12 @@ const selectEndpoint = async (endpoint: "/v1/decisions" | "/v1/systemone") => { await userEvent.click(await screen.findByRole("option", { name: endpoint, exact: true })); }; +const selectPreset = async (label: string) => { + screen.getByRole("combobox", { name: "Example" }).focus(); + await userEvent.keyboard("{ArrowDown}"); + await userEvent.click(await screen.findByRole("option", { name: label })); +}; + const createResponse = (body: SystemOneResponse | OpenAIDecisionsResponse, status = 200, errorText = "") => ({ ok: status >= 200 && status < 300, @@ -99,20 +105,81 @@ describe("SystemOneUI integration", () => { document.cookie = "token=; path=/; max-age=0"; }); - it("restores the example after the request is edited", async () => { - const user = userEvent.setup(); + it("switches between example presets and reads Custom once the request is edited", async () => { render(); const editor = await openJsonEditor(); const example = (editor as HTMLTextAreaElement).value; - const resetButton = screen.getByRole("button", { name: "Reset example" }); - expect(resetButton).toBeDisabled(); + const picker = screen.getByRole("combobox", { name: "Example" }); + expect(picker).toHaveTextContent("Triage a bug report"); fireEvent.change(editor, { target: { value: "{}" } }); - expect(resetButton).toBeEnabled(); - await user.click(resetButton); - + expect(picker).toHaveTextContent("Custom"); + await selectPreset("Triage a bug report"); expect(editor).toHaveValue(example); - expect(resetButton).toBeDisabled(); + expect(picker).toHaveTextContent("Triage a bug report"); + + await selectPreset("Score a sales lead"); + expect(picker).toHaveTextContent("Score a sales lead"); + expect(JSON.parse((editor as HTMLTextAreaElement).value)).toMatchObject({ questions: { fit: { type: "score" } } }); + }); + + it("clears the whole request and lets a preset be picked from nothing", async () => { + const user = userEvent.setup(); + render(); + const clear = screen.getByRole("button", { name: "Clear" }); + expect(screen.getByRole("textbox", { name: "Input" })).toHaveDisplayValue(/streaming responses/); + + await user.click(clear); + expect(screen.getByRole("textbox", { name: "Input" })).toHaveValue(""); + expect(screen.queryAllByRole("group", { name: /Question / })).toHaveLength(0); + expect(screen.getByRole("button", { name: "Send" })).toBeDisabled(); + expect(clear).toBeDisabled(); + expect(screen.getByRole("combobox", { name: "Example" })).toHaveTextContent("Custom"); + + await selectPreset("Route a support ticket"); + expect(screen.getByRole("textbox", { name: "Input" })).toHaveDisplayValue(/charged twice/); + expect(screen.getAllByRole("group", { name: /Question / })).toHaveLength(1); + expect(screen.getByRole("button", { name: "Send" })).toBeEnabled(); + }); + + it("keeps the preset label after picking a model and leaves an omitted model out when clearing", async () => { + mockModelLookup.mockResolvedValue(modelGroupInfoResponse(DECISION_AND_CHAT_MODELS)); + render(); + await waitFor(() => expect(screen.getByRole("combobox", { name: "Decision model" })).toHaveValue("jev-latest")); + const picker = screen.getByRole("combobox", { name: "Example" }); + const editor = await openJsonEditor(); + const example = JSON.parse((editor as HTMLTextAreaElement).value) as Record; + const { model: _model, ...withoutModel } = example; + + fireEvent.change(editor, { + target: { value: JSON.stringify({ ...withoutModel, model: "pplx-decider" }, null, 2) }, + }); + expect(picker).toHaveTextContent("Triage a bug report"); + + fireEvent.change(editor, { target: { value: JSON.stringify(withoutModel, null, 2) } }); + expect(picker).toHaveTextContent("Triage a bug report"); + await userEvent.click(screen.getByRole("button", { name: "Clear" })); + expect(JSON.parse((editor as HTMLTextAreaElement).value)).toEqual({ state: "", questions: {} }); + await selectPreset("Score a sales lead"); + expect(JSON.parse((editor as HTMLTextAreaElement).value)).not.toHaveProperty("model"); + }); + + it("offers the same presets on /v1/decisions in the OpenAI shape and clears to an empty input", async () => { + const user = userEvent.setup(); + render(); + await selectEndpoint("/v1/decisions"); + await selectPreset("Moderate a forum comment"); + await user.click(screen.getByRole("tab", { name: "JSON" })); + const editor = screen.getByRole("textbox", { name: "Decisions JSON payload" }) as HTMLTextAreaElement; + const payload = JSON.parse(editor.value); + expect(payload.input).toMatch(/stream=false/); + expect(payload.questions).toEqual([ + { type: "predicate", name: "breaks_rules", instructions: "Does this comment break the community guidelines?" }, + ]); + + await user.click(screen.getByRole("button", { name: "Clear" })); + expect(JSON.parse(editor.value)).toEqual({ input: "", questions: [] }); + expect(screen.getByRole("button", { name: "Send" })).toBeDisabled(); }); it("offers both literal proxy endpoints without TypeSafe or product prefixes", async () => { @@ -227,7 +294,7 @@ describe("SystemOneUI integration", () => { await selectEndpoint("/v1/decisions"); expect(screen.getByRole("textbox", { name: "Decisions JSON payload" })).toHaveValue(JSON.stringify(payload)); - await user.click(screen.getByRole("button", { name: "Reset example" })); + await selectPreset("Triage a bug report"); expect(screen.getByRole("textbox", { name: "Decisions JSON payload" })).toHaveValue(nativeExample); await selectEndpoint("/v1/systemone"); expect(screen.getByRole("textbox", { name: "System One JSON payload" })).toHaveValue(systemOneDraft); @@ -403,7 +470,7 @@ describe("SystemOneUI integration", () => { ); expect(next).toEqual({ ...payload, questions: [{ ...payload.questions[0], instructions: "Is it urgent?" }] }); await user.click(screen.getByRole("tab", { name: "Form" })); - await user.click(screen.getByRole("button", { name: "Reset example" })); + await selectPreset("Triage a bug report"); expect(screen.getByRole("textbox", { name: "Input" })).toBeInTheDocument(); expect(screen.getAllByRole("group", { name: /Question \d/ })).toHaveLength(3); await selectEndpoint("/v1/systemone"); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.tsx index 02d42851b8e..c46238315ff 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/SystemOneUI.tsx @@ -9,10 +9,10 @@ import { SearchSelect } from "@/components/shared/SearchSelect"; import { DECISIONS_DOCS_URL } from "@/lib/decisionModels"; import { uiHref } from "@/utils/uiHref"; import { useMutation } from "@tanstack/react-query"; -import { Code, Info, LoaderCircle, RotateCcw, Send } from "lucide-react"; +import { Code, Eraser, Info, LoaderCircle, Send } from "lucide-react"; import { useEffect, useMemo, useRef, useState } from "react"; import { makePlaygroundDecisionRequest, type PlaygroundDecisionRequest } from "../../llm_calls/system_one"; -import { decisionsExample, openAIDecisionsExample } from "./lib/example"; +import { DECISION_PRESETS, emptyPayload, matchPreset, presetPayload, type DecisionPreset } from "./lib/example"; import { payloadModel, withPayloadModel } from "./lib/payloadModel"; import JsonEditor from "./JsonEditor"; import QuestionBreakdown from "./QuestionBreakdown"; @@ -35,8 +35,36 @@ interface SystemOneSendVariables { signal: AbortSignal; } +const CUSTOM_PRESET = "custom"; const DECISION_MODELS_DISCUSSION_URL = "https://github.com/BerriAI/litellm/discussions/44231"; +interface PresetPickerProps { + activePreset: DecisionPreset | undefined; + onPick: (id: string | null) => void; +} + +function PresetPicker({ activePreset, onPick }: PresetPickerProps) { + return ( + + ); +} + function getCustomProxyBaseUrl(): string | undefined { return typeof window === "undefined" ? undefined : window.sessionStorage.getItem("customProxyBaseUrl") || undefined; } @@ -53,9 +81,10 @@ export default function SystemOneUI({ accessToken, disabledPersonalKeyCreation = const [endpoint, setEndpoint] = useState("/v1/systemone"); const [drafts, setDrafts] = useState>>({}); const [view, setView] = useState("form"); - const example = endpoint === "/v1/decisions" ? openAIDecisionsExample : decisionsExample; - const examplePayload = JSON.stringify(example(decisionModels[0]), null, 2); - const rawPayload = drafts[endpoint] ?? examplePayload; + const rawPayload = drafts[endpoint] ?? presetPayload(DECISION_PRESETS[0], endpoint, decisionModels[0]); + const draftModel = drafts[endpoint] === undefined ? decisionModels[0] : payloadModel(rawPayload); + const activePreset = matchPreset(endpoint, rawPayload); + const clearedPayload = emptyPayload(endpoint, draftModel); const activeController = useRef(null); const validated = useMemo( () => @@ -95,9 +124,11 @@ export default function SystemOneUI({ accessToken, disabledPersonalKeyCreation = } } - function handleResetExample() { - clearRequestState(); - setDrafts((current) => Object.fromEntries(Object.entries(current).filter(([key]) => key !== endpoint))); + function handlePresetPick(id: string | null) { + const preset = DECISION_PRESETS.find((candidate) => candidate.id === id); + if (preset !== undefined) { + handlePayloadChange(presetPayload(preset, endpoint, draftModel)); + } } function handleModelPick(model: string | null) { @@ -204,9 +235,14 @@ export default function SystemOneUI({ accessToken, disabledPersonalKeyCreation = )}
- {view === "json" && (