"use client"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; import type { UIMessage } from "ai"; import { ArrowDownIcon, DownloadIcon } from "lucide-react"; import type { ComponentProps } from "react"; import { useCallback } from "react"; import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom"; export type ConversationProps = ComponentProps; export const Conversation = ({ className, ...props }: ConversationProps) => ( ); export type ConversationContentProps = ComponentProps< typeof StickToBottom.Content >; export const ConversationContent = ({ className, ...props }: ConversationContentProps) => ( ); export type ConversationEmptyStateProps = ComponentProps<"div"> & { title?: string; description?: string; icon?: React.ReactNode; }; export const ConversationEmptyState = ({ className, title = "No messages yet", description = "Start a conversation to see messages here", icon, children, ...props }: ConversationEmptyStateProps) => (
{children ?? ( <> {icon &&
{icon}
}

{title}

{description && (

{description}

)}
)}
); export type ConversationScrollButtonProps = ComponentProps; export const ConversationScrollButton = ({ className, ...props }: ConversationScrollButtonProps) => { const { isAtBottom, scrollToBottom } = useStickToBottomContext(); const handleScrollToBottom = useCallback(() => { scrollToBottom(); }, [scrollToBottom]); return ( !isAtBottom && ( ) ); }; const getMessageText = (message: UIMessage): string => message.parts .filter((part) => part.type === "text") .map((part) => part.text) .join(""); export type ConversationDownloadProps = Omit< ComponentProps, "onClick" > & { messages: UIMessage[]; filename?: string; formatMessage?: (message: UIMessage, index: number) => string; }; const defaultFormatMessage = (message: UIMessage): string => { const roleLabel = message.role.charAt(0).toUpperCase() + message.role.slice(1); return `**${roleLabel}:** ${getMessageText(message)}`; }; export const messagesToMarkdown = ( messages: UIMessage[], formatMessage: ( message: UIMessage, index: number ) => string = defaultFormatMessage ): string => messages.map((msg, i) => formatMessage(msg, i)).join("\n\n"); export const ConversationDownload = ({ messages, filename = "conversation.md", formatMessage = defaultFormatMessage, className, children, ...props }: ConversationDownloadProps) => { const handleDownload = useCallback(() => { const markdown = messagesToMarkdown(messages, formatMessage); const blob = new Blob([markdown], { type: "text/markdown" }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = filename; document.body.append(link); link.click(); link.remove(); URL.revokeObjectURL(url); }, [messages, filename, formatMessage]); return ( ); };