[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:
commit
85522a946a
9 changed files with 88 additions and 68 deletions
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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}`,
|
||||
);
|
||||
|
|
|
@ -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[];
|
||||
}
|
Loading…
Add table
Reference in a new issue