This commit is contained in:
parent
ae14e05533
commit
11b0c051d1
@ -1,7 +1,7 @@
|
|||||||
import ReturnArrow from "@/assets/icons/svg/returnArrow.svg";
|
import ReturnArrow from "@/assets/icons/svg/returnArrow.svg";
|
||||||
import Chat from "@/components/ask/chat";
|
import Chat from "@/components/ask/chat";
|
||||||
import AskHello from "@/components/ask/hello";
|
import AskHello from "@/components/ask/hello";
|
||||||
import AudioRecordPlay from "@/components/ask/voice";
|
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 { Message } from "@/types/ask";
|
import { Message } from "@/types/ask";
|
||||||
@ -105,23 +105,7 @@ export default function AskScreen() {
|
|||||||
|
|
||||||
{/* 功能区 - 放在 KeyboardAvoidingView 内但在 ScrollView 外 */}
|
{/* 功能区 - 放在 KeyboardAvoidingView 内但在 ScrollView 外 */}
|
||||||
<View className="w-full px-[1.5rem] mb-[2rem]">
|
<View className="w-full px-[1.5rem] mb-[2rem]">
|
||||||
<ScrollView
|
<SendMessage setUserMessages={setUserMessages} setConversationId={setConversationId} setIsHello={setIsHello} conversationId={conversationId} />
|
||||||
horizontal
|
|
||||||
showsHorizontalScrollIndicator={false}
|
|
||||||
contentContainerStyle={{
|
|
||||||
paddingRight: 16,
|
|
||||||
alignItems: 'center',
|
|
||||||
flexDirection: 'row',
|
|
||||||
gap: 8
|
|
||||||
}}
|
|
||||||
className="pb-2"
|
|
||||||
>
|
|
||||||
<Chip icon="sunny" text="Tdy's vlog" />
|
|
||||||
<Chip icon="happy" text="Smiles" />
|
|
||||||
<Chip icon="image" text="Snapshots" />
|
|
||||||
<Chip icon="time" text="Moments" />
|
|
||||||
</ScrollView>
|
|
||||||
<AudioRecordPlay getConversation={getConversation} setIsHello={setIsHello} setInputValue={setInputValue} conversationId={conversationId} inputValue={inputValue} createNewConversation={createNewConversation} />
|
|
||||||
</View>
|
</View>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@ -95,7 +95,7 @@ const renderMessage = ({ item, sessionId, setModalVisible, modalVisible }: Rende
|
|||||||
<View className="mt-2 flex flex-row gap-2 w-full">
|
<View className="mt-2 flex flex-row gap-2 w-full">
|
||||||
{mergeArrays(item.content.image_material_infos || [], item.content.video_material_infos || [])?.slice(0, 3)?.map((image, index, array) => (
|
{mergeArrays(item.content.image_material_infos || [], item.content.video_material_infos || [])?.slice(0, 3)?.map((image, index, array) => (
|
||||||
<Pressable
|
<Pressable
|
||||||
key={image.id}
|
key={`${image.role}-${image.timestamp}`}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setModalVisible({ visible: true, data: image });
|
setModalVisible({ visible: true, data: image });
|
||||||
}}
|
}}
|
||||||
@ -127,7 +127,7 @@ const renderMessage = ({ item, sessionId, setModalVisible, modalVisible }: Rende
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
{item.askAgain && item.askAgain.length > 0 && (
|
{/* {item.askAgain && item.askAgain.length > 0 && (
|
||||||
<View className={`mr-10`}>
|
<View className={`mr-10`}>
|
||||||
{item.askAgain.map((suggestion, index, array) => (
|
{item.askAgain.map((suggestion, index, array) => (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
@ -138,7 +138,7 @@ const renderMessage = ({ item, sessionId, setModalVisible, modalVisible }: Rende
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
)}
|
)} */}
|
||||||
<Modal
|
<Modal
|
||||||
animationType="fade"
|
animationType="fade"
|
||||||
transparent={true}
|
transparent={true}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Message, Video } from '@/types/ask';
|
import { Message, Video } from '@/types/ask';
|
||||||
import { MaterialItem } from '@/types/personal-info';
|
import { MaterialItem } from '@/types/personal-info';
|
||||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
import React, { memo, useCallback, useEffect, useMemo, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
FlatList,
|
FlatList,
|
||||||
SafeAreaView
|
SafeAreaView
|
||||||
@ -18,7 +18,7 @@ function ChatComponent({ userMessages, sessionId }: ChatProps) {
|
|||||||
const [modalVisible, setModalVisible] = React.useState({ visible: false, data: {} as Video | MaterialItem });
|
const [modalVisible, setModalVisible] = React.useState({ visible: false, data: {} as Video | MaterialItem });
|
||||||
|
|
||||||
// 使用 useCallback 缓存 keyExtractor 函数
|
// 使用 useCallback 缓存 keyExtractor 函数
|
||||||
const keyExtractor = useCallback((item: Message) => item.timestamp, []);
|
const keyExtractor = useCallback((item: Message) => `${item.role}-${item.timestamp}`, []);
|
||||||
|
|
||||||
// 使用 useMemo 缓存样式对象
|
// 使用 useMemo 缓存样式对象
|
||||||
const contentContainerStyle = useMemo(() => ({ padding: 16 }), []);
|
const contentContainerStyle = useMemo(() => ({ padding: 16 }), []);
|
||||||
@ -50,3 +50,6 @@ function ChatComponent({ userMessages, sessionId }: ChatProps) {
|
|||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 使用 React.memo 包装组件,避免不必要的重渲染
|
||||||
|
export default memo(ChatComponent);
|
||||||
135
components/ask/send.tsx
Normal file
135
components/ask/send.tsx
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
'use client';
|
||||||
|
import VoiceSvg from '@/assets/icons/svg/vioce.svg';
|
||||||
|
import React, { Dispatch, SetStateAction, useCallback, useState } from 'react';
|
||||||
|
import {
|
||||||
|
StyleSheet,
|
||||||
|
TextInput,
|
||||||
|
TouchableOpacity,
|
||||||
|
View
|
||||||
|
} from 'react-native';
|
||||||
|
|
||||||
|
import { fetchApi } from '@/lib/server-api-util';
|
||||||
|
import { Message } from '@/types/ask';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
setIsHello: (isHello: boolean) => void,
|
||||||
|
conversationId: string | null,
|
||||||
|
setUserMessages: Dispatch<SetStateAction<Message[]>>;
|
||||||
|
setConversationId: (conversationId: string) => void,
|
||||||
|
}
|
||||||
|
export default function SendMessage(props: Props) {
|
||||||
|
const { setIsHello, conversationId, setUserMessages, setConversationId } = props;
|
||||||
|
|
||||||
|
// 用户询问
|
||||||
|
const [inputValue, setInputValue] = useState('');
|
||||||
|
|
||||||
|
// 创建新对话并获取消息
|
||||||
|
const createNewConversation = useCallback(async (user_text: string) => {
|
||||||
|
const data = await fetchApi<string>("/chat/new", {
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
setConversationId(data);
|
||||||
|
await getConversation({ session_id: data, user_text });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 获取对话信息
|
||||||
|
const getConversation = useCallback(async ({ session_id, user_text }: { session_id: string, user_text: string }) => {
|
||||||
|
// 获取对话信息必须要有对话id
|
||||||
|
if (!session_id) return;
|
||||||
|
const response = await fetchApi<Message>(`/chat`, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
session_id,
|
||||||
|
user_text
|
||||||
|
})
|
||||||
|
});
|
||||||
|
setUserMessages((prev: Message[]) => [...prev, response]?.filter((item: Message) => item.content.text !== '正在寻找,请稍等...'));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 发送询问
|
||||||
|
const handleSubmit = () => {
|
||||||
|
const text = inputValue;
|
||||||
|
// 用户输入信息之后进行后续操作
|
||||||
|
if (text) {
|
||||||
|
// 将用户输入信息添加到消息列表中
|
||||||
|
setUserMessages(pre => ([...pre, {
|
||||||
|
content: {
|
||||||
|
text: text
|
||||||
|
},
|
||||||
|
role: 'User',
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: {
|
||||||
|
text: "正在寻找,请稍等..."
|
||||||
|
},
|
||||||
|
role: 'Assistant',
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
}
|
||||||
|
]));
|
||||||
|
// 如果没有对话ID,创建新对话并获取消息,否则直接获取消息
|
||||||
|
if (!conversationId) {
|
||||||
|
createNewConversation(text);
|
||||||
|
setIsHello(false);
|
||||||
|
} else {
|
||||||
|
getConversation({
|
||||||
|
session_id: conversationId,
|
||||||
|
user_text: text
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 将输入框清空
|
||||||
|
setInputValue('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View className="relative w-full">
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Ask MeMo Anything..."
|
||||||
|
placeholderTextColor="#999"
|
||||||
|
value={inputValue}
|
||||||
|
onChangeText={(text: string) => {
|
||||||
|
setInputValue(text);
|
||||||
|
}}
|
||||||
|
onSubmitEditing={handleSubmit}
|
||||||
|
// 调起的键盘类型
|
||||||
|
returnKeyType="send"
|
||||||
|
/>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={styles.voiceButton}
|
||||||
|
className={`absolute right-0 top-1/2 -translate-y-1/2 `} // 使用绝对定位将按钮放在输入框内右侧
|
||||||
|
>
|
||||||
|
<VoiceSvg />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: '#transparent',
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
borderColor: '#FF9500',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderRadius: 25,
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
paddingVertical: 12,
|
||||||
|
fontSize: 16,
|
||||||
|
width: '100%', // 确保输入框宽度撑满
|
||||||
|
paddingRight: 50
|
||||||
|
},
|
||||||
|
voiceButton: {
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
borderRadius: 20,
|
||||||
|
backgroundColor: '#FF9500',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginRight: 8, // 添加一点右边距
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -1,28 +1,29 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import React, { useCallback, useState } from 'react';
|
import React, { Dispatch, SetStateAction, useCallback, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Platform,
|
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
TextInput,
|
TextInput,
|
||||||
View
|
View
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
|
|
||||||
|
import { fetchApi } from '@/lib/server-api-util';
|
||||||
|
import { Message } from '@/types/ask';
|
||||||
import { RecordingPresets, useAudioRecorder } from 'expo-audio';
|
import { RecordingPresets, useAudioRecorder } from 'expo-audio';
|
||||||
interface Props {
|
interface Props {
|
||||||
setIsHello: (isHello: boolean) => void,
|
setIsHello: (isHello: boolean) => void,
|
||||||
setInputValue: (inputValue: string) => void,
|
|
||||||
inputValue: string,
|
|
||||||
createNewConversation: (user_text: string) => void,
|
|
||||||
conversationId: string | null,
|
conversationId: string | null,
|
||||||
getConversation: ({ user_text, session_id }: { user_text: string, session_id: string }) => void
|
setUserMessages: Dispatch<SetStateAction<Message[]>>;
|
||||||
|
setConversationId: (conversationId: string) => void,
|
||||||
}
|
}
|
||||||
export default function AudioRecordPlay(props: Props) {
|
export default function AudioRecordPlay(props: Props) {
|
||||||
const { setIsHello, setInputValue, inputValue, createNewConversation, conversationId, getConversation } = props;
|
const { setIsHello, conversationId, setUserMessages, setConversationId } = props;
|
||||||
const audioRecorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY);
|
const audioRecorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY);
|
||||||
const [isRecording, setIsRecording] = useState(false);
|
const [isRecording, setIsRecording] = useState(false);
|
||||||
const [isVoiceStart, setIsVoiceStart] = useState(false);
|
const [isVoiceStart, setIsVoiceStart] = useState(false);
|
||||||
const [elapsedTime, setElapsedTime] = useState(0);
|
const [elapsedTime, setElapsedTime] = useState(0);
|
||||||
|
|
||||||
|
// 用户询问
|
||||||
|
const [inputValue, setInputValue] = useState('');
|
||||||
const [timerInterval, setTimerInterval] = useState<NodeJS.Timeout | number>(0);
|
const [timerInterval, setTimerInterval] = useState<NodeJS.Timeout | number>(0);
|
||||||
|
|
||||||
const formatTime = (ms: number): string => {
|
const formatTime = (ms: number): string => {
|
||||||
@ -68,15 +69,39 @@ export default function AudioRecordPlay(props: Props) {
|
|||||||
// }
|
// }
|
||||||
// })();
|
// })();
|
||||||
// }, []);
|
// }, []);
|
||||||
// 使用 useCallback 缓存回调函数
|
// 获取对话信息
|
||||||
const handleChangeText = useCallback((text: string) => {
|
const createNewConversation = useCallback(async (user_text: string) => {
|
||||||
setInputValue(text);
|
const data = await fetchApi<string>("/chat/new", {
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
setConversationId(data);
|
||||||
|
await getConversation({ session_id: data, user_text });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getConversation = useCallback(async ({ session_id, user_text }: { session_id: string, user_text: string }) => {
|
||||||
|
if (!session_id) return;
|
||||||
|
const response = await fetchApi<Message>(`/chat`, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
session_id,
|
||||||
|
user_text
|
||||||
|
})
|
||||||
|
});
|
||||||
|
setUserMessages((prev: Message[]) => [...prev, response]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 使用 useCallback 缓存 handleSubmit
|
// 使用 useCallback 缓存 handleSubmit
|
||||||
const handleSubmit = useCallback(() => {
|
const handleSubmit = () => {
|
||||||
const text = inputValue.trim();
|
const text = inputValue;
|
||||||
if (text) {
|
if (text) {
|
||||||
|
setUserMessages(pre => ([...pre, {
|
||||||
|
content: {
|
||||||
|
text: text
|
||||||
|
},
|
||||||
|
role: 'User',
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
}
|
||||||
|
]));
|
||||||
if (!conversationId) {
|
if (!conversationId) {
|
||||||
createNewConversation(text);
|
createNewConversation(text);
|
||||||
setIsHello(false);
|
setIsHello(false);
|
||||||
@ -88,15 +113,7 @@ export default function AudioRecordPlay(props: Props) {
|
|||||||
}
|
}
|
||||||
setInputValue('');
|
setInputValue('');
|
||||||
}
|
}
|
||||||
}, [conversationId]);
|
|
||||||
|
|
||||||
// 使用 useCallback 缓存 handleKeyPress
|
|
||||||
const handleKeyPress = useCallback((e: any) => {
|
|
||||||
if (Platform.OS === 'web' && e.nativeEvent.key !== 'Enter') {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
handleSubmit();
|
|
||||||
}, [handleSubmit]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user