Merge pull request #1803 from BerriAI/litellm_ui_view_usage

[Feat] UI view spend / budget per user
This commit is contained in:
Ishaan Jaff 2024-02-03 14:38:03 -08:00 • committed by GitHub
commit 6eb616cf12
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 68 additions and 1 deletions

View file

@ -208,6 +208,7 @@ class UpdateUserRequest(GenerateRequestBase):
spend: Optional[float] = None
metadata: Optional[dict] = None
user_role: Optional[str] = None
max_budget: Optional[float] = None
class KeyManagementSystem(enum.Enum):

View file

@ -554,7 +554,6 @@ async def user_api_key_auth(
db=custom_db_client,
)
)
if (
route.startswith("/key/")
or route.startswith("/user/")
@ -589,6 +588,11 @@ async def user_api_key_auth(
status_code=status.HTTP_403_FORBIDDEN,
detail="user not allowed to access this key's info",
)
elif route == "/user/update":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="only proxy admin can update user settings. Tried calling `/user/update`",
)
elif route == "/model/info":
# /model/info just shows models user has access to
pass

View file

@ -4,6 +4,7 @@ import { userInfoCall } from "./networking";
import { Grid, Col, Card, Text } from "@tremor/react";
import CreateKey from "./create_key_button";
import ViewKeyTable from "./view_key_table";
import ViewUserSpend from "./view_user_spend";
import EnterProxyUrl from "./enter_proxy_url";
import Navbar from "./navbar";
import { useSearchParams } from "next/navigation";
@ -14,6 +15,7 @@ const proxyBaseUrl = "http://localhost:4000" // http://localhost:4000
const UserDashboard = () => {
const [data, setData] = useState<null | any[]>(null); // Keep the initialization of state here
const [userSpendData, setUserSpendData] = useState<null | any[]>(null);
// Assuming useSearchParams() hook exists and works in your setup
const searchParams = useSearchParams();
const userID = searchParams.get("userID");
@ -71,6 +73,8 @@ const UserDashboard = () => {
accessToken,
userID
);
console.log("Response:", response);
setUserSpendData(response["user_info"])
setData(response["keys"]); // Assuming this is the correct path to your data
} catch (error) {
console.error("There was an error fetching the data", error);
@ -104,6 +108,10 @@ const UserDashboard = () => {
/>
<Grid numItems={1} className="gap-0 p-10 h-[75vh] w-full">
<Col numColSpan={1}>
<ViewUserSpend
userID={userID}
userSpendData={userSpendData}
/>
<ViewKeyTable
userID={userID}
userRole={userRole}

View file

@ -0,0 +1,54 @@
"use client";
import React, { useEffect } from "react";
import { keyDeleteCall } from "./networking";
import { StatusOnlineIcon, TrashIcon } from "@heroicons/react/outline";
import { DonutChart } from "@tremor/react";
import {
Badge,
Card,
Table,
Metric,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
Text,
Title,
Icon,
} from "@tremor/react";
// Define the props type
interface UserSpendData {
spend: number; // Adjust the type accordingly based on your data
max_budget?: number | null; // Optional property with a default of null
// Add other properties if needed
}
interface ViewUserSpendProps {
userID: string | null;
userSpendData: UserSpendData | null; // Use the UserSpendData interface here
}
const ViewUserSpend: React.FC<ViewUserSpendProps> = ({ userID, userSpendData }) => {
console.log("User SpendData:", userSpendData);
const spend = userSpendData?.spend;
const maxBudget = userSpendData?.max_budget || null;
const displayMaxBudget = maxBudget !== null ? `$${maxBudget} limit` : "No limit";
const displayText = `$${spend} / ${displayMaxBudget}`;
return (
<>
<Card className="mx-auto mb-4">
<Metric>
${spend}
</Metric>
<Title>
/ {displayMaxBudget}
</Title>
</Card>
</>
)
}
export default ViewUserSpend;