import { useState, useEffect } from 'react';
import {
View,
Text,
TextInput,
FlatList,
TouchableOpacity,
StyleSheet,
KeyboardAvoidingView,
Platform,
} from 'react-native';
import { useLLM, LLAMA3_2_1B, Message } from 'react-native-executorch';
export default function ChatApp() {
const llm = useLLM({ model: LLAMA3_2_1B });
const [input, setInput] = useState('');
const [displayMessages, setDisplayMessages] = useState<Message[]>([]);
useEffect(() => {
if (llm.messageHistory.length > 0) {
setDisplayMessages(llm.messageHistory);
}
}, [llm.messageHistory]);
const handleSend = async () => {
if (!input.trim() || !llm.isReady || llm.isGenerating) return;
const userMessage = input;
setInput('');
// Optimistically show user message
setDisplayMessages(prev => [
...prev,
{ role: 'user', content: userMessage },
]);
try {
await llm.sendMessage(userMessage);
} catch (error) {
console.error('Error:', error);
}
};
return (
<KeyboardAvoidingView
style={styles.container}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
keyboardVerticalOffset={100}
>
<View style={styles.header}>
<Text style={styles.title}>AI Chat</Text>
{!llm.isReady && (
<Text style={styles.status}>
Loading... {Math.round(llm.downloadProgress * 100)}%
</Text>
)}
</View>
<FlatList
data={displayMessages}
keyExtractor={(_, index) => index.toString()}
renderItem={({ item }) => (
<View
style={[
styles.message,
item.role === 'user' ? styles.userMessage : styles.aiMessage,
]}
>
<Text style={styles.messageRole}>
{item.role === 'user' ? 'You' : 'AI'}
</Text>
<Text>{item.content}</Text>
</View>
)}
contentContainerStyle={styles.messageList}
/>
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
value={input}
onChangeText={setInput}
placeholder="Type a message..."
editable={llm.isReady && !llm.isGenerating}
/>
<TouchableOpacity
style={[
styles.sendButton,
(!llm.isReady || llm.isGenerating) && styles.sendButtonDisabled,
]}
onPress={handleSend}
disabled={!llm.isReady || llm.isGenerating}
>
<Text style={styles.sendButtonText}>
{llm.isGenerating ? '...' : 'Send'}
</Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
header: {
padding: 20,
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
},
title: {
fontSize: 24,
fontWeight: 'bold',
},
status: {
marginTop: 5,
color: '#666',
},
messageList: {
padding: 10,
},
message: {
padding: 12,
borderRadius: 8,
marginVertical: 4,
maxWidth: '80%',
},
userMessage: {
alignSelf: 'flex-end',
backgroundColor: '#007AFF',
},
aiMessage: {
alignSelf: 'flex-start',
backgroundColor: '#e0e0e0',
},
messageRole: {
fontWeight: 'bold',
marginBottom: 4,
},
inputContainer: {
flexDirection: 'row',
padding: 10,
borderTopWidth: 1,
borderTopColor: '#e0e0e0',
},
input: {
flex: 1,
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 20,
paddingHorizontal: 15,
paddingVertical: 10,
marginRight: 10,
},
sendButton: {
backgroundColor: '#007AFF',
borderRadius: 20,
paddingHorizontal: 20,
justifyContent: 'center',
},
sendButtonDisabled: {
backgroundColor: '#ccc',
},
sendButtonText: {
color: '#fff',
fontWeight: 'bold',
},
});