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"]} >