/** * New Usage Page * * Uses the new `/user/daily/activity` endpoint to get daily activity data for a user. * * Works at 1m+ spend logs, by querying an aggregate table instead. */ import React, { useState, useEffect } from "react"; import { BarChart, Card, Title, Text, Grid, Col, TabGroup, TabList, Tab, TabPanel, TabPanels, DonutChart, Table, TableHead, TableRow, TableHeaderCell, TableBody, TableCell, Subtitle, DateRangePicker, DateRangePickerValue } from "@tremor/react"; import { AreaChart } from "@tremor/react"; import { userDailyActivityCall } from "./networking"; import ViewUserSpend from "./view_user_spend"; import TopKeyView from "./top_key_view"; import { ActivityMetrics, processActivityData } from './activity_metrics'; import { SpendMetrics, DailyData, ModelActivityData, MetricWithMetadata, KeyMetricWithMetadata } from './usage/types'; import EntityUsage from './entity_usage'; interface NewUsagePageProps { accessToken: string | null; userRole: string | null; userID: string | null; } const NewUsagePage: React.FC = ({ accessToken, userRole, userID, }) => { const [userSpendData, setUserSpendData] = useState<{ results: DailyData[]; metadata: any; }>({ results: [], metadata: {} }); // Add date range state const [dateValue, setDateValue] = useState({ from: new Date(Date.now() - 28 * 24 * 60 * 60 * 1000), to: new Date(), }); // Derived states from userSpendData const totalSpend = userSpendData.metadata?.total_spend || 0; // Calculate top models from the breakdown data const getTopModels = () => { const modelSpend: { [key: string]: MetricWithMetadata } = {}; userSpendData.results.forEach(day => { Object.entries(day.breakdown.models || {}).forEach(([model, metrics]) => { if (!modelSpend[model]) { modelSpend[model] = { metrics: { spend: 0, prompt_tokens: 0, completion_tokens: 0, total_tokens: 0, api_requests: 0, successful_requests: 0, failed_requests: 0, cache_read_input_tokens: 0, cache_creation_input_tokens: 0 }, metadata: {} }; } modelSpend[model].metrics.spend += metrics.metrics.spend; modelSpend[model].metrics.prompt_tokens += metrics.metrics.prompt_tokens; modelSpend[model].metrics.completion_tokens += metrics.metrics.completion_tokens; modelSpend[model].metrics.total_tokens += metrics.metrics.total_tokens; modelSpend[model].metrics.api_requests += metrics.metrics.api_requests; modelSpend[model].metrics.successful_requests += metrics.metrics.successful_requests || 0; modelSpend[model].metrics.failed_requests += metrics.metrics.failed_requests || 0; modelSpend[model].metrics.cache_read_input_tokens += metrics.metrics.cache_read_input_tokens || 0; modelSpend[model].metrics.cache_creation_input_tokens += metrics.metrics.cache_creation_input_tokens || 0; }); }); return Object.entries(modelSpend) .map(([model, metrics]) => ({ key: model, spend: metrics.metrics.spend, requests: metrics.metrics.api_requests, successful_requests: metrics.metrics.successful_requests, failed_requests: metrics.metrics.failed_requests, tokens: metrics.metrics.total_tokens })) .sort((a, b) => b.spend - a.spend) .slice(0, 5); }; // Calculate provider spend from the breakdown data const getProviderSpend = () => { const providerSpend: { [key: string]: MetricWithMetadata } = {}; userSpendData.results.forEach(day => { Object.entries(day.breakdown.providers || {}).forEach(([provider, metrics]) => { if (!providerSpend[provider]) { providerSpend[provider] = { metrics: { spend: 0, prompt_tokens: 0, completion_tokens: 0, total_tokens: 0, api_requests: 0, successful_requests: 0, failed_requests: 0, cache_read_input_tokens: 0, cache_creation_input_tokens: 0 }, metadata: {} }; } providerSpend[provider].metrics.spend += metrics.metrics.spend; providerSpend[provider].metrics.prompt_tokens += metrics.metrics.prompt_tokens; providerSpend[provider].metrics.completion_tokens += metrics.metrics.completion_tokens; providerSpend[provider].metrics.total_tokens += metrics.metrics.total_tokens; providerSpend[provider].metrics.api_requests += metrics.metrics.api_requests; providerSpend[provider].metrics.successful_requests += metrics.metrics.successful_requests || 0; providerSpend[provider].metrics.failed_requests += metrics.metrics.failed_requests || 0; providerSpend[provider].metrics.cache_read_input_tokens += metrics.metrics.cache_read_input_tokens || 0; providerSpend[provider].metrics.cache_creation_input_tokens += metrics.metrics.cache_creation_input_tokens || 0; }); }); return Object.entries(providerSpend) .map(([provider, metrics]) => ({ provider, spend: metrics.metrics.spend, requests: metrics.metrics.api_requests, successful_requests: metrics.metrics.successful_requests, failed_requests: metrics.metrics.failed_requests, tokens: metrics.metrics.total_tokens })); }; // Calculate top API keys from the breakdown data const getTopKeys = () => { const keySpend: { [key: string]: KeyMetricWithMetadata } = {}; userSpendData.results.forEach(day => { Object.entries(day.breakdown.api_keys || {}).forEach(([key, metrics]) => { if (!keySpend[key]) { keySpend[key] = { metrics: { spend: 0, prompt_tokens: 0, completion_tokens: 0, total_tokens: 0, api_requests: 0, successful_requests: 0, failed_requests: 0, cache_read_input_tokens: 0, cache_creation_input_tokens: 0 }, metadata: { key_alias: metrics.metadata.key_alias } }; } keySpend[key].metrics.spend += metrics.metrics.spend; keySpend[key].metrics.prompt_tokens += metrics.metrics.prompt_tokens; keySpend[key].metrics.completion_tokens += metrics.metrics.completion_tokens; keySpend[key].metrics.total_tokens += metrics.metrics.total_tokens; keySpend[key].metrics.api_requests += metrics.metrics.api_requests; keySpend[key].metrics.successful_requests += metrics.metrics.successful_requests; keySpend[key].metrics.failed_requests += metrics.metrics.failed_requests; keySpend[key].metrics.cache_read_input_tokens += metrics.metrics.cache_read_input_tokens || 0; keySpend[key].metrics.cache_creation_input_tokens += metrics.metrics.cache_creation_input_tokens || 0; }); }); return Object.entries(keySpend) .map(([api_key, metrics]) => ({ api_key, key_alias: metrics.metadata.key_alias || "-", // Using truncated key as alias spend: metrics.metrics.spend, })) .sort((a, b) => b.spend - a.spend) .slice(0, 5); }; const fetchUserSpendData = async () => { if (!accessToken || !dateValue.from || !dateValue.to) return; const startTime = dateValue.from; const endTime = dateValue.to; try { // Get first page const firstPageData = await userDailyActivityCall(accessToken, startTime, endTime); // Check if we need to fetch more pages if (firstPageData.metadata.total_pages > 10) { throw new Error("Too many pages of data (>10). Please select a smaller date range."); } // If only one page, just set the data if (firstPageData.metadata.total_pages === 1) { setUserSpendData(firstPageData); return; } // Fetch all pages const allResults = [...firstPageData.results]; for (let page = 2; page <= firstPageData.metadata.total_pages; page++) { const pageData = await userDailyActivityCall(accessToken, startTime, endTime, page); allResults.push(...pageData.results); } // Combine all results with the first page's metadata setUserSpendData({ results: allResults, metadata: firstPageData.metadata }); } catch (error) { console.error("Error fetching user spend data:", error); throw error; } }; useEffect(() => { fetchUserSpendData(); }, [accessToken, dateValue]); const modelMetrics = processActivityData(userSpendData); return (
Usage Analytics Dashboard Your Usage Tag Usage {/* Your Usage Panel */} Select Time Range { setDateValue(value); }} /> Cost Activity {/* Cost Panel */} {/* Total Spend Card */} Project Spend {new Date().toLocaleString('default', { month: 'long' })} 1 - {new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0).getDate()} Usage Metrics Total Requests {userSpendData.metadata?.total_api_requests?.toLocaleString() || 0} Successful Requests {userSpendData.metadata?.total_successful_requests?.toLocaleString() || 0} Failed Requests {userSpendData.metadata?.total_failed_requests?.toLocaleString() || 0} Total Tokens {userSpendData.metadata?.total_tokens?.toLocaleString() || 0} Average Cost per Request ${((totalSpend || 0) / (userSpendData.metadata?.total_api_requests || 1)).toFixed(4)} {/* Daily Spend Chart */} Daily Spend new Date(a.date).getTime() - new Date(b.date).getTime() )} index="date" categories={["metrics.spend"]} colors={["cyan"]} valueFormatter={(value) => `$${value.toFixed(2)}`} yAxisWidth={100} showLegend={false} customTooltip={({ payload, active }) => { if (!active || !payload?.[0]) return null; const data = payload[0].payload; return (

{data.date}

Spend: ${data.metrics.spend.toFixed(2)}

Requests: {data.metrics.api_requests}

Successful: {data.metrics.successful_requests}

Failed: {data.metrics.failed_requests}

Tokens: {data.metrics.total_tokens}

); }} />
{/* Top API Keys */} Top API Keys {/* Top Models */}
Top Models
`$${value.toFixed(2)}`} layout="vertical" yAxisWidth={200} showLegend={false} customTooltip={({ payload, active }) => { if (!active || !payload?.[0]) return null; const data = payload[0].payload; return (

{data.key}

Spend: ${data.spend.toFixed(2)}

Total Requests: {data.requests.toLocaleString()}

Successful: {data.successful_requests.toLocaleString()}

Failed: {data.failed_requests.toLocaleString()}

Tokens: {data.tokens.toLocaleString()}

); }} />
{/* Spend by Provider */}
Spend by Provider
`$${value.toFixed(2)}`} colors={["cyan"]} /> Provider Spend Successful Failed Tokens {getProviderSpend() .filter(provider => provider.spend > 0) .map((provider) => ( {provider.provider} ${provider.spend < 0.00001 ? "less than 0.00001" : provider.spend.toFixed(2)} {provider.successful_requests.toLocaleString()} {provider.failed_requests.toLocaleString()} {provider.tokens.toLocaleString()} ))}
{/* Usage Metrics */}
{/* Activity Panel */}
{/* Tag Usage Panel */}
); }; // Add this helper function to process model-specific activity data const getModelActivityData = (userSpendData: { results: DailyData[]; metadata: any; }) => { const modelData: { [key: string]: { total_requests: number; total_tokens: number; daily_data: Array<{ date: string; api_requests: number; total_tokens: number; }>; }; } = {}; userSpendData.results.forEach((day: DailyData) => { Object.entries(day.breakdown.models || {}).forEach(([model, metrics]) => { if (!modelData[model]) { modelData[model] = { total_requests: 0, total_tokens: 0, daily_data: [] }; } modelData[model].total_requests += metrics.metrics.api_requests; modelData[model].total_tokens += metrics.metrics.total_tokens; modelData[model].daily_data.push({ date: day.date, api_requests: metrics.metrics.api_requests, total_tokens: metrics.metrics.total_tokens }); }); }); return modelData; }; // Add this helper function for number formatting function valueFormatterNumbers(number: number) { const formatter = new Intl.NumberFormat('en-US', { maximumFractionDigits: 0, notation: 'compact', compactDisplay: 'short', }); return formatter.format(number); } export default NewUsagePage;