Files
beaver-gateway/ui/src/lib/shell/instruments.svelte
T

136 lines
4.2 KiB
Svelte

<script lang="ts">
import type { LimitWindow } from "$lib/api/types";
import { fmtCountdown, fmtMoney, fmtRelative, fmtTokens } from "$lib/format";
import { limitLabel, limitWord } from "$lib/limits";
import { cn } from "$lib/utils";
let {
contextTokens,
contextWindow,
windows,
spend = null,
now,
compact = false,
}: {
contextTokens: number;
contextWindow: number;
windows: LimitWindow[];
spend?: number | null;
now: number;
compact?: boolean;
} = $props();
const TONE: Record<string, string> = {
allowed: "bg-primary",
allowed_warning: "bg-primary",
rejected: "bg-destructive",
};
const contextPct = $derived(
Math.min(100, Math.round((contextTokens / contextWindow) * 100))
);
// The figure shown: the report's own, else the last one inside the window.
const figure = (w: LimitWindow) => w.utilization ?? w.last_known?.utilization;
const pct = (w: LimitWindow) =>
Math.min(100, Math.round((figure(w) ?? 0) * 100));
const bound = (w: LimitWindow) =>
w.utilization === null || w.utilization === undefined ? "≥ " : "";
</script>
<div
class={cn(
"grid gap-x-6 gap-y-4",
compact
? "@lg:grid-cols-5 grid-cols-2"
: "grid-cols-2 sm:grid-cols-3 lg:grid-cols-5"
)}
>
<div class="flex flex-col gap-1.5">
<span class="label-quiet">Context</span>
<span class="tabular font-semibold text-2xl leading-none tracking-tight">
{fmtTokens(contextTokens)}
<span class="font-normal text-muted-foreground text-sm">
/ {fmtTokens(contextWindow)}
</span>
</span>
<span
aria-label="context share"
aria-valuemax="100"
aria-valuemin="0"
aria-valuenow={contextPct}
class="h-0.5 w-full overflow-hidden rounded-full bg-muted"
role="progressbar"
>
<span
class="block h-full rounded-full bg-primary transition-[width] duration-500"
style="width: {contextPct}%"
></span>
</span>
</div>
{#each windows as w (w.window)}
{@const known = figure(w) !== null && figure(w) !== undefined}
<div class="flex flex-col gap-1.5">
<span class="label-quiet">{limitLabel(w.window)}</span>
{#if known}
<span
class={cn(
"tabular font-semibold text-2xl leading-none tracking-tight",
w.status === "rejected" && "text-destructive"
)}
>
{bound(w)}{pct(w)}
<span class="font-normal text-muted-foreground text-sm">%</span>
<span class="font-normal text-muted-foreground text-xs">
{fmtCountdown(w.resets_at, now).replace("resets ", "")}
</span>
</span>
<span
aria-label="{limitLabel(w.window)} utilization"
aria-valuemax="100"
aria-valuemin="0"
aria-valuenow={pct(w)}
class="h-0.5 w-full overflow-hidden rounded-full bg-muted"
role="progressbar"
>
<span
class={cn(
"block h-full rounded-full transition-[width] duration-500",
TONE[w.status]
)}
style="width: {pct(w)}%"
></span>
</span>
{:else}
<span
class={cn(
"font-semibold text-2xl leading-none tracking-tight",
w.status === "rejected" && "text-destructive"
)}
>
{limitWord(w.status)}
<span class="font-normal text-muted-foreground text-xs">
{fmtCountdown(w.resets_at, now).replace("resets ", "")}
</span>
</span>
<span
class="tabular text-muted-foreground text-xs"
title="through the gateway this window · last report {fmtRelative(w.ts, now)}"
>
{fmtTokens(
w.gateway.input + w.gateway.output + w.gateway.cache_creation
)}
· {fmtMoney(w.gateway.cost_usd)}
</span>
{/if}
</div>
{/each}
{#if spend !== null}
<div class="flex flex-col gap-1.5">
<span class="label-quiet">Spend · 24 h</span>
<span class="tabular font-semibold text-2xl leading-none tracking-tight">
{fmtMoney(spend)}
</span>
<span class="text-muted-foreground text-xs">in API price</span>
</div>
{/if}
</div>