import { type JSX, splitProps, createSignal, createContext, useContext, createEffect, onCleanup, Show } from 'solid-js'; import { cn } from '../utils/cn'; import { ChevronDown } from 'lucide-solid'; import { Markdown } from './markdown'; import { observeContentHeight } from '../primitives/use-resize-observer'; interface ReasoningContextValue { isOpen: () => boolean; onOpenChange: (open: boolean) => void; } const ReasoningContext = createContext(); function useReasoningContext() { const context = useContext(ReasoningContext); if (!context) { throw new Error('useReasoningContext must be used within a Reasoning provider'); } return context; } // --- Reasoning (Root) --- export interface ReasoningProps { children: JSX.Element; class?: string; open?: boolean; onOpenChange?: (open: boolean) => void; isStreaming?: boolean; } function Reasoning(props: ReasoningProps) { const [local] = splitProps(props, ['children', 'class', 'open', 'onOpenChange', 'isStreaming']); const [internalOpen, setInternalOpen] = createSignal(false); const [wasAutoOpened, setWasAutoOpened] = createSignal(false); const isControlled = () => local.open !== undefined; const isOpen = () => (isControlled() ? local.open! : internalOpen()); const handleOpenChange = (newOpen: boolean) => { if (!isControlled()) { setInternalOpen(newOpen); } local.onOpenChange?.(newOpen); }; createEffect(() => { const streaming = local.isStreaming; if (streaming && !wasAutoOpened()) { if (!isControlled()) setInternalOpen(true); setWasAutoOpened(true); } if (!streaming && wasAutoOpened()) { if (!isControlled()) setInternalOpen(false); setWasAutoOpened(false); } }); return (
{local.children}
); } // --- ReasoningTrigger --- export interface ReasoningTriggerProps extends JSX.ButtonHTMLAttributes { children: JSX.Element; } function ReasoningTrigger(props: ReasoningTriggerProps) { const [local, rest] = splitProps(props, ['children', 'class']); const { isOpen, onOpenChange } = useReasoningContext(); return ( ); } // --- ReasoningContent --- export interface ReasoningContentProps extends JSX.HTMLAttributes { children: JSX.Element; markdown?: boolean; contentClass?: string; } function ReasoningContent(props: ReasoningContentProps) { const [local, rest] = splitProps(props, ['children', 'class', 'contentClass', 'markdown']); const { isOpen } = useReasoningContext(); let contentRef: HTMLDivElement | undefined; let innerRef: HTMLDivElement | undefined; createEffect(() => { if (!contentRef || !innerRef) return; const dispose = observeContentHeight(innerRef, () => { if (contentRef && innerRef && isOpen()) { contentRef.style.maxHeight = `${innerRef.scrollHeight}px`; } }); if (isOpen()) { contentRef.style.maxHeight = `${innerRef.scrollHeight}px`; } else { contentRef.style.maxHeight = '0px'; } onCleanup(dispose); }); return (
); } export { Reasoning, ReasoningTrigger, ReasoningContent };