From c9a32ebf76d3e3f162a721cc484683dc9c4a6009 Mon Sep 17 00:00:00 2001
From: Ishaan Jaff
Date: Fri, 24 Jan 2025 18:49:32 -0800
Subject: [PATCH] (UI) - Usage page show days when spend is 0 and round spend
figures on charts to 2 sig figs (#7991)
* ui with cyan
* ui format usage tab better
* fix activity page
* ui fix activity page
* ui show correct usage
* ui usage page
---
ui/litellm-dashboard/src/components/usage.tsx | 222 ++++++++++++++----
1 file changed, 182 insertions(+), 40 deletions(-)
diff --git a/ui/litellm-dashboard/src/components/usage.tsx b/ui/litellm-dashboard/src/components/usage.tsx
index c2025112c7d..053f69c256f 100644
--- a/ui/litellm-dashboard/src/components/usage.tsx
+++ b/ui/litellm-dashboard/src/components/usage.tsx
@@ -76,15 +76,11 @@ const customTooltip = (props: CustomTooltipTypeBar) => {
const value = payload[0].payload;
const date = value["startTime"];
const model_values = value["models"];
- // Convert the object into an array of key-value pairs
const entries: [string, number][] = Object.entries(model_values).map(
([key, value]) => [key, value as number]
- ); // Type assertion to specify the value as number
+ );
- // Sort the array based on the float value in descending order
entries.sort((a, b) => b[1] - a[1]);
-
- // Get the top 5 key-value pairs
const topEntries = entries.slice(0, 5);
return (
@@ -98,7 +94,7 @@ const customTooltip = (props: CustomTooltipTypeBar) => {
{":"}
{" "}
- {value ? (value < 0.01 ? "<$0.01" : value.toFixed(2)) : ""}
+ {value ? `$${value.toFixed(2)}` : ""}
@@ -169,6 +165,7 @@ const UsagePage: React.FC = ({
to: new Date(),
});
const [proxySettings, setProxySettings] = useState(null);
+ const [totalMonthlySpend, setTotalMonthlySpend] = useState(0);
const firstDay = new Date(
currentDate.getFullYear(),
@@ -285,7 +282,7 @@ const UsagePage: React.FC = ({
console.log(`End date is ${endTime}`);
const valueFormatter = (number: number) =>
- `$ ${new Intl.NumberFormat("us").format(number).toString()}`;
+ `$ ${number.toFixed(2)}`;
const fetchAndSetData = async (
fetchFunction: () => Promise,
@@ -301,11 +298,94 @@ const UsagePage: React.FC = ({
}
};
- const fetchOverallSpend = () => fetchAndSetData(
- () => accessToken ? adminSpendLogsCall(accessToken) : Promise.reject("No access token"),
- setKeySpendData,
- "Error fetching overall spend"
- );
+ // Update the fillMissingDates function to handle different date formats
+ const fillMissingDates = (data: any[], startDate: Date, endDate: Date, categories: string[]) => {
+ const filledData = [];
+ const currentDate = new Date(startDate);
+
+ // Helper function to standardize date format
+ const standardizeDate = (dateStr: string) => {
+ if (dateStr.includes('-')) {
+ // Already in YYYY-MM-DD format
+ return dateStr;
+ } else {
+ // Convert "Jan 06" format
+ const [month, day] = dateStr.split(' ');
+ const year = new Date().getFullYear();
+ const monthIndex = new Date(`${month} 01 2024`).getMonth();
+ const fullDate = new Date(year, monthIndex, parseInt(day));
+ return fullDate.toISOString().split('T')[0];
+ }
+ };
+
+ // Create a map of existing dates for quick lookup
+ const existingDates = new Map(
+ data.map(item => {
+ const standardizedDate = standardizeDate(item.date);
+ return [standardizedDate, {
+ ...item,
+ date: standardizedDate // Store standardized date format
+ }];
+ })
+ );
+
+ // Iterate through each date in the range
+ while (currentDate <= endDate) {
+ const dateStr = currentDate.toISOString().split('T')[0];
+
+ if (existingDates.has(dateStr)) {
+ // Use existing data if we have it
+ filledData.push(existingDates.get(dateStr));
+ } else {
+ // Create an entry with zero values
+ const emptyEntry: any = {
+ date: dateStr,
+ api_requests: 0,
+ total_tokens: 0
+ };
+
+ // Add zero values for each model/team if needed
+ categories.forEach(category => {
+ if (!emptyEntry[category]) {
+ emptyEntry[category] = 0;
+ }
+ });
+
+ filledData.push(emptyEntry);
+ }
+
+ // Move to next day
+ currentDate.setDate(currentDate.getDate() + 1);
+ }
+
+ return filledData;
+ };
+
+ // Update the fetchOverallSpend function
+ const fetchOverallSpend = async () => {
+ if (!accessToken) {
+ return;
+ }
+ try {
+ const data = await adminSpendLogsCall(accessToken);
+
+ // Get the first and last day of the current month
+ const now = new Date();
+ const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
+ const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);
+
+ // Fill in missing dates
+ const filledData = fillMissingDates(data, firstDay, lastDay, []);
+
+ // Calculate total spend for the month and round to 2 decimal places
+ const monthlyTotal = Number(filledData.reduce((sum, day) => sum + (day.spend || 0), 0).toFixed(2));
+ setTotalMonthlySpend(monthlyTotal);
+
+ setKeySpendData(filledData);
+ } catch (error) {
+ console.error("Error fetching overall spend:", error);
+ }
+ };
const fetchProviderSpend = () => fetchAndSetData(
() => accessToken && token ? adminspendByProvider(accessToken, token, startTime, endTime) : Promise.reject("No access token or token"),
@@ -320,7 +400,7 @@ const UsagePage: React.FC = ({
const top_keys = await adminTopKeysCall(accessToken);
return top_keys.map((k: any) => ({
key: (k["key_alias"] || k["key_name"] || k["api_key"]).substring(0, 10),
- spend: k["total_spend"],
+ spend: Number(k["total_spend"].toFixed(2)),
}));
},
setTopKeys,
@@ -335,7 +415,7 @@ const UsagePage: React.FC = ({
const top_models = await adminTopModelsCall(accessToken);
return top_models.map((k: any) => ({
key: k["model"],
- spend: k["total_spend"],
+ spend: Number(k["total_spend"].toFixed(2)),
}));
},
setTopModels,
@@ -343,16 +423,31 @@ const UsagePage: React.FC = ({
);
};
+ // Update the fetchTeamSpend function
const fetchTeamSpend = async () => {
if (!accessToken) return;
await fetchAndSetData(
async () => {
const teamSpend = await teamSpendLogsCall(accessToken);
- setTeamSpendData(teamSpend.daily_spend);
+
+ // Get the first and last day of the current month
+ const now = new Date();
+ const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
+ const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);
+
+ // Fill in missing dates with zero values for all teams
+ const filledData = fillMissingDates(
+ teamSpend.daily_spend,
+ firstDay,
+ lastDay,
+ teamSpend.teams
+ );
+
+ setTeamSpendData(filledData);
setUniqueTeamIds(teamSpend.teams);
return teamSpend.total_spend_per_team.map((tspt: any) => ({
name: tspt["team_id"] || "",
- value: (tspt["total_spend"] || 0).toFixed(2),
+ value: Number(tspt["total_spend"] || 0).toFixed(2),
}));
},
setTotalSpendPerTeam,
@@ -390,22 +485,60 @@ const UsagePage: React.FC = ({
);
};
- const fetchGlobalActivity = () => {
+ // Update the fetchGlobalActivity function
+ const fetchGlobalActivity = async () => {
if (!accessToken) return;
- fetchAndSetData(
- () => adminGlobalActivity(accessToken, startTime, endTime),
- setGlobalActivity,
- "Error fetching global activity"
- );
+ try {
+ const data = await adminGlobalActivity(accessToken, startTime, endTime);
+
+ // Get the date range from the current month
+ const now = new Date();
+ const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
+ const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);
+
+ // Fill in missing dates for daily_data
+ const filledDailyData = fillMissingDates(
+ data.daily_data || [],
+ firstDay,
+ lastDay,
+ ['api_requests', 'total_tokens']
+ );
+
+ setGlobalActivity({
+ ...data,
+ daily_data: filledDailyData
+ });
+ } catch (error) {
+ console.error("Error fetching global activity:", error);
+ }
};
- const fetchGlobalActivityPerModel = () => {
+ // Update the fetchGlobalActivityPerModel function
+ const fetchGlobalActivityPerModel = async () => {
if (!accessToken) return;
- fetchAndSetData(
- () => adminGlobalActivityPerModel(accessToken, startTime, endTime),
- setGlobalActivityPerModel,
- "Error fetching global activity per model"
- );
+ try {
+ const data = await adminGlobalActivityPerModel(accessToken, startTime, endTime);
+
+ // Get the date range from the current month
+ const now = new Date();
+ const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
+ const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);
+
+ // Fill in missing dates for each model's daily data
+ const filledModelData = data.map((modelData: any) => ({
+ ...modelData,
+ daily_data: fillMissingDates(
+ modelData.daily_data || [],
+ firstDay,
+ lastDay,
+ ['api_requests', 'total_tokens']
+ )
+ }));
+
+ setGlobalActivityPerModel(filledModelData);
+ } catch (error) {
+ console.error("Error fetching global activity per model:", error);
+ }
};
useEffect(() => {
@@ -492,14 +625,19 @@ const UsagePage: React.FC = ({
-
+
+
+ Project Spend {new Date().toLocaleString('default', { month: 'long' })} 1 - {new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0).getDate()}
+
+
+
Monthly Spend
@@ -507,7 +645,7 @@ const UsagePage: React.FC = ({
data={keySpendData}
index="date"
categories={["spend"]}
- colors={["blue"]}
+ colors={["cyan"]}
valueFormatter={valueFormatter}
yAxisWidth={100}
tickGap={5}
@@ -523,12 +661,13 @@ const UsagePage: React.FC = ({
data={topKeys}
index="key"
categories={["spend"]}
- colors={["blue"]}
+ colors={["cyan"]}
yAxisWidth={80}
tickGap={5}
layout="vertical"
showXAxis={false}
showLegend={false}
+ valueFormatter={(value) => `$${value.toFixed(2)}`}
/>
@@ -540,11 +679,12 @@ const UsagePage: React.FC = ({
data={topModels}
index="key"
categories={["spend"]}
- colors={["blue"]}
+ colors={["cyan"]}
yAxisWidth={200}
layout="vertical"
showXAxis={false}
showLegend={false}
+ valueFormatter={(value) => `$${value.toFixed(2)}`}
/>
@@ -564,6 +704,8 @@ const UsagePage: React.FC = ({
data={spendByProvider}
index="provider"
category="spend"
+ colors={["cyan"]}
+ valueFormatter={(value) => `$${value.toFixed(2)}`}
/>
@@ -880,7 +1022,7 @@ const UsagePage: React.FC = ({
data={topTagsData}
index="name"
categories={["spend"]}
- colors={["blue"]}
+ colors={["cyan"]}
>