|
import { BarChart, BarList, Card, Title, Table, TableHead, TableHeaderCell, TableRow, TableCell, TableBody, Metric, Subtitle } from "@tremor/react"; |
|
|
|
import React, { useState, useEffect } from "react"; |
|
|
|
import ViewUserSpend from "./view_user_spend"; |
|
import { ProxySettings } from "./user_dashboard"; |
|
import { |
|
Grid, Col, Text, |
|
LineChart, TabPanel, TabPanels, |
|
TabGroup, TabList, Tab, Select, SelectItem, |
|
DateRangePicker, DateRangePickerValue, |
|
DonutChart, |
|
AreaChart, |
|
Callout, |
|
Button, |
|
MultiSelect, |
|
MultiSelectItem, |
|
} from "@tremor/react"; |
|
|
|
import { |
|
Select as Select2 |
|
} from "antd"; |
|
|
|
import { |
|
userSpendLogsCall, |
|
keyInfoCall, |
|
adminSpendLogsCall, |
|
adminTopKeysCall, |
|
adminTopModelsCall, |
|
adminTopEndUsersCall, |
|
teamSpendLogsCall, |
|
tagsSpendLogsCall, |
|
allTagNamesCall, |
|
modelMetricsCall, |
|
modelAvailableCall, |
|
adminspendByProvider, |
|
adminGlobalActivity, |
|
adminGlobalActivityPerModel, |
|
getProxyUISettings |
|
} from "./networking"; |
|
import { start } from "repl"; |
|
console.log("process.env.NODE_ENV", process.env.NODE_ENV); |
|
const isLocal = process.env.NODE_ENV === "development"; |
|
const proxyBaseUrl = isLocal ? "http://localhost:4000" : null; |
|
if (isLocal !== true) { |
|
console.log = function() {}; |
|
} |
|
|
|
interface UsagePageProps { |
|
accessToken: string | null; |
|
token: string | null; |
|
userRole: string | null; |
|
userID: string | null; |
|
keys: any[] | null; |
|
premiumUser: boolean; |
|
} |
|
|
|
interface GlobalActivityData { |
|
sum_api_requests: number; |
|
sum_total_tokens: number; |
|
daily_data: { date: string; api_requests: number; total_tokens: number }[]; |
|
} |
|
|
|
|
|
type CustomTooltipTypeBar = { |
|
payload: any; |
|
active: boolean | undefined; |
|
label: any; |
|
}; |
|
|
|
|
|
const customTooltip = (props: CustomTooltipTypeBar) => { |
|
const { payload, active } = props; |
|
if (!active || !payload) return null; |
|
|
|
const value = payload[0].payload; |
|
const date = value["startTime"]; |
|
const model_values = value["models"]; |
|
const entries: [string, number][] = Object.entries(model_values).map( |
|
([key, value]) => [key, value as number] |
|
); |
|
|
|
entries.sort((a, b) => b[1] - a[1]); |
|
const topEntries = entries.slice(0, 5); |
|
|
|
return ( |
|
<div className="w-56 rounded-tremor-default border border-tremor-border bg-tremor-background p-2 text-tremor-default shadow-tremor-dropdown"> |
|
{date} |
|
{topEntries.map(([key, value]) => ( |
|
<div key={key} className="flex flex-1 space-x-10"> |
|
<div className="p-2"> |
|
<p className="text-tremor-content text-xs"> |
|
{key} |
|
{":"} |
|
<span className="text-xs text-tremor-content-emphasis"> |
|
{" "} |
|
{value ? `$${value.toFixed(2)}` : ""} |
|
</span> |
|
</p> |
|
</div> |
|
</div> |
|
))} |
|
</div> |
|
); |
|
}; |
|
|
|
function getTopKeys(data: Array<{ [key: string]: unknown }>): any[] { |
|
const spendKeys: { key: string; spend: unknown }[] = []; |
|
|
|
data.forEach((dict) => { |
|
Object.entries(dict).forEach(([key, value]) => { |
|
if ( |
|
key !== "spend" && |
|
key !== "startTime" && |
|
key !== "models" && |
|
key !== "users" |
|
) { |
|
spendKeys.push({ key, spend: value }); |
|
} |
|
}); |
|
}); |
|
|
|
spendKeys.sort((a, b) => Number(b.spend) - Number(a.spend)); |
|
|
|
const topKeys = spendKeys.slice(0, 5).map((k) => k.key); |
|
console.log(`topKeys: ${Object.keys(topKeys[0])}`); |
|
return topKeys; |
|
} |
|
type DataDict = { [key: string]: unknown }; |
|
type UserData = { user_id: string; spend: number }; |
|
|
|
|
|
const isAdminOrAdminViewer = (role: string | null): boolean => { |
|
if (role === null) return false; |
|
return role === 'Admin' || role === 'Admin Viewer'; |
|
}; |
|
|
|
|
|
|
|
const UsagePage: React.FC<UsagePageProps> = ({ |
|
accessToken, |
|
token, |
|
userRole, |
|
userID, |
|
keys, |
|
premiumUser, |
|
}) => { |
|
const currentDate = new Date(); |
|
const [keySpendData, setKeySpendData] = useState<any[]>([]); |
|
const [topKeys, setTopKeys] = useState<any[]>([]); |
|
const [topModels, setTopModels] = useState<any[]>([]); |
|
const [topUsers, setTopUsers] = useState<any[]>([]); |
|
const [teamSpendData, setTeamSpendData] = useState<any[]>([]); |
|
const [topTagsData, setTopTagsData] = useState<any[]>([]); |
|
const [allTagNames, setAllTagNames] = useState<string[]>([]); |
|
const [uniqueTeamIds, setUniqueTeamIds] = useState<any[]>([]); |
|
const [totalSpendPerTeam, setTotalSpendPerTeam] = useState<any[]>([]); |
|
const [spendByProvider, setSpendByProvider] = useState<any[]>([]); |
|
const [globalActivity, setGlobalActivity] = useState<GlobalActivityData>({} as GlobalActivityData); |
|
const [globalActivityPerModel, setGlobalActivityPerModel] = useState<any[]>([]); |
|
const [selectedKeyID, setSelectedKeyID] = useState<string | null>(""); |
|
const [selectedTags, setSelectedTags] = useState<string[]>(["all-tags"]); |
|
const [dateValue, setDateValue] = useState<DateRangePickerValue>({ |
|
from: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), |
|
to: new Date(), |
|
}); |
|
const [proxySettings, setProxySettings] = useState<ProxySettings | null>(null); |
|
const [totalMonthlySpend, setTotalMonthlySpend] = useState<number>(0); |
|
|
|
const firstDay = new Date( |
|
currentDate.getFullYear(), |
|
currentDate.getMonth(), |
|
1 |
|
); |
|
const lastDay = new Date( |
|
currentDate.getFullYear(), |
|
currentDate.getMonth() + 1, |
|
0 |
|
); |
|
|
|
let startTime = formatDate(firstDay); |
|
let endTime = formatDate(lastDay); |
|
|
|
console.log("keys in usage", keys); |
|
console.log("premium user in usage", premiumUser); |
|
|
|
function valueFormatterNumbers(number: number) { |
|
const formatter = new Intl.NumberFormat('en-US', { |
|
maximumFractionDigits: 0, |
|
notation: 'compact', |
|
compactDisplay: 'short', |
|
}); |
|
|
|
return formatter.format(number); |
|
} |
|
|
|
|
|
const fetchProxySettings = async () => { |
|
if (accessToken) { |
|
try { |
|
const proxy_settings: ProxySettings = await getProxyUISettings(accessToken); |
|
console.log("usage tab: proxy_settings", proxy_settings); |
|
return proxy_settings; |
|
} catch (error) { |
|
console.error("Error fetching proxy settings:", error); |
|
} |
|
} |
|
}; |
|
|
|
useEffect(() => { |
|
updateTagSpendData(dateValue.from, dateValue.to); |
|
}, [dateValue, selectedTags]); |
|
|
|
|
|
const updateEndUserData = async (startTime: Date | undefined, endTime: Date | undefined, uiSelectedKey: string | null) => { |
|
if (!startTime || !endTime || !accessToken) { |
|
return; |
|
} |
|
|
|
|
|
endTime.setHours(23, 59, 59, 999); |
|
|
|
|
|
startTime.setHours(0, 0, 0, 0); |
|
|
|
console.log("uiSelectedKey", uiSelectedKey); |
|
|
|
let newTopUserData = await adminTopEndUsersCall( |
|
accessToken, |
|
uiSelectedKey, |
|
startTime.toISOString(), |
|
endTime.toISOString() |
|
) |
|
console.log("End user data updated successfully", newTopUserData); |
|
setTopUsers(newTopUserData); |
|
|
|
} |
|
|
|
const updateTagSpendData = async (startTime: Date | undefined, endTime: Date | undefined) => { |
|
if (!startTime || !endTime || !accessToken) { |
|
return; |
|
} |
|
|
|
|
|
|
|
const proxy_settings: ProxySettings | undefined = await fetchProxySettings(); |
|
|
|
if (proxy_settings?.DISABLE_EXPENSIVE_DB_QUERIES) { |
|
return; |
|
} |
|
|
|
|
|
endTime.setHours(23, 59, 59, 999); |
|
|
|
|
|
startTime.setHours(0, 0, 0, 0); |
|
|
|
let top_tags = await tagsSpendLogsCall( |
|
accessToken, |
|
startTime.toISOString(), |
|
endTime.toISOString(), |
|
selectedTags.length === 0 ? undefined : selectedTags |
|
); |
|
setTopTagsData(top_tags.spend_per_tag); |
|
console.log("Tag spend data updated successfully"); |
|
|
|
} |
|
|
|
function formatDate(date: Date) { |
|
const year = date.getFullYear(); |
|
let month = date.getMonth() + 1; |
|
let day = date.getDate(); |
|
|
|
|
|
const monthStr = month < 10 ? "0" + month : month; |
|
const dayStr = day < 10 ? "0" + day : day; |
|
|
|
return `${year}-${monthStr}-${dayStr}`; |
|
} |
|
|
|
console.log(`Start date is ${startTime}`); |
|
console.log(`End date is ${endTime}`); |
|
|
|
const valueFormatter = (number: number) => |
|
`$ ${number.toFixed(2)}`; |
|
|
|
const fetchAndSetData = async ( |
|
fetchFunction: () => Promise<any>, |
|
setStateFunction: React.Dispatch<React.SetStateAction<any>>, |
|
errorMessage: string |
|
) => { |
|
try { |
|
const data = await fetchFunction(); |
|
setStateFunction(data); |
|
} catch (error) { |
|
console.error(errorMessage, error); |
|
|
|
} |
|
}; |
|
|
|
|
|
const fillMissingDates = (data: any[], startDate: Date, endDate: Date, categories: string[]) => { |
|
const filledData = []; |
|
const currentDate = new Date(startDate); |
|
|
|
|
|
const standardizeDate = (dateStr: string) => { |
|
if (dateStr.includes('-')) { |
|
|
|
return dateStr; |
|
} else { |
|
|
|
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]; |
|
} |
|
}; |
|
|
|
|
|
const existingDates = new Map( |
|
data.map(item => { |
|
const standardizedDate = standardizeDate(item.date); |
|
return [standardizedDate, { |
|
...item, |
|
date: standardizedDate |
|
}]; |
|
}) |
|
); |
|
|
|
|
|
while (currentDate <= endDate) { |
|
const dateStr = currentDate.toISOString().split('T')[0]; |
|
|
|
if (existingDates.has(dateStr)) { |
|
|
|
filledData.push(existingDates.get(dateStr)); |
|
} else { |
|
|
|
const emptyEntry: any = { |
|
date: dateStr, |
|
api_requests: 0, |
|
total_tokens: 0 |
|
}; |
|
|
|
|
|
categories.forEach(category => { |
|
if (!emptyEntry[category]) { |
|
emptyEntry[category] = 0; |
|
} |
|
}); |
|
|
|
filledData.push(emptyEntry); |
|
} |
|
|
|
|
|
currentDate.setDate(currentDate.getDate() + 1); |
|
} |
|
|
|
return filledData; |
|
}; |
|
|
|
|
|
const fetchOverallSpend = async () => { |
|
if (!accessToken) { |
|
return; |
|
} |
|
try { |
|
const data = await adminSpendLogsCall(accessToken); |
|
|
|
|
|
const now = new Date(); |
|
const firstDay = new Date(now.getFullYear(), now.getMonth(), 1); |
|
const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0); |
|
|
|
|
|
const filledData = fillMissingDates(data, firstDay, lastDay, []); |
|
|
|
|
|
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"), |
|
setSpendByProvider, |
|
"Error fetching provider spend" |
|
); |
|
|
|
const fetchTopKeys = async () => { |
|
if (!accessToken) return; |
|
await fetchAndSetData( |
|
async () => { |
|
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: Number(k["total_spend"].toFixed(2)), |
|
})); |
|
}, |
|
setTopKeys, |
|
"Error fetching top keys" |
|
); |
|
}; |
|
|
|
const fetchTopModels = async () => { |
|
if (!accessToken) return; |
|
await fetchAndSetData( |
|
async () => { |
|
const top_models = await adminTopModelsCall(accessToken); |
|
return top_models.map((k: any) => ({ |
|
key: k["model"], |
|
spend: Number(k["total_spend"].toFixed(2)), |
|
})); |
|
}, |
|
setTopModels, |
|
"Error fetching top models" |
|
); |
|
}; |
|
|
|
|
|
const fetchTeamSpend = async () => { |
|
if (!accessToken) return; |
|
await fetchAndSetData( |
|
async () => { |
|
const teamSpend = await teamSpendLogsCall(accessToken); |
|
|
|
|
|
const now = new Date(); |
|
const firstDay = new Date(now.getFullYear(), now.getMonth(), 1); |
|
const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0); |
|
|
|
|
|
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: Number(tspt["total_spend"] || 0).toFixed(2), |
|
})); |
|
}, |
|
setTotalSpendPerTeam, |
|
"Error fetching team spend" |
|
); |
|
}; |
|
|
|
const fetchTagNames = () => { |
|
if (!accessToken) return; |
|
fetchAndSetData( |
|
async () => { |
|
const all_tag_names = await allTagNamesCall(accessToken); |
|
return all_tag_names.tag_names; |
|
}, |
|
setAllTagNames, |
|
"Error fetching tag names" |
|
); |
|
}; |
|
|
|
const fetchTopTags = () => { |
|
if (!accessToken) return; |
|
fetchAndSetData( |
|
() => tagsSpendLogsCall(accessToken, dateValue.from?.toISOString(), dateValue.to?.toISOString(), undefined), |
|
(data) => setTopTagsData(data.spend_per_tag), |
|
"Error fetching top tags" |
|
); |
|
}; |
|
|
|
const fetchTopEndUsers = () => { |
|
if (!accessToken) return; |
|
fetchAndSetData( |
|
() => adminTopEndUsersCall(accessToken, null, undefined, undefined), |
|
setTopUsers, |
|
"Error fetching top end users" |
|
); |
|
}; |
|
|
|
|
|
const fetchGlobalActivity = async () => { |
|
if (!accessToken) return; |
|
try { |
|
const data = await adminGlobalActivity(accessToken, startTime, endTime); |
|
|
|
|
|
const now = new Date(); |
|
const firstDay = new Date(now.getFullYear(), now.getMonth(), 1); |
|
const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0); |
|
|
|
|
|
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 = async () => { |
|
if (!accessToken) return; |
|
try { |
|
const data = await adminGlobalActivityPerModel(accessToken, startTime, endTime); |
|
|
|
|
|
const now = new Date(); |
|
const firstDay = new Date(now.getFullYear(), now.getMonth(), 1); |
|
const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0); |
|
|
|
|
|
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(() => { |
|
const initlizeUsageData = async () => { |
|
if (accessToken && token && userRole && userID) { |
|
const proxy_settings: ProxySettings | undefined = await fetchProxySettings(); |
|
if (proxy_settings) { |
|
setProxySettings(proxy_settings); |
|
if (proxy_settings?.DISABLE_EXPENSIVE_DB_QUERIES) { |
|
return; |
|
} |
|
} |
|
|
|
|
|
console.log("fetching data - valiue of proxySettings", proxySettings); |
|
|
|
|
|
fetchOverallSpend(); |
|
fetchProviderSpend(); |
|
fetchTopKeys(); |
|
fetchTopModels(); |
|
fetchGlobalActivity(); |
|
fetchGlobalActivityPerModel(); |
|
|
|
if (isAdminOrAdminViewer(userRole)) { |
|
fetchTeamSpend(); |
|
fetchTagNames(); |
|
fetchTopTags(); |
|
fetchTopEndUsers(); |
|
} |
|
} |
|
}; |
|
|
|
initlizeUsageData(); |
|
}, [accessToken, token, userRole, userID, startTime, endTime]); |
|
|
|
|
|
if (proxySettings?.DISABLE_EXPENSIVE_DB_QUERIES) { |
|
return ( |
|
<div style={{ width: "100%" }} className="p-8"> |
|
<Card> |
|
<Title>Database Query Limit Reached</Title> |
|
<Text className="mt-4"> |
|
SpendLogs in DB has {proxySettings.NUM_SPEND_LOGS_ROWS} rows. |
|
<br></br> |
|
Please follow our guide to view usage when SpendLogs has more than 1M rows. |
|
</Text> |
|
<Button className="mt-4"> |
|
<a href="https://docs.litellm.ai/docs/proxy/spending_monitoring" target="_blank"> |
|
View Usage Guide |
|
</a> |
|
</Button> |
|
</Card> |
|
</div> |
|
); |
|
} |
|
|
|
|
|
return ( |
|
<div style={{ width: "100%" }} className="p-8"> |
|
<TabGroup> |
|
<TabList className="mt-2"> |
|
<Tab>All Up</Tab> |
|
|
|
{isAdminOrAdminViewer(userRole) ? ( |
|
<> |
|
<Tab>Team Based Usage</Tab> |
|
<Tab>Customer Usage</Tab> |
|
<Tab>Tag Based Usage</Tab> |
|
</> |
|
) : ( |
|
<><div></div> |
|
</> |
|
)} |
|
</TabList> |
|
<TabPanels> |
|
<TabPanel> |
|
|
|
<TabGroup> |
|
<TabList variant="solid" className="mt-1"> |
|
<Tab>Cost</Tab> |
|
<Tab>Activity</Tab> |
|
</TabList> |
|
<TabPanels> |
|
<TabPanel> |
|
<Grid numItems={2} className="gap-2 h-[100vh] w-full"> |
|
<Col numColSpan={2}> |
|
<Text className="text-tremor-default text-tremor-content dark:text-dark-tremor-content mb-2 mt-2 text-lg"> |
|
Project Spend {new Date().toLocaleString('default', { month: 'long' })} 1 - {new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0).getDate()} |
|
</Text> |
|
<ViewUserSpend |
|
userID={userID} |
|
userRole={userRole} |
|
accessToken={accessToken} |
|
userSpend={totalMonthlySpend} |
|
selectedTeam={null} |
|
userMaxBudget={null} |
|
/> |
|
</Col> |
|
<Col numColSpan={2}> |
|
<Card> |
|
<Title>Monthly Spend</Title> |
|
<BarChart |
|
data={keySpendData} |
|
index="date" |
|
categories={["spend"]} |
|
colors={["cyan"]} |
|
valueFormatter={valueFormatter} |
|
yAxisWidth={100} |
|
tickGap={5} |
|
// customTooltip={customTooltip} |
|
/> |
|
</Card> |
|
</Col> |
|
<Col numColSpan={1}> |
|
<Card> |
|
<Title>Top API Keys</Title> |
|
<BarChart |
|
className="mt-4 h-40" |
|
data={topKeys} |
|
index="key" |
|
categories={["spend"]} |
|
colors={["cyan"]} |
|
yAxisWidth={80} |
|
tickGap={5} |
|
layout="vertical" |
|
showXAxis={false} |
|
showLegend={false} |
|
valueFormatter={(value) => `$${value.toFixed(2)}`} |
|
/> |
|
</Card> |
|
</Col> |
|
<Col numColSpan={1}> |
|
<Card> |
|
<Title>Top Models</Title> |
|
<BarChart |
|
className="mt-4 h-40" |
|
data={topModels} |
|
index="key" |
|
categories={["spend"]} |
|
colors={["cyan"]} |
|
yAxisWidth={200} |
|
layout="vertical" |
|
showXAxis={false} |
|
showLegend={false} |
|
valueFormatter={(value) => `$${value.toFixed(2)}`} |
|
/> |
|
</Card> |
|
|
|
</Col> |
|
<Col numColSpan={1}> |
|
|
|
</Col> |
|
<Col numColSpan={2}> |
|
<Card className="mb-2"> |
|
<Title>Spend by Provider</Title> |
|
<> |
|
<Grid numItems={2}> |
|
<Col numColSpan={1}> |
|
<DonutChart |
|
className="mt-4 h-40" |
|
variant="pie" |
|
data={spendByProvider} |
|
index="provider" |
|
category="spend" |
|
colors={["cyan"]} |
|
valueFormatter={(value) => `$${value.toFixed(2)}`} |
|
/> |
|
</Col> |
|
<Col numColSpan={1}> |
|
<Table> |
|
<TableHead> |
|
<TableRow> |
|
<TableHeaderCell>Provider</TableHeaderCell> |
|
<TableHeaderCell>Spend</TableHeaderCell> |
|
</TableRow> |
|
</TableHead> |
|
<TableBody> |
|
{spendByProvider.map((provider) => ( |
|
<TableRow key={provider.provider}> |
|
<TableCell>{provider.provider}</TableCell> |
|
<TableCell> |
|
{parseFloat(provider.spend.toFixed(2)) < 0.00001 |
|
? "less than 0.00" |
|
: provider.spend.toFixed(2)} |
|
</TableCell> |
|
</TableRow> |
|
))} |
|
</TableBody> |
|
</Table> |
|
</Col> |
|
</Grid> |
|
</> |
|
|
|
</Card> |
|
</Col> |
|
</Grid> |
|
</TabPanel> |
|
<TabPanel> |
|
<Grid numItems={1} className="gap-2 h-[75vh] w-full"> |
|
<Card> |
|
<Title>All Up</Title> |
|
<Grid numItems={2}> |
|
<Col> |
|
<Subtitle style={{ fontSize: "15px", fontWeight: "normal", color: "#535452"}}>API Requests { valueFormatterNumbers(globalActivity.sum_api_requests)}</Subtitle> |
|
<AreaChart |
|
className="h-40" |
|
data={globalActivity.daily_data} |
|
valueFormatter={valueFormatterNumbers} |
|
index="date" |
|
colors={['cyan']} |
|
categories={['api_requests']} |
|
onValueChange={(v) => console.log(v)} |
|
/> |
|
|
|
</Col> |
|
<Col> |
|
<Subtitle style={{ fontSize: "15px", fontWeight: "normal", color: "#535452"}}>Tokens { valueFormatterNumbers(globalActivity.sum_total_tokens)}</Subtitle> |
|
<BarChart |
|
className="h-40" |
|
data={globalActivity.daily_data} |
|
valueFormatter={valueFormatterNumbers} |
|
index="date" |
|
colors={['cyan']} |
|
categories={['total_tokens']} |
|
onValueChange={(v) => console.log(v)} |
|
/> |
|
</Col> |
|
</Grid> |
|
|
|
|
|
</Card> |
|
|
|
<> |
|
{globalActivityPerModel.map((globalActivity, index) => ( |
|
<Card key={index}> |
|
<Title>{globalActivity.model}</Title> |
|
<Grid numItems={2}> |
|
<Col> |
|
<Subtitle style={{ fontSize: "15px", fontWeight: "normal", color: "#535452"}}>API Requests {valueFormatterNumbers(globalActivity.sum_api_requests)}</Subtitle> |
|
<AreaChart |
|
className="h-40" |
|
data={globalActivity.daily_data} |
|
index="date" |
|
colors={['cyan']} |
|
categories={['api_requests']} |
|
valueFormatter={valueFormatterNumbers} |
|
onValueChange={(v) => console.log(v)} |
|
/> |
|
</Col> |
|
<Col> |
|
<Subtitle style={{ fontSize: "15px", fontWeight: "normal", color: "#535452"}}>Tokens {valueFormatterNumbers(globalActivity.sum_total_tokens)}</Subtitle> |
|
<BarChart |
|
className="h-40" |
|
data={globalActivity.daily_data} |
|
index="date" |
|
colors={['cyan']} |
|
categories={['total_tokens']} |
|
valueFormatter={valueFormatterNumbers} |
|
onValueChange={(v) => console.log(v)} |
|
/> |
|
</Col> |
|
</Grid> |
|
</Card> |
|
))} |
|
</> |
|
</Grid> |
|
</TabPanel> |
|
</TabPanels> |
|
</TabGroup> |
|
|
|
</TabPanel> |
|
<TabPanel> |
|
<Grid numItems={2} className="gap-2 h-[75vh] w-full"> |
|
<Col numColSpan={2}> |
|
<Card className="mb-2"> |
|
<Title>Total Spend Per Team</Title> |
|
<BarList |
|
data={totalSpendPerTeam} |
|
|
|
/> |
|
</Card> |
|
<Card> |
|
|
|
<Title>Daily Spend Per Team</Title> |
|
<BarChart |
|
className="h-72" |
|
data={teamSpendData} |
|
showLegend={true} |
|
index="date" |
|
categories={uniqueTeamIds} |
|
yAxisWidth={80} |
|
stack={true} |
|
/> |
|
</Card> |
|
</Col> |
|
<Col numColSpan={2}> |
|
</Col> |
|
</Grid> |
|
</TabPanel> |
|
<TabPanel> |
|
<p className="mb-2 text-gray-500 italic text-[12px]">Customers of your LLM API calls. Tracked when a `user` param is passed in your LLM calls <a className="text-blue-500" href="https://docs.litellm.ai/docs/proxy/users" target="_blank">docs here</a></p> |
|
<Grid numItems={2}> |
|
<Col> |
|
<Text>Select Time Range</Text> |
|
|
|
<DateRangePicker |
|
enableSelect={true} |
|
value={dateValue} |
|
onValueChange={(value) => { |
|
setDateValue(value); |
|
updateEndUserData(value.from, value.to, null); // Call updateModelMetrics with the new date range |
|
}} |
|
/> |
|
</Col> |
|
<Col> |
|
<Text>Select Key</Text> |
|
<Select defaultValue="all-keys"> |
|
<SelectItem |
|
key="all-keys" |
|
value="all-keys" |
|
onClick={() => { |
|
updateEndUserData(dateValue.from, dateValue.to, null); |
|
}} |
|
> |
|
All Keys |
|
</SelectItem> |
|
{keys?.map((key: any, index: number) => { |
|
if ( |
|
key && |
|
key["key_alias"] !== null && |
|
key["key_alias"].length > 0 |
|
) { |
|
return ( |
|
|
|
<SelectItem |
|
key={index} |
|
value={String(index)} |
|
onClick={() => { |
|
updateEndUserData(dateValue.from, dateValue.to, key["token"]); |
|
}} |
|
> |
|
{key["key_alias"]} |
|
</SelectItem> |
|
); |
|
} |
|
return null; // Add this line to handle the case when the condition is not met |
|
})} |
|
</Select> |
|
</Col> |
|
|
|
</Grid> |
|
|
|
|
|
|
|
<Card className="mt-4"> |
|
|
|
|
|
|
|
<Table className="max-h-[70vh] min-h-[500px]"> |
|
<TableHead> |
|
<TableRow> |
|
<TableHeaderCell>Customer</TableHeaderCell> |
|
<TableHeaderCell>Spend</TableHeaderCell> |
|
<TableHeaderCell>Total Events</TableHeaderCell> |
|
</TableRow> |
|
</TableHead> |
|
|
|
<TableBody> |
|
{topUsers?.map((user: any, index: number) => ( |
|
<TableRow key={index}> |
|
<TableCell>{user.end_user}</TableCell> |
|
<TableCell>{user.total_spend?.toFixed(4)}</TableCell> |
|
<TableCell>{user.total_count}</TableCell> |
|
</TableRow> |
|
))} |
|
</TableBody> |
|
</Table> |
|
|
|
</Card> |
|
|
|
</TabPanel> |
|
<TabPanel> |
|
<Grid numItems={2}> |
|
<Col numColSpan={1}> |
|
<DateRangePicker |
|
className="mb-4" |
|
enableSelect={true} |
|
value={dateValue} |
|
onValueChange={(value) => { |
|
setDateValue(value); |
|
updateTagSpendData(value.from, value.to); // Call updateModelMetrics with the new date range |
|
}} |
|
/> |
|
|
|
</Col> |
|
|
|
<Col> |
|
{ |
|
premiumUser ? ( |
|
<div> |
|
<MultiSelect |
|
value={selectedTags} |
|
onValueChange={(value) => setSelectedTags(value as string[])} |
|
> |
|
<MultiSelectItem |
|
key={"all-tags"} |
|
value={"all-tags"} |
|
onClick={() => setSelectedTags(["all-tags"])} |
|
> |
|
All Tags |
|
</MultiSelectItem> |
|
{allTagNames && |
|
allTagNames |
|
.filter((tag) => tag !== "all-tags") |
|
.map((tag: any, index: number) => { |
|
return ( |
|
<MultiSelectItem |
|
key={tag} |
|
value={String(tag)} |
|
> |
|
{tag} |
|
</MultiSelectItem> |
|
); |
|
})} |
|
</MultiSelect> |
|
|
|
</div> |
|
|
|
) : ( |
|
<div> |
|
|
|
<MultiSelect |
|
value={selectedTags} |
|
onValueChange={(value) => setSelectedTags(value as string[])} |
|
> |
|
<MultiSelectItem |
|
key={"all-tags"} |
|
value={"all-tags"} |
|
onClick={() => setSelectedTags(["all-tags"])} |
|
> |
|
All Tags |
|
</MultiSelectItem> |
|
{allTagNames && |
|
allTagNames |
|
.filter((tag) => tag !== "all-tags") |
|
.map((tag: any, index: number) => { |
|
return ( |
|
<SelectItem |
|
key={tag} |
|
value={String(tag)} |
|
// @ts-ignore |
|
disabled={true} |
|
> |
|
✨ {tag} (Enterprise only Feature) |
|
</SelectItem> |
|
); |
|
})} |
|
</MultiSelect> |
|
|
|
|
|
|
|
|
|
</div> |
|
) |
|
} |
|
|
|
</Col> |
|
|
|
</Grid> |
|
<Grid numItems={2} className="gap-2 h-[75vh] w-full mb-4"> |
|
|
|
|
|
<Col numColSpan={2}> |
|
|
|
<Card> |
|
<Title>Spend Per Tag</Title> |
|
<Text>Get Started Tracking cost per tag <a className="text-blue-500" href="https://docs.litellm.ai/docs/proxy/cost_tracking" target="_blank">here</a></Text> |
|
<BarChart |
|
className="h-72" |
|
data={topTagsData} |
|
index="name" |
|
categories={["spend"]} |
|
colors={["cyan"]} |
|
> |
|
|
|
</BarChart> |
|
</Card> |
|
</Col> |
|
<Col numColSpan={2}> |
|
</Col> |
|
</Grid> |
|
</TabPanel> |
|
|
|
</TabPanels> |
|
</TabGroup> |
|
</div> |
|
); |
|
}; |
|
|
|
export default UsagePage; |
|
|