diff --git a/src/components/PhotoSwipe/PhotoSwipe.tsx b/src/components/PhotoSwipe/PhotoSwipe.tsx index 592d1c176..db0d8119a 100644 --- a/src/components/PhotoSwipe/PhotoSwipe.tsx +++ b/src/components/PhotoSwipe/PhotoSwipe.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useContext, useEffect, useRef, useState } from 'react'; import Photoswipe from 'photoswipe'; import PhotoswipeUIDefault from 'photoswipe/dist/photoswipe-ui-default'; import classnames from 'classnames'; @@ -49,6 +49,7 @@ import { Formik } from 'formik'; import * as Yup from 'yup'; import EnteSpinner from 'components/EnteSpinner'; import EnteDateTimePicker from 'components/EnteDateTimePicker'; +import { AppContext } from 'pages/_app'; interface Iprops { isOpen: boolean; @@ -397,6 +398,7 @@ function InfoModal({ exif, scheduleUpdate, }) { + const appContext = useContext(AppContext); return ( @@ -438,16 +440,22 @@ function InfoModal({ {constants.SHOW_MAP} )} -
- {constants.PEOPLE} -
- -
- {constants.UNIDENTIFIED_FACES} -
- + {appContext.mlSearchEnabled && ( + <> +
+ {constants.PEOPLE} +
+ +
+ {constants.UNIDENTIFIED_FACES} +
+ + + )} {exif && ( <> diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx index 6c8343c5c..771c8d1d4 100644 --- a/src/components/SearchBar.tsx +++ b/src/components/SearchBar.tsx @@ -1,5 +1,5 @@ import { Search, SearchStats } from 'pages/gallery'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useContext, useEffect, useRef, useState } from 'react'; import styled from 'styled-components'; import AsyncSelect from 'react-select/async'; import { components } from 'react-select'; @@ -28,6 +28,7 @@ import VideoIcon from './icons/VideoIcon'; import { IconButton } from './Container'; import { Person } from 'types/machineLearning'; import { PeopleList } from './MachineLearning/PeopleList'; +import { AppContext } from 'pages/_app'; const Wrapper = styled.div<{ isDisabled: boolean; isOpen: boolean }>` position: fixed; @@ -118,6 +119,7 @@ interface Props { export default function SearchBar(props: Props) { const selectRef = useRef(null); const [value, setValue] = useState(null); + const appContext = useContext(AppContext); const handleChange = (value) => { setValue(value); @@ -129,12 +131,14 @@ export default function SearchBar(props: Props) { // Functionality // = ========================= const getAutoCompleteSuggestions = async (searchPhrase: string) => { - const options = await getAllPeopleSuggestion(); - // const options = []; + const options = []; searchPhrase = searchPhrase.trim().toLowerCase(); if (!searchPhrase?.length) { return []; } + if (appContext.mlSearchEnabled) { + options.push(...(await getAllPeopleSuggestion())); + } options.push(...getHolidaySuggestion(searchPhrase)); options.push(...getYearSuggestion(searchPhrase)); diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index fb9882d06..73e634254 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -37,6 +37,7 @@ import { TRASH_SECTION, } from 'components/pages/gallery/Collections'; import FixLargeThumbnails from './FixLargeThumbnail'; +import { AppContext } from 'pages/_app'; interface Props { collections: Collection[]; setDialogMessage: SetDialogMessage; @@ -56,6 +57,7 @@ export default function Sidebar(props: Props) { const [exportModalView, setExportModalView] = useState(false); const [fixLargeThumbsView, setFixLargeThumbsView] = useState(false); const galleryContext = useContext(GalleryContext); + const appContext = useContext(AppContext); useEffect(() => { const main = async () => { if (!isOpen) { @@ -297,6 +299,17 @@ export default function Sidebar(props: Props) { onClick={openFeedbackURL}> {constants.REQUEST_FEATURE} + { + appContext.updateMlSearchEnabled( + !appContext.mlSearchEnabled + ); + }}> + {appContext.mlSearchEnabled + ? constants.DISABLE_ML_SEARCH + : constants.ENABLE_ML_SEARCH} + { diff --git a/src/constants/machineLearning/config.ts b/src/constants/machineLearning/config.ts index e1ded7d9c..4ce213650 100644 --- a/src/constants/machineLearning/config.ts +++ b/src/constants/machineLearning/config.ts @@ -1,5 +1,5 @@ import { JobConfig } from 'types/common/job'; -import { MLSyncConfig } from 'types/machineLearning'; +import { MLSearchConfig, MLSyncConfig } from 'types/machineLearning'; export const DEFAULT_ML_SYNC_JOB_CONFIG: JobConfig = { intervalSec: 30, @@ -53,6 +53,10 @@ export const DEFAULT_ML_SYNC_CONFIG: MLSyncConfig = { mlVersion: 2, }; +export const DEFAULT_ML_SEARCH_CONFIG: MLSearchConfig = { + enabled: false, +}; + export const ML_SYNC_DOWNLOAD_TIMEOUT_MS = 300000; export const MAX_FACE_DISTANCE_PERCENT = Math.sqrt(2) / 100; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 95427f9bf..19a002207 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -16,6 +16,10 @@ import FlashMessageBar from 'components/FlashMessageBar'; import Head from 'next/head'; import { eventBus, Events } from 'services/events'; import mlWorkManager from 'services/machineLearning/mlWorkManager'; +import { + getMLSearchConfig, + updateMLSearchConfig, +} from 'utils/machineLearning/config'; const GlobalStyles = createGlobalStyle` /* ubuntu-regular - latin */ @@ -483,6 +487,8 @@ type AppContextType = { setDisappearingFlashMessage: (message: FlashMessage) => void; redirectUrl: string; setRedirectUrl: (url: string) => void; + mlSearchEnabled: boolean; + updateMlSearchEnabled: (enabled: boolean) => void; }; export enum FLASH_MESSAGE_TYPE { @@ -513,6 +519,7 @@ export default function App({ Component, err }) { const [redirectName, setRedirectName] = useState(null); const [flashMessage, setFlashMessage] = useState(null); const [redirectUrl, setRedirectUrl] = useState(null); + const [mlSearchEnabled, setMlSearchEnabled] = useState(false); useEffect(() => { if ( !('serviceWorker' in navigator) || @@ -550,12 +557,18 @@ export default function App({ Component, err }) { }, []); useEffect(() => { - try { - mlWorkManager; - eventBus.emit(Events.APP_START); - } catch (e) { - logError(e, 'Error in appStart handlers'); - } + const loadMlSearchState = async () => { + try { + const mlSearchConfig = await getMLSearchConfig(); + setMlSearchEnabled(mlSearchConfig.enabled); + mlWorkManager.setMlSearchEnabled(mlSearchConfig.enabled); + eventBus.emit(Events.APP_START); + } catch (e) { + logError(e, 'Error in appStart handlers'); + } + }; + + loadMlSearchState(); }, []); const setUserOnline = () => setOffline(false); @@ -612,6 +625,13 @@ export default function App({ Component, err }) { setFlashMessage(flashMessages); setTimeout(() => setFlashMessage(null), 5000); }; + const updateMlSearchEnabled = async (enabled: boolean) => { + const mlSearchConfig = await getMLSearchConfig(); + mlSearchConfig.enabled = enabled; + await updateMLSearchConfig(mlSearchConfig); + setMlSearchEnabled(enabled); + mlWorkManager.setMlSearchEnabled(enabled); + }; // ho ja yaar return ( <> @@ -657,6 +677,8 @@ export default function App({ Component, err }) { setDisappearingFlashMessage, redirectUrl, setRedirectUrl, + mlSearchEnabled, + updateMlSearchEnabled, }}> {loading ? ( diff --git a/src/services/machineLearning/mlWorkManager.ts b/src/services/machineLearning/mlWorkManager.ts index d19a2b325..dfdbbec19 100644 --- a/src/services/machineLearning/mlWorkManager.ts +++ b/src/services/machineLearning/mlWorkManager.ts @@ -19,8 +19,12 @@ class MLWorkManager { private mlSyncJob: MLSyncJob; private syncJobWorker: MLWorkerWithProxy; + private debouncedLiveSyncIdle: () => void; + private debouncedFilesUpdated: () => void; + private liveSyncQueue: PQueue; private liveSyncWorker: MLWorkerWithProxy; + private mlSearchEnabled: boolean; constructor() { this.liveSyncQueue = new PQueue({ @@ -29,31 +33,58 @@ class MLWorkManager { timeout: LIVE_SYNC_QUEUE_TIMEOUT_SEC * 1000, throwOnTimeout: true, }); - logQueueStats(this.liveSyncQueue, 'livesync'); + this.mlSearchEnabled = false; - const debouncedLiveSyncIdle = debounce( + eventBus.on(Events.LOGOUT, this.logoutHandler, this); + this.debouncedLiveSyncIdle = debounce( () => this.onLiveSyncIdle(), LIVE_SYNC_IDLE_DEBOUNCE_SEC * 1000 ); - this.liveSyncQueue.on('idle', () => debouncedLiveSyncIdle(), this); - - eventBus.on(Events.APP_START, this.appStartHandler, this); - - eventBus.on(Events.LOGIN, this.startSyncJob, this); - - eventBus.on(Events.LOGOUT, this.logoutHandler, this); - - eventBus.on(Events.FILE_UPLOADED, this.fileUploadedHandler, this); - - const debouncedFilesUpdated = debounce( - () => this.localFilesUpdatedHandler(), + this.debouncedFilesUpdated = debounce( + () => this.mlSearchEnabled && this.localFilesUpdatedHandler(), LOCAL_FILES_UPDATED_DEBOUNCE_SEC * 1000 ); - eventBus.on( - Events.LOCAL_FILES_UPDATED, - () => debouncedFilesUpdated(), - this - ); + } + + public async setMlSearchEnabled(enabled: boolean) { + if (!this.mlSearchEnabled && enabled) { + console.log('Enabling MLWorkManager'); + this.mlSearchEnabled = true; + + logQueueStats(this.liveSyncQueue, 'livesync'); + this.liveSyncQueue.on('idle', this.debouncedLiveSyncIdle, this); + + // eventBus.on(Events.APP_START, this.appStartHandler, this); + // eventBus.on(Events.LOGIN, this.startSyncJob, this); + eventBus.on(Events.FILE_UPLOADED, this.fileUploadedHandler, this); + eventBus.on( + Events.LOCAL_FILES_UPDATED, + this.debouncedFilesUpdated, + this + ); + + await this.startSyncJob(); + } else if (this.mlSearchEnabled && !enabled) { + console.log('Disabling MLWorkManager'); + this.mlSearchEnabled = false; + + this.liveSyncQueue.removeAllListeners(); + + // eventBus.removeListener(Events.APP_START, this.appStartHandler, this); + // eventBus.removeListener(Events.LOGIN, this.startSyncJob, this); + eventBus.removeListener( + Events.FILE_UPLOADED, + this.fileUploadedHandler, + this + ); + eventBus.removeListener( + Events.LOCAL_FILES_UPDATED, + this.debouncedFilesUpdated, + this + ); + + await this.stopSyncJob(); + } } // Handlers @@ -83,6 +114,9 @@ class MLWorkManager { enteFile: File; localFile: globalThis.File; }) { + if (!this.mlSearchEnabled) { + return; + } console.log('fileUploadedHandler: ', arg.enteFile.id); if (arg.enteFile.metadata.fileType !== FILE_TYPE.IMAGE) { console.log('Skipping non image file for local file processing'); @@ -131,7 +165,7 @@ class MLWorkManager { private async onLiveSyncIdle() { console.log('Live sync idle'); await this.terminateLiveSyncWorker(); - this.startSyncJob(); + this.mlSearchEnabled && this.startSyncJob(); } public async syncLocalFile(enteFile: File, localFile: globalThis.File) { @@ -185,6 +219,10 @@ class MLWorkManager { public async startSyncJob() { try { console.log('MLWorkManager.startSyncJob'); + if (!this.mlSearchEnabled) { + console.log('ML Search disabled, not starting ml sync job'); + return; + } if (!getToken()) { console.log('User not logged in, not starting ml sync job'); return; diff --git a/src/types/machineLearning/index.ts b/src/types/machineLearning/index.ts index aec5ac23a..175814fe2 100644 --- a/src/types/machineLearning/index.ts +++ b/src/types/machineLearning/index.ts @@ -244,6 +244,10 @@ export interface MLSyncConfig extends Config { mlVersion: number; } +export interface MLSearchConfig extends Config { + enabled: boolean; +} + export interface MLSyncContext { token: string; config: MLSyncConfig; diff --git a/src/utils/machineLearning/config.ts b/src/utils/machineLearning/config.ts index 46c1a9181..53a46aeff 100644 --- a/src/utils/machineLearning/config.ts +++ b/src/utils/machineLearning/config.ts @@ -1,10 +1,12 @@ import { + DEFAULT_ML_SEARCH_CONFIG, DEFAULT_ML_SYNC_CONFIG, DEFAULT_ML_SYNC_JOB_CONFIG, } from 'constants/machineLearning/config'; import { JobConfig } from 'types/common/job'; -import { MLSyncConfig } from 'types/machineLearning'; +import { MLSearchConfig, MLSyncConfig } from 'types/machineLearning'; import mlIDbStorage, { + ML_SEARCH_CONFIG_NAME, ML_SYNC_CONFIG_NAME, ML_SYNC_JOB_CONFIG_NAME, } from 'utils/storage/mlIDbStorage'; @@ -20,6 +22,13 @@ export async function getMLSyncConfig() { return mlIDbStorage.getConfig(ML_SYNC_CONFIG_NAME, DEFAULT_ML_SYNC_CONFIG); } +export async function getMLSearchConfig() { + return mlIDbStorage.getConfig( + ML_SEARCH_CONFIG_NAME, + DEFAULT_ML_SEARCH_CONFIG + ); +} + export async function updateMLSyncJobConfig(newConfig: JobConfig) { return mlIDbStorage.putConfig(ML_SYNC_JOB_CONFIG_NAME, newConfig); } @@ -27,3 +36,7 @@ export async function updateMLSyncJobConfig(newConfig: JobConfig) { export async function updateMLSyncConfig(newConfig: MLSyncConfig) { return mlIDbStorage.putConfig(ML_SYNC_CONFIG_NAME, newConfig); } + +export async function updateMLSearchConfig(newConfig: MLSearchConfig) { + return mlIDbStorage.putConfig(ML_SEARCH_CONFIG_NAME, newConfig); +} diff --git a/src/utils/storage/mlIDbStorage.ts b/src/utils/storage/mlIDbStorage.ts index a27524a2d..b1a58f016 100644 --- a/src/utils/storage/mlIDbStorage.ts +++ b/src/utils/storage/mlIDbStorage.ts @@ -1,4 +1,5 @@ import { + DEFAULT_ML_SEARCH_CONFIG, DEFAULT_ML_SYNC_CONFIG, DEFAULT_ML_SYNC_JOB_CONFIG, } from 'constants/machineLearning/config'; @@ -16,6 +17,7 @@ import { runningInBrowser } from 'utils/common'; export const ML_SYNC_JOB_CONFIG_NAME = 'ml-sync-job'; export const ML_SYNC_CONFIG_NAME = 'ml-sync'; +export const ML_SEARCH_CONFIG_NAME = 'ml-search'; const MLDATA_DB_NAME = 'mldata'; interface MLDb extends DBSchema { @@ -50,7 +52,7 @@ class MLIDbStorage { return; } - this.db = openDB(MLDATA_DB_NAME, 2, { + this.db = openDB(MLDATA_DB_NAME, 3, { upgrade(db, oldVersion, newVersion, tx) { if (oldVersion < 1) { const filesStore = db.createObjectStore('files', { @@ -82,6 +84,12 @@ class MLIDbStorage { ML_SYNC_CONFIG_NAME ); } + if (oldVersion < 3) { + tx.objectStore('configs').add( + DEFAULT_ML_SEARCH_CONFIG, + ML_SEARCH_CONFIG_NAME + ); + } }, }); } diff --git a/src/utils/strings/englishConstants.tsx b/src/utils/strings/englishConstants.tsx index 4535cd51a..cbc10cb19 100644 --- a/src/utils/strings/englishConstants.tsx +++ b/src/utils/strings/englishConstants.tsx @@ -204,6 +204,8 @@ const englishConstants = { ), CONTACT_SUPPORT: 'contact support', REQUEST_FEATURE: 'request feature', + ENABLE_ML_SEARCH: 'enable ML Search beta', + DISABLE_ML_SEARCH: 'disable ML Search beta', ML_DEBUG: 'ML Debug', SUPPORT: 'support', CONFIRM: 'confirm',