"use client"; import { type ReasoningGroupComponent, type ReasoningMessagePartComponent, useAuiState, useScrollLock, } from "@assistant-ui/react"; import { cva, type VariantProps } from "class-variance-authority"; import { BrainIcon, ChevronDownIcon } from "lucide-react"; import { memo, useCallback, useRef, useState } from "react"; import { MarkdownText } from "@/components/assistant-ui/markdown-text"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; import { cn } from "@/lib/utils"; const ANIMATION_DURATION = 200; const reasoningVariants = cva("aui-reasoning-root mb-4 w-full", { variants: { variant: { outline: "rounded-lg border px-3 py-2", ghost: "", muted: "rounded-lg bg-muted/50 px-3 py-2", }, }, defaultVariants: { variant: "outline", }, }); export type ReasoningRootProps = Omit< React.ComponentProps, "open" | "onOpenChange" > & VariantProps & { open?: boolean; onOpenChange?: (open: boolean) => void; defaultOpen?: boolean; }; function ReasoningRoot({ className, variant, open: controlledOpen, onOpenChange: controlledOnOpenChange, defaultOpen = false, children, ...props }: ReasoningRootProps) { const collapsibleRef = useRef(null); const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen); const lockScroll = useScrollLock(collapsibleRef, ANIMATION_DURATION); const isControlled = controlledOpen !== undefined; const isOpen = isControlled ? controlledOpen : uncontrolledOpen; const handleOpenChange = useCallback( (open: boolean) => { if (!open) { lockScroll(); } if (!isControlled) { setUncontrolledOpen(open); } controlledOnOpenChange?.(open); }, [lockScroll, isControlled, controlledOnOpenChange], ); return ( {children} ); } function ReasoningFade({ className, ...props }: React.ComponentProps<"div">) { return (
); } function ReasoningTrigger({ active, duration, className, ...props }: React.ComponentProps & { active?: boolean; duration?: number; }) { const durationText = duration ? ` (${duration}s)` : ""; return ( Reasoning{durationText} {active ? ( Reasoning{durationText} ) : null} ); } function ReasoningContent({ className, children, ...props }: React.ComponentProps) { return ( {children} ); } function ReasoningText({ className, ...props }: React.ComponentProps<"div">) { return (
); } const ReasoningImpl: ReasoningMessagePartComponent = ({ text, status }) => ( ); const ReasoningGroupImpl: ReasoningGroupComponent = ({ children, startIndex, endIndex, }) => { const isReasoningStreaming = useAuiState((s) => { if (s.message.status?.type !== "running") return false; const lastIndex = s.message.parts.length - 1; if (lastIndex < 0) return false; const lastType = s.message.parts[lastIndex]?.type; if (lastType !== "reasoning") return false; return lastIndex >= startIndex && lastIndex <= endIndex; }); return ( {children} ); }; const Reasoning = memo( ReasoningImpl, ) as unknown as ReasoningMessagePartComponent & { Root: typeof ReasoningRoot; Trigger: typeof ReasoningTrigger; Content: typeof ReasoningContent; Text: typeof ReasoningText; Fade: typeof ReasoningFade; }; Reasoning.displayName = "Reasoning"; Reasoning.Root = ReasoningRoot; Reasoning.Trigger = ReasoningTrigger; Reasoning.Content = ReasoningContent; Reasoning.Text = ReasoningText; Reasoning.Fade = ReasoningFade; const ReasoningGroup = memo(ReasoningGroupImpl); ReasoningGroup.displayName = "ReasoningGroup"; export { Reasoning, ReasoningContent, ReasoningFade, ReasoningGroup, ReasoningRoot, ReasoningText, ReasoningTrigger, reasoningVariants, };