283 lines
8.0 KiB
TypeScript

// Import necessary packages
import React, { useState, useEffect } from "react";
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, fetchWithTimeout } 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,
WHISPER_MODEL_DIR,
downloadWhisperModel,
download_status,
getWhisperDownloadStatus,
getWhisperTarget,
whisper_model_tag_t,
} from "@/app/lib/whisper";
import { Paths } from "expo-file-system/next";
type Language = {
code: string;
name: string;
};
type LanguageMatrix = {
[key: string]: Language;
};
type connection_test_t =
| {
success: true;
}
| {
success: false;
error: string;
};
const SettingsComponent: React.FC = () => {
const [hostLanguage, setHostLanguage] = useState<string | null>(null);
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
>();
const [langServerConn, setLangServerConn] = useState<
undefined | connection_test_t
>();
const fillHostLanguageOptions = async () => {
const settings = await Settings.getDefault();
const hostLang = await settings.getHostLanguage();
setHostLanguage(hostLang || "en");
const langServer = new LanguageServer(
libretranslateBaseUrl || LIBRETRANSLATE_BASE_URL
);
// Fetch languages from API
try {
const langData = await langServer.fetchLanguages();
setLanguages(langData);
setLangServerConn({ success: true });
} catch (err) {
console.warn("Got an error fetching: %s", err);
setLangServerConn({
success: false,
error: `Could not connect to ${libretranslateBaseUrl}: ${err}`,
});
}
}
useEffect(() => {
(async () => {
// Fetch the database connection
// const db = await getDb("down");
const settings = await Settings.getDefault();
await fillHostLanguageOptions();
console.log("Fetched settings");
// Get the current settings values
const libretranslateUrl =
(await settings.getLibretranslateBaseUrl()) || LIBRETRANSLATE_BASE_URL;
setLibretranslateBaseUrl(libretranslateUrl);
console.log("libretranslate url = %s", libretranslateUrl);
try {
const wModel = await settings.getWhisperModel();
setWhisperModel(wModel || "small");
} catch (err) {
console.warn(err);
}
// setWhisperModel(wModel);
setIsLoaded(true);
// console.log("Set is loaded: %s", isLoaded);
})();
// 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);
setInterval(async () => {
if (!libretranslateBaseUrl) return;
try {
const resp = await fetchWithTimeout(
libretranslateBaseUrl + "/languages",
{
method: "HEAD",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
},
5000
);
if (resp.status !== 200) {
throw new Error(resp.statusText);
}
setLangServerConn({ success: true });
} catch (err) {
setLangServerConn({
success: false,
error: `Could not connect to ${libretranslateBaseUrl}: ${err}`,
});
}
}, 1000);
setInterval(async () => {
const settings = await Settings.getDefault();
await settings.setHostLanguage(hostLanguage || "en");
await settings.setLibretranslateBaseUrl(
libretranslateBaseUrl || LIBRETRANSLATE_BASE_URL
);
await settings.setWhisperModel(whisperModel || "small");
}, 1000);
}, []);
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);
// Fetch the database connection
const db = await getDb();
const settings = new Settings(db);
// Save the updated setting value
await settings.setHostLanguage(value);
};
const handleLibretranslateBaseUrlChange = async (value: string) => {
setLibretranslateBaseUrl(value);
const settings = await Settings.getDefault();
// Save the updated setting value
await settings.setLibretranslateBaseUrl(value);
await fillHostLanguageOptions();
};
return isLoaded ? (
<View style={styles.container}>
<Text style={styles.label}>Host Language:</Text>
<Picker
selectedValue={hostLanguage || ""}
style={{ height: 50, width: "100%" }}
onValueChange={handleHostLanguageChange}
accessibilityHint="hostLanguage"
>
{languages &&
Object.entries(languages).map((lang) => (
<Picker.Item key={lang[0]} label={lang[1].name} value={lang[0]} />
))}
</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={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>
) : (
<View>
<Pressable onPress={doDownload} style={styles.button}>
<Text style={styles.buttonText}>Download</Text>
</Pressable>
<Text>
This will download to {Paths.join(WHISPER_MODEL_DIR, WHISPER_MODELS[whisperModel].target)}
</Text>
</View>
)}
</View>
)}
</View>
) : (
<View>
<Text>Loading ...</Text>
</View>
);
};
// Create styles for the component
const styles = StyleSheet.create({
button: {
backgroundColor: "blue",
flexDirection: "row",
display: "flex",
flexShrink: 1,
padding: 20,
alignItems: "center",
alignContent: "center",
},
buttonText: {
color: "white",
alignSelf: "center",
},
container: {
flex: 1,
padding: 20,
},
label: {
fontSize: 16,
marginBottom: 8,
},
input: {
height: 40,
borderColor: "gray",
borderWidth: 1,
marginBottom: 20,
paddingHorizontal: 8,
},
});
export default SettingsComponent;