import { Link } from "../../lib/navigation"; import { CreditCardIcon } from "lucide-react"; import { Button } from "../../../ui/button"; import { Notice } from "../../../ui/notice"; import { analyticsAction } from "../../../ui/analytics-actions"; import { apiErrorFacts } from "../../../ui/api-error"; import { modelListSummary } from "../../lib/credit-coverage"; import type { FreeCreditGap } from "../../lib/free-credit-gap"; /** * The API refused a Send or a new chat because no credit pays for its model. * Stays above the composer until the next accepted message, so the refusal is * not only a short line under a failed bubble: it names the model, the * server's reason, the models the remaining credit covers, and the next step. */ export function CreditRefusalNotice({ gap, message, workspaceId, canBuyCredits, canConnectModel, onChooseModel, onDismiss, }: { gap: FreeCreditGap; /** The API's refusal text. */ message?: string | null | undefined; workspaceId: string; canBuyCredits: boolean; canConnectModel: boolean; /** Select a covered model in the composer. */ onChooseModel?: ((id: string) => void) | undefined; onDismiss?: (() => void) | undefined; }) { const switchTo = !gap.selectionUsable && onChooseModel ? gap.covered[0] : undefined; const coverage = gap.selectionUsable ? "Send again to use the selected model." : gap.covered.length > 0 ? `Your credits cover ${modelListSummary(gap.covered.map((model) => model.label))}.` : null; const connect = canConnectModel && gap.covered.length === 0 && !gap.selectionUsable; // The server's sentence, without the SDK's "API 402:" prefix or the reference. const reason = message ? apiErrorFacts(message).serverMessage : undefined; return ( {reason ? {reason} : null} {coverage ? {coverage} : null} {switchTo || canBuyCredits || connect ? (
{switchTo ? ( ) : null} {canBuyCredits ? ( ) : null} {connect ? ( ) : null}
) : null}
); }