import React, { useState, useRef, useEffect } from 'react'; import "./global.css" import { View, Text, TextInput, ScrollView, Pressable, SafeAreaView, Animated, Keyboard, TouchableWithoutFeedback, TouchableOpacity, Platform, KeyboardAvoidingView, Alert, Image, Dimensions, ActivityIndicator } from 'react-native'; import { StatusBar } from 'expo-status-bar'; import { FontAwesome5, Ionicons, MaterialCommunityIcons } from '@expo/vector-icons'; import Markdown from 'react-native-markdown-display'; import * as Clipboard from 'expo-clipboard'; import { marked } from 'marked'; import highlightjs from 'highlight.js/lib/core'; import javascript from 'highlight.js/lib/languages/javascript'; import python from 'highlight.js/lib/languages/python'; import { Link, router, useRouter, Stack } from 'expo-router'; import Constants from 'expo-constants'; import * as Haptics from 'expo-haptics'; // Register commonly used languages highlightjs.registerLanguage('javascript', javascript); highlightjs.registerLanguage('python', python); // Screen dimensions for responsive layouts const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get('window'); // Suggested questions for users const SUGGESTED_QUESTIONS = [ "What are common symptoms of PCOS?", "How can I manage menstrual pain naturally?", "What should I know before pregnancy planning?", "Is my period flow normal?", "Why am I experiencing fatigue during periods?" ]; const FilleAI = () => { const router = useRouter(); const [isSearchSubmitted, setIsSearchSubmitted] = useState(false); const [isLoading, setIsLoading] = useState(false); const [messages, setMessages] = useState<{ type: string; text: string }[]>([]); const [inputText, setInputText] = useState(''); const [inputHeight, setInputHeight] = useState(40); const [suggestedQuestions, setSuggestedQuestions] = useState(SUGGESTED_QUESTIONS); const scrollViewRef = useRef(null); const inputRef = useRef(null); // Animation values const welcomeOpacity = useRef(new Animated.Value(1)).current; const centerContentMargin = useRef(new Animated.Value(150)).current; const floatingButtonScale = useRef(new Animated.Value(1)).current; const inputContainerAnimation = useRef(new Animated.Value(0)).current; // Pulse animation for suggestion bubbles useEffect(() => { const pulsate = Animated.loop( Animated.sequence([ Animated.timing(floatingButtonScale, { toValue: 1.05, duration: 1000, useNativeDriver: true, }), Animated.timing(floatingButtonScale, { toValue: 1, duration: 1000, useNativeDriver: true, }), ]) ); if (!isSearchSubmitted) { pulsate.start(); } else { pulsate.stop(); } return () => pulsate.stop(); }, [isSearchSubmitted]); // Show input container animation useEffect(() => { Animated.timing(inputContainerAnimation, { toValue: 1, duration: 500, delay: 500, useNativeDriver: true, }).start(); }, []); // Function to handle input height changes const updateInputHeight = (height: number) => { const newHeight = Math.min(Math.max(40, height), 100); setInputHeight(newHeight); }; // Function to format code blocks with custom renderer const formatMessage = (text: string) => { marked.setOptions({ highlight: function(code, language) { if (language && highlightjs.getLanguage(language)) { return highlightjs.highlight(code, { language: language }).value; } return highlightjs.highlightAuto(code).value; } }); return marked.parse(text); }; // User Message Component with enhanced styling const UserMessage = ({ text }: { text: string }) => ( {text} You ); // Computer Message Component with Markdown support const ComputerMessage = ({ text }: { text: string }) => { const [copiedIndex, setCopiedIndex] = useState(null); const handleCopyCode = async (code: string, index: number) => { await Clipboard.setStringAsync(code); setCopiedIndex(index); // Add haptic feedback if (Platform.OS !== 'web') { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); } setTimeout(() => setCopiedIndex(null), 2000); }; const markdownStyles = { body: { color: 'white', fontSize: 16, lineHeight: 24, }, heading1: { fontWeight: 'bold', fontSize: 22, marginTop: 8, marginBottom: 4, color: 'white', }, heading2: { fontWeight: 'bold', fontSize: 20, marginTop: 8, marginBottom: 4, color: 'white', }, heading3: { fontWeight: 'bold', fontSize: 18, marginTop: 8, marginBottom: 4, color: 'white', }, link: { color: '#FF9D4F', textDecorationLine: "underline", }, blockquote: { borderLeftWidth: 3, borderLeftColor: '#FF7B00', paddingLeft: 10, fontStyle: 'italic', }, code_block: { backgroundColor: 'rgba(0, 0, 0, 0.3)', padding: 10, borderRadius: 5, fontFamily: 'monospace', fontSize: 14, }, code_inline: { backgroundColor: 'rgba(0, 0, 0, 0.3)', padding: 4, borderRadius: 3, fontFamily: 'monospace', fontSize: 14, }, list_item: { marginBottom: 6, }, }; // Custom renderer for code blocks const renderCodeBlock = (props: { content: string; language?: string; index: number }) => { const { content, language } = props; return ( {language || 'code'} handleCopyCode(content, props.index)} style={{ backgroundColor: 'transparent', padding: 4, borderRadius: 4, }}> {content} ); }; return ( { return renderCodeBlock({ content: node.content, language: (node as any).language, index: parseInt(node.key, 10), }); } }} > {text} Fille AI ); }; // Enhanced Loading indicator with animation const LoadingIndicator = () => { const [dotIndex, setDotIndex] = useState(0); useEffect(() => { const interval = setInterval(() => { setDotIndex(prev => (prev + 1) % 4); }, 300); return () => clearInterval(interval); }, []); return ( {[0, 1, 2].map((i) => ( ))} ); }; // Suggestion bubble component const SuggestionBubble = ({ text }: { text: string }) => ( { // Add haptic feedback if (Platform.OS !== 'web') { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); } setInputText(text); handleSubmit(text); }} > {text} ); // Get server URL based on environment const getServerUrl = () => { if (Platform.OS === 'android') { // Special IP that Android emulator uses to access host machine return "http://10.0.2.2:8000/chat"; } else if (Platform.OS === 'ios') { return "http://localhost:8000/chat"; } else { // For web or other platforms return "http://localhost:8000/chat"; } }; // Handler for chat submit const handleSubmit = async (text = inputText) => { const messageToSend = text.trim(); if (messageToSend === '' || isLoading) return; // Add haptic feedback on send if (Platform.OS !== 'web') { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); } // Add user message to chat const userMessage = messageToSend; setMessages(prevMessages => [...prevMessages, { type: 'user', text: userMessage }]); setInputText(''); setInputHeight(40); // If this is the first message, transition the UI if (!isSearchSubmitted) { Animated.parallel([ Animated.timing(welcomeOpacity, { toValue: 0, duration: 300, useNativeDriver: true, }), Animated.timing(centerContentMargin, { toValue: 20, duration: 500, useNativeDriver: false, }), ]).start(() => { setIsSearchSubmitted(true); }); } // Set loading state setIsLoading(true); try { const serverUrl = getServerUrl(); console.log(`Sending request to: ${serverUrl}`); // Send request to server const response = await fetch(serverUrl, { method: "POST", headers: { "Content-Type": "application/json", "Accept": "application/json", }, body: JSON.stringify({ message: userMessage }), }); if (!response.ok) { throw new Error(`Failed to get response: ${response.status}`); } const data = await response.json(); // Add AI response to chat const aiResponse = typeof data.response === 'string' ? data.response : JSON.stringify(data.response); // Add haptic feedback when response arrives if (Platform.OS !== 'web') { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); } setMessages(prevMessages => [...prevMessages, { type: 'computer', text: aiResponse }]); // Update suggested questions based on conversation context updateSuggestedQuestions(userMessage, aiResponse); } catch (error) { console.error('Error:', error); // Add haptic feedback for error if (Platform.OS !== 'web') { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); } Alert.alert( "Connection Error", `Failed to connect to server: ${(error as Error).message}\n\nMake sure your server is running and the device can reach it.`, [{ text: "OK" }] ); setMessages(prevMessages => [ ...prevMessages, { type: 'computer', text: 'Sorry, there was an error processing your request. Please check your network connection and make sure the server is running.' }, ]); } finally { setIsLoading(false); } }; // Update suggested questions based on conversation const updateSuggestedQuestions = (userMessage: string, aiResponse: string) => { // This is a simplified approach. For production, you might want to use the AI to generate these. const periodRelated = userMessage.toLowerCase().includes('period') || aiResponse.toLowerCase().includes('period'); const painRelated = userMessage.toLowerCase().includes('pain') || aiResponse.toLowerCase().includes('pain'); const pregnancyRelated = userMessage.toLowerCase().includes('pregnan') || aiResponse.toLowerCase().includes('pregnan'); const hormoneRelated = userMessage.toLowerCase().includes('hormone') || aiResponse.toLowerCase().includes('hormone'); let newSuggestions = [...SUGGESTED_QUESTIONS]; if (periodRelated) { newSuggestions = [ "What causes irregular periods?", "How can I track my menstrual cycle?", "When should I be concerned about heavy flow?", ]; } else if (painRelated) { newSuggestions = [ "What are natural remedies for cramps?", "Should I see a doctor about period pain?", "How can exercise help with menstrual pain?", ]; } else if (pregnancyRelated) { newSuggestions = [ "What prenatal vitamins should I take?", "How does ovulation tracking work?", "What are early signs of pregnancy?", ]; } else if (hormoneRelated) { newSuggestions = [ "How do hormones affect mood?", "What foods help balance hormones?", "How does stress impact hormonal health?", ]; } // Shuffle the array to get different suggestions each time newSuggestions.sort(() => Math.random() - 0.5); // Take only the first 3 setSuggestedQuestions(newSuggestions.slice(0, 3)); }; // Scroll to bottom when messages change useEffect(() => { if (scrollViewRef.current && messages.length > 0) { setTimeout(() => { if (scrollViewRef.current) { scrollViewRef.current.scrollToEnd({ animated: true }); } }, 100); } }, [messages]); return ( <> {/* Add Stack.Screen options to hide the header */} {/* Navbar */} FILLE AI { if (Platform.OS !== 'web') { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); } router.push("/realchat"); }} > Talk to Doctor {/* Main Content */} {/* Center Content */} {/* Welcome Message */} {!isSearchSubmitted && ( Hello, GIRL! I'm your health companion. Ready to share your problems and feelings today? {/* Suggested Topics */} Try asking about: {suggestedQuestions.map((question, index) => ( ))} )} {/* Conversation Container */} {messages.map((message, index) => ( (message as { type: string; text: string }).type === 'user' ? ( ) : ( ) ))} {isLoading && } {/* Suggestions row (visible only after first message) */} {isSearchSubmitted && !isLoading && messages.length > 0 && ( {suggestedQuestions.map((question, index) => ( ))} )} {/* Input Container */} updateInputHeight(e.nativeEvent.contentSize.height) } /> handleSubmit()} disabled={isLoading || inputText.trim() === ''} style={{ padding: 10, backgroundColor: (isLoading || inputText.trim() === '') ? '#444' : '#FF7B00', borderRadius: 20, width: 40, height: 40, justifyContent: 'center', alignItems: 'center', marginBottom: 6, }} > {/* Footer */} For informational purposes. Consult a healthcare professional. ); }; export default FilleAI;