Spaces:
Paused
Paused
File size: 4,852 Bytes
b152fd5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | import type { QuotaWindow } from "@paperclipai/shared";
import { cn, quotaSourceDisplayName } from "@/lib/utils";
interface ClaudeSubscriptionPanelProps {
windows: QuotaWindow[];
source?: string | null;
error?: string | null;
}
const WINDOW_ORDER = [
"currentsession",
"currentweekallmodels",
"currentweeksonnetonly",
"currentweeksonnet",
"currentweekopusonly",
"currentweekopus",
"extrausage",
] as const;
function normalizeLabel(text: string): string {
return text.toLowerCase().replace(/[^a-z0-9]+/g, "");
}
function detailText(window: QuotaWindow): string | null {
if (typeof window.detail === "string" && window.detail.trim().length > 0) return window.detail.trim();
if (window.resetsAt) {
const formatted = new Date(window.resetsAt).toLocaleString(undefined, {
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
timeZoneName: "short",
});
return `Resets ${formatted}`;
}
return null;
}
function orderedWindows(windows: QuotaWindow[]): QuotaWindow[] {
return [...windows].sort((a, b) => {
const aIndex = WINDOW_ORDER.indexOf(normalizeLabel(a.label) as (typeof WINDOW_ORDER)[number]);
const bIndex = WINDOW_ORDER.indexOf(normalizeLabel(b.label) as (typeof WINDOW_ORDER)[number]);
return (aIndex === -1 ? WINDOW_ORDER.length : aIndex) - (bIndex === -1 ? WINDOW_ORDER.length : bIndex);
});
}
function fillClass(usedPercent: number | null): string {
if (usedPercent == null) return "bg-zinc-700";
if (usedPercent >= 90) return "bg-red-400";
if (usedPercent >= 70) return "bg-amber-400";
return "bg-primary/70";
}
export function ClaudeSubscriptionPanel({
windows,
source = null,
error = null,
}: ClaudeSubscriptionPanelProps) {
const ordered = orderedWindows(windows);
return (
<div className="border border-border px-4 py-4">
<div className="flex items-start justify-between gap-3 border-b border-border pb-3">
<div className="min-w-0">
<div className="text-[11px] font-semibold uppercase tracking-[0.2em] text-muted-foreground">
Anthropic subscription
</div>
<div className="mt-1 text-sm text-muted-foreground">
Live Claude quota windows.
</div>
</div>
{source ? (
<span className="shrink-0 border border-border px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.16em] text-muted-foreground">
{quotaSourceDisplayName(source)}
</span>
) : null}
</div>
{error ? (
<div className="mt-4 border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive">
{error}
</div>
) : null}
<div className="mt-4 space-y-4">
{ordered.map((window) => {
const normalized = normalizeLabel(window.label);
const detail = detailText(window);
if (normalized === "extrausage") {
return (
<div
key={window.label}
className="border border-border px-3.5 py-3"
>
<div className="flex items-center justify-between gap-3">
<div className="text-sm font-medium text-foreground">{window.label}</div>
{window.valueLabel ? (
<div className="text-sm font-medium text-foreground">{window.valueLabel}</div>
) : null}
</div>
{detail ? (
<div className="mt-2 text-sm text-muted-foreground">{detail}</div>
) : null}
</div>
);
}
const width = Math.min(100, Math.max(0, window.usedPercent ?? 0));
return (
<div
key={window.label}
className="border border-border px-3.5 py-3"
>
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<div className="text-sm font-medium text-foreground">{window.label}</div>
{detail ? (
<div className="mt-1 text-xs text-muted-foreground">{detail}</div>
) : null}
</div>
{window.usedPercent != null ? (
<div className="shrink-0 text-sm font-semibold tabular-nums text-foreground">
{window.usedPercent}% used
</div>
) : null}
</div>
<div className="mt-3 h-2 overflow-hidden bg-muted">
<div
className={cn("h-full transition-[width] duration-200", fillClass(window.usedPercent))}
style={{ width: `${width}%` }}
/>
</div>
</div>
);
})}
</div>
</div>
);
}
|