2025-08-07 17:00:14 +08:00

363 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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