Files
maestro/ui/src/components/detail/ContextUsageGauge.tsx
T
oss-sync b857c33ef6
CI / build-and-test (push) Has been cancelled
sync: update from private repo (f6d625db)
2026-06-26 03:35:45 +00:00

91 lines
3.1 KiB
TypeScript

import { useTranslation } from 'react-i18next';
interface ContextUsageGaugeProps {
promptTokens?: number | null;
limitTokens?: number | null;
jobStatus?: string;
/**
* compact: 入力欄の直上に常時表示する低プロファイルのバー。概要タブのカード型
* (既定)と同じ色・比率ロジックを共有しつつ、薄い 1 行表示にする。
*/
compact?: boolean;
}
function formatNumber(n: number): string {
return n.toLocaleString('en-US');
}
function pickColorClass(ratio: number): string {
if (ratio >= 0.95) return 'bg-red-500';
if (ratio >= 0.85) return 'bg-orange-500';
if (ratio >= 0.70) return 'bg-amber-500';
return 'bg-emerald-500';
}
function pickLabel(jobStatus: string | undefined): string {
switch (jobStatus) {
case 'succeeded':
case 'failed':
case 'cancelled':
return 'Context usage at finish';
case 'waiting_human':
case 'waiting_subtasks':
return 'Context usage (paused)';
default:
return 'Context usage';
}
}
export function ContextUsageGauge({ promptTokens, limitTokens, jobStatus, compact }: ContextUsageGaugeProps) {
const { t } = useTranslation('detail');
if (!limitTokens || limitTokens <= 0) return null;
const tokens = typeof promptTokens === 'number' ? promptTokens : 0;
const awaiting = typeof promptTokens !== 'number';
const ratio = Math.min(1, Math.max(0, tokens / limitTokens));
const percent = Math.round(ratio * 100);
const remaining = Math.max(0, limitTokens - tokens);
const colorClass = pickColorClass(ratio);
const label = pickLabel(jobStatus);
if (compact) {
return (
<div
className="flex items-center gap-2 text-2xs text-slate-500 tabular-nums"
title={`${formatNumber(tokens)} / ${formatNumber(limitTokens)} tokens`}
aria-label={t('context.ariaLabel', { remaining: formatNumber(remaining), percent })}
>
<div className="h-1.5 flex-1 bg-slate-100 rounded-full overflow-hidden">
<div
className={`h-full ${colorClass} transition-[width] duration-300 ease-out`}
style={{ width: `${percent}%` }}
/>
</div>
<span className="shrink-0">
{awaiting ? t('context.awaiting') : t('context.remaining', { remaining: formatNumber(remaining), percent })}
</span>
</div>
);
}
return (
<div className="bg-canvas border border-slate-200 rounded-xl p-4 shadow-sm">
<div className="flex items-baseline justify-between mb-2">
<span className="text-sm font-semibold text-slate-700">{label}</span>
<span className="text-xs text-slate-500 tabular-nums">
{awaiting ? 'Awaiting first LLM call' : `${percent}%`}
</span>
</div>
<div className="w-full h-2 bg-slate-100 rounded-full overflow-hidden">
<div
className={`h-full ${colorClass} transition-[width] duration-300 ease-out`}
style={{ width: `${percent}%` }}
/>
</div>
<div className="mt-2 text-2xs text-slate-500 tabular-nums">
{formatNumber(tokens)} / {formatNumber(limitTokens)} tokens
</div>
</div>
);
}