Available
Conditionally render components based on AI API availability (local LLM only).
The Available components check if specific AI APIs are available locally and conditionally render children or fallback based on availability. This is useful for gracefully handling cases where AI features may not be available on all devices or browsers.
Each API has its own component:
GencnUIRewriterAvailable- Checks Rewriter API availabilityGencnUIWriterAvailable- Checks Writer API availabilityGencnUIProofreaderAvailable- Checks Proofreader API availabilityGencnUILanguageModelAvailable- Checks LanguageModel (Prompt API) availabilityGencnUITranslatorAvailable- Checks Translator API availability (requires language pair)GencnUISummarizerAvailable- Checks Summarizer API availabilityGencnUILanguageDetectorAvailable- Checks Language Detector API availabilityGencnUIDetectLanguageAvailable- Convenience wrapper for GencnUIDetectLanguage component
Try out the component below to see how it works in practice.
Loading preview...
"use client"; import { useState, useMemo } from "react"; import { GencnUIRewriterAvailable, GencnUIWriterAvailable, GencnUIProofreaderAvailable, GencnUILanguageModelAvailable, GencnUITranslatorAvailable, GencnUISummarizerAvailable, GencnUILanguageDetectorAvailable, type MostSignificantStatus, } from "@/registry/new-york/gencn-ui/items/available/gencn-ui-available"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; type APIType = | "rewriter" | "writer" | "proofreader" | "languageModel" | "translator" | "summarizer" | "languageDetector"; const apiTypes: APIType[] = [ "rewriter", "writer", "proofreader", "languageModel", "translator", "summarizer", "languageDetector", ]; // Map API types to their corresponding components const apiComponents = { rewriter: GencnUIRewriterAvailable, writer: GencnUIWriterAvailable, proofreader: GencnUIProofreaderAvailable, languageModel: GencnUILanguageModelAvailable, translator: GencnUITranslatorAvailable, summarizer: GencnUISummarizerAvailable, languageDetector: GencnUILanguageDetectorAvailable, } as const; type NonTranslatorAPIType = Exclude<APIType, "translator">; type NonTranslatorComponent = | typeof GencnUIRewriterAvailable | typeof GencnUIWriterAvailable | typeof GencnUIProofreaderAvailable | typeof GencnUILanguageModelAvailable | typeof GencnUISummarizerAvailable | typeof GencnUILanguageDetectorAvailable; export function GencnUIAvailableExample() { const [statuses, setStatuses] = useState<Record<APIType, MostSignificantStatus | null>>({ rewriter: null, writer: null, proofreader: null, languageModel: null, translator: null, summarizer: null, languageDetector: null, }); // Memoize status change handlers for each API type to prevent infinite loops const statusHandlers = useMemo(() => { const handlers: Record<APIType, (status: MostSignificantStatus) => void> = {} as Record< APIType, (status: MostSignificantStatus) => void >; apiTypes.forEach((apiType) => { handlers[apiType] = (status: MostSignificantStatus) => { setStatuses((prev) => { // Only update if status actually changed to prevent unnecessary re-renders if (prev[apiType] === status) return prev; return { ...prev, [apiType]: status }; }); }; }); return handlers; }, []); // Empty deps - handlers are stable const getStatusBadgeColor = (status: MostSignificantStatus | null) => { if (!status) return "bg-muted text-muted-foreground"; switch (status) { case "unsupported": return "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200"; case "checking": return "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200"; case "available": return "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200"; case "downloadable": return "bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200"; case "unavailable": return "bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-200"; default: return "bg-muted text-muted-foreground"; } }; return ( <div className="space-y-6"> {apiTypes.map((apiType) => { const status = statuses[apiType]; return ( <Card key={apiType} className="shadow-none"> <CardHeader> <div className="flex items-center gap-2"> <CardTitle className="capitalize">{apiType}</CardTitle> {status && ( <span className={`rounded-full px-2 py-0.5 text-xs font-medium ${getStatusBadgeColor(status)}`} > {status} </span> )} </div> <CardDescription> Example using <code className="text-xs">{apiType}</code> API type </CardDescription> </CardHeader> <CardContent> {apiType === "translator" ? ( <GencnUITranslatorAvailable targetLanguage="es" sourceLanguage="en" fallback={ <p className="text-muted-foreground text-sm"> LLM Not available showing <b>fallback component</b> </p> } loading={ <p className="text-muted-foreground text-sm"> Checking {apiType} API support... </p> } onStatusChange={statusHandlers[apiType]} > <div className="space-y-2"> <p className="text-sm"> Wrapped in{" "} <code className="bg-muted rounded px-1 py-0.5 text-xs"> {apiType} </code>{" "} and is available </p> {status && ( <p className="text-xs text-muted-foreground"> Current status: <span className="font-medium">{status}</span> </p> )} </div> </GencnUITranslatorAvailable> ) : ( (() => { const Component = apiComponents[apiType] as NonTranslatorComponent; return ( <Component fallback={ <p className="text-muted-foreground text-sm"> LLM Not available showing <b>fallback component</b> </p> } loading={ <p className="text-muted-foreground text-sm"> Checking {apiType} API support... </p> } onStatusChange={statusHandlers[apiType]} > <div className="space-y-2"> <p className="text-sm"> Wrapped in{" "} <code className="bg-muted rounded px-1 py-0.5 text-xs"> {apiType} </code>{" "} and is available </p> {status && ( <p className="text-xs text-muted-foreground"> Current status: <span className="font-medium">{status}</span> </p> )} </div> </Component> ); })() )} </CardContent> </Card> ); })} </div> ); }
Server API
No server API required - this component uses client-side APIs only.
Installation
Setup Required: Make sure you have configured components.json first. See
the installation guide for setup instructions.
npx shadcn@latest add @gencn-ui/gencn-ui-available"use client"; import { useState, useRef, useEffect, type ReactNode, } from "react"; import { useRewriter } from "@/registry/new-york/gencn-ui/items/shared/hooks/internal/use-gencn-ui-rewriter"; import { useWriter } from "@/registry/new-york/gencn-ui/items/shared/hooks/internal/use-gencn-ui-writer"; import { useProofreader } from "@/registry/new-york/gencn-ui/items/shared/hooks/internal/use-gencn-ui-proofreader"; import { useLanguageModel } from "@/registry/new-york/gencn-ui/items/shared/hooks/internal/use-gencn-ui-language-model"; import { useTranslator } from "@/registry/new-york/gencn-ui/items/shared/hooks/internal/use-gencn-ui-translator"; import { isLanguageDetectorSupported as libIsLanguageDetectorSupported, checkLanguageDetectorAvailability as libCheckLanguageDetectorAvailability, } from "@/registry/new-york/gencn-ui/items/shared/lib/gencn-ui-language-detector"; import { useSummarizer } from "@/registry/new-york/gencn-ui/items/shared/hooks/use-gencn-ui-summarizer"; // Shared interface for individual API available components interface IndividualAPIAvailableProps { /** * Component to render when API is not available */ fallback: ReactNode; /** * Component to render while checking availability */ loading: ReactNode; /** * Children to render when API is available */ children: ReactNode; /** * Callback that receives the most significant status * Status priority: "unsupported" > "checking" > "available" > "downloadable" > "unavailable" */ onStatusChange?: (status: MostSignificantStatus) => void; } // Special interface for translator component that requires language pair interface TranslatorAvailableProps extends IndividualAPIAvailableProps { /** * Target language code (required for translator) */ targetLanguage: string; /** * Source language code (optional, defaults to "auto" if not provided) */ sourceLanguage?: string; } type AvailabilityStatus = "available" | "downloadable" | "unavailable" | null; // Type for the most significant status export type MostSignificantStatus = | "unsupported" | "checking" | "available" | "downloadable" | "unavailable"; // Helper function to get the most significant status const getMostSignificantStatus = ( isSupported: boolean | null, availability: AvailabilityStatus ): MostSignificantStatus => { // Most significant: browser doesn't support the API if (isSupported === false) { return "unsupported"; } // Still checking support or availability if (isSupported === null || availability === null) { return "checking"; } // At this point, isSupported === true and availability is known // Return the availability status (available > downloadable > unavailable) return availability; }; // Helper function to check if API is available const isAPIAvailable = ( isSupported: boolean | null, availability: AvailabilityStatus ): boolean => { // First check if browser supports the API if (isSupported === false) { return false; } // If we're still checking support, return false if (isSupported === null) { return false; } // At this point, isSupported === true, so browser supports it // Check local availability only if (availability === null) { // Still checking availability return false; } // If locally available or downloadable, it's available if (availability === "available" || availability === "downloadable") { return true; } // If unavailable locally, return false return false; }; // Individual API Available Component Hook function useAPIAvailability( isSupported: boolean | null, availability: AvailabilityStatus, onStatusChange?: (status: MostSignificantStatus) => void ) { const [isChecking, setIsChecking] = useState(true); const [isAvailable, setIsAvailable] = useState(false); const timeoutRef = useRef<NodeJS.Timeout | null>(null); useEffect(() => { let cancelled = false; const checkAvailability = () => { const status = getMostSignificantStatus(isSupported, availability); // Notify callback of status change if (!cancelled && onStatusChange) { onStatusChange(status); } // If explicitly not supported, we're done if (isSupported === false) { if (!cancelled) { setIsAvailable(false); setIsChecking(false); } return; } // If we have availability info, check it synchronously if (availability !== null) { if (!cancelled) { setIsAvailable(isAPIAvailable(isSupported, availability)); setIsChecking(false); } return; } // If isSupported is null, we're still checking support // If isSupported is true but availability is null, we need to check availability if (isSupported === null || (isSupported === true && availability === null)) { // Add a timeout to stop checking after a reasonable time (2 seconds) if (timeoutRef.current) { clearTimeout(timeoutRef.current); } timeoutRef.current = setTimeout(() => { if (!cancelled) { // If still null after timeout, assume not supported setIsAvailable(false); setIsChecking(false); if (onStatusChange) { onStatusChange("unsupported"); } } timeoutRef.current = null; }, 2000); if (!cancelled) { setIsChecking(true); setIsAvailable(false); } } }; checkAvailability(); return () => { cancelled = true; if (timeoutRef.current) { clearTimeout(timeoutRef.current); timeoutRef.current = null; } }; }, [isSupported, availability, onStatusChange]); return { isChecking, isAvailable }; } /** * GencnUIRewriterAvailable component checks if Rewriter API is available locally * and conditionally renders children or fallback based on availability. */ export function GencnUIRewriterAvailable({ fallback, loading, children, onStatusChange, }: IndividualAPIAvailableProps) { const rewriter = useRewriter(); const { isChecking, isAvailable } = useAPIAvailability( rewriter.isSupported, rewriter.availability, onStatusChange ); if (isChecking) { return <>{loading}</>; } return <>{isAvailable ? children : fallback}</>; } /** * GencnUIWriterAvailable component checks if Writer API is available locally * and conditionally renders children or fallback based on availability. */ export function GencnUIWriterAvailable({ fallback, loading, children, onStatusChange, }: IndividualAPIAvailableProps) { const writer = useWriter(); const { isChecking, isAvailable } = useAPIAvailability( writer.isSupported, writer.availability, onStatusChange ); if (isChecking) { return <>{loading}</>; } return <>{isAvailable ? children : fallback}</>; } /** * GencnUIProofreaderAvailable component checks if Proofreader API is available locally * and conditionally renders children or fallback based on availability. */ export function GencnUIProofreaderAvailable({ fallback, loading, children, onStatusChange, }: IndividualAPIAvailableProps) { const proofreader = useProofreader(); const { isChecking, isAvailable } = useAPIAvailability( proofreader.isSupported, proofreader.availability, onStatusChange ); if (isChecking) { return <>{loading}</>; } return <>{isAvailable ? children : fallback}</>; } /** * GencnUILanguageModelAvailable component checks if LanguageModel (Prompt API) is available locally * and conditionally renders children or fallback based on availability. */ export function GencnUILanguageModelAvailable({ fallback, loading, children, onStatusChange, }: IndividualAPIAvailableProps) { const languageModel = useLanguageModel(); const { isChecking, isAvailable } = useAPIAvailability( languageModel.isSupported, languageModel.availability, onStatusChange ); if (isChecking) { return <>{loading}</>; } return <>{isAvailable ? children : fallback}</>; } /** * GencnUITranslatorAvailable component checks if Translator API is available locally * for a specific language pair and conditionally renders children or fallback based on availability. */ export function GencnUITranslatorAvailable({ fallback, loading, children, targetLanguage, sourceLanguage, onStatusChange, }: TranslatorAvailableProps) { const translator = useTranslator(); const [pairAvailability, setPairAvailability] = useState<AvailabilityStatus>(null); // Check language pair availability when targetLanguage or sourceLanguage changes useEffect(() => { // Only check if translator is supported and we have a target language if (!translator.isSupported || !targetLanguage) { setPairAvailability(null); return; } const sourceLang = sourceLanguage || "auto"; translator.checkLanguagePairAvailability(sourceLang, targetLanguage) .then((avail) => { setPairAvailability(avail); }) .catch(() => { // Check if still supported before setting to null if (translator.isSupported) { setPairAvailability(null); } }); }, [translator.isSupported, translator.checkLanguagePairAvailability, targetLanguage, sourceLanguage]); // Use language pair availability if available, otherwise fall back to general availability const availability = pairAvailability !== null ? pairAvailability : translator.availability; // Convert isSupported from boolean to boolean | null for consistency const isSupported: boolean | null = translator.isSupported ? true : false; const { isChecking, isAvailable } = useAPIAvailability(isSupported, availability, onStatusChange); if (isChecking) { return <>{loading}</>; } return <>{isAvailable ? children : fallback}</>; } /** * GencnUISummarizerAvailable component checks if Summarizer API is available locally * and conditionally renders children or fallback based on availability. */ export function GencnUISummarizerAvailable({ fallback, loading, children, onStatusChange, }: IndividualAPIAvailableProps) { const summarizer = useSummarizer(); const { isChecking, isAvailable } = useAPIAvailability( summarizer.isSupported, summarizer.availability, onStatusChange ); if (isChecking) { return <>{loading}</>; } return <>{isAvailable ? children : fallback}</>; } /** * GencnUILanguageDetectorAvailable component checks if Language Detector API is available locally * and conditionally renders children or fallback based on availability. */ export function GencnUILanguageDetectorAvailable({ fallback, loading, children, onStatusChange, }: IndividualAPIAvailableProps) { const [isSupported, setIsSupported] = useState<boolean | null>(null); const [availability, setAvailability] = useState<AvailabilityStatus>(null); // Check language detector support and availability useEffect(() => { const supported = libIsLanguageDetectorSupported(); setIsSupported(supported); if (supported) { libCheckLanguageDetectorAvailability() .then((avail) => { setAvailability(avail); }) .catch(() => { setAvailability(null); }); } }, []); const { isChecking, isAvailable } = useAPIAvailability(isSupported, availability, onStatusChange); if (isChecking) { return <>{loading}</>; } return <>{isAvailable ? children : fallback}</>; } /** * GencnUIDetectLanguageAvailable component checks if Language Detector API is available locally * and conditionally renders GencnUIDetectLanguage component or fallback based on availability. * This is a convenience wrapper around GencnUILanguageDetectorAvailable for the GencnUIDetectLanguage component. */ export function GencnUIDetectLanguageAvailable({ fallback, loading, children, onStatusChange, }: IndividualAPIAvailableProps) { return ( <GencnUILanguageDetectorAvailable fallback={fallback} loading={loading} onStatusChange={onStatusChange}> {children} </GencnUILanguageDetectorAvailable> ); }

