import type { ParsedSessionUsageLimitReachedRefusal, SessionUsageLimit, } from "@opengeni/sdk/session-usage-limits"; import { GaugeIcon, XIcon } from "lucide-react"; import { useEffect, useState, type ReactNode } from "react"; import { cn } from "../lib/cn"; import { sessionUsageLimitLabels, sessionUsageLimitPercent, summarizeSessionUsageLimit, summarizeSessionUsageLimitRefusal, type SessionUsageLimitLabels, type SessionUsageLimitSummary, } from "../usage/session-usage-limit"; import { useOpenGeniText } from "../messages/runtime"; export type SessionUsageLimitNoticeProps = { /** The task's usage-limit projection (`GET .../usage-limit` or `session.usageLimit`). */ usage: SessionUsageLimit | null | undefined; /** * A refused Send's typed refusal (`OpenGeniSessionUsageLimitReachedError`), * shown as reached until a re-read projection replaces it. Clear it when a * newer `usage` arrives: a projection that already says reached wins. */ refusal?: Pick< ParsedSessionUsageLimitReachedRefusal, "limitMicros" | "usedMicros" | "rootSessionId" > | null; /** Opens the limit editor. Absent: the notice says who can change it instead. */ onChangeLimit?: (() => void) | undefined; /** A child chat: link to the main task that owns the limit, instead of an editor. */ mainTaskHref?: string | undefined; /** The goal paused at the limit: changing it never resumes, so say Resume is next. */ goalPaused?: boolean; /** * The last refresh of `usage` failed. The last known amount and any reached * state stay, with a note that they may be out of date and that the limit * still applies. Pass the read hook's error state; never zero the amount. */ stale?: boolean; /** * Remember a dismissed near-limit notice for this tab. The key should name * the workspace; the task and limit version are added, so a changed limit * shows it again. Reaching the limit is never dismissible. */ dismissStorageKey?: string | undefined; labels?: Partial | undefined; className?: string | undefined; }; function readDismissed(key: string | null): boolean { if (!key || typeof sessionStorage === "undefined") return false; try { return sessionStorage.getItem(key) === "1"; } catch { return false; } } /** * The calm composer line for a task's usage limit: a dismissible note near * the limit and a status once it is reached, with one action (Change limit). * Renders nothing while usage is comfortable, unlimited, unknown or loading. * Never a purchase or credits remedy: the limit is a person's own setting. */ export function SessionUsageLimitNotice({ usage, refusal, onChangeLimit, mainTaskHref, goalPaused = false, stale = false, dismissStorageKey, labels: overrides, className, }: SessionUsageLimitNoticeProps) { const t = useOpenGeniText(); const labels = sessionUsageLimitLabels(t, overrides); const fromUsage = summarizeSessionUsageLimit(t, usage, overrides, { stale }); const summary: SessionUsageLimitSummary = refusal && fromUsage.state !== "reached" ? summarizeSessionUsageLimitRefusal(t, refusal, overrides) : fromUsage; const near = summary.state === "near"; const key = dismissStorageKey && near && summary.rootSessionId ? `${dismissStorageKey}:session-usage-limit:${summary.rootSessionId}:${summary.version ?? 0}` : null; const [dismissed, setDismissed] = useState(() => readDismissed(key)); useEffect(() => setDismissed(readDismissed(key)), [key]); if (summary.state !== "reached" && !near) return null; if (near && dismissed) return null; const reached = summary.state === "reached"; const amount = summary.amountText ?? ""; const dismiss = () => { setDismissed(true); if (!key) return; try { sessionStorage.setItem(key, "1"); } catch { // Storage can be unavailable (privacy mode); the in-memory dismissal holds. } }; let action: ReactNode = null; if (onChangeLimit) { action = ( ); } else if (mainTaskHref) { action = ( {labels.openMainTask} ); } const remedy = !onChangeLimit && reached ? labels.reachedReadOnlyRemedy : goalPaused ? labels.reachedGoalRemedy : labels.reachedRemedy; return (
{reached ? ( <>

{labels.reachedTitle}

{amount ? `${labels.reachedAmount(amount)} ` : ""} {remedy} {summary.stale ? ` ${labels.staleNote}` : ""}

) : (

{labels.near(sessionUsageLimitPercent(summary.fraction ?? 0), amount)} {summary.stale ? ` ${labels.staleNote}` : ""}

)}
{action} {!reached ? ( ) : null}
); }