feat: ask交互

This commit is contained in:
jinyaqiu 2025-07-30 11:30:34 +08:00
parent 479eecdc95
commit b261fbf970
5 changed files with 94 additions and 61 deletions

View File

@ -5,8 +5,8 @@ import SendMessage from "@/components/ask/send";
import { ThemedText } from "@/components/ThemedText";
import { fetchApi } from "@/lib/server-api-util";
import { Message } from "@/types/ask";
import { router, useLocalSearchParams } from "expo-router";
import { default as React, default as React, useCallback, useEffect, useRef, useState } from 'react';
import { router, useFocusEffect, useLocalSearchParams } from "expo-router";
import { useCallback, useEffect, useRef, useState } from 'react';
import {
Animated,
FlatList,
@ -116,7 +116,6 @@ export default function AskScreen() {
Animated.timing(fadeAnimChat, {
toValue: 1,
duration: 300,
duration: 300,
useNativeDriver: true,
})
]).start(() => {
@ -131,31 +130,17 @@ export default function AskScreen() {
useEffect(() => {
if (!isHello) {
const timer = setTimeout(() => {
scrollToEnd(false);
}, 300);
return () => clearTimeout(timer);
// 不再自动关闭键盘,让用户手动控制
// 这里可以添加其他需要在隐藏hello界面时执行的逻辑
scrollToEnd(false);
}
}, [isHello]);
useEffect(() => {
const timer = setTimeout(() => {
if (!isHello) {
try {
if (TextInput.State?.currentlyFocusedInput) {
const input = TextInput.State.currentlyFocusedInput();
if (input) input.blur();
}
} catch (error) {
console.log('失去焦点失败:', error);
}
scrollToEnd(false);
}
}, 200);
return () => clearTimeout(timer);
}, [isHello]);
useFocusEffect(
useCallback(() => {
setIsHello(true);
}, [])
);
return (
<View style={[styles.container, { paddingTop: insets.top }]}>
@ -299,7 +284,7 @@ const styles = StyleSheet.create({
padding: 16,
paddingBottom: 24,
backgroundColor: 'white',
borderTopWidth: 1,
borderTopColor: '#f0f0f0',
// borderTopWidth: 1,
// borderTopColor: '#f0f0f0',
},
});

3
assets/icons/svg/sun.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 3V1.5M9 15V16.5M4.81066 4.81066L3.75 3.75M13.296 13.296L14.3567 14.3567M3 9H1.5M15 9H16.5M13.2964 4.81066L14.357 3.75M4.81103 13.296L3.75037 14.3567M9 12.75C6.92893 12.75 5.25 11.0711 5.25 9C5.25 6.92893 6.92893 5.25 9 5.25C11.0711 5.25 12.75 6.92893 12.75 9C12.75 11.0711 11.0711 12.75 9 12.75Z" stroke="#FFB645" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 497 B

View File

@ -0,0 +1,3 @@
<svg width="15" height="16" viewBox="0 0 15 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.5 0.609375C3.35685 0.609375 0 3.96623 0 8.10938C0 12.2525 3.35685 15.6094 7.5 15.6094C11.6431 15.6094 15 12.2525 15 8.10938C15 3.96623 11.6431 0.609375 7.5 0.609375ZM9.91935 5.69002C10.4546 5.69002 10.8871 6.12248 10.8871 6.65776C10.8871 7.19304 10.4546 7.6255 9.91935 7.6255C9.38407 7.6255 8.95161 7.19304 8.95161 6.65776C8.95161 6.12248 9.38407 5.69002 9.91935 5.69002ZM5.08065 5.69002C5.61593 5.69002 6.04839 6.12248 6.04839 6.65776C6.04839 7.19304 5.61593 7.6255 5.08065 7.6255C4.54536 7.6255 4.1129 7.19304 4.1129 6.65776C4.1129 6.12248 4.54536 5.69002 5.08065 5.69002ZM10.9718 10.8372C10.1099 11.8715 8.84577 12.4642 7.5 12.4642C6.15423 12.4642 4.89012 11.8715 4.02823 10.8372C3.61694 10.3443 4.36089 9.72732 4.77218 10.2172C5.4496 11.0307 6.44153 11.4934 7.5 11.4934C8.55847 11.4934 9.5504 11.0277 10.2278 10.2172C10.6331 9.72732 11.38 10.3443 10.9718 10.8372Z" fill="#E2793F"/>
</svg>

After

Width:  |  Height:  |  Size: 1001 B

View File

@ -5,7 +5,8 @@ import { useTranslation } from 'react-i18next';
import {
FlatList,
FlatListProps,
SafeAreaView
SafeAreaView,
View
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import MessageItem from './aiChat';
@ -27,7 +28,11 @@ function ChatComponent(
const { t } = useTranslation();
const keyExtractor = useCallback((item: Message) => `${item.role}-${item.timestamp}`, []);
const contentContainerStyle = useMemo(() => ({ padding: 16, flexGrow: 1 }), []);
const contentContainerStyle = useMemo(() => ({
padding: 16,
flexGrow: 1,
paddingTop: 0,
}), []);
const [modalDetailsVisible, setModalDetailsVisible] = useState<boolean>(false);
@ -37,7 +42,14 @@ function ChatComponent(
ref={ref}
data={userMessages}
keyExtractor={keyExtractor}
renderItem={({ item }) => MessageItem({ t, setSelectedImages, selectedImages, insets, item, sessionId, modalVisible, setModalVisible, setModalDetailsVisible, modalDetailsVisible })}
renderItem={({ item, index }) => {
const itemStyle = index === 0 ? { marginTop: 16 } : {};
return (
<View style={itemStyle}>
{MessageItem({ t, setSelectedImages, selectedImages, insets, item, sessionId, modalVisible, setModalVisible, setModalDetailsVisible, modalDetailsVisible })}
</View>
);
}}
contentContainerStyle={contentContainerStyle}
keyboardDismissMode="interactive"
keyboardShouldPersistTaps="handled"

View File

@ -1,8 +1,12 @@
'use client';
import SendSvg from '@/assets/icons/svg/send.svg';
import React, { Dispatch, SetStateAction, useCallback, useEffect, useState } from 'react';
import SunSvg from '@/assets/icons/svg/sun.svg';
import VideoSvg from '@/assets/icons/svg/video.svg';
import React, { Dispatch, SetStateAction, useCallback, useEffect, useRef, useState } from 'react';
import {
EventSubscription,
Keyboard,
ScrollView,
StyleSheet,
TextInput,
TouchableOpacity,
@ -11,6 +15,7 @@ import {
import { fetchApi } from '@/lib/server-api-util';
import { Message } from '@/types/ask';
import { ThemedText } from '../ThemedText';
interface Props {
setIsHello: Dispatch<SetStateAction<boolean>>,
@ -52,9 +57,36 @@ export default function SendMessage(props: Props) {
setUserMessages((prev: Message[]) => [...prev, response]?.filter((item: Message) => item.content.text !== '正在寻找,请稍等...'));
}, []);
// 添加一个ref来跟踪键盘状态
const keyboardDidShowListener = useRef<EventSubscription | null>(null);
const keyboardDidHideListener = useRef<EventSubscription | null>(null);
const isKeyboardVisible = useRef(false);
useEffect(() => {
// 使用keyboardWillShow而不是keyboardDidShow这样可以在键盘完全显示前更新UI
const showSubscription = Keyboard.addListener('keyboardWillShow', () => {
isKeyboardVisible.current = true;
if (!conversationId) {
// 确保在下一个事件循环中更新状态,避免可能的渲染问题
requestAnimationFrame(() => {
setIsHello(false);
});
}
});
const hideSubscription = Keyboard.addListener('keyboardWillHide', () => {
isKeyboardVisible.current = false;
});
return () => {
showSubscription.remove();
hideSubscription.remove();
};
}, [conversationId]);
// 发送询问
const handleSubmit = () => {
const text = inputValue;
const handleSubmit = useCallback(() => {
const text = inputValue.trim();
// 用户输入信息之后进行后续操作
if (text) {
// 将用户输入信息添加到消息列表中
@ -85,38 +117,25 @@ export default function SendMessage(props: Props) {
}
// 将输入框清空
setInputValue('');
// 关闭键盘
Keyboard.dismiss();
}
}
useEffect(() => {
const keyboardWillShowListener = Keyboard.addListener(
'keyboardWillShow',
() => {
if (!conversationId) {
console.log('Keyboard will show');
setIsHello(false);
setUserMessages([{
content: {
text: "快来寻找你的记忆吧。。。"
},
role: 'Assistant',
timestamp: new Date().toISOString()
}])
} else {
}
// 只有在键盘可见时才关闭键盘
if (isKeyboardVisible.current) {
Keyboard.dismiss();
}
);
return () => {
keyboardWillShowListener.remove();
};
}, [conversationId]);
}
}, [inputValue, conversationId, selectedImages, createNewConversation, getConversation]);
return (
<View style={styles.container}>
<View className="relative w-full">
<ScrollView horizontal={true}>
<TouchableOpacity style={[styles.button, { borderColor: '#FFB645' }]}>
<SunSvg width={18} height={18} />
<ThemedText></ThemedText>
</TouchableOpacity><TouchableOpacity style={[styles.button, { borderColor: '#E2793F' }]}>
<VideoSvg width={18} height={18} />
<ThemedText></ThemedText>
</TouchableOpacity>
</ScrollView>
<TextInput
style={styles.input}
placeholder="Ask MeMo Anything..."
@ -132,7 +151,7 @@ export default function SendMessage(props: Props) {
<TouchableOpacity
style={styles.voiceButton}
onPress={handleSubmit}
className={`absolute right-0 top-1/2 -translate-y-1/2 `} // 使用绝对定位将按钮放在输入框内右侧
className={`absolute right-0 bottom-0`} // 使用绝对定位将按钮放在输入框内右侧
>
<View style={{ transform: [{ rotate: '330deg' }] }}>
<SendSvg color={'white'} width={24} height={24} />
@ -144,6 +163,17 @@ export default function SendMessage(props: Props) {
}
const styles = StyleSheet.create({
button: {
paddingHorizontal: 8,
paddingVertical: 4,
margin: 5,
borderRadius: 25,
alignItems: 'center',
borderWidth: 2,
display: 'flex',
flexDirection: 'row',
gap: 5
},
container: {
justifyContent: 'center',
backgroundColor: '#transparent',