Files
EvoScientist-WebUI/src/app/components/ContextUsageIndicator.tsx
T
2026-08-11 17:44:52 +08:00

70 lines
2.1 KiB
TypeScript

"use client";
import React from "react";
import { CircleGauge } from "lucide-react";
import {
formatCompactTokens,
type ContextUsageEvent,
} from "@/lib/contextUsage";
import { cn } from "@/lib/utils";
import { useTranslations } from "next-intl";
/**
* Live context-occupancy pill.
*
* `measuredTokens` is the provider-measured prompt size of the most recent
* model call (covers system prompt, memory and tools) and is preferred over
* the middleware's message-text estimate, which systematically undercounts
* whenever prompts carry large non-message sections. The denominator is the
* snapshot-frozen `input_limit` carried by the stream event, so the pill
* appears once the session's first model call completes.
*/
export const ContextUsageIndicator = React.memo<{
usage: ContextUsageEvent | null;
measuredTokens: number | null;
}>(function ContextUsageIndicator({ usage, measuredTokens }) {
const t = useTranslations("chat");
if (!usage) return null;
const estimated = usage.usedTokens + usage.reservedTokens;
const occupied = measuredTokens ?? estimated;
const limit = usage.inputLimit;
const percent = Math.max(0, Math.round((occupied / limit) * 100));
const nearlyFull = occupied >= limit * 0.85;
const label = t("contextUsage.label", {
percent,
used: formatCompactTokens(occupied),
limit: formatCompactTokens(limit),
});
const tooltipParams = {
used: occupied.toLocaleString(),
limit: limit.toLocaleString(),
soft: usage.softTokens.toLocaleString(),
};
const tooltip =
measuredTokens !== null
? t("contextUsage.tooltipMeasured", tooltipParams)
: t("contextUsage.tooltipEstimate", tooltipParams);
return (
<span
title={tooltip}
aria-label={tooltip}
className={cn(
"flex shrink-0 items-center gap-1.5 rounded px-1.5 py-0.5",
nearlyFull
? "text-amber-700 dark:text-amber-300"
: "text-muted-foreground"
)}
>
<CircleGauge
className="size-3.5 shrink-0"
aria-hidden="true"
/>
<span className="hidden sm:inline">{label}</span>
</span>
);
});
ContextUsageIndicator.displayName = "ContextUsageIndicator";