diff --git a/client/src/components/Chat/Input/TokenUsageIndicator.tsx b/client/src/components/Chat/Input/TokenUsageIndicator.tsx index d99c1bcf94..a70f356d03 100644 --- a/client/src/components/Chat/Input/TokenUsageIndicator.tsx +++ b/client/src/components/Chat/Input/TokenUsageIndicator.tsx @@ -1,5 +1,5 @@ import { memo } from 'react'; -import { TooltipAnchor } from '@librechat/client'; +import { HoverCard, HoverCardTrigger, HoverCardContent, HoverCardPortal } from '@librechat/client'; import { useLocalize, useTokenUsage } from '~/hooks'; import { cn } from '~/utils'; @@ -13,6 +13,147 @@ function formatTokens(n: number): string { 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(); @@ -28,17 +169,6 @@ const TokenUsageIndicator = memo(function TokenUsageIndicator() { const circumference = 2 * Math.PI * radius; const offset = circumference - (percentage / 100) * circumference; - const tooltipText = hasMaxContext - ? localize('com_ui_token_usage_with_max', { - 0: formatTokens(inputTokens), - 1: formatTokens(outputTokens), - 2: formatTokens(maxContext), - }) - : localize('com_ui_token_usage_no_max', { - 0: formatTokens(inputTokens), - 1: formatTokens(outputTokens), - }); - const ariaLabel = hasMaxContext ? localize('com_ui_token_usage_aria_full', { 0: formatTokens(inputTokens), @@ -66,12 +196,11 @@ const TokenUsageIndicator = memo(function TokenUsageIndicator() { }; return ( - + +
- } - /> + + + + + + + + ); }); diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 9a15b24253..4efe902309 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -1324,8 +1324,14 @@ "com_ui_token_url": "Token URL", "com_ui_token_usage_aria_full": "Token usage: {{0}} input, {{1}} output, {{2}} max context, {{3}}% used", "com_ui_token_usage_aria_no_max": "Token usage: {{0}} input, {{1}} output", + "com_ui_token_usage_context": "Context Usage", "com_ui_token_usage_indicator": "Token usage indicator", + "com_ui_token_usage_input": "Input", + "com_ui_token_usage_max_context": "Max Context", "com_ui_token_usage_no_max": "Input: {{0}} | Output: {{1}} | Max: N/A", + "com_ui_token_usage_output": "Output", + "com_ui_token_usage_percent": "{{0}}% used", + "com_ui_token_usage_total": "Total", "com_ui_token_usage_with_max": "Input: {{0}} | Output: {{1}} | Max: {{2}}", "com_ui_tokens": "tokens", "com_ui_tool_collection_prefix": "A collection of tools from",