Debugging page

This commit is contained in:
Manav Rathi
2024-08-28 09:19:31 +05:30
parent 703ba3ef6e
commit 0657b16002
4 changed files with 299 additions and 7 deletions

View File

@@ -0,0 +1,254 @@
import { SelectionBar } from "@/base/components/Navbar";
import { pt } from "@/base/i18n";
import log from "@/base/log";
import { wipClusterPageContents } from "@/new/photos/services/ml";
import { EnteFile } from "@/new/photos/types/file";
import {
FluidContainer,
VerticallyCentered,
} from "@ente/shared/components/Container";
import EnteSpinner from "@ente/shared/components/EnteSpinner";
import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages";
import { CustomError } from "@ente/shared/error";
import useMemoSingleThreaded from "@ente/shared/hooks/useMemoSingleThreaded";
import BackButton from "@mui/icons-material/ArrowBackOutlined";
import { Box, IconButton, styled } from "@mui/material";
import Typography from "@mui/material/Typography";
import { DedupePhotoList } from "components/PhotoList/dedupe";
import PreviewCard from "components/pages/gallery/PreviewCard";
import { ALL_SECTION } from "constants/collection";
import { t } from "i18next";
import { useRouter } from "next/router";
import { AppContext } from "pages/_app";
import { createContext, useContext, useEffect, useState } from "react";
import AutoSizer from "react-virtualized-auto-sizer";
import { getLocalCollections } from "services/collectionService";
import { Duplicate } from "services/deduplicationService";
import {
DeduplicateContextType,
DefaultDeduplicateContext,
} from "types/deduplicate";
import { updateFileMsrcProps } from "utils/photoFrame";
const DeduplicateContext = createContext<DeduplicateContextType>(
DefaultDeduplicateContext,
);
const Info = styled("div")`
padding: 24px;
font-size: 18px;
`;
// TODO-Cluster Temporary component for debugging
export default function Deduplicate() {
const { startLoading, finishLoading, showNavBar } = useContext(AppContext);
const [duplicates, setDuplicates] = useState<Duplicate[]>(null);
const [collectionNameMap, setCollectionNameMap] = useState(
new Map<number, string>(),
);
useEffect(() => {
showNavBar(true);
}, []);
useEffect(() => {
syncWithRemote();
}, []);
const syncWithRemote = async () => {
startLoading();
const collections = await getLocalCollections();
const collectionNameMap = new Map<number, string>();
for (const collection of collections) {
collectionNameMap.set(collection.id, collection.name);
}
setCollectionNameMap(collectionNameMap);
const faceAndFiles = await wipClusterPageContents();
// const files = await getLocalFiles();
// const duplicateFiles = await getDuplicates(files, collectionNameMap);
const duplicateFiles = faceAndFiles.map(({ face, file }) => ({
files: [file],
size: face.score,
}));
const currFileSizeMap = new Map<number, number>();
let toSelectFileIDs: number[] = [];
let count = 0;
for (const dupe of duplicateFiles) {
// select all except first file
toSelectFileIDs = [
...toSelectFileIDs,
...dupe.files.slice(1).map((f) => f.id),
];
count += dupe.files.length - 1;
for (const file of dupe.files) {
currFileSizeMap.set(file.id, dupe.size);
}
}
setDuplicates(duplicateFiles);
const selectedFiles = {
count: count,
ownCount: count,
collectionID: ALL_SECTION,
};
for (const fileID of toSelectFileIDs) {
selectedFiles[fileID] = true;
}
finishLoading();
};
const duplicateFiles = useMemoSingleThreaded(() => {
return (duplicates ?? []).reduce((acc, dupe) => {
return [...acc, ...dupe.files];
}, []);
}, [duplicates]);
if (!duplicates) {
return (
<VerticallyCentered>
<EnteSpinner />
</VerticallyCentered>
);
}
return (
<DeduplicateContext.Provider
value={{
...DefaultDeduplicateContext,
collectionNameMap,
isOnDeduplicatePage: true,
}}
>
{duplicateFiles.length > 0 && (
<Info>{t("DEDUPLICATE_BASED_ON_SIZE")}</Info>
)}
{duplicateFiles.length === 0 ? (
<VerticallyCentered>
<Typography variant="large" color="text.muted">
{t("NO_DUPLICATES_FOUND")}
</Typography>
</VerticallyCentered>
) : (
<ClusterDebugPhotoFrame
files={duplicateFiles}
duplicates={duplicates}
activeCollectionID={ALL_SECTION}
/>
)}
<Options />
</DeduplicateContext.Provider>
);
}
const Options: React.FC = () => {
const router = useRouter();
const close = () => {
router.push(PAGES.GALLERY);
};
return (
<SelectionBar>
<FluidContainer>
<IconButton onClick={close}>
<BackButton />
</IconButton>
<Box sx={{ marginInline: "auto" }}>{pt("Faces")}</Box>
</FluidContainer>
</SelectionBar>
);
};
interface ClusterDebugPhotoFrameProps {
files: EnteFile[];
duplicates?: Duplicate[];
activeCollectionID: number;
}
const ClusterDebugPhotoFrame: React.FC<ClusterDebugPhotoFrameProps> = ({
duplicates,
files,
activeCollectionID,
}) => {
const displayFiles = useMemoSingleThreaded(() => {
return files.map((item) => {
const filteredItem = {
...item,
w: window.innerWidth,
h: window.innerHeight,
title: item.pubMagicMetadata?.data.caption,
};
return filteredItem;
});
}, [files]);
const updateURL =
(index: number) => (id: number, url: string, forceUpdate?: boolean) => {
const file = displayFiles[index];
// this is to prevent outdated updateURL call from updating the wrong file
if (file.id !== id) {
log.info(
`[${id}]PhotoSwipe: updateURL: file id mismatch: ${file.id} !== ${id}`,
);
throw Error(CustomError.UPDATE_URL_FILE_ID_MISMATCH);
}
if (file.msrc && !forceUpdate) {
throw Error(CustomError.URL_ALREADY_SET);
}
updateFileMsrcProps(file, url);
};
const getThumbnail = (
item: EnteFile,
index: number,
isScrolling: boolean,
) => (
<PreviewCard
key={`tile-${item.id}`}
file={item}
updateURL={updateURL(index)}
onClick={() => {}}
selectable={false}
onSelect={() => {}}
selected={false}
selectOnClick={false}
onHover={() => {}}
onRangeSelect={() => {}}
isRangeSelectActive={false}
isInsSelectRange={false}
activeCollectionID={activeCollectionID}
showPlaceholder={isScrolling}
/>
);
return (
<Container>
<AutoSizer>
{({ height, width }) => (
<DedupePhotoList /*PhotoList*/
width={width}
height={height}
getThumbnail={getThumbnail}
duplicates={duplicates}
activeCollectionID={activeCollectionID}
showAppDownloadBanner={false}
/>
)}
</AutoSizer>
</Container>
);
};
const Container = styled("div")`
display: block;
flex: 1;
width: 100%;
flex-wrap: wrap;
margin: 0 auto;
overflow: hidden;
.pswp-thumbnail {
display: inline-block;
cursor: pointer;
}
`;

View File

@@ -1,5 +1,5 @@
import { EnteDrawer } from "@/base/components/EnteDrawer";
import { MenuItemGroup, MenuSectionTitle } from "@/base/components/Menu";
import { MenuItemGroup } from "@/base/components/Menu";
import { Titlebar } from "@/base/components/Titlebar";
import { pt, ut } from "@/base/i18n";
import log from "@/base/log";
@@ -8,7 +8,6 @@ import {
enableML,
mlStatusSnapshot,
mlStatusSubscribe,
wipCluster,
wipClusterEnable,
type MLStatus,
} from "@/new/photos/services/ml";
@@ -28,6 +27,7 @@ import {
type DialogProps,
} from "@mui/material";
import { t } from "i18next";
import { useRouter } from "next/router";
import React, { useEffect, useState, useSyncExternalStore } from "react";
import { Trans } from "react-i18next";
import type { NewAppContextPhotos } from "../types/context";
@@ -338,7 +338,10 @@ const ManageML: React.FC<ManageMLProps> = ({
});
};
const wipClusterNow = () => void wipCluster();
// TODO-Cluster
// const wipClusterNow = () => void wipCluster();
const router = useRouter();
const wipClusterNow = () => router.push("/cluster-debug");
return (
<Stack px={"16px"} py={"20px"} gap={4}>
@@ -387,15 +390,17 @@ const ManageML: React.FC<ManageMLProps> = ({
<Box>
<MenuItemGroup>
<EnteMenuItem
label={ut("Cluster internal only option")}
label={ut(
"View clusters internal only option",
)}
onClick={wipClusterNow}
/>
</MenuItemGroup>
<MenuSectionTitle
{/* <MenuSectionTitle
title={ut(
"Create clusters locally, afresh and in-memory. Existing local clusters will be overwritten. Nothing will be saved or synced to remote.",
)}
/>
/> */}
</Box>
)}
</Stack>

View File

@@ -287,7 +287,7 @@ export const clusterFaces = async (faceIndexes: FaceIndex[]) => {
`Clustered ${faces.length} faces into ${validClusters.length} clusters (${Date.now() - t} ms)`,
);
return { clusters: validClusters, cgroups };
return { faces, clusters: validClusters, cgroups };
};
/**

View File

@@ -28,6 +28,7 @@ import {
faceIndexes,
indexableAndIndexedCounts,
} from "./db";
import type { Face } from "./face";
import { MLWorker } from "./worker";
import type { CLIPMatches } from "./worker-types";
@@ -342,6 +343,38 @@ export const wipSearchPersons = async () => {
return _wip_searchPersons ?? [];
};
export const wipClusterPageContents = async () => {
if (!(await wipClusterEnable())) return [];
log.info("clustering");
_wip_isClustering = true;
_wip_searchPersons = undefined;
triggerStatusUpdate();
const { faces } = await clusterFaces(await faceIndexes());
// const searchPersons = await convertToSearchPersons(clusters, cgroups);
const localFiles = await getAllLocalFiles();
const localFileByID = new Map(localFiles.map((f) => [f.id, f]));
const result1: { file: EnteFile; face: Face }[] = [];
for (const face of faces) {
const file = ensure(
localFileByID.get(ensure(fileIDFromFaceID(face.faceID))),
);
result1.push({ file, face });
}
const result = result1.sort((a, b) => b.face.score - a.face.score);
_wip_isClustering = false;
// _wip_searchPersons = searchPersons;
triggerStatusUpdate();
// return { faces, clusters, cgroups };
return result;
};
export const wipCluster = async () => {
if (!(await wipClusterEnable())) return;