From 165d48e87bbd7ff28e182b6554642235ff99dc7f Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:35:06 -0500 Subject: [PATCH 01/18] Reset selected index on new searches --- web/src/views/search/SearchView.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/src/views/search/SearchView.tsx b/web/src/views/search/SearchView.tsx index e64affa36d..58aa149a97 100644 --- a/web/src/views/search/SearchView.tsx +++ b/web/src/views/search/SearchView.tsx @@ -172,6 +172,10 @@ export default function SearchView({ setSelectedIndex(index); }, []); + useEffect(() => { + setSelectedIndex(0); + }, [searchTerm, searchFilter]); + // update search detail when results change useEffect(() => { From 70be0f2776fd2d8c6e552f9f90f28a80e09a0dd0 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Mon, 14 Oct 2024 16:41:43 -0500 Subject: [PATCH 02/18] Remove right click for similarity search --- web/src/components/card/SearchThumbnail.tsx | 5 ----- web/src/views/search/SearchView.tsx | 5 ----- 2 files changed, 10 deletions(-) diff --git a/web/src/components/card/SearchThumbnail.tsx b/web/src/components/card/SearchThumbnail.tsx index fe174e968e..4133c0e1e1 100644 --- a/web/src/components/card/SearchThumbnail.tsx +++ b/web/src/components/card/SearchThumbnail.tsx @@ -13,27 +13,22 @@ import ImageLoadingIndicator from "../indicators/ImageLoadingIndicator"; import ActivityIndicator from "../indicators/activity-indicator"; import { capitalizeFirstLetter } from "@/utils/stringUtil"; import { SearchResult } from "@/types/search"; -import useContextMenu from "@/hooks/use-contextmenu"; import { cn } from "@/lib/utils"; import { TooltipPortal } from "@radix-ui/react-tooltip"; type SearchThumbnailProps = { searchResult: SearchResult; - findSimilar: () => void; onClick: (searchResult: SearchResult) => void; }; export default function SearchThumbnail({ searchResult, - findSimilar, onClick, }: SearchThumbnailProps) { const apiHost = useApiHost(); const { data: config } = useSWR("config"); const [imgRef, imgLoaded, onImgLoad] = useImageLoaded(); - useContextMenu(imgRef, findSimilar); - const handleOnClick = useCallback(() => { onClick(searchResult); }, [searchResult, onClick]); diff --git a/web/src/views/search/SearchView.tsx b/web/src/views/search/SearchView.tsx index 58aa149a97..0e08a887d4 100644 --- a/web/src/views/search/SearchView.tsx +++ b/web/src/views/search/SearchView.tsx @@ -397,11 +397,6 @@ export default function SearchView({ > { - if (config?.semantic_search.enabled) { - setSimilaritySearch(value); - } - }} onClick={() => onSelectSearch(value, index)} /> {(searchTerm || From 53afc58afe71da0e9957475e23345d6463648d5c Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 14 Oct 2024 16:04:06 -0600 Subject: [PATCH 03/18] Fix sub label icon --- web/src/components/card/SearchThumbnail.tsx | 26 ++++++++++++++++++--- web/src/types/frigateConfig.ts | 1 + 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/web/src/components/card/SearchThumbnail.tsx b/web/src/components/card/SearchThumbnail.tsx index 4133c0e1e1..fa17ff0d58 100644 --- a/web/src/components/card/SearchThumbnail.tsx +++ b/web/src/components/card/SearchThumbnail.tsx @@ -1,4 +1,4 @@ -import { useCallback } from "react"; +import { useCallback, useMemo } from "react"; import { useApiHost } from "@/api"; import { getIconForLabel } from "@/utils/iconUtil"; import TimeAgo from "../dynamic/TimeAgo"; @@ -33,6 +33,26 @@ export default function SearchThumbnail({ onClick(searchResult); }, [searchResult, onClick]); + const objectLabel = useMemo(() => { + if ( + !config || + !searchResult.sub_label || + !config.model.attributes_map[searchResult.label] + ) { + return searchResult.label; + } + + if ( + config.model.attributes_map[searchResult.label].includes( + searchResult.sub_label, + ) + ) { + return searchResult.sub_label; + } + + return `${searchResult.label}-verified`; + }, [config, searchResult]); + // date const formattedDate = useFormattedTimestamp( @@ -78,14 +98,14 @@ export default function SearchThumbnail({ className={`z-0 flex items-start justify-between space-x-1 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500`} onClick={() => onClick(searchResult)} > - {getIconForLabel(searchResult.label, "size-3 text-white")} + {getIconForLabel(objectLabel, "size-3 text-white")} - {[...new Set([searchResult.label])] + {[objectLabel] .filter( (item) => item !== undefined && !item.includes("-verified"), ) diff --git a/web/src/types/frigateConfig.ts b/web/src/types/frigateConfig.ts index fe889ed9d9..2c54b289e9 100644 --- a/web/src/types/frigateConfig.ts +++ b/web/src/types/frigateConfig.ts @@ -340,6 +340,7 @@ export interface FrigateConfig { path: string | null; width: number; colormap: { [key: string]: [number, number, number] }; + attributes_map: { [key: string]: [string] }; }; motion: Record | null; From be474003ad58645a60c2665fb5083f892499ba21 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Mon, 14 Oct 2024 17:29:12 -0500 Subject: [PATCH 04/18] add card footer --- web/src/components/card/SearchThumbnail.tsx | 76 ++++++++++++++++++--- 1 file changed, 66 insertions(+), 10 deletions(-) diff --git a/web/src/components/card/SearchThumbnail.tsx b/web/src/components/card/SearchThumbnail.tsx index 4133c0e1e1..3128a3b3ed 100644 --- a/web/src/components/card/SearchThumbnail.tsx +++ b/web/src/components/card/SearchThumbnail.tsx @@ -15,6 +15,21 @@ import { capitalizeFirstLetter } from "@/utils/stringUtil"; import { SearchResult } from "@/types/search"; import { cn } from "@/lib/utils"; import { TooltipPortal } from "@radix-ui/react-tooltip"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { + LuActivity, + LuCamera, + LuDownload, + LuMoreVertical, + LuSearch, + LuTrash2, +} from "react-icons/lu"; +import FrigatePlusIcon from "@/components/icons/FrigatePlusIcon"; type SearchThumbnailProps = { searchResult: SearchResult; @@ -98,16 +113,57 @@ export default function SearchThumbnail({
-
-
- {searchResult.end_time ? ( - - ) : ( -
- -
- )} - {formattedDate} +
+
+
+ {searchResult.end_time ? ( + + ) : ( +
+ +
+ )} + {formattedDate} +
+
+ + + + + Submit to Frigate+ + + + + + + + Find similar + + + + + + + + + + Download video + + + + Download snapshot + + + + View object lifecycle + + + + Delete + + + +
From 97568293252654dad0cf7347c31d4ba26fa2125d Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Mon, 14 Oct 2024 16:45:37 -0600 Subject: [PATCH 05/18] Add Frigate+ dialog --- web/src/components/card/SearchThumbnail.tsx | 33 ++++++++++++++++----- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/web/src/components/card/SearchThumbnail.tsx b/web/src/components/card/SearchThumbnail.tsx index 55f903d3d5..f696bc447d 100644 --- a/web/src/components/card/SearchThumbnail.tsx +++ b/web/src/components/card/SearchThumbnail.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo } from "react"; +import { useCallback, useMemo, useState } from "react"; import { useApiHost } from "@/api"; import { getIconForLabel } from "@/utils/iconUtil"; import TimeAgo from "../dynamic/TimeAgo"; @@ -30,6 +30,8 @@ import { LuTrash2, } from "react-icons/lu"; import FrigatePlusIcon from "@/components/icons/FrigatePlusIcon"; +import { FrigatePlusDialog } from "../overlay/dialog/FrigatePlusDialog"; +import { Event } from "@/types/event"; type SearchThumbnailProps = { searchResult: SearchResult; @@ -44,6 +46,10 @@ export default function SearchThumbnail({ const { data: config } = useSWR("config"); const [imgRef, imgLoaded, onImgLoad] = useImageLoaded(); + // interactions + + const [showFrigatePlus, setShowFrigatePlus] = useState(false); + const handleOnClick = useCallback(() => { onClick(searchResult); }, [searchResult, onClick]); @@ -78,6 +84,14 @@ export default function SearchThumbnail({ return (
+ setShowFrigatePlus(false)} + onEventUploaded={() => {}} + /> +
- - - - - Submit to Frigate+ - + {config?.plus?.enabled && searchResult.end_time && ( + + + setShowFrigatePlus(true)} + /> + + Submit to Frigate+ + + )} From 778fd7b84c43c5cf46f036c41e380d304ea49911 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Mon, 14 Oct 2024 18:48:37 -0500 Subject: [PATCH 06/18] Move buttons and menu to thumbnail footer --- web/src/components/card/SearchThumbnail.tsx | 85 +------------ .../components/card/SearchThumbnailFooter.tsx | 116 ++++++++++++++++++ web/src/views/search/SearchView.tsx | 10 +- 3 files changed, 123 insertions(+), 88 deletions(-) create mode 100644 web/src/components/card/SearchThumbnailFooter.tsx diff --git a/web/src/components/card/SearchThumbnail.tsx b/web/src/components/card/SearchThumbnail.tsx index f696bc447d..8b65ad22f2 100644 --- a/web/src/components/card/SearchThumbnail.tsx +++ b/web/src/components/card/SearchThumbnail.tsx @@ -1,35 +1,17 @@ import { useCallback, useMemo, useState } from "react"; import { useApiHost } from "@/api"; import { getIconForLabel } from "@/utils/iconUtil"; -import TimeAgo from "../dynamic/TimeAgo"; import useSWR from "swr"; import { FrigateConfig } from "@/types/frigateConfig"; import { isIOS, isSafari } from "react-device-detect"; import Chip from "@/components/indicators/Chip"; -import { useFormattedTimestamp } from "@/hooks/use-date-utils"; import useImageLoaded from "@/hooks/use-image-loaded"; import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; import ImageLoadingIndicator from "../indicators/ImageLoadingIndicator"; -import ActivityIndicator from "../indicators/activity-indicator"; import { capitalizeFirstLetter } from "@/utils/stringUtil"; import { SearchResult } from "@/types/search"; import { cn } from "@/lib/utils"; import { TooltipPortal } from "@radix-ui/react-tooltip"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { - LuActivity, - LuCamera, - LuDownload, - LuMoreVertical, - LuSearch, - LuTrash2, -} from "react-icons/lu"; -import FrigatePlusIcon from "@/components/icons/FrigatePlusIcon"; import { FrigatePlusDialog } from "../overlay/dialog/FrigatePlusDialog"; import { Event } from "@/types/event"; @@ -74,14 +56,6 @@ export default function SearchThumbnail({ return `${searchResult.label}-verified`; }, [config, searchResult]); - // date - - const formattedDate = useFormattedTimestamp( - searchResult.start_time, - config?.ui.time_format == "24hour" ? "%b %-d, %H:%M" : "%b %-d, %I:%M %p", - config?.ui.timezone, - ); - return (
-
-
-
- {searchResult.end_time ? ( - - ) : ( -
- -
- )} - {formattedDate} -
-
- {config?.plus?.enabled && searchResult.end_time && ( - - - setShowFrigatePlus(true)} - /> - - Submit to Frigate+ - - )} - - - - - - Find similar - - - - - - - - - - Download video - - - - Download snapshot - - - - View object lifecycle - - - - Delete - - - -
-
-
+
); diff --git a/web/src/components/card/SearchThumbnailFooter.tsx b/web/src/components/card/SearchThumbnailFooter.tsx new file mode 100644 index 0000000000..c09d2424a4 --- /dev/null +++ b/web/src/components/card/SearchThumbnailFooter.tsx @@ -0,0 +1,116 @@ +import { useState } from "react"; +import TimeAgo from "../dynamic/TimeAgo"; +import useSWR from "swr"; +import { FrigateConfig } from "@/types/frigateConfig"; +import { useFormattedTimestamp } from "@/hooks/use-date-utils"; +import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; +import ActivityIndicator from "../indicators/activity-indicator"; +import { SearchResult } from "@/types/search"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { + LuActivity, + LuCamera, + LuDownload, + LuMoreVertical, + LuSearch, + LuTrash2, +} from "react-icons/lu"; +import FrigatePlusIcon from "@/components/icons/FrigatePlusIcon"; +import { FrigatePlusDialog } from "../overlay/dialog/FrigatePlusDialog"; +import { Event } from "@/types/event"; + +type SearchThumbnailProps = { + searchResult: SearchResult; +}; + +export default function SearchThumbnailFooter({ + searchResult, +}: SearchThumbnailProps) { + const { data: config } = useSWR("config"); + + // interactions + + const [showFrigatePlus, setShowFrigatePlus] = useState(false); + + // date + + const formattedDate = useFormattedTimestamp( + searchResult.start_time, + config?.ui.time_format == "24hour" ? "%b %-d, %H:%M" : "%b %-d, %I:%M %p", + config?.ui.timezone, + ); + + return ( + <> + setShowFrigatePlus(false)} + onEventUploaded={() => {}} + /> + +
+ {searchResult.end_time ? ( + + ) : ( +
+ +
+ )} + {formattedDate} +
+
+ {config?.plus?.enabled && + searchResult.has_snapshot && + searchResult.end_time && ( + + + setShowFrigatePlus(true)} + /> + + Submit to Frigate+ + + )} + + + + + + Find similar + + + + + + + + + + Download video + + + + Download snapshot + + + + View object lifecycle + + + + Delete + + + +
+ + ); +} diff --git a/web/src/views/search/SearchView.tsx b/web/src/views/search/SearchView.tsx index 0e08a887d4..fe4cdf71a4 100644 --- a/web/src/views/search/SearchView.tsx +++ b/web/src/views/search/SearchView.tsx @@ -33,6 +33,7 @@ import { PopoverTrigger, } from "@/components/ui/popover"; import { usePersistence } from "@/hooks/use-persistence"; +import SearchThumbnailFooter from "@/components/card/SearchThumbnailFooter"; type SearchViewProps = { search: string; @@ -76,8 +77,6 @@ export default function SearchView({ "sm:grid-cols-4": effectiveColumnCount === 4, "sm:grid-cols-5": effectiveColumnCount === 5, "sm:grid-cols-6": effectiveColumnCount === 6, - "sm:grid-cols-7": effectiveColumnCount === 7, - "sm:grid-cols-8": effectiveColumnCount >= 8, }); // suggestions values @@ -392,7 +391,7 @@ export default function SearchView({ >
+
+ +
); })} @@ -466,7 +468,7 @@ export default function SearchView({ setColumnCount(value)} - max={8} + max={6} min={2} step={1} className="flex-grow" From 5684e4124be1fef6172f6513de4d683ef7ba1a97 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Mon, 14 Oct 2024 19:07:27 -0500 Subject: [PATCH 07/18] Add similarity search --- web/src/components/card/SearchThumbnail.tsx | 14 +---------- .../components/card/SearchThumbnailFooter.tsx | 25 ++++++++++++------- web/src/views/search/SearchView.tsx | 9 ++++++- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/web/src/components/card/SearchThumbnail.tsx b/web/src/components/card/SearchThumbnail.tsx index 8b65ad22f2..213111b137 100644 --- a/web/src/components/card/SearchThumbnail.tsx +++ b/web/src/components/card/SearchThumbnail.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useState } from "react"; +import { useCallback, useMemo } from "react"; import { useApiHost } from "@/api"; import { getIconForLabel } from "@/utils/iconUtil"; import useSWR from "swr"; @@ -12,8 +12,6 @@ import { capitalizeFirstLetter } from "@/utils/stringUtil"; import { SearchResult } from "@/types/search"; import { cn } from "@/lib/utils"; import { TooltipPortal } from "@radix-ui/react-tooltip"; -import { FrigatePlusDialog } from "../overlay/dialog/FrigatePlusDialog"; -import { Event } from "@/types/event"; type SearchThumbnailProps = { searchResult: SearchResult; @@ -30,8 +28,6 @@ export default function SearchThumbnail({ // interactions - const [showFrigatePlus, setShowFrigatePlus] = useState(false); - const handleOnClick = useCallback(() => { onClick(searchResult); }, [searchResult, onClick]); @@ -58,14 +54,6 @@ export default function SearchThumbnail({ return (
- setShowFrigatePlus(false)} - onEventUploaded={() => {}} - /> - void; }; export default function SearchThumbnailFooter({ searchResult, + findSimilar, }: SearchThumbnailProps) { const { data: config } = useSWR("config"); @@ -70,7 +72,7 @@ export default function SearchThumbnailFooter({ searchResult.has_snapshot && searchResult.end_time && ( - + setShowFrigatePlus(true)} @@ -80,12 +82,17 @@ export default function SearchThumbnailFooter({ )} - - - - - Find similar - + {config?.semantic_search?.enabled && ( + + + + + Find similar + + )} @@ -101,7 +108,7 @@ export default function SearchThumbnailFooter({ Download snapshot - + View object lifecycle diff --git a/web/src/views/search/SearchView.tsx b/web/src/views/search/SearchView.tsx index fe4cdf71a4..1df543b2c6 100644 --- a/web/src/views/search/SearchView.tsx +++ b/web/src/views/search/SearchView.tsx @@ -428,7 +428,14 @@ export default function SearchView({ className={`review-item-ring pointer-events-none absolute inset-0 z-10 size-full rounded-lg outline outline-[3px] -outline-offset-[2.8px] ${selected ? `shadow-selected outline-selected` : "outline-transparent duration-500"}`} />
- + { + if (config?.semantic_search.enabled) { + setSimilaritySearch(value); + } + }} + />
); From 216e54a9623b5e3013d37cb3475bbcb6070bc303 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 15 Oct 2024 06:23:41 -0600 Subject: [PATCH 08/18] Show object score --- web/src/components/card/SearchThumbnail.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/src/components/card/SearchThumbnail.tsx b/web/src/components/card/SearchThumbnail.tsx index 213111b137..3217917c1a 100644 --- a/web/src/components/card/SearchThumbnail.tsx +++ b/web/src/components/card/SearchThumbnail.tsx @@ -86,10 +86,11 @@ export default function SearchThumbnail({
onClick(searchResult)} > {getIconForLabel(objectLabel, "size-3 text-white")} + {Math.round(searchResult.data.score * 100)}%
From 87c8aaf5f7c4cb7f69b73971471fac2b44344685 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 15 Oct 2024 06:31:08 -0600 Subject: [PATCH 09/18] Implement download buttons --- .../components/card/SearchThumbnailFooter.tsx | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/web/src/components/card/SearchThumbnailFooter.tsx b/web/src/components/card/SearchThumbnailFooter.tsx index e88aecf0ac..b01b8b7057 100644 --- a/web/src/components/card/SearchThumbnailFooter.tsx +++ b/web/src/components/card/SearchThumbnailFooter.tsx @@ -23,6 +23,7 @@ import FrigatePlusIcon from "@/components/icons/FrigatePlusIcon"; import { FrigatePlusDialog } from "../overlay/dialog/FrigatePlusDialog"; import { Event } from "@/types/event"; import { FaArrowsRotate } from "react-icons/fa6"; +import { baseUrl } from "@/api/baseUrl"; type SearchThumbnailProps = { searchResult: SearchResult; @@ -100,12 +101,24 @@ export default function SearchThumbnailFooter({ - - Download video + + + Download video + - - Download snapshot + + + Download snapshot + From 6e6fa0d333ff4db51d1a61dc2761146033389e32 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Tue, 15 Oct 2024 07:33:43 -0500 Subject: [PATCH 10/18] remove confidence score --- web/src/views/search/SearchView.tsx | 44 +---------------------------- 1 file changed, 1 insertion(+), 43 deletions(-) diff --git a/web/src/views/search/SearchView.tsx b/web/src/views/search/SearchView.tsx index 1df543b2c6..c3cf41b15e 100644 --- a/web/src/views/search/SearchView.tsx +++ b/web/src/views/search/SearchView.tsx @@ -1,7 +1,6 @@ import SearchThumbnail from "@/components/card/SearchThumbnail"; import SearchFilterGroup from "@/components/filter/SearchFilterGroup"; import ActivityIndicator from "@/components/indicators/activity-indicator"; -import Chip from "@/components/indicators/Chip"; import SearchDetailDialog from "@/components/overlay/detail/SearchDetailDialog"; import { Toaster } from "@/components/ui/sonner"; import { @@ -14,7 +13,7 @@ import { FrigateConfig } from "@/types/frigateConfig"; import { SearchFilter, SearchResult, SearchSource } from "@/types/search"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { isDesktop, isMobileOnly } from "react-device-detect"; -import { LuColumns, LuImage, LuSearchX, LuText } from "react-icons/lu"; +import { LuColumns, LuSearchX } from "react-icons/lu"; import useSWR from "swr"; import ExploreView from "../explore/ExploreView"; import useKeyboardListener, { @@ -25,7 +24,6 @@ import InputWithTags from "@/components/input/InputWithTags"; import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; import { isEqual } from "lodash"; import { formatDateToLocaleString } from "@/utils/dateUtil"; -import { TooltipPortal } from "@radix-ui/react-tooltip"; import { Slider } from "@/components/ui/slider"; import { Popover, @@ -190,21 +188,6 @@ export default function SearchView({ } }, [searchResults, searchDetail]); - // confidence score - - const zScoreToConfidence = (score: number) => { - // Normalizing is not needed for similarity searches - // Sigmoid function for normalized: 1 / (1 + e^x) - // Cosine for similarity - if (searchFilter) { - const notNormalized = searchFilter?.search_type?.includes("similarity"); - - const confidence = notNormalized ? 1 - score : 1 / (1 + Math.exp(score)); - - return Math.round(confidence * 100); - } - }; - const hasExistingSearch = useMemo( () => searchResults != undefined || searchFilter != undefined, [searchResults, searchFilter], @@ -398,31 +381,6 @@ export default function SearchView({ searchResult={value} onClick={() => onSelectSearch(value, index)} /> - {(searchTerm || - searchFilter?.search_type?.includes("similarity")) && ( -
- - - - {value.search_source == "thumbnail" ? ( - - ) : ( - - )} - {zScoreToConfidence(value.search_distance)}% - - - - - Matched {value.search_source} at{" "} - {zScoreToConfidence(value.search_distance)}% - - - -
- )}
Date: Tue, 15 Oct 2024 07:37:26 -0500 Subject: [PATCH 11/18] conditionally show submenu items --- .../components/card/SearchThumbnailFooter.tsx | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/web/src/components/card/SearchThumbnailFooter.tsx b/web/src/components/card/SearchThumbnailFooter.tsx index b01b8b7057..934e65874c 100644 --- a/web/src/components/card/SearchThumbnailFooter.tsx +++ b/web/src/components/card/SearchThumbnailFooter.tsx @@ -100,26 +100,30 @@ export default function SearchThumbnailFooter({ - - - - Download video - - - - - - Download snapshot - - + {searchResult.has_clip && ( + + + + Download video + + + )} + {searchResult.has_snapshot && ( + + + + Download snapshot + + + )} View object lifecycle From 2442fb8414aaae651fed2f5ac4d7b793fc2d06ff Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 15 Oct 2024 06:39:08 -0600 Subject: [PATCH 12/18] Implement delete --- .../components/card/SearchThumbnailFooter.tsx | 26 +++++++++++++++++-- web/src/pages/Explore.tsx | 3 ++- web/src/views/search/SearchView.tsx | 7 +++-- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/web/src/components/card/SearchThumbnailFooter.tsx b/web/src/components/card/SearchThumbnailFooter.tsx index b01b8b7057..c9349cf61d 100644 --- a/web/src/components/card/SearchThumbnailFooter.tsx +++ b/web/src/components/card/SearchThumbnailFooter.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useCallback, useState } from "react"; import TimeAgo from "../dynamic/TimeAgo"; import useSWR from "swr"; import { FrigateConfig } from "@/types/frigateConfig"; @@ -24,15 +24,19 @@ import { FrigatePlusDialog } from "../overlay/dialog/FrigatePlusDialog"; import { Event } from "@/types/event"; import { FaArrowsRotate } from "react-icons/fa6"; import { baseUrl } from "@/api/baseUrl"; +import axios from "axios"; +import { toast } from "sonner"; type SearchThumbnailProps = { searchResult: SearchResult; findSimilar: () => void; + refreshResults: () => void; }; export default function SearchThumbnailFooter({ searchResult, findSimilar, + refreshResults, }: SearchThumbnailProps) { const { data: config } = useSWR("config"); @@ -40,6 +44,24 @@ export default function SearchThumbnailFooter({ const [showFrigatePlus, setShowFrigatePlus] = useState(false); + const handleDelete = useCallback(() => { + axios + .delete(`events/${searchResult.id}`) + .then((resp) => { + if (resp.status == 200) { + toast.success("Deleted object successfully.", { + position: "top-center", + }); + refreshResults(); + } + }) + .catch(() => { + toast.error("Failed to delete object.", { + position: "top-center", + }); + }); + }, [searchResult, refreshResults]); + // date const formattedDate = useFormattedTimestamp( @@ -124,7 +146,7 @@ export default function SearchThumbnailFooter({ View object lifecycle - + Delete diff --git a/web/src/pages/Explore.tsx b/web/src/pages/Explore.tsx index 816618fe53..ffbef10607 100644 --- a/web/src/pages/Explore.tsx +++ b/web/src/pages/Explore.tsx @@ -384,6 +384,7 @@ export default function Explore() { searchFilter={searchFilter} searchResults={searchResults} isLoading={(isLoadingInitialData || isLoadingMore) ?? true} + hasMore={!isReachingEnd} setSearch={setSearch} setSimilaritySearch={(search) => { setSearchFilter({ @@ -395,7 +396,7 @@ export default function Explore() { setSearchFilter={setSearchFilter} onUpdateFilter={setSearchFilter} loadMore={loadMore} - hasMore={!isReachingEnd} + refresh={mutate} /> )} diff --git a/web/src/views/search/SearchView.tsx b/web/src/views/search/SearchView.tsx index 1df543b2c6..f747c80f95 100644 --- a/web/src/views/search/SearchView.tsx +++ b/web/src/views/search/SearchView.tsx @@ -41,12 +41,13 @@ type SearchViewProps = { searchFilter?: SearchFilter; searchResults?: SearchResult[]; isLoading: boolean; + hasMore: boolean; setSearch: (search: string) => void; setSimilaritySearch: (search: SearchResult) => void; setSearchFilter: (filter: SearchFilter) => void; onUpdateFilter: (filter: SearchFilter) => void; loadMore: () => void; - hasMore: boolean; + refresh: () => void; }; export default function SearchView({ search, @@ -54,12 +55,13 @@ export default function SearchView({ searchFilter, searchResults, isLoading, + hasMore, setSearch, setSimilaritySearch, setSearchFilter, onUpdateFilter, loadMore, - hasMore, + refresh, }: SearchViewProps) { const contentRef = useRef(null); const { data: config } = useSWR("config", { @@ -435,6 +437,7 @@ export default function SearchView({ setSimilaritySearch(value); } }} + refreshResults={refresh} />
From 9d498ae0ea6c4550ede5a3c1ca053aa3041e26c5 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Tue, 15 Oct 2024 07:45:51 -0500 Subject: [PATCH 13/18] fix icon color --- web/src/components/card/SearchThumbnailFooter.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/src/components/card/SearchThumbnailFooter.tsx b/web/src/components/card/SearchThumbnailFooter.tsx index f4bc62159e..38c8c16e2f 100644 --- a/web/src/components/card/SearchThumbnailFooter.tsx +++ b/web/src/components/card/SearchThumbnailFooter.tsx @@ -97,7 +97,7 @@ export default function SearchThumbnailFooter({ setShowFrigatePlus(true)} /> @@ -109,7 +109,7 @@ export default function SearchThumbnailFooter({ @@ -119,7 +119,7 @@ export default function SearchThumbnailFooter({ - + {searchResult.has_clip && ( From 143d8f19bf688be28b764014e6635aec3752ddc6 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Tue, 15 Oct 2024 06:48:10 -0600 Subject: [PATCH 14/18] Add object lifecycle button --- .../components/card/SearchThumbnailFooter.tsx | 4 +++- .../overlay/detail/SearchDetailDialog.tsx | 17 +++++++++----- web/src/views/search/SearchView.tsx | 22 ++++++++++++++----- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/web/src/components/card/SearchThumbnailFooter.tsx b/web/src/components/card/SearchThumbnailFooter.tsx index f4bc62159e..3ee7c252f4 100644 --- a/web/src/components/card/SearchThumbnailFooter.tsx +++ b/web/src/components/card/SearchThumbnailFooter.tsx @@ -31,12 +31,14 @@ type SearchThumbnailProps = { searchResult: SearchResult; findSimilar: () => void; refreshResults: () => void; + showObjectLifecycle: () => void; }; export default function SearchThumbnailFooter({ searchResult, findSimilar, refreshResults, + showObjectLifecycle, }: SearchThumbnailProps) { const { data: config } = useSWR("config"); @@ -146,7 +148,7 @@ export default function SearchThumbnailFooter({ )} - + View object lifecycle diff --git a/web/src/components/overlay/detail/SearchDetailDialog.tsx b/web/src/components/overlay/detail/SearchDetailDialog.tsx index 1cee70aaac..23734ea90d 100644 --- a/web/src/components/overlay/detail/SearchDetailDialog.tsx +++ b/web/src/components/overlay/detail/SearchDetailDialog.tsx @@ -69,16 +69,20 @@ const SEARCH_TABS = [ "video", "object lifecycle", ] as const; -type SearchTab = (typeof SEARCH_TABS)[number]; +export type SearchTab = (typeof SEARCH_TABS)[number]; type SearchDetailDialogProps = { search?: SearchResult; + page: SearchTab; setSearch: (search: SearchResult | undefined) => void; + setSearchPage: (page: SearchTab) => void; setSimilarity?: () => void; }; export default function SearchDetailDialog({ search, + page, setSearch, + setSearchPage, setSimilarity, }: SearchDetailDialogProps) { const { data: config } = useSWR("config", { @@ -87,8 +91,11 @@ export default function SearchDetailDialog({ // tabs - const [page, setPage] = useState("details"); - const [pageToggle, setPageToggle] = useOptimisticState(page, setPage, 100); + const [pageToggle, setPageToggle] = useOptimisticState( + page, + setSearchPage, + 100, + ); // dialog and mobile page @@ -130,9 +137,9 @@ export default function SearchDetailDialog({ } if (!searchTabs.includes(pageToggle)) { - setPage("details"); + setSearchPage("details"); } - }, [pageToggle, searchTabs]); + }, [pageToggle, searchTabs, setSearchPage]); if (!search) { return; diff --git a/web/src/views/search/SearchView.tsx b/web/src/views/search/SearchView.tsx index 29acd67197..bc4f5b54d5 100644 --- a/web/src/views/search/SearchView.tsx +++ b/web/src/views/search/SearchView.tsx @@ -1,7 +1,9 @@ import SearchThumbnail from "@/components/card/SearchThumbnail"; import SearchFilterGroup from "@/components/filter/SearchFilterGroup"; import ActivityIndicator from "@/components/indicators/activity-indicator"; -import SearchDetailDialog from "@/components/overlay/detail/SearchDetailDialog"; +import SearchDetailDialog, { + SearchTab, +} from "@/components/overlay/detail/SearchDetailDialog"; import { Toaster } from "@/components/ui/sonner"; import { Tooltip, @@ -160,16 +162,21 @@ export default function SearchView({ // detail const [searchDetail, setSearchDetail] = useState(); + const [page, setPage] = useState("details"); // search interaction const [selectedIndex, setSelectedIndex] = useState(null); const itemRefs = useRef<(HTMLDivElement | null)[]>([]); - const onSelectSearch = useCallback((item: SearchResult, index: number) => { - setSearchDetail(item); - setSelectedIndex(index); - }, []); + const onSelectSearch = useCallback( + (item: SearchResult, index: number, page: SearchTab = "details") => { + setPage(page); + setSearchDetail(item); + setSelectedIndex(index); + }, + [], + ); useEffect(() => { setSelectedIndex(0); @@ -298,7 +305,9 @@ export default function SearchView({ setSimilaritySearch(searchDetail)) } @@ -396,6 +405,9 @@ export default function SearchView({ } }} refreshResults={refresh} + showObjectLifecycle={() => + onSelectSearch(value, index, "object lifecycle") + } /> From ae679ac15cf7ef70b542427b0b0352e6f035b1af Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Tue, 15 Oct 2024 07:49:48 -0500 Subject: [PATCH 15/18] fix score --- web/src/components/card/SearchThumbnail.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web/src/components/card/SearchThumbnail.tsx b/web/src/components/card/SearchThumbnail.tsx index 3217917c1a..4ad7d7c5c4 100644 --- a/web/src/components/card/SearchThumbnail.tsx +++ b/web/src/components/card/SearchThumbnail.tsx @@ -90,7 +90,10 @@ export default function SearchThumbnail({ onClick={() => onClick(searchResult)} > {getIconForLabel(objectLabel, "size-3 text-white")} - {Math.round(searchResult.data.score * 100)}% + {Math.floor( + searchResult.score ?? searchResult.data.top_score * 100, + )} + % From 90c60000d075f8f2f1fc9777fee50b8d90a532b9 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Tue, 15 Oct 2024 07:58:08 -0500 Subject: [PATCH 16/18] delete confirmation --- .../components/card/SearchThumbnailFooter.tsx | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/web/src/components/card/SearchThumbnailFooter.tsx b/web/src/components/card/SearchThumbnailFooter.tsx index cb4915e7f7..2aa9936658 100644 --- a/web/src/components/card/SearchThumbnailFooter.tsx +++ b/web/src/components/card/SearchThumbnailFooter.tsx @@ -12,6 +12,16 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "../ui/alert-dialog"; import { LuCamera, LuDownload, @@ -45,20 +55,21 @@ export default function SearchThumbnailFooter({ // interactions const [showFrigatePlus, setShowFrigatePlus] = useState(false); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const handleDelete = useCallback(() => { axios .delete(`events/${searchResult.id}`) .then((resp) => { if (resp.status == 200) { - toast.success("Deleted object successfully.", { + toast.success("Tracked object deleted successfully.", { position: "top-center", }); refreshResults(); } }) .catch(() => { - toast.error("Failed to delete object.", { + toast.error("Failed to delete tracked object.", { position: "top-center", }); }); @@ -74,6 +85,28 @@ export default function SearchThumbnailFooter({ return ( <> + setDeleteDialogOpen(!deleteDialogOpen)} + > + + + Confirm Delete + + + Are you sure you want to delete this tracked object? + + + Cancel + + Delete + + + + - + {searchResult.has_clip && ( View object lifecycle - + setDeleteDialogOpen(true)}> Delete From 934a0e47feedff5f4b43209f3e44fec3875ee3c8 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Tue, 15 Oct 2024 08:08:57 -0500 Subject: [PATCH 17/18] small tweaks --- .../components/card/SearchThumbnailFooter.tsx | 19 ++++++++++--------- web/src/components/input/InputWithTags.tsx | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/web/src/components/card/SearchThumbnailFooter.tsx b/web/src/components/card/SearchThumbnailFooter.tsx index 2aa9936658..3e5dbe2365 100644 --- a/web/src/components/card/SearchThumbnailFooter.tsx +++ b/web/src/components/card/SearchThumbnailFooter.tsx @@ -10,6 +10,8 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { @@ -22,13 +24,7 @@ import { AlertDialogHeader, AlertDialogTitle, } from "../ui/alert-dialog"; -import { - LuCamera, - LuDownload, - LuMoreVertical, - LuSearch, - LuTrash2, -} from "react-icons/lu"; +import { LuCamera, LuDownload, LuMoreVertical, LuTrash2 } from "react-icons/lu"; import FrigatePlusIcon from "@/components/icons/FrigatePlusIcon"; import { FrigatePlusDialog } from "../overlay/dialog/FrigatePlusDialog"; import { Event } from "@/types/event"; @@ -36,6 +32,7 @@ import { FaArrowsRotate } from "react-icons/fa6"; import { baseUrl } from "@/api/baseUrl"; import axios from "axios"; import { toast } from "sonner"; +import { MdImageSearch } from "react-icons/md"; type SearchThumbnailProps = { searchResult: SearchResult; @@ -125,7 +122,7 @@ export default function SearchThumbnailFooter({ )} {formattedDate} -
+
{config?.plus?.enabled && searchResult.has_snapshot && searchResult.end_time && ( @@ -143,7 +140,7 @@ export default function SearchThumbnailFooter({ {config?.semantic_search?.enabled && ( - @@ -157,6 +154,10 @@ export default function SearchThumbnailFooter({ + + Tracked Object Actions + + {searchResult.has_clip && (
From 37c7985f62e033fd2d40b094a1ffbeb4faab8fe3 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Tue, 15 Oct 2024 08:19:07 -0500 Subject: [PATCH 18/18] consistent icons --- web/src/components/input/InputWithTags.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/components/input/InputWithTags.tsx b/web/src/components/input/InputWithTags.tsx index 2e36eb2eed..9ca1e40938 100644 --- a/web/src/components/input/InputWithTags.tsx +++ b/web/src/components/input/InputWithTags.tsx @@ -2,7 +2,6 @@ import React, { useState, useRef, useEffect, useCallback } from "react"; import { LuX, LuFilter, - LuImage, LuChevronDown, LuChevronUp, LuTrash2, @@ -44,6 +43,7 @@ import { import { toast } from "sonner"; import useSWR from "swr"; import { FrigateConfig } from "@/types/frigateConfig"; +import { MdImageSearch } from "react-icons/md"; type InputWithTagsProps = { inputFocused: boolean; @@ -549,7 +549,7 @@ export default function InputWithTags({ {isSimilaritySearch && ( -