mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
feat(ui): drive the models page model group filter from ?model_group=
The All Models model-group filter was in-memory React state, so a filtered view could not be linked to or bookmarked. Deriving it from a ?model_group= query param makes the filter shareable and gives other pages (for example an organization's model list) a URL to point at Selecting a group writes the param with history.replaceState so the filter does not spam browser history; choosing All Model Groups removes it
This commit is contained in:
parent
c56e657097
commit
da439f7b70
2 changed files with 76 additions and 2 deletions
|
|
@ -0,0 +1,61 @@
|
|||
/* @vitest-environment jsdom */
|
||||
import { act, render } from "@testing-library/react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import AllModelsPanel from "./AllModelsPanel";
|
||||
|
||||
interface CapturedTabProps {
|
||||
selectedModelGroup: string | null;
|
||||
setSelectedModelGroup: (modelGroup: string) => void;
|
||||
}
|
||||
|
||||
const captured: { props: CapturedTabProps | null } = { props: null };
|
||||
|
||||
vi.mock("@/app/(dashboard)/models-and-endpoints/components/AllModelsTab", () => ({
|
||||
default: (props: CapturedTabProps) => {
|
||||
captured.props = props;
|
||||
return <div data-testid="all-models-tab" />;
|
||||
},
|
||||
}));
|
||||
vi.mock("@/app/(dashboard)/models-and-endpoints/components/AllModelsTable", () => ({
|
||||
ALL_MODEL_GROUPS_VALUE: "all",
|
||||
}));
|
||||
vi.mock("@/app/(dashboard)/models-and-endpoints/useModelDashboardData", () => ({
|
||||
useModelDashboardData: () => ({ availableModelGroups: [], availableModelAccessGroups: [] }),
|
||||
}));
|
||||
vi.mock("next/navigation", () => ({ useSearchParams: () => new URLSearchParams(window.location.search) }));
|
||||
|
||||
describe("AllModelsPanel ?model_group= filter", () => {
|
||||
beforeEach(() => {
|
||||
captured.props = null;
|
||||
window.history.pushState(null, "", "/models-and-endpoints/");
|
||||
});
|
||||
|
||||
it("seeds the model group filter from the URL", () => {
|
||||
window.history.pushState(null, "", "/models-and-endpoints/?model_group=gpt-4o");
|
||||
render(<AllModelsPanel />);
|
||||
expect(captured.props?.selectedModelGroup).toBe("gpt-4o");
|
||||
});
|
||||
|
||||
it("defaults to no filter when the param is absent", () => {
|
||||
render(<AllModelsPanel />);
|
||||
expect(captured.props?.selectedModelGroup).toBeNull();
|
||||
});
|
||||
|
||||
it("writes the selected group to the URL via replaceState", () => {
|
||||
render(<AllModelsPanel />);
|
||||
const spy = vi.spyOn(window.history, "replaceState");
|
||||
act(() => captured.props?.setSelectedModelGroup("claude-opus-5"));
|
||||
expect(spy.mock.calls.at(-1)?.[2]).toContain("model_group=claude-opus-5");
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it("removes the param when the filter resets to all groups", () => {
|
||||
window.history.pushState(null, "", "/models-and-endpoints/?model_group=gpt-4o");
|
||||
render(<AllModelsPanel />);
|
||||
const spy = vi.spyOn(window.history, "replaceState");
|
||||
act(() => captured.props?.setSelectedModelGroup("all"));
|
||||
expect(spy.mock.calls.at(-1)?.[2] as string).not.toContain("model_group");
|
||||
spy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,12 +1,25 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useCallback } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import AllModelsTab from "@/app/(dashboard)/models-and-endpoints/components/AllModelsTab";
|
||||
import { ALL_MODEL_GROUPS_VALUE } from "@/app/(dashboard)/models-and-endpoints/components/AllModelsTable";
|
||||
import { useModelDashboardData } from "@/app/(dashboard)/models-and-endpoints/useModelDashboardData";
|
||||
import { useModelDetailRouting } from "@/app/(dashboard)/models-and-endpoints/detailNavigation";
|
||||
import { navigateWithParams } from "@/app/(dashboard)/navigateWithParams";
|
||||
|
||||
export default function AllModelsPanel() {
|
||||
const [selectedModelGroup, setSelectedModelGroup] = useState<string | null>(null);
|
||||
const searchParams = useSearchParams();
|
||||
const selectedModelGroup = searchParams?.get("model_group") ?? null;
|
||||
const setSelectedModelGroup = useCallback((modelGroup: string) => {
|
||||
navigateWithParams((params) => {
|
||||
if (modelGroup && modelGroup !== ALL_MODEL_GROUPS_VALUE) {
|
||||
params.set("model_group", modelGroup);
|
||||
} else {
|
||||
params.delete("model_group");
|
||||
}
|
||||
}, "replace");
|
||||
}, []);
|
||||
const { availableModelGroups, availableModelAccessGroups } = useModelDashboardData();
|
||||
const { openModel, openTeam } = useModelDetailRouting();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue