import React, { useRef, useCallback, useEffect, type ReactElement, } from 'react'; import { GestureDetector } from 'react-native-gesture-handler'; import { useKeyboardAwareChat } from './useKeyboardAwareChat'; import { View, Text, FlatList, TouchableOpacity, Alert, type FlatListProps, type ListRenderItem, Platform, } from 'react-native'; import { ArrowDown, Download } from 'lucide-react-native'; import type { UIMessage } from 'ai'; import { Message, MessageContent, MessageResponse } from './message'; import { BlankProvider, useBlankContext } from './blank-context'; import type { LegendListRef } from '@legendapp/list'; import { AnimatedLegendList } from '@legendapp/list/reanimated'; 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) => ( {title} ); export type ConversationContentProps = { messages: UIMessage[]; renderItem?: ListRenderItem; estimatedItemSize?: number; } & Omit, 'data' | 'renderItem'>; export const ConversationContent = ({ messages, renderItem, estimatedItemSize = 140, ...flatListProps }: ConversationContentProps) => { const flatListRef = useRef | LegendListRef>(null); const defaultRenderItem: ListRenderItem = useCallback( ({ item: message, index }) => ( {message.parts ?.filter((part) => part.type === 'text') .map((part, i) => ( ))} ), [] ); const { scrollHandler, panGesture } = useKeyboardAwareChat(); const { blankSize } = useBlankContext(); const prevLengthRef = useRef(messages.length); useEffect(() => { const shouldScroll = messages.length > prevLengthRef.current && messages[messages.length - 1].role === 'user'; if (shouldScroll && Platform.OS !== 'web') { flatListRef.current?.scrollToEnd?.(); } prevLengthRef.current = messages.length; }, [messages]); const { messagesContainerHeight } = useBlankContext(); return ( { const height = e.nativeEvent.layout.height; messagesContainerHeight.value = height; }} > {messages.length === 0 ? ( ) : ( item.id} scrollEventThrottle={16} estimatedItemSize={estimatedItemSize} removeClippedSubviews={Platform.OS !== 'web'} initialNumToRender={15} windowSize={10} maxToRenderPerBatch={10} 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 ( ); };