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;