Merge pull request #19543 from BerriAI/litellm_model_select_team

[Feature] UI - Create Team: Reusable Model Select
This commit is contained in:
yuneng-jiang
2026-01-21 21:11:09 -08:00
committed by GitHub
7 changed files with 110 additions and 63 deletions
@@ -379,7 +379,11 @@ describe("useAllProxyModels", () => {
"test-access-token",
"test-user-id",
"Admin",
true
true,
null,
true,
false,
"expand"
);
expect(modelAvailableCall).toHaveBeenCalledTimes(1);
});
@@ -56,7 +56,7 @@ export const useAllProxyModels = () => {
const { accessToken, userId, userRole } = useAuthorized();
return useQuery<AllProxyModelsResponse>({
queryKey: allProxyModelsKeys.list({}),
queryFn: async () => await modelAvailableCall(accessToken!, userId!, userRole!, true),
queryFn: async () => await modelAvailableCall(accessToken!, userId!, userRole!, true, null, true, false, "expand"),
enabled: Boolean(accessToken && userId && userRole),
});
};
@@ -526,7 +526,7 @@ const CreateTeamModal = ({
valuePropName="checked"
help="Bypass global guardrails for this team"
>
<Switch
<Switch
checkedChildren="Yes"
unCheckedChildren="No"
/>
@@ -53,14 +53,13 @@ const contextFilters: Record<ModelSelectProps["context"], (args: FilterContextAr
team: ({ allProxyModels, selectedOrganization, userModels }) => {
if (selectedOrganization) {
if (selectedOrganization.models.includes(MODEL_SELECT_ALL_PROXY_MODELS_SPECIAL_VALUE.value)) {
if (selectedOrganization.models.includes(MODEL_SELECT_ALL_PROXY_MODELS_SPECIAL_VALUE.value) || selectedOrganization.models.length === 0) {
return allProxyModels;
}
// Return organization's models (filtered from allProxyModels)
return allProxyModels.filter((model) => selectedOrganization.models.includes(model));
}
return userModels ?? [];
return allProxyModels ?? [];
},
organization: ({ allProxyModels, selectedOrganization, options }) => {
@@ -102,9 +101,12 @@ export const ModelSelect = (props: ModelSelectProps) => {
const isSpecialOption = (value: string) => MODEL_SELECT_SPECIAL_VALUES_ARRAY.some((sv) => sv.value === value);
const hasSpecialOptionSelected = value.some(isSpecialOption);
const isLoading = isLoadingAllProxyModels || isLoadingTeam || isLoadingOrganization || isCurrentUserLoading;
const organizationHasAllProxyModels = organization?.models.includes(MODEL_SELECT_ALL_PROXY_MODELS_SPECIAL_VALUE.value) || organization?.models.length === 0;
console.log("organization:", organization);
console.log("organizationHasAllProxyModels:", organizationHasAllProxyModels);
const shouldShowAllProxyModels =
showAllProxyModelsOverride ||
(organization?.models.includes(MODEL_SELECT_ALL_PROXY_MODELS_SPECIAL_VALUE.value) && includeSpecialOptions);
(organizationHasAllProxyModels && includeSpecialOptions);
if (isLoading) {
return <Skeleton.Input active block />;
@@ -143,51 +145,51 @@ export const ModelSelect = (props: ModelSelectProps) => {
options={[
includeSpecialOptions
? {
label: <span>Special Options</span>,
title: "Special Options",
options: [
...(shouldShowAllProxyModels
? [
{
label: <span>All Proxy Models</span>,
value: MODEL_SELECT_ALL_PROXY_MODELS_SPECIAL_VALUE.value,
disabled:
value.length > 0 &&
value.some(
(v) => isSpecialOption(v) && v !== MODEL_SELECT_ALL_PROXY_MODELS_SPECIAL_VALUE.value,
),
key: MODEL_SELECT_ALL_PROXY_MODELS_SPECIAL_VALUE.value,
},
]
: []),
{
label: <span>No Default Models</span>,
value: MODEL_SELECT_NO_DEFAULT_MODELS_SPECIAL_VALUE.value,
disabled:
value.length > 0 &&
value.some((v) => isSpecialOption(v) && v !== MODEL_SELECT_NO_DEFAULT_MODELS_SPECIAL_VALUE.value),
key: MODEL_SELECT_NO_DEFAULT_MODELS_SPECIAL_VALUE.value,
},
],
}
label: <span>Special Options</span>,
title: "Special Options",
options: [
...(shouldShowAllProxyModels
? [
{
label: <span>All Proxy Models</span>,
value: MODEL_SELECT_ALL_PROXY_MODELS_SPECIAL_VALUE.value,
disabled:
value.length > 0 &&
value.some(
(v) => isSpecialOption(v) && v !== MODEL_SELECT_ALL_PROXY_MODELS_SPECIAL_VALUE.value,
),
key: MODEL_SELECT_ALL_PROXY_MODELS_SPECIAL_VALUE.value,
},
]
: []),
{
label: <span>No Default Models</span>,
value: MODEL_SELECT_NO_DEFAULT_MODELS_SPECIAL_VALUE.value,
disabled:
value.length > 0 &&
value.some((v) => isSpecialOption(v) && v !== MODEL_SELECT_NO_DEFAULT_MODELS_SPECIAL_VALUE.value),
key: MODEL_SELECT_NO_DEFAULT_MODELS_SPECIAL_VALUE.value,
},
],
}
: [],
...(wildcard.length > 0
? [
{
label: <span>Wildcard Options</span>,
title: "Wildcard Options",
options: wildcard.map((model) => {
const provider = model.replace("/*", "");
const capitalizedProvider = provider.charAt(0).toUpperCase() + provider.slice(1);
{
label: <span>Wildcard Options</span>,
title: "Wildcard Options",
options: wildcard.map((model) => {
const provider = model.replace("/*", "");
const capitalizedProvider = provider.charAt(0).toUpperCase() + provider.slice(1);
return {
label: <span>{`All ${capitalizedProvider} models`}</span>,
value: model,
disabled: hasSpecialOptionSelected,
};
}),
},
]
return {
label: <span>{`All ${capitalizedProvider} models`}</span>,
value: model,
disabled: hasSpecialOptionSelected,
};
}),
},
]
: []),
{
label: <span>Models</span>,
@@ -60,6 +60,31 @@ vi.mock("@/components/team/team_info", () => ({
},
}));
vi.mock("./ModelSelect/ModelSelect", () => {
const ModelSelect = React.forwardRef(({ value, onChange, dataTestId, id }: any, ref: any) => {
return (
<input
ref={ref}
id={id}
type="text"
data-testid={dataTestId || "model-select"}
value={Array.isArray(value) ? value.join(", ") : ""}
onChange={(e) => {
// Mock onChange - in real usage this would be handled by Ant Design Select
if (onChange) {
onChange(value || []);
}
}}
readOnly
/>
);
});
ModelSelect.displayName = "ModelSelect";
return {
ModelSelect,
};
});
vi.mock("@/app/(dashboard)/hooks/organizations/useOrganizations", () => ({
useOrganizations: () => mockUseOrganizations(),
}));
@@ -313,6 +338,7 @@ describe("OldTeams - handleCreate organization handling", () => {
created_at: new Date().toISOString(),
keys: [],
members_with_roles: [],
spend: 0,
},
]}
searchParams={{}}
@@ -387,6 +413,7 @@ describe("OldTeams - empty state", () => {
created_at: new Date().toISOString(),
keys: [],
members_with_roles: [],
spend: 0,
},
]}
searchParams={{}}
@@ -555,6 +582,7 @@ describe("OldTeams - premium props", () => {
created_at: new Date().toISOString(),
keys: [],
members_with_roles: [],
spend: 0,
},
]}
searchParams={{}}
@@ -603,6 +631,7 @@ describe("OldTeams - Default Team Settings tab visibility", () => {
created_at: new Date().toISOString(),
keys: [],
members_with_roles: [],
spend: 0,
},
]}
searchParams={{}}
@@ -633,6 +662,7 @@ describe("OldTeams - Default Team Settings tab visibility", () => {
created_at: new Date().toISOString(),
keys: [],
members_with_roles: [],
spend: 0,
},
]}
searchParams={{}}
@@ -663,6 +693,7 @@ describe("OldTeams - Default Team Settings tab visibility", () => {
created_at: new Date().toISOString(),
keys: [],
members_with_roles: [],
spend: 0,
},
]}
searchParams={{}}
@@ -693,6 +724,7 @@ describe("OldTeams - Default Team Settings tab visibility", () => {
created_at: new Date().toISOString(),
keys: [],
members_with_roles: [],
spend: 0,
},
]}
searchParams={{}}
@@ -791,6 +823,7 @@ describe("OldTeams - organization alias display", () => {
created_at: new Date().toISOString(),
keys: [],
members_with_roles: [],
spend: 0,
},
]}
searchParams={{}}
@@ -824,6 +857,7 @@ describe("OldTeams - organization alias display", () => {
created_at: new Date().toISOString(),
keys: [],
members_with_roles: [],
spend: 0,
},
]}
searchParams={{}}
@@ -856,6 +890,7 @@ describe("OldTeams - organization alias display", () => {
created_at: new Date().toISOString(),
keys: [],
members_with_roles: [],
spend: 0,
},
]}
searchParams={{}}
@@ -85,6 +85,7 @@ import { updateExistingKeys } from "@/utils/dataUtils";
import DeleteResourceModal from "./common_components/DeleteResourceModal";
import TableIconActionButton from "./common_components/IconActionButton/TableIconActionButtons/TableIconActionButton";
import { Member, teamCreateCall, v2TeamListCall } from "./networking";
import { ModelSelect } from "./ModelSelect/ModelSelect";
interface TeamInfo {
members_with_roles: Member[];
@@ -1064,11 +1065,11 @@ const Teams: React.FC<TeamProps> = ({
rules={
isOrgAdmin
? [
{
required: true,
message: "Please select an organization",
},
]
{
required: true,
message: "Please select an organization",
},
]
: []
}
help={
@@ -1135,16 +1136,17 @@ const Teams: React.FC<TeamProps> = ({
]}
name="models"
>
<Select2 mode="multiple" placeholder="Select models" style={{ width: "100%" }}>
<Select2.Option key="no-default-models" value="no-default-models">
No Default Models
</Select2.Option>
{modelsToPick.map((model) => (
<Select2.Option key={model} value={model}>
{getModelDisplayName(model)}
</Select2.Option>
))}
</Select2>
<ModelSelect
value={form.getFieldValue("models") || []}
onChange={(values) => form.setFieldValue("models", values)}
organizationID={form.getFieldValue("organization_id")}
options={{
includeSpecialOptions: true,
showAllProxyModelsOverride: !form.getFieldValue("organization_id"),
}}
context="team"
dataTestId="create-team-models-select"
/>
</Form.Item>
<Form.Item label="Max Budget (USD)" name="max_budget">
@@ -2457,6 +2457,7 @@ export const modelAvailableCall = async (
teamID: string | null = null,
include_model_access_groups: boolean = false,
only_model_access_groups: boolean = false,
scope?: string
) => {
/**
* Get all the models user has access to
@@ -2475,6 +2476,9 @@ export const modelAvailableCall = async (
if (teamID) {
params.append("team_id", teamID.toString());
}
if (scope) {
params.append("scope", scope);
}
if (params.toString()) {
url += `?${params.toString()}`;
}