import { Fragment, type ReactNode } from 'react';
import { Zap } from 'lucide-react';
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/Tooltip';
import { defineMessages, useIntl } from '../i18n';
import { cn } from '../utils';
import type { MessageUsage } from '../types/message';
import {
formatCost,
formatDuration,
formatTokenCount,
formatTokensPerSecond,
tokensPerSecond,
} from '../utils/usageFormatting';
const i18n = defineMessages({
tokensPerSecondUnit: {
id: 'messageUsageStats.tokensPerSecondUnit',
defaultMessage: 'tok/s',
},
tokenUnit: {
id: 'messageUsageStats.tokenUnit',
defaultMessage: 'tok',
},
input: {
id: 'messageUsageStats.input',
defaultMessage: 'Input',
},
cacheRead: {
id: 'messageUsageStats.cacheRead',
defaultMessage: 'cache read',
},
cacheWrite: {
id: 'messageUsageStats.cacheWrite',
defaultMessage: 'cache write',
},
cacheHitRate: {
id: 'messageUsageStats.cacheHitRate',
defaultMessage: '({percent}% of input)',
},
output: {
id: 'messageUsageStats.output',
defaultMessage: 'Output',
},
total: {
id: 'messageUsageStats.total',
defaultMessage: 'Total',
},
firstToken: {
id: 'messageUsageStats.firstToken',
defaultMessage: 'First token',
},
totalTime: {
id: 'messageUsageStats.totalTime',
defaultMessage: 'Total time',
},
speed: {
id: 'messageUsageStats.speed',
defaultMessage: 'Speed',
},
cost: {
id: 'messageUsageStats.cost',
defaultMessage: 'Cost',
},
estimated: {
id: 'messageUsageStats.estimated',
defaultMessage: '(estimated)',
},
reported: {
id: 'messageUsageStats.reported',
defaultMessage: '(reported)',
},
compaction: {
id: 'messageUsageStats.compaction',
defaultMessage: 'Compaction',
},
});
function hasValue(value: number | null | undefined): value is number {
return typeof value === 'number' && Number.isFinite(value);
}
/** One label/value row of the tooltip's stats card. */
function StatRow({
label,
value,
sub,
}: {
label: string;
value: ReactNode;
/** Dimmer, indented breakdown row (e.g. cache read/write). */
sub?: boolean;
}) {
return (
{label}
{value}
);
}
/**
* Per-message usage chip (tok/s, cost, total tokens) with a tooltip breaking
* down tokens, caching, timing, and cost. Renders nothing without data.
*/
export default function MessageUsageStats({ usage }: { usage: MessageUsage }) {
const intl = useIntl();
const {
inputTokens,
outputTokens,
totalTokens,
cacheReadTokens,
cacheWriteTokens,
cost,
costSource,
elapsedMs,
timeToFirstTokenMs,
isCompaction,
} = usage;
const tps = tokensPerSecond(outputTokens, elapsedMs);
const chipSegments: ReactNode[] = [];
if (tps !== null) {
chipSegments.push(
{formatTokensPerSecond(tps)} {intl.formatMessage(i18n.tokensPerSecondUnit)}
);
}
if (hasValue(cost)) {
chipSegments.push(
{costSource === 'estimated' ? '~' : ''}
{formatCost(cost)}
);
}
if (hasValue(totalTokens)) {
chipSegments.push(
{formatTokenCount(totalTokens)} {intl.formatMessage(i18n.tokenUnit)}
);
}
if (chipSegments.length === 0) {
return null;
}
const hasTokensSection =
hasValue(inputTokens) ||
hasValue(outputTokens) ||
hasValue(totalTokens) ||
hasValue(cacheReadTokens) ||
hasValue(cacheWriteTokens);
const hasTimingSection = hasValue(timeToFirstTokenMs) || hasValue(elapsedMs) || tps !== null;
const hasCostSection = hasValue(cost);
const section = 'py-1.5 first:pt-0 last:pb-0 space-y-[3px]';
return (
{chipSegments.map((segment, index) => (
{index > 0 && ยท}
{segment}
))}
{hasTokensSection && (
{hasValue(inputTokens) && (
)}
{hasValue(cacheReadTokens) && (
{formatTokenCount(cacheReadTokens)}
{hasValue(inputTokens) && inputTokens > 0 && (
{' '}
{intl.formatMessage(i18n.cacheHitRate, {
percent: Math.round((cacheReadTokens / inputTokens) * 100),
})}
)}
>
}
/>
)}
{hasValue(cacheWriteTokens) && (
)}
{hasValue(outputTokens) && (
)}
{hasValue(totalTokens) && (
)}
)}
{hasTimingSection && (
{hasValue(timeToFirstTokenMs) && (
)}
{hasValue(elapsedMs) && (
)}
{tps !== null && (
)}
)}
{hasCostSection && (
{hasValue(cost) && (
{formatCost(cost)}
{(costSource === 'provider_reported' || costSource === 'estimated') && (
{' '}
{intl.formatMessage(
costSource === 'provider_reported' ? i18n.reported : i18n.estimated
)}
)}
>
}
/>
)}
)}
{isCompaction && (
{intl.formatMessage(i18n.compaction)}
)}
);
}