2025-03-11 07:26:49 -07:00

268 lines
7.8 KiB
TypeScript

import React, { useState, useEffect } from "react";
import { View, Text, TextInput, Pressable, StyleSheet } from "react-native";
import {
WHISPER_FILES,
WhisperFile,
download_status_t,
whisper_tag_t,
} from "@/app/lib/whisper";
import { Settings } from "@/app/lib/settings";
import { Picker } from "@react-native-picker/picker";
import {
LanguageServer,
language_matrix,
language_matrix_entry,
} from "@/app/i18n/api";
const WHISPER_MODELS = {
small: new WhisperFile("small"),
medium: new WhisperFile("medium"),
large: new WhisperFile("large"),
};
const LIBRETRANSLATE_BASE_URL = "https://translate.argosopentech.com/translate";
const SettingsComponent = () => {
const [hostLanguage, setHostLanguage] = useState<string | null>(null);
const [libretranslateBaseUrl, setLibretranslateBaseUrl] = useState<
string | null
>(null);
const [languageOptions, setLanguageOptions] = useState<
language_matrix | undefined
>();
const [langServerConn, setLangServerConn] = useState<{
success: boolean;
error?: string;
} | null>(null);
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>();
const [statusTimeout, setStatusTimeout] = useState<
NodeJS.Timeout | undefined
>();
useEffect(() => {
(async function () {
const settings = await Settings.getDefault();
setHostLanguage((await settings.getHostLanguage()) || "en");
setLibretranslateBaseUrl(
(await settings.getLibretranslateBaseUrl()) || LIBRETRANSLATE_BASE_URL
);
setWhisperModel((await settings.getWhisperModel()) || "small");
setWhisperFile(WHISPER_FILES[whisperModel]);
await whisperFile?.syncHfMetadata();
await whisperFile?.updateTargetExistence();
await whisperFile?.updateTargetHash();
})();
}, [whisperFile]);
const getLanguageOptions = async () => {
const languageServer = await LanguageServer.getDefault();
setLanguageOptions(await languageServer.fetchLanguages());
};
const handleHostLanguageChange = async (lang: string) => {
const settings = await Settings.getDefault();
setHostLanguage(lang);
await settings.setHostLanguage(lang);
};
const handleLibretranslateBaseUrlChange = async (url: string) => {
const settings = await Settings.getDefault();
setLibretranslateBaseUrl(url);
await settings.setLibretranslateBaseUrl(url);
checkLangServerConnection(url);
};
const checkLangServerConnection = async (baseUrl: string) => {
try {
// Replace with actual connection check logic
setLangServerConn({ success: true });
} catch (error) {
setLangServerConn({ success: false, error: `${error}` });
}
};
const handleWhisperModelChange = async (model: whisper_tag_t) => {
const settings = await Settings.getDefault();
await settings.setWhisperModel(model);
setWhisperModel(model);
const wFile = WHISPER_FILES[whisperModel];
await wFile.syncHfMetadata();
await wFile.updateTargetExistence();
await wFile.updateTargetHash();
setIsWhisperHashValid(wFile.isHashValid);
setWhisperFile(wFile);
setWhisperFileExists(wFile.does_target_exist);
};
const doSetDownloadStatus = (arg0: WhisperFile) => {
console.log("Downloading ....")
setIsWhisperHashValid(arg0.isHashValid);
setBytesDone(arg0.download_data?.totalBytesWritten);
setBytesRemaining(arg0.download_data?.totalBytesExpectedToWrite);
};
const doDownload = async () => {
if (!whisperModel) {
throw new Error("Could not start download because whisperModel not set.");
}
console.log("Starting download of %s", whisperModel);
if (!whisperFile) throw new Error("No whisper file");
try {
const resumable = await whisperFile.createDownloadResumable({
onData: doSetDownloadStatus,
});
setDownloader(resumable);
await resumable.resumeAsync();
} catch (error) {
console.error("Failed to download whisper model:", error);
}
};
const doStopDownload = async () => {
downloader.cancelAsync();
setDownloader(null);
};
const doDelete = async () => {
const whisperFile = WHISPER_MODELS[whisperModel];
whisperFile.delete();
setStatusTimeout(undefined);
};
return hostLanguage && libretranslateBaseUrl ? (
<View style={styles.container}>
<Text style={styles.label}>Host Language:</Text>
{
<Picker
selectedValue={hostLanguage}
style={{ height: 50, width: "100%" }}
onValueChange={handleHostLanguageChange}
accessibilityHint="host language"
>
{languageOptions &&
Object.entries(languageOptions).map(([key, value]) => {
return <Picker.Item label={value.name} value={value.code} />;
})}
</Picker>
}
<Text style={styles.label}>LibreTranslate Base URL:</Text>
<TextInput
style={styles.input}
value={libretranslateBaseUrl || LIBRETRANSLATE_BASE_URL}
onChangeText={handleLibretranslateBaseUrlChange}
accessibilityHint="libretranslate base url"
/>
{langServerConn &&
(langServerConn.success ? (
<Text>Success connecting to {libretranslateBaseUrl}</Text>
) : (
<Text>
Error connecting to {libretranslateBaseUrl}: {langServerConn.error}
</Text>
))}
<Picker
selectedValue={whisperModel}
style={{ height: 50, width: "100%" }}
onValueChange={handleWhisperModelChange}
accessibilityHint="whisper models"
>
{Object.entries(WHISPER_MODELS).map(([key, whisperFile]) => (
<Picker.Item
key={whisperFile.tag}
label={whisperFile.label}
value={key}
/>
))}
</Picker>
<View>
{/* <Text>whisper file: { whisperFile?.tag }</Text> */}
{whisperFile &&
( whisperFileExists && (<Pressable onPress={doDelete} style={styles.deleteButton}>
<Text>DELETE {whisperModel.toUpperCase()}</Text>
</Pressable>))
}
<Pressable onPress={doDownload} style={styles.pauseDownloadButton}>
<Text>DOWNLOAD {whisperModel.toUpperCase()}</Text>
</Pressable>
))}
{bytesDone && bytesRemaining && (
<View>
<Text>
{bytesDone} of{" "}
{bytesRemaining} (
{bytesDone / bytesRemaining * 100} %){" "}
</Text>
</View>
)}
</View>
</View>
) : (
<View>
<Text>Loading ...</Text>
</View>
);
};
// Create styles for the component
const styles = StyleSheet.create({
downloadButtonWrapper: {
flexDirection: "row",
},
downloadButton: {
backgroundColor: "darkblue",
padding: 20,
margin: 10,
flex: 3,
flexDirection: "column",
},
deleteButton: {
backgroundColor: "darkred",
flex: 1,
flexDirection: "column",
padding: 10,
margin: 10,
height: 50,
},
pauseDownloadButton: {
backgroundColor: "#444444",
padding: 10,
margin: 10,
height: 50,
},
buttonText: {
color: "#fff",
flex: 1,
fontSize: 16,
alignSelf: "center",
textAlign: "center",
textAlignVertical: "top",
},
container: {
flex: 1,
padding: 20,
},
label: {
fontSize: 16,
marginBottom: 8,
},
input: {
height: 40,
borderColor: "gray",
borderWidth: 1,
marginBottom: 20,
paddingHorizontal: 8,
},
});
export default SettingsComponent;