diff --git a/ui/litellm-dashboard/.eslintrc.json b/ui/litellm-dashboard/.eslintrc.json index f8f6b92aba4..a5d8e6a05bf 100644 --- a/ui/litellm-dashboard/.eslintrc.json +++ b/ui/litellm-dashboard/.eslintrc.json @@ -60,6 +60,7 @@ "src/components/policies/policy_table.tsx", "src/components/guardrails/guardrail_table.tsx", "src/components/prompts/prompt_table.tsx", + "src/components/common_components/ModelAliasManager.tsx", "src/components/permissions/VectorStorePermissions.tsx", "src/components/permissions/AgentPermissions.tsx", "src/components/permissions/MCPServerPermissions.tsx", diff --git a/ui/litellm-dashboard/src/components/common_components/ModelAliasManager.tsx b/ui/litellm-dashboard/src/components/common_components/ModelAliasManager.tsx index b4b1fa779c6..0b4a1f26653 100644 --- a/ui/litellm-dashboard/src/components/common_components/ModelAliasManager.tsx +++ b/ui/litellm-dashboard/src/components/common_components/ModelAliasManager.tsx @@ -1,6 +1,18 @@ import React, { useState, useEffect } from "react"; -import { PlusCircleIcon, PencilIcon, TrashIcon } from "@heroicons/react/outline"; -import { Card, Title, Text, Table, TableHead, TableHeaderCell, TableBody, TableRow, TableCell } from "@tremor/react"; +import { Pencil, Plus, Trash2 } from "lucide-react"; +// eslint-disable-next-line litellm-ui/no-banned-ui-imports +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeaderCell, + TableRow, +} from "@tremor/react"; +import { Button } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { cn } from "@/lib/utils"; import ModelSelector from "./ModelSelector"; import NotificationsManager from "../molecules/notifications_manager"; @@ -28,24 +40,34 @@ const ModelAliasManager: React.FC = ({ const [editingAlias, setEditingAlias] = useState(null); useEffect(() => { - // Convert object to array for display - const aliasArray = Object.entries(initialModelAliases).map(([aliasName, targetModel], index) => ({ - id: `${index}-${aliasName}`, - aliasName, - targetModel, - })); + const aliasArray = Object.entries(initialModelAliases).map( + ([aliasName, targetModel], index) => ({ + id: `${index}-${aliasName}`, + aliasName, + targetModel, + }), + ); setAliases(aliasArray); }, [initialModelAliases]); + const notifyParent = (list: AliasItem[]) => { + const obj: { [key: string]: string } = {}; + list.forEach((a) => (obj[a.aliasName] = a.targetModel)); + if (onAliasUpdate) onAliasUpdate(obj); + }; + const handleAddAlias = () => { if (!newAlias.aliasName || !newAlias.targetModel) { - NotificationsManager.fromBackend("Please provide both alias name and target model"); + NotificationsManager.fromBackend( + "Please provide both alias name and target model", + ); return; } - // Check for duplicate alias names if (aliases.some((alias) => alias.aliasName === newAlias.aliasName)) { - NotificationsManager.fromBackend("An alias with this name already exists"); + NotificationsManager.fromBackend( + "An alias with this name already exists", + ); return; } @@ -58,16 +80,7 @@ const ModelAliasManager: React.FC = ({ const updatedAliases = [...aliases, newAliasObj]; setAliases(updatedAliases); setNewAlias({ aliasName: "", targetModel: "" }); - - // Convert array back to object format and notify parent - const aliasObject: { [key: string]: string } = {}; - updatedAliases.forEach((alias) => { - aliasObject[alias.aliasName] = alias.targetModel; - }); - - if (onAliasUpdate) { - onAliasUpdate(aliasObject); - } + notifyParent(updatedAliases); NotificationsManager.success("Alias added successfully"); }; @@ -80,30 +93,32 @@ const ModelAliasManager: React.FC = ({ if (!editingAlias) return; if (!editingAlias.aliasName || !editingAlias.targetModel) { - NotificationsManager.fromBackend("Please provide both alias name and target model"); + NotificationsManager.fromBackend( + "Please provide both alias name and target model", + ); return; } - // Check for duplicate alias names (excluding current alias) - if (aliases.some((alias) => alias.id !== editingAlias.id && alias.aliasName === editingAlias.aliasName)) { - NotificationsManager.fromBackend("An alias with this name already exists"); + if ( + aliases.some( + (alias) => + alias.id !== editingAlias.id && + alias.aliasName === editingAlias.aliasName, + ) + ) { + NotificationsManager.fromBackend( + "An alias with this name already exists", + ); return; } - const updatedAliases = aliases.map((alias) => (alias.id === editingAlias.id ? editingAlias : alias)); + const updatedAliases = aliases.map((alias) => + alias.id === editingAlias.id ? editingAlias : alias, + ); setAliases(updatedAliases); setEditingAlias(null); - - // Convert array back to object format and notify parent - const aliasObject: { [key: string]: string } = {}; - updatedAliases.forEach((alias) => { - aliasObject[alias.aliasName] = alias.targetModel; - }); - - if (onAliasUpdate) { - onAliasUpdate(aliasObject); - } + notifyParent(updatedAliases); NotificationsManager.success("Alias updated successfully"); }; @@ -115,21 +130,11 @@ const ModelAliasManager: React.FC = ({ const deleteAlias = (aliasId: string) => { const updatedAliases = aliases.filter((alias) => alias.id !== aliasId); setAliases(updatedAliases); - - // Convert array back to object format and notify parent - const aliasObject: { [key: string]: string } = {}; - updatedAliases.forEach((alias) => { - aliasObject[alias.aliasName] = alias.targetModel; - }); - - if (onAliasUpdate) { - onAliasUpdate(aliasObject); - } + notifyParent(updatedAliases); NotificationsManager.success("Alias deleted successfully"); }; - // Convert current aliases to object for config example const aliasObject = aliases.reduce( (acc, alias) => { acc[alias.aliasName] = alias.targetModel; @@ -138,14 +143,18 @@ const ModelAliasManager: React.FC = ({ {} as { [key: string]: string }, ); + const addDisabled = !newAlias.aliasName || !newAlias.targetModel; + return (
- Add New Alias +

Add New Alias

- - + Alias Name + + @@ -155,11 +164,12 @@ const ModelAliasManager: React.FC = ({ }) } placeholder="e.g., gpt-4o" - className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm" />
- + = ({ />
- +
- Manage Existing Aliases +

+ Manage Existing Aliases +

- Alias Name - Target Model + + Alias Name + + + Target Model + Actions @@ -203,7 +222,7 @@ const ModelAliasManager: React.FC = ({ {editingAlias && editingAlias.id === alias.id ? ( <> - @@ -212,7 +231,7 @@ const ModelAliasManager: React.FC = ({ aliasName: e.target.value, }) } - className="w-full px-2 py-1 border border-gray-300 rounded-md text-sm" + className="h-8" /> @@ -231,39 +250,53 @@ const ModelAliasManager: React.FC = ({
- - +
) : ( <> - {alias.aliasName} - {alias.targetModel} + + {alias.aliasName} + + + {alias.targetModel} +
- - + + +
@@ -272,7 +305,10 @@ const ModelAliasManager: React.FC = ({ ))} {aliases.length === 0 && ( - + No aliases added yet. Add a new alias above. @@ -282,16 +318,17 @@ const ModelAliasManager: React.FC = ({ - {/* Configuration Example */} {showExampleConfig && ( - - Configuration Example - Here's how your current aliases would look in the config: -
-
+ +
Configuration Example
+

+ Here's how your current aliases would look in the config: +

+
+
model_aliases: {Object.keys(aliasObject).length === 0 ? ( - +
  # No aliases configured yet