Compare commits
33 Commits
main
...
fix/unexpe
| Author | SHA1 | Date | |
|---|---|---|---|
| c6be061130 | |||
| 60152a64f0 | |||
| 9de8c3b5c7 | |||
| 4c4360cefc | |||
| 76f2e6ed48 | |||
| 384e607fe1 | |||
| a7b6aeeb31 | |||
| 4e755b8f10 | |||
| 797414e78b | |||
| 2ff82495ac | |||
| 0482f23d97 | |||
| 85d9b823de | |||
| 027f7b1672 | |||
| 8f0cb0ada2 | |||
| 1891f5c359 | |||
| f8bd3b13be | |||
| 1a28d8becd | |||
| 3f2b849db2 | |||
| 995f5ad981 | |||
| 9341a1560f | |||
| f2b09cb013 | |||
| 827bf7b164 | |||
| fd5ea7f318 | |||
| 162f3b91e4 | |||
| b1031cf2b6 | |||
| 45a3660ab8 | |||
| ce50710818 | |||
| 448e8dfb53 | |||
| d59378c2da | |||
| 15fc8f3ad4 | |||
| da0b949ca4 | |||
| da00968586 | |||
| 193084fb62 |
@ -1,4 +1,3 @@
|
|||||||
import { HapticTab } from '@/components/HapticTab';
|
|
||||||
import AskNavbar from '@/components/layout/ask';
|
import AskNavbar from '@/components/layout/ask';
|
||||||
import { TabBarIcon } from '@/components/navigation/TabBarIcon';
|
import { TabBarIcon } from '@/components/navigation/TabBarIcon';
|
||||||
import { requestNotificationPermission } from '@/components/owner/utils';
|
import { requestNotificationPermission } from '@/components/owner/utils';
|
||||||
@ -188,28 +187,13 @@ export default function TabLayout() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tabs
|
<Tabs
|
||||||
screenOptions={{
|
tabBar={props => <AskNavbar {...props} wsStatus={wsStatus} />}
|
||||||
tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
|
|
||||||
headerShown: false,
|
|
||||||
tabBarButton: HapticTab,
|
|
||||||
tabBarBackground: TabBarBackground,
|
|
||||||
tabBarStyle: Platform.select({
|
|
||||||
ios: {
|
|
||||||
// Use a transparent background on iOS to show the blur effect
|
|
||||||
position: 'absolute',
|
|
||||||
},
|
|
||||||
default: {},
|
|
||||||
}),
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{/* 落地页 */}
|
{/* 落地页 */}
|
||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="index"
|
name="index"
|
||||||
options={{
|
options={{
|
||||||
title: 'Memo',
|
href: null,
|
||||||
tabBarButton: () => null, // 隐藏底部标签栏
|
|
||||||
headerShown: false, // 隐藏导航栏
|
|
||||||
tabBarStyle: { display: 'none' } // 确保在标签栏中不显示
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/* 登录 */}
|
{/* 登录 */}
|
||||||
@ -260,10 +244,7 @@ export default function TabLayout() {
|
|||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="ask"
|
name="ask"
|
||||||
options={{
|
options={{
|
||||||
title: 'ask',
|
href: null,
|
||||||
tabBarButton: () => null, // 隐藏底部标签栏
|
|
||||||
headerShown: false, // 隐藏导航栏
|
|
||||||
tabBarStyle: { display: 'none' }, // 确保在标签栏中不显示
|
|
||||||
...TransitionPresets.ShiftTransition,
|
...TransitionPresets.ShiftTransition,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -271,10 +252,7 @@ export default function TabLayout() {
|
|||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="memo-list"
|
name="memo-list"
|
||||||
options={{
|
options={{
|
||||||
title: 'memo-list',
|
href: null,
|
||||||
tabBarButton: () => null, // 隐藏底部标签栏
|
|
||||||
headerShown: false, // 隐藏导航栏
|
|
||||||
tabBarStyle: { display: 'none' }, // 确保在标签栏中不显示
|
|
||||||
...TransitionPresets.ShiftTransition,
|
...TransitionPresets.ShiftTransition,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -282,10 +260,7 @@ export default function TabLayout() {
|
|||||||
<Tabs.Screen
|
<Tabs.Screen
|
||||||
name="owner"
|
name="owner"
|
||||||
options={{
|
options={{
|
||||||
title: 'owner',
|
href: null,
|
||||||
tabBarButton: () => null, // 隐藏底部标签栏
|
|
||||||
headerShown: false, // 隐藏导航栏
|
|
||||||
tabBarStyle: { display: 'none' }, // 确保在标签栏中不显示
|
|
||||||
...TransitionPresets.ShiftTransition,
|
...TransitionPresets.ShiftTransition,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -377,7 +352,6 @@ export default function TabLayout() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Tabs >
|
</Tabs >
|
||||||
<AskNavbar wsStatus={wsStatus} />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,9 @@
|
|||||||
import ReturnArrow from "@/assets/icons/svg/returnArrow.svg";
|
import ReturnArrow from "@/assets/icons/svg/returnArrow.svg";
|
||||||
import Chat from "@/components/ask/chat";
|
|
||||||
import AskHello from "@/components/ask/hello";
|
import AskHello from "@/components/ask/hello";
|
||||||
import SendMessage from "@/components/ask/send";
|
|
||||||
import { ThemedText } from "@/components/ThemedText";
|
import { ThemedText } from "@/components/ThemedText";
|
||||||
import { fetchApi } from "@/lib/server-api-util";
|
import { fetchApi } from "@/lib/server-api-util";
|
||||||
import { getWebSocketErrorMessage, webSocketManager, WsMessage } from "@/lib/websocket-util";
|
import { Message } from "@/types/ask";
|
||||||
import { Assistant, Message } from "@/types/ask";
|
import { useFocusEffect, useLocalSearchParams, useRouter } from "expo-router";
|
||||||
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
|
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
@ -25,6 +22,7 @@ import { runOnJS } from 'react-native-reanimated';
|
|||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
|
|
||||||
export default function AskScreen() {
|
export default function AskScreen() {
|
||||||
|
const router = useRouter();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
|
||||||
const chatListRef = useRef<FlatList>(null);
|
const chatListRef = useRef<FlatList>(null);
|
||||||
@ -99,88 +97,88 @@ export default function AskScreen() {
|
|||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
webSocketManager.connect();
|
// webSocketManager.connect();
|
||||||
|
|
||||||
const handleChatStream = (message: WsMessage) => {
|
// const handleChatStream = (message: WsMessage) => {
|
||||||
if (message.type === 'ChatStream') {
|
// if (message.type === 'ChatStream') {
|
||||||
setUserMessages(prevMessages => {
|
// setUserMessages(prevMessages => {
|
||||||
const newMessages = [...prevMessages];
|
// const newMessages = [...prevMessages];
|
||||||
const lastMessage = newMessages[newMessages.length - 1];
|
// const lastMessage = newMessages[newMessages.length - 1];
|
||||||
|
|
||||||
if (lastMessage && lastMessage.role === Assistant) {
|
// if (lastMessage && lastMessage.role === Assistant) {
|
||||||
if (typeof lastMessage.content === 'string') {
|
// if (typeof lastMessage.content === 'string') {
|
||||||
if (lastMessage.content === 'keepSearchIng') {
|
// if (lastMessage.content === 'keepSearchIng') {
|
||||||
// 第一次收到流式消息,替换占位符
|
// // 第一次收到流式消息,替换占位符
|
||||||
lastMessage.content = message.chunk;
|
// lastMessage.content = message.chunk;
|
||||||
} else {
|
// } else {
|
||||||
// 持续追加流式消息
|
// // 持续追加流式消息
|
||||||
lastMessage.content += message.chunk;
|
// lastMessage.content += message.chunk;
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
// 如果 content 是数组,则更新第一个 text 部分
|
// // 如果 content 是数组,则更新第一个 text 部分
|
||||||
const textPart = lastMessage.content.find(p => p.type === 'text');
|
// const textPart = lastMessage.content.find(p => p.type === 'text');
|
||||||
if (textPart) {
|
// if (textPart) {
|
||||||
textPart.text = (textPart.text || '') + message.chunk;
|
// textPart.text = (textPart.text || '') + message.chunk;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return newMessages;
|
// return newMessages;
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
const handleChatStreamEnd = (message: WsMessage) => {
|
// const handleChatStreamEnd = (message: WsMessage) => {
|
||||||
if (message.type === 'ChatStreamEnd') {
|
// if (message.type === 'ChatStreamEnd') {
|
||||||
setUserMessages(prevMessages => {
|
// setUserMessages(prevMessages => {
|
||||||
const newMessages = [...prevMessages];
|
// const newMessages = [...prevMessages];
|
||||||
const lastMessage = newMessages[newMessages.length - 1];
|
// const lastMessage = newMessages[newMessages.length - 1];
|
||||||
if (lastMessage && lastMessage.role === Assistant) {
|
// if (lastMessage && lastMessage.role === Assistant) {
|
||||||
// 使用最终消息替换流式消息,确保 message.message 存在
|
// // 使用最终消息替换流式消息,确保 message.message 存在
|
||||||
if (message.message) {
|
// if (message.message) {
|
||||||
newMessages[newMessages.length - 1] = message.message as Message;
|
// newMessages[newMessages.length - 1] = message.message as Message;
|
||||||
} else {
|
// } else {
|
||||||
// 如果最终消息为空,则移除 'keepSearchIng' 占位符
|
// // 如果最终消息为空,则移除 'keepSearchIng' 占位符
|
||||||
return prevMessages.filter(m => !(typeof m.content === 'string' && m.content === 'keepSearchIng'));
|
// return prevMessages.filter(m => !(typeof m.content === 'string' && m.content === 'keepSearchIng'));
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return newMessages;
|
// return newMessages;
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
const handleError = (message: WsMessage) => {
|
// const handleError = (message: WsMessage) => {
|
||||||
if (message.type === 'Error') {
|
// if (message.type === 'Error') {
|
||||||
console.log(`WebSocket Error: ${message.code} - ${message.message}`);
|
// console.log(`WebSocket Error: ${message.code} - ${message.message}`);
|
||||||
// 可以在这里添加错误提示,例如替换最后一条消息为错误信息
|
// // 可以在这里添加错误提示,例如替换最后一条消息为错误信息
|
||||||
setUserMessages(prev => {
|
// setUserMessages(prev => {
|
||||||
// 创建新的数组和新的消息对象
|
// // 创建新的数组和新的消息对象
|
||||||
return prev.map((msg, index) => {
|
// return prev.map((msg, index) => {
|
||||||
if (index === prev.length - 1 &&
|
// if (index === prev.length - 1 &&
|
||||||
typeof msg.content === 'string' &&
|
// typeof msg.content === 'string' &&
|
||||||
msg.content === 'keepSearchIng') {
|
// msg.content === 'keepSearchIng') {
|
||||||
// 返回新的消息对象
|
// // 返回新的消息对象
|
||||||
return {
|
// return {
|
||||||
...msg,
|
// ...msg,
|
||||||
content: getWebSocketErrorMessage(message.code, t)
|
// content: getWebSocketErrorMessage(message.code, t)
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
return msg;
|
// return msg;
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
webSocketManager.subscribe('ChatStream', handleChatStream);
|
// webSocketManager.subscribe('ChatStream', handleChatStream);
|
||||||
webSocketManager.subscribe('ChatStreamEnd', handleChatStreamEnd);
|
// webSocketManager.subscribe('ChatStreamEnd', handleChatStreamEnd);
|
||||||
webSocketManager.subscribe('Error', handleError);
|
// webSocketManager.subscribe('Error', handleError);
|
||||||
|
|
||||||
return () => {
|
// return () => {
|
||||||
webSocketManager.unsubscribe('ChatStream', handleChatStream);
|
// webSocketManager.unsubscribe('ChatStream', handleChatStream);
|
||||||
webSocketManager.unsubscribe('ChatStreamEnd', handleChatStreamEnd);
|
// webSocketManager.unsubscribe('ChatStreamEnd', handleChatStreamEnd);
|
||||||
webSocketManager.unsubscribe('Error', handleError);
|
// webSocketManager.unsubscribe('Error', handleError);
|
||||||
// 可以在这里选择断开连接,或者保持连接以加快下次进入页面的速度
|
// // 可以在这里选择断开连接,或者保持连接以加快下次进入页面的速度
|
||||||
// webSocketManager.disconnect();
|
// // webSocketManager.disconnect();
|
||||||
};
|
// };
|
||||||
}, [])
|
}, [])
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -196,7 +194,7 @@ export default function AskScreen() {
|
|||||||
setIsHello(true);
|
setIsHello(true);
|
||||||
setConversationId(null);
|
setConversationId(null);
|
||||||
}
|
}
|
||||||
}, [sessionId, newSession]);
|
}, [sessionId, newSession])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isHello) {
|
if (isHello) {
|
||||||
@ -234,23 +232,6 @@ export default function AskScreen() {
|
|||||||
}
|
}
|
||||||
}, [isHello, fadeAnim, fadeAnimChat]);
|
}, [isHello, fadeAnim, fadeAnimChat]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isHello) {
|
|
||||||
// 不再自动关闭键盘,让用户手动控制
|
|
||||||
// 这里可以添加其他需要在隐藏hello界面时执行的逻辑
|
|
||||||
scrollToEnd(false);
|
|
||||||
}
|
|
||||||
}, [isHello]);
|
|
||||||
|
|
||||||
useFocusEffect(
|
|
||||||
useCallback(() => {
|
|
||||||
if (!sessionId) {
|
|
||||||
setIsHello(true);
|
|
||||||
setUserMessages([])
|
|
||||||
}
|
|
||||||
}, [sessionId])
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GestureDetector gesture={gesture}>
|
<GestureDetector gesture={gesture}>
|
||||||
<View style={[styles.container, { paddingTop: insets.top, paddingBottom: insets.bottom }]}>
|
<View style={[styles.container, { paddingTop: insets.top, paddingBottom: insets.bottom }]}>
|
||||||
@ -268,7 +249,9 @@ export default function AskScreen() {
|
|||||||
console.log('失去焦点失败:', error);
|
console.log('失去焦点失败:', error);
|
||||||
}
|
}
|
||||||
Keyboard.dismiss();
|
Keyboard.dismiss();
|
||||||
router.push('/memo-list');
|
setTimeout(() => {
|
||||||
|
router.replace('/memo-list');
|
||||||
|
}, 100);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ReturnArrow />
|
<ReturnArrow />
|
||||||
@ -303,17 +286,17 @@ export default function AskScreen() {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Chat
|
{/* <Chat
|
||||||
ref={chatListRef}
|
ref={chatListRef}
|
||||||
userMessages={userMessages}
|
userMessages={userMessages}
|
||||||
sessionId={sessionId}
|
sessionId={sessionId}
|
||||||
setSelectedImages={setSelectedImages}
|
setSelectedImages={setSelectedImages}
|
||||||
selectedImages={selectedImages}
|
selectedImages={selectedImages}
|
||||||
style={styles.chatContainer}
|
style={styles.chatContainer}
|
||||||
contentContainerStyle={styles.chatContentContainer}
|
contentContainerStyle={styles.chatContentContainer}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
onContentSizeChange={() => scrollToEnd()}
|
onContentSizeChange={() => scrollToEnd()}
|
||||||
/>
|
/> */}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
@ -322,18 +305,18 @@ export default function AskScreen() {
|
|||||||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||||
keyboardVerticalOffset={0} >
|
keyboardVerticalOffset={0} >
|
||||||
<View style={styles.inputContainer} key={conversationId}>
|
<View style={styles.inputContainer} key={conversationId}>
|
||||||
<SendMessage
|
{/* <SendMessage
|
||||||
setIsHello={setIsHello}
|
setIsHello={setIsHello}
|
||||||
conversationId={conversationId}
|
conversationId={conversationId}
|
||||||
setConversationId={setConversationId}
|
setConversationId={setConversationId}
|
||||||
setUserMessages={setUserMessages}
|
setUserMessages={setUserMessages}
|
||||||
selectedImages={selectedImages}
|
selectedImages={selectedImages}
|
||||||
setSelectedImages={setSelectedImages}
|
setSelectedImages={setSelectedImages}
|
||||||
/>
|
/> */}
|
||||||
</View>
|
</View>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
</View >
|
</View >
|
||||||
</GestureDetector>
|
</GestureDetector >
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -225,7 +225,7 @@ export default function HomeScreen() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
checkAuthStatus(router, () => {
|
checkAuthStatus(router, () => {
|
||||||
router.replace('/ask')
|
router.replace('/memo-list')
|
||||||
}, false).then(() => {
|
}, false).then(() => {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import SkeletonItem from '@/components/memo/SkeletonItem';
|
|||||||
|
|
||||||
// 类型定义
|
// 类型定义
|
||||||
import { useUploadManager } from '@/hooks/useUploadManager';
|
import { useUploadManager } from '@/hooks/useUploadManager';
|
||||||
import { getCachedData, prefetchChatDetail, prefetchChats } from '@/lib/prefetch';
|
import { getCachedData, prefetchChatDetail } from '@/lib/prefetch';
|
||||||
import { fetchApi } from '@/lib/server-api-util';
|
import { fetchApi } from '@/lib/server-api-util';
|
||||||
import { Chat, getMessageText } from '@/types/ask';
|
import { Chat, getMessageText } from '@/types/ask';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -105,18 +105,11 @@ const MemoList = () => {
|
|||||||
|
|
||||||
const initialize = async () => {
|
const initialize = async () => {
|
||||||
try {
|
try {
|
||||||
// 并行预加载资源和数据
|
// 并行预加载资源和主数据
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
preloadAssets(),
|
preloadAssets(),
|
||||||
prefetchChats().then((data) => {
|
fetchHistoryList()
|
||||||
if (isActive && data) {
|
|
||||||
setHistoryList(data as Chat[]);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 主数据加载
|
|
||||||
await fetchHistoryList();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('初始化失败:', error);
|
console.error('初始化失败:', error);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { ThemedText } from "@/components/ThemedText";
|
import { ThemedText } from "@/components/ThemedText";
|
||||||
import { webSocketManager } from "@/lib/websocket-util";
|
import { webSocketManager } from "@/lib/websocket-util";
|
||||||
import { Message } from "@/types/ask";
|
import { Message } from "@/types/ask";
|
||||||
import { Dispatch, SetStateAction } from "react";
|
import { Dispatch, SetStateAction, useCallback, useRef } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Dimensions, Image, ScrollView, StyleSheet, TouchableOpacity, View } from 'react-native';
|
import { Image, ScrollView, StyleSheet, TouchableOpacity, useWindowDimensions, View } from 'react-native';
|
||||||
import { createNewConversation } from "./utils";
|
import { createNewConversation } from "./utils";
|
||||||
|
|
||||||
interface AskHelloProps {
|
interface AskHelloProps {
|
||||||
@ -13,39 +13,63 @@ interface AskHelloProps {
|
|||||||
}
|
}
|
||||||
export default function AskHello({ setUserMessages, setConversationId, setIsHello }: AskHelloProps) {
|
export default function AskHello({ setUserMessages, setConversationId, setIsHello }: AskHelloProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const width = Dimensions.get('window').width;
|
const { width, height } = useWindowDimensions();
|
||||||
const height = Dimensions.get('window').height;
|
|
||||||
|
|
||||||
const handleCase = async (text: string) => {
|
const inFlightRef = useRef(false);
|
||||||
setIsHello(false);
|
|
||||||
setUserMessages([
|
const handleCase = useCallback(async (text: string) => {
|
||||||
{
|
if (inFlightRef.current) return;
|
||||||
id: Math.random().toString(36).substring(2, 9),
|
inFlightRef.current = true;
|
||||||
content: text,
|
try {
|
||||||
role: 'user',
|
// UI
|
||||||
timestamp: new Date().toISOString()
|
setIsHello(false);
|
||||||
},
|
setUserMessages([
|
||||||
{
|
{
|
||||||
id: Math.random().toString(36).substring(2, 9),
|
id: Math.random().toString(36).substring(2, 9),
|
||||||
content: "keepSearchIng",
|
content: text,
|
||||||
role: 'assistant',
|
role: 'user',
|
||||||
timestamp: new Date().toISOString()
|
timestamp: new Date().toISOString()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: Math.random().toString(36).substring(2, 9),
|
||||||
|
content: "keepSearchIng",
|
||||||
|
role: 'assistant',
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
const sessionId = await createNewConversation(text);
|
||||||
|
if (!sessionId) {
|
||||||
|
console.error("Failed to create a new conversation.");
|
||||||
|
|
||||||
|
setUserMessages(prev => prev.filter(item => item.content !== 'keepSearchIng'));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
]);
|
|
||||||
|
|
||||||
const sessionId = await createNewConversation(text);
|
|
||||||
if (sessionId) {
|
|
||||||
setConversationId(sessionId);
|
setConversationId(sessionId);
|
||||||
webSocketManager.send({
|
try {
|
||||||
type: 'Chat',
|
if (webSocketManager && typeof (webSocketManager as any).send === 'function') {
|
||||||
session_id: sessionId,
|
(webSocketManager as any).send({
|
||||||
message: text
|
type: 'Chat',
|
||||||
});
|
session_id: sessionId,
|
||||||
} else {
|
message: text
|
||||||
console.error("Failed to create a new conversation.");
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error('WebSocket manager is not ready');
|
||||||
|
}
|
||||||
|
} catch (wsErr) {
|
||||||
|
console.error('WebSocket send failed:', wsErr);
|
||||||
|
setUserMessages(prev => prev.filter(item => item.content !== 'keepSearchIng'));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('handleCase failed:', err);
|
||||||
|
|
||||||
setUserMessages(prev => prev.filter(item => item.content !== 'keepSearchIng'));
|
setUserMessages(prev => prev.filter(item => item.content !== 'keepSearchIng'));
|
||||||
|
} finally {
|
||||||
|
inFlightRef.current = false;
|
||||||
}
|
}
|
||||||
}
|
}, [setConversationId, setIsHello, setUserMessages]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className="flex-1 bg-white w-full">
|
<View className="flex-1 bg-white w-full">
|
||||||
<ScrollView
|
<ScrollView
|
||||||
@ -96,7 +120,7 @@ export default function AskHello({ setUserMessages, setConversationId, setIsHell
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</View>
|
</View >
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +130,6 @@ const styles = StyleSheet.create({
|
|||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
gap: 8,
|
|
||||||
width: '100%',
|
width: '100%',
|
||||||
marginTop: 16
|
marginTop: 16
|
||||||
},
|
},
|
||||||
@ -115,6 +138,8 @@ const styles = StyleSheet.create({
|
|||||||
borderColor: "#AC7E35",
|
borderColor: "#AC7E35",
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
paddingHorizontal: 8,
|
paddingHorizontal: 8,
|
||||||
|
marginHorizontal: 4,
|
||||||
|
marginVertical: 4,
|
||||||
width: 'auto',
|
width: 'auto',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: "#4C320C"
|
color: "#4C320C"
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import { Message } from "@/types/ask";
|
|||||||
import * as FileSystem from 'expo-file-system';
|
import * as FileSystem from 'expo-file-system';
|
||||||
import * as MediaLibrary from 'expo-media-library';
|
import * as MediaLibrary from 'expo-media-library';
|
||||||
import { TFunction } from "i18next";
|
import { TFunction } from "i18next";
|
||||||
import { useCallback } from "react";
|
|
||||||
import { Alert } from 'react-native';
|
import { Alert } from 'react-native';
|
||||||
|
|
||||||
// 实现一个函数,从两个数组中轮流插入新数组
|
// 实现一个函数,从两个数组中轮流插入新数组
|
||||||
@ -19,12 +18,12 @@ export const mergeArrays = (arr1: any[], arr2: any[]) => {
|
|||||||
|
|
||||||
|
|
||||||
// 创建新对话并获取消息
|
// 创建新对话并获取消息
|
||||||
export const createNewConversation = useCallback(async (user_text: string) => {
|
export const createNewConversation = async (user_text: string) => {
|
||||||
const data = await fetchApi<string>("/chat/new", {
|
const data = await fetchApi<string>("/chat/new", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
});
|
});
|
||||||
return data
|
return data
|
||||||
}, []);
|
};
|
||||||
|
|
||||||
// 获取对话信息
|
// 获取对话信息
|
||||||
export const getConversation = async ({
|
export const getConversation = async ({
|
||||||
|
|||||||
@ -3,8 +3,9 @@ import ChatNotInSvg from "@/assets/icons/svg/chatNotIn.svg";
|
|||||||
import PersonInSvg from "@/assets/icons/svg/personIn.svg";
|
import PersonInSvg from "@/assets/icons/svg/personIn.svg";
|
||||||
import PersonNotInSvg from "@/assets/icons/svg/personNotIn.svg";
|
import PersonNotInSvg from "@/assets/icons/svg/personNotIn.svg";
|
||||||
import { WebSocketStatus } from "@/lib/websocket-util";
|
import { WebSocketStatus } from "@/lib/websocket-util";
|
||||||
import { router, usePathname } from "expo-router";
|
import { BottomTabBarProps } from "@react-navigation/bottom-tabs";
|
||||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
import { router } from "expo-router";
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
import { Dimensions, Image, StyleSheet, TouchableOpacity, View } from 'react-native';
|
import { Dimensions, Image, StyleSheet, TouchableOpacity, View } from 'react-native';
|
||||||
import Svg, { Circle, Ellipse, G, Mask, Path, Rect } from "react-native-svg";
|
import Svg, { Circle, Ellipse, G, Mask, Path, Rect } from "react-native-svg";
|
||||||
|
|
||||||
@ -42,14 +43,15 @@ const CenterButtonSvg = React.memo(() => (
|
|||||||
</Svg>
|
</Svg>
|
||||||
));
|
));
|
||||||
|
|
||||||
interface AskNavbarProps {
|
type AskNavbarProps = BottomTabBarProps & {
|
||||||
wsStatus: WebSocketStatus;
|
wsStatus: WebSocketStatus;
|
||||||
}
|
};
|
||||||
|
|
||||||
const AskNavbar = ({ wsStatus }: AskNavbarProps) => {
|
const AskNavbar = ({ state, descriptors, navigation, wsStatus }: AskNavbarProps) => {
|
||||||
// 获取设备尺寸
|
// 获取设备尺寸
|
||||||
const { width } = useMemo(() => Dimensions.get('window'), []);
|
const { width } = useMemo(() => Dimensions.get('window'), []);
|
||||||
const pathname = usePathname();
|
const { routes, index } = state;
|
||||||
|
const currentRouteName = routes[index].name;
|
||||||
|
|
||||||
const statusColor = useMemo(() => {
|
const statusColor = useMemo(() => {
|
||||||
switch (wsStatus) {
|
switch (wsStatus) {
|
||||||
@ -64,34 +66,6 @@ const AskNavbar = ({ wsStatus }: AskNavbarProps) => {
|
|||||||
}
|
}
|
||||||
}, [wsStatus]);
|
}, [wsStatus]);
|
||||||
|
|
||||||
// 预加载目标页面
|
|
||||||
useEffect(() => {
|
|
||||||
const preloadPages = async () => {
|
|
||||||
try {
|
|
||||||
await Promise.all([
|
|
||||||
router.prefetch('/memo-list'),
|
|
||||||
router.prefetch('/ask'),
|
|
||||||
router.prefetch('/owner')
|
|
||||||
]);
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('预加载页面失败:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
preloadPages();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 使用 useCallback 缓存导航函数
|
|
||||||
const navigateTo = useCallback((route: string) => {
|
|
||||||
if (route === '/ask') {
|
|
||||||
router.push({
|
|
||||||
pathname: '/ask',
|
|
||||||
params: { newSession: "true" }
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
router.push(route as any);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 使用 useMemo 缓存样式对象
|
// 使用 useMemo 缓存样式对象
|
||||||
const styles = useMemo(() => StyleSheet.create({
|
const styles = useMemo(() => StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
@ -156,7 +130,7 @@ const AskNavbar = ({ wsStatus }: AskNavbarProps) => {
|
|||||||
}), [width, statusColor]);
|
}), [width, statusColor]);
|
||||||
|
|
||||||
// 如果当前路径是ask页面,则不渲染导航栏
|
// 如果当前路径是ask页面,则不渲染导航栏
|
||||||
if (pathname != '/memo-list' && pathname != '/owner') {
|
if (currentRouteName !== 'memo-list' && currentRouteName !== 'owner') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,18 +139,18 @@ const AskNavbar = ({ wsStatus }: AskNavbarProps) => {
|
|||||||
<Image source={require('@/assets/images/png/owner/ask.png')} style={{ width: width * 1.18, height: 100, resizeMode: 'cover', marginLeft: -width * 0.07 }} />
|
<Image source={require('@/assets/images/png/owner/ask.png')} style={{ width: width * 1.18, height: 100, resizeMode: 'cover', marginLeft: -width * 0.07 }} />
|
||||||
<View style={styles.navContainer}>
|
<View style={styles.navContainer}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => navigateTo('/memo-list')}
|
onPress={() => navigation.navigate('memo-list')}
|
||||||
style={[styles.navButton, { alignItems: "flex-start", paddingLeft: 16 }]}
|
style={[styles.navButton, { alignItems: "flex-start", paddingLeft: 16 }]}
|
||||||
>
|
>
|
||||||
<TabIcon
|
<TabIcon
|
||||||
isActive={pathname === "/memo-list"}
|
isActive={currentRouteName === "memo-list"}
|
||||||
ActiveIcon={ChatInSvg}
|
ActiveIcon={ChatInSvg}
|
||||||
InactiveIcon={ChatNotInSvg}
|
InactiveIcon={ChatNotInSvg}
|
||||||
/>
|
/>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => navigateTo('/ask')}
|
onPress={() => router.push({ pathname: '/ask', params: { newSession: "true" } })}
|
||||||
style={styles.centerButton}
|
style={styles.centerButton}
|
||||||
>
|
>
|
||||||
<View style={styles.statusIndicator} />
|
<View style={styles.statusIndicator} />
|
||||||
@ -184,11 +158,11 @@ const AskNavbar = ({ wsStatus }: AskNavbarProps) => {
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => navigateTo('/owner')}
|
onPress={() => navigation.navigate('owner')}
|
||||||
style={styles.navButton}
|
style={styles.navButton}
|
||||||
>
|
>
|
||||||
<TabIcon
|
<TabIcon
|
||||||
isActive={pathname === "/owner"}
|
isActive={currentRouteName === "owner"}
|
||||||
ActiveIcon={PersonInSvg}
|
ActiveIcon={PersonInSvg}
|
||||||
InactiveIcon={PersonNotInSvg}
|
InactiveIcon={PersonNotInSvg}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { View } from 'react-native';
|
||||||
|
|
||||||
// This is a shim for web and Android where the tab bar is generally opaque.
|
// This is a shim for web and Android where the tab bar is generally opaque.
|
||||||
export default undefined;
|
export default function TabBarBackground() {
|
||||||
|
return <View style={{ flex: 1, backgroundColor: 'transparent' }} />;
|
||||||
|
}
|
||||||
|
|
||||||
export function useBottomTabOverflow() {
|
export function useBottomTabOverflow() {
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@ -25,7 +25,7 @@ export interface PagedResult<T> {
|
|||||||
// 获取.env文件中的变量
|
// 获取.env文件中的变量
|
||||||
|
|
||||||
|
|
||||||
export const API_ENDPOINT = Constants.expoConfig?.extra?.API_ENDPOINT || "http://192.168.31.16:31646/api";
|
export const API_ENDPOINT = process.env.EXPO_PUBLIC_API_ENDPOINT || Constants.expoConfig?.extra?.API_ENDPOINT;
|
||||||
|
|
||||||
|
|
||||||
// 更新 access_token 的逻辑 - 用于React组件中
|
// 更新 access_token 的逻辑 - 用于React组件中
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { TFunction } from 'i18next';
|
|||||||
import { Platform } from 'react-native';
|
import { Platform } from 'react-native';
|
||||||
|
|
||||||
// 从环境变量或默认值中定义 WebSocket 端点
|
// 从环境变量或默认值中定义 WebSocket 端点
|
||||||
export const WEBSOCKET_ENDPOINT = Constants.expoConfig?.extra?.WEBSOCKET_ENDPOINT || "ws://192.168.31.16:31646/ws/chat";
|
export const WEBSOCKET_ENDPOINT = process.env.EXPO_PUBLIC_WEBSOCKET_ENDPOINT || Constants.expoConfig?.extra?.WEBSOCKET_ENDPOINT;
|
||||||
|
|
||||||
export type WebSocketStatus = 'connecting' | 'connected' | 'disconnected' | 'reconnecting';
|
export type WebSocketStatus = 'connecting' | 'connected' | 'disconnected' | 'reconnecting';
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user