add whisper download utils. add react-navigator.

This commit is contained in:
Jordan
2025-02-16 19:55:26 -08:00
parent 081ac367ba
commit bc3d481d25
11 changed files with 682 additions and 85 deletions

View File

@ -1,71 +1,71 @@
import React, { useState, useEffect } from "react";
import { ScrollView, 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 { WhisperContext } from "whisper.rn";
import { NavigationProp, ParamListBase } from "@react-navigation/native";
import {
CachedTranslator,
language_matrix_entry,
Translator,
} from "@/app/i18n/api";
import { CachedTranslator, LanguageServer } from "@/app/i18n/api";
import { getDb } from "@/app/lib/db";
import LiveAudioStream from 'react-native-live-audio-stream';
import LiveAudioStream from "react-native-live-audio-stream";
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
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);
interface ConversationThreadProps {
conversation: Conversation;
whisperContext: WhisperContext;
onGoBack?: () => any;
}
const ConversationThread = ({ route } : {route?: Route<"Conversation", {conversation : Conversation}>}) => {
const navigation = useNavigation();
if (!route) {
return (<View><Text>Missing Params!</Text></View>)
}
/* 2. Get the param */
const { conversation } = route?.params;
const ConversationThread = (p: ConversationThreadProps) => {
const [messages, setMessages] = useState<Message[]>([]);
const [guestSpeak, setGuestSpeak] = useState<string | undefined>();
const [guestSpeakLoaded, setGuestSpeakLoaded] = useState<boolean>(false);
const ct = new CachedTranslator("en", p.conversation.guest.language);
const [cachedTranslator, setCachedTranslator] = useState<
undefined | CachedTranslator
>();
useEffect(() => {
(async () => {
setCachedTranslator(
new CachedTranslator(
"en",
conversation.guest.language,
await LanguageServer.getDefault()
)
);
if (!cachedTranslator) throw new Error("cachedTranslator is undefined");
setGuestSpeak(await cachedTranslator.translate("Speak"));
})();
const updateMessages = (c: Conversation) => {
setMessages([...c]);
};
p.conversation.onAddMessage = updateMessages;
p.conversation.onTranslationDone = updateMessages;
conversation.onAddMessage = updateMessages;
conversation.onTranslationDone = updateMessages;
return () => {
p.conversation.onAddMessage = undefined;
p.conversation.onTranslationDone = undefined;
conversation.onAddMessage = undefined;
conversation.onTranslationDone = undefined;
};
}, [p.conversation, guestSpeak]);
useEffect(() => {
const fetchData = async () => {
setGuestSpeak(await ct.translate("Speak"));
}
fetchData();
}, [guestSpeak])
}, [conversation, guestSpeak]);
const renderMessages = () =>
messages.map((message, index) => (
<MessageBubble key={index} message={message} />
));
function onGoBack() {
p.onGoBack && p.onGoBack();
}
return (
return cachedTranslator ? (
<View style={{ flex: 1, flexDirection: "column" }}>
<ScrollView
style={{
@ -85,7 +85,7 @@ const ConversationThread = (p: ConversationThreadProps) => {
</TouchableHighlight>
<TouchableHighlight
style={{ backgroundColor: "gray", padding: 3, borderRadius: 5 }}
onPress={onGoBack}
onPress={navigation.goBack}
>
<Text style={{ color: "white", fontSize: 30 }}>Go Back</Text>
</TouchableHighlight>
@ -98,6 +98,10 @@ const ConversationThread = (p: ConversationThreadProps) => {
</TouchableHighlight>
</View>
</View>
) : (
<View>
<Text>Loading...</Text>
</View>
);
};

View File

@ -1,12 +1,13 @@
// Import necessary packages
import React, { useState, useEffect } from "react";
import { View, Text, TextInput, StyleSheet } from "react-native"; // Add Picker import
import { View, Text, TextInput, StyleSheet, Pressable } from "react-native"; // Add Picker import
import { getDb } from "@/app/lib/db";
import { Settings } from "@/app/lib/settings";
import { LanguageServer } from "@/app/i18n/api";
import {Picker} from "@react-native-picker/picker"
import { longLang } from "@/app/i18n/lang";
import { LIBRETRANSLATE_BASE_URL } from "@/constants/api";
import { WHISPER_MODELS, downloadWhisperModel, download_status, getWhisperDownloadStatus, whisper_model_tag_t } from "@/app/lib/whisper";
type Language = {
code: string;
@ -22,6 +23,8 @@ const SettingsComponent: React.FC = () => {
const [libretranslateBaseUrl, setLibretranslateBaseUrl] = useState<string | null>(null);
const [languages, setLanguages] = useState<undefined|LanguageMatrix>();
const [isLoaded, setIsLoaded] = useState<boolean>(false);
const [whisperModel, setWhisperModel] = useState<undefined|whisper_model_tag_t>()
const [downloadStatus, setDownloadStatus] = useState<undefined|download_status>();
useEffect(() => {
(async () => {
@ -33,16 +36,35 @@ const SettingsComponent: React.FC = () => {
const hostLang = await settings.getHostLanguage();
const libretranslateUrl = await settings.getLibretranslateBaseUrl();
const langServer = new LanguageServer(libretranslateBaseUrl || LIBRETRANSLATE_BASE_URL);
const wModel = await settings.getWhisperModel()
// Fetch languages from API
const langData = await langServer.fetchLanguages();
setLanguages(langData);
setHostLanguage(hostLang || "en");
setLibretranslateBaseUrl(libretranslateUrl);
setWhisperModel(wModel);
setIsLoaded(true);
})();
// Check for whether a model is currently downloading and set the status.
setInterval(async () => {
if (!whisperModel) return null;
const dlStatus = await getWhisperDownloadStatus(whisperModel);
setDownloadStatus(dlStatus)
}, 200);
}, []);
const doReadownload = async () => {
if (!whisperModel) return;
await downloadWhisperModel(whisperModel, {force_redownload: true});
}
const doDownload = async () => {
if (!whisperModel) return;
await downloadWhisperModel(whisperModel)
}
const handleHostLanguageChange = async (value: string) => {
setHostLanguage(value);
@ -86,6 +108,38 @@ const SettingsComponent: React.FC = () => {
onChangeText={handleLibretranslateBaseUrlChange}
accessibilityHint="libretranslate base url"
/>
<Picker
selectedValue={whisperModel || ""}
style={{ height: 50, width: "100%" }}
onValueChange={setWhisperModel}
accessibilityHint="language"
>
{Object.entries(WHISPER_MODELS).map(([key, {label}]) => (
<Picker.Item key={key} label={label} value={key} />
))}
</Picker>
{whisperModel && (
<View>
{
downloadStatus?.status === "complete" ? (
<Pressable onPress={doReadownload}>
Re-Download
</Pressable>
) : (
downloadStatus?.status === "in_progress" ? (
<Text>
{downloadStatus.bytes.done / downloadStatus.bytes.total * 100.0} % complete
{ downloadStatus.bytes.done } bytes of { downloadStatus.bytes.total }
</Text>
) : (
<Pressable onPress={doDownload}>
Download
</Pressable>
)
)
}
</View>
)}
</View> : <View><Text>Loading ...</Text></View>
);
};