fix(ui): api usage
This commit is contained in:
parent
aa62d285cd
commit
9650283367
|
|
@ -282,6 +282,41 @@ function fmtLatencyMs(ms: number | null | undefined): string {
|
|||
return `${(ms / 1000).toFixed(2)}s`;
|
||||
}
|
||||
|
||||
function usageBarColor(pct: number): string {
|
||||
if (pct > 90) return "bg-[color:var(--danger)]";
|
||||
if (pct > 75) return "bg-[color:var(--warning)]";
|
||||
return "bg-[color:var(--success)]";
|
||||
}
|
||||
|
||||
function fmtPct(pct: number): string {
|
||||
return `${Math.max(0, Math.min(100, pct)).toFixed(0)}%`;
|
||||
}
|
||||
|
||||
interface UsageWindowBarProps {
|
||||
label: string;
|
||||
pct: number;
|
||||
resetInMs?: number | null;
|
||||
}
|
||||
|
||||
function UsageWindowBar({ label, pct, resetInMs }: UsageWindowBarProps) {
|
||||
const clamped = Math.max(0, Math.min(100, pct));
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center justify-between text-[11px]">
|
||||
<span className="font-medium text-muted">{label}</span>
|
||||
<span className="tabular-nums text-strong">{fmtPct(clamped)} used</span>
|
||||
</div>
|
||||
<div className="h-1.5 overflow-hidden rounded-full bg-[color:var(--surface-strong)]">
|
||||
<div
|
||||
className={`h-full rounded-full transition-all ${usageBarColor(clamped)}`}
|
||||
style={{ width: `${clamped}%` }}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-[11px] text-muted">Resets in {fmtResetMs(resetInMs)}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function UsageStrip({ credentialId, provider }: { credentialId: string; provider: string }) {
|
||||
const [usage, setUsage] = useState<ProviderUsageLiveRead | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
|
@ -333,6 +368,28 @@ function UsageStrip({ credentialId, provider }: { credentialId: string; provider
|
|||
const inputTok = usage.input_tokens;
|
||||
const req = usage.requests;
|
||||
const isOllama = provider === "ollama";
|
||||
const usageBars: UsageWindowBarProps[] = [];
|
||||
if (inputTok.pct_used != null) {
|
||||
usageBars.push({
|
||||
label: "Current session",
|
||||
pct: inputTok.pct_used,
|
||||
resetInMs: inputTok.reset_in_ms,
|
||||
});
|
||||
}
|
||||
if (tok.pct_used != null) {
|
||||
usageBars.push({
|
||||
label: usageBars.length > 0 ? "All models" : "Usage",
|
||||
pct: tok.pct_used,
|
||||
resetInMs: tok.reset_in_ms,
|
||||
});
|
||||
}
|
||||
if (usageBars.length === 0 && req.limit != null && req.remaining != null && req.limit > 0) {
|
||||
usageBars.push({
|
||||
label: "Requests",
|
||||
pct: ((req.limit - req.remaining) / req.limit) * 100,
|
||||
resetInMs: req.reset_in_ms,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-2 rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] p-2.5">
|
||||
|
|
@ -350,29 +407,34 @@ function UsageStrip({ credentialId, provider }: { credentialId: string; provider
|
|||
<RefreshCw className={`h-3 w-3 ${loading ? "animate-spin" : ""}`} />
|
||||
</button>
|
||||
</div>
|
||||
{(usage.sample_input_tokens != null || usage.sample_output_tokens != null) && (
|
||||
<div className="flex items-center justify-between text-[11px] text-muted">
|
||||
<span>Usage (last probe)</span>
|
||||
<span className="tabular-nums text-strong">
|
||||
in {fmtTokens(usage.sample_input_tokens)} · out {fmtTokens(usage.sample_output_tokens)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{usage.sample_latency_ms != null && (
|
||||
<div className="flex items-center justify-between text-[11px] text-muted">
|
||||
<span>Time (last probe)</span>
|
||||
<span className="tabular-nums text-strong">
|
||||
{fmtLatencyMs(usage.sample_latency_ms)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{usage.sample_latency_ms != null && (
|
||||
<div className="flex items-center justify-between text-[11px] text-muted">
|
||||
<span>Time (last probe)</span>
|
||||
<span className="tabular-nums text-strong">
|
||||
{fmtLatencyMs(usage.sample_latency_ms)}
|
||||
</span>
|
||||
{usageBars.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{usageBars.map((bar) => (
|
||||
<UsageWindowBar key={bar.label} label={bar.label} pct={bar.pct} resetInMs={bar.resetInMs} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{(usage.sample_input_tokens != null || usage.sample_output_tokens != null) && (
|
||||
<div className="flex items-center justify-between text-[11px] text-muted">
|
||||
<span>Usage (last probe)</span>
|
||||
<span className="tabular-nums text-strong">
|
||||
in {fmtTokens(usage.sample_input_tokens)} · out {fmtTokens(usage.sample_output_tokens)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{usage.sample_latency_ms != null && (
|
||||
<div className="flex items-center justify-between text-[11px] text-muted">
|
||||
<span>Time (last probe)</span>
|
||||
<span className="tabular-nums text-strong">
|
||||
{fmtLatencyMs(usage.sample_latency_ms)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<p className="text-[11px] text-muted">
|
||||
This provider did not return active usage windows for percent + reset tracking.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
<div className="flex items-center justify-between text-[11px] text-muted">
|
||||
{lastFetched && <span>Updated {Math.round((Date.now() - lastFetched.getTime()) / 1000)}s ago</span>}
|
||||
|
|
@ -380,86 +442,34 @@ function UsageStrip({ credentialId, provider }: { credentialId: string; provider
|
|||
</div>
|
||||
) : (
|
||||
<div className="space-y-1.5">
|
||||
{(usage.sample_input_tokens != null || usage.sample_output_tokens != null) && (
|
||||
<div className="flex items-center justify-between text-[11px] text-muted">
|
||||
<span>Usage (last probe)</span>
|
||||
<span className="tabular-nums text-strong">
|
||||
in {fmtTokens(usage.sample_input_tokens)} · out {fmtTokens(usage.sample_output_tokens)}
|
||||
</span>
|
||||
{usageBars.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{usageBars.map((bar) => (
|
||||
<UsageWindowBar key={bar.label} label={bar.label} pct={bar.pct} resetInMs={bar.resetInMs} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Tokens */}
|
||||
{tok.limit != null ? (
|
||||
<div>
|
||||
<div className="mb-1 flex items-center justify-between text-[11px]">
|
||||
<span className="font-medium text-muted">Tokens</span>
|
||||
<span className="tabular-nums text-strong">
|
||||
{fmtTokens(tok.remaining)} / {fmtTokens(tok.limit)} remaining
|
||||
{tok.reset_in_ms != null && (
|
||||
<span className="ml-2 text-muted">· resets in {fmtResetMs(tok.reset_in_ms)}</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
{tok.pct_used != null && (
|
||||
<div className="h-1.5 overflow-hidden rounded-full bg-[color:var(--surface-strong)]">
|
||||
<div
|
||||
className={`h-full rounded-full transition-all ${
|
||||
tok.pct_used > 90 ? "bg-[color:var(--danger)]" :
|
||||
tok.pct_used > 75 ? "bg-[color:var(--warning)]" :
|
||||
"bg-[color:var(--success)]"
|
||||
}`}
|
||||
style={{ width: `${Math.min(100, tok.pct_used)}%` }}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{(usage.sample_input_tokens != null || usage.sample_output_tokens != null) && (
|
||||
<div className="flex items-center justify-between text-[11px] text-muted">
|
||||
<span>Usage (last probe)</span>
|
||||
<span className="tabular-nums text-strong">
|
||||
in {fmtTokens(usage.sample_input_tokens)} · out {fmtTokens(usage.sample_output_tokens)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* Anthropic input tokens */}
|
||||
{inputTok.limit != null ? (
|
||||
<div>
|
||||
<div className="mb-1 flex items-center justify-between text-[11px]">
|
||||
<span className="font-medium text-muted">Input tokens</span>
|
||||
<span className="tabular-nums text-strong">
|
||||
{fmtTokens(inputTok.remaining)} / {fmtTokens(inputTok.limit)} remaining
|
||||
{inputTok.reset_in_ms != null && (
|
||||
<span className="ml-2 text-muted">· resets in {fmtResetMs(inputTok.reset_in_ms)}</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
{inputTok.pct_used != null && (
|
||||
<div className="h-1.5 overflow-hidden rounded-full bg-[color:var(--surface-strong)]">
|
||||
<div
|
||||
className={`h-full rounded-full transition-all ${
|
||||
inputTok.pct_used > 90 ? "bg-[color:var(--danger)]" :
|
||||
inputTok.pct_used > 75 ? "bg-[color:var(--warning)]" :
|
||||
"bg-[color:var(--success)]"
|
||||
}`}
|
||||
style={{ width: `${Math.min(100, inputTok.pct_used)}%` }}
|
||||
/>
|
||||
{usage.sample_latency_ms != null && (
|
||||
<div className="flex items-center justify-between text-[11px] text-muted">
|
||||
<span>Time (last probe)</span>
|
||||
<span className="tabular-nums text-strong">
|
||||
{fmtLatencyMs(usage.sample_latency_ms)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* Requests */}
|
||||
{req.limit != null ? (
|
||||
<div className="flex items-center justify-between text-[11px] text-muted">
|
||||
<span>Requests</span>
|
||||
<span className="tabular-nums">
|
||||
{req.remaining ?? "—"} / {req.limit} remaining
|
||||
{req.reset_in_ms != null && (
|
||||
<span className="ml-2">· resets in {fmtResetMs(req.reset_in_ms)}</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{tok.limit == null && inputTok.limit == null && req.limit == null && (
|
||||
<p className="text-[11px] text-muted">
|
||||
Connected — no token/request limit windows were returned for this key right now.
|
||||
</p>
|
||||
<p className="text-[11px] text-muted">
|
||||
Connected — provider did not return usage windows for percent + reset tracking.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-between text-[11px] text-muted">
|
||||
|
|
|
|||
Loading…
Reference in New Issue