diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/QuestionBreakdown.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/QuestionBreakdown.test.tsx
deleted file mode 100644
index 289ce8a53f5..00000000000
--- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/QuestionBreakdown.test.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { render, screen } from "@testing-library/react";
-import { describe, expect, it } from "vitest";
-import QuestionBreakdown from "./QuestionBreakdown";
-import { validateSystemOnePayload } from "./lib/validatePayload";
-
-const previewOf = (questions: object) => validateSystemOnePayload(JSON.stringify({ state: "Hi", questions })).payload;
-
-describe("QuestionBreakdown", () => {
- it("previews every noul criterion that will be sent, not only true and false", () => {
- render(
- ,
- );
-
- expect(screen.getByText("Asks for a person")).toBeInTheDocument();
- expect(screen.getByText("unsure")).toBeInTheDocument();
- expect(screen.getByText("Maybe")).toBeInTheDocument();
- expect(screen.queryByText("No criteria defined")).not.toBeInTheDocument();
- });
-
- it("says when a noul question has no criteria", () => {
- render( );
-
- expect(screen.getByText("No criteria defined")).toBeInTheDocument();
- });
-});
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/QuestionBreakdown.tsx b/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/QuestionBreakdown.tsx
deleted file mode 100644
index 8fc06101783..00000000000
--- a/ui/litellm-dashboard/src/app/(dashboard)/playground/components/systemOneUI/QuestionBreakdown.tsx
+++ /dev/null
@@ -1,103 +0,0 @@
-import { Badge } from "@/components/ui/badge";
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
-import type { PlaygroundQuestion, PlaygroundRequest } from "./lib/schemas";
-
-function formatState(state: unknown): string {
- if (typeof state === "string") {
- return state;
- }
- return JSON.stringify(state, null, 2) ?? String(state);
-}
-
-function QuestionCriteria({ question }: { question: PlaygroundQuestion }) {
- if (question.type === "choice") {
- return (
-
- {Object.entries(question.criteria).map(([label, description]) => (
-
-
{label}
- {formatState(description)}
-
- ))}
-
- );
- }
-
- if (question.type === "noul") {
- const criteria = Object.entries(question.criteria ?? {}).filter(([, description]) => description !== undefined);
- return criteria.length > 0 ? (
-
- {criteria.map(([label, description]) => (
-
-
{label}
-
- {typeof description === "string" ? description : JSON.stringify(description)}
-
-
- ))}
-
- ) : (
- No criteria defined
- );
- }
-
- return (
-
- {question.criteria.map((description, index) => (
-
- {index}
- {formatState(description)}
-
- ))}
-
- );
-}
-
-export default function QuestionBreakdown({ payload }: { payload?: PlaygroundRequest }) {
- if (!payload) {
- return (
-
-
- Question breakdown
- Enter a valid request to preview its state and questions.
-
-
- );
- }
-
- return (
-
-
- Question breakdown
- Review the state and criteria that will be sent.
-
-
-
-
- State
-
-
- {formatState(payload.state)}
-
-
-
- {Object.entries(payload.questions).map(([id, question]) => (
-
-
-
- {id}
-
- {question.type}
-
- {question.instructions != null && {formatState(question.instructions)}
}
-
-
Criteria
-
-
-
- ))}
-
-
-
- );
-}
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 eb75c836b2b..470a2495d4f 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
@@ -11,7 +11,7 @@ import {
import type { ReactElement } from "react";
import userEvent from "@testing-library/user-event";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
-import SystemOneUI, { NOUL_HELP } from "./SystemOneUI";
+import SystemOneUI from "./SystemOneUI";
import type { SystemOneResponse } from "./lib/schemas";
import type { OpenAIDecisionsResponse } from "./lib/openAIDecisions";
@@ -194,17 +194,11 @@ describe("SystemOneUI integration", () => {
expect(screen.queryByText(/TypeSafe/)).not.toBeInTheDocument();
await userEvent.keyboard("{Escape}");
- expect(screen.getByRole("note", { name: "Decision endpoint notice" })).toHaveTextContent(
- "omit model to use the proxy's configured default.",
- );
expect(screen.getByRole("link", { name: "How to call /v1/decisions and /v1/systemone" })).toHaveAttribute(
"href",
"https://docs.litellm.ai/docs/decisions",
);
- expect(screen.getByRole("link", { name: "Give us feedback on what you want for decision models" })).toHaveAttribute(
- "href",
- "https://github.com/BerriAI/litellm/discussions/44231",
- );
+ expect(screen.queryByRole("note")).not.toBeInTheDocument();
expect(screen.queryByRole("alert")).not.toBeInTheDocument();
});
@@ -653,6 +647,27 @@ describe("SystemOneUI integration", () => {
expect(screen.getByRole("button", { name: "Send" })).toBeDisabled();
});
+ it("keeps Send below the form and shows results only after a request", async () => {
+ const user = userEvent.setup();
+ render( );
+
+ const send = screen.getByRole("button", { name: "Send" });
+ const editor = screen.getByRole("region", { name: "Decisions request editor" });
+ const form = within(editor).getByRole("textbox", { name: "Input" });
+ expect(form.compareDocumentPosition(send) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
+ expect(screen.queryByRole("region", { name: "Decisions results" })).not.toBeInTheDocument();
+ expect(screen.queryByRole("note")).not.toBeInTheDocument();
+
+ await user.click(send);
+
+ const results = await screen.findByRole("region", { name: "Decisions results" });
+ expect(within(results).getByText("Calibrated probabilities")).toBeInTheDocument();
+ expect(editor.compareDocumentPosition(results) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
+
+ fireEvent.change(within(editor).getByRole("textbox", { name: "Input" }), { target: { value: "Edited state" } });
+ expect(screen.queryByRole("region", { name: "Decisions results" })).not.toBeInTheDocument();
+ });
+
it("sends to /v1/systemone even without discovered models and renders calibrated answers", async () => {
const user = userEvent.setup();
sessionStorage.setItem("customProxyBaseUrl", "https://stale.example.com/");
@@ -712,7 +727,8 @@ describe("SystemOneUI integration", () => {
expect(screen.getByRole("button", { name: "Send" })).toBeDisabled();
await userEvent.click(screen.getByRole("tab", { name: "Form" }));
- expect(screen.getByText("Enter a valid request to preview its state and questions.")).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: "Send" })).toBeDisabled();
+ expect(screen.queryByRole("region", { name: "Decisions results" })).not.toBeInTheDocument();
});
it("sends structured native questions without changing their payload", async () => {
@@ -729,7 +745,6 @@ describe("SystemOneUI integration", () => {
fireEvent.change(editor, { target: { value: draft } });
expect(screen.getByRole("button", { name: "Send" })).toBeEnabled();
await user.click(screen.getByRole("tab", { name: "Form" }));
- expect(screen.getByText(/"text": "Help"/)).toBeInTheDocument();
await user.click(screen.getByRole("button", { name: "Send" }));
expect(await screen.findByText("Selected choice")).toBeInTheDocument();
@@ -920,13 +935,10 @@ describe("SystemOneUI integration", () => {
fireEvent.blur(field);
}
- it("explains noul on hover in the banner and on the answer type option", async () => {
+ it("explains noul on the answer type option", async () => {
const user = userEvent.setup();
render( );
- await user.hover(screen.getByText("noul", { selector: "span.cursor-help" }));
- expect(await screen.findByText(NOUL_HELP)).toBeInTheDocument();
-
question("has_repro_steps").getByRole("combobox", { name: "Answer type" }).focus();
await user.keyboard("{ArrowDown}");
expect(await screen.findByRole("option", { name: "noul" })).toHaveAttribute(
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 3d2d4c0f1a0..716f5c78b08 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
@@ -1,22 +1,21 @@
"use client";
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
-import { Button } from "@/components/ui/button";
+import { Button, buttonVariants } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { SimpleTooltip } from "@/components/ui/tooltip";
import { SearchSelect } from "@/components/shared/SearchSelect";
+import { cn } from "@/lib/cva.config";
import { DECISIONS_DOCS_URL } from "@/lib/decisionModels";
import { uiHref } from "@/utils/uiHref";
import { useMutation } from "@tanstack/react-query";
-import { Code, Eraser, Info, LoaderCircle, Send } from "lucide-react";
+import { BookOpen, Code, Eraser, LoaderCircle, Send } from "lucide-react";
import { useEffect, useMemo, useRef, useState } from "react";
import { makePlaygroundDecisionRequest, type PlaygroundDecisionRequest } from "../../llm_calls/system_one";
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";
import ResponseView from "./ResponseView";
import SystemOneForm from "./SystemOneForm";
import OpenAIDecisionsForm from "./OpenAIDecisionsForm";
@@ -38,7 +37,6 @@ interface SystemOneSendVariables {
export const NOUL_HELP = "noul is a yes / no question. The model returns the probability that the answer is yes.";
const CUSTOM_PRESET = "custom";
-const DECISION_MODELS_DISCUSSION_URL = "https://github.com/BerriAI/litellm/discussions/44231";
interface PresetPickerProps {
activePreset: DecisionPreset | undefined;
@@ -103,6 +101,7 @@ export default function SystemOneUI({ accessToken, disabledPersonalKeyCreation =
makePlaygroundDecisionRequest(request, apiKey, getCustomProxyBaseUrl(), { signal }),
});
const isLoading = systemOne.isPending;
+ const hasResult = isLoading || systemOne.data !== undefined || systemOne.error !== null;
const { reset: resetSystemOne } = systemOne;
useEffect(() => () => activeController.current?.abort(), []);
@@ -163,49 +162,49 @@ export default function SystemOneUI({ accessToken, disabledPersonalKeyCreation =
}
return (
-
-
-
-
Endpoint
-
{
- if (value === "/v1/decisions" || value === "/v1/systemone") {
- clearRequestState();
- setEndpoint(value);
- }
- }}
- >
-
- {endpoint}
-
-
- /v1/decisions
- /v1/systemone
-
-
-
Model
-
- ({ label: model, value: model }))}
- value={payloadModel(rawPayload) ?? null}
- onValueChange={handleModelPick}
- placeholder={decisionModels.length > 0 ? "Pick a decision model" : "No decision models yet"}
- emptyText="No decision models found"
- disabled={hasSyntaxError}
- allowClear={false}
- />
-
- {decisionModelsLoaded && decisionModels.length === 0 && (
-
- Add a decision model
-
- )}
-
-
+
+
+
-
Virtual Key Source
+
{
+ if (value === "/v1/decisions" || value === "/v1/systemone") {
+ clearRequestState();
+ setEndpoint(value);
+ }
+ }}
+ >
+
+ {endpoint}
+
+
+ /v1/decisions
+ /v1/systemone
+
+
+
+ ({ label: model, value: model }))}
+ value={payloadModel(rawPayload) ?? null}
+ onValueChange={handleModelPick}
+ placeholder={decisionModels.length > 0 ? "Pick a decision model" : "No decision models yet"}
+ emptyText="No decision models found"
+ disabled={hasSyntaxError}
+ allowClear={false}
+ />
+
+ {decisionModelsLoaded && decisionModels.length === 0 && (
+
+ Add a decision model
+
+ )}
{
@@ -215,7 +214,7 @@ export default function SystemOneUI({ accessToken, disabledPersonalKeyCreation =
}}
disabled={disabledPersonalKeyCreation}
>
-
+
{apiKeySource === "custom" ? "Virtual Key" : "Current UI Session"}
@@ -236,71 +235,32 @@ export default function SystemOneUI({ accessToken, disabledPersonalKeyCreation =
/>
)}
-
-
-
handlePayloadChange(clearedPayload)}
- disabled={rawPayload === clearedPayload}
- >
-
- Clear
-
- {view === "json" && (
-
-
- Format JSON
-
- )}
- {isLoading && (
-
- Cancel request
-
- )}
-
- {isLoading ? : }
- Send
-
-
-
-
-
- {endpoint}
-
- {endpoint === "/v1/decisions" ? (
- "Uses input and a questions array with predicate, choice, and score questions. Edit the form or JSON and inspect the response JSON in the OpenAI Decisions format."
- ) : (
- <>
- Uses state and a questions object with{" "}
-
- noul
-
- , choice, and score questions in the System One format.
- >
- )}{" "}
- Pick a decision model under Model, or omit model to use the proxy's configured default.{" "}
-
- How to call /v1/decisions and /v1/systemone
-
- {" ยท "}
-
- Give us feedback on what you want for decision models
-
-
-
-
-
-
(value === "form" || value === "json") && setView(value)}
className="min-h-0 flex-1"
>
-
- Form
- JSON
-
+
{validated.endpoint === "/v1/decisions" ? (
+
+
+
handlePayloadChange(clearedPayload)}
+ disabled={rawPayload === clearedPayload}
+ >
+
+ Clear
+
+ {view === "json" && (
+
+
+ Format JSON
+
+ )}
+
+ {isLoading && (
+
+ Cancel request
+
+ )}
+
+ {isLoading ? : }
+ Send
+
+
+
-
-
- {view === "form" && validated.endpoint === "/v1/systemone" && (
-
- )}
-
+
+ {hasResult && (
+
+ )}
);