add knex-expo-sqlite-dialect submodule. migrate to expo (possibly a mistake)
This commit is contained in:
@ -1,9 +1,17 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { View, Text, TextInput, Pressable, StyleSheet } from "react-native";
|
||||
import { WhisperFile, download_status_t, whisper_tag_t } from "@/app/lib/whisper";
|
||||
import {
|
||||
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";
|
||||
import {
|
||||
LanguageServer,
|
||||
language_matrix,
|
||||
language_matrix_entry,
|
||||
} from "@/app/i18n/api";
|
||||
const WHISPER_MODELS = {
|
||||
small: new WhisperFile("small"),
|
||||
medium: new WhisperFile("medium"),
|
||||
@ -17,7 +25,9 @@ const SettingsComponent = () => {
|
||||
const [libretranslateBaseUrl, setLibretranslateBaseUrl] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [languageOptions, setLanguageOptions] = useState<language_matrix | undefined>();
|
||||
const [languageOptions, setLanguageOptions] = useState<
|
||||
language_matrix | undefined
|
||||
>();
|
||||
const [langServerConn, setLangServerConn] = useState<{
|
||||
success: boolean;
|
||||
error?: string;
|
||||
@ -25,31 +35,30 @@ const SettingsComponent = () => {
|
||||
const [whisperModel, setWhisperModel] =
|
||||
useState<keyof typeof WHISPER_MODELS>("small");
|
||||
const [downloader, setDownloader] = useState<any>(null);
|
||||
const [whisperFile, setWhisperFile] = useState<WhisperFile>(null);
|
||||
const [downloadStatus, setDownloadStatus] = useState<undefined | download_status_t>();
|
||||
const [downloadStatusChecker, setDownloadStatusChecker] = useState<undefined | any>();
|
||||
const [whisperFile, setWhisperFile] = useState<WhisperFile | undefined>();
|
||||
const [downloadStatus, setDownloadStatus] = useState<
|
||||
undefined | download_status_t
|
||||
>();
|
||||
const [statusTimeout, setStatusTimeout] = useState<
|
||||
NodeJS.Timeout | undefined
|
||||
>();
|
||||
|
||||
useEffect(() => {
|
||||
loadSettings();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
checkDownloadStatus(whisperModel);
|
||||
}, [whisperModel]);
|
||||
|
||||
const getLanguageOptions = async () => {
|
||||
const languageServer = await LanguageServer.getDefault();
|
||||
setLanguageOptions(await languageServer.fetchLanguages());
|
||||
}
|
||||
};
|
||||
|
||||
const loadSettings = async () => {
|
||||
const settings = await Settings.getDefault();
|
||||
const hostLanguage = await settings.getHostLanguage();
|
||||
setHostLanguage(hostLanguage);
|
||||
const libretranslateBaseUrl = await settings.getLibretranslateBaseUrl();
|
||||
setLibretranslateBaseUrl(libretranslateBaseUrl);
|
||||
const whisperModel = await settings.getWhisperModel();
|
||||
setWhisperModel(whisperModel as keyof typeof WHISPER_MODELS);
|
||||
setHostLanguage((await settings.getHostLanguage()) || "en");
|
||||
setLibretranslateBaseUrl(
|
||||
(await settings.getLibretranslateBaseUrl()) || LIBRETRANSLATE_BASE_URL
|
||||
);
|
||||
setWhisperModel(await settings.getWhisperModel());
|
||||
};
|
||||
|
||||
const handleHostLanguageChange = async (lang: string) => {
|
||||
@ -78,11 +87,9 @@ const SettingsComponent = () => {
|
||||
if (!whisperFile) return;
|
||||
const status = await whisperFile.getDownloadStatus();
|
||||
setDownloadStatus(status);
|
||||
}
|
||||
};
|
||||
|
||||
const handleWhisperModelChange = async (
|
||||
model: whisper_tag_t,
|
||||
) => {
|
||||
const handleWhisperModelChange = async (model: whisper_tag_t) => {
|
||||
const settings = await Settings.getDefault();
|
||||
await settings.setWhisperModel(model);
|
||||
setWhisperModel(model);
|
||||
@ -90,13 +97,20 @@ const SettingsComponent = () => {
|
||||
};
|
||||
|
||||
const doDownload = async () => {
|
||||
const resumable = await whisperFile.createDownloadResumable({
|
||||
onData: (progress) => setWhisperDownloadProgress(progress),
|
||||
});
|
||||
if (!whisperModel) {
|
||||
throw new Error("Could not start download because whisperModel not set.");
|
||||
}
|
||||
|
||||
console.log("Starging download of %s", whisperModel)
|
||||
|
||||
const whisperFile = new WhisperFile(whisperModel);
|
||||
|
||||
const resumable = await whisperFile.createDownloadResumable();
|
||||
setDownloader(resumable);
|
||||
try {
|
||||
await resumable.downloadAsync();
|
||||
checkDownloadStatus(whisperModel);
|
||||
const statusTimeout = setInterval(intervalUpdateDownloadStatus, 200);
|
||||
setStatusTimeout(statusTimeout);
|
||||
} catch (error) {
|
||||
console.error("Failed to download whisper model:", error);
|
||||
}
|
||||
@ -110,33 +124,25 @@ const SettingsComponent = () => {
|
||||
const doDelete = async () => {
|
||||
const whisperFile = WHISPER_MODELS[whisperModel];
|
||||
whisperFile.delete();
|
||||
checkDownloadStatus(whisperModel);
|
||||
};
|
||||
|
||||
const checkDownloadStatus = async (model: keyof typeof WHISPER_MODELS) => {
|
||||
const whisperFile = WHISPER_MODELS[model];
|
||||
const status = await whisperFile.getDownloadStatus();
|
||||
if (
|
||||
!status.isDownloadComplete &&
|
||||
(!status.doesTargetExist || !status.hasDownloadStarted)
|
||||
) {
|
||||
setDownloader(null);
|
||||
}
|
||||
setStatusTimeout(undefined);
|
||||
};
|
||||
|
||||
return hostLanguage && libretranslateBaseUrl ? (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.label}>Host Language:</Text>
|
||||
{languageOptions && (<Picker
|
||||
selectedValue={hostLanguage}
|
||||
style={{ height: 50, width: "100%" }}
|
||||
onValueChange={handleHostLanguageChange}
|
||||
accessibilityHint="hostLanguage"
|
||||
>
|
||||
{languageOptions && Object.entries(languageOptions).map(([key, value]) => {
|
||||
return (<Picker.Item label={value.name} value={value.code} />)
|
||||
})}
|
||||
</Picker>)}
|
||||
{
|
||||
<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
|
||||
@ -157,7 +163,7 @@ const SettingsComponent = () => {
|
||||
selectedValue={whisperModel}
|
||||
style={{ height: 50, width: "100%" }}
|
||||
onValueChange={handleWhisperModelChange}
|
||||
accessibilityHint="language"
|
||||
accessibilityHint="whisper models"
|
||||
>
|
||||
{Object.entries(WHISPER_MODELS).map(([key, whisperFile]) => (
|
||||
<Picker.Item
|
||||
@ -168,46 +174,31 @@ const SettingsComponent = () => {
|
||||
))}
|
||||
</Picker>
|
||||
<View>
|
||||
{downloader && whisperDownloadProgress && (
|
||||
<Text>
|
||||
{whisperDownloadProgress.totalBytesWritten} bytes of{" "}
|
||||
{whisperDownloadProgress.totalBytesExpectedToWrite} bytes (
|
||||
{Math.round(
|
||||
(whisperDownloadProgress.totalBytesWritten /
|
||||
whisperDownloadProgress.totalBytesExpectedToWrite) *
|
||||
100
|
||||
)}
|
||||
%)
|
||||
</Text>
|
||||
)}
|
||||
<View style={styles.downloadButtonWrapper}>
|
||||
{downloader &&
|
||||
whisperDownloadProgress &&
|
||||
whisperDownloadProgress.totalBytesWritten !==
|
||||
whisperDownloadProgress.totalBytesExpectedToWrite ? (
|
||||
<Pressable
|
||||
onPress={doStopDownload}
|
||||
style={styles.pauseDownloadButton}
|
||||
>
|
||||
<Text style={styles.buttonText}>Pause Download</Text>
|
||||
</Pressable>
|
||||
) : (
|
||||
<Pressable onPress={doDownload} style={styles.downloadButton}>
|
||||
<Text style={styles.buttonText}>DOWNLOAD</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
{whisperModel &&
|
||||
WHISPER_MODELS[whisperModel] &&
|
||||
WHISPER_MODELS[whisperModel].doesTargetExist && (
|
||||
<Pressable
|
||||
onPress={doDelete}
|
||||
style={styles.deleteButton}
|
||||
aria-label="Delete"
|
||||
>
|
||||
<Text style={styles.buttonText}>Delete</Text>
|
||||
{whisperModel &&
|
||||
(downloadStatus?.isDownloadComplete ? (
|
||||
downloadStatus?.doesTargetExist ? (
|
||||
<Pressable onPress={doDelete}>
|
||||
<Text>DELETE {whisperModel.toUpperCase()}</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
</View>
|
||||
) : (
|
||||
<Pressable onPress={doStopDownload}>
|
||||
<Text>PAUSE</Text>
|
||||
</Pressable>
|
||||
)
|
||||
) : (
|
||||
<Pressable onPress={doDownload}>
|
||||
<Text>DOWNLOAD {whisperModel.toUpperCase()}</Text>
|
||||
</Pressable>
|
||||
))}
|
||||
{downloadStatus?.progress && (
|
||||
<View>
|
||||
<Text>
|
||||
{downloadStatus.progress.current} of{" "}
|
||||
{downloadStatus.progress.total} (
|
||||
{downloadStatus.progress.percentRemaining} %){" "}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
|
@ -5,10 +5,10 @@ import { language_matrix } from "@/app/i18n/api";
|
||||
import { Settings } from "@/app/lib/settings";
|
||||
import { getDb } from "@/app/lib/db";
|
||||
import { Knex } from "knex";
|
||||
import { WhisperFile } from "@/app/lib/whisper";
|
||||
|
||||
const RENDER_TIME = 1000;
|
||||
|
||||
// Mock the WhisperFile class
|
||||
jest.mock("@/app/lib/whisper", () => {
|
||||
const originalModule = jest.requireActual("@/app/lib/whisper");
|
||||
|
||||
@ -21,7 +21,10 @@ jest.mock("@/app/lib/whisper", () => {
|
||||
size,
|
||||
doesTargetExist: jest.fn(),
|
||||
getDownloadStatus: jest.fn(), // Mock other methods as needed
|
||||
isDownloadcomplete: () => true,
|
||||
isDownloadComplete: jest.fn(() => false), // Initially assume download is not complete
|
||||
createDownloadResumable: jest.fn().mockResolvedValue({
|
||||
startAsync: jest.fn().mockResolvedValue({}),
|
||||
}),
|
||||
})),
|
||||
};
|
||||
});
|
||||
@ -39,32 +42,33 @@ jest.mock("expo-file-system", () => {
|
||||
});
|
||||
|
||||
jest.mock("@/app/i18n/api", () => {
|
||||
class LanguageServer {
|
||||
fetchLanguages = () => {
|
||||
return {
|
||||
en: {
|
||||
code: "en",
|
||||
name: "English",
|
||||
targets: ["fr", "es"],
|
||||
},
|
||||
fr: {
|
||||
code: "fr",
|
||||
name: "French",
|
||||
targets: ["en", "es"],
|
||||
},
|
||||
es: {
|
||||
code: "es",
|
||||
name: "Spanish",
|
||||
targets: ["en", "fr"],
|
||||
},
|
||||
} as language_matrix;
|
||||
};
|
||||
}
|
||||
class Translator {
|
||||
translate = jest.fn((text: string, target: string) => {
|
||||
return "Hola, como estas?";
|
||||
});
|
||||
}
|
||||
const LanguageServer = jest.fn();
|
||||
const Translator = jest.fn();
|
||||
|
||||
// Mock the fetchLanguages method to return a predefined language matrix
|
||||
LanguageServer.prototype.fetchLanguages = jest.fn(() => ({
|
||||
en: {
|
||||
code: "en",
|
||||
name: "English",
|
||||
targets: ["fr", "es"],
|
||||
},
|
||||
fr: {
|
||||
code: "fr",
|
||||
name: "French",
|
||||
targets: ["en", "es"],
|
||||
},
|
||||
es: {
|
||||
code: "es",
|
||||
name: "Spanish",
|
||||
targets: ["en", "fr"],
|
||||
},
|
||||
} as language_matrix));
|
||||
|
||||
// Mock the translate method
|
||||
Translator.prototype.translate = jest.fn((text: string, target: string) => {
|
||||
return "Hola, como estas?";
|
||||
});
|
||||
|
||||
return {
|
||||
LanguageServer,
|
||||
Translator,
|
||||
@ -100,7 +104,6 @@ describe("SettingsComponent", () => {
|
||||
|
||||
test("renders correctly with initial settings", async () => {
|
||||
render(<SettingsComponent />);
|
||||
jest.advanceTimersByTime(RENDER_TIME);
|
||||
screen.debug();
|
||||
|
||||
// Wait for the component to fetch and display the initial settings
|
||||
@ -121,7 +124,7 @@ describe("SettingsComponent", () => {
|
||||
await screen.findByText(/LibreTranslate Base URL:/i);
|
||||
|
||||
// Change the host language input value
|
||||
const picker = screen.getByAccessibilityHint("hostLanguage");
|
||||
const picker = screen.getByAccessibilityHint("host language");
|
||||
fireEvent(picker, "onvalueChange", "es");
|
||||
expect(picker.props.selectedIndex).toStrictEqual(0);
|
||||
});
|
||||
@ -147,4 +150,72 @@ describe("SettingsComponent", () => {
|
||||
screen.getByAccessibilityHint("libretranslate base url")
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
|
||||
describe("Download Whisper Model", () => {
|
||||
it("should trigger download when model is not present", async () => {
|
||||
const whisperFile = new WhisperFile("small");
|
||||
(whisperFile.doesTargetExist as jest.Mock).mockResolvedValue(false);
|
||||
|
||||
render(<SettingsComponent />);
|
||||
await screen.findByText(/\s*Download Small\s*/i);
|
||||
// Assuming there's a button or trigger to start download
|
||||
act(() => {
|
||||
fireEvent.press(screen.getByText(/\s*Download Small\s*/i));
|
||||
})
|
||||
|
||||
expect(whisperFile.createDownloadResumable).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should show progress when download is in progress", async () => {
|
||||
const whisperFile = new WhisperFile("small");
|
||||
(whisperFile.doesTargetExist as jest.Mock).mockResolvedValue(false);
|
||||
(whisperFile.getDownloadStatus as jest.Mock).mockResolvedValue({
|
||||
doesTargetExist: false,
|
||||
isDownloadComplete: false,
|
||||
hasDownloadStarted: true,
|
||||
progress: {
|
||||
current: 1024,
|
||||
total: 2048,
|
||||
remaining: 1024,
|
||||
percentRemaining: 50,
|
||||
},
|
||||
});
|
||||
|
||||
render(<SettingsComponent />);
|
||||
await screen.findByText(/Host Language:/i);
|
||||
fireEvent.press(screen.getByText(/Download Model/i));
|
||||
|
||||
expect(await screen.findByText("50%")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should indicate download is complete", async () => {
|
||||
const whisperFile = new WhisperFile("small");
|
||||
(whisperFile.doesTargetExist as jest.Mock).mockResolvedValue(false);
|
||||
(whisperFile.getDownloadStatus as jest.Mock)
|
||||
.mockResolvedValueOnce({
|
||||
doesTargetExist: false,
|
||||
isDownloadComplete: false,
|
||||
hasDownloadStarted: true,
|
||||
progress: {
|
||||
current: 1024,
|
||||
total: 2048,
|
||||
remaining: 1024,
|
||||
percentRemaining: 50,
|
||||
},
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
doesTargetExist: true,
|
||||
isDownloadComplete: true,
|
||||
hasDownloadStarted: false,
|
||||
progress: undefined,
|
||||
});
|
||||
|
||||
render(<SettingsComponent />);
|
||||
await screen.findByText(/Host Language:/i);
|
||||
fireEvent.press(screen.getByText(/Download Model/i));
|
||||
|
||||
expect(await screen.findByText("Download Complete")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user