diff --git a/frontend/src/app/settings/ai-providers/page.tsx b/frontend/src/app/settings/ai-providers/page.tsx index e8874c2..2b50337 100644 --- a/frontend/src/app/settings/ai-providers/page.tsx +++ b/frontend/src/app/settings/ai-providers/page.tsx @@ -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 ( +
+
+ {label} + {fmtPct(clamped)} used +
+
+
+
+

Resets in {fmtResetMs(resetInMs)}

+
+ ); +} + function UsageStrip({ credentialId, provider }: { credentialId: string; provider: string }) { const [usage, setUsage] = useState(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 (
@@ -350,29 +407,34 @@ function UsageStrip({ credentialId, provider }: { credentialId: string; provider
- {(usage.sample_input_tokens != null || usage.sample_output_tokens != null) && ( -
- Usage (last probe) - - in {fmtTokens(usage.sample_input_tokens)} · out {fmtTokens(usage.sample_output_tokens)} - -
- )} - {usage.sample_latency_ms != null && ( -
- Time (last probe) - - {fmtLatencyMs(usage.sample_latency_ms)} - -
- )} - {usage.sample_latency_ms != null && ( -
- Time (last probe) - - {fmtLatencyMs(usage.sample_latency_ms)} - + {usageBars.length > 0 ? ( +
+ {usageBars.map((bar) => ( + + ))}
+ ) : ( + <> + {(usage.sample_input_tokens != null || usage.sample_output_tokens != null) && ( +
+ Usage (last probe) + + in {fmtTokens(usage.sample_input_tokens)} · out {fmtTokens(usage.sample_output_tokens)} + +
+ )} + {usage.sample_latency_ms != null && ( +
+ Time (last probe) + + {fmtLatencyMs(usage.sample_latency_ms)} + +
+ )} +

+ This provider did not return active usage windows for percent + reset tracking. +

+ )}
{lastFetched && Updated {Math.round((Date.now() - lastFetched.getTime()) / 1000)}s ago} @@ -380,86 +442,34 @@ function UsageStrip({ credentialId, provider }: { credentialId: string; provider
) : (
- {(usage.sample_input_tokens != null || usage.sample_output_tokens != null) && ( -
- Usage (last probe) - - in {fmtTokens(usage.sample_input_tokens)} · out {fmtTokens(usage.sample_output_tokens)} - + {usageBars.length > 0 ? ( +
+ {usageBars.map((bar) => ( + + ))}
- )} - - {/* Tokens */} - {tok.limit != null ? ( -
-
- Tokens - - {fmtTokens(tok.remaining)} / {fmtTokens(tok.limit)} remaining - {tok.reset_in_ms != null && ( - · resets in {fmtResetMs(tok.reset_in_ms)} - )} - -
- {tok.pct_used != null && ( -
-
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) && ( +
+ Usage (last probe) + + in {fmtTokens(usage.sample_input_tokens)} · out {fmtTokens(usage.sample_output_tokens)} +
)} -
- ) : null} - - {/* Anthropic input tokens */} - {inputTok.limit != null ? ( -
-
- Input tokens - - {fmtTokens(inputTok.remaining)} / {fmtTokens(inputTok.limit)} remaining - {inputTok.reset_in_ms != null && ( - · resets in {fmtResetMs(inputTok.reset_in_ms)} - )} - -
- {inputTok.pct_used != null && ( -
-
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 && ( +
+ Time (last probe) + + {fmtLatencyMs(usage.sample_latency_ms)} +
)} -
- ) : null} - - {/* Requests */} - {req.limit != null ? ( -
- Requests - - {req.remaining ?? "—"} / {req.limit} remaining - {req.reset_in_ms != null && ( - · resets in {fmtResetMs(req.reset_in_ms)} - )} - -
- ) : null} - - {tok.limit == null && inputTok.limit == null && req.limit == null && ( -

- Connected — no token/request limit windows were returned for this key right now. -

+

+ Connected — provider did not return usage windows for percent + reset tracking. +

+ )}