136 lines
4.2 KiB
Svelte
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>
|