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`;
|
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 }) {
|
function UsageStrip({ credentialId, provider }: { credentialId: string; provider: string }) {
|
||||||
const [usage, setUsage] = useState<ProviderUsageLiveRead | null>(null);
|
const [usage, setUsage] = useState<ProviderUsageLiveRead | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
@ -333,6 +368,28 @@ function UsageStrip({ credentialId, provider }: { credentialId: string; provider
|
||||||
const inputTok = usage.input_tokens;
|
const inputTok = usage.input_tokens;
|
||||||
const req = usage.requests;
|
const req = usage.requests;
|
||||||
const isOllama = provider === "ollama";
|
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 (
|
return (
|
||||||
<div className="mt-2 rounded-lg border border-[color:var(--border)] bg-[color:var(--surface)] p-2.5">
|
<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" : ""}`} />
|
<RefreshCw className={`h-3 w-3 ${loading ? "animate-spin" : ""}`} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{(usage.sample_input_tokens != null || usage.sample_output_tokens != null) && (
|
{usageBars.length > 0 ? (
|
||||||
<div className="flex items-center justify-between text-[11px] text-muted">
|
<div className="space-y-2">
|
||||||
<span>Usage (last probe)</span>
|
{usageBars.map((bar) => (
|
||||||
<span className="tabular-nums text-strong">
|
<UsageWindowBar key={bar.label} label={bar.label} pct={bar.pct} resetInMs={bar.resetInMs} />
|
||||||
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>
|
|
||||||
</div>
|
</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">
|
<div className="flex items-center justify-between text-[11px] text-muted">
|
||||||
{lastFetched && <span>Updated {Math.round((Date.now() - lastFetched.getTime()) / 1000)}s ago</span>}
|
{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>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
{(usage.sample_input_tokens != null || usage.sample_output_tokens != null) && (
|
{usageBars.length > 0 ? (
|
||||||
<div className="flex items-center justify-between text-[11px] text-muted">
|
<div className="space-y-2">
|
||||||
<span>Usage (last probe)</span>
|
{usageBars.map((bar) => (
|
||||||
<span className="tabular-nums text-strong">
|
<UsageWindowBar key={bar.label} label={bar.label} pct={bar.pct} resetInMs={bar.resetInMs} />
|
||||||
in {fmtTokens(usage.sample_input_tokens)} · out {fmtTokens(usage.sample_output_tokens)}
|
))}
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
) : (
|
||||||
|
<>
|
||||||
{/* Tokens */}
|
{(usage.sample_input_tokens != null || usage.sample_output_tokens != null) && (
|
||||||
{tok.limit != null ? (
|
<div className="flex items-center justify-between text-[11px] text-muted">
|
||||||
<div>
|
<span>Usage (last probe)</span>
|
||||||
<div className="mb-1 flex items-center justify-between text-[11px]">
|
<span className="tabular-nums text-strong">
|
||||||
<span className="font-medium text-muted">Tokens</span>
|
in {fmtTokens(usage.sample_input_tokens)} · out {fmtTokens(usage.sample_output_tokens)}
|
||||||
<span className="tabular-nums text-strong">
|
</span>
|
||||||
{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)}%` }}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
{usage.sample_latency_ms != null && (
|
||||||
) : null}
|
<div className="flex items-center justify-between text-[11px] text-muted">
|
||||||
|
<span>Time (last probe)</span>
|
||||||
{/* Anthropic input tokens */}
|
<span className="tabular-nums text-strong">
|
||||||
{inputTok.limit != null ? (
|
{fmtLatencyMs(usage.sample_latency_ms)}
|
||||||
<div>
|
</span>
|
||||||
<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)}%` }}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
<p className="text-[11px] text-muted">
|
||||||
) : null}
|
Connected — provider did not return usage windows for percent + reset tracking.
|
||||||
|
</p>
|
||||||
{/* 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>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center justify-between text-[11px] text-muted">
|
<div className="flex items-center justify-between text-[11px] text-muted">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue