diff --git a/ui/litellm-dashboard/src/components/OldTeams.tsx b/ui/litellm-dashboard/src/components/OldTeams.tsx index 14927162b92..57612705086 100644 --- a/ui/litellm-dashboard/src/components/OldTeams.tsx +++ b/ui/litellm-dashboard/src/components/OldTeams.tsx @@ -5,6 +5,7 @@ import TeamSSOSettings from "@/components/TeamSSOSettings"; import { isProxyAdminRole } from "@/utils/roles"; import { InfoCircleOutlined, + MinusCircleOutlined, PlusOutlined, TeamOutlined, ReloadOutlined, @@ -21,6 +22,7 @@ import { Flex, Form, Input, + InputNumber, Layout, Modal, Pagination, @@ -172,6 +174,127 @@ const getOrganizationAlias = ( return organization?.organization_alias || organizationId; }; +const BUDGET_DURATION_OPTIONS = [ + { label: "No reset", value: "" }, + { label: "Daily", value: "24h" }, + { label: "Weekly", value: "7d" }, + { label: "Monthly", value: "30d" }, + { label: "Yearly", value: "365d" }, +]; + +interface ModelBudgetRow { + id: string; + model: string; + max_budget: number | null; + budget_duration: string; +} + +type ModelBudgetValue = Record; + +interface ModelBudgetEditorProps { + value?: ModelBudgetValue; + onChange?: (val: ModelBudgetValue | undefined) => void; + availableModels: string[]; +} + +const ModelBudgetEditor: React.FC = ({ value, onChange, availableModels }) => { + const [rows, setRows] = useState(() => { + if (!value || Object.keys(value).length === 0) return []; + return Object.entries(value).map(([model, cfg]) => ({ + id: Math.random().toString(36).slice(2), + model, + max_budget: cfg.max_budget ?? null, + budget_duration: cfg.budget_duration ?? "", + })); + }); + + const notifyChange = (newRows: ModelBudgetRow[]) => { + if (!onChange) return; + const filled = newRows.filter((r) => r.model); + if (filled.length === 0) { + onChange(undefined); + return; + } + const obj: ModelBudgetValue = {}; + for (const r of filled) { + obj[r.model] = { + max_budget: r.max_budget ?? 0, + ...(r.budget_duration ? { budget_duration: r.budget_duration } : {}), + }; + } + onChange(obj); + }; + + const addRow = () => { + const newRows = [ + ...rows, + { id: Math.random().toString(36).slice(2), model: "", max_budget: null, budget_duration: "" }, + ]; + setRows(newRows); + }; + + const updateRow = (id: string, field: keyof ModelBudgetRow, val: any) => { + const newRows = rows.map((r) => (r.id === id ? { ...r, [field]: val } : r)); + setRows(newRows); + notifyChange(newRows); + }; + + const removeRow = (id: string) => { + const newRows = rows.filter((r) => r.id !== id); + setRows(newRows); + notifyChange(newRows); + }; + + return ( +
+ {rows.map((row) => ( +
+ updateRow(row.id, "budget_duration", val)} + options={BUDGET_DURATION_OPTIONS} + /> +
+ ))} + +
+ ); +}; + // @deprecated const Teams: React.FC = ({ teams, @@ -563,21 +686,6 @@ const Teams: React.FC = ({ delete formValues.allowed_agents_and_groups; } - // Parse team_member_model_max_budget from JSON string - if (formValues.team_member_model_max_budget) { - if (typeof formValues.team_member_model_max_budget === "string") { - if (formValues.team_member_model_max_budget.trim() === "") { - delete formValues.team_member_model_max_budget; - } else { - try { - formValues.team_member_model_max_budget = JSON.parse(formValues.team_member_model_max_budget); - } catch (e) { - throw new Error("Failed to parse team member model budget: " + e); - } - } - } - } - // Add model_aliases if any are defined if (Object.keys(modelAliases).length > 0) { formValues.model_aliases = modelAliases; @@ -1242,9 +1350,9 @@ const Teams: React.FC = ({ - +