AI Error Handler

PreviousNext

Display and handle AI errors with retry functionality.

Docs
hextauiui

Preview

Loading preview…
registry/new-york/blocks/ai/ai-error-handler.tsx
"use client";

import { AlertTriangle, Loader2, RefreshCw, X } from "lucide-react";
import { useCallback, useState } from "react";
import { cn } from "@/lib/utils";
import {
  Alert,
  AlertDescription,
  AlertTitle,
} from "@/registry/new-york/ui/alert";
import { Button } from "@/registry/new-york/ui/button";

export interface AIErrorHandlerProps {
  error?: string | Error | null;
  title?: string;
  onRetry?: () => Promise<void> | void;
  onDismiss?: () => void;
  className?: string;
}

function getErrorMessage(error: string | Error | null | undefined): string {
  if (!error) return "";
  if (typeof error === "string") return error;
  return error.message || "An error occurred";
}

export default function AIErrorHandler({
  error,
  title = "An Error Occurred",
  onRetry,
  onDismiss,
  className,
}: AIErrorHandlerProps) {
  const [isRetrying, setIsRetrying] = useState(false);

  const message = getErrorMessage(error);

  const handleRetry = useCallback(async () => {
    if (!onRetry) return;

    setIsRetrying(true);
    try {
      await onRetry();
    } finally {
      setIsRetrying(false);
    }
  }, [onRetry]);

  if (!(error && message)) return null;

  return (
    <Alert
      className={cn("w-full", className)}
      live="assertive"
      variant="destructive"
    >
      <AlertTriangle aria-hidden="true" className="size-4" />
      <div className="flex min-w-0 flex-1 flex-col gap-3">
        <div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
          <div className="flex min-w-0 flex-1 flex-col gap-2">
            <AlertTitle className="wrap-break-word font-medium text-base">
              {title}
            </AlertTitle>
            <AlertDescription className="wrap-break-word">
              {message}
            </AlertDescription>
          </div>
          {onDismiss && (
            <Button
              aria-label="Dismiss error"
              className="min-h-[32px] min-w-[32px] shrink-0 touch-manipulation"
              onClick={onDismiss}
              size="icon-sm"
              type="button"
              variant="ghost"
            >
              <X aria-hidden="true" className="size-4" />
            </Button>
          )}
        </div>

        {onRetry && (
          <Button
            aria-busy={isRetrying}
            className="ml-auto min-h-[32px] w-fit touch-manipulation sm:ml-0"
            data-loading={isRetrying}
            disabled={isRetrying}
            onClick={handleRetry}
            type="button"
            variant={"secondary"}
          >
            {isRetrying ? (
              <>
                <Loader2 aria-hidden="true" className="size-4 animate-spin" />
                <span>Retrying…</span>
              </>
            ) : (
              <>
                <RefreshCw aria-hidden="true" className="size-4" />
                <span>Retry</span>
              </>
            )}
          </Button>
        )}
      </div>
    </Alert>
  );
}

Installation

npx shadcn@latest add @hextaui/ai-error-handler

Usage

import { AiErrorHandler } from "@/components/ui/ai-error-handler"
<AiErrorHandler />