mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-02-15 23:18:09 +01:00
112 lines
3.2 KiB
TypeScript
112 lines
3.2 KiB
TypeScript
import { memo } from 'react';
|
|
import { TooltipAnchor } 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();
|
|
}
|
|
|
|
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 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),
|
|
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 (
|
|
<TooltipAnchor
|
|
description={tooltipText}
|
|
render={
|
|
<div
|
|
className="flex size-9 items-center justify-center rounded-full p-1 transition-colors hover:bg-surface-hover"
|
|
role="img"
|
|
aria-label={ariaLabel}
|
|
>
|
|
<svg
|
|
width={size}
|
|
height={size}
|
|
viewBox={`0 0 ${size} ${size}`}
|
|
className="rotate-[-90deg]"
|
|
aria-hidden="true"
|
|
>
|
|
{/* Background ring */}
|
|
<circle
|
|
cx={size / 2}
|
|
cy={size / 2}
|
|
r={radius}
|
|
fill="transparent"
|
|
strokeWidth={strokeWidth}
|
|
className="stroke-border-heavy"
|
|
/>
|
|
{/* Progress ring */}
|
|
<circle
|
|
cx={size / 2}
|
|
cy={size / 2}
|
|
r={radius}
|
|
fill="transparent"
|
|
strokeWidth={strokeWidth}
|
|
strokeDasharray={circumference}
|
|
strokeDashoffset={hasMaxContext ? offset : circumference}
|
|
strokeLinecap="round"
|
|
className={cn('transition-all duration-300', getProgressColor())}
|
|
/>
|
|
</svg>
|
|
</div>
|
|
}
|
|
/>
|
|
);
|
|
});
|
|
|
|
export default TokenUsageIndicator;
|