fix(ui): show the legacy evaluation mode as decisions in the Model Hub and guardrail picker

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
kerrylu-berri 2026-10-12 22:05:17 +00:00 • committed by kerry
parent 56a1e94a31
commit 1a5b87796d
7 changed files with 37 additions and 7 deletions

View file

@ -53,7 +53,7 @@ const NoDecisionModels: React.FC<{ providerFilter: string | null }> = ({ provide
<AlertTitle>No decision models yet</AlertTitle>
<AlertDescription>
<p>
Add a decision model{source} to use this guardrail. A model shows up here once its mode is evaluation.{" "}
Add a decision model{source} to use this guardrail. A model shows up here once its mode is decisions.{" "}
<a href={uiHref("models-and-endpoints")} target="_blank" rel="noopener noreferrer">
Add a model
</a>

View file

@ -44,7 +44,7 @@ import { checkTokenValidity } from "@/utils/jwtUtils";
import { getCookie } from "@/utils/cookieUtils";
import { getLoginUrl } from "@/utils/returnUrlUtils";
import { uiHref } from "@/utils/uiHref";
import { DECISIONS_DOCS_URL, SYSTEM_ONE_PLAYGROUND_ROUTE, isDecisionMode } from "@/lib/decisionModels";
import { DECISIONS_DOCS_URL, SYSTEM_ONE_PLAYGROUND_ROUTE, displayMode, isDecisionMode } from "@/lib/decisionModels";
interface ModelHubTableProps {
accessToken: string | null;
@ -672,7 +672,7 @@ const ModelHubTable: React.FC<ModelHubTableProps> = ({
</div>
<div>
<p className="font-medium">Mode:</p>
<p>{selectedModel.mode || "Not specified"}</p>
<p>{displayMode(selectedModel.mode) ?? "Not specified"}</p>
</div>
<div>
<p className="font-medium">Providers:</p>

View file

@ -37,6 +37,12 @@ describe("getModelHubTableColumns", () => {
expect(screen.getByText("gpt-4o")).toBeInTheDocument();
});
it("shows the legacy evaluation mode as decisions", () => {
renderTable([{ ...mockModel, mode: "evaluation" }]);
expect(screen.getByText("decisions")).toBeInTheDocument();
expect(screen.queryByText("evaluation")).not.toBeInTheDocument();
});
it("shows the first two providers and '+1' for overflow", () => {
renderTable([mockModel]);
expect(screen.getByText("openai")).toBeInTheDocument();

View file

@ -14,6 +14,7 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { cn } from "@/lib/cva.config";
import { displayMode } from "@/lib/decisionModels";
import { copyToClipboard } from "@/utils/dataUtils";
export interface ModelHubData {
@ -136,7 +137,7 @@ export const getModelHubTableColumns = ({ onModelClick }: ModelHubTableColumnsDe
sortingFn: "alphanumeric",
cell: ({ row }) =>
row.original.mode ? (
<span className="font-mono text-xs text-muted-foreground">{row.original.mode}</span>
<span className="font-mono text-xs text-muted-foreground">{displayMode(row.original.mode)}</span>
) : (
<span className="text-xs text-muted-foreground">-</span>
),

View file

@ -1,5 +1,6 @@
import React, { useState, useEffect, useMemo, useRef } from "react";
import { Card } from "@/components/ui/card";
import { displayMode } from "@/lib/decisionModels";
import { matchesSearchTerm } from "@/utils/searchUtils";
interface ModelGroupInfo {
@ -51,7 +52,8 @@ const ModelFilters: React.FC<ModelFiltersProps> = ({
const getUniqueModes = (data: ModelGroupInfo[]) => {
const modes = new Set<string>();
data.forEach((model) => {
if (model.mode) modes.add(model.mode);
const mode = displayMode(model.mode);
if (mode) modes.add(mode);
});
return Array.from(modes);
};
@ -79,7 +81,7 @@ const ModelFilters: React.FC<ModelFiltersProps> = ({
modelHubData?.filter((model) => {
const matchesSearch = matchesSearchTerm(searchTerm, [model.model_group]);
const matchesProvider = selectedProvider === "" || model.providers.includes(selectedProvider);
const matchesMode = selectedMode === "" || model.mode === selectedMode;
const matchesMode = selectedMode === "" || displayMode(model.mode) === selectedMode;
// Check if model has the selected feature
const matchesFeature =

View file

@ -1,5 +1,11 @@
import { describe, expect, it } from "vitest";
import { EMPTY_DECISION_CATALOG, buildDecisionCatalog, isDecisionMode, isDecisionSelection } from "./decisionModels";
import {
EMPTY_DECISION_CATALOG,
buildDecisionCatalog,
displayMode,
isDecisionMode,
isDecisionSelection,
} from "./decisionModels";
const COST_MAP = {
"typesafe/jev-latest": { litellm_provider: "typesafe", mode: "evaluation" },
@ -26,6 +32,16 @@ describe("isDecisionMode", () => {
});
});
describe("displayMode", () => {
it("shows the legacy evaluation mode as decisions and leaves other modes alone", () => {
expect(displayMode("evaluation")).toBe("decisions");
expect(displayMode("decisions")).toBe("decisions");
expect(displayMode("chat")).toBe("chat");
expect(displayMode(null)).toBeUndefined();
expect(displayMode(undefined)).toBeUndefined();
});
});
describe("buildDecisionCatalog", () => {
const catalog = buildDecisionCatalog(COST_MAP);

View file

@ -28,6 +28,11 @@ export const EMPTY_DECISION_CATALOG: DecisionCatalog = { models: new Set(), prov
export const isDecisionMode = (mode: string | null | undefined): boolean =>
mode === "decisions" || mode === "evaluation";
export const DECISIONS_MODE = "decisions";
export const displayMode = (mode: string | null | undefined): string | undefined =>
isDecisionMode(mode) ? DECISIONS_MODE : (mode ?? undefined);
const isDecisionEntry = (entry: CostMapEntry): boolean =>
isDecisionMode(entry.mode) || (entry.supported_endpoints ?? []).some((endpoint) => DECISION_ENDPOINTS.has(endpoint));