import React, { useState, useMemo, useRef, useEffect } from 'react'; import { View, ScrollView, TextInput, KeyboardAvoidingView, Platform, TouchableOpacity } from 'react-native'; import RNSSE from 'react-native-sse'; import { useTheme } from '@/hooks/useTheme'; import { Screen } from '@/components/Screen'; import { ThemedText } from '@/components/ThemedText'; import { ThemedView } from '@/components/ThemedView'; import { FontAwesome6 } from '@expo/vector-icons'; import { createStyles } from './styles'; interface Message { role: 'user' | 'assistant'; content: string; timestamp: number; } export default function ChatScreen() { const { theme, isDark } = useTheme(); const styles = useMemo(() => createStyles(theme), [theme]); const [messages, setMessages] = useState(() => [{ role: 'assistant', content: '你好!我是你的健康饮食和减脂顾问助手。我可以帮助你:\n\n• 制定科学的饮食计划\n• 分析食物营养成分\n• 提供减脂建议\n• 解答健康问题\n\n请问有什么可以帮助你的吗?', timestamp: Date.now(), }]); const [inputText, setInputText] = useState(''); const [loading, setLoading] = useState(false); const scrollViewRef = useRef(null); const sseRef = useRef(null); useEffect(() => { return () => { if (sseRef.current) { sseRef.current.close(); } }; }, []); const sendMessage = async () => { if (!inputText.trim() || loading) return; const userMessage: Message = { role: 'user', content: inputText.trim(), timestamp: Date.now(), }; setMessages((prev) => [...prev, userMessage]); setInputText(''); setLoading(true); // 构建对话历史 const conversationHistory = messages .slice(1) // 跳过欢迎消息 .map((msg) => ({ role: msg.role, content: msg.content })); try { /** * 服务端文件:server/src/routes/ai-chat.ts * 接口:POST /api/v1/ai-chat/chat * Body 参数:message: string, conversationHistory?: Array<{role: string, content: string}> */ const url = `${process.env.EXPO_PUBLIC_BACKEND_BASE_URL}/api/v1/ai-chat/chat`; const sse = new RNSSE(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: inputText.trim(), conversationHistory, }), }); sseRef.current = sse; let assistantContent = ''; const assistantMessage: Message = { role: 'assistant', content: '', timestamp: Date.now(), }; // 添加空的助手消息 setMessages((prev) => [...prev, assistantMessage]); sse.addEventListener('message', (event: any) => { if (event.data === '[DONE]') { sse.close(); setLoading(false); return; } try { const data = JSON.parse(event.data); if (data.content) { assistantContent += data.content; setMessages((prev) => { const updated = [...prev]; updated[updated.length - 1] = { ...updated[updated.length - 1], content: assistantContent, }; return updated; }); } } catch (e) { console.error('Parse error:', e); } }); sse.addEventListener('error', (error: any) => { console.error('SSE error:', error); sse.close(); setLoading(false); setMessages((prev) => { const updated = [...prev]; updated[updated.length - 1] = { ...updated[updated.length - 1], content: assistantContent || '抱歉,我遇到了一些问题,请稍后再试。', }; return updated; }); }); } catch (error) { console.error('Send message error:', error); setLoading(false); setMessages((prev) => [ ...prev, { role: 'assistant', content: '抱歉,发送消息失败,请检查网络连接。', timestamp: Date.now(), }, ]); } }; return ( AI 健康助手 {loading ? '正在回复...' : '在线'} { scrollViewRef.current?.scrollToEnd({ animated: true }); }} > {messages.map((message, index) => ( {message.content} {new Date(message.timestamp).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', })} ))} {loading && messages[messages.length - 1]?.role === 'assistant' && ( )} ); }