Spaces:
Sleeping
Sleeping
import React from 'react'; | |
import { Card, Grid, Text, Title, Accordion, AccordionHeader, AccordionBody } from '@tremor/react'; | |
import { AreaChart, BarChart } from '@tremor/react'; | |
import { SpendMetrics, DailyData, ModelActivityData, MetricWithMetadata, KeyMetricWithMetadata } from './usage/types'; | |
import { Collapse } from 'antd'; | |
interface ActivityMetricsProps { | |
modelMetrics: Record<string, ModelActivityData>; | |
} | |
const ModelSection = ({ modelName, metrics }: { modelName: string; metrics: ModelActivityData }) => { | |
return ( | |
<div className="space-y-2"> | |
{/* Summary Cards */} | |
<Grid numItems={4} className="gap-4"> | |
<Card> | |
<Text>Total Requests</Text> | |
<Title>{metrics.total_requests.toLocaleString()}</Title> | |
</Card> | |
<Card> | |
<Text>Total Successful Requests</Text> | |
<Title>{metrics.total_successful_requests.toLocaleString()}</Title> | |
</Card> | |
<Card> | |
<Text>Total Tokens</Text> | |
<Title>{metrics.total_tokens.toLocaleString()}</Title> | |
<Text>{Math.round(metrics.total_tokens / metrics.total_successful_requests)} avg per successful request</Text> | |
</Card> | |
<Card> | |
<Text>Total Spend</Text> | |
<Title>${metrics.total_spend.toFixed(2)}</Title> | |
<Text>${(metrics.total_spend / metrics.total_successful_requests).toFixed(3)} per successful request</Text> | |
</Card> | |
</Grid> | |
{/* Charts */} | |
<Grid numItems={2} className="gap-4"> | |
<Card> | |
<Title>Total Tokens</Title> | |
<AreaChart | |
data={metrics.daily_data} | |
index="date" | |
categories={["metrics.prompt_tokens", "metrics.completion_tokens", "metrics.total_tokens"]} | |
colors={["blue", "cyan", "indigo"]} | |
valueFormatter={(number: number) => number.toLocaleString()} | |
/> | |
</Card> | |
<Card> | |
<Title>Requests per day</Title> | |
<BarChart | |
data={metrics.daily_data} | |
index="date" | |
categories={["metrics.api_requests"]} | |
colors={["blue"]} | |
valueFormatter={(number: number) => number.toLocaleString()} | |
/> | |
</Card> | |
<Card> | |
<Title>Spend per day</Title> | |
<BarChart | |
data={metrics.daily_data} | |
index="date" | |
categories={["metrics.spend"]} | |
colors={["green"]} | |
valueFormatter={(value: number) => `$${value.toFixed(2)}`} | |
/> | |
</Card> | |
<Card> | |
<Title>Success vs Failed Requests</Title> | |
<AreaChart | |
data={metrics.daily_data} | |
index="date" | |
categories={["metrics.successful_requests", "metrics.failed_requests"]} | |
colors={["emerald", "red"]} | |
valueFormatter={(number: number) => number.toLocaleString()} | |
stack | |
/> | |
</Card> | |
<Card> | |
<Title>Prompt Caching Metrics</Title> | |
<div className="mb-2"> | |
<Text>Cache Read: {metrics.total_cache_read_input_tokens?.toLocaleString() || 0} tokens</Text> | |
<Text>Cache Creation: {metrics.total_cache_creation_input_tokens?.toLocaleString() || 0} tokens</Text> | |
</div> | |
<AreaChart | |
data={metrics.daily_data} | |
index="date" | |
categories={["metrics.cache_read_input_tokens", "metrics.cache_creation_input_tokens"]} | |
colors={["cyan", "purple"]} | |
valueFormatter={(number: number) => number.toLocaleString()} | |
/> | |
</Card> | |
</Grid> | |
</div> | |
); | |
}; | |
export const ActivityMetrics: React.FC<ActivityMetricsProps> = ({ modelMetrics }) => { | |
const modelNames = Object.keys(modelMetrics).sort((a, b) => { | |
if (a === '') return 1; | |
if (b === '') return -1; | |
return modelMetrics[b].total_spend - modelMetrics[a].total_spend; | |
}); | |
// Calculate total metrics across all models | |
const totalMetrics = { | |
total_requests: 0, | |
total_successful_requests: 0, | |
total_tokens: 0, | |
total_spend: 0, | |
total_cache_read_input_tokens: 0, | |
total_cache_creation_input_tokens: 0, | |
daily_data: {} as Record<string, { | |
prompt_tokens: number; | |
completion_tokens: number; | |
total_tokens: number; | |
api_requests: number; | |
spend: number; | |
successful_requests: number; | |
failed_requests: number; | |
cache_read_input_tokens: number; | |
cache_creation_input_tokens: number; | |
}> | |
}; | |
// Aggregate data | |
Object.values(modelMetrics).forEach(model => { | |
totalMetrics.total_requests += model.total_requests; | |
totalMetrics.total_successful_requests += model.total_successful_requests; | |
totalMetrics.total_tokens += model.total_tokens; | |
totalMetrics.total_spend += model.total_spend; | |
totalMetrics.total_cache_read_input_tokens += model.total_cache_read_input_tokens || 0; | |
totalMetrics.total_cache_creation_input_tokens += model.total_cache_creation_input_tokens || 0; | |
// Aggregate daily data | |
model.daily_data.forEach(day => { | |
if (!totalMetrics.daily_data[day.date]) { | |
totalMetrics.daily_data[day.date] = { | |
prompt_tokens: 0, | |
completion_tokens: 0, | |
total_tokens: 0, | |
api_requests: 0, | |
spend: 0, | |
successful_requests: 0, | |
failed_requests: 0, | |
cache_read_input_tokens: 0, | |
cache_creation_input_tokens: 0 | |
}; | |
} | |
totalMetrics.daily_data[day.date].prompt_tokens += day.metrics.prompt_tokens; | |
totalMetrics.daily_data[day.date].completion_tokens += day.metrics.completion_tokens; | |
totalMetrics.daily_data[day.date].total_tokens += day.metrics.total_tokens; | |
totalMetrics.daily_data[day.date].api_requests += day.metrics.api_requests; | |
totalMetrics.daily_data[day.date].spend += day.metrics.spend; | |
totalMetrics.daily_data[day.date].successful_requests += day.metrics.successful_requests; | |
totalMetrics.daily_data[day.date].failed_requests += day.metrics.failed_requests; | |
totalMetrics.daily_data[day.date].cache_read_input_tokens += day.metrics.cache_read_input_tokens || 0; | |
totalMetrics.daily_data[day.date].cache_creation_input_tokens += day.metrics.cache_creation_input_tokens || 0; | |
}); | |
}); | |
// Convert daily_data object to array and sort by date | |
const sortedDailyData = Object.entries(totalMetrics.daily_data) | |
.map(([date, metrics]) => ({ date, metrics })) | |
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); | |
return ( | |
<div className="space-y-8"> | |
{/* Global Summary */} | |
<div className="border rounded-lg p-4"> | |
<Title>Overall Usage</Title> | |
<Grid numItems={4} className="gap-4 mb-4"> | |
<Card> | |
<Text>Total Requests</Text> | |
<Title>{totalMetrics.total_requests.toLocaleString()}</Title> | |
</Card> | |
<Card> | |
<Text>Total Successful Requests</Text> | |
<Title>{totalMetrics.total_successful_requests.toLocaleString()}</Title> | |
</Card> | |
<Card> | |
<Text>Total Tokens</Text> | |
<Title>{totalMetrics.total_tokens.toLocaleString()}</Title> | |
</Card> | |
<Card> | |
<Text>Total Spend</Text> | |
<Title>${totalMetrics.total_spend.toFixed(2)}</Title> | |
</Card> | |
</Grid> | |
<Grid numItems={2} className="gap-4"> | |
<Card> | |
<Title>Total Tokens Over Time</Title> | |
<AreaChart | |
data={sortedDailyData} | |
index="date" | |
categories={["metrics.prompt_tokens", "metrics.completion_tokens", "metrics.total_tokens"]} | |
colors={["blue", "cyan", "indigo"]} | |
valueFormatter={(number: number) => number.toLocaleString()} | |
/> | |
</Card> | |
<Card> | |
<Title>Total Requests Over Time</Title> | |
<AreaChart | |
data={sortedDailyData} | |
index="date" | |
categories={["metrics.successful_requests", "metrics.failed_requests"]} | |
colors={["emerald", "red"]} | |
valueFormatter={(number: number) => number.toLocaleString()} | |
stack | |
/> | |
</Card> | |
</Grid> | |
</div> | |
{/* Individual Model Sections */} | |
<Collapse defaultActiveKey={modelNames[0]}> | |
{modelNames.map((modelName) => ( | |
<Collapse.Panel | |
key={modelName} | |
header={ | |
<div className="flex justify-between items-center w-full"> | |
<Title>{modelMetrics[modelName].label || 'Unknown Item'}</Title> | |
<div className="flex space-x-4 text-sm text-gray-500"> | |
<span>${modelMetrics[modelName].total_spend.toFixed(2)}</span> | |
<span>{modelMetrics[modelName].total_requests.toLocaleString()} requests</span> | |
</div> | |
</div> | |
} | |
> | |
<ModelSection | |
modelName={modelName || 'Unknown Model'} | |
metrics={modelMetrics[modelName]} | |
/> | |
</Collapse.Panel> | |
))} | |
</Collapse> | |
</div> | |
); | |
}; | |
// Helper function to format key label | |
const formatKeyLabel = (modelData: KeyMetricWithMetadata, model: string): string => { | |
const keyAlias = modelData.metadata.key_alias || `key-hash-${model}`; | |
const teamId = modelData.metadata.team_id; | |
return teamId ? `${keyAlias} (team_id: ${teamId})` : keyAlias; | |
}; | |
// Process data function | |
export const processActivityData = (dailyActivity: { results: DailyData[] }, key: "models" | "api_keys"): Record<string, ModelActivityData> => { | |
const modelMetrics: Record<string, ModelActivityData> = {}; | |
dailyActivity.results.forEach((day) => { | |
Object.entries(day.breakdown[key] || {}).forEach(([model, modelData]) => { | |
if (!modelMetrics[model]) { | |
modelMetrics[model] = { | |
label: key === 'api_keys' | |
? formatKeyLabel(modelData as KeyMetricWithMetadata, model) | |
: model, | |
total_requests: 0, | |
total_successful_requests: 0, | |
total_failed_requests: 0, | |
total_tokens: 0, | |
prompt_tokens: 0, | |
completion_tokens: 0, | |
total_spend: 0, | |
total_cache_read_input_tokens: 0, | |
total_cache_creation_input_tokens: 0, | |
daily_data: [] | |
}; | |
} | |
// Update totals | |
modelMetrics[model].total_requests += modelData.metrics.api_requests; | |
modelMetrics[model].prompt_tokens += modelData.metrics.prompt_tokens; | |
modelMetrics[model].completion_tokens += modelData.metrics.completion_tokens; | |
modelMetrics[model].total_tokens += modelData.metrics.total_tokens; | |
modelMetrics[model].total_spend += modelData.metrics.spend; | |
modelMetrics[model].total_successful_requests += modelData.metrics.successful_requests; | |
modelMetrics[model].total_failed_requests += modelData.metrics.failed_requests; | |
modelMetrics[model].total_cache_read_input_tokens += modelData.metrics.cache_read_input_tokens || 0; | |
modelMetrics[model].total_cache_creation_input_tokens += modelData.metrics.cache_creation_input_tokens || 0; | |
// Add daily data | |
modelMetrics[model].daily_data.push({ | |
date: day.date, | |
metrics: { | |
prompt_tokens: modelData.metrics.prompt_tokens, | |
completion_tokens: modelData.metrics.completion_tokens, | |
total_tokens: modelData.metrics.total_tokens, | |
api_requests: modelData.metrics.api_requests, | |
spend: modelData.metrics.spend, | |
successful_requests: modelData.metrics.successful_requests, | |
failed_requests: modelData.metrics.failed_requests, | |
cache_read_input_tokens: modelData.metrics.cache_read_input_tokens || 0, | |
cache_creation_input_tokens: modelData.metrics.cache_creation_input_tokens || 0 | |
} | |
}); | |
}); | |
}); | |
// Sort daily data | |
Object.values(modelMetrics).forEach(metrics => { | |
metrics.daily_data.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); | |
}); | |
return modelMetrics; | |
}; |