mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): common_components/ModelAliasManager to shadcn
- @tremor Card/Title/Text + @heroicons PlusCircle/Pencil/Trash \u2192 shadcn Card/Button/Input + lucide Plus/Pencil/Trash2. - Tremor Table family preserved (phase 1 whitelist). - Edit/Delete action chips keep categorical blue/red tints; file added to .eslintrc.json overrides. Added-alias button reuses categorical emerald when enabled. Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
3ee523b8ae
commit
5ff1af0dea
2 changed files with 124 additions and 86 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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<ModelAliasManagerProps> = ({
|
|||
const [editingAlias, setEditingAlias] = useState<AliasItem | null>(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<ModelAliasManagerProps> = ({
|
|||
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<ModelAliasManagerProps> = ({
|
|||
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<ModelAliasManagerProps> = ({
|
|||
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<ModelAliasManagerProps> = ({
|
|||
{} as { [key: string]: string },
|
||||
);
|
||||
|
||||
const addDisabled = !newAlias.aliasName || !newAlias.targetModel;
|
||||
|
||||
return (
|
||||
<div className="mt-4">
|
||||
<div className="mb-6">
|
||||
<Text className="text-sm font-medium text-gray-700 mb-2">Add New Alias</Text>
|
||||
<p className="text-sm font-medium text-foreground mb-2">Add New Alias</p>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="block text-xs text-gray-500 mb-1">Alias Name</label>
|
||||
<input
|
||||
<label className="block text-xs text-muted-foreground mb-1">
|
||||
Alias Name
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
value={newAlias.aliasName}
|
||||
onChange={(e) =>
|
||||
|
|
@ -155,11 +164,12 @@ const ModelAliasManager: React.FC<ModelAliasManagerProps> = ({
|
|||
})
|
||||
}
|
||||
placeholder="e.g., gpt-4o"
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-xs text-gray-500 mb-1">Target Model</label>
|
||||
<label className="block text-xs text-muted-foreground mb-1">
|
||||
Target Model
|
||||
</label>
|
||||
<ModelSelector
|
||||
accessToken={accessToken}
|
||||
value={newAlias.targetModel}
|
||||
|
|
@ -174,26 +184,35 @@ const ModelAliasManager: React.FC<ModelAliasManagerProps> = ({
|
|||
/>
|
||||
</div>
|
||||
<div className="flex items-end">
|
||||
<button
|
||||
<Button
|
||||
onClick={handleAddAlias}
|
||||
disabled={!newAlias.aliasName || !newAlias.targetModel}
|
||||
className={`flex items-center px-4 py-2 rounded-md text-sm ${!newAlias.aliasName || !newAlias.targetModel ? "bg-gray-300 text-gray-500 cursor-not-allowed" : "bg-green-600 text-white hover:bg-green-700"}`}
|
||||
disabled={addDisabled}
|
||||
className={cn(
|
||||
!addDisabled &&
|
||||
"bg-emerald-600 text-white hover:bg-emerald-700",
|
||||
)}
|
||||
>
|
||||
<PlusCircleIcon className="w-4 h-4 mr-1" />
|
||||
<Plus className="h-4 w-4" />
|
||||
Add Alias
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Text className="text-sm font-medium text-gray-700 mb-2">Manage Existing Aliases</Text>
|
||||
<p className="text-sm font-medium text-foreground mb-2">
|
||||
Manage Existing Aliases
|
||||
</p>
|
||||
<div className="rounded-lg custom-border relative mb-6">
|
||||
<div className="overflow-x-auto">
|
||||
<Table className="[&_td]:py-0.5 [&_th]:py-1">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableHeaderCell className="py-1 h-8">Alias Name</TableHeaderCell>
|
||||
<TableHeaderCell className="py-1 h-8">Target Model</TableHeaderCell>
|
||||
<TableHeaderCell className="py-1 h-8">
|
||||
Alias Name
|
||||
</TableHeaderCell>
|
||||
<TableHeaderCell className="py-1 h-8">
|
||||
Target Model
|
||||
</TableHeaderCell>
|
||||
<TableHeaderCell className="py-1 h-8">Actions</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
|
|
@ -203,7 +222,7 @@ const ModelAliasManager: React.FC<ModelAliasManagerProps> = ({
|
|||
{editingAlias && editingAlias.id === alias.id ? (
|
||||
<>
|
||||
<TableCell className="py-0.5">
|
||||
<input
|
||||
<Input
|
||||
type="text"
|
||||
value={editingAlias.aliasName}
|
||||
onChange={(e) =>
|
||||
|
|
@ -212,7 +231,7 @@ const ModelAliasManager: React.FC<ModelAliasManagerProps> = ({
|
|||
aliasName: e.target.value,
|
||||
})
|
||||
}
|
||||
className="w-full px-2 py-1 border border-gray-300 rounded-md text-sm"
|
||||
className="h-8"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="py-0.5">
|
||||
|
|
@ -231,39 +250,53 @@ const ModelAliasManager: React.FC<ModelAliasManagerProps> = ({
|
|||
</TableCell>
|
||||
<TableCell className="py-0.5 whitespace-nowrap">
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onClick={handleUpdateAlias}
|
||||
className="text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded hover:bg-blue-100"
|
||||
className="h-7"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={handleCancelEdit}
|
||||
className="text-xs bg-gray-50 text-gray-600 px-2 py-1 rounded hover:bg-gray-100"
|
||||
className="h-7"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<TableCell className="py-0.5 text-sm text-gray-900">{alias.aliasName}</TableCell>
|
||||
<TableCell className="py-0.5 text-sm text-gray-500">{alias.targetModel}</TableCell>
|
||||
<TableCell className="py-0.5 text-sm text-foreground">
|
||||
{alias.aliasName}
|
||||
</TableCell>
|
||||
<TableCell className="py-0.5 text-sm text-muted-foreground">
|
||||
{alias.targetModel}
|
||||
</TableCell>
|
||||
<TableCell className="py-0.5 whitespace-nowrap">
|
||||
<div className="flex space-x-2">
|
||||
<button
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-7 w-7 text-blue-600 bg-blue-50 hover:bg-blue-100 dark:bg-blue-950/30 dark:hover:bg-blue-950/60 dark:text-blue-300"
|
||||
onClick={() => handleEditAlias(alias)}
|
||||
className="text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded hover:bg-blue-100"
|
||||
aria-label="Edit alias"
|
||||
>
|
||||
<PencilIcon className="w-3 h-3" />
|
||||
</button>
|
||||
<button
|
||||
<Pencil className="h-3 w-3" />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-7 w-7 text-destructive bg-red-50 hover:bg-red-100 dark:bg-red-950/30 dark:hover:bg-red-950/60"
|
||||
onClick={() => deleteAlias(alias.id)}
|
||||
className="text-xs bg-red-50 text-red-600 px-2 py-1 rounded hover:bg-red-100"
|
||||
aria-label="Delete alias"
|
||||
>
|
||||
<TrashIcon className="w-3 h-3" />
|
||||
</button>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</>
|
||||
|
|
@ -272,7 +305,10 @@ const ModelAliasManager: React.FC<ModelAliasManagerProps> = ({
|
|||
))}
|
||||
{aliases.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={3} className="py-0.5 text-sm text-gray-500 text-center">
|
||||
<TableCell
|
||||
colSpan={3}
|
||||
className="py-0.5 text-sm text-muted-foreground text-center"
|
||||
>
|
||||
No aliases added yet. Add a new alias above.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
|
@ -282,16 +318,17 @@ const ModelAliasManager: React.FC<ModelAliasManagerProps> = ({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Configuration Example */}
|
||||
{showExampleConfig && (
|
||||
<Card>
|
||||
<Title className="mb-4">Configuration Example</Title>
|
||||
<Text className="text-gray-600 mb-4">Here's how your current aliases would look in the config:</Text>
|
||||
<div className="bg-gray-100 rounded-lg p-4 font-mono text-sm">
|
||||
<div className="text-gray-700">
|
||||
<Card className="p-4">
|
||||
<h5 className="mb-4 font-semibold">Configuration Example</h5>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
Here's how your current aliases would look in the config:
|
||||
</p>
|
||||
<div className="bg-muted rounded-lg p-4 font-mono text-sm">
|
||||
<div className="text-foreground">
|
||||
model_aliases:
|
||||
{Object.keys(aliasObject).length === 0 ? (
|
||||
<span className="text-gray-500">
|
||||
<span className="text-muted-foreground">
|
||||
<br />
|
||||
# No aliases configured yet
|
||||
</span>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue