168 lines · 6.5 KB · TypeScript
RawDownload
import { Button } from "../ui/button";import { type ContextWindowSnapshot, formatContextWindowTokens } from "~/lib/contextWindow";import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover";import { formatContextWindowCompactionMessage } from "./ContextWindowMeter.logic";import { Minimize2Icon } from "lucide-react";import { composerFloatingLayerProps } from "./composerEventScope"; function formatPercentage(value: number | null): string | null {  if (value === null || !Number.isFinite(value)) {    return null;  }  if (value < 10) {    return `${value.toFixed(1).replace(/\.0$/, "")}%`;  }  return `${Math.round(value)}%`;} export function ContextWindowMeter(props: {  usage: ContextWindowSnapshot;  modelDisplayName?: string | null;  onCompact?: (() => void) | undefined;  compactDisabled?: boolean | undefined;  compactDisabledReason?: string | null | undefined;}) {  const { usage, modelDisplayName, onCompact, compactDisabled, compactDisabledReason } = props;  const usedPercentage = formatPercentage(usage.usedPercentage);  const normalizedPercentage = Math.max(0, Math.min(100, usage.usedPercentage ?? 0));  const radius = 9.75;  const circumference = 2 * Math.PI * radius;  const dashOffset = circumference * (1 - normalizedPercentage / 100);  const totalProcessedTokens = usage.totalProcessedTokens ?? null;  const showTotalProcessed = totalProcessedTokens !== null && totalProcessedTokens > 0;  const isOverloaded = normalizedPercentage > 90;  const usageColor = isOverloaded    ? "var(--color-error)"    : "color-mix(in oklab, var(--color-muted-foreground) 72%, transparent)";   return (    <Popover>      <PopoverTrigger        openOnHover        delay={150}        closeDelay={onCompact ? 150 : 0}        render={          <Button            size="icon-sm"            variant="ghost-muted"            className="size-7 rounded-full hover:text-muted-foreground data-pressed:text-muted-foreground"            aria-label={              usage.maxTokens !== null && usedPercentage                ? `Context window ${usedPercentage} used`                : `Context window ${formatContextWindowTokens(usage.usedTokens)} tokens used`            }          >            <span className="relative flex size-5 items-center justify-center">              <svg                viewBox="0 0 24 24"                className="-rotate-90 absolute inset-0 size-full transform-gpu mx-0!"                aria-hidden="true"              >                <circle                  cx="12"                  cy="12"                  r={radius}                  fill="none"                  stroke="color-mix(in oklab, var(--color-muted-foreground) 24%, transparent)"                  strokeWidth="3"                />                <circle                  cx="12"                  cy="12"                  r={radius}                  fill="none"                  stroke={usageColor}                  strokeWidth="3"                  strokeLinecap="round"                  strokeDasharray={circumference}                  strokeDashoffset={dashOffset}                  className="transition-[stroke-dashoffset,stroke] duration-500 ease-out motion-reduce:transition-none"                />              </svg>            </span>          </Button>        }      />      <PopoverPopup        {...composerFloatingLayerProps}        tooltipStyle        side="top"        align="end"        viewportClassName="p-0"        className="w-64 max-w-none text-left whitespace-normal"      >        <div className="flex flex-col gap-2 p-[var(--floating-content-inset)]">          <div className="flex items-center justify-between gap-3">            <div className="font-medium text-muted-foreground text-xs">Context Window</div>            {usage.maxTokens !== null && usedPercentage ? (              <div className="text-secondary-label text-[11px] tabular-nums">                <span>{usedPercentage}</span>                <span className="mx-1">·</span>                <span>                  {formatContextWindowTokens(usage.usedTokens)}/                  {formatContextWindowTokens(usage.maxTokens ?? null)}                </span>              </div>            ) : (              <div className="text-secondary-label text-[11px] tabular-nums">                {formatContextWindowTokens(usage.usedTokens)}              </div>            )}          </div>          {usage.maxTokens !== null ? (            <div              className="h-1.5 w-full overflow-hidden rounded-full bg-muted/60"              role="progressbar"              aria-valuemin={0}              aria-valuemax={100}              aria-valuenow={Math.round(normalizedPercentage)}              aria-label="Context window usage"            >              <div                className="h-full rounded-full transition-[width,background-color] duration-500 ease-out motion-reduce:transition-none"                style={{ width: `${normalizedPercentage}%`, backgroundColor: usageColor }}              />            </div>          ) : null}          {showTotalProcessed ? (            <div className="flex items-center justify-between gap-3 text-[11px] leading-4">              <span className="text-secondary-label">Total processed</span>              <span className="font-medium tabular-nums text-secondary-label">                {formatContextWindowTokens(totalProcessedTokens)}              </span>            </div>          ) : null}          {usage.compactsAutomatically ? (            <div className="mt-1 text-pretty text-secondary-label text-[11px] font-medium">              {formatContextWindowCompactionMessage(modelDisplayName, usage.autoCompactThreshold)}            </div>          ) : null}          {onCompact ? (            <>              <Button                size="xs"                variant="outline"                className="mt-1 w-full justify-center"                disabled={compactDisabled}                onClick={onCompact}              >                <Minimize2Icon aria-hidden="true" />                Compact context              </Button>              {compactDisabled && compactDisabledReason ? (                <div className="text-pretty text-secondary-label text-[11px]">                  {compactDisabledReason}                </div>              ) : null}            </>          ) : null}        </div>      </PopoverPopup>    </Popover>  );} /** Holds the meter's footprint while a thread's activities are still loading. */export function ContextWindowMeterPlaceholder() {  return <span aria-hidden="true" className="size-7 shrink-0" />;}