fix(ui): let admins change a model's team from the model edit page

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Devin AI 2026-09-11 08:54:05 +00:00 • committed by ryan-crabbe-berri
parent 10616d7407
commit a7daa21df7
3 changed files with 73 additions and 5 deletions

View file

@ -24,6 +24,7 @@ import CacheControlInjectionPoints, {
CACHE_CONTROL_TOOLTIP,
type CacheControlInjectionPoint,
} from "./add_model/cache_control_settings";
import type { Team } from "./key_team_helpers/key_list";
import type { CredentialItem } from "./networking";
import NumericalInput from "./shared/numerical_input";
import type { Tag } from "./tag_management/types";
@ -103,6 +104,7 @@ export interface ModelEditFormValues {
litellm_credential_name?: string;
litellm_extra_params?: string;
model_info?: string;
team_id?: string;
}
type ModelEditFieldName = keyof ModelEditFormValues;
@ -139,6 +141,7 @@ const modelEditShape = {
litellm_credential_name: textish,
litellm_extra_params: textish,
model_info: textish,
team_id: textish,
};
const isJson = (value: string): boolean => {
@ -260,6 +263,7 @@ export const toModelEditFormValues = (localModelData: any, isWildcardModel: bool
null,
2,
),
team_id: localModelData.model_info?.team_id ?? undefined,
});
const displayCost = (localModelData: any, field: TouchedPricingField): string => {
@ -286,6 +290,7 @@ interface ModelInfoEditFormProps {
tagsList: Record<string, Tag>;
credentialsList: CredentialItem[];
healthCheckModelOptions: { value: string; label: string }[];
teams: Team[] | null;
}
const Display: React.FC<{ children: React.ReactNode }> = ({ children }) => (
@ -357,6 +362,7 @@ const ModelInfoEditForm: React.FC<ModelInfoEditFormProps> = ({
tagsList,
credentialsList,
healthCheckModelOptions,
teams,
}) => {
// Neither RHF's blur-based touchedFields nor its resettable dirtyFields matches antd's touched-on-change.
const touchedRef = React.useRef<ReadonlySet<string>>(new Set<string>());
@ -802,11 +808,40 @@ const ModelInfoEditForm: React.FC<ModelInfoEditFormProps> = ({
<div>
<FieldLabel>Team</FieldLabel>
<Display>
{teamAlias
? `${teamAlias} (${modelData.model_info.team_id})`
: modelData.model_info.team_id || "Not Set"}
</Display>
{isEditing ? (
<FormField control={form.control} name="team_id">
{({ id, value, onChange, onBlur }) => {
const items = (teams ?? []).map((team) => ({
value: team.team_id,
label: team.team_alias ? `${team.team_alias} (${team.team_id})` : team.team_id,
}));
return (
<Select
items={items}
value={(value as string) ?? ""}
onValueChange={(selected: string | null) => onChange(selected ?? "")}
>
<SelectTrigger id={id} className="w-full" onBlur={onBlur}>
<SelectValue placeholder="Select a team" />
</SelectTrigger>
<SelectContent>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
</SelectContent>
</Select>
);
}}
</FormField>
) : (
<Display>
{teamAlias
? `${teamAlias} (${modelData.model_info.team_id})`
: modelData.model_info.team_id || "Not Set"}
</Display>
)}
</div>
</div>

View file

@ -1642,6 +1642,32 @@ describe("ModelInfoView", () => {
expect(payload.model_info).toMatchObject({ team_id: "team-7" });
});
it("sends the team picked in the Team ID selector", async () => {
mockUseTeams.mockReturnValue({
data: [
{ team_id: "team-1", team_alias: "alpha" },
{ team_id: "team-2", team_alias: "beta" },
],
isLoading: false,
error: null,
});
const teamModel = {
...defaultModelData,
model_info: { ...defaultModelData.model_info, team_id: "team-1" },
};
mockUseModelsInfo.mockReturnValue({ data: { data: [teamModel] }, isLoading: false, error: null });
mockModelInfoV1Call.mockResolvedValue({ data: [teamModel] });
const user = userEvent.setup();
await enterEditMode(user);
await user.click(screen.getByText("alpha (team-1)"));
await user.click(await screen.findByText("beta (team-2)"));
const payload = await save(user);
expect(payload.model_info.team_id).toBe("team-2");
});
it("sends the edited LiteLLM extra params", async () => {
const user = userEvent.setup();
await enterEditMode(user);

View file

@ -376,6 +376,12 @@ export default function ModelInfoView({
health_check_model: values.health_check_model,
};
}
if (values.team_id) {
updatedModelInfo = {
...updatedModelInfo,
team_id: values.team_id,
};
}
updatedModelInfo = applyPtuModelInfo(updatedModelInfo, values, ptuCostAttributionEnabled);
} catch (e) {
toast.fromError("Invalid JSON in Model Info");
@ -736,6 +742,7 @@ export default function ModelInfoView({
tagsList={tagsList}
credentialsList={credentialsList}
healthCheckModelOptions={healthCheckModelOptions}
teams={teams ?? null}
/>
) : (
<p className="text-sm">Loading...</p>