feat(ui): label the System One yes / no question type noul and explain it on hover (#45841)

* feat(ui): label the System One yes / no question type noul and explain it on hover

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* feat(ui): capitalize the Noul answer type label

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* Revert "feat(ui): capitalize the Noul answer type label"

This reverts commit e79a40fce2.

* test(ui): cover the noul banner tooltip and the answer type option title

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: kerry <kerry@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot] 2026-10-10 14:27:50 -07:00 • committed by GitHub
parent a1e5c90e56
commit 988cc7dc94
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 38 additions and 7 deletions

View file

@ -27,7 +27,12 @@ import {
import type { SystemOnePayloadValidation } from "./lib/validatePayload";
const QUESTION_TYPES: readonly QuestionType[] = ["choice", "noul", "score"];
const TYPE_LABELS: Record<QuestionType, string> = { choice: "Choice", noul: "Yes / no", score: "Score" };
const TYPE_LABELS: Record<QuestionType, string> = { choice: "Choice", noul: "noul", score: "Score" };
const TYPE_HELP: Record<QuestionType, string | undefined> = {
choice: undefined,
noul: "noul is a yes / no question",
score: undefined,
};
const isQuestionType = (value: unknown): value is QuestionType => QUESTION_TYPES.some((type) => type === value);
@ -219,7 +224,7 @@ function QuestionCard({ name, question, taken, onChange, onRename, onRemove }: Q
</SelectTrigger>
<SelectContent>
{QUESTION_TYPES.map((type) => (
<SelectItem key={type} value={type}>
<SelectItem key={type} value={type} title={TYPE_HELP[type]}>
{TYPE_LABELS[type]}
</SelectItem>
))}

View file

@ -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 from "./SystemOneUI";
import SystemOneUI, { NOUL_HELP } from "./SystemOneUI";
import type { SystemOneResponse } from "./lib/schemas";
import type { OpenAIDecisionsResponse } from "./lib/openAIDecisions";
@ -920,6 +920,22 @@ describe("SystemOneUI integration", () => {
fireEvent.blur(field);
}
it("explains noul on hover in the banner and on the answer type option", async () => {
const user = userEvent.setup();
render(<SystemOneUI accessToken="session-key" />);
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(
"title",
"noul is a yes / no question",
);
expect(screen.getByRole("option", { name: "Choice" })).not.toHaveAttribute("title");
});
it("sends a choice and a yes/no question built in the form without opening JSON", async () => {
const user = userEvent.setup();
render(<SystemOneUI accessToken="session-key" />);
@ -941,7 +957,7 @@ describe("SystemOneUI integration", () => {
fill(question("team").getByRole("textbox", { name: "Description of infra" }), "Deploys");
await user.click(screen.getByRole("button", { name: "Add question" }));
await pickAnswerType(user, "question_1", "Yes / no");
await pickAnswerType(user, "question_1", "noul");
rename(question("question_1").getByRole("textbox", { name: "Question name" }), "urgent");
fill(question("urgent").getByRole("textbox", { name: "Instructions" }), "Is this blocking users?");
fill(question("urgent").getByRole("textbox", { name: "Yes means" }), "Nobody can log in");

View file

@ -5,6 +5,7 @@ import { Button } 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 { DECISIONS_DOCS_URL } from "@/lib/decisionModels";
import { uiHref } from "@/utils/uiHref";
@ -35,6 +36,7 @@ interface SystemOneSendVariables {
signal: AbortSignal;
}
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";
@ -265,9 +267,17 @@ export default function SystemOneUI({ accessToken, disabledPersonalKeyCreation =
<Info />
<AlertTitle>{endpoint}</AlertTitle>
<AlertDescription>
{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."}{" "}
{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{" "}
<SimpleTooltip content={NOUL_HELP} className="underline decoration-dotted underline-offset-2">
noul
</SimpleTooltip>
, choice, and score questions in the System One format.
</>
)}{" "}
Pick a decision model under Model, or omit model to use the proxy&apos;s configured default.{" "}
<a href={DECISIONS_DOCS_URL} target="_blank" rel="noopener noreferrer" className="underline">
How to call /v1/decisions and /v1/systemone