Consolidate

This commit is contained in:
Manav Rathi 2024-04-12 16:08:36 +05:30
parent b36175a779
commit 401f879424
No known key found for this signature in database
10 changed files with 394 additions and 425 deletions

View file

@ -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<FaceCropImageViewProps> = ({
url,
faceID,
}) => {
const [objectURL, setObjectURL] = useState<string | undefined>();
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 ? (
<img src={objectURL} />
) : (
<Skeleton variant="circular" height={120} width={120} />
);
};

View file

@ -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 (
<EnteDrawer
transitionDuration={0}
open={open}
onClose={handleDrawerClose}
BackdropProps={{
sx: { "&&&": { backgroundColor: "transparent" } },
}}
>
<Stack spacing={"4px"} py={"12px"}>
<Titlebar
onClose={onClose}
title={t("ENABLE_FACE_SEARCH_TITLE")}
onRootClose={handleRootClose}
/>
<Stack py={"20px"} px={"8px"} spacing={"32px"}>
<Typography color="text.muted" px={"8px"}>
<Trans
i18nKey={"ENABLE_FACE_SEARCH_DESCRIPTION"}
components={{
a: (
<Link
target="_blank"
href="https://ente.io/privacy#8-biometric-information-privacy-policy"
underline="always"
sx={{
color: "inherit",
textDecorationColor: "inherit",
}}
/>
),
}}
/>
</Typography>
<FormGroup sx={{ width: "100%" }}>
<FormControlLabel
sx={{
color: "text.muted",
ml: 0,
mt: 2,
}}
control={
<Checkbox
size="small"
checked={acceptTerms}
onChange={(e) =>
setAcceptTerms(e.target.checked)
}
/>
}
label={t("FACE_SEARCH_CONFIRMATION")}
/>
</FormGroup>
<Stack px={"8px"} spacing={"8px"}>
<Button
color={"accent"}
size="large"
disabled={!acceptTerms}
onClick={enableFaceSearch}
>
{t("ENABLE_FACE_SEARCH")}
</Button>
<Button
color={"secondary"}
size="large"
onClick={onClose}
>
{t("CANCEL")}
</Button>
</Stack>
</Stack>
</Stack>
</EnteDrawer>
);
}

View file

@ -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 (
<Stack spacing={"4px"} py={"12px"}>
<Titlebar
onClose={onClose}
title={t("ML_SEARCH")}
onRootClose={onRootClose}
/>
<Stack py={"20px"} px={"8px"} spacing={"32px"}>
<Box px={"8px"}>
{" "}
<Typography color="text.muted">
<Trans i18nKey={"ENABLE_ML_SEARCH_DESCRIPTION"} />
</Typography>
</Box>
<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>
</Stack>
</Stack>
);
}

View file

@ -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: (
<Typography>
<Trans i18nKey={"DISABLE_FACE_SEARCH_DESCRIPTION"} />
</Typography>
),
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 (
<Box>
<EnteDrawer
anchor="left"
transitionDuration={0}
open={open}
onClose={handleDrawerClose}
BackdropProps={{
sx: { "&&&": { backgroundColor: "transparent" } },
}}
>
{mlSearchEnabled ? (
<ManageMLSearch
onClose={onClose}
disableMlSearch={disableMlSearch}
handleDisableFaceSearch={confirmDisableFaceSearch}
onRootClose={handleRootClose}
/>
) : (
<EnableMLSearch
onClose={onClose}
enableMlSearch={enableMlSearch}
onRootClose={handleRootClose}
/>
)}
</EnteDrawer>
<EnableFaceSearch
open={enableFaceSearchView}
onClose={closeEnableFaceSearch}
enableFaceSearch={enableFaceSearch}
onRootClose={handleRootClose}
/>
</Box>
);
};
export default MLSearchSettings;

View file

@ -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 (
<Stack spacing={"4px"} py={"12px"}>
<Titlebar
onClose={onClose}
title={t("ML_SEARCH")}
onRootClose={onRootClose}
/>
<Box px={"16px"}>
<Stack py={"20px"} spacing={"24px"}>
<MenuItemGroup>
<EnteMenuItem
onClick={disableMlSearch}
label={t("DISABLE_BETA")}
/>
</MenuItemGroup>
<MenuItemGroup>
<EnteMenuItem
onClick={handleDisableFaceSearch}
label={t("DISABLE_FACE_SEARCH")}
/>
</MenuItemGroup>
</Stack>
</Box>
</Stack>
);
}

View file

@ -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";

View file

@ -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";

View file

@ -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";

View file

@ -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: (
<Typography>
<Trans i18nKey={"DISABLE_FACE_SEARCH_DESCRIPTION"} />
</Typography>
),
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 (
<Box>
<EnteDrawer
anchor="left"
transitionDuration={0}
open={open}
onClose={handleDrawerClose}
BackdropProps={{
sx: { "&&&": { backgroundColor: "transparent" } },
}}
>
{mlSearchEnabled ? (
<ManageMLSearch
onClose={onClose}
disableMlSearch={disableMlSearch}
handleDisableFaceSearch={confirmDisableFaceSearch}
onRootClose={handleRootClose}
/>
) : (
<EnableMLSearch
onClose={onClose}
enableMlSearch={enableMlSearch}
onRootClose={handleRootClose}
/>
)}
</EnteDrawer>
<EnableFaceSearch
open={enableFaceSearchView}
onClose={closeEnableFaceSearch}
enableFaceSearch={enableFaceSearch}
onRootClose={handleRootClose}
/>
</Box>
);
};
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 (
<EnteDrawer
transitionDuration={0}
open={open}
onClose={handleDrawerClose}
BackdropProps={{
sx: { "&&&": { backgroundColor: "transparent" } },
}}
>
<Stack spacing={"4px"} py={"12px"}>
<Titlebar
onClose={onClose}
title={t("ENABLE_FACE_SEARCH_TITLE")}
onRootClose={handleRootClose}
/>
<Stack py={"20px"} px={"8px"} spacing={"32px"}>
<Typography color="text.muted" px={"8px"}>
<Trans
i18nKey={"ENABLE_FACE_SEARCH_DESCRIPTION"}
components={{
a: (
<Link
target="_blank"
href="https://ente.io/privacy#8-biometric-information-privacy-policy"
underline="always"
sx={{
color: "inherit",
textDecorationColor: "inherit",
}}
/>
),
}}
/>
</Typography>
<FormGroup sx={{ width: "100%" }}>
<FormControlLabel
sx={{
color: "text.muted",
ml: 0,
mt: 2,
}}
control={
<Checkbox
size="small"
checked={acceptTerms}
onChange={(e) =>
setAcceptTerms(e.target.checked)
}
/>
}
label={t("FACE_SEARCH_CONFIRMATION")}
/>
</FormGroup>
<Stack px={"8px"} spacing={"8px"}>
<Button
color={"accent"}
size="large"
disabled={!acceptTerms}
onClick={enableFaceSearch}
>
{t("ENABLE_FACE_SEARCH")}
</Button>
<Button
color={"secondary"}
size="large"
onClick={onClose}
>
{t("CANCEL")}
</Button>
</Stack>
</Stack>
</Stack>
</EnteDrawer>
);
}
function EnableMLSearch({ onClose, enableMlSearch, onRootClose }) {
const showDetails = () =>
openLink("https://ente.io/blog/desktop-ml-beta", true);
return (
<Stack spacing={"4px"} py={"12px"}>
<Titlebar
onClose={onClose}
title={t("ML_SEARCH")}
onRootClose={onRootClose}
/>
<Stack py={"20px"} px={"8px"} spacing={"32px"}>
<Box px={"8px"}>
{" "}
<Typography color="text.muted">
<Trans i18nKey={"ENABLE_ML_SEARCH_DESCRIPTION"} />
</Typography>
</Box>
<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>
</Stack>
</Stack>
);
}
function ManageMLSearch({
onClose,
disableMlSearch,
handleDisableFaceSearch,
onRootClose,
}) {
return (
<Stack spacing={"4px"} py={"12px"}>
<Titlebar
onClose={onClose}
title={t("ML_SEARCH")}
onRootClose={onRootClose}
/>
<Box px={"16px"}>
<Stack py={"20px"} spacing={"24px"}>
<MenuItemGroup>
<EnteMenuItem
onClick={disableMlSearch}
label={t("DISABLE_BETA")}
/>
</MenuItemGroup>
<MenuItemGroup>
<EnteMenuItem
onClick={handleDisableFaceSearch}
label={t("DISABLE_FACE_SEARCH")}
/>
</MenuItemGroup>
</Stack>
</Box>
</Stack>
);
}

View file

@ -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<FaceCropImageViewProps> = ({
url,
faceID,
}) => {
const [objectURL, setObjectURL] = useState<string | undefined>();
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 ? (
<img src={objectURL} />
) : (
<Skeleton variant="circular" height={120} width={120} />
);
};