mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-15 10:24:33 +00:00
Merge pull request #19543 from BerriAI/litellm_model_select_team
[Feature] UI - Create Team: Reusable Model Select
This commit is contained in:
@@ -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),
|
||||
});
|
||||
};
|
||||
|
||||
+1
-1
@@ -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()}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user