All checks were successful
Dev Deploy / Explore-Gitea-Actions (push) Successful in 15s
221 lines
7.1 KiB
TypeScript
221 lines
7.1 KiB
TypeScript
'use client';
|
|
import React, { Dispatch, SetStateAction, useCallback, useState } from 'react';
|
|
import {
|
|
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,
|
|
conversationId: string | null,
|
|
setUserMessages: Dispatch<SetStateAction<Message[]>>;
|
|
setConversationId: (conversationId: string) => void,
|
|
}
|
|
export default function AudioRecordPlay(props: 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 => {
|
|
const totalSeconds = ms / 1000;
|
|
const minutes = Math.floor(totalSeconds / 60);
|
|
const seconds = Math.floor(totalSeconds % 60);
|
|
const milliseconds = Math.floor(ms % 1000);
|
|
|
|
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}.${String(milliseconds).padStart(3, '0')}`;
|
|
};
|
|
// 开始录音
|
|
const record = async () => {
|
|
await audioRecorder.prepareToRecordAsync();
|
|
const startTime = Date.now();
|
|
|
|
// 每 10ms 更新一次时间
|
|
const interval = setInterval(() => {
|
|
const elapsed = Date.now() - startTime;
|
|
setElapsedTime(elapsed);
|
|
}, 10);
|
|
|
|
setTimerInterval(interval);
|
|
setIsVoiceStart(true)
|
|
audioRecorder.record();
|
|
setIsRecording(true);
|
|
};
|
|
|
|
const stopRecording = async () => {
|
|
// The recording will be available on `audioRecorder.uri`.
|
|
|
|
|
|
if (timerInterval) clearInterval(timerInterval);
|
|
setTimerInterval(0);
|
|
await audioRecorder.stop();
|
|
setIsRecording(false);
|
|
};
|
|
|
|
// useEffect(() => {
|
|
// (async () => {
|
|
// const status = await AudioModule.requestRecordingPermissionsAsync();
|
|
// if (!status.granted) {
|
|
// Alert.alert('Permission to access microphone was denied');
|
|
// }
|
|
// })();
|
|
// }, []);
|
|
// 获取对话信息
|
|
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 = () => {
|
|
const text = inputValue;
|
|
if (text) {
|
|
setUserMessages(pre => ([...pre, {
|
|
content: {
|
|
text: text
|
|
},
|
|
role: 'User',
|
|
timestamp: new Date().toISOString()
|
|
}
|
|
]));
|
|
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">
|
|
{/* <TouchableOpacity
|
|
onPress={() => console.log('Left icon pressed')}
|
|
className={`absolute left-2 top-1/2 -translate-y-1/2 p-2 bg-white rounded-full ${isVoiceStart ? "opacity-100" : "opacity-0"}`} // 使用绝对定位将按钮放在输入框内右侧
|
|
>
|
|
<VoiceDeleteSvg />
|
|
</TouchableOpacity> */}
|
|
<TextInput
|
|
style={styles.input}
|
|
placeholder="Ask MeMo Anything..."
|
|
placeholderTextColor="#999"
|
|
className={isVoiceStart ? 'bg-bgPrimary border-none pl-12' : ''}
|
|
value={isVoiceStart ? `· · · · · · · · · · · · · · ${formatTime(elapsedTime)}` : inputValue}
|
|
onChangeText={(text: string) => {
|
|
setInputValue(text);
|
|
}}
|
|
onSubmitEditing={handleSubmit}
|
|
editable={!isVoiceStart}
|
|
// 调起的键盘类型
|
|
returnKeyType="send"
|
|
/>
|
|
{/* <TouchableOpacity
|
|
style={styles.voiceButton}
|
|
className={`absolute right-0 top-1/2 -translate-y-1/2 ${isVoiceStart ? 'bg-white px-8' : 'bg-bgPrimary'}`} // 使用绝对定位将按钮放在输入框内右侧
|
|
onPress={isVoiceStart ? stopRecording : record}
|
|
>
|
|
{isVoiceStart ? <VoiceSendSvg /> : <VoiceSvg />}
|
|
</TouchableOpacity> */}
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
justifyContent: 'center',
|
|
backgroundColor: '#fff',
|
|
},
|
|
title: {
|
|
fontSize: 20,
|
|
fontWeight: 'bold',
|
|
marginBottom: 20,
|
|
textAlign: 'center',
|
|
},
|
|
recordButton: {
|
|
padding: 15,
|
|
borderRadius: 8,
|
|
alignItems: 'center',
|
|
marginBottom: 20,
|
|
},
|
|
startButton: {
|
|
backgroundColor: '#ff6b6b',
|
|
},
|
|
stopButton: {
|
|
backgroundColor: '#4CAF50',
|
|
},
|
|
buttonText: {
|
|
color: 'white',
|
|
fontSize: 16,
|
|
},
|
|
listTitle: {
|
|
fontWeight: 'bold',
|
|
marginBottom: 10,
|
|
},
|
|
emptyText: {
|
|
fontStyle: 'italic',
|
|
color: '#888',
|
|
marginBottom: 10,
|
|
},
|
|
recordingItem: {
|
|
padding: 10,
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: '#eee',
|
|
},
|
|
uriText: {
|
|
fontSize: 12,
|
|
color: '#777',
|
|
},
|
|
leftIcon: {
|
|
padding: 10,
|
|
paddingLeft: 15,
|
|
},
|
|
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, // 添加一点右边距
|
|
},
|
|
}); |