Add ml search config to enable or disable ml search

Show ml search ui elements when enabled
This commit is contained in:
Shailesh Pandit 2022-01-24 19:11:40 +05:30
parent b21e5608bd
commit 8c0c3c5c94
10 changed files with 159 additions and 43 deletions

View file

@ -1,4 +1,4 @@
import React, { useEffect, useRef, useState } from 'react'; import React, { useContext, useEffect, useRef, useState } from 'react';
import Photoswipe from 'photoswipe'; import Photoswipe from 'photoswipe';
import PhotoswipeUIDefault from 'photoswipe/dist/photoswipe-ui-default'; import PhotoswipeUIDefault from 'photoswipe/dist/photoswipe-ui-default';
import classnames from 'classnames'; import classnames from 'classnames';
@ -49,6 +49,7 @@ import { Formik } from 'formik';
import * as Yup from 'yup'; import * as Yup from 'yup';
import EnteSpinner from 'components/EnteSpinner'; import EnteSpinner from 'components/EnteSpinner';
import EnteDateTimePicker from 'components/EnteDateTimePicker'; import EnteDateTimePicker from 'components/EnteDateTimePicker';
import { AppContext } from 'pages/_app';
interface Iprops { interface Iprops {
isOpen: boolean; isOpen: boolean;
@ -397,6 +398,7 @@ function InfoModal({
exif, exif,
scheduleUpdate, scheduleUpdate,
}) { }) {
const appContext = useContext(AppContext);
return ( return (
<Modal show={showInfo} onHide={handleCloseInfo}> <Modal show={showInfo} onHide={handleCloseInfo}>
<Modal.Header closeButton> <Modal.Header closeButton>
@ -438,16 +440,22 @@ function InfoModal({
{constants.SHOW_MAP} {constants.SHOW_MAP}
</a> </a>
)} )}
<div> {appContext.mlSearchEnabled && (
<Legend>{constants.PEOPLE}</Legend> <>
</div> <div>
<PhotoPeopleList file={items[photoSwipe?.getCurrentIndex()]} /> <Legend>{constants.PEOPLE}</Legend>
<div> </div>
<Legend>{constants.UNIDENTIFIED_FACES}</Legend> <PhotoPeopleList
</div> file={items[photoSwipe?.getCurrentIndex()]}
<UnidentifiedFaces />
file={items[photoSwipe?.getCurrentIndex()]} <div>
/> <Legend>{constants.UNIDENTIFIED_FACES}</Legend>
</div>
<UnidentifiedFaces
file={items[photoSwipe?.getCurrentIndex()]}
/>
</>
)}
{exif && ( {exif && (
<> <>
<ExifData exif={exif} /> <ExifData exif={exif} />

View file

@ -1,5 +1,5 @@
import { Search, SearchStats } from 'pages/gallery'; 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 styled from 'styled-components';
import AsyncSelect from 'react-select/async'; import AsyncSelect from 'react-select/async';
import { components } from 'react-select'; import { components } from 'react-select';
@ -28,6 +28,7 @@ import VideoIcon from './icons/VideoIcon';
import { IconButton } from './Container'; import { IconButton } from './Container';
import { Person } from 'types/machineLearning'; import { Person } from 'types/machineLearning';
import { PeopleList } from './MachineLearning/PeopleList'; import { PeopleList } from './MachineLearning/PeopleList';
import { AppContext } from 'pages/_app';
const Wrapper = styled.div<{ isDisabled: boolean; isOpen: boolean }>` const Wrapper = styled.div<{ isDisabled: boolean; isOpen: boolean }>`
position: fixed; position: fixed;
@ -118,6 +119,7 @@ interface Props {
export default function SearchBar(props: Props) { export default function SearchBar(props: Props) {
const selectRef = useRef(null); const selectRef = useRef(null);
const [value, setValue] = useState<Suggestion>(null); const [value, setValue] = useState<Suggestion>(null);
const appContext = useContext(AppContext);
const handleChange = (value) => { const handleChange = (value) => {
setValue(value); setValue(value);
@ -129,12 +131,14 @@ export default function SearchBar(props: Props) {
// Functionality // Functionality
// = ========================= // = =========================
const getAutoCompleteSuggestions = async (searchPhrase: string) => { const getAutoCompleteSuggestions = async (searchPhrase: string) => {
const options = await getAllPeopleSuggestion(); const options = [];
// const options = [];
searchPhrase = searchPhrase.trim().toLowerCase(); searchPhrase = searchPhrase.trim().toLowerCase();
if (!searchPhrase?.length) { if (!searchPhrase?.length) {
return []; return [];
} }
if (appContext.mlSearchEnabled) {
options.push(...(await getAllPeopleSuggestion()));
}
options.push(...getHolidaySuggestion(searchPhrase)); options.push(...getHolidaySuggestion(searchPhrase));
options.push(...getYearSuggestion(searchPhrase)); options.push(...getYearSuggestion(searchPhrase));

View file

@ -37,6 +37,7 @@ import {
TRASH_SECTION, TRASH_SECTION,
} from 'components/pages/gallery/Collections'; } from 'components/pages/gallery/Collections';
import FixLargeThumbnails from './FixLargeThumbnail'; import FixLargeThumbnails from './FixLargeThumbnail';
import { AppContext } from 'pages/_app';
interface Props { interface Props {
collections: Collection[]; collections: Collection[];
setDialogMessage: SetDialogMessage; setDialogMessage: SetDialogMessage;
@ -56,6 +57,7 @@ export default function Sidebar(props: Props) {
const [exportModalView, setExportModalView] = useState(false); const [exportModalView, setExportModalView] = useState(false);
const [fixLargeThumbsView, setFixLargeThumbsView] = useState(false); const [fixLargeThumbsView, setFixLargeThumbsView] = useState(false);
const galleryContext = useContext(GalleryContext); const galleryContext = useContext(GalleryContext);
const appContext = useContext(AppContext);
useEffect(() => { useEffect(() => {
const main = async () => { const main = async () => {
if (!isOpen) { if (!isOpen) {
@ -297,6 +299,17 @@ export default function Sidebar(props: Props) {
onClick={openFeedbackURL}> onClick={openFeedbackURL}>
{constants.REQUEST_FEATURE} {constants.REQUEST_FEATURE}
</LinkButton> </LinkButton>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => {
appContext.updateMlSearchEnabled(
!appContext.mlSearchEnabled
);
}}>
{appContext.mlSearchEnabled
? constants.DISABLE_ML_SEARCH
: constants.ENABLE_ML_SEARCH}
</LinkButton>
<LinkButton <LinkButton
style={{ marginTop: '30px' }} style={{ marginTop: '30px' }}
onClick={() => { onClick={() => {

View file

@ -1,5 +1,5 @@
import { JobConfig } from 'types/common/job'; 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 = { export const DEFAULT_ML_SYNC_JOB_CONFIG: JobConfig = {
intervalSec: 30, intervalSec: 30,
@ -53,6 +53,10 @@ export const DEFAULT_ML_SYNC_CONFIG: MLSyncConfig = {
mlVersion: 2, mlVersion: 2,
}; };
export const DEFAULT_ML_SEARCH_CONFIG: MLSearchConfig = {
enabled: false,
};
export const ML_SYNC_DOWNLOAD_TIMEOUT_MS = 300000; export const ML_SYNC_DOWNLOAD_TIMEOUT_MS = 300000;
export const MAX_FACE_DISTANCE_PERCENT = Math.sqrt(2) / 100; export const MAX_FACE_DISTANCE_PERCENT = Math.sqrt(2) / 100;

View file

@ -16,6 +16,10 @@ import FlashMessageBar from 'components/FlashMessageBar';
import Head from 'next/head'; import Head from 'next/head';
import { eventBus, Events } from 'services/events'; import { eventBus, Events } from 'services/events';
import mlWorkManager from 'services/machineLearning/mlWorkManager'; import mlWorkManager from 'services/machineLearning/mlWorkManager';
import {
getMLSearchConfig,
updateMLSearchConfig,
} from 'utils/machineLearning/config';
const GlobalStyles = createGlobalStyle` const GlobalStyles = createGlobalStyle`
/* ubuntu-regular - latin */ /* ubuntu-regular - latin */
@ -483,6 +487,8 @@ type AppContextType = {
setDisappearingFlashMessage: (message: FlashMessage) => void; setDisappearingFlashMessage: (message: FlashMessage) => void;
redirectUrl: string; redirectUrl: string;
setRedirectUrl: (url: string) => void; setRedirectUrl: (url: string) => void;
mlSearchEnabled: boolean;
updateMlSearchEnabled: (enabled: boolean) => void;
}; };
export enum FLASH_MESSAGE_TYPE { export enum FLASH_MESSAGE_TYPE {
@ -513,6 +519,7 @@ export default function App({ Component, err }) {
const [redirectName, setRedirectName] = useState<string>(null); const [redirectName, setRedirectName] = useState<string>(null);
const [flashMessage, setFlashMessage] = useState<FlashMessage>(null); const [flashMessage, setFlashMessage] = useState<FlashMessage>(null);
const [redirectUrl, setRedirectUrl] = useState(null); const [redirectUrl, setRedirectUrl] = useState(null);
const [mlSearchEnabled, setMlSearchEnabled] = useState(false);
useEffect(() => { useEffect(() => {
if ( if (
!('serviceWorker' in navigator) || !('serviceWorker' in navigator) ||
@ -550,12 +557,18 @@ export default function App({ Component, err }) {
}, []); }, []);
useEffect(() => { useEffect(() => {
try { const loadMlSearchState = async () => {
mlWorkManager; try {
eventBus.emit(Events.APP_START); const mlSearchConfig = await getMLSearchConfig();
} catch (e) { setMlSearchEnabled(mlSearchConfig.enabled);
logError(e, 'Error in appStart handlers'); mlWorkManager.setMlSearchEnabled(mlSearchConfig.enabled);
} eventBus.emit(Events.APP_START);
} catch (e) {
logError(e, 'Error in appStart handlers');
}
};
loadMlSearchState();
}, []); }, []);
const setUserOnline = () => setOffline(false); const setUserOnline = () => setOffline(false);
@ -612,6 +625,13 @@ export default function App({ Component, err }) {
setFlashMessage(flashMessages); setFlashMessage(flashMessages);
setTimeout(() => setFlashMessage(null), 5000); 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 // ho ja yaar
return ( return (
<> <>
@ -657,6 +677,8 @@ export default function App({ Component, err }) {
setDisappearingFlashMessage, setDisappearingFlashMessage,
redirectUrl, redirectUrl,
setRedirectUrl, setRedirectUrl,
mlSearchEnabled,
updateMlSearchEnabled,
}}> }}>
{loading ? ( {loading ? (
<Container> <Container>

View file

@ -19,8 +19,12 @@ class MLWorkManager {
private mlSyncJob: MLSyncJob; private mlSyncJob: MLSyncJob;
private syncJobWorker: MLWorkerWithProxy; private syncJobWorker: MLWorkerWithProxy;
private debouncedLiveSyncIdle: () => void;
private debouncedFilesUpdated: () => void;
private liveSyncQueue: PQueue; private liveSyncQueue: PQueue;
private liveSyncWorker: MLWorkerWithProxy; private liveSyncWorker: MLWorkerWithProxy;
private mlSearchEnabled: boolean;
constructor() { constructor() {
this.liveSyncQueue = new PQueue({ this.liveSyncQueue = new PQueue({
@ -29,31 +33,58 @@ class MLWorkManager {
timeout: LIVE_SYNC_QUEUE_TIMEOUT_SEC * 1000, timeout: LIVE_SYNC_QUEUE_TIMEOUT_SEC * 1000,
throwOnTimeout: true, throwOnTimeout: true,
}); });
logQueueStats(this.liveSyncQueue, 'livesync'); this.mlSearchEnabled = false;
const debouncedLiveSyncIdle = debounce( eventBus.on(Events.LOGOUT, this.logoutHandler, this);
this.debouncedLiveSyncIdle = debounce(
() => this.onLiveSyncIdle(), () => this.onLiveSyncIdle(),
LIVE_SYNC_IDLE_DEBOUNCE_SEC * 1000 LIVE_SYNC_IDLE_DEBOUNCE_SEC * 1000
); );
this.liveSyncQueue.on('idle', () => debouncedLiveSyncIdle(), this); this.debouncedFilesUpdated = debounce(
() => this.mlSearchEnabled && this.localFilesUpdatedHandler(),
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(),
LOCAL_FILES_UPDATED_DEBOUNCE_SEC * 1000 LOCAL_FILES_UPDATED_DEBOUNCE_SEC * 1000
); );
eventBus.on( }
Events.LOCAL_FILES_UPDATED,
() => debouncedFilesUpdated(), public async setMlSearchEnabled(enabled: boolean) {
this 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 // Handlers
@ -83,6 +114,9 @@ class MLWorkManager {
enteFile: File; enteFile: File;
localFile: globalThis.File; localFile: globalThis.File;
}) { }) {
if (!this.mlSearchEnabled) {
return;
}
console.log('fileUploadedHandler: ', arg.enteFile.id); console.log('fileUploadedHandler: ', arg.enteFile.id);
if (arg.enteFile.metadata.fileType !== FILE_TYPE.IMAGE) { if (arg.enteFile.metadata.fileType !== FILE_TYPE.IMAGE) {
console.log('Skipping non image file for local file processing'); console.log('Skipping non image file for local file processing');
@ -131,7 +165,7 @@ class MLWorkManager {
private async onLiveSyncIdle() { private async onLiveSyncIdle() {
console.log('Live sync idle'); console.log('Live sync idle');
await this.terminateLiveSyncWorker(); await this.terminateLiveSyncWorker();
this.startSyncJob(); this.mlSearchEnabled && this.startSyncJob();
} }
public async syncLocalFile(enteFile: File, localFile: globalThis.File) { public async syncLocalFile(enteFile: File, localFile: globalThis.File) {
@ -185,6 +219,10 @@ class MLWorkManager {
public async startSyncJob() { public async startSyncJob() {
try { try {
console.log('MLWorkManager.startSyncJob'); console.log('MLWorkManager.startSyncJob');
if (!this.mlSearchEnabled) {
console.log('ML Search disabled, not starting ml sync job');
return;
}
if (!getToken()) { if (!getToken()) {
console.log('User not logged in, not starting ml sync job'); console.log('User not logged in, not starting ml sync job');
return; return;

View file

@ -244,6 +244,10 @@ export interface MLSyncConfig extends Config {
mlVersion: number; mlVersion: number;
} }
export interface MLSearchConfig extends Config {
enabled: boolean;
}
export interface MLSyncContext { export interface MLSyncContext {
token: string; token: string;
config: MLSyncConfig; config: MLSyncConfig;

View file

@ -1,10 +1,12 @@
import { import {
DEFAULT_ML_SEARCH_CONFIG,
DEFAULT_ML_SYNC_CONFIG, DEFAULT_ML_SYNC_CONFIG,
DEFAULT_ML_SYNC_JOB_CONFIG, DEFAULT_ML_SYNC_JOB_CONFIG,
} from 'constants/machineLearning/config'; } from 'constants/machineLearning/config';
import { JobConfig } from 'types/common/job'; import { JobConfig } from 'types/common/job';
import { MLSyncConfig } from 'types/machineLearning'; import { MLSearchConfig, MLSyncConfig } from 'types/machineLearning';
import mlIDbStorage, { import mlIDbStorage, {
ML_SEARCH_CONFIG_NAME,
ML_SYNC_CONFIG_NAME, ML_SYNC_CONFIG_NAME,
ML_SYNC_JOB_CONFIG_NAME, ML_SYNC_JOB_CONFIG_NAME,
} from 'utils/storage/mlIDbStorage'; } from 'utils/storage/mlIDbStorage';
@ -20,6 +22,13 @@ export async function getMLSyncConfig() {
return mlIDbStorage.getConfig(ML_SYNC_CONFIG_NAME, DEFAULT_ML_SYNC_CONFIG); 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) { export async function updateMLSyncJobConfig(newConfig: JobConfig) {
return mlIDbStorage.putConfig(ML_SYNC_JOB_CONFIG_NAME, newConfig); 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) { export async function updateMLSyncConfig(newConfig: MLSyncConfig) {
return mlIDbStorage.putConfig(ML_SYNC_CONFIG_NAME, newConfig); return mlIDbStorage.putConfig(ML_SYNC_CONFIG_NAME, newConfig);
} }
export async function updateMLSearchConfig(newConfig: MLSearchConfig) {
return mlIDbStorage.putConfig(ML_SEARCH_CONFIG_NAME, newConfig);
}

View file

@ -1,4 +1,5 @@
import { import {
DEFAULT_ML_SEARCH_CONFIG,
DEFAULT_ML_SYNC_CONFIG, DEFAULT_ML_SYNC_CONFIG,
DEFAULT_ML_SYNC_JOB_CONFIG, DEFAULT_ML_SYNC_JOB_CONFIG,
} from 'constants/machineLearning/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_JOB_CONFIG_NAME = 'ml-sync-job';
export const ML_SYNC_CONFIG_NAME = 'ml-sync'; export const ML_SYNC_CONFIG_NAME = 'ml-sync';
export const ML_SEARCH_CONFIG_NAME = 'ml-search';
const MLDATA_DB_NAME = 'mldata'; const MLDATA_DB_NAME = 'mldata';
interface MLDb extends DBSchema { interface MLDb extends DBSchema {
@ -50,7 +52,7 @@ class MLIDbStorage {
return; return;
} }
this.db = openDB<MLDb>(MLDATA_DB_NAME, 2, { this.db = openDB<MLDb>(MLDATA_DB_NAME, 3, {
upgrade(db, oldVersion, newVersion, tx) { upgrade(db, oldVersion, newVersion, tx) {
if (oldVersion < 1) { if (oldVersion < 1) {
const filesStore = db.createObjectStore('files', { const filesStore = db.createObjectStore('files', {
@ -82,6 +84,12 @@ class MLIDbStorage {
ML_SYNC_CONFIG_NAME ML_SYNC_CONFIG_NAME
); );
} }
if (oldVersion < 3) {
tx.objectStore('configs').add(
DEFAULT_ML_SEARCH_CONFIG,
ML_SEARCH_CONFIG_NAME
);
}
}, },
}); });
} }

View file

@ -204,6 +204,8 @@ const englishConstants = {
), ),
CONTACT_SUPPORT: 'contact support', CONTACT_SUPPORT: 'contact support',
REQUEST_FEATURE: 'request feature', REQUEST_FEATURE: 'request feature',
ENABLE_ML_SEARCH: 'enable ML Search beta',
DISABLE_ML_SEARCH: 'disable ML Search beta',
ML_DEBUG: 'ML Debug', ML_DEBUG: 'ML Debug',
SUPPORT: 'support', SUPPORT: 'support',
CONFIRM: 'confirm', CONFIRM: 'confirm',