mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
Merge pull request #2077 from BerriAI/litellm_request_model_access
[Feat] UI - allow a user to request access to a model
This commit is contained in:
commit
45326c93dc
5 changed files with 141 additions and 3 deletions
|
|
@ -687,6 +687,8 @@ async def user_api_key_auth(
|
|||
elif route == "/model/info":
|
||||
# /model/info just shows models user has access to
|
||||
pass
|
||||
elif route == "/user/request_model":
|
||||
pass # this allows any user to request a model through the UI
|
||||
elif allow_user_auth == True and route == "/key/generate":
|
||||
pass
|
||||
elif allow_user_auth == True and route == "/key/delete":
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { Card, Title, Subtitle, Table, TableHead, TableRow, TableCell, TableBody, Metric, Grid } from "@tremor/react";
|
||||
import { modelInfoCall } from "./networking";
|
||||
import { Badge, BadgeDelta } from '@tremor/react';
|
||||
import { Badge, BadgeDelta, Button } from '@tremor/react';
|
||||
import RequestAccess from "./request_model_access";
|
||||
|
||||
interface ModelDashboardProps {
|
||||
accessToken: string | null;
|
||||
|
|
@ -41,6 +42,7 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
if (!modelData) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
let all_models_on_proxy: any[] = [];
|
||||
|
||||
// loop through model data and edit each row
|
||||
for (let i = 0; i < modelData.data.length; i++) {
|
||||
|
|
@ -82,9 +84,12 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
modelData.data[i].output_cost = output_cost
|
||||
modelData.data[i].max_tokens = max_tokens
|
||||
|
||||
all_models_on_proxy.push(curr_model.model_name);
|
||||
|
||||
console.log(modelData.data[i]);
|
||||
|
||||
}
|
||||
// when users click request access show pop up to allow them to request access
|
||||
|
||||
return (
|
||||
<div style={{ width: "100%" }}>
|
||||
|
|
@ -109,7 +114,7 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
|
|||
<TableCell>{model.provider}</TableCell>
|
||||
|
||||
<TableCell>
|
||||
{model.user_access ? <Badge color={"green"}>Yes</Badge> : <Badge color={"red"}>Request Access</Badge>}
|
||||
{model.user_access ? <Badge color={"green"}>Yes</Badge> : <RequestAccess userModels={all_models_on_proxy} accessToken={accessToken} userID={userID}></RequestAccess>}
|
||||
</TableCell>
|
||||
|
||||
<TableCell>{model.input_cost}</TableCell>
|
||||
|
|
|
|||
|
|
@ -292,3 +292,38 @@ export const spendUsersCall = async (accessToken: String, userID: String) => {
|
|||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
export const userRequestModelCall = async (accessToken: String, model: String, UserID: String, justification: String) => {
|
||||
try {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/user/request_model` : `user/request_model`;
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
models: [model],
|
||||
user_id: UserID,
|
||||
justification: justification,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.text();
|
||||
message.error("Failed to delete key: " + errorData);
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
message.success("");
|
||||
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;
|
||||
}
|
||||
};
|
||||
96
ui/litellm-dashboard/src/components/request_model_access.tsx
Normal file
96
ui/litellm-dashboard/src/components/request_model_access.tsx
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { Modal, Form, Input, Select, InputNumber, message } from "antd";
|
||||
import { Button } from "@tremor/react";
|
||||
import { userRequestModelCall } from "./networking";
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
interface RequestAccessProps {
|
||||
userModels: string[];
|
||||
accessToken: string;
|
||||
userID: string;
|
||||
}
|
||||
|
||||
function onRequestAccess(formData: Record<string, any>): void {
|
||||
// This function does nothing for now
|
||||
}
|
||||
|
||||
const RequestAccess: React.FC<RequestAccessProps> = ({ userModels, accessToken, userID }) => {
|
||||
const [form] = Form.useForm();
|
||||
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||
|
||||
const handleOk = () => {
|
||||
setIsModalVisible(false);
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setIsModalVisible(false);
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
const handleRequestAccess = async (formValues: Record<string, any>) => {
|
||||
try {
|
||||
message.info("Requesting access");
|
||||
// Extract form values
|
||||
const { selectedModel, accessReason } = formValues;
|
||||
|
||||
// Call userRequestModelCall
|
||||
const response = await userRequestModelCall(
|
||||
accessToken, // You need to have accessToken available
|
||||
selectedModel,
|
||||
userID, // You need to have UserID available
|
||||
accessReason
|
||||
);
|
||||
|
||||
onRequestAccess(formValues);
|
||||
setIsModalVisible(true);
|
||||
} catch (error) {
|
||||
console.error("Error requesting access:", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button size="xs" onClick={() => setIsModalVisible(true)}>
|
||||
Request Access
|
||||
</Button>
|
||||
<Modal
|
||||
title="Request Access"
|
||||
visible={isModalVisible}
|
||||
width={800}
|
||||
footer={null}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleRequestAccess}
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 16 }}
|
||||
labelAlign="left"
|
||||
>
|
||||
<Form.Item label="Select Model" name="selectedModel">
|
||||
<Select placeholder="Select model" style={{ width: '100%' }}>
|
||||
{userModels.map((model) => (
|
||||
<Option key={model} value={model}>
|
||||
{model}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item label="Reason for Access" name="accessReason">
|
||||
<Input.TextArea rows={4} placeholder="Enter reason for access" />
|
||||
</Form.Item>
|
||||
<div style={{ textAlign: 'right', marginTop: '10px' }}>
|
||||
<Button>Request Access</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RequestAccess;
|
||||
|
|
@ -129,7 +129,7 @@ const UserDashboard: React.FC<UserDashboardProps> = ({
|
|||
const model_info = await modelInfoCall(accessToken, userID, userRole);
|
||||
console.log("model_info:", model_info);
|
||||
// loop through model_info["data"] and create an array of element.model_name
|
||||
let available_model_names = model_info["data"].map((element: { model_name: string; }) => element.model_name);
|
||||
let available_model_names = model_info["data"].filter((element: { model_name: string; user_access: boolean }) => element.user_access === true).map((element: { model_name: string; }) => element.model_name);
|
||||
console.log("available_model_names:", available_model_names);
|
||||
setUserModels(available_model_names);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue