feat: 0.4版本
All checks were successful
Dev Deploy / Explore-Gitea-Actions (push) Successful in 15s

This commit is contained in:
jinyaqiu 2025-07-07 13:44:44 +08:00
parent ae14e05533
commit 11b0c051d1
5 changed files with 184 additions and 45 deletions

View File

@ -1,7 +1,7 @@
import ReturnArrow from "@/assets/icons/svg/returnArrow.svg";
import Chat from "@/components/ask/chat";
import AskHello from "@/components/ask/hello";
import AudioRecordPlay from "@/components/ask/voice";
import SendMessage from "@/components/ask/send";
import { ThemedText } from "@/components/ThemedText";
import { fetchApi } from "@/lib/server-api-util";
import { Message } from "@/types/ask";
@ -105,23 +105,7 @@ export default function AskScreen() {
{/* 功能区 - 放在 KeyboardAvoidingView 内但在 ScrollView 外 */}
<View className="w-full px-[1.5rem] mb-[2rem]">
<ScrollView
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} />
<SendMessage setUserMessages={setUserMessages} setConversationId={setConversationId} setIsHello={setIsHello} conversationId={conversationId} />
</View>
</KeyboardAvoidingView>
</View>

View File

@ -95,7 +95,7 @@ const renderMessage = ({ item, sessionId, setModalVisible, modalVisible }: Rende
<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) => (
<Pressable
key={image.id}
key={`${image.role}-${image.timestamp}`}
onPress={() => {
setModalVisible({ visible: true, data: image });
}}
@ -127,7 +127,7 @@ const renderMessage = ({ item, sessionId, setModalVisible, modalVisible }: Rende
)}
</View>
</View>
{item.askAgain && item.askAgain.length > 0 && (
{/* {item.askAgain && item.askAgain.length > 0 && (
<View className={`mr-10`}>
{item.askAgain.map((suggestion, index, array) => (
<TouchableOpacity
@ -138,7 +138,7 @@ const renderMessage = ({ item, sessionId, setModalVisible, modalVisible }: Rende
</TouchableOpacity>
))}
</View>
)}
)} */}
<Modal
animationType="fade"
transparent={true}

View File

@ -1,6 +1,6 @@
import { Message, Video } from '@/types/ask';
import { MaterialItem } from '@/types/personal-info';
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import React, { memo, useCallback, useEffect, useMemo, useRef } from 'react';
import {
FlatList,
SafeAreaView
@ -18,7 +18,7 @@ function ChatComponent({ userMessages, sessionId }: ChatProps) {
const [modalVisible, setModalVisible] = React.useState({ visible: false, data: {} as Video | MaterialItem });
// 使用 useCallback 缓存 keyExtractor 函数
const keyExtractor = useCallback((item: Message) => item.timestamp, []);
const keyExtractor = useCallback((item: Message) => `${item.role}-${item.timestamp}`, []);
// 使用 useMemo 缓存样式对象
const contentContainerStyle = useMemo(() => ({ padding: 16 }), []);
@ -49,4 +49,7 @@ function ChatComponent({ userMessages, sessionId }: ChatProps) {
/>
</SafeAreaView>
);
}
}
// 使用 React.memo 包装组件,避免不必要的重渲染
export default memo(ChatComponent);

135
components/ask/send.tsx Normal file
View 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, // 添加一点右边距
},
});

View File

@ -1,28 +1,29 @@
'use client';
import React, { useCallback, useState } from 'react';
import React, { Dispatch, SetStateAction, useCallback, useState } from 'react';
import {
Platform,
StyleSheet,
TextInput,
View
} from 'react-native';
import { fetchApi } from '@/lib/server-api-util';
import { Message } from '@/types/ask';
import { RecordingPresets, useAudioRecorder } from 'expo-audio';
interface Props {
setIsHello: (isHello: boolean) => void,
setInputValue: (inputValue: string) => void,
inputValue: string,
createNewConversation: (user_text: string) => void,
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) {
const { setIsHello, setInputValue, inputValue, createNewConversation, conversationId, getConversation } = props;
const { setIsHello, conversationId, setUserMessages, setConversationId } = props;
const audioRecorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY);
const [isRecording, setIsRecording] = useState(false);
const [isVoiceStart, setIsVoiceStart] = useState(false);
const [elapsedTime, setElapsedTime] = useState(0);
// 用户询问
const [inputValue, setInputValue] = useState('');
const [timerInterval, setTimerInterval] = useState<NodeJS.Timeout | number>(0);
const formatTime = (ms: number): string => {
@ -68,15 +69,39 @@ export default function AudioRecordPlay(props: Props) {
// }
// })();
// }, []);
// 使用 useCallback 缓存回调函数
const handleChangeText = useCallback((text: string) => {
setInputValue(text);
// 获取对话信息
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 }) => {
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
const handleSubmit = useCallback(() => {
const text = inputValue.trim();
const handleSubmit = () => {
const text = inputValue;
if (text) {
setUserMessages(pre => ([...pre, {
content: {
text: text
},
role: 'User',
timestamp: new Date().toISOString()
}
]));
if (!conversationId) {
createNewConversation(text);
setIsHello(false);
@ -88,15 +113,7 @@ export default function AudioRecordPlay(props: Props) {
}
setInputValue('');
}
}, [conversationId]);
// 使用 useCallback 缓存 handleKeyPress
const handleKeyPress = useCallback((e: any) => {
if (Platform.OS === 'web' && e.nativeEvent.key !== 'Enter') {
return;
}
handleSubmit();
}, [handleSubmit]);
}
return (
<View style={styles.container}>