diff --git a/web/packages/new/photos/components/MLSettings.tsx b/web/packages/new/photos/components/MLSettings.tsx index 72bf56e4d2..ee3624d6fa 100644 --- a/web/packages/new/photos/components/MLSettings.tsx +++ b/web/packages/new/photos/components/MLSettings.tsx @@ -6,6 +6,7 @@ import { mlStatusSnapshot, mlStatusSubscribe, pauseML, + resumeML, type MLStatus, } from "@/new/photos/services/ml"; import { EnteDrawer } from "@/new/shared/components/EnteDrawer"; @@ -70,13 +71,7 @@ export const MLSettings: React.FC = ({ else onClose(); }; - // The user may've changed the remote flag on a different device, so in both - // cases (enable or resume), do the same flow: - // - // - If remote flag is not set, then show the consent dialog - // - Otherwise enable ML (both locally and on remote). - // - const handleEnableOrResumeML = async () => { + const handleEnableML = async () => { startLoading(); try { if (!(await getIsMLEnabledRemote())) { @@ -106,15 +101,6 @@ export const MLSettings: React.FC = ({ } }; - const handleToggleLocal = async () => { - try { - isMLEnabled() ? pauseML() : await handleEnableOrResumeML(); - } catch (e) { - log.error("Failed to toggle local state of ML", e); - somethingWentWrong(); - } - }; - const handleDisableML = async () => { startLoading(); try { @@ -131,12 +117,11 @@ export const MLSettings: React.FC = ({ if (!mlStatus) { component = ; } else if (mlStatus.phase == "disabled") { - component = ; + component = ; } else { component = ( ); @@ -321,8 +306,6 @@ const FaceConsent: React.FC = ({ interface ManageMLProps { /** The {@link MLStatus}; a non-disabled one. */ mlStatus: Exclude; - /** Called when the user wants to toggle the ML status locally. */ - onToggleLocal: () => void; /** Called when the user wants to disable ML. */ onDisableML: () => void; /** Subset of appContext. */ @@ -331,12 +314,31 @@ interface ManageMLProps { const ManageML: React.FC = ({ mlStatus, - onToggleLocal, onDisableML, setDialogBoxAttributesV2, }) => { const { phase, nSyncedFiles, nTotalFiles } = mlStatus; + let status: string; + switch (phase) { + case "paused": + status = pt("Paused"); + break; + case "indexing": + status = pt("Indexing"); + break; + case "scheduled": + status = pt("Scheduled"); + break; + // TODO: Clustering + default: + status = pt("Done"); + break; + } + const processed = `${nSyncedFiles} / ${nTotalFiles}`; + + const handleToggleLocal = () => (isMLEnabled() ? pauseML() : resumeML()); + const confirmDisableML = () => { setDialogBoxAttributesV2({ title: pt("Disable ML search"), @@ -373,19 +375,19 @@ const ManageML: React.FC = ({ label={pt("On this device")} variant="toggle" checked={phase != "paused"} - onClick={onToggleLocal} + onClick={handleToggleLocal} /> - + Status - Indexing + {status} = ({ justifyContent={"space-between"} > Processed - - {`${nSyncedFiles} / ${nTotalFiles}`} - + {processed} diff --git a/web/packages/new/photos/services/ml/index.ts b/web/packages/new/photos/services/ml/index.ts index e0084045d1..f5a501151d 100644 --- a/web/packages/new/photos/services/ml/index.ts +++ b/web/packages/new/photos/services/ml/index.ts @@ -149,6 +149,7 @@ export const enableML = async () => { setIsMLEnabledLocally(true); _isMLEnabledRemote = true; _isMLEnabledLocal = true; + setInterimScheduledStatus(); triggerStatusUpdate(); triggerMLSync(); }; @@ -188,6 +189,7 @@ export const pauseML = () => { export const resumeML = () => { setIsMLEnabledLocally(true); _isMLEnabledLocal = true; + setInterimScheduledStatus(); triggerStatusUpdate(); triggerMLSync(); }; @@ -347,8 +349,11 @@ export const mlStatusSnapshot = (): MLStatus | undefined => { const triggerStatusUpdate = () => void updateMLStatusSnapshot(); /** Unconditionally update of the {@link MLStatus} snapshot. */ -const updateMLStatusSnapshot = async () => { - _mlStatusSnapshot = await getMLStatus(); +const updateMLStatusSnapshot = async () => + setMLStatusSnapshot(await getMLStatus()); + +const setMLStatusSnapshot = (snapshot: MLStatus) => { + _mlStatusSnapshot = snapshot; _mlStatusListeners.forEach((l) => l()); }; @@ -358,7 +363,7 @@ const updateMLStatusSnapshot = async () => { * Precondition: ML must be enabled on remote, though it is fine if it is paused * locally. */ -export const getMLStatus = async (): Promise => { +const getMLStatus = async (): Promise => { if (!_isMLEnabledRemote) return { phase: "disabled" }; const { indexedCount, indexableCount } = await indexableAndIndexedCounts(); @@ -383,6 +388,26 @@ export const getMLStatus = async (): Promise => { }; }; +/** + * When the user enables or resumes ML, we wish to give immediate feedback. + * + * So this is an intermediate state with possibly incorrect counts (but correct + * phase) that is set immediately to trigger a UI update. It uses the counts + * from the last known status, just updates the phase. + * + * Once the worker is initialized and the correct counts fetched, this will + * update to the correct state (should take less than one second). + */ +const setInterimScheduledStatus = () => { + let nSyncedFiles = 0, + nTotalFiles = 0; + if (_mlStatusSnapshot && _mlStatusSnapshot.phase != "disabled") { + nSyncedFiles = _mlStatusSnapshot.nSyncedFiles; + nTotalFiles = _mlStatusSnapshot.nTotalFiles; + } + setMLStatusSnapshot({ phase: "scheduled", nSyncedFiles, nTotalFiles }); +}; + /** * Return the IDs of all the faces in the given {@link enteFile} that are not * associated with a person cluster.