mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): user_edit_view to shadcn
- @tremor Button/SelectItem/TextInput/Textarea + antd Checkbox/Tooltip + @ant-design InfoCircleOutlined \u2192 shadcn Button/Input/Textarea/ Checkbox/Tooltip + lucide Info. - antd Form/Form.Item/Select preserved (form.setFieldsValue + validation rules). Role + models multi-select kept on antd Select since shadcn Select doesn't support multiple. Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
1f4656bc4c
commit
afa60be4af
1 changed files with 83 additions and 37 deletions
|
|
@ -1,6 +1,15 @@
|
|||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { Button, SelectItem, TextInput, Textarea } from "@tremor/react";
|
||||
import { Checkbox, Form, Select, Tooltip } from "antd";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { Form, Select } from "antd";
|
||||
import { Info } from "lucide-react";
|
||||
import React, { useState } from "react";
|
||||
import { all_admin_roles } from "../utils/roles";
|
||||
import BudgetDurationDropdown from "./common_components/budget_duration_dropdown";
|
||||
|
|
@ -8,9 +17,12 @@ import { getModelDisplayName } from "./key_team_helpers/fetch_available_models_t
|
|||
import NumericalInput from "./shared/numerical_input";
|
||||
|
||||
interface UserEditViewProps {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
userData: any;
|
||||
onCancel: () => void;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
onSubmit: (values: any) => void;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
teams: any[] | null;
|
||||
accessToken: string | null;
|
||||
userID: string | null;
|
||||
|
|
@ -24,8 +36,11 @@ export function UserEditView({
|
|||
userData,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
teams,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
accessToken,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
userID,
|
||||
userRole,
|
||||
userModels,
|
||||
|
|
@ -35,7 +50,6 @@ export function UserEditView({
|
|||
const [form] = Form.useForm();
|
||||
const [unlimitedBudget, setUnlimitedBudget] = useState(false);
|
||||
|
||||
// Set initial form values
|
||||
React.useEffect(() => {
|
||||
const maxBudget = userData.user_info?.max_budget;
|
||||
const isUnlimited = maxBudget === null || maxBudget === undefined;
|
||||
|
|
@ -49,20 +63,21 @@ export function UserEditView({
|
|||
models: userData.user_info?.models || [],
|
||||
max_budget: isUnlimited ? "" : maxBudget,
|
||||
budget_duration: userData.user_info?.budget_duration,
|
||||
metadata: userData.user_info?.metadata ? JSON.stringify(userData.user_info.metadata, null, 2) : undefined,
|
||||
metadata: userData.user_info?.metadata
|
||||
? JSON.stringify(userData.user_info.metadata, null, 2)
|
||||
: undefined,
|
||||
});
|
||||
}, [userData, form]);
|
||||
|
||||
const handleUnlimitedBudgetChange = (e: any) => {
|
||||
const checked = e.target.checked;
|
||||
const handleUnlimitedBudgetChange = (checked: boolean) => {
|
||||
setUnlimitedBudget(checked);
|
||||
if (checked) {
|
||||
form.setFieldsValue({ max_budget: "" });
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const handleSubmit = (values: any) => {
|
||||
// Convert metadata back to an object if it exists and is a string
|
||||
if (values.metadata && typeof values.metadata === "string") {
|
||||
try {
|
||||
values.metadata = JSON.parse(values.metadata);
|
||||
|
|
@ -72,7 +87,11 @@ export function UserEditView({
|
|||
}
|
||||
}
|
||||
|
||||
if (unlimitedBudget || values.max_budget === "" || values.max_budget === undefined) {
|
||||
if (
|
||||
unlimitedBudget ||
|
||||
values.max_budget === "" ||
|
||||
values.max_budget === undefined
|
||||
) {
|
||||
values.max_budget = null;
|
||||
}
|
||||
|
||||
|
|
@ -83,43 +102,53 @@ export function UserEditView({
|
|||
<Form form={form} onFinish={handleSubmit} layout="vertical">
|
||||
{!isBulkEdit && (
|
||||
<Form.Item label="User ID" name="user_id">
|
||||
<TextInput disabled />
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
{!isBulkEdit && (
|
||||
<Form.Item label="Email" name="user_email">
|
||||
<TextInput />
|
||||
<Input />
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<Form.Item label="User Alias" name="user_alias">
|
||||
<TextInput />
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={
|
||||
<span>
|
||||
Global Proxy Role{" "}
|
||||
<Tooltip title="This is the role that the user will globally on the proxy. This role is independent of any team/org specific roles.">
|
||||
<InfoCircleOutlined />
|
||||
</Tooltip>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="ml-1 h-3 w-3 inline text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs">
|
||||
This is the role that the user will globally on the proxy.
|
||||
This role is independent of any team/org specific roles.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</span>
|
||||
}
|
||||
name="user_role"
|
||||
>
|
||||
<Select>
|
||||
{possibleUIRoles &&
|
||||
Object.entries(possibleUIRoles).map(([role, { ui_label, description }]) => (
|
||||
<SelectItem key={role} value={role} title={ui_label}>
|
||||
<div className="flex">
|
||||
{ui_label}{" "}
|
||||
<p className="ml-2" style={{ color: "gray", fontSize: "12px" }}>
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
Object.entries(possibleUIRoles).map(
|
||||
([role, { ui_label, description }]) => (
|
||||
<Select.Option key={role} value={role} title={ui_label}>
|
||||
<div className="flex">
|
||||
{ui_label}{" "}
|
||||
<p className="ml-2 text-muted-foreground text-xs">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</Select.Option>
|
||||
),
|
||||
)}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
|
|
@ -127,9 +156,18 @@ export function UserEditView({
|
|||
label={
|
||||
<span>
|
||||
Personal Models{" "}
|
||||
<Tooltip title="Select which models this user can access outside of team-scope. Choose 'All Proxy Models' to grant access to all models available on the proxy.">
|
||||
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
|
||||
</Tooltip>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="ml-1 h-3 w-3 inline text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs">
|
||||
Select which models this user can access outside of
|
||||
team-scope. Choose 'All Proxy Models' to grant
|
||||
access to all models available on the proxy.
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</span>
|
||||
}
|
||||
name="models"
|
||||
|
|
@ -156,22 +194,30 @@ export function UserEditView({
|
|||
|
||||
<Form.Item
|
||||
label={
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "12px" }}>
|
||||
<div className="flex items-center gap-3">
|
||||
<span>Max Budget (USD)</span>
|
||||
<Checkbox
|
||||
checked={unlimitedBudget}
|
||||
onChange={handleUnlimitedBudgetChange}
|
||||
>
|
||||
Unlimited Budget
|
||||
</Checkbox>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<Checkbox
|
||||
checked={unlimitedBudget}
|
||||
onCheckedChange={(c) => handleUnlimitedBudgetChange(c === true)}
|
||||
/>
|
||||
<span className="text-sm">Unlimited Budget</span>
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
name="max_budget"
|
||||
rules={[
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (!unlimitedBudget && (value === "" || value === null || value === undefined)) {
|
||||
return Promise.reject(new Error("Please enter a budget or select Unlimited Budget"));
|
||||
if (
|
||||
!unlimitedBudget &&
|
||||
(value === "" || value === null || value === undefined)
|
||||
) {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
"Please enter a budget or select Unlimited Budget",
|
||||
),
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue