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< WhisperContext | undefined >(); const [cachedTranslator, setCachedTranslator] = useState< undefined | CachedTranslator >(); const [languageLabels, setLanguageLabels] = useState< | undefined | { hostNative: { host: string; guest: string; }; guestNative: { host: string; guest: string; }; } >(); // 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 cachedTranslator = new CachedTranslator( "en", conversation.guest.language, languageServer ); console.log("Set cached translator from %s", languageServer.baseUrl); setCachedTranslator(cachedTranslator); const settings = await Settings.getDefault(); const whisperFileLabel = (await settings.getWhisperModel()) || "small"; const whisperFile = WHISPER_FILES[whisperFileLabel]; if (!whisperFile) { throw new Error(`Could not find the whisper file with the label ${whisperFileLabel}`); } try { setWhisperContext( await initWhisper({ filePath: whisperFile.targetPath, }) ); } catch (err) { console.error(err) throw err; } // recorder settings (async () => { const status = await AudioModule.requestRecordingPermissionsAsync(); if (!status.granted) { Alert.alert("Permission to access microphone was denied"); } })(); setGuestSpeak(await cachedTranslator.translate("Speak")); const hostLang1 = languages[conversation.host.language].name; const guestLang1 = languages[conversation.host.language].name; const hostLang2 = await cachedTranslator.translate( languages[conversation.host.language].name ); const guestLang2 = await cachedTranslator.translate( languages[conversation.host.language].name ); setLanguageLabels({ hostNative: { host: hostLang1, guest: guestLang1, }, guestNative: { host: hostLang2, guest: guestLang2, }, }); } catch (err) { console.error( "Could not set translator from %s: %s", languageServer.baseUrl, err ); } })(); 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;