diff --git a/app/(tabs)/ask.tsx b/app/(tabs)/ask.tsx index 7d9fa4e..b8fa3bf 100644 --- a/app/(tabs)/ask.tsx +++ b/app/(tabs)/ask.tsx @@ -4,7 +4,9 @@ import AskHello from "@/components/ask/hello"; import SendMessage from "@/components/ask/send"; import { ThemedText } from "@/components/ThemedText"; import { fetchApi } from "@/lib/server-api-util"; -import { Message } from "@/types/ask"; +import { getWebSocketErrorMessage, getWebSocketManager, WsMessage } from "@/lib/websocket-util"; +import { Assistant, Message } from "@/types/ask"; +import { useWebSocketSubscription } from "@/hooks/useWebSocketSubscription"; import { useFocusEffect, useLocalSearchParams, useRouter } from "expo-router"; import { useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from "react-i18next"; @@ -97,163 +99,25 @@ export default function AskScreen() { }; }, [isHello]); - // useFocusEffect( - // useCallback(() => { - // // 确保在组件挂载时才连接 - // const webSocketManager = getWebSocketManager(); - // webSocketManager.connect(); + useFocusEffect( + useCallback(() => { + let isMounted = true; + + // 使用新的WebSocket订阅hook + const { subscribeToWebSocket } = useWebSocketSubscription(setUserMessages, isMounted); + + // 订阅WebSocket消息 + const unsubscribe = subscribeToWebSocket(); - // let isMounted = true; - - // const handleChatStream = (message: WsMessage) => { - // // 确保组件仍然挂载 - // if (!isMounted) return; - - // if (message.type === 'ChatStream') { - // setUserMessages(prevMessages => { - // // 使用 try-catch 包装以防止错误导致应用崩溃 - // try { - // const lastMessage = prevMessages[prevMessages.length - 1]; - - // // 检查是否是有效的助手消息 - // if (!lastMessage || lastMessage.role !== Assistant) { - // return prevMessages; - // } - - // // 创建新的消息数组 - // const newMessages = [...prevMessages]; - - // if (typeof lastMessage.content === 'string') { - // if (lastMessage.content === 'keepSearchIng') { - // // 第一次收到流式消息,替换占位符 - // newMessages[newMessages.length - 1] = { - // ...lastMessage, - // content: message.chunk - // }; - // } else { - // // 持续追加流式消息 - // newMessages[newMessages.length - 1] = { - // ...lastMessage, - // content: lastMessage.content + message.chunk - // }; - // } - // } else if (Array.isArray(lastMessage.content)) { - // // 如果 content 是数组,则更新第一个 text 部分 - // const textPartIndex = lastMessage.content.findIndex(p => p.type === 'text'); - // if (textPartIndex !== -1) { - // const updatedContent = [...lastMessage.content]; - // updatedContent[textPartIndex] = { - // ...updatedContent[textPartIndex], - // text: (updatedContent[textPartIndex].text || '') + message.chunk - // }; - - // newMessages[newMessages.length - 1] = { - // ...lastMessage, - // content: updatedContent - // }; - // } - // } - - // return newMessages; - // } catch (error) { - // console.error('处理 ChatStream 消息时出错:', error); - // // 发生错误时返回原始消息数组 - // return prevMessages; - // } - // }); - // } - // }; - - // const handleChatStreamEnd = (message: WsMessage) => { - // // 确保组件仍然挂载 - // if (!isMounted) return; - - // if (message.type === 'ChatStreamEnd') { - // setUserMessages(prevMessages => { - // // 使用 try-catch 包装以防止错误导致应用崩溃 - // try { - // const lastMessage = prevMessages[prevMessages.length - 1]; - - // // 检查是否是有效的助手消息 - // if (!lastMessage || lastMessage.role !== Assistant) { - // return prevMessages; - // } - - // // 使用最终消息替换流式消息,确保 message.message 存在 - // if (message.message) { - // const newMessages = [...prevMessages]; - // newMessages[newMessages.length - 1] = message.message as Message; - // return newMessages; - // } else { - // // 如果最终消息为空,则移除 'keepSearchIng' 占位符 - // return prevMessages.filter(m => - // !(typeof m.content === 'string' && m.content === 'keepSearchIng') - // ); - // } - // } catch (error) { - // console.error('处理 ChatStreamEnd 消息时出错:', error); - // // 发生错误时返回原始消息数组 - // return prevMessages; - // } - // }); - // } - // }; - - // const handleError = (message: WsMessage) => { - // // 确保组件仍然挂载 - // if (!isMounted) return; - - // if (message.type === 'Error') { - // console.log(`WebSocket Error: ${message.code} - ${message.message}`); - - // setUserMessages(prevMessages => { - // // 使用 try-catch 包装以防止错误导致应用崩溃 - // try { - // const lastMessage = prevMessages[prevMessages.length - 1]; - - // // 检查是否是有效的助手消息且包含占位符 - // if (!lastMessage || - // lastMessage.role !== Assistant || - // typeof lastMessage.content !== 'string' || - // lastMessage.content !== 'keepSearchIng') { - // return prevMessages; - // } - - // // 替换占位符为错误消息 - // const newMessages = [...prevMessages]; - // newMessages[newMessages.length - 1] = { - // ...lastMessage, - // content: getWebSocketErrorMessage(message.code, t) - // }; - - // return newMessages; - // } catch (error) { - // console.error('处理 Error 消息时出错:', error); - // // 发生错误时返回原始消息数组 - // return prevMessages; - // } - // }); - // } - // }; - - // webSocketManager.subscribe('ChatStream', handleChatStream); - // webSocketManager.subscribe('ChatStreamEnd', handleChatStreamEnd); - // webSocketManager.subscribe('Error', handleError); - - // return () => { - // // 设置组件卸载标志 - // isMounted = false; - - // // 清理订阅 - // webSocketManager.unsubscribe('ChatStream', handleChatStream); - // webSocketManager.unsubscribe('ChatStreamEnd', handleChatStreamEnd); - // webSocketManager.unsubscribe('Error', handleError); - - // // 可以在这里选择断开连接,或者保持连接以加快下次进入页面的速度 - // webSocketManager.disconnect(); - // }; - // }, [t]) - // ); + return () => { + // 设置组件卸载标志 + isMounted = false; + + // 取消订阅 + unsubscribe(); + }; + }, [t]) + ); // 创建动画样式 const welcomeStyle = useAnimatedStyle(() => { diff --git a/hooks/useWebSocketSubscription.ts b/hooks/useWebSocketSubscription.ts new file mode 100644 index 0000000..fb3ff69 --- /dev/null +++ b/hooks/useWebSocketSubscription.ts @@ -0,0 +1,169 @@ +import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { getWebSocketManager, WsMessage, getWebSocketErrorMessage } from '@/lib/websocket-util'; +import { Message, Assistant } from '@/types/ask'; +import { Dispatch, SetStateAction } from 'react'; + +export const useWebSocketSubscription = ( + setUserMessages: Dispatch>, + isMounted: boolean +) => { + const { t } = useTranslation(); + + const handleChatStream = useCallback((message: WsMessage) => { + // 确保组件仍然挂载 + if (!isMounted) return; + + if (message.type === 'ChatStream') { + setUserMessages(prevMessages => { + // 使用 try-catch 包装以防止错误导致应用崩溃 + try { + const lastMessage = prevMessages[prevMessages.length - 1]; + + // 检查是否是有效的助手消息 + if (!lastMessage || lastMessage.role !== Assistant) { + return prevMessages; + } + + // 创建新的消息数组 + const newMessages = [...prevMessages]; + + if (typeof lastMessage.content === 'string') { + if (lastMessage.content === 'keepSearchIng') { + // 第一次收到流式消息,替换占位符 + newMessages[newMessages.length - 1] = { + ...lastMessage, + content: message.chunk + }; + } else { + // 持续追加流式消息 + newMessages[newMessages.length - 1] = { + ...lastMessage, + content: lastMessage.content + message.chunk + }; + } + } else if (Array.isArray(lastMessage.content)) { + // 如果 content 是数组,则更新第一个 text 部分 + const textPartIndex = lastMessage.content.findIndex(p => p.type === 'text'); + if (textPartIndex !== -1) { + const updatedContent = [...lastMessage.content]; + updatedContent[textPartIndex] = { + ...updatedContent[textPartIndex], + text: (updatedContent[textPartIndex].text || '') + message.chunk + }; + + newMessages[newMessages.length - 1] = { + ...lastMessage, + content: updatedContent + }; + } + } + + return newMessages; + } catch (error) { + console.error('处理 ChatStream 消息时出错:', error); + // 发生错误时返回原始消息数组 + return prevMessages; + } + }); + } + }, [setUserMessages, isMounted]); + + const handleChatStreamEnd = useCallback((message: WsMessage) => { + // 确保组件仍然挂载 + if (!isMounted) return; + + if (message.type === 'ChatStreamEnd') { + setUserMessages(prevMessages => { + // 使用 try-catch 包装以防止错误导致应用崩溃 + try { + const lastMessage = prevMessages[prevMessages.length - 1]; + + // 检查是否是有效的助手消息 + if (!lastMessage || lastMessage.role !== Assistant) { + return prevMessages; + } + + // 使用最终消息替换流式消息,确保 message.message 存在 + if (message.message) { + const newMessages = [...prevMessages]; + newMessages[newMessages.length - 1] = message.message as Message; + return newMessages; + } else { + // 如果最终消息为空,则移除 'keepSearchIng' 占位符 + return prevMessages.filter(m => + !(typeof m.content === 'string' && m.content === 'keepSearchIng') + ); + } + } catch (error) { + console.error('处理 ChatStreamEnd 消息时出错:', error); + // 发生错误时返回原始消息数组 + return prevMessages; + } + }); + } + }, [setUserMessages, isMounted]); + + const handleError = useCallback((message: WsMessage) => { + // 确保组件仍然挂载 + if (!isMounted) return; + + if (message.type === 'Error') { + console.log(`WebSocket Error: ${message.code} - ${message.message}`); + + setUserMessages(prevMessages => { + // 使用 try-catch 包装以防止错误导致应用崩溃 + try { + const lastMessage = prevMessages[prevMessages.length - 1]; + + // 检查是否是有效的助手消息且包含占位符 + if (!lastMessage || + lastMessage.role !== Assistant || + typeof lastMessage.content !== 'string' || + lastMessage.content !== 'keepSearchIng') { + return prevMessages; + } + + // 替换占位符为错误消息 + const newMessages = [...prevMessages]; + newMessages[newMessages.length - 1] = { + ...lastMessage, + content: getWebSocketErrorMessage(message.code, t) + }; + + return newMessages; + } catch (error) { + console.error('处理 Error 消息时出错:', error); + // 发生错误时返回原始消息数组 + return prevMessages; + } + }); + } + }, [setUserMessages, isMounted, t]); + + const subscribeToWebSocket = useCallback(() => { + const webSocketManager = getWebSocketManager(); + webSocketManager.connect(); + + webSocketManager.subscribe('ChatStream', handleChatStream); + webSocketManager.subscribe('ChatStreamEnd', handleChatStreamEnd); + webSocketManager.subscribe('Error', handleError); + + return () => { + // 清理订阅 + webSocketManager.unsubscribe('ChatStream', handleChatStream); + webSocketManager.unsubscribe('ChatStreamEnd', handleChatStreamEnd); + webSocketManager.unsubscribe('Error', handleError); + + // 可以在这里选择断开连接,或者保持连接以加快下次进入页面的速度 + webSocketManager.disconnect(); + }; + }, [handleChatStream, handleChatStreamEnd, handleError]); + + return { + subscribeToWebSocket, + handleChatStream, + handleChatStreamEnd, + handleError + }; +};