2025-07-07 13:42:11 +08:00

204 lines
6.5 KiB
TypeScript

'use client';
import React, { useCallback, useState } from 'react';
import {
Platform,
StyleSheet,
TextInput,
View
} from 'react-native';
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
}
export default function AudioRecordPlay(props: Props) {
const { setIsHello, setInputValue, inputValue, createNewConversation, conversationId, getConversation } = props;
const audioRecorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY);
const [isRecording, setIsRecording] = useState(false);
const [isVoiceStart, setIsVoiceStart] = useState(false);
const [elapsedTime, setElapsedTime] = useState(0);
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');
// }
// })();
// }, []);
// 使用 useCallback 缓存回调函数
const handleChangeText = useCallback((text: string) => {
setInputValue(text);
}, []);
// 使用 useCallback 缓存 handleSubmit
const handleSubmit = useCallback(() => {
const text = inputValue.trim();
if (text) {
if (!conversationId) {
createNewConversation(text);
setIsHello(false);
} else {
getConversation({
session_id: conversationId,
user_text: text
});
}
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}>
<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, // 添加一点右边距
},
});