import {
collectLimitAccounts,
collectLimitNotices,
collectLimitPools,
formatDuration,
formatResetsIn,
type LimitAccount,
type LimitPool,
type LimitPoolMember,
type LimitPoolWindow,
remainingPercent,
} from "@t3tools/shared/usageLimits";
import { TicketIcon } from "lucide-react";
import { type ReactNode, useState } from "react";
import { usePrimarySettings } from "../../hooks/useSettings";
import { cn } from "../../lib/utils";
import { formatUpcomingTimestamp } from "../../timestampFormat";
import { ProviderInstanceIcon } from "../chat/ProviderInstanceIcon";
import { getDriverOption } from "../settings/providerDriverMeta";
import { RedactedSensitiveText } from "../settings/RedactedSensitiveText";
import { Button } from "../ui/button";
import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover";
import {
PaceIcon,
ResetCreditDialog,
barColor,
resetCreditsSummary,
useResetCredit,
} from "./UsageLimits";
/** `someone@example.com` → `SE`: enough to tell accounts apart, too little to identify one. */
function accountInitials(email: string): string {
const [local = "", domain = ""] = email.split("@");
return `${local[0] ?? ""}${domain[0] ?? ""}`.toUpperCase() || "?";
}
/** A stable hue per email, so the same account gets the same chip on every visit. */
function accountHue(email: string): number {
let hash = 0;
for (let index = 0; index < email.length; index += 1) {
hash = (hash * 31 + email.charCodeAt(index)) | 0;
}
return Math.abs(hash) % 360;
}
/** The two-letter chip for an email, coloured by a stable hue per address. */
function AccountChip({ email }: { readonly email: string }) {
const hue = accountHue(email);
return (
{accountInitials(email)}
);
}
/**
* The same mark the model picker uses for a native instance (provider glyph,
* initials badge, accent); hub accounts have no instance, so they get the chip.
*/
function AccountAvatar({
account,
className,
}: {
readonly account: LimitAccount;
readonly className?: string;
}) {
if (account.redeem) {
return (
);
}
return account.email ? : null;
}
/**
* Who an account is, without printing the email: the instance name when there
* is one, else a two-letter chip. The address itself is revealed on demand in
* the segment's popover.
*/
function AccountName({
account,
className,
}: {
readonly account: LimitAccount;
readonly className?: string;
}) {
if (account.displayName) return {account.displayName};
if (account.email) {
return (
);
}
return (
{getDriverOption(account.driver)?.label ?? String(account.driver)}
);
}
function Row({ label, children }: { readonly label: string; readonly children: ReactNode }) {
return (
{label}{children}
);
}
/**
* Everything about one account in one window: plan, where it is signed in,
* the email on request, reset time and share of the pool it restores, and the
* reset-credit action. Opens on hover for a glance, on click to act.
*/
function SegmentPopover({
account,
window,
reset,
now,
redeem,
onRedeem,
}: {
readonly account: LimitAccount;
readonly window: LimitPoolMember["window"];
readonly reset: LimitPoolWindow["resets"][number] | undefined;
readonly now: number;
/** Redeem state owned by the segment, since the confirm lives outside this popover. */
readonly redeem: ReturnType | null;
readonly onRedeem: () => void;
}) {
const timestampFormat = usePrimarySettings((settings) => settings.timestampFormat);
const remaining = remainingPercent(window);
const resetsIn = formatResetsIn(window, now);
const where =
account.environments.length > 0
? account.environments.map((environment) => environment.label).join(", ")
: account.sourceLabel;
const credits =
redeem && account.limits.resetCredits?.availableCount ? account.limits.resetCredits : null;
return (
{remaining}%
{window.resetsAt ? (
{formatUpcomingTimestamp(window.resetsAt, timestampFormat, now)}
{resetsIn ? ` · ${resetsIn.replace("resets in ", "in ")}` : ""}
) : null}
{reset && reset.restoresPercent > 0 ? (
+{reset.restoresPercent}% of pool
) : null}
{credits && redeem ? (
{resetCreditsSummary(credits, now, true)}
) : null}
);
}
/**
* One account's share of one pooled window: the segment, its popover, and the
* reset confirm. The confirm is a sibling of the popover, not a child: dialogs
* stack under popovers, and the popover closes as the confirm opens.
*/
function PoolSegment({
account,
window,
reset,
color,
now,
index,
}: {
readonly account: LimitAccount;
readonly window: LimitPoolMember["window"];
readonly reset: LimitPoolWindow["resets"][number] | undefined;
readonly color: string;
readonly now: number;
/** 1-based position in the bar, shown on the strip and its legend row to tie them together. */
readonly index: number;
}) {
const [open, setOpen] = useState(false);
const remaining = remainingPercent(window);
const resetsIn = formatResetsIn(window, now);
const credits = account.limits.resetCredits?.availableCount ?? 0;
return (
}
>
{/* Translucent so the label reads over the fill for any provider colour and theme. */}
{/* The spent share is hatched, not blank: it is what the countdown restores. */}
{remaining < 100 && reset ? (
) : null}
{index}
{remaining}%
{/* Countdown and badge get their own plate: fill and hatching run under them otherwise. */}
{resetsIn?.replace("resets in ", "↻ ") ?? ""}
{credits ? (
<>
{resetsIn ? (
·
) : null}
{credits}
>
) : null}
{account.redeem ? (
setOpen(false)}
/>
) : (
{}}
/>
)}
);
}
/**
* Below the strip at narrow widths: one row per account in bar order, carrying
* the text the segment has no room for. Tapping a row opens the same popover
* as its segment, so the two are one control with two handles.
*/
function LegendRow({
account,
window,
color,
now,
index,
}: {
readonly account: LimitAccount;
readonly window: LimitPoolMember["window"];
readonly color: string;
readonly now: number;
readonly index: number;
}) {
const remaining = remainingPercent(window);
const resetsIn = formatResetsIn(window, now);
const credits = account.limits.resetCredits?.availableCount ?? 0;
return (
Segment {index}{remaining}%
{resetsIn?.replace("resets in ", "↻ ") ?? ""}
{credits ? (
<>
{resetsIn ? · : null}
{credits}
{credits} reset {credits === 1 ? "credit" : "credits"} banked
>
) : null}
);
}
/** Split out so the redeem hook only runs for accounts that can redeem. */
function RedeemableSegmentPopup({
account,
window,
reset,
now,
redeemAt,
closePopover,
}: {
readonly account: LimitAccount;
readonly window: LimitPoolMember["window"];
readonly reset: LimitPoolWindow["resets"][number] | undefined;
readonly now: number;
readonly redeemAt: NonNullable;
readonly closePopover: () => void;
}) {
const redeem = useResetCredit(redeemAt.environmentId, redeemAt.input);
return (
<>
{
closePopover();
redeem.setConfirming(true);
}}
/>
void redeem.redeem()}
/>
{/* The popover closed before the confirm, so the outcome needs a home outside it. */}
{redeem.status ? (
{redeem.status}
) : null}
>
);
}
/**
* One pooled window as equal-width segments, one per account, each filled by
* the share of that account's quota still open. Equal widths are honest: every
* account contributes the same share of the pool, whatever its plan.
*
* Wide, each segment carries its own label. Narrow, the bar is a bare strip
* and a legend below lists the accounts in the same order; both open the
* same popover.
*/
function PoolBar({
pool,
color,
now,
}: {
readonly pool: LimitPoolWindow;
readonly color: string;
readonly now: number;
}) {
const restores = new Map(pool.resets.map((reset) => [reset.member.account.key, reset]));
return (
);
}
/**
* Big pooled number and the segment bar. Accounts keep the same column across
* windows; each segment's popover shows its own reset time and share restored.
*/
function PoolWindowCard({
pool,
color,
now,
}: {
readonly pool: LimitPoolWindow;
readonly color: string;
readonly now: number;
}) {
// The soonest reset that hands anything back; an untouched account resets to no effect.
const nextRefill = pool.resets.find((reset) => reset.restoresPercent > 0);
return (
);
}
function PoolSection({ pool, now }: { readonly pool: LimitPool; readonly now: number }) {
const color = barColor(pool.driver);
const label = getDriverOption(pool.driver)?.label ?? String(pool.driver);
return (
{label}
{pool.windows.map((window) => (
))}
);
}
/**
* Accounts pooled per provider: what is open across all of them, who resets
* next, and how much of the pool that hands back. Answers "can I keep going"
* before "on which account".
*/
export function UsageLimitsPooled({
presentations,
now,
}: {
readonly presentations: Parameters[0];
readonly now: number;
}) {
const pools = collectLimitPools(collectLimitAccounts(presentations), now);
const notices = collectLimitNotices(presentations);
return (
{pools.length === 0 ? (
No provider on the selected environments reports subscription limits.
) : null}
{pools.map((pool) => (
))}
);
}
/** Sources and providers that could not be read, so a missing bar is not mistaken for a full one. */
function LimitNotices({ notices }: { readonly notices: readonly string[] }) {
if (notices.length === 0) return null;
return (