mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-08 22:21:35 +00:00
Enhance chart readability with short-form notation for large numbers (#12370)
* format y-axis value for total tokens * format y-axis for the rest of the charts on model-activity * revert changes for requests per day * labels modified to plain text * added plain text label for api_requests and spend * minor * move components to utils
This commit is contained in:
parent
88e4d302a2
commit
ebae03cf93
2 changed files with 138 additions and 163 deletions
|
|
@ -1,143 +1,16 @@
|
|||
import React from 'react';
|
||||
import { Card, Grid, Text, Title, Accordion, AccordionHeader, AccordionBody } from '@tremor/react';
|
||||
import { Card, Grid, Text, Title } from '@tremor/react';
|
||||
import { AreaChart, BarChart } from '@tremor/react';
|
||||
import { SpendMetrics, DailyData, ModelActivityData, MetricWithMetadata, KeyMetricWithMetadata, TopApiKeyData } from './usage/types';
|
||||
import { DailyData, ModelActivityData, KeyMetricWithMetadata, TopApiKeyData } from './usage/types';
|
||||
import { Collapse } from 'antd';
|
||||
import { formatNumberWithCommas } from '@/utils/dataUtils';
|
||||
import type { CustomTooltipProps } from "@tremor/react";
|
||||
import {
|
||||
valueFormatter,
|
||||
valueFormatterSpend,
|
||||
} from "../components/usage/utils/value_formatters";
|
||||
import { valueFormatter } from '../components/usage/utils/value_formatters';
|
||||
import { CustomTooltip, CustomLegend } from './common_components/chartUtils';
|
||||
|
||||
interface ActivityMetricsProps {
|
||||
modelMetrics: Record<string, ModelActivityData>;
|
||||
}
|
||||
|
||||
interface ChartDataPoint {
|
||||
date: string;
|
||||
metrics: SpendMetrics;
|
||||
}
|
||||
|
||||
const colorNameToHex: { [key: string]: string } = {
|
||||
blue: "#3b82f6",
|
||||
cyan: "#06b6d4",
|
||||
indigo: "#6366f1",
|
||||
green: "#22c55e",
|
||||
red: "#ef4444",
|
||||
purple: "#8b5cf6",
|
||||
};
|
||||
|
||||
export const CustomTooltip = ({
|
||||
active,
|
||||
payload,
|
||||
label,
|
||||
}: CustomTooltipProps) => {
|
||||
if (active && payload && payload.length) {
|
||||
const formatCategoryName = (name: string): string => {
|
||||
return name
|
||||
.replace("metrics.", "")
|
||||
.replace(/_/g, " ")
|
||||
.split(" ")
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(" ");
|
||||
};
|
||||
|
||||
const getRawValue = (
|
||||
dataPoint: ChartDataPoint,
|
||||
key: string
|
||||
): number | undefined => {
|
||||
// key is like "metrics.total_tokens"
|
||||
const metricKey = key.substring(
|
||||
key.indexOf(".") + 1
|
||||
) as keyof SpendMetrics;
|
||||
if (dataPoint.metrics && metricKey in dataPoint.metrics) {
|
||||
return dataPoint.metrics[metricKey];
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-56 rounded-tremor-default border border-tremor-border bg-tremor-background p-2 text-tremor-default shadow-tremor-dropdown">
|
||||
<p className="text-tremor-content-strong">{label}</p>
|
||||
{payload.map((item) => {
|
||||
const dataKey = item.dataKey?.toString();
|
||||
if (!dataKey || !item.payload) return null;
|
||||
|
||||
const rawValue = getRawValue(item.payload, dataKey);
|
||||
const isSpend = dataKey.includes("spend");
|
||||
const formattedValue =
|
||||
rawValue !== undefined
|
||||
? isSpend
|
||||
? `$${rawValue.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
|
||||
: rawValue.toLocaleString()
|
||||
: "N/A";
|
||||
|
||||
const colorName = item.color as keyof typeof colorNameToHex;
|
||||
const hexColor = colorNameToHex[colorName] || item.color;
|
||||
return (
|
||||
<div
|
||||
key={dataKey}
|
||||
className="flex items-center justify-between space-x-4"
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span
|
||||
className={`h-2 w-2 shrink-0 rounded-full ring-2 ring-white drop-shadow-md`}
|
||||
style={{ backgroundColor: hexColor }}
|
||||
/>
|
||||
<p className="font-medium text-tremor-content dark:text-dark-tremor-content">
|
||||
{formatCategoryName(dataKey)}
|
||||
</p>
|
||||
</div>
|
||||
<p className="font-medium text-tremor-content-emphasis dark:text-dark-tremor-content-emphasis">
|
||||
{formattedValue}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const CustomLegend = ({
|
||||
categories,
|
||||
colors,
|
||||
}: {
|
||||
categories: string[];
|
||||
colors: string[];
|
||||
}) => {
|
||||
const formatCategoryName = (name: string): string => {
|
||||
return name
|
||||
.replace("metrics.", "")
|
||||
.replace(/_/g, " ")
|
||||
.split(" ")
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(" ");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-end space-x-4">
|
||||
{categories.map((category, idx) => {
|
||||
const colorName = colors[idx] as keyof typeof colorNameToHex;
|
||||
const hexColor = colorNameToHex[colorName] || colors[idx];
|
||||
return (
|
||||
<div key={category} className="flex items-center space-x-2">
|
||||
<span
|
||||
className={`h-2 w-2 shrink-0 rounded-full ring-4 ring-white`}
|
||||
style={{ backgroundColor: hexColor }}
|
||||
/>
|
||||
<p className="text-sm text-tremor-content dark:text-dark-tremor-content">
|
||||
{formatCategoryName(category)}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ModelSection = ({
|
||||
modelName,
|
||||
metrics,
|
||||
|
|
@ -215,14 +88,7 @@ const ModelSection = ({
|
|||
<Card>
|
||||
<div className="flex justify-between items-center">
|
||||
<Title>Total Tokens</Title>
|
||||
<CustomLegend
|
||||
categories={[
|
||||
"metrics.prompt_tokens",
|
||||
"metrics.completion_tokens",
|
||||
"metrics.total_tokens",
|
||||
]}
|
||||
colors={["blue", "cyan", "indigo"]}
|
||||
/>
|
||||
<CustomLegend categories={["metrics.prompt_tokens", "metrics.completion_tokens", "metrics.total_tokens"]} colors={["blue", "cyan", "indigo"]} />
|
||||
</div>
|
||||
<AreaChart
|
||||
className="mt-4"
|
||||
|
|
@ -243,10 +109,7 @@ const ModelSection = ({
|
|||
<Card>
|
||||
<div className="flex justify-between items-center">
|
||||
<Title>Requests per day</Title>
|
||||
<CustomLegend
|
||||
categories={["metrics.api_requests"]}
|
||||
colors={["blue"]}
|
||||
/>
|
||||
<CustomLegend categories={["metrics.api_requests"]} colors={["blue"]} />
|
||||
</div>
|
||||
<BarChart
|
||||
className="mt-4"
|
||||
|
|
@ -271,9 +134,7 @@ const ModelSection = ({
|
|||
index="date"
|
||||
categories={["metrics.spend"]}
|
||||
colors={["green"]}
|
||||
valueFormatter={valueFormatterSpend}
|
||||
customTooltip={CustomTooltip}
|
||||
showLegend={false}
|
||||
valueFormatter={(value: number) => `$${formatNumberWithCommas(value, 2)}`}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
|
|
@ -487,26 +348,14 @@ export const ActivityMetrics: React.FC<ActivityMetricsProps> = ({
|
|||
/>
|
||||
</Card>
|
||||
<Card>
|
||||
<div className="flex justify-between items-center">
|
||||
<Title>Total Requests Over Time</Title>
|
||||
<CustomLegend
|
||||
categories={[
|
||||
"metrics.successful_requests",
|
||||
"metrics.failed_requests",
|
||||
]}
|
||||
colors={["green", "red"]}
|
||||
/>
|
||||
</div>
|
||||
<Title>Total Requests Over Time</Title>
|
||||
<AreaChart
|
||||
className="mt-4"
|
||||
data={sortedDailyData}
|
||||
index="date"
|
||||
categories={[
|
||||
"metrics.successful_requests",
|
||||
"metrics.failed_requests",
|
||||
]}
|
||||
colors={["green", "red"]}
|
||||
valueFormatter={valueFormatter}
|
||||
categories={["metrics.successful_requests", "metrics.failed_requests"]}
|
||||
colors={["emerald", "red"]}
|
||||
valueFormatter={(number: number) => number.toLocaleString()}
|
||||
stack
|
||||
customTooltip={CustomTooltip}
|
||||
showLegend={false}
|
||||
|
|
@ -585,7 +434,6 @@ export const processActivityData = (dailyActivity: { results: DailyData[] }, key
|
|||
daily_data: []
|
||||
};
|
||||
}
|
||||
|
||||
// Update totals
|
||||
modelMetrics[model].total_requests += modelData.metrics.api_requests;
|
||||
modelMetrics[model].prompt_tokens += modelData.metrics.prompt_tokens;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,127 @@
|
|||
import React from "react";
|
||||
import type { CustomTooltipProps } from "@tremor/react";
|
||||
import { SpendMetrics } from "../usage/types";
|
||||
|
||||
interface ChartDataPoint {
|
||||
date: string;
|
||||
metrics: SpendMetrics;
|
||||
}
|
||||
|
||||
const colorNameToHex: { [key: string]: string } = {
|
||||
blue: "#3b82f6",
|
||||
cyan: "#06b6d4",
|
||||
indigo: "#6366f1",
|
||||
green: "#22c55e",
|
||||
red: "#ef4444",
|
||||
purple: "#8b5cf6",
|
||||
};
|
||||
|
||||
export const CustomTooltip = ({
|
||||
active,
|
||||
payload,
|
||||
label,
|
||||
}: CustomTooltipProps) => {
|
||||
if (active && payload && payload.length) {
|
||||
const formatCategoryName = (name: string): string => {
|
||||
return name
|
||||
.replace("metrics.", "")
|
||||
.replace(/_/g, " ")
|
||||
.split(" ")
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(" ");
|
||||
};
|
||||
|
||||
const getRawValue = (
|
||||
dataPoint: ChartDataPoint,
|
||||
key: string
|
||||
): number | undefined => {
|
||||
// key is like "metrics.total_tokens"
|
||||
const metricKey = key.substring(
|
||||
key.indexOf(".") + 1
|
||||
) as keyof SpendMetrics;
|
||||
if (dataPoint.metrics && metricKey in dataPoint.metrics) {
|
||||
return dataPoint.metrics[metricKey];
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-56 rounded-tremor-default border border-tremor-border bg-tremor-background p-2 text-tremor-default shadow-tremor-dropdown">
|
||||
<p className="text-tremor-content-strong">{label}</p>
|
||||
{payload.map((item) => {
|
||||
const dataKey = item.dataKey?.toString();
|
||||
if (!dataKey || !item.payload) return null;
|
||||
|
||||
const rawValue = getRawValue(item.payload, dataKey);
|
||||
const isSpend = dataKey.includes("spend");
|
||||
const formattedValue =
|
||||
rawValue !== undefined
|
||||
? isSpend
|
||||
? `$${rawValue.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
|
||||
: rawValue.toLocaleString()
|
||||
: "N/A";
|
||||
|
||||
const colorName = item.color as keyof typeof colorNameToHex;
|
||||
const hexColor = colorNameToHex[colorName] || item.color;
|
||||
return (
|
||||
<div
|
||||
key={dataKey}
|
||||
className="flex items-center justify-between space-x-4"
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span
|
||||
className={`h-2 w-2 shrink-0 rounded-full ring-2 ring-white drop-shadow-md`}
|
||||
style={{ backgroundColor: hexColor }}
|
||||
/>
|
||||
<p className="font-medium text-tremor-content dark:text-dark-tremor-content">
|
||||
{formatCategoryName(dataKey)}
|
||||
</p>
|
||||
</div>
|
||||
<p className="font-medium text-tremor-content-emphasis dark:text-dark-tremor-content-emphasis">
|
||||
{formattedValue}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const CustomLegend = ({
|
||||
categories,
|
||||
colors,
|
||||
}: {
|
||||
categories: string[];
|
||||
colors: string[];
|
||||
}) => {
|
||||
const formatCategoryName = (name: string): string => {
|
||||
return name
|
||||
.replace("metrics.", "")
|
||||
.replace(/_/g, " ")
|
||||
.split(" ")
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(" ");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-end space-x-4">
|
||||
{categories.map((category, idx) => {
|
||||
const colorName = colors[idx] as keyof typeof colorNameToHex;
|
||||
const hexColor = colorNameToHex[colorName] || colors[idx];
|
||||
return (
|
||||
<div key={category} className="flex items-center space-x-2">
|
||||
<span
|
||||
className={`h-2 w-2 shrink-0 rounded-full ring-4 ring-white`}
|
||||
style={{ backgroundColor: hexColor }}
|
||||
/>
|
||||
<p className="text-sm text-tremor-content dark:text-dark-tremor-content">
|
||||
{formatCategoryName(category)}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Loading…
Add table
Reference in a new issue