import React, { useState, useEffect } from "react"; import { Alert, ScrollView, StyleSheet, Text, TouchableHighlight, View } from "react-native"; import { useNavigation, Route } from "@react-navigation/native"; import { Conversation, Message } from "@/app/lib/conversation"; import MessageBubble from "@/components/ui/MessageBubble"; import { CachedTranslator, LanguageServer, language_matrix_entry } from "@/app/i18n/api"; import { Settings } from "@/app/lib/settings"; import { WHISPER_FILES } from "@/app/lib/whisper"; import { initWhisper, WhisperContext } from 'whisper.rn' import { useAudioRecorder, AudioModule, RecordingPresets } from 'expo-audio'; const lasOptions = { sampleRate: 32000, // default is 44100 but 32000 is adequate for accurate voice recognition channels: 1, // 1 or 2, default 1 bitsPerSample: 16, // 8 or 16, default 16 audioSource: 6, // android only (see below) bufferSize: 4096, // default is 2048 }; // LiveAudioStream.init(lasOptions as any); const ConversationThread = ({ route }: { route?: Route<"Conversation", { conversation: Conversation }> }) => { const navigation = useNavigation(); if (!route) { return (Missing Params!) } /* 2. Get the param */ const { conversation } = route?.params; const [messages, setMessages] = useState([]); const [guestSpeak, setGuestSpeak] = useState(); const [guestSpeakLoaded, setGuestSpeakLoaded] = useState(false); const [whisperContext, setWhisperContext] = useState(); const [cachedTranslator, setCachedTranslator] = useState< undefined | CachedTranslator >(); const [languageLabels, setLanguageLabels] = useState() // recorder settings const audioRecorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY); const record = async () => { await audioRecorder.prepareToRecordAsync(); audioRecorder.record(); }; const stopRecording = async () => { // The recording will be available on `audioRecorder.uri`. await audioRecorder.stop(); }; useEffect(() => { (async function () { const languageServer = await LanguageServer.getDefault(); try { const languages = await languageServer.fetchLanguages(5000); const cc = new CachedTranslator( "en", conversation.guest.language, languageServer, ) console.log("Set cached translator from %s", languageServer.baseUrl) setCachedTranslator(cc); } catch (err) { console.error("Could not set translator from %s: %s", languageServer.baseUrl, err) } const settings = await Settings.getDefault(); const whisperFile = WHISPER_FILES[await settings.getWhisperModel() || "en"]; setWhisperContext(await initWhisper({ filePath: whisperFile.targetPath, })); // recorder settings (async () => { const status = await AudioModule.requestRecordingPermissionsAsync(); if (!status.granted) { Alert.alert('Permission to access microphone was denied'); } })(); setGuestSpeak(await cc.translate("Speak")); const hostLang1 = languages[conversation.host.language].name; const guestLang1 = languages[conversation.host.language].name; const hostLang2 = await cc.translate(languages[conversation.host.language].name); const guestLang2 = await cc.translate(languages[conversation.host.language].name); setLanguageLabels({ hostNative: { host: hostLang1, guest: guestLang1, }, guestNative: { host: hostLang2, guest: guestLang2, } }) })(); const updateMessages = (c: Conversation) => { setMessages([...c]); }; if (!conversation) { console.warn("Conversation is null or undefined.") } conversation.on("add_message", updateMessages); conversation.on("translation_done", updateMessages); // return () => { // conversation.on("add_message", undefined); // conversation.on("translation_done", undefined); // }; }, [conversation, guestSpeak]); const renderMessages = () => messages.map((message, index) => ( )); return cachedTranslator ? ( {languageLabels && ( {languageLabels.hostNative.host} / {languageLabels.hostNative.guest} {languageLabels.guestNative.host} / {languageLabels.guestNative.guest} ) } {renderMessages()} Speak Go Back {guestSpeak ? guestSpeak : "Speak"} ) : ( Loading... ); }; const styles = StyleSheet.create({ languageLabels: { }, nativeHostLabel: { }, nativeGuestLabel: { }, }) export default ConversationThread;