From 5139bce8c60609cea53a8ad83a823ba382706927 Mon Sep 17 00:00:00 2001 From: jinyaqiu Date: Fri, 25 Jul 2025 19:45:10 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=E9=94=AE=E7=9B=98=E5=94=A4?= =?UTF-8?q?=E8=B5=B7bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(tabs)/ask.tsx | 23 ++++++++++++----------- components/ask/send.tsx | 24 ++++++++++++++---------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/app/(tabs)/ask.tsx b/app/(tabs)/ask.tsx index a50f7b2..d157744 100644 --- a/app/(tabs)/ask.tsx +++ b/app/(tabs)/ask.tsx @@ -3,13 +3,13 @@ import Chat from "@/components/ask/chat"; import AskHello from "@/components/ask/hello"; import SendMessage from "@/components/ask/send"; import { ThemedText } from "@/components/ThemedText"; -import { checkAuthStatus } from '@/lib/auth'; import { fetchApi } from "@/lib/server-api-util"; import { Message } from "@/types/ask"; import { router, useLocalSearchParams } from "expo-router"; import React, { useEffect, useRef, useState } from 'react'; import { Animated, + Keyboard, KeyboardAvoidingView, Platform, ScrollView, @@ -21,9 +21,7 @@ import { useSafeAreaInsets } from "react-native-safe-area-context"; export default function AskScreen() { const insets = useSafeAreaInsets(); - useEffect(() => { - checkAuthStatus(router); - }, []); + // 在组件内部添加 ref const scrollViewRef = useRef(null); const [isHello, setIsHello] = useState(true); @@ -56,11 +54,11 @@ export default function AskScreen() { setUserMessages(res); }); } - // if (newSession) { - // setIsHello(false); - // createNewConversation(); - // } - }, [sessionId]); + if (newSession) { + setIsHello(true); + setConversationId(null); + } + }, [sessionId, newSession]); // 动画效果 useEffect(() => { @@ -101,7 +99,10 @@ export default function AskScreen() { router.push('/memo-list')} + onPress={() => { + router.push('/memo-list') + Keyboard.dismiss(); + }} > @@ -148,7 +149,7 @@ export default function AskScreen() { {/* 输入框 */} - + { - console.log('Keyboard will show'); - setIsHello(false); - setUserMessages([{ - content: { - text: "快来寻找你的记忆吧。。。" - }, - role: 'Assistant', - timestamp: new Date().toISOString() - }]) + if (!conversationId) { + console.log('Keyboard will show'); + setIsHello(false); + setUserMessages([{ + content: { + text: "快来寻找你的记忆吧。。。" + }, + role: 'Assistant', + timestamp: new Date().toISOString() + }]) + } else { + console.log('Keyboard will show1213'); + } } ); return () => { keyboardWillShowListener.remove(); }; - }, []); + }, [conversationId]); return ( From c1a382474f8d3a60df6e123db250f1f1161f61af Mon Sep 17 00:00:00 2001 From: jinyaqiu Date: Fri, 25 Jul 2025 20:32:43 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=E6=BB=9A=E5=8A=A8=E6=9D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(tabs)/ask.tsx | 141 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 125 insertions(+), 16 deletions(-) diff --git a/app/(tabs)/ask.tsx b/app/(tabs)/ask.tsx index d157744..1c90adc 100644 --- a/app/(tabs)/ask.tsx +++ b/app/(tabs)/ask.tsx @@ -6,7 +6,7 @@ import { ThemedText } from "@/components/ThemedText"; import { fetchApi } from "@/lib/server-api-util"; import { Message } from "@/types/ask"; import { router, useLocalSearchParams } from "expo-router"; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import { Animated, Keyboard, @@ -14,6 +14,7 @@ import { Platform, ScrollView, StyleSheet, + TextInput, TouchableOpacity, View } from 'react-native'; @@ -38,14 +39,56 @@ export default function AskScreen() { newSession: string; }>(); - // 处理滚动到底部 - useEffect(() => { + // 处理滚动到底部 - 优化版本 + const scrollToBottom = useCallback((animated = true) => { if (scrollViewRef.current && !isHello) { - scrollViewRef.current.scrollToEnd({ animated: true }); + // 使用更长的延迟确保内容渲染完成 + setTimeout(() => { + scrollViewRef.current?.scrollToEnd({ animated }); + }, 150); } - }, [userMessages, isHello]); + }, [isHello]); - // 处理路由参数 + // 监听键盘显示/隐藏事件 - 增强版本 + useEffect(() => { + const keyboardDidShowListener = Keyboard.addListener( + 'keyboardDidShow', + (e) => { + // 键盘显示时滚动到底部 + setTimeout(() => { + scrollToBottom(true); + }, 100); + } + ); + + const keyboardDidHideListener = Keyboard.addListener( + 'keyboardDidHide', + () => { + // 键盘隐藏时也滚动到底部(可选) + setTimeout(() => { + scrollToBottom(false); + }, 100); + } + ); + + return () => { + keyboardDidShowListener.remove(); + keyboardDidHideListener.remove(); + }; + }, [scrollToBottom]); + + // 处理消息变化时滚动 - 优化版本 + useEffect(() => { + if (!isHello && userMessages.length > 0) { + // 消息变化时立即滚动到底部 + const timer = setTimeout(() => { + scrollToBottom(true); + }, 50); + return () => clearTimeout(timer); + } + }, [userMessages.length, isHello, scrollToBottom]); + + // 处理路由参数 - 优化版本 useEffect(() => { if (sessionId) { setConversationId(sessionId); @@ -81,17 +124,56 @@ export default function AskScreen() { Animated.parallel([ Animated.timing(fadeAnim, { toValue: 0, - duration: 1000, + duration: 300, useNativeDriver: true, }), Animated.timing(fadeAnimChat, { toValue: 1, - duration: 1000, + duration: 300, useNativeDriver: true, }) - ]).start(); + ]).start(() => { + // 动画完成后滚动到底部 + setTimeout(() => { + scrollToBottom(false); + }, 50); + }); } - }, [isHello, fadeAnim, fadeAnimChat]); + }, [isHello, fadeAnim, fadeAnimChat, scrollToBottom]); + + // 页面进入时的处理 - 新增 + useEffect(() => { + if (!isHello) { + // 页面完全加载后滚动到底部 + const timer = setTimeout(() => { + scrollToBottom(false); + }, 300); + return () => clearTimeout(timer); + } + }, [isHello, scrollToBottom]); + + // 新增:页面获得焦点时处理 + useEffect(() => { + // 页面进入时确保键盘关闭并滚动到底部 + const timer = setTimeout(() => { + if (!isHello) { + // 让所有输入框失去焦点 + try { + if (TextInput.State?.currentlyFocusedInput) { + const input = TextInput.State.currentlyFocusedInput(); + if (input) input.blur(); + } + } catch (error) { + console.log('失去焦点失败:', error); + } + + // 滚动到底部 + scrollToBottom(false); + } + }, 200); + + return () => clearTimeout(timer); + }, [isHello, scrollToBottom]); return ( @@ -100,8 +182,17 @@ export default function AskScreen() { { - router.push('/memo-list') + // 确保关闭键盘 + try { + if (TextInput.State?.currentlyFocusedInput) { + const input = TextInput.State.currentlyFocusedInput(); + if (input) input.blur(); + } + } catch (error) { + console.log('失去焦点失败:', error); + } Keyboard.dismiss(); + router.push('/memo-list'); }} > @@ -113,7 +204,6 @@ export default function AskScreen() { @@ -138,13 +228,27 @@ export default function AskScreen() { styles.absoluteView, { opacity: fadeAnimChat, - // 使用 pointerEvents 控制交互 pointerEvents: isHello ? 'none' : 'auto', zIndex: 0 } ]} > - + scrollToBottom(true)} + > + + @@ -210,16 +314,21 @@ const styles = StyleSheet.create({ contentContainer: { flex: 1, justifyContent: 'center', - paddingBottom: 20, }, absoluteView: { - position: 'absolute', // 保持绝对定位 + position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'white', }, + chatContainer: { + flex: 1, + }, + chatContentContainer: { + paddingBottom: 20, + }, inputContainer: { padding: 16, paddingBottom: 24, From 1584ed7fadf0044a3c85676f7f8a7464f458e6e7 Mon Sep 17 00:00:00 2001 From: jinyaqiu Date: Mon, 28 Jul 2025 11:51:10 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20chat=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/(tabs)/ask.tsx | 182 ++++++++++++++++------------------------ components/ask/chat.tsx | 51 ++++------- 2 files changed, 89 insertions(+), 144 deletions(-) diff --git a/app/(tabs)/ask.tsx b/app/(tabs)/ask.tsx index 1c90adc..164e9bc 100644 --- a/app/(tabs)/ask.tsx +++ b/app/(tabs)/ask.tsx @@ -9,10 +9,10 @@ import { router, useLocalSearchParams } from "expo-router"; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { Animated, + FlatList, Keyboard, KeyboardAvoidingView, Platform, - ScrollView, StyleSheet, TextInput, TouchableOpacity, @@ -23,14 +23,11 @@ import { useSafeAreaInsets } from "react-native-safe-area-context"; export default function AskScreen() { const insets = useSafeAreaInsets(); - // 在组件内部添加 ref - const scrollViewRef = useRef(null); + const chatListRef = useRef(null); const [isHello, setIsHello] = useState(true); const [conversationId, setConversationId] = useState(null); const [userMessages, setUserMessages] = useState([]); - // 选择图片 const [selectedImages, setSelectedImages] = useState([]); - // 动画值 const fadeAnim = useRef(new Animated.Value(1)).current; const fadeAnimChat = useRef(new Animated.Value(0)).current; @@ -39,24 +36,27 @@ export default function AskScreen() { newSession: string; }>(); - // 处理滚动到底部 - 优化版本 - const scrollToBottom = useCallback((animated = true) => { - if (scrollViewRef.current && !isHello) { - // 使用更长的延迟确保内容渲染完成 - setTimeout(() => { - scrollViewRef.current?.scrollToEnd({ animated }); - }, 150); + // 创建一个可复用的滚动函数 + const scrollToEnd = useCallback((animated = true) => { + if (chatListRef.current) { + setTimeout(() => chatListRef.current?.scrollToEnd({ animated }), 100); } - }, [isHello]); + }, []); + + useEffect(() => { + if (!isHello && userMessages.length > 0) { + scrollToEnd(); + } + }, [userMessages, isHello, scrollToEnd]); - // 监听键盘显示/隐藏事件 - 增强版本 useEffect(() => { const keyboardDidShowListener = Keyboard.addListener( 'keyboardDidShow', (e) => { - // 键盘显示时滚动到底部 setTimeout(() => { - scrollToBottom(true); + if (!isHello) { + scrollToEnd(); + } }, 100); } ); @@ -64,9 +64,10 @@ export default function AskScreen() { const keyboardDidHideListener = Keyboard.addListener( 'keyboardDidHide', () => { - // 键盘隐藏时也滚动到底部(可选) setTimeout(() => { - scrollToBottom(false); + if (!isHello) { + scrollToEnd(false); + } }, 100); } ); @@ -75,20 +76,8 @@ export default function AskScreen() { keyboardDidShowListener.remove(); keyboardDidHideListener.remove(); }; - }, [scrollToBottom]); + }, [isHello]); - // 处理消息变化时滚动 - 优化版本 - useEffect(() => { - if (!isHello && userMessages.length > 0) { - // 消息变化时立即滚动到底部 - const timer = setTimeout(() => { - scrollToBottom(true); - }, 50); - return () => clearTimeout(timer); - } - }, [userMessages.length, isHello, scrollToBottom]); - - // 处理路由参数 - 优化版本 useEffect(() => { if (sessionId) { setConversationId(sessionId); @@ -103,10 +92,8 @@ export default function AskScreen() { } }, [sessionId, newSession]); - // 动画效果 useEffect(() => { if (isHello) { - // 显示欢迎页,隐藏聊天页 Animated.parallel([ Animated.timing(fadeAnim, { toValue: 1, @@ -120,7 +107,6 @@ export default function AskScreen() { }) ]).start(); } else { - // 显示聊天页,隐藏欢迎页 Animated.parallel([ Animated.timing(fadeAnim, { toValue: 0, @@ -133,31 +119,27 @@ export default function AskScreen() { useNativeDriver: true, }) ]).start(() => { - // 动画完成后滚动到底部 setTimeout(() => { - scrollToBottom(false); + if (!isHello) { + scrollToEnd(false); + } }, 50); }); } - }, [isHello, fadeAnim, fadeAnimChat, scrollToBottom]); + }, [isHello, fadeAnim, fadeAnimChat]); - // 页面进入时的处理 - 新增 useEffect(() => { if (!isHello) { - // 页面完全加载后滚动到底部 const timer = setTimeout(() => { - scrollToBottom(false); + scrollToEnd(false); }, 300); return () => clearTimeout(timer); } - }, [isHello, scrollToBottom]); + }, [isHello]); - // 新增:页面获得焦点时处理 useEffect(() => { - // 页面进入时确保键盘关闭并滚动到底部 const timer = setTimeout(() => { if (!isHello) { - // 让所有输入框失去焦点 try { if (TextInput.State?.currentlyFocusedInput) { const input = TextInput.State.currentlyFocusedInput(); @@ -167,13 +149,12 @@ export default function AskScreen() { console.log('失去焦点失败:', error); } - // 滚动到底部 - scrollToBottom(false); + scrollToEnd(false); } }, 200); return () => clearTimeout(timer); - }, [isHello, scrollToBottom]); + }, [isHello]); return ( @@ -182,7 +163,6 @@ export default function AskScreen() { { - // 确保关闭键盘 try { if (TextInput.State?.currentlyFocusedInput) { const input = TextInput.State.currentlyFocusedInput(); @@ -201,64 +181,56 @@ export default function AskScreen() { + + {/* 欢迎页面 */} + + + + + {/* 聊天页面 */} + + scrollToEnd()} + /> + + + + {/* 输入框区域 */} - - {/* 欢迎页面 */} - - - - - {/* 聊天页面 */} - - scrollToBottom(true)} - > - - - - - - {/* 输入框 */} + keyboardVerticalOffset={0} > @@ -271,7 +243,7 @@ export default function AskScreen() { const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: 'white', + backgroundColor: '#f8f8f8', }, navbar: { flexDirection: 'row', @@ -280,10 +252,8 @@ const styles = StyleSheet.create({ paddingVertical: 16, paddingHorizontal: 16, backgroundColor: 'white', - // 使用 border 替代阴影 borderBottomWidth: 1, borderBottomColor: 'rgba(0,0,0,0.1)', - // 如果需要更柔和的边缘,可以添加一个微妙的阴影 elevation: 1, // Android shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, @@ -307,13 +277,9 @@ const styles = StyleSheet.create({ placeholder: { width: 40, }, - // 更新 keyboardAvoidingView 和 contentContainer 样式 - keyboardAvoidingView: { - flex: 1, - }, contentContainer: { flex: 1, - justifyContent: 'center', + position: 'relative', }, absoluteView: { position: 'absolute', diff --git a/components/ask/chat.tsx b/components/ask/chat.tsx index 8fd14a0..9a65dc5 100644 --- a/components/ask/chat.tsx +++ b/components/ask/chat.tsx @@ -1,76 +1,55 @@ import { Message, Video } from '@/types/ask'; import { MaterialItem } from '@/types/personal-info'; -import React, { Dispatch, memo, SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { Dispatch, ForwardedRef, forwardRef, memo, SetStateAction, useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { FlatList, + FlatListProps, SafeAreaView } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import MessageItem from './aiChat'; -interface ChatProps { +// 继承 FlatListProps 来接收所有 FlatList 的属性 +interface ChatProps extends Omit, 'data' | 'renderItem'> { userMessages: Message[]; sessionId: string; setSelectedImages: Dispatch>; selectedImages: string[]; } -function ChatComponent({ userMessages, sessionId, setSelectedImages, selectedImages }: ChatProps) { - const flatListRef = useRef(null); +function ChatComponent( + { userMessages, sessionId, setSelectedImages, selectedImages, ...restProps }: ChatProps, + ref: ForwardedRef> +) { const insets = useSafeAreaInsets(); const [modalVisible, setModalVisible] = React.useState({ visible: false, data: {} as Video | MaterialItem }); const { t } = useTranslation(); - // 使用 useCallback 缓存 keyExtractor 函数 const keyExtractor = useCallback((item: Message) => `${item.role}-${item.timestamp}`, []); - // 使用 useMemo 缓存样式对象 - const contentContainerStyle = useMemo(() => ({ padding: 16 }), []); + const contentContainerStyle = useMemo(() => ({ padding: 16, flexGrow: 1 }), []); - // 详情弹窗 const [modalDetailsVisible, setModalDetailsVisible] = useState(false); - // 自动滚动到底部 - useEffect(() => { - if (userMessages.length > 0) { - // 延迟滚动以确保渲染完成 - const timer = setTimeout(() => { - flatListRef.current?.scrollToEnd({ animated: true }); - }, 150); - return () => clearTimeout(timer); - } - }, [userMessages]); - - // 优化 FlatList 性能 - 提供 getItemLayout 方法 - const getItemLayout = useCallback((data: Message[] | null | undefined, index: number) => { - // 假设每个消息项的高度大约为 100(可根据实际情况调整) - const averageItemHeight = 100; - return { - length: averageItemHeight, - offset: averageItemHeight * index, - index, - }; - }, []); - return ( - + MessageItem({ t, setSelectedImages, selectedImages, insets, item, sessionId, modalVisible, setModalVisible, setModalDetailsVisible, modalDetailsVisible })} contentContainerStyle={contentContainerStyle} keyboardDismissMode="interactive" + keyboardShouldPersistTaps="handled" removeClippedSubviews={true} maxToRenderPerBatch={10} updateCellsBatchingPeriod={50} initialNumToRender={10} windowSize={11} - getItemLayout={getItemLayout} - renderItem={({ item }) => MessageItem({ t, setSelectedImages, selectedImages, insets, item, sessionId, modalVisible, setModalVisible, setModalDetailsVisible, modalDetailsVisible })} + {...restProps} // 将所有其他属性传递给 FlatList /> ); } -// 使用 React.memo 包装组件,避免不必要的重渲染 -export default memo(ChatComponent); \ No newline at end of file +export default memo(forwardRef(ChatComponent)); \ No newline at end of file