give up on downloader idea. Use file from assets.
This commit is contained in:
@ -1,14 +1,24 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Alert, ScrollView, StyleSheet, Text, TouchableHighlight, View } from "react-native";
|
||||
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 {
|
||||
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';
|
||||
|
||||
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
|
||||
@ -19,11 +29,19 @@ const lasOptions = {
|
||||
};
|
||||
// LiveAudioStream.init(lasOptions as any);
|
||||
|
||||
const ConversationThread = ({ route }: { route?: Route<"Conversation", { conversation: Conversation }> }) => {
|
||||
const ConversationThread = ({
|
||||
route,
|
||||
}: {
|
||||
route?: Route<"Conversation", { conversation: Conversation }>;
|
||||
}) => {
|
||||
const navigation = useNavigation();
|
||||
|
||||
if (!route) {
|
||||
return (<View><Text>Missing Params!</Text></View>)
|
||||
return (
|
||||
<View>
|
||||
<Text>Missing Params!</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/* 2. Get the param */
|
||||
@ -32,21 +50,26 @@ const ConversationThread = ({ route }: { route?: Route<"Conversation", { convers
|
||||
const [messages, setMessages] = useState<Message[]>([]);
|
||||
const [guestSpeak, setGuestSpeak] = useState<string | undefined>();
|
||||
const [guestSpeakLoaded, setGuestSpeakLoaded] = useState<boolean>(false);
|
||||
const [whisperContext, setWhisperContext] = useState<WhisperContext | undefined>();
|
||||
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,
|
||||
}
|
||||
}>()
|
||||
const [languageLabels, setLanguageLabels] = useState<
|
||||
| undefined
|
||||
| {
|
||||
hostNative: {
|
||||
host: string;
|
||||
guest: string;
|
||||
};
|
||||
guestNative: {
|
||||
host: string;
|
||||
guest: string;
|
||||
};
|
||||
}
|
||||
>();
|
||||
|
||||
// recorder settings
|
||||
const audioRecorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY);
|
||||
@ -63,50 +86,70 @@ const ConversationThread = ({ route }: { route?: Route<"Conversation", { convers
|
||||
|
||||
useEffect(() => {
|
||||
(async function () {
|
||||
|
||||
const languageServer = await LanguageServer.getDefault();
|
||||
try {
|
||||
const languages = await languageServer.fetchLanguages(5000);
|
||||
const cc = new CachedTranslator(
|
||||
const cachedTranslator = new CachedTranslator(
|
||||
"en",
|
||||
conversation.guest.language,
|
||||
languageServer,
|
||||
)
|
||||
console.log("Set cached translator from %s", languageServer.baseUrl)
|
||||
setCachedTranslator(cc);
|
||||
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)
|
||||
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) => {
|
||||
@ -114,7 +157,7 @@ const ConversationThread = ({ route }: { route?: Route<"Conversation", { convers
|
||||
};
|
||||
|
||||
if (!conversation) {
|
||||
console.warn("Conversation is null or undefined.")
|
||||
console.warn("Conversation is null or undefined.");
|
||||
}
|
||||
|
||||
conversation.on("add_message", updateMessages);
|
||||
@ -133,11 +176,17 @@ const ConversationThread = ({ route }: { route?: Route<"Conversation", { convers
|
||||
|
||||
return cachedTranslator ? (
|
||||
<View style={{ flex: 1, flexDirection: "column" }}>
|
||||
{languageLabels && (<View style={styles.languageLabels}>
|
||||
<Text style={styles.nativeHostLabel}>{languageLabels.hostNative.host} / {languageLabels.hostNative.guest}</Text>
|
||||
<Text style={styles.nativeGuestLabel}>{languageLabels.guestNative.host} / {languageLabels.guestNative.guest}</Text>
|
||||
</View>)
|
||||
}
|
||||
{languageLabels && (
|
||||
<View style={styles.languageLabels}>
|
||||
<Text style={styles.nativeHostLabel}>
|
||||
{languageLabels.hostNative.host} / {languageLabels.hostNative.guest}
|
||||
</Text>
|
||||
<Text style={styles.nativeGuestLabel}>
|
||||
{languageLabels.guestNative.host} /{" "}
|
||||
{languageLabels.guestNative.guest}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
<ScrollView
|
||||
style={{
|
||||
borderColor: "black",
|
||||
@ -177,15 +226,9 @@ const ConversationThread = ({ route }: { route?: Route<"Conversation", { convers
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
languageLabels: {
|
||||
|
||||
},
|
||||
nativeHostLabel: {
|
||||
|
||||
},
|
||||
nativeGuestLabel: {
|
||||
|
||||
},
|
||||
})
|
||||
languageLabels: {},
|
||||
nativeHostLabel: {},
|
||||
nativeGuestLabel: {},
|
||||
});
|
||||
|
||||
export default ConversationThread;
|
||||
|
@ -36,8 +36,6 @@ const SettingsComponent = () => {
|
||||
const [whisperModel, setWhisperModel] =
|
||||
useState<keyof typeof WHISPER_MODELS>("small");
|
||||
const [whisperFile, setWhisperFile] = useState<WhisperFile | undefined>();
|
||||
const [whisperFileExists, setWhisperFileExists] = useState<boolean>(false);
|
||||
const [isWhisperHashValid, setIsWhisperHashValid] = useState<boolean>(false);
|
||||
const [downloader, setDownloader] = useState<any>(null);
|
||||
const [bytesDone, setBytesDone] = useState<number | undefined>();
|
||||
const [bytesRemaining, setBytesRemaining] = useState<number | undefined>();
|
||||
@ -54,12 +52,13 @@ const SettingsComponent = () => {
|
||||
);
|
||||
setWhisperModel((await settings.getWhisperModel()) || "small");
|
||||
setWhisperFile(WHISPER_FILES[whisperModel]);
|
||||
if (whisperFile) {
|
||||
await whisperFile.syncHfMetadata();
|
||||
await whisperFile.updateTargetExistence();
|
||||
await whisperFile.updateTargetHash();
|
||||
setWhisperFileExists(whisperFile.does_target_exist)
|
||||
if (!whisperFile) {
|
||||
throw new Error("Invalid Whisper file!");
|
||||
}
|
||||
await whisperFile.syncHfMetadata();
|
||||
await whisperFile.updateTargetExistence();
|
||||
await whisperFile.updateTargetHash();
|
||||
console.log("Does %s exist? part=%s, target=%s", whisperFile.label, whisperFile.does_part_target_exist, whisperFile.does_target_exist)
|
||||
})();
|
||||
}, [whisperFile]);
|
||||
|
||||
@ -100,19 +99,21 @@ const SettingsComponent = () => {
|
||||
// await wFile.updateTargetHash();
|
||||
// setIsWhisperHashValid(wFile.isHashValid);
|
||||
setWhisperFile(wFile);
|
||||
setWhisperFileExists(wFile.does_target_exist);
|
||||
};
|
||||
|
||||
const doSetDownloadStatus = (arg0: WhisperFile) => {
|
||||
console.log("Downloading ....")
|
||||
setIsWhisperHashValid(arg0.isHashValid);
|
||||
// console.log("Downloading ....");
|
||||
setBytesDone(arg0.download_data?.totalBytesWritten);
|
||||
setBytesRemaining(arg0.download_data?.totalBytesExpectedToWrite);
|
||||
};
|
||||
|
||||
const doOnComplete = (arg0: WhisperFile) => {
|
||||
const doOnComplete = async (arg0: WhisperFile) => {
|
||||
console.log("✅ Download complete.");
|
||||
setDownloader(undefined);
|
||||
await arg0.updateTargetExistence();
|
||||
setWhisperFile(arg0);
|
||||
}
|
||||
await whisperFile?.updateTargetExistence();
|
||||
};
|
||||
|
||||
const doDownload = async () => {
|
||||
if (!whisperModel) {
|
||||
@ -144,7 +145,7 @@ const SettingsComponent = () => {
|
||||
const doDelete = async () => {
|
||||
const whisperFile = WHISPER_MODELS[whisperModel];
|
||||
whisperFile.delete();
|
||||
setStatusTimeout(undefined);
|
||||
await whisperFile.updateTargetExistence();
|
||||
};
|
||||
|
||||
return hostLanguage && libretranslateBaseUrl ? (
|
||||
@ -194,36 +195,82 @@ const SettingsComponent = () => {
|
||||
))}
|
||||
</Picker>
|
||||
<View style={styles.downloadButtonWrapper}>
|
||||
{((!downloader) && whisperFile) &&
|
||||
(whisperFile.does_target_exist && (<Pressable onPress={doDelete} style={styles.deleteButton}>
|
||||
<Text style={styles.buttonText}>DELETE {whisperModel.toUpperCase()}</Text>
|
||||
</Pressable>))
|
||||
}
|
||||
<Pressable onPress={doDownload} style={styles.downloadButton}>
|
||||
<Text style={styles.buttonText}>DOWNLOAD {whisperModel.toUpperCase()}</Text>
|
||||
</Pressable>
|
||||
))
|
||||
|
||||
{
|
||||
downloader && (
|
||||
<Pressable onPress={doStopDownload} style={styles.pauseDownloadButton}>
|
||||
<Text style={styles.buttonText}>STOP DOWNLOAD</Text>
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
{bytesDone && bytesRemaining && whisperFile?.does_part_target_exist && (
|
||||
<View>
|
||||
{whisperFile &&
|
||||
(<Text>
|
||||
Downloading to {whisperFile.targetPath}
|
||||
</Text>)}
|
||||
<Text>
|
||||
{bytesDone} of{" "}
|
||||
{bytesRemaining} (
|
||||
{bytesDone / bytesRemaining * 100} %){" "}
|
||||
{/* The target is completely downloaded */}
|
||||
{!downloader && whisperFile?.does_target_exist && (
|
||||
<Pressable onPress={doDelete} style={styles.deleteButton}>
|
||||
<Text style={styles.buttonText}>
|
||||
DELETE {whisperModel.toUpperCase()}
|
||||
</Text>
|
||||
</View>
|
||||
</Pressable>
|
||||
)}
|
||||
|
||||
{/* The target "part" is present and is downloading */}
|
||||
|
||||
{downloader && whisperFile?.does_part_target_exist && (
|
||||
<Pressable
|
||||
onPress={doStopDownload}
|
||||
style={styles.pauseDownloadButton}
|
||||
>
|
||||
<Text style={styles.buttonText}>STOP DOWNLOAD</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
|
||||
{/* The target "part" is present and we are NOT downloading */}
|
||||
|
||||
{!downloader && whisperFile?.does_part_target_exist && (
|
||||
<>
|
||||
<Pressable onPress={doDownload} style={styles.downloadButton}>
|
||||
<Text style={styles.buttonText}>RESUME DOWNLOAD</Text>
|
||||
</Pressable>
|
||||
<Pressable onPress={doDelete} style={styles.deleteButton}>
|
||||
<Text style={styles.buttonText}>
|
||||
DELETE {whisperModel.toUpperCase()}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Anything else -- usually if the file has not yet been downloaded */}
|
||||
|
||||
{!(
|
||||
downloader ||
|
||||
whisperFile?.does_target_exist ||
|
||||
whisperFile?.does_part_target_exist
|
||||
) && (
|
||||
<Pressable onPress={doDownload} style={styles.downloadButton}>
|
||||
<Text style={styles.buttonText}>
|
||||
DOWNLOAD {whisperModel.toUpperCase()}
|
||||
</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
|
||||
{downloader &&
|
||||
bytesDone &&
|
||||
bytesRemaining &&
|
||||
whisperFile?.does_part_target_exist && (
|
||||
<View>
|
||||
{whisperFile && (
|
||||
<Text>Downloading to {whisperFile.targetPath}</Text>
|
||||
)}
|
||||
<Text>
|
||||
{bytesDone} of {bytesRemaining} (
|
||||
{(bytesDone / bytesRemaining) * 100} %){" "}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<Text>Debug Panel</Text>
|
||||
<Text>downloader is null? {downloader ? "no" : "yes"}</Text>
|
||||
<Text>
|
||||
whisperFile.does_target_exist{" "}
|
||||
{whisperFile?.does_target_exist ? "yes" : "no"}
|
||||
</Text>
|
||||
<Text>
|
||||
whisperFile.does_part_target_exist{" "}
|
||||
{whisperFile?.does_part_target_exist ? "yes" : "no"}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
@ -239,11 +286,12 @@ const styles = StyleSheet.create({
|
||||
flexDirection: "row",
|
||||
},
|
||||
downloadButton: {
|
||||
backgroundColor: "darkblue",
|
||||
backgroundColor: "#236b9f",
|
||||
padding: 20,
|
||||
margin: 10,
|
||||
flex: 3,
|
||||
flex: 1,
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
},
|
||||
deleteButton: {
|
||||
backgroundColor: "darkred",
|
||||
@ -261,11 +309,11 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
buttonText: {
|
||||
color: "#fff",
|
||||
flex: 1,
|
||||
fontSize: 16,
|
||||
alignSelf: "center",
|
||||
textAlign: "center",
|
||||
textAlignVertical: "top",
|
||||
// flex: 1,
|
||||
// fontSize: 16,
|
||||
// alignSelf: "center",
|
||||
// textAlign: "center",
|
||||
// textAlignVertical: "top",
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
|
Reference in New Issue
Block a user