"use client" /** * AskLeoSidebar — app-wide right sidebar for the AI assistant. * Mirrors the left sidebar behavior: slides in/out with a toggle button. * Lives in the (app) layout so it persists across all pages. */ import * as React from "react" import { useLocation } from "react-router-dom" import { useProduct } from "@/contexts/product-context" import { productSlug } from "@/stores/app-store" import { cn } from "@/lib/utils" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { AskLeoComposer } from "@/components/ask-leo-composer" import { LeoThreadMessages } from "@/components/leo-thread-messages" import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip" import { useSidebar } from "@/components/ui/sidebar" import { StatusBadge } from "@/components/ui/status-badge" import { ASK_LEO_PANEL_WIDTH_DEFAULT, ASK_LEO_PANEL_WIDTH_KEY, NestedSecondaryPanelShell, } from "@/components/templates/nested-secondary-panel-shell" import { useSidebarReflowZoom } from "@/hooks/use-sidebar-reflow-zoom" import { ASK_LEO_GENERIC_SUGGESTIONS, getAskLeoRouteContext } from "@/lib/ask-leo-route-context" import { NAV_USER } from "@/lib/mock/navigation" import { useLeoThread } from "@/lib/use-leo-thread" import { AskLeoShortcutKbds, useAskLeo } from "@/components/ask-leo-context" import { AskLeoViewToggle } from "@/components/ask-leo-view-toggle" // React.lazy + Suspense for the chart bundle. // PR-6. Same call-site shape (``) — Suspense boundary is internal. const LeoIconLazy = React.lazy(() => import("@/components/ui/leo-icon").then(m => ({ default: m.LeoIcon })), ) function LeoIcon(props: React.ComponentProps) { return ( ) } function isLeoLandingPath(pathname: string, leoHref: string) { return pathname === leoHref || pathname.startsWith(`${leoHref}/`) } /** * Right-rail Ask Leo — same shell as {@link NestedSecondaryPanelShell} / library secondary nav. * Sits beside `SidebarInset` in the app row (not inside the main inset card). */ export function AskLeoSidebar() { const { open, setOpen, consumePendingComposerPrompt, pageContext } = useAskLeo() const { setOpen: setSidebarOpen, isMobile } = useSidebar() const reflowZoom = useSidebarReflowZoom() const navFlyout = isMobile || reflowZoom const { pathname } = useLocation() const { product } = useProduct() const leoHref = `/${productSlug(product)}/leo` const isOnLeoLanding = isLeoLandingPath(pathname, leoHref) const [composerValue, setComposerValue] = React.useState("") const [composerExpanded, setComposerExpanded] = React.useState(false) const composerTextareaRef = React.useRef(null) const { messages: threadMessages, isThinking, send, stop, reset, } = useLeoThread() const routeContext = React.useMemo(() => getAskLeoRouteContext(pathname), [pathname]) const suggestions = pageContext?.suggestions && pageContext.suggestions.length > 0 ? pageContext.suggestions : routeContext.suggestions ?? [] const suggestionChips = suggestions.length > 0 ? suggestions : ASK_LEO_GENERIC_SUGGESTIONS const appendUserTurn = React.useCallback( (text: string) => { send(text) }, [send], ) React.useEffect(() => { if (!open) { reset() setComposerValue("") return } const pending = consumePendingComposerPrompt() if (pending !== null) { setComposerValue(pending) queueMicrotask(() => composerTextareaRef.current?.focus()) } else { setComposerValue("") } }, [open, consumePendingComposerPrompt, reset]) // Collapse main sidebar when Ask Leo opens, expand when it closes const prevOpen = React.useRef(open) React.useEffect(() => { if (open && !prevOpen.current) setSidebarOpen(false) if (!open && prevOpen.current) setSidebarOpen(true) prevOpen.current = open }, [open, setSidebarOpen]) if (isOnLeoLanding) { return null } return ( <> {navFlyout && open ? (