work on downloading some more.

This commit is contained in:
Jordan
2025-03-11 07:26:49 -07:00
parent 8f67d0421b
commit dca3987e18
14 changed files with 480 additions and 414 deletions

View File

@ -8,6 +8,7 @@ import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
import { Conversation, Speaker } from "@/app/lib/conversation";
import { NavigationProp, ParamListBase } from "@react-navigation/native";
import { Link, useNavigation } from "expo-router";
import { migrateDb } from "@/app/lib/db";
export function LanguageSelection(props: {
@ -30,6 +31,7 @@ export function LanguageSelection(props: {
useEffect(() => {
(async () => {
await migrateDb();
try {
// Replace with your actual async data fetching logic
setTranslator(await CachedTranslator.getDefault());
@ -49,12 +51,12 @@ export function LanguageSelection(props: {
<Text>Settings</Text>
</Pressable>
<ScrollView >
<SafeAreaProvider >
<SafeAreaView>
<SafeAreaProvider>
<SafeAreaView style={styles.table}>
{(languages && languagesLoaded) ? Object.entries(languages).filter((l) => (LANG_FLAGS as any)[l[0]] !== undefined).map(
([lang, lang_entry]) => {
return (
<ISpeakButton language={lang_entry} key={lang_entry.code} onLangSelected={onLangSelected} translator={translator} />
<ISpeakButton language={lang_entry} key={lang_entry.code} onLangSelected={onLangSelected} translator={translator} />
);
}
) : <Text>Waiting...</Text>
@ -66,11 +68,15 @@ export function LanguageSelection(props: {
)
}
const DEBUG_BORDER = {
borderWidth: 3,
borderStyle: "dotted",
borderColor: "blue",
}
const styles = StyleSheet.create({
column: {
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap',
padding: 8,
table: {
flexDirection: "row",
flexWrap: "wrap",
},
})

View File

@ -1,6 +1,7 @@
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,
@ -34,33 +35,36 @@ const SettingsComponent = () => {
} | null>(null);
const [whisperModel, setWhisperModel] =
useState<keyof typeof WHISPER_MODELS>("small");
const [downloader, setDownloader] = useState<any>(null);
const [whisperFile, setWhisperFile] = useState<WhisperFile | undefined>();
const [downloadStatus, setDownloadStatus] = useState<
undefined | download_status_t
>();
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(() => {
loadSettings();
}, []);
(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 loadSettings = async () => {
const settings = await Settings.getDefault();
setHostLanguage((await settings.getHostLanguage()) || "en");
setLibretranslateBaseUrl(
(await settings.getLibretranslateBaseUrl()) || LIBRETRANSLATE_BASE_URL
);
setWhisperModel(await settings.getWhisperModel());
};
const handleHostLanguageChange = async (lang: string) => {
const settings = await Settings.getDefault();
setHostLanguage(lang);
@ -83,17 +87,24 @@ const SettingsComponent = () => {
}
};
const intervalUpdateDownloadStatus = async () => {
if (!whisperFile) return;
const status = await whisperFile.getDownloadStatus();
setDownloadStatus(status);
};
const handleWhisperModelChange = async (model: whisper_tag_t) => {
const settings = await Settings.getDefault();
await settings.setWhisperModel(model);
setWhisperModel(model);
setWhisperFile(new WhisperFile(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 () => {
@ -101,16 +112,16 @@ const SettingsComponent = () => {
throw new Error("Could not start download because whisperModel not set.");
}
console.log("Starging download of %s", whisperModel)
console.log("Starting download of %s", whisperModel);
const whisperFile = new WhisperFile(whisperModel);
if (!whisperFile) throw new Error("No whisper file");
const resumable = await whisperFile.createDownloadResumable();
setDownloader(resumable);
try {
await resumable.downloadAsync();
const statusTimeout = setInterval(intervalUpdateDownloadStatus, 200);
setStatusTimeout(statusTimeout);
const resumable = await whisperFile.createDownloadResumable({
onData: doSetDownloadStatus,
});
setDownloader(resumable);
await resumable.resumeAsync();
} catch (error) {
console.error("Failed to download whisper model:", error);
}
@ -174,28 +185,22 @@ const SettingsComponent = () => {
))}
</Picker>
<View>
{whisperModel &&
(downloadStatus?.isDownloadComplete ? (
downloadStatus?.doesTargetExist ? (
<Pressable onPress={doDelete}>
<Text>DELETE {whisperModel.toUpperCase()}</Text>
</Pressable>
) : (
<Pressable onPress={doStopDownload}>
<Text>PAUSE</Text>
</Pressable>
)
) : (
<Pressable onPress={doDownload}>
{/* <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>
))}
{downloadStatus?.progress && (
{bytesDone && bytesRemaining && (
<View>
<Text>
{downloadStatus.progress.current} of{" "}
{downloadStatus.progress.total} (
{downloadStatus.progress.percentRemaining} %){" "}
{bytesDone} of{" "}
{bytesRemaining} (
{bytesDone / bytesRemaining * 100} %){" "}
</Text>
</View>
)}

View File

@ -1,5 +1,5 @@
jest.mock("@/app/i18n/api", () => require("../../__mocks__/api.ts"));
import { renderRouter} from 'expo-router/testing-library';
import { renderRouter } from "expo-router/testing-library";
import React from "react";
import {
act,
@ -13,14 +13,21 @@ import {
createNavigationContainerRef,
} from "@react-navigation/native";
import TTNavStack from "../TTNavStack";
import { migrateDb } from "@/app/lib/db";
describe("Navigation", () => {
beforeEach(() => {
beforeEach(async () => {
await migrateDb("development", "up");
// Reset the navigation state before each test
jest.clearAllMocks();
jest.useFakeTimers();
});
afterEach(async () => {
await migrateDb("development", "down");
jest.clearAllMocks();
jest.useRealTimers();
});
it("Navigates to ConversationThread on language selection", async () => {
const MockComponent = jest.fn(() => <TTNavStack />);
renderRouter(
@ -28,7 +35,7 @@ describe("Navigation", () => {
index: MockComponent,
},
{
initialUrl: '/',
initialUrl: "/",
}
);
const languageSelectionText = await waitFor(() =>
@ -47,14 +54,16 @@ describe("Navigation", () => {
index: MockComponent,
},
{
initialUrl: '/',
initialUrl: "/",
}
);
const settingsButton = await waitFor(() =>
screen.getByText(/.*Settings.*/i)
);
fireEvent.press(settingsButton);
expect(await waitFor(() => screen.getByText(/Settings/i))).toBeOnTheScreen();
expect(
await waitFor(() => screen.getByText(/Settings/i))
).toBeOnTheScreen();
// expect(waitFor(() => screen.getByText(/Settings/i))).toBeTruthy()
expect(screen.getByText("Settings")).toBeOnTheScreen();
});

View File

@ -106,7 +106,12 @@ const ISpeakButton = (props: ISpeakButtonProps) => {
<View style={styles.flag}>
{countries &&
countries.map((c) => {
return <CountryFlag isoCode={c} size={25} key={c} />;
return (
<View>
<Text>{c}</Text>
<CountryFlag isoCode={c} size={25} key={c} />
</View>
);
})}
</View>
<View>
@ -121,14 +126,13 @@ const ISpeakButton = (props: ISpeakButtonProps) => {
const styles = StyleSheet.create({
button: {
width: "20%",
borderRadius: 10,
borderColor: "white",
borderWidth: 1,
borderStyle: "solid",
height: 110,
alignSelf: "flex-start",
margin: 8,
width: 170,
margin: 10,
},
flag: {},
iSpeak: {

View File

@ -83,6 +83,7 @@ describe("SettingsComponent", () => {
beforeEach(async () => {
db = await getDb("development");
await migrateDb("development");
settings = new Settings(db);
jest.spyOn(Settings, 'getDefault').mockResolvedValue(settings);
await settings.setHostLanguage("en");