Merge pull request #2102 from BerriAI/litellm_create_new_users

[FEAT] UI - Create Users on LiteLLM UI
This commit is contained in:
Ishaan Jaff 2024-02-20 17:11:30 -08:00 committed by GitHub
commit a4365926a1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 251 additions and 11 deletions

View file

@ -69,6 +69,9 @@ const CreateKeyPage = () => {
}
}
if (!userRole) {
return <div>Loading...</div>;
}
return (
<Suspense fallback={<div>Loading...</div>}>

View file

@ -78,15 +78,13 @@ const CreateKey: React.FC<CreateKeyProps> = ({
label="Key Name"
name="key_alias"
>
<Input />
<p style={{ fontStyle: 'italic', color: 'gray' }}>Optional</p>
<Input />
</Form.Item>
<Form.Item
label="Team ID"
name="team_id"
>
<Input placeholder="ai_team" />
<p style={{ fontStyle: 'italic', color: 'gray' }}>Optional</p>
</Form.Item>
<Form.Item
label="Models"
@ -103,44 +101,36 @@ const CreateKey: React.FC<CreateKeyProps> = ({
</Option>
))}
</Select>
<p style={{ fontStyle: 'italic', color: 'gray' }}>Optional, defaults to all models</p>
</Form.Item>
<Form.Item
label="Max Budget (USD)"
name="max_budget"
>
<InputNumber step={0.01} precision={2} width={200}/>
<p style={{ fontStyle: 'italic', color: 'gray' }}>Optional, defaults Unlimited Budget </p>
</Form.Item>
<Form.Item
label="Tokens per minute Limit (TPM)"
name="tpm_limit"
>
<InputNumber step={1} width={400}/>
<p style={{ fontStyle: 'italic', color: 'gray' }}>Optional, defaults to Unlimited TPM</p>
</Form.Item>
<Form.Item
label="Requests per minute Limit (RPM)"
name="rpm_limit"
>
<InputNumber step={1} width={400}/>
<p style={{ fontStyle: 'italic', color: 'gray' }}>Optional, defaults to Unlimited RPM</p>
</Form.Item>
<Form.Item
label="Duration (eg: 30s, 30h, 30d)"
name="duration"
>
<Input />
<p style={{ fontStyle: 'italic', color: 'gray' }}>Optional, defaults to never expiring key</p>
</Form.Item>
<Form.Item
label="Metadata"
name="metadata"
>
<Input.TextArea rows={4} placeholder="Enter metadata as JSON" />
<p style={{ fontStyle: 'italic', color: 'gray' }}>Optional, defaults to null</p>
</Form.Item>
</>
) : (

View file

@ -0,0 +1,176 @@
import React, { useState, useEffect } from "react";
import { Button, Modal, Form, Input, message, Select, InputNumber } from "antd";
import { Button as Button2 } from "@tremor/react";
import { userCreateCall, modelInfoCall } from "./networking";
const { Option } = Select;
interface CreateuserProps {
userID: string;
accessToken: string;
}
const Createuser: React.FC<CreateuserProps> = ({ userID, accessToken }) => {
const [form] = Form.useForm();
const [isModalVisible, setIsModalVisible] = useState(false);
const [apiuser, setApiuser] = useState<string | null>(null);
const [userModels, setUserModels] = useState<string[]>([]);
// get all models
useEffect(() => {
const fetchData = async () => {
try {
const userRole = "any"; // You may need to get the user role dynamically
const modelDataResponse = await modelInfoCall(accessToken, userID, userRole);
// Assuming modelDataResponse.data contains an array of model objects with a 'model_name' property
const availableModels = [];
for (let i = 0; i < modelDataResponse.data.length; i++) {
const model = modelDataResponse.data[i];
availableModels.push(model.model_name);
}
console.log("Model data response:", modelDataResponse.data);
console.log("Available models:", availableModels);
// Assuming modelDataResponse.data contains an array of model names
setUserModels(availableModels);
} catch (error) {
console.error("Error fetching model data:", error);
}
};
fetchData(); // Call the function to fetch model data when the component mounts
}, []); // Empty dependency array to run only once
const handleOk = () => {
setIsModalVisible(false);
form.resetFields();
};
const handleCancel = () => {
setIsModalVisible(false);
setApiuser(null);
form.resetFields();
};
const handleCreate = async (formValues: { user_id: string }) => {
try {
message.info("Making API Call");
setIsModalVisible(true);
console.log("formValues in create user:", formValues);
const response = await userCreateCall(accessToken, userID, formValues);
console.log("user create Response:", response);
setApiuser(response["key"]);
message.success("API user Created");
form.resetFields();
localStorage.removeItem("userData" + userID);
} catch (error) {
console.error("Error creating the user:", error);
}
};
return (
<div>
<Button2 className="mx-auto" onClick={() => setIsModalVisible(true)}>
+ Create New User
</Button2>
<Modal
title="Create User"
visible={isModalVisible}
width={800}
footer={null}
onOk={handleOk}
onCancel={handleCancel}
>
<Form form={form} onFinish={handleCreate} labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} labelAlign="left">
<Form.Item
label="User ID"
name="user_id"
>
<Input placeholder="Enter User ID" />
</Form.Item>
<Form.Item
label="Team ID"
name="team_id"
>
<Input placeholder="ai_team" />
</Form.Item>
<Form.Item
label="Models"
name="models"
>
<Select
mode="multiple"
placeholder="Select models"
style={{ width: '100%' }}
>
{userModels.map((model) => (
<Option key={model} value={model}>
{model}
</Option>
))}
</Select>
</Form.Item>
<Form.Item
label="Max Budget (USD)"
name="max_budget"
>
<InputNumber step={0.01} precision={2} width={200}/>
</Form.Item>
<Form.Item
label="Tokens per minute Limit (TPM)"
name="tpm_limit"
>
<InputNumber step={1} width={400}/>
</Form.Item>
<Form.Item
label="Requests per minute Limit (RPM)"
name="rpm_limit"
>
<InputNumber step={1} width={400}/>
</Form.Item>
<Form.Item
label="Duration (eg: 30s, 30h, 30d)"
name="duration"
>
<Input />
</Form.Item>
<Form.Item
label="Metadata"
name="metadata"
>
<Input.TextArea rows={4} placeholder="Enter metadata as JSON" />
</Form.Item>
<div style={{ textAlign: 'right', marginTop: '10px' }}>
<Button htmlType="submit">
Create User
</Button>
</div>
</Form>
</Modal>
{apiuser && (
<Modal
title="Save Your User"
visible={isModalVisible}
onOk={handleOk}
onCancel={handleCancel}
footer={null}
>
<p>
Please save this secret user somewhere safe and accessible. For
security reasons, <b>you will not be able to view it again</b> through
your LiteLLM account. If you lose this secret user, you will need to
generate a new one.
</p>
<p>{apiuser != null ? `API user: ${apiuser}` : "User being created, this might take 30s"}</p>
</Modal>
)}
</div>
);
};
export default Createuser;

View file

@ -69,6 +69,71 @@ export const keyCreateCall = async (
}
};
export const userCreateCall = async (
accessToken: string,
userID: string,
formValues: Record<string, any> // Assuming formValues is an object
) => {
try {
console.log("Form Values in keyCreateCall:", formValues); // Log the form values before making the API call
// check if formValues.description is not undefined, make it a string and add it to formValues.metadata
if (formValues.description) {
// add to formValues.metadata
if (!formValues.metadata) {
formValues.metadata = {};
}
// value needs to be in "", valid JSON
formValues.metadata.description = formValues.description;
// remove descrption from formValues
delete formValues.description;
formValues.metadata = JSON.stringify(formValues.metadata);
}
// if formValues.metadata is not undefined, make it a valid dict
if (formValues.metadata) {
console.log("formValues.metadata:", formValues.metadata);
// if there's an exception JSON.parse, show it in the message
try {
formValues.metadata = JSON.parse(formValues.metadata);
} catch (error) {
message.error("Failed to parse metadata: " + error);
throw new Error("Failed to parse metadata: " + error);
}
}
console.log("Form Values after check:", formValues);
const url = proxyBaseUrl ? `${proxyBaseUrl}/user/new` : `/user/new`;
const response = await fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: userID,
...formValues, // Include formValues in the request body
}),
});
if (!response.ok) {
const errorData = await response.text();
message.error("Failed to create key: " + errorData);
console.error("Error response from the server:", errorData);
throw new Error("Network response was not ok");
}
const data = await response.json();
console.log("API Response:", data);
return data;
// Handle success - you might want to update some state or UI based on the created key
} catch (error) {
console.error("Failed to create key:", error);
throw error;
}
};
export const keyDeleteCall = async (accessToken: String, user_key: String) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/key/delete` : `/key/delete`;

View file

@ -3,6 +3,7 @@ import { Card, Title, Subtitle, Table, TableHead, TableRow, TableCell, TableBody
import { userInfoCall } from "./networking";
import { Badge, BadgeDelta, Button } from '@tremor/react';
import RequestAccess from "./request_model_access";
import CreateUser from "./create_user_button";
interface ViewUserDashboardProps {
accessToken: string | null;
@ -55,6 +56,11 @@ const ViewUserDashboard: React.FC<ViewUserDashboardProps> = ({
return (
<div style={{ width: "100%" }}>
<Grid className="gap-2 p-10 h-[75vh] w-full">
<CreateUser
userID={userID}
userModels={["litellm-proxy-budget", "model2"]}
accessToken={accessToken}
/>
<Card>
<Table className="mt-5">
<TableHead>