"use client" import * as React from "react" import type { ComponentDocSpec } from "@/lib/design-system/component-doc-types" import { LeoThreadMessages } from "@/components/leo-thread-messages" import type { LeoThreadMessage } from "@/lib/use-leo-thread" import { Button } from "@/components/ui/button" import { ButtonSegmentedControl } from "@/components/ui/button-segmented-control" import { MessageScroller, MessageScrollerButton, MessageScrollerContent, MessageScrollerItem, MessageScrollerProvider, MessageScrollerViewport, useMessageScroller, useMessageScrollerScrollable, useMessageScrollerVisibility, type MessageScrollerDefaultScrollPosition, } from "@/components/ui/message-scroller" import { Bubble, BubbleContent } from "@/components/ui/bubble" import { Message, MessageContent } from "@/components/ui/message" import { Marker, MarkerContent, MarkerIcon, } from "@/components/ui/marker" import { HoverCard, HoverCardContent, HoverCardTrigger, } from "@/components/ui/hover-card" import { LeoIcon } from "@/components/ui/leo-icon" import { cn } from "@/lib/utils" function ex( section: Omit, children: React.ReactNode, description?: string, ) { return { ...section, description, children } } const SAMPLE_MESSAGES: LeoThreadMessage[] = [ { id: "1", role: "user", content: "Which students are at risk this week?" }, { id: "2", role: "assistant", content: "Three students have overdue compliance items before placement start. Open the Library hub filtered to non-compliant to review.", }, ] const LONG_THREAD: LeoThreadMessage[] = [ { id: "t1", role: "user", content: "Summarize placement readiness for the spring cohort.", }, { id: "t2", role: "assistant", content: "Twelve students are green. Four need immunization updates. Two contracts are still unsigned at partner sites.", }, { id: "t3", role: "user", content: "Who needs immunization updates?", }, { id: "t4", role: "assistant", content: "Avery Chen, Jordan Blake, Sam Rivera, and Priya Nair. Prioritize Avery and Jordan — their start dates are within 14 days.", }, { id: "t5", role: "user", content: "Draft a reminder for Avery and Jordan.", }, { id: "t6", role: "assistant", content: "Subject: Action needed before placement start.\n\nPlease upload your immunization records in Exxat by Friday so we can clear you for the site. Reply if you need help finding the upload form.", }, ] const OPENING_THREAD: LeoThreadMessage[] = [ { id: "o1", role: "user", content: "This is the first message the user sent in the conversation.", }, { id: "o2", role: "assistant", content: "Workspace creation rose 8%, but first invite completion only rose 2%.", }, { id: "o3", role: "user", content: "This is the last message the user sent in the conversation.", }, { id: "o4", role: "assistant", content: "Start with the invite step. Teams are creating workspaces but waiting to add collaborators. Compare invite drop-off by account size, then review empty-state copy on the first project screen.", }, ] function DemoTurn({ message, }: { message: LeoThreadMessage }) { const isUser = message.role === "user" return ( {message.content} ) } function ScrollDemoShell({ messages, defaultScrollPosition = "last-anchor", autoScroll = true, children, className, }: { messages: LeoThreadMessage[] defaultScrollPosition?: MessageScrollerDefaultScrollPosition autoScroll?: boolean children?: React.ReactNode className?: string }) { return (
{children} {messages.map((message) => ( ))}
) } function OpeningPositionDemo() { const [position, setPosition] = React.useState("last-anchor") return ( // Controls live inside the transcript chrome (header strip), never over // the message list — floating toggles were painting on top of bubbles.

defaultScrollPosition

setPosition(next as MessageScrollerDefaultScrollPosition) } options={[ { value: "start", label: "Start" }, { value: "end", label: "End" }, { value: "last-anchor", label: "Last anchor" }, ]} />
) } function JumpCommandsBar() { const { scrollToMessage, scrollToEnd, scrollToStart } = useMessageScroller() return (
) } function JumpToMessageDemo() { return ( ) } const VISIBILITY_THREAD: LeoThreadMessage[] = [ { id: "vis-brief", role: "user", content: "Review the incident handoff and tell me what to read first.", }, { id: "vis-brief-reply", role: "assistant", content: "Start with the summary and the impact section. The regression affected the upload queue, but the recovery path completed for every queued job.", }, { id: "vis-impact", role: "user", content: "What was the customer impact?", }, { id: "vis-impact-reply", role: "assistant", content: "Impact was limited to delayed processing.\n\nNo records were dropped, and the reconciliation worker confirmed each retry batch. Support saw confusion from two customers, but there were no checkout or billing errors.", }, { id: "vis-actions", role: "user", content: "What actions are open?", }, { id: "vis-actions-reply", role: "assistant", content: "Keep the retry window enabled until the next deploy, then add a queue-depth alert as the long-term fix.\n\nThe alert should fire on sustained queue growth, not a single short spike.", }, { id: "vis-checklist", role: "user", content: "Give me the follow-up checklist.", }, { id: "vis-checklist-reply", role: "assistant", content: "After that, compare the queue recovery graph with the deploy timeline so the handoff shows exactly when processing returned to baseline.\n\nI would also add a short owner note beside each follow-up item. The checklist is small, but ownership keeps the retry-window decision, alert tuning, and support macro from drifting into separate follow-up conversations.", }, ] function trimmedAnchorLabel(content: string) { return content.length > 42 ? `${content.slice(0, 39)}…` : content } /** * Vertical position rail + outline jump menu (shadcn Transcript Outline). * Ticks track `currentAnchorId`; the HoverCard lists anchored turns via Marker. */ function TranscriptOutline() { const { scrollToMessage } = useMessageScroller() const { currentAnchorId, visibleMessageIds } = useMessageScrollerVisibility() const anchors = VISIBILITY_THREAD.filter((m) => m.role === "user") const visibleIds = new Set(visibleMessageIds) return ( {anchors.map((message) => { const current = message.id === currentAnchorId return (