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 =
)}
-