2025-08-07 01:43:08 +08:00

348 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 SendMessage from "@/components/ask/send";
import { ThemedText } from "@/components/ThemedText";
import { useWebSocketStreamHandler } from "@/hooks/useWebSocketStreamHandler";
import { fetchApi } from "@/lib/server-api-util";
import { Message } from "@/types/ask";
import { useFocusEffect, useLocalSearchParams, useRouter } from "expo-router";
import { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from "react-i18next";
import {
Animated,
FlatList,
Keyboard,
KeyboardAvoidingView,
Platform,
StyleSheet,
TextInput,
TouchableOpacity,
View
} from 'react-native';
import { useAnimatedStyle, useSharedValue, withTiming } 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逻辑
// 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,
// enableBatching: false
// });
// useFocusEffect(
// useCallback(() => {
// isMountedRef.current = true;
// 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逻辑
// 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);
// abortControllerRef.current = new AbortController();
// fetchApi<Message[]>(`/chats/${sessionId}/message-history`, {
// signal: abortControllerRef.current.signal
// }).then((res) => {
// if (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) {
// 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',
},
});