From 00ff6d9936eb20eb7103cf50a0e4320211dfabb4 Mon Sep 17 00:00:00 2001 From: notsafeforgit Date: Mon, 23 Mar 2026 01:16:27 -0700 Subject: [PATCH] perf(ui): optimize duplicate checker UI to prevent browser freezing This fixes an issue where Chrome would become unresponsive and prompt the user to kill the page when a large number of duplicates (e.g. 30,000+ groups) were found. 1. Changed the fetchPolicy on FindDuplicateImages to 'no-cache'. Loading 30k+ complex objects into the Apollo normalized cache blocked the main thread for an extended period. Bypassing the cache for this massive one-off query resolves the blocking. 2. Optimized the sorting algorithm in both Image and Scene duplicate checkers. Previously, the group size was recalculated by iterating over all nested files inside the sort's comparison function, resulting in millions of unnecessary iterations (O(N log N) with a heavy inner loop). Now, group sizes are precalculated into a map (O(N)) before sorting. --- .../ImageDuplicateChecker/ImageDuplicateChecker.tsx | 10 ++++++++-- .../SceneDuplicateChecker/SceneDuplicateChecker.tsx | 8 +++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/ui/v2.5/src/components/ImageDuplicateChecker/ImageDuplicateChecker.tsx b/ui/v2.5/src/components/ImageDuplicateChecker/ImageDuplicateChecker.tsx index 0a3b90ed6..73444852c 100644 --- a/ui/v2.5/src/components/ImageDuplicateChecker/ImageDuplicateChecker.tsx +++ b/ui/v2.5/src/components/ImageDuplicateChecker/ImageDuplicateChecker.tsx @@ -86,7 +86,7 @@ const ImageDuplicateChecker: React.FC = () => { const { data, loading, refetch } = useFindDuplicateImagesQuery({ variables: { distance: hashDistance }, - fetchPolicy: "network-only", + fetchPolicy: "no-cache", }); const getGroupTotalSize = (group: GQL.SlimImageDataFragment[]) => { @@ -101,8 +101,14 @@ const ImageDuplicateChecker: React.FC = () => { const allGroups = useMemo(() => { const groups = data?.findDuplicateImages ?? []; + + const groupSizes = new Map(); + groups.forEach((group) => { + groupSizes.set(group, getGroupTotalSize(group)); + }); + return [...groups].sort((a, b) => { - return getGroupTotalSize(b) - getGroupTotalSize(a); + return (groupSizes.get(b) ?? 0) - (groupSizes.get(a) ?? 0); }); }, [data?.findDuplicateImages]); diff --git a/ui/v2.5/src/components/SceneDuplicateChecker/SceneDuplicateChecker.tsx b/ui/v2.5/src/components/SceneDuplicateChecker/SceneDuplicateChecker.tsx index d396a01f4..5a8cf3499 100644 --- a/ui/v2.5/src/components/SceneDuplicateChecker/SceneDuplicateChecker.tsx +++ b/ui/v2.5/src/components/SceneDuplicateChecker/SceneDuplicateChecker.tsx @@ -92,9 +92,15 @@ export const SceneDuplicateChecker: React.FC = () => { const scenes = useMemo(() => { const groups = data?.findDuplicateScenes ?? []; + + const groupSizes = new Map(); + groups.forEach((group) => { + groupSizes.set(group, getGroupTotalSize(group)); + }); + // Sort by total file size descending (largest groups first) return [...groups].sort((a, b) => { - return getGroupTotalSize(b) - getGroupTotalSize(a); + return (groupSizes.get(b) ?? 0) - (groupSizes.get(a) ?? 0); }); }, [data?.findDuplicateScenes]);