소스 검색

Inline ML related functions

Manav Rathi 1 년 전
부모
커밋
c98762b448
3개의 변경된 파일155개의 추가작업 그리고 73개의 파일을 삭제
  1. 0 54
      desktop/src/api/clip.ts
  2. 155 2
      desktop/src/preload.ts
  3. 0 17
      desktop/src/utils/error.ts

+ 0 - 54
desktop/src/api/clip.ts

@@ -1,54 +0,0 @@
-import { ipcRenderer } from "electron";
-import { writeStream } from "../services/fs";
-import { Model } from "../types";
-import { isExecError, parseExecError } from "../utils/error";
-
-export async function computeImageEmbedding(
-    model: Model,
-    imageData: Uint8Array,
-): Promise<Float32Array> {
-    let tempInputFilePath = null;
-    try {
-        tempInputFilePath = await ipcRenderer.invoke("get-temp-file-path", "");
-        const imageStream = new Response(imageData.buffer).body;
-        await writeStream(tempInputFilePath, imageStream);
-        const embedding = await ipcRenderer.invoke(
-            "compute-image-embedding",
-            model,
-            tempInputFilePath,
-        );
-        return embedding;
-    } catch (err) {
-        if (isExecError(err)) {
-            const parsedExecError = parseExecError(err);
-            throw Error(parsedExecError);
-        } else {
-            throw err;
-        }
-    } finally {
-        if (tempInputFilePath) {
-            await ipcRenderer.invoke("remove-temp-file", tempInputFilePath);
-        }
-    }
-}
-
-export async function computeTextEmbedding(
-    model: Model,
-    text: string,
-): Promise<Float32Array> {
-    try {
-        const embedding = await ipcRenderer.invoke(
-            "compute-text-embedding",
-            model,
-            text,
-        );
-        return embedding;
-    } catch (err) {
-        if (isExecError(err)) {
-            const parsedExecError = parseExecError(err);
-            throw Error(parsedExecError);
-        } else {
-            throw err;
-        }
-    }
-}

+ 155 - 2
desktop/src/preload.ts

@@ -27,9 +27,11 @@
  * just keep the entire preload setup in this single file.
  */
 
-import { contextBridge } from "electron";
+import { contextBridge, ipcRenderer } from "electron";
+import { existsSync } from "fs";
+import * as fs from "promise-fs";
+import { Readable } from "stream";
 import { deleteDiskCache, openDiskCache } from "./api/cache";
-import { computeImageEmbedding, computeTextEmbedding } from "./api/clip";
 import {
     getAppVersion,
     logToDisk,
@@ -84,6 +86,157 @@ import {
 } from "./api/watch";
 import { setupLogging } from "./utils/logging";
 
+/* preload: duplicated writeStream */
+/* Some of the code below has been duplicated to make this file self contained.
+   Enhancement: consider alternatives */
+
+export const convertBrowserStreamToNode = (
+    fileStream: ReadableStream<Uint8Array>,
+) => {
+    const reader = fileStream.getReader();
+    const rs = new Readable();
+
+    rs._read = async () => {
+        try {
+            const result = await reader.read();
+
+            if (!result.done) {
+                rs.push(Buffer.from(result.value));
+            } else {
+                rs.push(null);
+                return;
+            }
+        } catch (e) {
+            rs.emit("error", e);
+        }
+    };
+
+    return rs;
+};
+
+export async function writeNodeStream(
+    filePath: string,
+    fileStream: NodeJS.ReadableStream,
+) {
+    const writeable = fs.createWriteStream(filePath);
+
+    fileStream.on("error", (error) => {
+        writeable.destroy(error); // Close the writable stream with an error
+    });
+
+    fileStream.pipe(writeable);
+
+    await new Promise((resolve, reject) => {
+        writeable.on("finish", resolve);
+        writeable.on("error", async (e) => {
+            if (existsSync(filePath)) {
+                await fs.unlink(filePath);
+            }
+            reject(e);
+        });
+    });
+}
+
+export async function writeStream(
+    filePath: string,
+    fileStream: ReadableStream<Uint8Array>,
+) {
+    const readable = convertBrowserStreamToNode(fileStream);
+    await writeNodeStream(filePath, readable);
+}
+
+// -
+
+/* preload: duplicated Model */
+export enum Model {
+    GGML_CLIP = "ggml-clip",
+    ONNX_CLIP = "onnx-clip",
+}
+
+const computeImageEmbedding = async (
+    model: Model,
+    imageData: Uint8Array,
+): Promise<Float32Array> => {
+    let tempInputFilePath = null;
+    try {
+        tempInputFilePath = await ipcRenderer.invoke("get-temp-file-path", "");
+        const imageStream = new Response(imageData.buffer).body;
+        await writeStream(tempInputFilePath, imageStream);
+        const embedding = await ipcRenderer.invoke(
+            "compute-image-embedding",
+            model,
+            tempInputFilePath,
+        );
+        return embedding;
+    } catch (err) {
+        if (isExecError(err)) {
+            const parsedExecError = parseExecError(err);
+            throw Error(parsedExecError);
+        } else {
+            throw err;
+        }
+    } finally {
+        if (tempInputFilePath) {
+            await ipcRenderer.invoke("remove-temp-file", tempInputFilePath);
+        }
+    }
+};
+
+export async function computeTextEmbedding(
+    model: Model,
+    text: string,
+): Promise<Float32Array> {
+    try {
+        const embedding = await ipcRenderer.invoke(
+            "compute-text-embedding",
+            model,
+            text,
+        );
+        return embedding;
+    } catch (err) {
+        if (isExecError(err)) {
+            const parsedExecError = parseExecError(err);
+            throw Error(parsedExecError);
+        } else {
+            throw err;
+        }
+    }
+}
+
+// -
+
+/* preload: duplicated CustomErrors */
+const CustomErrorsP = {
+    WINDOWS_NATIVE_IMAGE_PROCESSING_NOT_SUPPORTED:
+        "Windows native image processing is not supported",
+    INVALID_OS: (os: string) => `Invalid OS - ${os}`,
+    WAIT_TIME_EXCEEDED: "Wait time exceeded",
+    UNSUPPORTED_PLATFORM: (platform: string, arch: string) =>
+        `Unsupported platform - ${platform} ${arch}`,
+    MODEL_DOWNLOAD_PENDING:
+        "Model download pending, skipping clip search request",
+    INVALID_FILE_PATH: "Invalid file path",
+    INVALID_CLIP_MODEL: (model: string) => `Invalid Clip model - ${model}`,
+};
+
+const isExecError = (err: any) => {
+    return err.message.includes("Command failed:");
+};
+
+const parseExecError = (err: any) => {
+    const errMessage = err.message;
+    if (errMessage.includes("Bad CPU type in executable")) {
+        return CustomErrorsP.UNSUPPORTED_PLATFORM(
+            process.platform,
+            process.arch,
+        );
+    } else {
+        return errMessage;
+    }
+};
+
+// -
+
 setupLogging();
 
 // These objects exposed here will become available to the JS code in our

+ 0 - 17
desktop/src/utils/error.ts

@@ -1,17 +0,0 @@
-import { CustomErrors } from "../constants/errors";
-
-export const isExecError = (err: any) => {
-    return err.message.includes("Command failed:");
-};
-
-export const parseExecError = (err: any) => {
-    const errMessage = err.message;
-    if (errMessage.includes("Bad CPU type in executable")) {
-        return CustomErrors.UNSUPPORTED_PLATFORM(
-            process.platform,
-            process.arch,
-        );
-    } else {
-        return errMessage;
-    }
-};