[desktop] Short-circuit ML (#1580)

This is so that we can make a release. Post-release, we'll come back to
this and give it the finishing touches and re-enable it. This avoids
doing a re-indexing for actual users in case we need to change stuff
during the finishing touches.
This commit is contained in:
Manav Rathi 2024-05-02 12:07:09 +05:30 committed by GitHub
commit 85522a946a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 88 additions and 68 deletions

View file

@ -1,6 +1,5 @@
import { basename } from "@/next/file";
import log from "@/next/log";
import { type FileAndPath } from "@/next/types/file";
import type { CollectionMapping, Electron, ZipItem } from "@/next/types/ipc";
import { CustomError } from "@ente/shared/error";
import { isPromise } from "@ente/shared/utils";
@ -20,7 +19,7 @@ import {
getPublicCollectionUploaderName,
savePublicCollectionUploaderName,
} from "services/publicCollectionService";
import type { UploadItem } from "services/upload/types";
import type { FileAndPath, UploadItem } from "services/upload/types";
import type {
InProgressUpload,
SegregatedFinishedUploads,

View file

@ -22,7 +22,7 @@ import {
getFaceSearchEnabledStatus,
updateFaceSearchEnabledStatus,
} from "services/userService";
import { openLink } from "utils/common";
import { isInternalUser } from "utils/user";
export const MLSearchSettings = ({ open, onClose, onRootClose }) => {
const {
@ -255,8 +255,8 @@ function EnableFaceSearch({ open, onClose, enableFaceSearch, onRootClose }) {
}
function EnableMLSearch({ onClose, enableMlSearch, onRootClose }) {
const showDetails = () =>
openLink("https://ente.io/blog/desktop-ml-beta", true);
// const showDetails = () =>
// openLink("https://ente.io/blog/desktop-ml-beta", true);
return (
<Stack spacing={"4px"} py={"12px"}>
@ -269,25 +269,37 @@ function EnableMLSearch({ onClose, enableMlSearch, onRootClose }) {
<Box px={"8px"}>
{" "}
<Typography color="text.muted">
<Trans i18nKey={"ENABLE_ML_SEARCH_DESCRIPTION"} />
{/* <Trans i18nKey={"ENABLE_ML_SEARCH_DESCRIPTION"} /> */}
<p>
We're putting finishing touches, coming back soon!
</p>
<p>
<small>
Existing indexed faces will continue to show.
</small>
</p>
</Typography>
</Box>
<Stack px={"8px"} spacing={"8px"}>
<Button
color={"accent"}
size="large"
onClick={enableMlSearch}
>
{t("ENABLE")}
</Button>
<Button
{isInternalUser() && (
<Stack px={"8px"} spacing={"8px"}>
<Button
color={"accent"}
size="large"
onClick={enableMlSearch}
>
{t("ENABLE")}
</Button>
{/*
<Button
color="secondary"
size="large"
onClick={showDetails}
>
{t("ML_MORE_DETAILS")}
</Button>
</Stack>
>
{t("ML_MORE_DETAILS")}
</Button>
*/}
</Stack>
)}
</Stack>
</Stack>
);

View file

@ -370,7 +370,7 @@ export default function Gallery() {
syncWithRemote(false, true);
}, SYNC_INTERVAL_IN_MICROSECONDS);
if (electron) {
void clipService.setupOnFileUploadListener();
// void clipService.setupOnFileUploadListener();
electron.onMainWindowFocus(() => syncWithRemote(false, true));
}
};

View file

@ -86,7 +86,11 @@ export const syncEmbeddings = async () => {
allLocalFiles.forEach((file) => {
fileIdToKeyMap.set(file.id, file.key);
});
await cleanupDeletedEmbeddings(allLocalFiles, allEmbeddings);
await cleanupDeletedEmbeddings(
allLocalFiles,
allEmbeddings,
EMBEDDINGS_TABLE,
);
log.info(`Syncing embeddings localCount: ${allEmbeddings.length}`);
for (const model of models) {
let modelLastSinceTime = await getModelEmbeddingSyncTime(model);
@ -168,7 +172,11 @@ export const syncFileEmbeddings = async () => {
allLocalFiles.forEach((file) => {
fileIdToKeyMap.set(file.id, file.key);
});
await cleanupDeletedEmbeddings(allLocalFiles, allEmbeddings);
await cleanupDeletedEmbeddings(
allLocalFiles,
allEmbeddings,
FILE_EMBEDING_TABLE,
);
log.info(`Syncing embeddings localCount: ${allEmbeddings.length}`);
for (const model of models) {
let modelLastSinceTime = await getModelEmbeddingSyncTime(model);
@ -289,6 +297,7 @@ export const putEmbedding = async (
export const cleanupDeletedEmbeddings = async (
allLocalFiles: EnteFile[],
allLocalEmbeddings: Embedding[] | FileML[],
tableName: string,
) => {
const activeFileIds = new Set<number>();
allLocalFiles.forEach((file) => {
@ -302,6 +311,6 @@ export const cleanupDeletedEmbeddings = async (
log.info(
`cleanupDeletedEmbeddings embeddingsCount: ${allLocalEmbeddings.length} remainingEmbeddingsCount: ${remainingEmbeddings.length}`,
);
await localForage.setItem(EMBEDDINGS_TABLE, remainingEmbeddings);
await localForage.setItem(tableName, remainingEmbeddings);
}
};

View file

@ -1,4 +1,3 @@
import type { FileAndPath } from "@/next/types/file";
import type { ZipItem } from "@/next/types/ipc";
/**
@ -30,6 +29,17 @@ import type { ZipItem } from "@/next/types/ipc";
*/
export type UploadItem = File | FileAndPath | string | ZipItem;
/**
* When we are running in the context of our desktop app, we have access to the
* absolute path of {@link File} objects. This convenience type clubs these two
* bits of information, saving us the need to query the path again and again
* using the {@link getPathForFile} method of {@link Electron}.
*/
export interface FileAndPath {
file: File;
path: string;
}
/**
* The of cases of {@link UploadItem} that apply when we're running in the
* context of our desktop app.

View file

@ -609,11 +609,25 @@ class UploadManager {
].includes(uploadResult)
) {
try {
let file: File | undefined;
const uploadItem =
uploadableItem.uploadItem ??
uploadableItem.livePhotoAssets.image;
if (uploadItem) {
if (uploadItem instanceof File) {
file = uploadItem;
} else if (
typeof uploadItem == "string" ||
Array.isArray(uploadItem)
) {
// path from desktop, no file object
} else {
file = uploadItem.file;
}
}
eventBus.emit(Events.FILE_UPLOADED, {
enteFile: decryptedFile,
localFile:
uploadableItem.uploadItem ??
uploadableItem.livePhotoAssets.image,
localFile: file,
});
} catch (e) {
log.warn("Ignoring error in fileUploaded handlers", e);

View file

@ -10,6 +10,7 @@ import mlIDbStorage, {
ML_SYNC_CONFIG_NAME,
ML_SYNC_JOB_CONFIG_NAME,
} from "utils/storage/mlIDbStorage";
import { isInternalUser } from "utils/user";
export async function getMLSyncJobConfig() {
return mlIDbStorage.getConfig(
@ -23,10 +24,15 @@ export async function getMLSyncConfig() {
}
export async function getMLSearchConfig() {
return mlIDbStorage.getConfig(
ML_SEARCH_CONFIG_NAME,
DEFAULT_ML_SEARCH_CONFIG,
);
if (isInternalUser()) {
return mlIDbStorage.getConfig(
ML_SEARCH_CONFIG_NAME,
DEFAULT_ML_SEARCH_CONFIG,
);
}
// Force disabled for everyone else while we finalize it to avoid redundant
// reindexing for users.
return DEFAULT_ML_SEARCH_CONFIG;
}
export async function updateMLSyncJobConfig(newConfig: JobConfig) {

View file

@ -144,7 +144,13 @@ class MLIDbStorage {
.objectStore("configs")
.add(DEFAULT_ML_SEARCH_CONFIG, ML_SEARCH_CONFIG_NAME);
}
/*
This'll go in version 5. Note that version 4 was never released,
but it was in main for a while, so we'll just skip it to avoid
breaking the upgrade path for people who ran the mainline.
*/
if (oldVersion < 4) {
/*
try {
await tx
.objectStore("configs")
@ -163,8 +169,8 @@ class MLIDbStorage {
// the shipped implementation should have a more
// deterministic migration.
}
*/
}
log.info(
`ML DB upgraded from version ${oldVersion} to version ${newVersion}`,
);

View file

@ -1,36 +0,0 @@
/*
* ElectronFile is a custom interface that is used to represent
* any file on disk as a File-like object in the Electron desktop app.
*
* This was added to support the auto-resuming of failed uploads
* which needed absolute paths to the files which the
* normal File interface does not provide.
*/
export interface ElectronFile {
name: string;
path: string;
size: number;
lastModified: number;
stream: () => Promise<ReadableStream<Uint8Array>>;
blob: () => Promise<Blob>;
arrayBuffer: () => Promise<Uint8Array>;
}
/**
* When we are running in the context of our desktop app, we have access to the
* absolute path of {@link File} objects. This convenience type clubs these two
* bits of information, saving us the need to query the path again and again
* using the {@link getPathForFile} method of {@link Electron}.
*/
export interface FileAndPath {
file: File;
path: string;
}
export interface EventQueueItem {
type: "upload" | "trash";
folderPath: string;
collectionName?: string;
paths?: string[];
files?: ElectronFile[];
}