import React, { useState, useRef } from 'react'; import { View, Text, ScrollView, TextInput, Pressable, StyleSheet, KeyboardAvoidingView, Platform, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useRouter } from 'expo-router'; import { LinearGradient } from 'expo-linear-gradient'; import { X, ArrowUp, Cpu } from 'lucide-react-native'; import Animated, { FadeIn, SlideInDown } from 'react-native-reanimated'; import { Colors, Fonts, FontSize, BorderRadius, Spacing } from '@/constants/theme'; import { chatSuggestions } from '@/constants/mockData'; import PressableScale from '@/components/PressableScale'; import { useMobileAuth } from '@/providers/MobileAuthProvider'; import { mobileRuntime } from '@/lib/runtime'; import { createRequestId } from '../../shared/request-id.js'; interface ChatMessage { id: string; role: 'user' | 'bot'; text: string; } function MessageBubble({ message }: { message: ChatMessage }) { const isBot = message.role === 'bot'; return ( {isBot && ( )} {message.text} ); } export default function ChatScreen() { const insets = useSafeAreaInsets(); const router = useRouter(); const { accessToken } = useMobileAuth(); const [messages, setMessages] = useState([]); const [inputText, setInputText] = useState(''); const [sending, setSending] = useState(false); const scrollRef = useRef(null); const sendMessage = async (text: string) => { if (!text.trim() || sending) return; const userMsg: ChatMessage = { id: `user-${Date.now()}`, role: 'user', text: text.trim() }; setMessages(prev => [...prev, userMsg]); setInputText(''); setSending(true); try { const res = await fetch(`${mobileRuntime.tradingApiUrl}/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${accessToken}`, 'x-request-id': createRequestId('mobile-chat'), }, body: JSON.stringify({ message: text.trim(), context: [] }), }); if (!res.ok) throw new Error(`Chat request failed (${res.status})`); const body = await res.json(); const reply = body.summary || body.message || body.response || 'No response from assistant.'; const botMsg: ChatMessage = { id: `bot-${Date.now()}`, role: 'bot', text: reply }; setMessages(prev => [...prev, botMsg]); } catch (e) { const errMsg: ChatMessage = { id: `bot-err-${Date.now()}`, role: 'bot', text: `Error: ${e instanceof Error ? e.message : 'Failed to reach assistant'}`, }; setMessages(prev => [...prev, errMsg]); } finally { setSending(false); } }; return ( Bytelyst AI Trading Assistant router.back()}> scrollRef.current?.scrollToEnd({ animated: true })} > {messages.length === 0 && ( Bytelyst AI Ask about your strategies, market conditions, or get trade recommendations. )} {messages.map((msg) => ( ))} {messages.length === 0 && ( {chatSuggestions.map((s) => ( void sendMessage(s)} > {s} ))} )} void sendMessage(inputText)} selectionColor={Colors.accent.green} editable={!sending} /> void sendMessage(inputText)} > ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: Colors.background.primary, }, flex: { flex: 1, }, header: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 18, paddingVertical: 14, borderBottomWidth: 1, borderBottomColor: Colors.border.default, gap: 12, }, headerAvatar: { width: 40, height: 40, borderRadius: 12, backgroundColor: 'rgba(0,255,136,0.1)', borderWidth: 1, borderColor: 'rgba(0,255,136,0.2)', alignItems: 'center', justifyContent: 'center', }, headerInfo: { flex: 1, }, headerTitle: { fontFamily: Fonts.inter.extraBold, fontSize: FontSize.body, color: Colors.text.primary, }, headerSubtitle: { fontFamily: Fonts.inter.medium, fontSize: FontSize.micro, color: Colors.text.secondary, }, closeBtn: { width: 36, height: 36, borderRadius: 10, backgroundColor: 'rgba(255,255,255,0.05)', alignItems: 'center', justifyContent: 'center', }, messages: { flex: 1, }, messagesContent: { padding: Spacing.screenPadding, gap: 14, paddingBottom: 20, }, welcomeMsg: { alignItems: 'center', gap: 12, paddingVertical: 32, paddingHorizontal: 24, }, welcomeTitle: { fontFamily: Fonts.inter.black, fontSize: FontSize.subheading, color: Colors.text.primary, }, welcomeText: { fontFamily: Fonts.inter.medium, fontSize: FontSize.body, color: Colors.text.secondary, textAlign: 'center', lineHeight: 22, }, msgRow: { flexDirection: 'row', gap: 10, maxWidth: '85%', }, msgRowBot: { alignSelf: 'flex-start', }, msgRowUser: { alignSelf: 'flex-end', }, botAvatar: { width: 30, height: 30, borderRadius: 10, backgroundColor: 'rgba(0,255,136,0.1)', alignItems: 'center', justifyContent: 'center', marginTop: 2, }, bubble: { padding: 14, paddingHorizontal: 16, borderRadius: 16, borderWidth: 1, flexShrink: 1, }, botBubble: { borderTopLeftRadius: 4, borderColor: 'rgba(255,255,255,0.06)', }, userBubble: { borderTopRightRadius: 4, borderColor: 'rgba(59,130,246,0.2)', }, msgText: { fontFamily: Fonts.inter.medium, fontSize: FontSize.body, lineHeight: 20, }, botText: { color: '#d4d4d8', }, userText: { color: '#93c5fd', }, suggestions: { flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginTop: 8, }, suggestionChip: { backgroundColor: 'rgba(255,255,255,0.05)', borderWidth: 1, borderColor: Colors.border.medium, borderRadius: 20, paddingHorizontal: 16, paddingVertical: 8, }, suggestionText: { fontFamily: Fonts.inter.medium, fontSize: FontSize.bodySmall, color: Colors.text.secondary, }, inputBar: { paddingHorizontal: Spacing.screenPadding, paddingTop: 12, borderTopWidth: 1, borderTopColor: Colors.border.default, backgroundColor: Colors.background.primary, }, inputContainer: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#161722', borderRadius: 16, borderWidth: 1, borderColor: 'rgba(255,255,255,0.12)', paddingLeft: 16, paddingRight: 6, gap: 8, }, input: { flex: 1, fontFamily: Fonts.inter.medium, fontSize: FontSize.body, color: Colors.text.primary, paddingVertical: 12, }, sendBtnWrapper: { borderRadius: 10, }, sendBtnDisabled: { opacity: 0.5, }, sendBtn: { width: 36, height: 36, borderRadius: 10, alignItems: 'center', justifyContent: 'center', }, });