Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

64 linesCodeBlameRaw
1/**
2 * AI Elements' Conversation (registry.ai-sdk.dev/conversation.json), in
3 * g1t's tokens: a scrolling log that follows new words to the bottom while
4 * the reader is there, and stops following the moment they scroll up to
5 * read (use-stick-to-bottom). A button takes them back down.
6 */
7import { ArrowDown } from "lucide-react";
8import type { ComponentProps, ReactNode } from "react";
9import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
10
11import { cn } from "../../lib/cn";
12import { Button } from "../ui/button";
13
14export type ConversationProps = ComponentProps<typeof StickToBottom>;
15
16export function Conversation({ className, ...props }: ConversationProps) {
17 return <StickToBottom className={cn("relative min-h-0 flex-1 overflow-y-hidden", className)} initial="smooth" resize="smooth" role="log" {...props} />;
18}
19
20export function ConversationContent({ className, ...props }: ComponentProps<typeof StickToBottom.Content>) {
21 return <StickToBottom.Content className={cn("flex flex-col gap-6 px-5 py-4", className)} {...props} />;
22}
23
24export function ConversationEmptyState({
25 className,
26 title,
27 description,
28 icon,
29 children,
30 ...props
31}: ComponentProps<"div"> & { title?: string; description?: ReactNode; icon?: ReactNode }) {
32 return (
33 <div className={cn("flex flex-col items-center justify-center gap-3 px-6 py-10 text-center", className)} {...props}>
34 {children ?? (
35 <>
36 {icon && <div className="text-accent">{icon}</div>}
37 <div className="space-y-1">
38 {title && <h3 className="text-sm font-medium text-fg">{title}</h3>}
39 {description && <p className="text-sm text-muted">{description}</p>}
40 </div>
41 </>
42 )}
43 </div>
44 );
45}
46
47/** Back to the newest words, shown only while the reader is scrolled up. */
48export function ConversationScrollButton({ className, ...props }: ComponentProps<typeof Button>) {
49 const { isAtBottom, scrollToBottom } = useStickToBottomContext();
50 if (isAtBottom) return null;
51 return (
52 <Button
53 type="button"
54 variant="outline"
55 size="icon-sm"
56 aria-label="Scroll to the newest"
57 onClick={() => void scrollToBottom()}
58 className={cn("absolute bottom-3 left-1/2 -translate-x-1/2 rounded-full bg-surface shadow-lg shadow-black/30", className)}
59 {...props}
60 >
61 <ArrowDown size={15} />
62 </Button>
63 );
64}