diff --git a/desktop/src/main.ts b/desktop/src/main.ts index 467d9c881..cc1798ae0 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -29,7 +29,7 @@ import { createWatcher } from "./main/services/watch"; import { userPreferences } from "./main/stores/user-preferences"; import { migrateLegacyWatchStoreIfNeeded } from "./main/stores/watch"; import { registerStreamProtocol } from "./main/stream"; -import { isDev } from "./main/util"; +import { isDev } from "./main/utils-electron"; /** * The URL where the renderer HTML is being served from. diff --git a/desktop/src/main/ipc.ts b/desktop/src/main/ipc.ts index eab2e8b59..a421e7642 100644 --- a/desktop/src/main/ipc.ts +++ b/desktop/src/main/ipc.ts @@ -66,7 +66,7 @@ import { watchUpdateIgnoredFiles, watchUpdateSyncedFiles, } from "./services/watch"; -import { openDirectory, openLogDirectory } from "./util"; +import { openDirectory, openLogDirectory } from "./utils-electron"; /** * Listen for IPC events sent/invoked by the renderer process, and route them to diff --git a/desktop/src/main/log.ts b/desktop/src/main/log.ts index d43161fea..22ebb5300 100644 --- a/desktop/src/main/log.ts +++ b/desktop/src/main/log.ts @@ -1,6 +1,6 @@ import log from "electron-log"; import util from "node:util"; -import { isDev } from "./util"; +import { isDev } from "./utils-electron"; /** * Initialize logging in the main process. diff --git a/desktop/src/main/menu.ts b/desktop/src/main/menu.ts index bd8810428..d62935c98 100644 --- a/desktop/src/main/menu.ts +++ b/desktop/src/main/menu.ts @@ -9,7 +9,7 @@ import { allowWindowClose } from "../main"; import { forceCheckForAppUpdates } from "./services/app-update"; import autoLauncher from "./services/auto-launcher"; import { userPreferences } from "./stores/user-preferences"; -import { openLogDirectory } from "./util"; +import { openLogDirectory } from "./utils-electron"; /** Create and return the entries in the app's main menu bar */ export const createApplicationMenu = async (mainWindow: BrowserWindow) => { diff --git a/desktop/src/main/services/ffmpeg.ts b/desktop/src/main/services/ffmpeg.ts index 01406d8ae..fba43ab0f 100644 --- a/desktop/src/main/services/ffmpeg.ts +++ b/desktop/src/main/services/ffmpeg.ts @@ -5,7 +5,8 @@ import { ElectronFile } from "../../types/ipc"; import log from "../log"; import { writeStream } from "../stream"; import { generateTempFilePath, getTempDirPath } from "../temp"; -import { execAsync } from "../util"; +import { withTimeout } from "../utils"; +import { execAsync } from "../utils-electron"; const INPUT_PATH_PLACEHOLDER = "INPUT"; const FFMPEG_PLACEHOLDER = "FFMPEG"; @@ -132,23 +133,3 @@ export async function deleteTempFile(tempFilePath: string) { log.error("Attempting to delete a non-temp file ${tempFilePath}"); await fs.rm(tempFilePath, { force: true }); } - -/** - * Await the given {@link promise} for {@link timeoutMS} milliseconds. If it - * does not resolve within {@link timeoutMS}, then reject with a timeout error. - */ -export const withTimeout = async (promise: Promise, ms: number) => { - let timeoutId: ReturnType; - const rejectOnTimeout = new Promise((_, reject) => { - timeoutId = setTimeout( - () => reject(new Error("Operation timed out")), - ms, - ); - }); - const promiseAndCancelTimeout = async () => { - const result = await promise; - clearTimeout(timeoutId); - return result; - }; - return Promise.race([promiseAndCancelTimeout(), rejectOnTimeout]); -}; diff --git a/desktop/src/main/services/imageProcessor.ts b/desktop/src/main/services/imageProcessor.ts index f636c153a..a731cc80f 100644 --- a/desktop/src/main/services/imageProcessor.ts +++ b/desktop/src/main/services/imageProcessor.ts @@ -5,7 +5,7 @@ import { CustomErrors, ElectronFile } from "../../types/ipc"; import log from "../log"; import { writeStream } from "../stream"; import { generateTempFilePath } from "../temp"; -import { execAsync, isDev } from "../util"; +import { execAsync, isDev } from "../utils-electron"; import { deleteTempFile } from "./ffmpeg"; const IMAGE_MAGICK_PLACEHOLDER = "IMAGE_MAGICK"; diff --git a/desktop/src/main/services/ml-clip.ts b/desktop/src/main/services/ml-clip.ts index 4fdfcf3cc..954e3859f 100644 --- a/desktop/src/main/services/ml-clip.ts +++ b/desktop/src/main/services/ml-clip.ts @@ -22,14 +22,16 @@ import { modelSavePath, } from "./ml"; -const textModelName = "clip-text-vit-32-uint8.onnx"; -const textModelByteSize = 64173509; // 61.2 MB - const cachedCLIPImageSession = makeCachedInferenceSession( "clip-image-vit-32-float32.onnx", 351468764 /* 335.2 MB */, ); +const cachedCLIPTextSession = makeCachedInferenceSession( + "clip-text-vit-32-uint8.onnx", + 64173509 /* 61.2 MB */, +); + let textModelDownloadInProgress = false; /* TODO(MR): use the generic method. Then we can remove the exports for the @@ -202,7 +204,13 @@ const getTokenizer = () => { }; export const clipTextEmbedding = async (text: string) => { - const session = await onnxTextSession(); + const session = await Promise.race([ + cachedCLIPTextSession(), + new Promise<"downloading-model">((resolve) => + setTimeout(() => resolve("downloading-model"), 100), + ), + ]); + await onnxTextSession(); const t1 = Date.now(); const tokenizer = getTokenizer(); const tokenizedText = Int32Array.from(tokenizer.encodeForCLIP(text)); diff --git a/desktop/src/main/util.ts b/desktop/src/main/utils-electron.ts similarity index 100% rename from desktop/src/main/util.ts rename to desktop/src/main/utils-electron.ts diff --git a/desktop/src/main/utils.ts b/desktop/src/main/utils.ts new file mode 100644 index 000000000..132859a43 --- /dev/null +++ b/desktop/src/main/utils.ts @@ -0,0 +1,35 @@ +/** + * @file grab bag of utitity functions. + * + * Many of these are verbatim copies of functions from web code since there + * isn't currently a common package that both of them share. + */ + +/** + * Wait for {@link ms} milliseconds + * + * This function is a promisified `setTimeout`. It returns a promise that + * resolves after {@link ms} milliseconds. + */ +export const wait = (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms)); + +/** + * Await the given {@link promise} for {@link timeoutMS} milliseconds. If it + * does not resolve within {@link timeoutMS}, then reject with a timeout error. + */ +export const withTimeout = async (promise: Promise, ms: number) => { + let timeoutId: ReturnType; + const rejectOnTimeout = new Promise((_, reject) => { + timeoutId = setTimeout( + () => reject(new Error("Operation timed out")), + ms, + ); + }); + const promiseAndCancelTimeout = async () => { + const result = await promise; + clearTimeout(timeoutId); + return result; + }; + return Promise.race([promiseAndCancelTimeout(), rejectOnTimeout]); +};