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 SearchThumbnailPlayer from "@/components/player/SearchThumbnailPlayer"; import { Input } from "@/components/ui/input"; import { Toaster } from "@/components/ui/sonner"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; import { FrigateConfig } from "@/types/frigateConfig"; import { Preview } from "@/types/preview"; import { PartialSearchResult, SearchFilter, SearchResult, } from "@/types/search"; import { useCallback, useMemo, useState } from "react"; import { isMobileOnly } from "react-device-detect"; import { LuImage, LuSearchX, LuText, LuXCircle } from "react-icons/lu"; import useSWR from "swr"; type SearchViewProps = { search: string; searchTerm: string; searchFilter?: SearchFilter; searchResults?: SearchResult[]; allPreviews?: Preview[]; isLoading: boolean; similaritySearch?: PartialSearchResult; setSearch: (search: string) => void; setSimilaritySearch: (search: SearchResult) => void; onUpdateFilter: (filter: SearchFilter) => void; onOpenSearch: (item: SearchResult) => void; }; export default function SearchView({ search, searchTerm, searchFilter, searchResults, allPreviews, isLoading, similaritySearch, setSearch, setSimilaritySearch, onUpdateFilter, onOpenSearch, }: SearchViewProps) { const { data: config } = useSWR("config", { revalidateOnFocus: false, }); // remove duplicate event ids const uniqueResults = useMemo(() => { return searchResults?.filter( (value, index, self) => index === self.findIndex((v) => v.id === value.id), ); }, [searchResults]); // detail const [searchDetail, setSearchDetail] = useState(); // search interaction const onSelectSearch = useCallback( (item: SearchResult, detail: boolean) => { if (detail) { setSearchDetail(item); } else { onOpenSearch(item); } }, [onOpenSearch], ); // confidence score - probably needs tweaking const zScoreToConfidence = (score: number, source: string) => { let midpoint, scale; if (source === "thumbnail") { midpoint = 2; scale = 0.5; } else { midpoint = 0.5; scale = 1.5; } // Sigmoid function: 1 / (1 + e^x) const confidence = 1 / (1 + Math.exp((score - midpoint) * scale)); return Math.round(confidence * 100); }; const hasExistingSearch = useMemo( () => searchResults != undefined || searchFilter != undefined, [searchResults, searchFilter], ); return (
setSimilaritySearch(searchDetail)) } />
{config?.semantic_search?.enabled && (
setSearch(e.target.value)} /> {search && ( setSearch("")} /> )}
)} {hasExistingSearch && ( )}
{searchTerm.length > 0 && searchResults?.length == 0 && (
No Detected Objects Found
)} {isLoading && ( )}
{uniqueResults && uniqueResults.map((value) => { const selected = false; return (
{(searchTerm || similaritySearch) && (
{value.search_source == "thumbnail" ? ( ) : ( )} {zScoreToConfidence( value.search_distance, value.search_source, )} % Matched {value.search_source} at{" "} {zScoreToConfidence( value.search_distance, value.search_source, )} %
)}
); })}
); }