mirror of
				https://github.com/blakeblackshear/frigate.git
				synced 2025-10-27 10:52:11 +01:00 
			
		
		
		
	Find similar footage from review item snapshots (#13662)
* Find similar footage from review item snapshots * Include confidence score for similarity search
This commit is contained in:
		
							parent
							
								
									d2588d9de4
								
							
						
					
					
						commit
						e016bd6900
					
				| @ -25,7 +25,7 @@ import { cn } from "@/lib/utils"; | |||||||
| import { FrigatePlusDialog } from "../dialog/FrigatePlusDialog"; | import { FrigatePlusDialog } from "../dialog/FrigatePlusDialog"; | ||||||
| import ObjectLifecycle from "./ObjectLifecycle"; | import ObjectLifecycle from "./ObjectLifecycle"; | ||||||
| import Chip from "@/components/indicators/Chip"; | import Chip from "@/components/indicators/Chip"; | ||||||
| import { FaDownload } from "react-icons/fa"; | import { FaDownload, FaImages } from "react-icons/fa"; | ||||||
| import FrigatePlusIcon from "@/components/icons/FrigatePlusIcon"; | import FrigatePlusIcon from "@/components/icons/FrigatePlusIcon"; | ||||||
| import { FaArrowsRotate } from "react-icons/fa6"; | import { FaArrowsRotate } from "react-icons/fa6"; | ||||||
| import { | import { | ||||||
| @ -33,6 +33,7 @@ import { | |||||||
|   TooltipContent, |   TooltipContent, | ||||||
|   TooltipTrigger, |   TooltipTrigger, | ||||||
| } from "@/components/ui/tooltip"; | } from "@/components/ui/tooltip"; | ||||||
|  | import { useNavigate } from "react-router-dom"; | ||||||
| 
 | 
 | ||||||
| type ReviewDetailDialogProps = { | type ReviewDetailDialogProps = { | ||||||
|   review?: ReviewSegment; |   review?: ReviewSegment; | ||||||
| @ -234,6 +235,8 @@ function EventItem({ | |||||||
| 
 | 
 | ||||||
|   const [hovered, setHovered] = useState(isMobile); |   const [hovered, setHovered] = useState(isMobile); | ||||||
| 
 | 
 | ||||||
|  |   const navigate = useNavigate(); | ||||||
|  | 
 | ||||||
|   return ( |   return ( | ||||||
|     <> |     <> | ||||||
|       <div |       <div | ||||||
| @ -328,6 +331,27 @@ function EventItem({ | |||||||
|                   <TooltipContent>View Object Lifecycle</TooltipContent> |                   <TooltipContent>View Object Lifecycle</TooltipContent> | ||||||
|                 </Tooltip> |                 </Tooltip> | ||||||
|               )} |               )} | ||||||
|  | 
 | ||||||
|  |               {event.has_snapshot && config?.semantic_search.enabled && ( | ||||||
|  |                 <Tooltip> | ||||||
|  |                   <TooltipTrigger> | ||||||
|  |                     <Chip | ||||||
|  |                       className="cursor-pointer rounded-md bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500" | ||||||
|  |                       onClick={() => { | ||||||
|  |                         const similaritySearchParams = new URLSearchParams({ | ||||||
|  |                           search_type: "similarity", | ||||||
|  |                           event_id: event.id, | ||||||
|  |                         }).toString(); | ||||||
|  | 
 | ||||||
|  |                         navigate(`/search?${similaritySearchParams}`); | ||||||
|  |                       }} | ||||||
|  |                     > | ||||||
|  |                       <FaImages className="size-4 text-white" /> | ||||||
|  |                     </Chip> | ||||||
|  |                   </TooltipTrigger> | ||||||
|  |                   <TooltipContent>Find Similar</TooltipContent> | ||||||
|  |                 </Tooltip> | ||||||
|  |               )} | ||||||
|             </div> |             </div> | ||||||
|           </div> |           </div> | ||||||
|         )} |         )} | ||||||
|  | |||||||
| @ -3,7 +3,11 @@ import { useCameraPreviews } from "@/hooks/use-camera-previews"; | |||||||
| import { useOverlayState } from "@/hooks/use-overlay-state"; | import { useOverlayState } from "@/hooks/use-overlay-state"; | ||||||
| import { FrigateConfig } from "@/types/frigateConfig"; | import { FrigateConfig } from "@/types/frigateConfig"; | ||||||
| import { RecordingStartingPoint } from "@/types/record"; | import { RecordingStartingPoint } from "@/types/record"; | ||||||
| import { SearchFilter, SearchResult } from "@/types/search"; | import { | ||||||
|  |   PartialSearchResult, | ||||||
|  |   SearchFilter, | ||||||
|  |   SearchResult, | ||||||
|  | } from "@/types/search"; | ||||||
| import { TimeRange } from "@/types/timeline"; | import { TimeRange } from "@/types/timeline"; | ||||||
| import { RecordingView } from "@/views/recording/RecordingView"; | import { RecordingView } from "@/views/recording/RecordingView"; | ||||||
| import SearchView from "@/views/search/SearchView"; | import SearchView from "@/views/search/SearchView"; | ||||||
| @ -38,7 +42,27 @@ export default function Search() { | |||||||
| 
 | 
 | ||||||
|   // search api
 |   // search api
 | ||||||
| 
 | 
 | ||||||
|   const [similaritySearch, setSimilaritySearch] = useState<SearchResult>(); |   const [similaritySearch, setSimilaritySearch] = | ||||||
|  |     useState<PartialSearchResult>(); | ||||||
|  | 
 | ||||||
|  |   useEffect(() => { | ||||||
|  |     if ( | ||||||
|  |       config?.semantic_search.enabled && | ||||||
|  |       searchSearchParams["search_type"] == "similarity" && | ||||||
|  |       searchSearchParams["event_id"]?.length != 0 && | ||||||
|  |       searchFilter | ||||||
|  |     ) { | ||||||
|  |       setSimilaritySearch({ | ||||||
|  |         id: searchSearchParams["event_id"], | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       // remove event id from url params
 | ||||||
|  |       const { event_id: _event_id, ...newFilter } = searchFilter; | ||||||
|  |       setSearchFilter(newFilter); | ||||||
|  |     } | ||||||
|  |     // only run similarity search with event_id in the url when coming from review
 | ||||||
|  |     // eslint-disable-next-line react-hooks/exhaustive-deps
 | ||||||
|  |   }, []); | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (similaritySearch) { |     if (similaritySearch) { | ||||||
|  | |||||||
| @ -25,6 +25,9 @@ export type SearchResult = { | |||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | export type PartialSearchResult = Partial<SearchResult> & { id: string }; | ||||||
|  | 
 | ||||||
| export type SearchFilter = { | export type SearchFilter = { | ||||||
|   cameras?: string[]; |   cameras?: string[]; | ||||||
|   labels?: string[]; |   labels?: string[]; | ||||||
| @ -33,4 +36,5 @@ export type SearchFilter = { | |||||||
|   before?: number; |   before?: number; | ||||||
|   after?: number; |   after?: number; | ||||||
|   search_type?: SearchSource[]; |   search_type?: SearchSource[]; | ||||||
|  |   event_id?: string; | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -13,7 +13,11 @@ import { | |||||||
| import { cn } from "@/lib/utils"; | import { cn } from "@/lib/utils"; | ||||||
| import { FrigateConfig } from "@/types/frigateConfig"; | import { FrigateConfig } from "@/types/frigateConfig"; | ||||||
| import { Preview } from "@/types/preview"; | import { Preview } from "@/types/preview"; | ||||||
| import { SearchFilter, SearchResult } from "@/types/search"; | import { | ||||||
|  |   PartialSearchResult, | ||||||
|  |   SearchFilter, | ||||||
|  |   SearchResult, | ||||||
|  | } from "@/types/search"; | ||||||
| import { useCallback, useMemo, useState } from "react"; | import { useCallback, useMemo, useState } from "react"; | ||||||
| import { isMobileOnly } from "react-device-detect"; | import { isMobileOnly } from "react-device-detect"; | ||||||
| import { LuImage, LuSearchX, LuText, LuXCircle } from "react-icons/lu"; | import { LuImage, LuSearchX, LuText, LuXCircle } from "react-icons/lu"; | ||||||
| @ -26,7 +30,7 @@ type SearchViewProps = { | |||||||
|   searchResults?: SearchResult[]; |   searchResults?: SearchResult[]; | ||||||
|   allPreviews?: Preview[]; |   allPreviews?: Preview[]; | ||||||
|   isLoading: boolean; |   isLoading: boolean; | ||||||
|   similaritySearch?: SearchResult; |   similaritySearch?: PartialSearchResult; | ||||||
|   setSearch: (search: string) => void; |   setSearch: (search: string) => void; | ||||||
|   setSimilaritySearch: (search: SearchResult) => void; |   setSimilaritySearch: (search: SearchResult) => void; | ||||||
|   onUpdateFilter: (filter: SearchFilter) => void; |   onUpdateFilter: (filter: SearchFilter) => void; | ||||||
| @ -186,7 +190,7 @@ export default function SearchView({ | |||||||
|                       scrollLock={false} |                       scrollLock={false} | ||||||
|                       onClick={onSelectSearch} |                       onClick={onSelectSearch} | ||||||
|                     /> |                     /> | ||||||
|                     {searchTerm && ( |                     {(searchTerm || similaritySearch) && ( | ||||||
|                       <div className={cn("absolute right-2 top-2 z-40")}> |                       <div className={cn("absolute right-2 top-2 z-40")}> | ||||||
|                         <Tooltip> |                         <Tooltip> | ||||||
|                           <TooltipTrigger> |                           <TooltipTrigger> | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user