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 (
);
};