import { type ProviderInstanceId, type ServerProvider } from "@t3tools/contracts";import { memo } from "react";import { InfoIcon, XIcon } from "lucide-react";import { cn } from "~/lib/utils";import { Button } from "../ui/button";import { formatProviderDriverKindLabel } from "../../providerModels";import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; export function getProviderStatusBannerKey(status: ServerProvider | null): string | null { if (!status || status.status === "ready" || status.status === "disabled") return null; // Antigravity checks saved credentials when a session starts. Its local // health check leaves auth unknown after a restart, which is not a failure. if ( status.driver === "antigravity" && status.installed && status.status === "warning" && status.auth.status === "unknown" ) { return null; } return [status.instanceId, status.status, status.auth.status, status.message ?? ""].join( "\u0000", );} export function shouldShowProviderStatusBanner( status: ServerProvider | null, dismissedBannerKey: string | null,): boolean { const bannerKey = getProviderStatusBannerKey(status); return bannerKey !== null && bannerKey !== dismissedBannerKey;} export function hasProviderSetup(status: ServerProvider): boolean { return ( status.driver === "antigravity" || status.setup?.canAuthenticate === true || status.setup?.canInstall === true );} /** Keep the environment's error intact in both the banner and model picker. */export function getProviderStatusMessage(status: ServerProvider): string { if (status.message) return status.message; const providerName = status.displayName?.trim() || formatProviderDriverKindLabel(status.driver); if (!status.installed && hasProviderSetup(status)) { return `Open provider setup to install ${formatProviderDriverKindLabel(status.driver)} on this environment.`; } if (status.auth.status === "unauthenticated") { if (hasProviderSetup(status)) { return status.driver === "antigravity" ? "Open provider setup to sign in with Google." : "Open provider setup to sign in."; } return "Sign in via the CLI to authenticate again."; } return status.status === "ready" ? "No models are available for this provider." : status.status === "error" ? `${providerName} provider is unavailable.` : `${providerName} provider has limited availability.`;} export const ProviderStatusBanner = memo(function ProviderStatusBanner({ onDismiss, onOpenProviderSetup, status,}: { onDismiss: () => void; onOpenProviderSetup?: (instanceId: ProviderInstanceId) => void; status: ServerProvider | null;}) { if (!status || getProviderStatusBannerKey(status) === null) { return null; } const providerName = status.displayName?.trim() || formatProviderDriverKindLabel(status.driver); const isUnauthenticated = status.status === "error" && status.auth.status === "unauthenticated"; const title = isUnauthenticated ? `${providerName} is unauthenticated` : `${providerName} provider status`; const message = getProviderStatusMessage(status); return ( <div className="pointer-events-auto mx-auto w-fit max-w-[calc(100%-2rem)] pt-3"> <div className={cn( "alert-glass relative inline-flex items-center gap-3 rounded-xl border py-3 ps-3.5 pe-10 text-card-foreground text-sm", status.status === "warning" ? "border-warning/32 [&_svg]:text-warning" : "border-destructive/32 text-destructive-foreground [&_svg]:text-destructive", )} data-variant={status.status === "warning" ? "warning" : "error"} role="alert" > <InfoIcon className="size-4 shrink-0" aria-hidden /> <div className="flex min-w-0 flex-col gap-1"> <div className="font-medium">{title}</div> <Tooltip> <TooltipTrigger render={<div className="line-clamp-3 text-muted-foreground">{message}</div>} /> <TooltipPopup side="top" className="max-w-96 whitespace-pre-wrap"> {message} </TooltipPopup> </Tooltip> {onOpenProviderSetup && hasProviderSetup(status) ? ( <Button className="self-start px-0 text-foreground" onClick={() => onOpenProviderSetup(status.instanceId)} size="xs" variant="link" > Open provider setup </Button> ) : null} </div> <Button aria-label={`Dismiss ${providerName} provider ${status.status}`} className="absolute top-2 right-2 size-6 text-muted-foreground hover:text-foreground" onClick={onDismiss} size="icon-xs" variant="ghost" > <XIcon aria-hidden className="size-3.5" /> </Button> </div> </div> );});
import { type ProviderInstanceId, type ServerProvider } from "@t3tools/contracts";
import { memo } from "react";
import { InfoIcon, XIcon } from "lucide-react";
import { cn } from "~/lib/utils";
import { Button } from "../ui/button";
import { formatProviderDriverKindLabel } from "../../providerModels";
import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
export function getProviderStatusBannerKey(status: ServerProvider | null): string | null {
if (!status || status.status === "ready" || status.status === "disabled") return null;
// Antigravity checks saved credentials when a session starts. Its local
// health check leaves auth unknown after a restart, which is not a failure.
if (
status.driver === "antigravity" &&
status.installed &&
status.status === "warning" &&
status.auth.status === "unknown"
) {
return null;
}
return [status.instanceId, status.status, status.auth.status, status.message ?? ""].join(
"\u0000",
);
}
export function shouldShowProviderStatusBanner(
status: ServerProvider | null,
dismissedBannerKey: string | null,
): boolean {
const bannerKey = getProviderStatusBannerKey(status);
return bannerKey !== null && bannerKey !== dismissedBannerKey;
}
export function hasProviderSetup(status: ServerProvider): boolean {
return (
status.driver === "antigravity" ||
status.setup?.canAuthenticate === true ||
status.setup?.canInstall === true
);
}
/** Keep the environment's error intact in both the banner and model picker. */
export function getProviderStatusMessage(status: ServerProvider): string {
if (status.message) return status.message;
const providerName = status.displayName?.trim() || formatProviderDriverKindLabel(status.driver);
if (!status.installed && hasProviderSetup(status)) {
return `Open provider setup to install ${formatProviderDriverKindLabel(status.driver)} on this environment.`;
}
if (status.auth.status === "unauthenticated") {
if (hasProviderSetup(status)) {
return status.driver === "antigravity"
? "Open provider setup to sign in with Google."
: "Open provider setup to sign in.";
}
return "Sign in via the CLI to authenticate again.";
}
return status.status === "ready"
? "No models are available for this provider."
: status.status === "error"
? `${providerName} provider is unavailable.`
: `${providerName} provider has limited availability.`;
}
export const ProviderStatusBanner = memo(function ProviderStatusBanner({
onDismiss,
onOpenProviderSetup,
status,
}: {
onDismiss: () => void;
onOpenProviderSetup?: (instanceId: ProviderInstanceId) => void;
status: ServerProvider | null;
}) {
if (!status || getProviderStatusBannerKey(status) === null) {
return null;
}
const providerName = status.displayName?.trim() || formatProviderDriverKindLabel(status.driver);
const isUnauthenticated = status.status === "error" && status.auth.status === "unauthenticated";
const title = isUnauthenticated
? `${providerName} is unauthenticated`
: `${providerName} provider status`;
const message = getProviderStatusMessage(status);
return (
<div className="pointer-events-auto mx-auto w-fit max-w-[calc(100%-2rem)] pt-3">
<div
className={cn(
"alert-glass relative inline-flex items-center gap-3 rounded-xl border py-3 ps-3.5 pe-10 text-card-foreground text-sm",
status.status === "warning"
? "border-warning/32 [&_svg]:text-warning"
: "border-destructive/32 text-destructive-foreground [&_svg]:text-destructive",
)}
data-variant={status.status === "warning" ? "warning" : "error"}
role="alert"
>
<InfoIcon className="size-4 shrink-0" aria-hidden />
<div className="flex min-w-0 flex-col gap-1">
<div className="font-medium">{title}</div>
<Tooltip>
<TooltipTrigger
render={<div className="line-clamp-3 text-muted-foreground">{message}</div>}
/>
<TooltipPopup side="top" className="max-w-96 whitespace-pre-wrap">
{message}
</TooltipPopup>
</Tooltip>
{onOpenProviderSetup && hasProviderSetup(status) ? (
<Button
className="self-start px-0 text-foreground"
onClick={() => onOpenProviderSetup(status.instanceId)}
size="xs"
variant="link"
>
Open provider setup
</Button>
) : null}
</div>
<Button
aria-label={`Dismiss ${providerName} provider ${status.status}`}
className="absolute top-2 right-2 size-6 text-muted-foreground hover:text-foreground"
onClick={onDismiss}
size="icon-xs"
variant="ghost"
>
<XIcon aria-hidden className="size-3.5" />
</Button>
</div>
</div>
);
});