import { memo } from 'react';
import { HoverCard, HoverCardTrigger, HoverCardContent, HoverCardPortal } from '@librechat/client';
import { useLocalize, useTokenUsage } from '~/hooks';
import { cn } from '~/utils';
function formatTokens(n: number): string {
if (n >= 1000000) {
return `${(n / 1000000).toFixed(1)}M`;
}
if (n >= 1000) {
return `${(n / 1000).toFixed(1)}K`;
}
return n.toString();
}
interface ProgressBarProps {
value: number;
max: number;
colorClass: string;
showPercentage?: boolean;
}
function ProgressBar({ value, max, colorClass, showPercentage = false }: ProgressBarProps) {
const percentage = max > 0 ? Math.min((value / max) * 100, 100) : 0;
return (
{showPercentage && (
{Math.round(percentage)}%
)}
);
}
interface TokenRowProps {
label: string;
value: number;
total: number;
colorClass: string;
}
function TokenRow({ label, value, total, colorClass }: TokenRowProps) {
const percentage = total > 0 ? Math.round((value / total) * 100) : 0;
return (
{label}
{formatTokens(value)}
({percentage}%)
);
}
function TokenUsageContent() {
const localize = useLocalize();
const { inputTokens, outputTokens, maxContext } = useTokenUsage();
const totalUsed = inputTokens + outputTokens;
const hasMaxContext = maxContext !== null && maxContext > 0;
const percentage = hasMaxContext ? Math.min((totalUsed / maxContext) * 100, 100) : 0;
const getMainProgressColor = () => {
if (!hasMaxContext) {
return 'bg-text-secondary';
}
if (percentage > 90) {
return 'bg-red-500';
}
if (percentage > 75) {
return 'bg-yellow-500';
}
return 'bg-green-500';
};
return (
{/* Header */}
{localize('com_ui_token_usage_context')}
{hasMaxContext && (
90,
'text-yellow-500': percentage > 75 && percentage <= 90,
'text-green-500': percentage <= 75,
})}
>
{localize('com_ui_token_usage_percent', { 0: Math.round(percentage).toString() })}
)}
{/* Main Progress Bar */}
{hasMaxContext && (
{formatTokens(totalUsed)}
{formatTokens(maxContext)}
)}
{/* Divider */}
{/* Input/Output Breakdown */}
{/* Total Section */}
{localize('com_ui_token_usage_total')}
{formatTokens(totalUsed)}
{/* Max Context (when available) */}
{hasMaxContext && (
{localize('com_ui_token_usage_max_context')}
{formatTokens(maxContext)}
)}
);
}
const TokenUsageIndicator = memo(function TokenUsageIndicator() {
const localize = useLocalize();
const { inputTokens, outputTokens, maxContext } = useTokenUsage();
const totalUsed = inputTokens + outputTokens;
const hasMaxContext = maxContext !== null && maxContext > 0;
const percentage = hasMaxContext ? Math.min((totalUsed / maxContext) * 100, 100) : 0;
// Ring calculations
const size = 28;
const strokeWidth = 3.5;
const radius = (size - strokeWidth) / 2;
const circumference = 2 * Math.PI * radius;
const offset = circumference - (percentage / 100) * circumference;
const ariaLabel = hasMaxContext
? localize('com_ui_token_usage_aria_full', {
0: formatTokens(inputTokens),
1: formatTokens(outputTokens),
2: formatTokens(maxContext),
3: Math.round(percentage).toString(),
})
: localize('com_ui_token_usage_aria_no_max', {
0: formatTokens(inputTokens),
1: formatTokens(outputTokens),
});
// Color based on percentage
const getProgressColor = () => {
if (!hasMaxContext) {
return 'stroke-text-secondary';
}
if (percentage > 90) {
return 'stroke-red-500';
}
if (percentage > 75) {
return 'stroke-yellow-500';
}
return 'stroke-green-500';
};
return (
);
});
export default TokenUsageIndicator;