diff --git a/frigate/embeddings/embeddings.py b/frigate/embeddings/embeddings.py index 19b28c6f8..9ee508823 100644 --- a/frigate/embeddings/embeddings.py +++ b/frigate/embeddings/embeddings.py @@ -178,7 +178,7 @@ class Embeddings: embeddings = [] for desc in event_descriptions.values(): - embeddings.append(self.text_embedding([desc])) + embeddings.append(self.text_embedding([desc])[0]) ids = list(event_descriptions.keys()) diff --git a/web/src/components/filter/SearchFilterGroup.tsx b/web/src/components/filter/SearchFilterGroup.tsx index 376c9b599..5fe301f19 100644 --- a/web/src/components/filter/SearchFilterGroup.tsx +++ b/web/src/components/filter/SearchFilterGroup.tsx @@ -1,5 +1,4 @@ import { Button } from "../ui/button"; -import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; import useSWR from "swr"; import { FrigateConfig } from "@/types/frigateConfig"; import { useCallback, useEffect, useMemo, useState } from "react"; @@ -16,18 +15,11 @@ import { SearchFilter, SearchFilters, SearchSource, - DEFAULT_TIME_RANGE_AFTER, - DEFAULT_TIME_RANGE_BEFORE, } from "@/types/search"; import { DateRange } from "react-day-picker"; import { cn } from "@/lib/utils"; -import SubFilterIcon from "../icons/SubFilterIcon"; -import { FaLocationDot } from "react-icons/fa6"; import { MdLabel } from "react-icons/md"; -import SearchSourceIcon from "../icons/SearchSourceIcon"; import PlatformAwareDialog from "../overlay/dialog/PlatformAwareDialog"; -import { FaArrowRight, FaClock } from "react-icons/fa"; -import { useFormattedHour } from "@/hooks/use-date-utils"; import SearchFilterDialog from "../overlay/dialog/SearchFilterDialog"; import { CalendarRangeFilterButton } from "./CalendarFilterButton"; @@ -185,7 +177,6 @@ export default function SearchFilterGroup({ config={config} filter={filter} filterValues={filterValues} - groups={groups} onUpdateFilter={onUpdateFilter} /> @@ -364,503 +355,3 @@ export function GeneralFilterContent({ ); } - -type ZoneFilterButtonProps = { - allZones: string[]; - selectedZones?: string[]; - updateZoneFilter: (zones: string[] | undefined) => void; -}; -function ZoneFilterButton({ - allZones, - selectedZones, - updateZoneFilter, -}: ZoneFilterButtonProps) { - const [open, setOpen] = useState(false); - - const [currentZones, setCurrentZones] = useState( - selectedZones, - ); - - const buttonText = useMemo(() => { - if (isMobile) { - return "Zones"; - } - - if (!selectedZones || selectedZones.length == 0) { - return "All Zones"; - } - - if (selectedZones.length == 1) { - return selectedZones[0]; - } - - return `${selectedZones.length} Zones`; - }, [selectedZones]); - - // ui - - useEffect(() => { - setCurrentZones(selectedZones); - // only refresh when state changes - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedZones]); - - const trigger = ( - - ); - const content = ( - setOpen(false)} - /> - ); - - return ( - { - if (!open) { - setCurrentZones(selectedZones); - } - - setOpen(open); - }} - /> - ); -} - -type ZoneFilterContentProps = { - allZones?: string[]; - selectedZones?: string[]; - currentZones?: string[]; - updateZoneFilter?: (zones: string[] | undefined) => void; - setCurrentZones?: (zones: string[] | undefined) => void; - onClose: () => void; -}; -export function ZoneFilterContent({ - allZones, - selectedZones, - currentZones, - updateZoneFilter, - setCurrentZones, - onClose, -}: ZoneFilterContentProps) { - return ( - <> -
- {allZones && setCurrentZones && ( - <> - {isDesktop && } -
- - { - if (isChecked) { - setCurrentZones(undefined); - } - }} - /> -
-
- {allZones.map((item) => ( - { - if (isChecked) { - const updatedZones = currentZones - ? [...currentZones] - : []; - - updatedZones.push(item); - setCurrentZones(updatedZones); - } else { - const updatedZones = currentZones - ? [...currentZones] - : []; - - // can not deselect the last item - if (updatedZones.length > 1) { - updatedZones.splice(updatedZones.indexOf(item), 1); - setCurrentZones(updatedZones); - } - } - }} - /> - ))} -
- - )} -
- {isDesktop && } -
- - -
- - ); -} - -type SubFilterButtonProps = { - allSubLabels: string[]; - selectedSubLabels: string[] | undefined; - updateSubLabelFilter: (labels: string[] | undefined) => void; -}; -function SubFilterButton({ - allSubLabels, - selectedSubLabels, - updateSubLabelFilter, -}: SubFilterButtonProps) { - const [open, setOpen] = useState(false); - const [currentSubLabels, setCurrentSubLabels] = useState< - string[] | undefined - >(selectedSubLabels); - - const buttonText = useMemo(() => { - if (isMobile) { - return "Sub Labels"; - } - - if (!selectedSubLabels || selectedSubLabels.length == 0) { - return "All Sub Labels"; - } - - if (selectedSubLabels.length == 1) { - return selectedSubLabels[0]; - } - - return `${selectedSubLabels.length} Sub Labels`; - }, [selectedSubLabels]); - - const trigger = ( - - ); - const content = ( - setOpen(false)} - /> - ); - - return ( - { - if (!open) { - setCurrentSubLabels(selectedSubLabels); - } - - setOpen(open); - }} - /> - ); -} - -type SubFilterContentProps = { - allSubLabels: string[]; - selectedSubLabels: string[] | undefined; - currentSubLabels: string[] | undefined; - updateSubLabelFilter: (labels: string[] | undefined) => void; - setCurrentSubLabels: (labels: string[] | undefined) => void; - onClose: () => void; -}; -export function SubFilterContent({ - allSubLabels, - selectedSubLabels, - currentSubLabels, - updateSubLabelFilter, - setCurrentSubLabels, - onClose, -}: SubFilterContentProps) { - return ( - <> -
-
- - { - if (isChecked) { - setCurrentSubLabels(undefined); - } - }} - /> -
-
- {allSubLabels.map((item) => ( - { - if (isChecked) { - const updatedLabels = currentSubLabels - ? [...currentSubLabels] - : []; - - updatedLabels.push(item); - setCurrentSubLabels(updatedLabels); - } else { - const updatedLabels = currentSubLabels - ? [...currentSubLabels] - : []; - - // can not deselect the last item - if (updatedLabels.length > 1) { - updatedLabels.splice(updatedLabels.indexOf(item), 1); - setCurrentSubLabels(updatedLabels); - } - } - }} - /> - ))} -
-
- {isDesktop && } -
- - -
- - ); -} - -type SearchTypeButtonProps = { - selectedSearchSources: SearchSource[] | undefined; - updateSearchSourceFilter: (sources: SearchSource[] | undefined) => void; -}; -function SearchTypeButton({ - selectedSearchSources, - updateSearchSourceFilter, -}: SearchTypeButtonProps) { - const [open, setOpen] = useState(false); - - const buttonText = useMemo(() => { - if (isMobile) { - return "Sources"; - } - - if ( - !selectedSearchSources || - selectedSearchSources.length == 0 || - selectedSearchSources.length == 2 - ) { - return "All Search Sources"; - } - - if (selectedSearchSources.length == 1) { - return selectedSearchSources[0]; - } - - return `${selectedSearchSources.length} Search Sources`; - }, [selectedSearchSources]); - - const trigger = ( - - ); - const content = ( - setOpen(false)} - /> - ); - - return ( - - ); -} - -type SearchTypeContentProps = { - selectedSearchSources: SearchSource[] | undefined; - updateSearchSourceFilter: (sources: SearchSource[] | undefined) => void; - onClose: () => void; -}; -export function SearchTypeContent({ - selectedSearchSources, - updateSearchSourceFilter, - onClose, -}: SearchTypeContentProps) { - const [currentSearchSources, setCurrentSearchSources] = useState< - SearchSource[] | undefined - >(selectedSearchSources); - - return ( - <> -
-
- { - const updatedSources = currentSearchSources - ? [...currentSearchSources] - : []; - - if (isChecked) { - updatedSources.push("thumbnail"); - setCurrentSearchSources(updatedSources); - } else { - if (updatedSources.length > 1) { - const index = updatedSources.indexOf("thumbnail"); - if (index !== -1) updatedSources.splice(index, 1); - setCurrentSearchSources(updatedSources); - } - } - }} - /> - { - const updatedSources = currentSearchSources - ? [...currentSearchSources] - : []; - - if (isChecked) { - updatedSources.push("description"); - setCurrentSearchSources(updatedSources); - } else { - if (updatedSources.length > 1) { - const index = updatedSources.indexOf("description"); - if (index !== -1) updatedSources.splice(index, 1); - setCurrentSearchSources(updatedSources); - } - } - }} - /> -
- {isDesktop && } -
- - -
-
- - ); -} diff --git a/web/src/components/overlay/dialog/PlatformAwareDialog.tsx b/web/src/components/overlay/dialog/PlatformAwareDialog.tsx index a9aed8a65..79ee64f71 100644 --- a/web/src/components/overlay/dialog/PlatformAwareDialog.tsx +++ b/web/src/components/overlay/dialog/PlatformAwareDialog.tsx @@ -1,5 +1,9 @@ -import { MobilePage, MobilePageContent } from "@/components/mobile/MobilePage"; -import { Button } from "@/components/ui/button"; +import { + MobilePage, + MobilePageContent, + MobilePageHeader, + MobilePageTitle, +} from "@/components/mobile/MobilePage"; import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer"; import { Popover, @@ -64,12 +68,20 @@ export function PlatformAwareSheet({ }: PlatformAwareSheetProps) { if (isMobile) { return ( - - - - {content} - - +
+
onOpenChange(true)}>{trigger}
+ + + onOpenChange(false)} + > + More Filters + +
{content}
+
+
+
); } diff --git a/web/src/components/overlay/dialog/SearchFilterDialog.tsx b/web/src/components/overlay/dialog/SearchFilterDialog.tsx index 17ed0cb30..9fd66a162 100644 --- a/web/src/components/overlay/dialog/SearchFilterDialog.tsx +++ b/web/src/components/overlay/dialog/SearchFilterDialog.tsx @@ -10,15 +10,20 @@ import { SearchFilter, SearchSource, } from "@/types/search"; -import { CameraGroupConfig, FrigateConfig } from "@/types/frigateConfig"; +import { FrigateConfig } from "@/types/frigateConfig"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; -import { isDesktop } from "react-device-detect"; +import { isDesktop, isMobile, isMobileOnly } from "react-device-detect"; import { useFormattedHour } from "@/hooks/use-date-utils"; import Heading from "@/components/ui/heading"; +import FilterSwitch from "@/components/filter/FilterSwitch"; +import { Switch } from "@/components/ui/switch"; +import { Label } from "@/components/ui/label"; +import { DropdownMenuSeparator } from "@/components/ui/dropdown-menu"; +import { cn } from "@/lib/utils"; type SearchFilterDialogProps = { config?: FrigateConfig; @@ -29,14 +34,12 @@ type SearchFilterDialogProps = { zones: string[]; search_type: SearchSource[]; }; - groups: [string, CameraGroupConfig][]; onUpdateFilter: (filter: SearchFilter) => void; }; export default function SearchFilterDialog({ config, filter, filterValues, - groups, onUpdateFilter, }: SearchFilterDialogProps) { // data @@ -60,9 +63,53 @@ export default function SearchFilterDialog({ config={config} timeRange={currentFilter.time_range} updateTimeRange={(newRange) => - setCurrentFilter({ time_range: newRange, ...currentFilter }) + setCurrentFilter({ ...currentFilter, time_range: newRange }) } /> + + setCurrentFilter({ ...currentFilter, zones: newZones }) + } + /> + + setCurrentFilter({ ...currentFilter, sub_labels: newSubLabels }) + } + /> + + onUpdateFilter({ ...currentFilter, search_type: newSearchSource }) + } + /> + {isDesktop && } +
+ + +
); @@ -70,9 +117,18 @@ export default function SearchFilterDialog({ { + if (!open) { + setCurrentFilter(filter ?? {}); + } + + setOpen(open); + }} /> ); } @@ -210,58 +266,183 @@ function TimeRangeFilterContent({ ); } -/** - * {filters.includes("date") && ( - - )} - {filters.includes("time") && ( - - onUpdateFilter({ ...filter, time_range }) - } - /> - )} - {filters.includes("zone") && allZones.length > 0 && ( - - onUpdateFilter({ ...filter, zones: newZones }) - } - /> - )} - {filters.includes("sub") && ( - - onUpdateFilter({ ...filter, sub_labels: newSubLabels }) - } - /> - )} - {config?.semantic_search?.enabled && - filters.includes("source") && - !filter?.search_type?.includes("similarity") && ( - - onUpdateFilter({ ...filter, search_type: newSearchSource }) - } - /> +type ZoneFilterContentProps = { + allZones?: string[]; + zones?: string[]; + updateZones: (zones: string[] | undefined) => void; +}; +export function ZoneFilterContent({ + allZones, + zones, + updateZones, +}: ZoneFilterContentProps) { + return ( + <> +
+ {isDesktop && } + Zones + {allZones && ( + <> +
+ + { + if (isChecked) { + updateZones(undefined); + } + }} + /> +
+
+ {allZones.map((item) => ( + { + if (isChecked) { + const updatedZones = zones ? [...zones] : []; + + updatedZones.push(item); + updateZones(updatedZones); + } else { + const updatedZones = zones ? [...zones] : []; + + // can not deselect the last item + if (updatedZones.length > 1) { + updatedZones.splice(updatedZones.indexOf(item), 1); + updateZones(updatedZones); + } + } + }} + /> + ))} +
+ )} - */ +
+ + ); +} + +type SubFilterContentProps = { + allSubLabels: string[]; + subLabels: string[] | undefined; + setSubLabels: (labels: string[] | undefined) => void; +}; +export function SubFilterContent({ + allSubLabels, + subLabels, + setSubLabels, +}: SubFilterContentProps) { + return ( +
+ {isDesktop && } + Sub Labels +
+ + { + if (isChecked) { + setSubLabels(undefined); + } + }} + /> +
+
+ {allSubLabels.map((item) => ( + { + if (isChecked) { + const updatedLabels = subLabels ? [...subLabels] : []; + + updatedLabels.push(item); + setSubLabels(updatedLabels); + } else { + const updatedLabels = subLabels ? [...subLabels] : []; + + // can not deselect the last item + if (updatedLabels.length > 1) { + updatedLabels.splice(updatedLabels.indexOf(item), 1); + setSubLabels(updatedLabels); + } + } + }} + /> + ))} +
+
+ ); +} + +type SearchTypeContentProps = { + searchSources: SearchSource[] | undefined; + setSearchSources: (sources: SearchSource[] | undefined) => void; +}; +export function SearchTypeContent({ + searchSources, + setSearchSources, +}: SearchTypeContentProps) { + return ( + <> +
+ {isDesktop && } + Search Sources +
+ { + const updatedSources = searchSources ? [...searchSources] : []; + + if (isChecked) { + updatedSources.push("thumbnail"); + setSearchSources(updatedSources); + } else { + if (updatedSources.length > 1) { + const index = updatedSources.indexOf("thumbnail"); + if (index !== -1) updatedSources.splice(index, 1); + setSearchSources(updatedSources); + } + } + }} + /> + { + const updatedSources = searchSources ? [...searchSources] : []; + + if (isChecked) { + updatedSources.push("description"); + setSearchSources(updatedSources); + } else { + if (updatedSources.length > 1) { + const index = updatedSources.indexOf("description"); + if (index !== -1) updatedSources.splice(index, 1); + setSearchSources(updatedSources); + } + } + }} + /> +
+
+ + ); +}