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',
},
});