import React, { useRef, useCallback, useEffect, type ReactElement, } from 'react'; import { useKeyboardAwareChat } from './useKeyboardAwareChat'; import { View, Text, FlatList, TouchableOpacity, Alert, type FlatListProps, type ListRenderItem, } from 'react-native'; import { ArrowDown, Download, MessageSquare } from 'lucide-react-native'; import type { UIMessage } from 'ai'; import { Message, MessageContent, MessageResponse } from './message'; import { BlankProvider, useBlankContext } from './blank-context'; export type ConversationProps = React.PropsWithChildren<{ className?: string }>; export const Conversation = ({ children, className }: ConversationProps) => ( {children} ); export type ConversationEmptyStateProps = { title?: string; description?: string; icon?: ReactElement; className?: string; }; export const ConversationEmptyState = ({ title = 'Start a conversation', description = 'Type a message below to begin chatting', icon, className, }: ConversationEmptyStateProps) => ( {icon ?? } {title} {description} ); export type ConversationContentProps = { messages: UIMessage[]; renderItem?: ListRenderItem; estimatedItemSize?: number; } & Omit, 'data' | 'renderItem'>; export const ConversationContent = ({ messages, renderItem, ...flatListProps }: ConversationContentProps) => { const flatListRef = useRef>(null); const defaultRenderItem: ListRenderItem = useCallback( ({ item: message, index }) => ( {message.parts ?.filter((part) => part.type === 'text') .map((part, i) => ( ))} ), [] ); const { scrollHandler } = useKeyboardAwareChat(); const { blankSize } = useBlankContext(); const prevLengthRef = useRef(messages.length); useEffect(() => { const shouldScroll = messages.length > prevLengthRef.current && messages[messages.length - 1].role === 'user'; if (shouldScroll) { flatListRef.current?.scrollToEnd?.(); } prevLengthRef.current = messages.length; }, [messages]); const { messagesContainerHeight } = useBlankContext(); return ( { const height = e.nativeEvent.layout.height; messagesContainerHeight.value = height; }} > item.id} className="flex-1" contentContainerClassName="px-4 py-6 gap-6" scrollEventThrottle={16} ListEmptyComponent={ messages.length === 0 ? : undefined } contentContainerStyle={{ paddingBottom: blankSize.value, }} {...flatListProps} /> ); }; export const ConversationScrollButton = () => ( {}} className="absolute bottom-6 left-1/2 -translate-x-1/2 bg-primary h-11 w-11 items-center justify-center rounded-full shadow-lg" > ); export type ConversationDownloadProps = { messages: UIMessage[] }; export const ConversationDownload = ({ messages, }: ConversationDownloadProps) => { const handleDownload = useCallback(() => { const markdown = messages .map((msg) => { const role = msg.role === 'user' ? 'User' : 'Assistant'; const text = msg.parts ?.filter((p) => p.type === 'text') .map((p) => p.text) .join('\n'); return `**${role}:**\n${text}`; }) .join('\n\n'); Alert.alert('Download', `Markdown ready (${messages.length} messages)`); }, [messages]); return ( ); };