diff --git a/web/apps/photos/src/components/MachineLearning/FaceCropImageView.tsx b/web/apps/photos/src/components/MachineLearning/FaceCropImageView.tsx deleted file mode 100644 index d5b364cc9..000000000 --- a/web/apps/photos/src/components/MachineLearning/FaceCropImageView.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import log from "@/next/log"; -import { cached } from "@ente/shared/storage/cache"; -import { getData, LS_KEYS } from "@ente/shared/storage/localStorage"; -import { User } from "@ente/shared/user/types"; -import { Skeleton } from "@mui/material"; -import { useEffect, useState } from "react"; -import machineLearningService from "services/machineLearning/machineLearningService"; - -interface FaceCropImageViewProps { - url: string; - faceID: string; -} - -export const FaceCropImageView: React.FC = ({ - url, - faceID, -}) => { - const [objectURL, setObjectURL] = useState(); - - useEffect(() => { - let didCancel = false; - - async function loadImage() { - const user: User = getData(LS_KEYS.USER); - let blob: Blob; - if (!url || !user) { - blob = undefined; - } else { - blob = await cached("face-crops", url, async () => { - try { - log.debug( - () => - `ImageCacheView: regenerate face crop for ${faceID}`, - ); - return machineLearningService.regenerateFaceCrop( - user.token, - user.id, - faceID, - ); - } catch (e) { - log.error( - "ImageCacheView: regenerate face crop failed", - e, - ); - } - }); - } - - if (didCancel) return; - setObjectURL(URL.createObjectURL(blob)); - } - - loadImage(); - - return () => { - didCancel = true; - if (objectURL) URL.revokeObjectURL(objectURL); - }; - }, [url, faceID]); - - return objectURL ? ( - - ) : ( - - ); -}; diff --git a/web/apps/photos/src/components/MachineLearning/MLSearchSettings/enableFaceSearch.tsx b/web/apps/photos/src/components/MachineLearning/MLSearchSettings/enableFaceSearch.tsx deleted file mode 100644 index a007cb398..000000000 --- a/web/apps/photos/src/components/MachineLearning/MLSearchSettings/enableFaceSearch.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { - Button, - Checkbox, - DialogProps, - FormControlLabel, - FormGroup, - Link, - Stack, - Typography, -} from "@mui/material"; -import { EnteDrawer } from "components/EnteDrawer"; -import Titlebar from "components/Titlebar"; -import { t } from "i18next"; -import { useEffect, useState } from "react"; -import { Trans } from "react-i18next"; -export default function EnableFaceSearch({ - open, - onClose, - enableFaceSearch, - onRootClose, -}) { - const [acceptTerms, setAcceptTerms] = useState(false); - - useEffect(() => { - setAcceptTerms(false); - }, [open]); - - const handleRootClose = () => { - onClose(); - onRootClose(); - }; - - const handleDrawerClose: DialogProps["onClose"] = (_, reason) => { - if (reason === "backdropClick") { - handleRootClose(); - } else { - onClose(); - } - }; - return ( - - - - - - - ), - }} - /> - - - - setAcceptTerms(e.target.checked) - } - /> - } - label={t("FACE_SEARCH_CONFIRMATION")} - /> - - - - - - - - - ); -} diff --git a/web/apps/photos/src/components/MachineLearning/MLSearchSettings/enableMLSearch.tsx b/web/apps/photos/src/components/MachineLearning/MLSearchSettings/enableMLSearch.tsx deleted file mode 100644 index 1cd0a3b3f..000000000 --- a/web/apps/photos/src/components/MachineLearning/MLSearchSettings/enableMLSearch.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { Box, Button, Stack, Typography } from "@mui/material"; -import Titlebar from "components/Titlebar"; -import { t } from "i18next"; -import { Trans } from "react-i18next"; -import { openLink } from "utils/common"; - -export default function EnableMLSearch({ - onClose, - enableMlSearch, - onRootClose, -}) { - const showDetails = () => - openLink("https://ente.io/blog/desktop-ml-beta", true); - - return ( - - - - - {" "} - - - - - - - - - - - ); -} diff --git a/web/apps/photos/src/components/MachineLearning/MLSearchSettings/index.tsx b/web/apps/photos/src/components/MachineLearning/MLSearchSettings/index.tsx deleted file mode 100644 index 9b33a984a..000000000 --- a/web/apps/photos/src/components/MachineLearning/MLSearchSettings/index.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import log from "@/next/log"; -import { Box, DialogProps, Typography } from "@mui/material"; -import { EnteDrawer } from "components/EnteDrawer"; -import { t } from "i18next"; -import { AppContext } from "pages/_app"; -import { useContext, useState } from "react"; -import { Trans } from "react-i18next"; -import { - getFaceSearchEnabledStatus, - updateFaceSearchEnabledStatus, -} from "services/userService"; -import EnableFaceSearch from "./enableFaceSearch"; -import EnableMLSearch from "./enableMLSearch"; -import ManageMLSearch from "./manageMLSearch"; - -const MLSearchSettings = ({ open, onClose, onRootClose }) => { - const { - updateMlSearchEnabled, - mlSearchEnabled, - setDialogMessage, - somethingWentWrong, - startLoading, - finishLoading, - } = useContext(AppContext); - - const [enableFaceSearchView, setEnableFaceSearchView] = useState(false); - - const openEnableFaceSearch = () => { - setEnableFaceSearchView(true); - }; - const closeEnableFaceSearch = () => { - setEnableFaceSearchView(false); - }; - - const enableMlSearch = async () => { - try { - const hasEnabledFaceSearch = await getFaceSearchEnabledStatus(); - if (!hasEnabledFaceSearch) { - openEnableFaceSearch(); - } else { - updateMlSearchEnabled(true); - } - } catch (e) { - log.error("Enable ML search failed", e); - somethingWentWrong(); - } - }; - - const enableFaceSearch = async () => { - try { - startLoading(); - await updateFaceSearchEnabledStatus(true); - updateMlSearchEnabled(true); - closeEnableFaceSearch(); - finishLoading(); - } catch (e) { - log.error("Enable face search failed", e); - somethingWentWrong(); - } - }; - - const disableMlSearch = async () => { - try { - await updateMlSearchEnabled(false); - onClose(); - } catch (e) { - log.error("Disable ML search failed", e); - somethingWentWrong(); - } - }; - - const disableFaceSearch = async () => { - try { - startLoading(); - await updateFaceSearchEnabledStatus(false); - await disableMlSearch(); - finishLoading(); - } catch (e) { - log.error("Disable face search failed", e); - somethingWentWrong(); - } - }; - - const confirmDisableFaceSearch = () => { - setDialogMessage({ - title: t("DISABLE_FACE_SEARCH_TITLE"), - content: ( - - - - ), - close: { text: t("CANCEL") }, - proceed: { - variant: "primary", - text: t("DISABLE_FACE_SEARCH"), - action: disableFaceSearch, - }, - }); - }; - - const handleRootClose = () => { - onClose(); - onRootClose(); - }; - - const handleDrawerClose: DialogProps["onClose"] = (_, reason) => { - if (reason === "backdropClick") { - handleRootClose(); - } else { - onClose(); - } - }; - - return ( - - - {mlSearchEnabled ? ( - - ) : ( - - )} - - - - - ); -}; - -export default MLSearchSettings; diff --git a/web/apps/photos/src/components/MachineLearning/MLSearchSettings/manageMLSearch.tsx b/web/apps/photos/src/components/MachineLearning/MLSearchSettings/manageMLSearch.tsx deleted file mode 100644 index 15dacd7b2..000000000 --- a/web/apps/photos/src/components/MachineLearning/MLSearchSettings/manageMLSearch.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Box, Stack } from "@mui/material"; -import { EnteMenuItem } from "components/Menu/EnteMenuItem"; -import { MenuItemGroup } from "components/Menu/MenuItemGroup"; -import Titlebar from "components/Titlebar"; -import { t } from "i18next"; - -export default function ManageMLSearch({ - onClose, - disableMlSearch, - handleDisableFaceSearch, - onRootClose, -}) { - return ( - - - - - - - - - - - - - - ); -} diff --git a/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx b/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx index 24de200c0..34fdb8e34 100644 --- a/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx +++ b/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx @@ -10,11 +10,8 @@ import TextSnippetOutlined from "@mui/icons-material/TextSnippetOutlined"; import { Box, DialogProps, Link, Stack, styled } from "@mui/material"; import { Chip } from "components/Chip"; import { EnteDrawer } from "components/EnteDrawer"; -import { - PhotoPeopleList, - UnidentifiedFaces, -} from "components/MachineLearning/PeopleList"; import Titlebar from "components/Titlebar"; +import { PhotoPeopleList, UnidentifiedFaces } from "components/ml/PeopleList"; import LinkButton from "components/pages/gallery/LinkButton"; import { t } from "i18next"; import { AppContext } from "pages/_app"; diff --git a/web/apps/photos/src/components/Search/SearchBar/searchInput/MenuWithPeople.tsx b/web/apps/photos/src/components/Search/SearchBar/searchInput/MenuWithPeople.tsx index 89bdce56a..6ebc0d942 100644 --- a/web/apps/photos/src/components/Search/SearchBar/searchInput/MenuWithPeople.tsx +++ b/web/apps/photos/src/components/Search/SearchBar/searchInput/MenuWithPeople.tsx @@ -1,6 +1,6 @@ import { Row } from "@ente/shared/components/Container"; import { Box, styled } from "@mui/material"; -import { PeopleList } from "components/MachineLearning/PeopleList"; +import { PeopleList } from "components/ml/PeopleList"; import { t } from "i18next"; import { AppContext } from "pages/_app"; import { useContext } from "react"; diff --git a/web/apps/photos/src/components/Sidebar/AdvancedSettings.tsx b/web/apps/photos/src/components/Sidebar/AdvancedSettings.tsx index 817aecb2b..6972cc161 100644 --- a/web/apps/photos/src/components/Sidebar/AdvancedSettings.tsx +++ b/web/apps/photos/src/components/Sidebar/AdvancedSettings.tsx @@ -3,9 +3,9 @@ import ChevronRight from "@mui/icons-material/ChevronRight"; import ScienceIcon from "@mui/icons-material/Science"; import { Box, DialogProps, Stack, Typography } from "@mui/material"; import { EnteDrawer } from "components/EnteDrawer"; -import MLSearchSettings from "components/MachineLearning/MLSearchSettings"; import MenuSectionTitle from "components/Menu/MenuSectionTitle"; import Titlebar from "components/Titlebar"; +import { MLSearchSettings } from "components/ml/MLSearchSettings"; import { t } from "i18next"; import { useContext, useEffect, useState } from "react"; diff --git a/web/apps/photos/src/components/ml/MLSearchSettings.tsx b/web/apps/photos/src/components/ml/MLSearchSettings.tsx new file mode 100644 index 000000000..583b79529 --- /dev/null +++ b/web/apps/photos/src/components/ml/MLSearchSettings.tsx @@ -0,0 +1,327 @@ +import log from "@/next/log"; +import { + Box, + Button, + Checkbox, + DialogProps, + FormControlLabel, + FormGroup, + Link, + Stack, + Typography, +} from "@mui/material"; +import { EnteDrawer } from "components/EnteDrawer"; +import { EnteMenuItem } from "components/Menu/EnteMenuItem"; +import { MenuItemGroup } from "components/Menu/MenuItemGroup"; +import Titlebar from "components/Titlebar"; +import { t } from "i18next"; +import { AppContext } from "pages/_app"; +import { useContext, useEffect, useState } from "react"; +import { Trans } from "react-i18next"; +import { + getFaceSearchEnabledStatus, + updateFaceSearchEnabledStatus, +} from "services/userService"; +import { openLink } from "utils/common"; + +export const MLSearchSettings = ({ open, onClose, onRootClose }) => { + const { + updateMlSearchEnabled, + mlSearchEnabled, + setDialogMessage, + somethingWentWrong, + startLoading, + finishLoading, + } = useContext(AppContext); + + const [enableFaceSearchView, setEnableFaceSearchView] = useState(false); + + const openEnableFaceSearch = () => { + setEnableFaceSearchView(true); + }; + const closeEnableFaceSearch = () => { + setEnableFaceSearchView(false); + }; + + const enableMlSearch = async () => { + try { + const hasEnabledFaceSearch = await getFaceSearchEnabledStatus(); + if (!hasEnabledFaceSearch) { + openEnableFaceSearch(); + } else { + updateMlSearchEnabled(true); + } + } catch (e) { + log.error("Enable ML search failed", e); + somethingWentWrong(); + } + }; + + const enableFaceSearch = async () => { + try { + startLoading(); + await updateFaceSearchEnabledStatus(true); + updateMlSearchEnabled(true); + closeEnableFaceSearch(); + finishLoading(); + } catch (e) { + log.error("Enable face search failed", e); + somethingWentWrong(); + } + }; + + const disableMlSearch = async () => { + try { + await updateMlSearchEnabled(false); + onClose(); + } catch (e) { + log.error("Disable ML search failed", e); + somethingWentWrong(); + } + }; + + const disableFaceSearch = async () => { + try { + startLoading(); + await updateFaceSearchEnabledStatus(false); + await disableMlSearch(); + finishLoading(); + } catch (e) { + log.error("Disable face search failed", e); + somethingWentWrong(); + } + }; + + const confirmDisableFaceSearch = () => { + setDialogMessage({ + title: t("DISABLE_FACE_SEARCH_TITLE"), + content: ( + + + + ), + close: { text: t("CANCEL") }, + proceed: { + variant: "primary", + text: t("DISABLE_FACE_SEARCH"), + action: disableFaceSearch, + }, + }); + }; + + const handleRootClose = () => { + onClose(); + onRootClose(); + }; + + const handleDrawerClose: DialogProps["onClose"] = (_, reason) => { + if (reason === "backdropClick") { + handleRootClose(); + } else { + onClose(); + } + }; + + return ( + + + {mlSearchEnabled ? ( + + ) : ( + + )} + + + + + ); +}; + +function EnableFaceSearch({ open, onClose, enableFaceSearch, onRootClose }) { + const [acceptTerms, setAcceptTerms] = useState(false); + + useEffect(() => { + setAcceptTerms(false); + }, [open]); + + const handleRootClose = () => { + onClose(); + onRootClose(); + }; + + const handleDrawerClose: DialogProps["onClose"] = (_, reason) => { + if (reason === "backdropClick") { + handleRootClose(); + } else { + onClose(); + } + }; + return ( + + + + + + + ), + }} + /> + + + + setAcceptTerms(e.target.checked) + } + /> + } + label={t("FACE_SEARCH_CONFIRMATION")} + /> + + + + + + + + + ); +} + +function EnableMLSearch({ onClose, enableMlSearch, onRootClose }) { + const showDetails = () => + openLink("https://ente.io/blog/desktop-ml-beta", true); + + return ( + + + + + {" "} + + + + + + + + + + + ); +} + +function ManageMLSearch({ + onClose, + disableMlSearch, + handleDisableFaceSearch, + onRootClose, +}) { + return ( + + + + + + + + + + + + + + ); +} diff --git a/web/apps/photos/src/components/MachineLearning/PeopleList.tsx b/web/apps/photos/src/components/ml/PeopleList.tsx similarity index 71% rename from web/apps/photos/src/components/MachineLearning/PeopleList.tsx rename to web/apps/photos/src/components/ml/PeopleList.tsx index 397227693..ebe3ad443 100644 --- a/web/apps/photos/src/components/MachineLearning/PeopleList.tsx +++ b/web/apps/photos/src/components/ml/PeopleList.tsx @@ -1,8 +1,12 @@ import log from "@/next/log"; -import { styled } from "@mui/material"; +import { cached } from "@ente/shared/storage/cache"; +import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; +import { User } from "@ente/shared/user/types"; +import { Skeleton, styled } from "@mui/material"; import { Legend } from "components/PhotoViewer/styledComponents/Legend"; import { t } from "i18next"; import React, { useEffect, useState } from "react"; +import machineLearningService from "services/machineLearning/machineLearningService"; import { EnteFile } from "types/file"; import { Face, Person } from "types/machineLearning"; import { @@ -10,7 +14,6 @@ import { getPeopleList, getUnidentifiedFaces, } from "utils/machineLearning"; -import { FaceCropImageView } from "./FaceCropImageView"; const FaceChipContainer = styled("div")` display: flex; @@ -181,3 +184,62 @@ export function UnidentifiedFaces(props: { ); } + +interface FaceCropImageViewProps { + url: string; + faceID: string; +} + +export const FaceCropImageView: React.FC = ({ + url, + faceID, +}) => { + const [objectURL, setObjectURL] = useState(); + + useEffect(() => { + let didCancel = false; + + async function loadImage() { + const user: User = getData(LS_KEYS.USER); + let blob: Blob; + if (!url || !user) { + blob = undefined; + } else { + blob = await cached("face-crops", url, async () => { + try { + log.debug( + () => + `ImageCacheView: regenerate face crop for ${faceID}`, + ); + return machineLearningService.regenerateFaceCrop( + user.token, + user.id, + faceID, + ); + } catch (e) { + log.error( + "ImageCacheView: regenerate face crop failed", + e, + ); + } + }); + } + + if (didCancel) return; + setObjectURL(URL.createObjectURL(blob)); + } + + loadImage(); + + return () => { + didCancel = true; + if (objectURL) URL.revokeObjectURL(objectURL); + }; + }, [url, faceID]); + + return objectURL ? ( + + ) : ( + + ); +};