fix(ui): friendly fallback group labels, page padding, short LiteAdmin action title

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
This commit is contained in:
Devin AI 2026-10-10 02:13:26 +00:00
parent cc4fc1b3a1
commit 0bd1fcd940
4 changed files with 55 additions and 20 deletions

View file

@ -6,12 +6,10 @@ import Fallbacks from "@/components/Settings/RouterSettings/Fallbacks/Fallbacks"
export default function FallbacksPage() {
const { accessToken, userRole, userId } = useAuthorized();
return (
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-4 px-8 py-6">
<div className="flex flex-col gap-2">
<h1 className="text-2xl font-bold">Fallbacks</h1>
<p className="text-sm text-muted-foreground">
Automatically retry a different model when the primary fails
</p>
<p className="text-sm text-muted-foreground">Automatically retry a different model when the primary fails</p>
</div>
<Fallbacks accessToken={accessToken} userRole={userRole} userID={userId} />
</div>

View file

@ -53,7 +53,11 @@ describe("FallbackSelectionForm", () => {
it("should display tabs when groups exist", () => {
const groups: FallbackGroup[] = [{ id: "1", primaryModel: null, fallbackModels: [] }];
render(
<FallbackSelectionForm groups={groups} onGroupsChange={mockOnGroupsChange} modelGroups={AVAILABLE_MODEL_GROUPS} />,
<FallbackSelectionForm
groups={groups}
onGroupsChange={mockOnGroupsChange}
modelGroups={AVAILABLE_MODEL_GROUPS}
/>,
);
expect(screen.getByRole("tab", { name: /group 1/i })).toBeInTheDocument();
});
@ -61,16 +65,36 @@ describe("FallbackSelectionForm", () => {
it("should display primary model as tab label when set", () => {
const groups: FallbackGroup[] = [{ id: "1", primaryModel: "gpt-4", fallbackModels: [] }];
render(
<FallbackSelectionForm groups={groups} onGroupsChange={mockOnGroupsChange} modelGroups={AVAILABLE_MODEL_GROUPS} />,
<FallbackSelectionForm
groups={groups}
onGroupsChange={mockOnGroupsChange}
modelGroups={AVAILABLE_MODEL_GROUPS}
/>,
);
expect(screen.getByRole("tab", { name: "gpt-4" })).toBeInTheDocument();
});
it("should display friendly label for provider wildcard primary", () => {
const groups: FallbackGroup[] = [{ id: "1", primaryModel: "openai/*", fallbackModels: [] }];
render(
<FallbackSelectionForm
groups={groups}
onGroupsChange={mockOnGroupsChange}
modelGroups={AVAILABLE_MODEL_GROUPS}
/>,
);
expect(screen.getByRole("tab", { name: "All OpenAI Models" })).toBeInTheDocument();
});
it("should call onGroupsChange when add tab button is clicked", async () => {
const user = userEvent.setup();
const groups: FallbackGroup[] = [{ id: "1", primaryModel: null, fallbackModels: [] }];
render(
<FallbackSelectionForm groups={groups} onGroupsChange={mockOnGroupsChange} modelGroups={AVAILABLE_MODEL_GROUPS} />,
<FallbackSelectionForm
groups={groups}
onGroupsChange={mockOnGroupsChange}
modelGroups={AVAILABLE_MODEL_GROUPS}
/>,
);
const addTabButton = screen.getByRole("button", { name: /add fallback group/i });
@ -125,7 +149,11 @@ describe("FallbackSelectionForm", () => {
{ id: "2", primaryModel: "gpt-3.5-turbo", fallbackModels: [] },
];
render(
<FallbackSelectionForm groups={groups} onGroupsChange={mockOnGroupsChange} modelGroups={AVAILABLE_MODEL_GROUPS} />,
<FallbackSelectionForm
groups={groups}
onGroupsChange={mockOnGroupsChange}
modelGroups={AVAILABLE_MODEL_GROUPS}
/>,
);
const removeButtons = screen.getAllByRole("button", { name: /^remove /i });
@ -142,7 +170,11 @@ describe("FallbackSelectionForm", () => {
it("should render FallbackGroupConfig for each group", () => {
const groups: FallbackGroup[] = [{ id: "1", primaryModel: null, fallbackModels: [] }];
render(
<FallbackSelectionForm groups={groups} onGroupsChange={mockOnGroupsChange} modelGroups={AVAILABLE_MODEL_GROUPS} />,
<FallbackSelectionForm
groups={groups}
onGroupsChange={mockOnGroupsChange}
modelGroups={AVAILABLE_MODEL_GROUPS}
/>,
);
expect(screen.getByRole("combobox", { name: /primary model/i })).toHaveValue("");
expect(screen.getByText("Primary Model")).toBeInTheDocument();
@ -151,7 +183,11 @@ describe("FallbackSelectionForm", () => {
it("should display group with primary and fallback models in FallbackGroupConfig", () => {
const groups: FallbackGroup[] = [{ id: "1", primaryModel: "gpt-4", fallbackModels: ["gpt-3.5-turbo"] }];
render(
<FallbackSelectionForm groups={groups} onGroupsChange={mockOnGroupsChange} modelGroups={AVAILABLE_MODEL_GROUPS} />,
<FallbackSelectionForm
groups={groups}
onGroupsChange={mockOnGroupsChange}
modelGroups={AVAILABLE_MODEL_GROUPS}
/>,
);
expect(screen.getByRole("tab", { name: "gpt-4" })).toBeInTheDocument();
expect(screen.getAllByText("gpt-4").length).toBeGreaterThan(0);

View file

@ -10,7 +10,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Plus, X } from "lucide-react";
import React, { useEffect, useMemo, useState } from "react";
import { toast } from "@/lib/toast";
import { fallbackChainOptions, primaryModelOptions } from "./fallbackOptions";
import { fallbackChainOptions, fallbackPrimaryLabel, primaryModelOptions } from "./fallbackOptions";
import { FallbackGroup, FallbackGroupConfig } from "./FallbackGroupConfig";
interface FallbackSelectionFormProps {
@ -81,7 +81,7 @@ export function FallbackSelectionForm({
};
const groupLabel = (group: FallbackGroup, index: number) =>
group.primaryModel ? group.primaryModel : `Group ${index + 1}`;
group.primaryModel ? fallbackPrimaryLabel(group.primaryModel) : `Group ${index + 1}`;
if (groups.length === 0) {
return (

View file

@ -109,7 +109,11 @@ const fallbackSetFields = {
model: text.describe(
"The primary model group, or a provider wildcard like openai/* meaning all models from that provider.",
),
fallbacks: z.array(fallbackTarget).min(1).max(10),
fallbacks: z
.array(fallbackTarget)
.min(1)
.max(10)
.describe("Concrete model groups to try in order. Never a wildcard."),
};
const fallbackDeleteFields = { model: text };
@ -359,15 +363,12 @@ export function createLiteAdminOperations(context: OperationContext) {
query: { ...a, start_date: `${a.start_date} 00:00:00`, end_date: `${a.end_date} 23:59:59` },
}),
),
operation("read", "fallback")(
"fallbacks_list",
"List configured router fallbacks",
object({}),
async () => ({ fallbacks: fallbackEntries(await readRouterSettings()) }),
),
operation("read", "fallback")("fallbacks_list", "List configured router fallbacks", object({}), async () => ({
fallbacks: fallbackEntries(await readRouterSettings()),
})),
operation("write", "fallback")(
"fallback_set",
"Set the fallback chain for a primary model. The model may be a provider wildcard like openai/* meaning all models from that provider; wildcards are allowed as model but never inside fallbacks",
"Set the fallback chain for a model",
object(fallbackSetFields),
async (a) => {
const settings = await readRouterSettings();