mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-05 08:07:05 +00:00
fix(ui): filter the models page by exact model group instead of substring search
Pass the selected group as the exact model= param on /v2/model/info rather than as the substring search, so a group like gpt-4 no longer pulls gpt-4o rows into the page and count. Drop two comments that restated helper behavior.
This commit is contained in:
parent
56a80c8125
commit
fe63ebdb19
7 changed files with 66 additions and 17 deletions
|
|
@ -38,6 +38,7 @@ export const useModelsInfo = (
|
|||
sortBy?: string,
|
||||
sortOrder?: string,
|
||||
excludeAutoRouters: boolean = false,
|
||||
modelName?: string,
|
||||
) => {
|
||||
const { accessToken, userId, userRole } = useAuthorized();
|
||||
return useQuery<PaginatedModelInfoResponse>({
|
||||
|
|
@ -48,6 +49,7 @@ export const useModelsInfo = (
|
|||
page,
|
||||
size,
|
||||
...(search && { search }),
|
||||
...(modelName && { modelName }),
|
||||
...(modelId && { modelId }),
|
||||
...(teamId && { teamId }),
|
||||
...(sortBy && { sortBy }),
|
||||
|
|
@ -70,6 +72,7 @@ export const useModelsInfo = (
|
|||
sortBy,
|
||||
sortOrder,
|
||||
excludeAutoRouters,
|
||||
modelName,
|
||||
),
|
||||
enabled: Boolean(accessToken && userId && userRole),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ interface ModelsInfoArgs {
|
|||
teamId?: string;
|
||||
sortBy?: string;
|
||||
sortOrder?: string;
|
||||
modelName?: string;
|
||||
}
|
||||
|
||||
const modelsInfoCalls: ModelsInfoArgs[] = [];
|
||||
|
|
@ -47,12 +48,14 @@ type UseModelsInfoArgs = [
|
|||
teamId?: string,
|
||||
sortBy?: string,
|
||||
sortOrder?: string,
|
||||
excludeAutoRouters?: boolean,
|
||||
modelName?: string,
|
||||
];
|
||||
|
||||
vi.mock("../../hooks/models/useModels", () => ({
|
||||
useModelsInfo: (...args: UseModelsInfoArgs) => {
|
||||
const [page, size, search, , teamId, sortBy, sortOrder] = args;
|
||||
const call: ModelsInfoArgs = { page, size, search, teamId, sortBy, sortOrder };
|
||||
const [page, size, search, , teamId, sortBy, sortOrder, , modelName] = args;
|
||||
const call: ModelsInfoArgs = { page, size, search, teamId, sortBy, sortOrder, modelName };
|
||||
modelsInfoCalls.push(call);
|
||||
return { ...modelsInfoResult, refetch: mockRefetch };
|
||||
},
|
||||
|
|
@ -260,25 +263,26 @@ describe("AllModelsTab", () => {
|
|||
expect(within(table).queryByText("gpt-4")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("queries the server for the selected model group so deployments beyond the first page are found", () => {
|
||||
it("asks the server for the exact selected model group so deployments beyond the first page are found", () => {
|
||||
render(<AllModelsTab {...defaultProps} selectedModelGroup="claude-opus" />);
|
||||
|
||||
expect(lastModelsInfoCall().search).toBe("claude-opus");
|
||||
});
|
||||
|
||||
it.each(["all", "wildcard"])("does not seed the server search from the %s pseudo group", (group) => {
|
||||
render(<AllModelsTab {...defaultProps} selectedModelGroup={group} />);
|
||||
|
||||
expect(lastModelsInfoCall().modelName).toBe("claude-opus");
|
||||
expect(lastModelsInfoCall().search).toBeUndefined();
|
||||
});
|
||||
|
||||
it("lets a typed search override the selected model group in the server query", async () => {
|
||||
const user = userEvent.setup();
|
||||
it.each(["all", "wildcard"])("sends no exact model name for the %s pseudo group", (group) => {
|
||||
render(<AllModelsTab {...defaultProps} selectedModelGroup={group} />);
|
||||
|
||||
expect(lastModelsInfoCall().modelName).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps the exact model group alongside a typed search", async () => {
|
||||
render(<AllModelsTab {...defaultProps} selectedModelGroup="claude-opus" />);
|
||||
|
||||
await user.type(screen.getByPlaceholderText("Search model names…"), "gpt");
|
||||
fireEvent.change(screen.getByPlaceholderText("Search model names…"), { target: { value: "opus" } });
|
||||
|
||||
await waitFor(() => expect(lastModelsInfoCall().search).toBe("gpt"));
|
||||
await waitFor(() => expect(lastModelsInfoCall().search).toBe("opus"));
|
||||
expect(lastModelsInfoCall().modelName).toBe("claude-opus");
|
||||
});
|
||||
|
||||
it("resets search, filters, team and sorting from the drawer reset button", async () => {
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ const AllModelsTab = ({
|
|||
Boolean(selectedModelGroup) &&
|
||||
selectedModelGroup !== ALL_MODEL_GROUPS_VALUE &&
|
||||
selectedModelGroup !== WILDCARD_MODEL_GROUP_VALUE;
|
||||
const searchForQuery = debouncedSearch || (isConcreteModelGroup ? selectedModelGroup ?? undefined : undefined);
|
||||
const modelNameForQuery = isConcreteModelGroup ? selectedModelGroup ?? undefined : undefined;
|
||||
|
||||
const sortBy = useMemo(() => {
|
||||
if (sorting.length === 0) return undefined;
|
||||
|
|
@ -105,7 +105,7 @@ const AllModelsTab = ({
|
|||
} = useModelsInfo(
|
||||
pagination.pageIndex + 1,
|
||||
pagination.pageSize,
|
||||
searchForQuery,
|
||||
debouncedSearch || undefined,
|
||||
undefined,
|
||||
teamIdForQuery,
|
||||
sortBy,
|
||||
|
|
@ -113,6 +113,7 @@ const AllModelsTab = ({
|
|||
// Auto-routers are routing constructs, not deployments; the sibling Auto-Routers tab
|
||||
// lists and manages them. Excluded server-side so total_count stays honest.
|
||||
true,
|
||||
modelNameForQuery,
|
||||
);
|
||||
const isLoading = isLoadingModelsInfo || isLoadingModelCostMap;
|
||||
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ export interface ModelGroupFilterRouting {
|
|||
setModelGroup: (modelGroup: string | null) => void;
|
||||
}
|
||||
|
||||
/** `?model_group=` backs the All Models group filter so other pages can deep-link to one group. */
|
||||
export function useModelGroupFilterRouting(): ModelGroupFilterRouting {
|
||||
const [modelGroup, setParam] = useQueryState("model_group", parseAsString);
|
||||
|
||||
|
|
|
|||
|
|
@ -104,6 +104,45 @@ describe("loginCall - storeLoginToken integration", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("modelInfoCall", () => {
|
||||
let currentFetch: typeof global.fetch;
|
||||
|
||||
beforeEach(() => {
|
||||
currentFetch = global.fetch;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.fetch = currentFetch;
|
||||
});
|
||||
|
||||
it("sends the exact model name as the model query param and leaves search alone", async () => {
|
||||
const mockFetch = vi.fn().mockResolvedValue({ ok: true, json: vi.fn().mockResolvedValue({ data: [] }) } as any);
|
||||
global.fetch = mockFetch as any;
|
||||
|
||||
await Networking.modelInfoCall(
|
||||
"token",
|
||||
"user",
|
||||
"Admin",
|
||||
2,
|
||||
25,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
"gpt-4",
|
||||
);
|
||||
|
||||
const parsed = new URL(mockFetch.mock.calls[0][0] as string, "http://example.com");
|
||||
expect(parsed.pathname).toBe("/v2/model/info");
|
||||
expect(parsed.searchParams.get("model")).toBe("gpt-4");
|
||||
expect(parsed.searchParams.has("search")).toBe(false);
|
||||
expect(parsed.searchParams.get("page")).toBe("2");
|
||||
expect(parsed.searchParams.get("exclude_auto_routers")).toBe("true");
|
||||
});
|
||||
});
|
||||
|
||||
describe("daily activity helpers", () => {
|
||||
const startTime = new Date("2025-02-12T00:00:00.000Z");
|
||||
const endTime = new Date("2025-02-19T00:00:00.000Z");
|
||||
|
|
|
|||
|
|
@ -1656,6 +1656,7 @@ export const modelInfoCall = async (
|
|||
sortBy?: string,
|
||||
sortOrder?: string,
|
||||
excludeAutoRouters?: boolean,
|
||||
modelName?: string,
|
||||
) => {
|
||||
/**
|
||||
* Get all models on proxy
|
||||
|
|
@ -1669,6 +1670,9 @@ export const modelInfoCall = async (
|
|||
if (search && search.trim()) {
|
||||
params.append("search", search.trim());
|
||||
}
|
||||
if (modelName && modelName.trim()) {
|
||||
params.append("model", modelName.trim());
|
||||
}
|
||||
if (modelId && modelId.trim()) {
|
||||
params.append("modelId", modelId.trim());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ export function orgDetailHref(orgId: string): string {
|
|||
return `${migratedHref("organizations")}?org=${encodeURIComponent(orgId)}`;
|
||||
}
|
||||
|
||||
/** Models page filtered to one model group; undefined for grant sentinels that name no deployment. */
|
||||
export function modelGroupHref(modelGroup: string): string | undefined {
|
||||
if (MODEL_GRANT_SENTINELS.has(modelGroup)) return undefined;
|
||||
return `${migratedHref("models-and-endpoints")}?model_group=${encodeURIComponent(modelGroup)}`;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue