import { createContext, useContext, useEffect, useState, type PropsWithChildren, } from "react"; import { createRoot } from "react-dom/client"; import { Toast } from "../overlay/toast"; export type AiPanelPosition = "left" | "right"; type AiPanelContextValue = { close: () => void; enabled: boolean; isOpen: boolean; open: () => void; position: AiPanelPosition; toggle: () => void; }; type AiPanelProviderConfig = { defaultOpen?: boolean; enabled?: boolean; error?: string; onOpenChange?: (open: boolean) => void; open?: boolean; position?: AiPanelPosition; }; const AiPanelContext = createContext(null); let toastCleanup: (() => void) | undefined; function showAiPanelConfigurationError( description = "Set aiPanelConfig.enabled to true on the nearest UhuruAppLayout before using useAiPanel().", ) { if (typeof document === "undefined") return; toastCleanup?.(); const host = document.createElement("div"); host.setAttribute("role", "status"); host.setAttribute("aria-live", "assertive"); host.className = "fixed right-4 bottom-4 z-[10000] max-w-[calc(100vw-2rem)]"; document.body.appendChild(host); const root = createRoot(host); const cleanup = () => { root.unmount(); host.remove(); if (toastCleanup === cleanup) toastCleanup = undefined; }; toastCleanup = cleanup; root.render( , ); window.setTimeout(cleanup, 4200); } export function UhuruAiPanelProvider({ children, config, }: PropsWithChildren<{ config?: AiPanelProviderConfig }>) { const enabled = config?.enabled ?? false; const controlled = config?.open !== undefined; const [internalOpen, setInternalOpen] = useState( enabled && (config?.defaultOpen ?? false), ); const isOpen = enabled && (controlled ? Boolean(config?.open) : internalOpen); useEffect(() => { if (config?.error) showAiPanelConfigurationError(config.error); }, [config?.error]); useEffect(() => { if (!enabled && internalOpen) setInternalOpen(false); }, [enabled, internalOpen]); function setOpen(next: boolean) { if (!enabled) { showAiPanelConfigurationError(config?.error); return; } if (!controlled) setInternalOpen(next); config?.onOpenChange?.(next); } return ( setOpen(false), enabled, isOpen, open: () => setOpen(true), position: config?.position ?? "left", toggle: () => setOpen(!isOpen), }} > {children} ); } export function useAiPanel() { const context = useContext(AiPanelContext); if (context) return context; return { close: showAiPanelConfigurationError, enabled: false, isOpen: false, open: showAiPanelConfigurationError, position: "left" as const, toggle: showAiPanelConfigurationError, }; } export function useAiPanelContext() { return useContext(AiPanelContext); }