Consolidate
This commit is contained in:
parent
b36175a779
commit
401f879424
10 changed files with 394 additions and 425 deletions
|
@ -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} />
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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;
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
327
web/apps/photos/src/components/ml/MLSearchSettings.tsx
Normal file
327
web/apps/photos/src/components/ml/MLSearchSettings.tsx
Normal 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>
|
||||
);
|
||||
}
|
|
@ -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} />
|
||||
);
|
||||
};
|
Loading…
Add table
Reference in a new issue