"use client"; import { ChevronRight } from "lucide-react"; import { motion, useReducedMotion } from "motion/react"; import { type ReactNode, useLayoutEffect, useRef, useState } from "react"; import { EASE_OUT } from "@/lib/ease"; import { cn } from "@/lib/utils"; import { ThreadShimmerText } from "./cards"; import { ThreadStreamingCaret } from "./status"; export { ThreadCard, ThreadCardButton, ThreadCommandRow, ThreadDiffCard, ThreadDiffRow, ThreadFileCard, ThreadShimmerText, } from "./cards"; export { ThreadApprovalCard, ThreadBranchSwitcher, ThreadCheckpoint, ThreadElicitation, ThreadErrorState, ThreadScrollPill, ThreadStreamingCaret, ThreadSuggestions, ThreadSystemBanner, ThreadTask, ThreadTaskList, ThreadThinking, ThreadToolCall, ThreadUsage, } from "./status"; export interface ThreadProps { className?: string; children?: ReactNode; } /** * Conversation column — centers the message stream at a fixed reading * width. Renders `children` directly; spacing between items (user messages, * turns) comes from each item's own margins rather than a gap here, so a * lone item still looks right regardless of what precedes it. */ export function Thread({ className, children }: ThreadProps) { return (
{children}
); } export interface ThreadItemProps { className?: string; children?: ReactNode; } /** * Entrance wrapper for a stream item (a user message or an agent turn) as it * streams in — a short opacity + upward slide, reduced to an opacity-only * fade under `useReducedMotion()`. Purely presentational: callers decide * whether to wrap a given item at all (e.g. history rendered on first paint * may skip it to avoid replaying the entrance). */ export function ThreadItem({ className, children }: ThreadItemProps) { const reduce = useReducedMotion() ?? false; return ( {children} ); } export interface ThreadUserMessageProps { className?: string; children?: ReactNode; } /** Right-aligned user message — a rounded gray pill capped at 77% of the column width. */ export function ThreadUserMessage({ className, children }: ThreadUserMessageProps) { return (
{children}
); } export interface ThreadTurnHeaderProps { /** Controlled open state — purely a chevron-rotation signal (see below). */ open?: boolean; onOpenChange?: (next: boolean) => void; /** While the turn is still executing: shimmers the label. The header stays fully interactive — a running turn's live work log can be expanded and collapsed too. */ working?: boolean; className?: string; children?: ReactNode; } /** * Turn-header button, e.g. "Worked for 2m 4s ›". Renders only the header * itself — label plus a chevron that rotates 90° to signal open/closed — * and does not fold or animate any content region below it: pair it with * `ThreadCollapse` (wired to the same `open`) to collapse the turn's work * log, or render your own region. Works controlled (`open`/`onOpenChange`) * or uncontrolled. `working` (the turn is still executing, e.g. a live * elapsed-seconds label) only swaps the label into `ThreadShimmerText`; * chevron and toggling stay live, since a running turn's work log can be * inspected mid-flight. */ export function ThreadTurnHeader({ open: openProp, onOpenChange, working = false, className, children, }: ThreadTurnHeaderProps) { const reduce = useReducedMotion() ?? false; const [openState, setOpenState] = useState(false); const open = openProp ?? openState; const toggle = () => { const next = !open; setOpenState(next); onOpenChange?.(next); }; return ( ); } export interface ThreadCollapseProps { open: boolean; className?: string; children?: ReactNode; } /** * Measured-height collapse region — the pairing for `ThreadTurnHeader`: put * the turn's work log (thinking row, tool calls, interim notes) inside and * wire `open` to the header's state so clicking the header collapses and * expands it. Height comes from a `ResizeObserver`, so content that changes * while open (e.g. rows streaming in) is tracked; animated 0 ↔ measured * with `EASE_OUT` (0.25s), switched instantly under `useReducedMotion()`. */ export function ThreadCollapse({ open, className, children }: ThreadCollapseProps) { const reduce = useReducedMotion() ?? false; const contentRef = useRef(null); const [contentHeight, setContentHeight] = useState(0); useLayoutEffect(() => { const node = contentRef.current; if (!node) return; const update = () => setContentHeight(node.offsetHeight); update(); const observer = new ResizeObserver(update); observer.observe(node); return () => observer.disconnect(); }, []); return (
{children}
); } export interface ThreadMessageProps { /** Appends a blinking `ThreadStreamingCaret` after `children` while the response is still streaming in. */ streaming?: boolean; className?: string; children?: ReactNode; } /** * Typography container for agent-authored prose. A pure styling shell — the * Markdown → JSX rendering engine is left to the caller (this component * doesn't depend on any particular one); descendant selectors give * paragraphs, lists and inline marks consistent spacing regardless of which * renderer produced them. `streaming` appends a `ThreadStreamingCaret` at * the container tail — after a block-level last paragraph that lands on its * own line; to embed the caret inside the last line of text, place * `ThreadStreamingCaret` directly in your own JSX instead. */ export function ThreadMessage({ streaming = false, className, children }: ThreadMessageProps) { return (
{children} {streaming ? : null}
); } export interface ThreadInlineCodeProps { className?: string; children?: ReactNode; } /** Inline code chip for use inside `ThreadMessage` prose. */ export function ThreadInlineCode({ className, children }: ThreadInlineCodeProps) { return ( {children} ); } export interface ThreadCodeBlockProps { /** Small label in the top-right corner, e.g. a language name like "bash". */ label?: ReactNode; className?: string; children?: ReactNode; } /** Fenced code block for `ThreadMessage` prose — a `pre > code` structure with an optional corner label. */ export function ThreadCodeBlock({ label, className, children }: ThreadCodeBlockProps) { return (
{label ? ( {label} ) : null}
        {children}
      
); } export interface ThreadActionBarProps { /** Rendered after the buttons, e.g. a relative send time. */ timestamp?: ReactNode; className?: string; children?: ReactNode; } /** * Turn-tail action row — hidden until the turn is hovered or a child gains * focus. Pair it with a parent that carries the `group/turn` class (see the * preview) so `group-hover/turn:opacity-100` has something to key off; a * bare hover on the bar itself would only reveal it once the pointer is * already over these 20px-tall icons. */ export function ThreadActionBar({ timestamp, className, children }: ThreadActionBarProps) { return (
{children} {timestamp ? {timestamp} : null}
); } export interface ThreadActionButtonProps { "aria-label": string; onClick?: () => void; children?: ReactNode; className?: string; } /** One icon button inside a `ThreadActionBar` (copy, react, share, ...). */ export function ThreadActionButton({ "aria-label": ariaLabel, onClick, children, className, }: ThreadActionButtonProps) { return ( ); }