363 lines
12 KiB
TypeScript
363 lines
12 KiB
TypeScript
import ReturnArrow from "@/assets/icons/svg/returnArrow.svg";
|
||
import Chat from "@/components/ask/chat";
|
||
import AskHello from "@/components/ask/hello";
|
||
import { ThemedText } from "@/components/ThemedText";
|
||
import { useWebSocketStreamHandler } from "@/hooks/useWebSocketStreamHandler";
|
||
import { fetchApi } from "@/lib/server-api-util";
|
||
import { Message } from "@/types/ask";
|
||
import { useLocalSearchParams, useRouter } from "expo-router";
|
||
import React, { useEffect, useRef, useState } from 'react';
|
||
import { useTranslation } from "react-i18next";
|
||
import {
|
||
Animated,
|
||
FlatList,
|
||
Keyboard,
|
||
StyleSheet,
|
||
TextInput,
|
||
TouchableOpacity,
|
||
View
|
||
} from 'react-native';
|
||
import { useSharedValue } from 'react-native-reanimated';
|
||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||
|
||
export default function AskScreen() {
|
||
const router = useRouter();
|
||
const insets = useSafeAreaInsets();
|
||
|
||
const chatListRef = useRef<FlatList>(null);
|
||
const isMountedRef = useRef(true);
|
||
const scrollTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||
const keyboardTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||
const abortControllerRef = useRef<AbortController | null>(null);
|
||
|
||
const [isHello, setIsHello] = useState(true);
|
||
const [conversationId, setConversationId] = useState<string | null>(null);
|
||
const [userMessages, setUserMessages] = useState<Message[]>([]);
|
||
const [selectedImages, setSelectedImages] = useState<string[]>([]);
|
||
const fadeAnim = useSharedValue(1);
|
||
const fadeAnimChat = useSharedValue(0);
|
||
const { t } = useTranslation();
|
||
|
||
const { sessionId, newSession } = useLocalSearchParams<{
|
||
sessionId: string;
|
||
newSession: string;
|
||
}>();
|
||
|
||
// 创建一个安全的滚动函数
|
||
// const scrollToEnd = useCallback((animated = true) => {
|
||
// if (!isMountedRef.current || !chatListRef.current) return;
|
||
|
||
// // 清理之前的定时器
|
||
// if (scrollTimeoutRef.current) {
|
||
// clearTimeout(scrollTimeoutRef.current);
|
||
// }
|
||
|
||
// scrollTimeoutRef.current = setTimeout(() => {
|
||
// if (isMountedRef.current && chatListRef.current) {
|
||
// try {
|
||
// chatListRef.current.scrollToEnd({ animated });
|
||
// } catch (error) {
|
||
// console.warn('滚动到底部失败:', error);
|
||
// }
|
||
// }
|
||
// }, 100);
|
||
// }, []);
|
||
|
||
// 清理函数
|
||
// const cleanup = useCallback(() => {
|
||
// isMountedRef.current = false;
|
||
|
||
// // 清理定时器
|
||
// if (scrollTimeoutRef.current) {
|
||
// clearTimeout(scrollTimeoutRef.current);
|
||
// scrollTimeoutRef.current = null;
|
||
// }
|
||
// if (keyboardTimeoutRef.current) {
|
||
// clearTimeout(keyboardTimeoutRef.current);
|
||
// keyboardTimeoutRef.current = null;
|
||
// }
|
||
|
||
// // 取消API请求
|
||
// if (abortControllerRef.current) {
|
||
// abortControllerRef.current.abort();
|
||
// abortControllerRef.current = null;
|
||
// }
|
||
// }, []);
|
||
|
||
// useEffect(() => {
|
||
// if (!isHello && userMessages.length > 0 && isMountedRef.current) {
|
||
// scrollToEnd();
|
||
// }
|
||
// }, [userMessages, isHello, scrollToEnd]);
|
||
|
||
// useEffect(() => {
|
||
// const keyboardDidShowListener = Keyboard.addListener(
|
||
// 'keyboardDidShow',
|
||
// (e) => {
|
||
// if (keyboardTimeoutRef.current) {
|
||
// clearTimeout(keyboardTimeoutRef.current);
|
||
// }
|
||
|
||
// keyboardTimeoutRef.current = setTimeout(() => {
|
||
// if (isMountedRef.current && !isHello) {
|
||
// scrollToEnd();
|
||
// }
|
||
// }, 100);
|
||
// }
|
||
// );
|
||
|
||
// const keyboardDidHideListener = Keyboard.addListener(
|
||
// 'keyboardDidHide',
|
||
// () => {
|
||
// if (keyboardTimeoutRef.current) {
|
||
// clearTimeout(keyboardTimeoutRef.current);
|
||
// }
|
||
|
||
// keyboardTimeoutRef.current = setTimeout(() => {
|
||
// if (isMountedRef.current && !isHello) {
|
||
// scrollToEnd(false);
|
||
// }
|
||
// }, 100);
|
||
// }
|
||
// );
|
||
|
||
// return () => {
|
||
// keyboardDidShowListener.remove();
|
||
// keyboardDidHideListener.remove();
|
||
// if (keyboardTimeoutRef.current) {
|
||
// clearTimeout(keyboardTimeoutRef.current);
|
||
// }
|
||
// };
|
||
// }, [isHello, scrollToEnd]);
|
||
|
||
// 使用新的WebSocket流处理hook,使用实时模式
|
||
const { subscribeToWebSocket } = useWebSocketStreamHandler({
|
||
setUserMessages,
|
||
isMounted: true, // 传递静态值,hook内部会使用ref跟踪
|
||
enableBatching: false // AskScreen使用实时模式
|
||
});
|
||
|
||
// useFocusEffect(
|
||
// useCallback(() => {
|
||
// isMountedRef.current = true;
|
||
|
||
// // 订阅WebSocket消息
|
||
// const unsubscribe = subscribeToWebSocket();
|
||
|
||
// return () => {
|
||
// // 取消订阅和执行清理
|
||
// unsubscribe();
|
||
// cleanup();
|
||
// };
|
||
// }, [subscribeToWebSocket, cleanup])
|
||
// );
|
||
|
||
// 创建动画样式
|
||
// const welcomeStyle = useAnimatedStyle(() => {
|
||
// return {
|
||
// opacity: fadeAnim.value,
|
||
// pointerEvents: isHello ? 'auto' : 'none',
|
||
// };
|
||
// });
|
||
|
||
// const chatStyle = useAnimatedStyle(() => {
|
||
// return {
|
||
// opacity: fadeAnimChat.value,
|
||
// pointerEvents: isHello ? 'none' : 'auto',
|
||
// };
|
||
// });
|
||
|
||
// 触发动画
|
||
// useEffect(() => {
|
||
// fadeAnim.value = withTiming(isHello ? 1 : 0, { duration: 300 });
|
||
// fadeAnimChat.value = withTiming(isHello ? 0 : 1, { duration: 300 });
|
||
// }, [isHello]);
|
||
|
||
useEffect(() => {
|
||
if (sessionId && isMountedRef.current) {
|
||
setConversationId(sessionId);
|
||
setIsHello(false);
|
||
|
||
// 创建新的AbortController
|
||
abortControllerRef.current = new AbortController();
|
||
|
||
fetchApi<Message[]>(`/chats/${sessionId}/message-history`, {
|
||
signal: abortControllerRef.current.signal
|
||
}).then((res) => {
|
||
if (isMountedRef.current) {
|
||
console.log("isMountedRef.current", isMountedRef.current)
|
||
setUserMessages(res);
|
||
}
|
||
}).catch((error) => {
|
||
if (error.name !== 'AbortError') {
|
||
console.error('获取消息历史失败:', error);
|
||
}
|
||
});
|
||
}
|
||
if (newSession && isMountedRef.current) {
|
||
setIsHello(true);
|
||
setConversationId(null);
|
||
}
|
||
}, [sessionId, newSession]);
|
||
|
||
// useEffect(() => {
|
||
// if (!isHello && isMountedRef.current) {
|
||
// // 不再自动关闭键盘,让用户手动控制
|
||
// // 这里可以添加其他需要在隐藏hello界面时执行的逻辑
|
||
// scrollToEnd(false);
|
||
// }
|
||
// }, [isHello, scrollToEnd]);
|
||
|
||
// useFocusEffect(
|
||
// useCallback(() => {
|
||
// if (!sessionId && isMountedRef.current) {
|
||
// setIsHello(true);
|
||
// setUserMessages([]);
|
||
// }
|
||
// }, [sessionId])
|
||
// );
|
||
|
||
// 组件卸载时的清理
|
||
// useEffect(() => {
|
||
// return () => {
|
||
// cleanup();
|
||
// };
|
||
// }, [cleanup]);
|
||
|
||
return (
|
||
<View style={[styles.container, { paddingTop: insets.top, paddingBottom: insets.bottom }]}>
|
||
{/* 导航栏 */}
|
||
<View style={[styles.navbar, isHello && styles.hiddenNavbar]}>
|
||
<TouchableOpacity
|
||
style={styles.backButton}
|
||
onPress={() => {
|
||
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');
|
||
}}
|
||
>
|
||
<ReturnArrow />
|
||
</TouchableOpacity>
|
||
<ThemedText style={styles.title} onPress={() => { router.push('/owner') }}>MemoWake</ThemedText>
|
||
<View style={styles.placeholder} />
|
||
</View>
|
||
|
||
<View style={styles.contentContainer}>
|
||
{/* 欢迎页面 */}
|
||
<Animated.View
|
||
style={[styles.absoluteView,
|
||
// welcomeStyle,
|
||
{ zIndex: 1 }]}
|
||
>
|
||
<AskHello setUserMessages={setUserMessages} setConversationId={setConversationId} setIsHello={setIsHello} />
|
||
</Animated.View>
|
||
|
||
{/* 聊天页面 */}
|
||
<Animated.View
|
||
style={[styles.absoluteView,
|
||
// chatStyle,
|
||
{ zIndex: 0 }]}
|
||
>
|
||
<Chat
|
||
ref={chatListRef}
|
||
userMessages={userMessages}
|
||
sessionId={sessionId}
|
||
setSelectedImages={setSelectedImages}
|
||
selectedImages={selectedImages}
|
||
style={styles.chatContainer}
|
||
contentContainerStyle={styles.chatContentContainer}
|
||
showsVerticalScrollIndicator={false}
|
||
// onContentSizeChange={() => scrollToEnd()}
|
||
/>
|
||
</Animated.View>
|
||
</View>
|
||
|
||
{/* 输入框区域 */}
|
||
{/* <KeyboardAvoidingView
|
||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||
keyboardVerticalOffset={0} >
|
||
<View style={styles.inputContainer} key={conversationId}>
|
||
<SendMessage
|
||
setIsHello={setIsHello}
|
||
conversationId={conversationId}
|
||
setConversationId={setConversationId}
|
||
setUserMessages={setUserMessages}
|
||
selectedImages={selectedImages}
|
||
setSelectedImages={setSelectedImages}
|
||
/>
|
||
</View>
|
||
</KeyboardAvoidingView> */}
|
||
</View>
|
||
);
|
||
}
|
||
|
||
const styles = StyleSheet.create({
|
||
container: {
|
||
flex: 1,
|
||
backgroundColor: '#fff',
|
||
},
|
||
navbar: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
justifyContent: 'space-between',
|
||
paddingVertical: 16,
|
||
paddingHorizontal: 16,
|
||
borderBottomWidth: 1,
|
||
borderBottomColor: 'rgba(0,0,0,0.1)',
|
||
elevation: 1, // Android
|
||
shadowColor: '#000',
|
||
shadowOffset: { width: 0, height: 1 },
|
||
shadowOpacity: 0.1,
|
||
shadowRadius: 1,
|
||
},
|
||
hiddenNavbar: {
|
||
shadowOpacity: 0,
|
||
elevation: 0,
|
||
opacity: 0
|
||
},
|
||
backButton: {
|
||
padding: 8,
|
||
marginRight: 8,
|
||
},
|
||
title: {
|
||
fontSize: 20,
|
||
fontWeight: '600',
|
||
textAlign: 'center',
|
||
flex: 1,
|
||
},
|
||
placeholder: {
|
||
width: 40,
|
||
},
|
||
contentContainer: {
|
||
flex: 1,
|
||
position: 'relative'
|
||
},
|
||
absoluteView: {
|
||
position: 'absolute',
|
||
top: 0,
|
||
left: 0,
|
||
right: 0,
|
||
bottom: 0,
|
||
backgroundColor: 'white',
|
||
},
|
||
chatContainer: {
|
||
flex: 1,
|
||
},
|
||
chatContentContainer: {
|
||
paddingBottom: 20,
|
||
},
|
||
inputContainer: {
|
||
padding: 16,
|
||
paddingBottom: 24,
|
||
backgroundColor: 'white',
|
||
// borderTopWidth: 1,
|
||
// borderTopColor: '#f0f0f0',
|
||
},
|
||
}); |