From fd7580ce97c958174c535b1e8b31320a6afd9f9f Mon Sep 17 00:00:00 2001
From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
Date: Tue, 15 Oct 2024 16:45:45 -0500
Subject: [PATCH 01/10] use two columns on mobile view
---
.../components/card/SearchThumbnailFooter.tsx | 45 +++++++++----------
web/src/views/search/SearchView.tsx | 17 ++++---
web/src/views/settings/SearchSettingsView.tsx | 2 +-
3 files changed, 32 insertions(+), 32 deletions(-)
diff --git a/web/src/components/card/SearchThumbnailFooter.tsx b/web/src/components/card/SearchThumbnailFooter.tsx
index 9037fcf25c..78841159b6 100644
--- a/web/src/components/card/SearchThumbnailFooter.tsx
+++ b/web/src/components/card/SearchThumbnailFooter.tsx
@@ -10,8 +10,6 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
@@ -24,13 +22,7 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from "../ui/alert-dialog";
-import {
- LuCamera,
- LuCheck,
- LuDownload,
- LuMoreVertical,
- 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";
@@ -39,6 +31,7 @@ import { baseUrl } from "@/api/baseUrl";
import axios from "axios";
import { toast } from "sonner";
import { MdImageSearch } from "react-icons/md";
+import { isMobileOnly } from "react-device-detect";
type SearchThumbnailProps = {
searchResult: SearchResult;
@@ -130,18 +123,12 @@ export default function SearchThumbnailFooter({
)}
{formattedDate}
-
- {config?.plus?.enabled &&
+
+ {!isMobileOnly &&
+ config?.plus?.enabled &&
searchResult.has_snapshot &&
searchResult.end_time &&
- (searchResult.plus_id ? (
-
-
-
-
- Submitted to Frigate+
-
- ) : (
+ !searchResult.plus_id && (
Submit to Frigate+
- ))}
+ )}
{config?.semantic_search?.enabled && (
@@ -170,10 +157,6 @@ export default function SearchThumbnailFooter({
-
- Tracked Object Actions
-
-
{searchResult.has_clip && (
View object lifecycle
+
+ {isMobileOnly &&
+ config?.plus?.enabled &&
+ searchResult.has_snapshot &&
+ searchResult.end_time &&
+ !searchResult.plus_id && (
+ setShowFrigatePlus(true)}
+ >
+
+ Submit to Frigate+
+
+ )}
setDeleteDialogOpen(true)}
diff --git a/web/src/views/search/SearchView.tsx b/web/src/views/search/SearchView.tsx
index bc4f5b54d5..5d18e53d46 100644
--- a/web/src/views/search/SearchView.tsx
+++ b/web/src/views/search/SearchView.tsx
@@ -73,13 +73,16 @@ export default function SearchView({
const [columnCount, setColumnCount] = usePersistence("exploreGridColumns", 4);
const effectiveColumnCount = useMemo(() => columnCount ?? 4, [columnCount]);
- const gridClassName = cn("grid w-full gap-2 px-1 gap-2 lg:gap-4 md:mx-2", {
- "sm:grid-cols-2": effectiveColumnCount <= 2,
- "sm:grid-cols-3": effectiveColumnCount === 3,
- "sm:grid-cols-4": effectiveColumnCount === 4,
- "sm:grid-cols-5": effectiveColumnCount === 5,
- "sm:grid-cols-6": effectiveColumnCount === 6,
- });
+ const gridClassName = cn(
+ "grid w-full gap-2 px-1 gap-2 lg:gap-4 md:mx-2 grid-cols-2",
+ {
+ "sm:grid-cols-2": effectiveColumnCount <= 2,
+ "sm:grid-cols-3": effectiveColumnCount === 3,
+ "sm:grid-cols-4": effectiveColumnCount === 4,
+ "sm:grid-cols-5": effectiveColumnCount === 5,
+ "sm:grid-cols-6": effectiveColumnCount === 6,
+ },
+ );
// suggestions values
diff --git a/web/src/views/settings/SearchSettingsView.tsx b/web/src/views/settings/SearchSettingsView.tsx
index 0bbb8a15a9..904eb980cf 100644
--- a/web/src/views/settings/SearchSettingsView.tsx
+++ b/web/src/views/settings/SearchSettingsView.tsx
@@ -169,7 +169,7 @@ export default function SearchSettingsView({
rel="noopener noreferrer"
className="inline"
>
- Read the semantic search docs
+ Read the documentation
From 37bece341932c366089416553fda354fb9954ad3 Mon Sep 17 00:00:00 2001
From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
Date: Tue, 15 Oct 2024 17:06:36 -0500
Subject: [PATCH 02/10] clean up search settings view
---
web/src/views/settings/SearchSettingsView.tsx | 121 +++++++++---------
1 file changed, 64 insertions(+), 57 deletions(-)
diff --git a/web/src/views/settings/SearchSettingsView.tsx b/web/src/views/settings/SearchSettingsView.tsx
index 904eb980cf..ccf3a5629e 100644
--- a/web/src/views/settings/SearchSettingsView.tsx
+++ b/web/src/views/settings/SearchSettingsView.tsx
@@ -125,7 +125,7 @@ export default function SearchSettingsView({
if (changedValue) {
addMessage(
"search_settings",
- `Unsaved search settings changes)`,
+ `Unsaved search settings changes`,
undefined,
"search_settings",
);
@@ -151,31 +151,37 @@ export default function SearchSettingsView({
Search Settings
-
-
- Semantic Search in Frigate allows you to find tracked objects within
- your review items using either the image itself, a user-defined text
- description, or an automatically generated one. This feature works
- by creating embeddings — numerical vector representations — for both
- the images and text descriptions of your tracked objects. By
- comparing these embeddings, Frigate assesses their similarities to
- deliver relevant search results.
-
+
+
+ Semantic Search
+
+
+
+
+ Semantic Search in Frigate allows you to find tracked objects
+ within your review items using either the image itself, a
+ user-defined text description, or an automatically generated one.
+ This feature works by creating embeddings — numerical vector
+ representations — for both the images and text descriptions of
+ your tracked objects. By comparing these embeddings, Frigate
+ assesses their similarities to deliver relevant search results.
+
-
-
- Read the documentation
-
-
+
+
+ Read the Documentation
+
+
+
-
-
+
+
Enabled
@@ -210,23 +216,24 @@ export default function SearchSettingsView({
/>
-
+
-
Model Size
+
Model Size
- Configure the size of the model used for semantic search
- embeddings:
-
-
- • Configuring the small model employs a quantized
- version of the model that uses much less RAM and runs faster
- on CPU with a very negligible difference in embedding quality.
-
-
- • Configuring the large model employs the full Jina
- model and will automatically run on the GPU if applicable.
+ The size of the model used for semantic search embeddings.
+
+
+ Using small employs a quantized version of the
+ model that uses much less RAM and runs faster on CPU with a
+ very negligible difference in embedding quality.
+
+
+ Using large employs the full Jina model and will
+ automatically run on the GPU if applicable.
+
+
-
-
-
- Reset
-
-
- {isLoading ? (
-
- ) : (
- "Save"
- )}
-
-
+
+
+
+
+ Reset
+
+
+ {isLoading ? (
+
+ ) : (
+ "Save"
+ )}
+
From b7ffc1ec4cbb7bd327acc592d86f834cf4241e96 Mon Sep 17 00:00:00 2001
From: Nicolas Mowen
Date: Tue, 15 Oct 2024 16:16:56 -0600
Subject: [PATCH 03/10] Add zones and saving logic
---
.../components/filter/SearchFilterGroup.tsx | 99 -----------------
.../overlay/dialog/SearchFilterDialog.tsx | 101 +++++++++++++++++-
2 files changed, 100 insertions(+), 100 deletions(-)
diff --git a/web/src/components/filter/SearchFilterGroup.tsx b/web/src/components/filter/SearchFilterGroup.tsx
index 376c9b5992..6850b9df3d 100644
--- a/web/src/components/filter/SearchFilterGroup.tsx
+++ b/web/src/components/filter/SearchFilterGroup.tsx
@@ -448,105 +448,6 @@ function ZoneFilterButton({
);
}
-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 &&
}
-
-
- All Zones
-
- {
- 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 && }
-
- {
- if (updateZoneFilter && selectedZones != currentZones) {
- updateZoneFilter(currentZones);
- }
-
- onClose();
- }}
- >
- Apply
-
- {
- setCurrentZones?.(undefined);
- updateZoneFilter?.(undefined);
- }}
- >
- Reset
-
-
- >
- );
-}
-
type SubFilterButtonProps = {
allSubLabels: string[];
selectedSubLabels: string[] | undefined;
diff --git a/web/src/components/overlay/dialog/SearchFilterDialog.tsx b/web/src/components/overlay/dialog/SearchFilterDialog.tsx
index 17ed0cb306..7359505f8a 100644
--- a/web/src/components/overlay/dialog/SearchFilterDialog.tsx
+++ b/web/src/components/overlay/dialog/SearchFilterDialog.tsx
@@ -19,6 +19,10 @@ import {
import { isDesktop } 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";
type SearchFilterDialogProps = {
config?: FrigateConfig;
@@ -63,6 +67,35 @@ export default function SearchFilterDialog({
setCurrentFilter({ time_range: newRange, ...currentFilter })
}
/>
+
+ setCurrentFilter({ zones: newZones, ...currentFilter })
+ }
+ />
+ {isDesktop && }
+
+ {
+ if (currentFilter != filter) {
+ onUpdateFilter(currentFilter);
+ }
+
+ setOpen(false);
+ }}
+ >
+ Apply
+
+ {
+ setCurrentFilter(filter ?? {});
+ }}
+ >
+ Reset
+
+
>
);
@@ -70,7 +103,7 @@ export default function SearchFilterDialog({
@@ -210,6 +243,72 @@ function TimeRangeFilterContent({
);
}
+type ZoneFilterContentProps = {
+ allZones?: string[];
+ zones?: string[];
+ updateZones: (zones: string[] | undefined) => void;
+};
+export function ZoneFilterContent({
+ allZones,
+ zones,
+ updateZones,
+}: ZoneFilterContentProps) {
+ return (
+ <>
+
+ {allZones && (
+ <>
+ {isDesktop &&
}
+
+
+ All Zones
+
+ {
+ 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);
+ }
+ }
+ }}
+ />
+ ))}
+
+ >
+ )}
+
+ >
+ );
+}
+
/**
* {filters.includes("date") && (
Date: Tue, 15 Oct 2024 16:33:22 -0600
Subject: [PATCH 04/10] Add all filters to side panel
---
.../components/filter/SearchFilterGroup.tsx | 410 ------------------
.../overlay/dialog/SearchFilterDialog.tsx | 200 ++++++---
2 files changed, 140 insertions(+), 470 deletions(-)
diff --git a/web/src/components/filter/SearchFilterGroup.tsx b/web/src/components/filter/SearchFilterGroup.tsx
index 6850b9df3d..5fe301f196 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,404 +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 = (
-
-
-
- {buttonText}
-
-
- );
- const content = (
- setOpen(false)}
- />
- );
-
- return (
- {
- if (!open) {
- setCurrentZones(selectedZones);
- }
-
- setOpen(open);
- }}
- />
- );
-}
-
-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 = (
-
-
-
- {buttonText}
-
-
- );
- 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 (
- <>
-
-
-
- All Sub Labels
-
- {
- 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 && }
-
- {
- if (selectedSubLabels != currentSubLabels) {
- updateSubLabelFilter(currentSubLabels);
- }
-
- onClose();
- }}
- >
- Apply
-
- {
- updateSubLabelFilter(undefined);
- }}
- >
- Reset
-
-
- >
- );
-}
-
-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 = (
-
-
-
- {buttonText}
-
-
- );
- 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 &&
}
-
- {
- if (selectedSearchSources != currentSearchSources) {
- updateSearchSourceFilter(currentSearchSources);
- }
-
- onClose();
- }}
- >
- Apply
-
- {
- updateSearchSourceFilter(undefined);
- setCurrentSearchSources(["thumbnail", "description"]);
- }}
- >
- Reset
-
-
-
- >
- );
-}
diff --git a/web/src/components/overlay/dialog/SearchFilterDialog.tsx b/web/src/components/overlay/dialog/SearchFilterDialog.tsx
index 7359505f8a..c955efb97d 100644
--- a/web/src/components/overlay/dialog/SearchFilterDialog.tsx
+++ b/web/src/components/overlay/dialog/SearchFilterDialog.tsx
@@ -10,7 +10,7 @@ import {
SearchFilter,
SearchSource,
} from "@/types/search";
-import { CameraGroupConfig, FrigateConfig } from "@/types/frigateConfig";
+import { FrigateConfig } from "@/types/frigateConfig";
import {
Popover,
PopoverContent,
@@ -33,14 +33,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
@@ -50,6 +48,8 @@ export default function SearchFilterDialog({
// state
+ console.log(`the filter is ${JSON.stringify(currentFilter)}`);
+
const [open, setOpen] = useState(false);
const trigger = (
@@ -64,14 +64,29 @@ export default function SearchFilterDialog({
config={config}
timeRange={currentFilter.time_range}
updateTimeRange={(newRange) =>
- setCurrentFilter({ time_range: newRange, ...currentFilter })
+ setCurrentFilter({ ...currentFilter, time_range: newRange })
}
/>
- setCurrentFilter({ zones: newZones, ...currentFilter })
+ setCurrentFilter({ ...currentFilter, zones: newZones })
+ }
+ />
+
+ setCurrentFilter({ ...currentFilter, sub_labels: newSubLabels })
+ }
+ />
+
+ onUpdateFilter({ ...currentFilter, search_type: newSearchSource })
}
/>
{isDesktop && }
@@ -105,7 +120,13 @@ export default function SearchFilterDialog({
content={content}
contentClassName="w-auto lg:w-[300px]"
open={open}
- onOpenChange={setOpen}
+ onOpenChange={(open) => {
+ if (!open) {
+ setCurrentFilter(filter ?? {});
+ }
+
+ setOpen(open);
+ }}
/>
);
}
@@ -256,9 +277,10 @@ export function ZoneFilterContent({
return (
<>
+ {isDesktop &&
}
+
Zones
{allZones && (
<>
- {isDesktop &&
}
- )}
- {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 SubFilterContentProps = {
+ allSubLabels: string[];
+ subLabels: string[] | undefined;
+ setSubLabels: (labels: string[] | undefined) => void;
+};
+export function SubFilterContent({
+ allSubLabels,
+ subLabels,
+ setSubLabels,
+}: SubFilterContentProps) {
+ return (
+
+ {isDesktop &&
}
+
Sub Labels
+
+
+ All 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);
+ }
+ }
+ }}
+ />
+
+
+ >
+ );
+}
From 683e350ca13e98ff8d92312a22b7e1ecb18d9afc Mon Sep 17 00:00:00 2001
From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
Date: Tue, 15 Oct 2024 17:19:59 -0500
Subject: [PATCH 05/10] better match with ui settings
---
web/src/views/settings/SearchSettingsView.tsx | 59 +++++++++----------
1 file changed, 28 insertions(+), 31 deletions(-)
diff --git a/web/src/views/settings/SearchSettingsView.tsx b/web/src/views/settings/SearchSettingsView.tsx
index ccf3a5629e..f5d80679dc 100644
--- a/web/src/views/settings/SearchSettingsView.tsx
+++ b/web/src/views/settings/SearchSettingsView.tsx
@@ -161,10 +161,6 @@ export default function SearchSettingsView({
Semantic Search in Frigate allows you to find tracked objects
within your review items using either the image itself, a
user-defined text description, or an automatically generated one.
- This feature works by creating embeddings — numerical vector
- representations — for both the images and text descriptions of
- your tracked objects. By comparing these embeddings, Frigate
- assesses their similarities to deliver relevant search results.
@@ -182,52 +178,53 @@ export default function SearchSettingsView({
-
-
- Enabled
-
+
{
handleSearchConfigChange({ enabled: isChecked });
}}
/>
-
-
-
Re-Index On Startup
-
- Re-indexing will reprocess all thumbnails and descriptions (if
- enabled) and apply the embeddings on each startup. Don't forget
- to disable the option after restarting!
+ Enabled
+
+
+
+
+
{
+ handleSearchConfigChange({ reindex: isChecked });
+ }}
+ />
+
+ Re-Index On Startup
-
{
- handleSearchConfigChange({ reindex: isChecked });
- }}
- />
+
+ Re-indexing will reprocess all thumbnails and descriptions (if
+ enabled) and apply the embeddings on each startup. Don't forget to
+ disable the option after restarting!
+
-
-
+
-
Model Size
-
+
Model Size
+
The size of the model used for semantic search embeddings.
Using small employs a quantized version of the
- model that uses much less RAM and runs faster on CPU with a
- very negligible difference in embedding quality.
+ model that uses less RAM and runs faster on CPU with a very
+ negligible difference in embedding quality.
Using large employs the full Jina model and will
From 00d16d869187f078eceb3be4606a2b063c06e8bd Mon Sep 17 00:00:00 2001
From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
Date: Tue, 15 Oct 2024 17:31:23 -0500
Subject: [PATCH 06/10] fix icon size
---
web/src/components/card/SearchThumbnailFooter.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/web/src/components/card/SearchThumbnailFooter.tsx b/web/src/components/card/SearchThumbnailFooter.tsx
index 78841159b6..1a16b3ad06 100644
--- a/web/src/components/card/SearchThumbnailFooter.tsx
+++ b/web/src/components/card/SearchThumbnailFooter.tsx
@@ -198,7 +198,7 @@ export default function SearchThumbnailFooter({
className="cursor-pointer"
onClick={() => setShowFrigatePlus(true)}
>
-
+
Submit to Frigate+
)}
From 8ce569d365fa2560a9dc65c84a0240ff8ee142e9 Mon Sep 17 00:00:00 2001
From: Nicolas Mowen
Date: Tue, 15 Oct 2024 16:42:44 -0600
Subject: [PATCH 07/10] Fix mobile fitler page
---
.../overlay/dialog/PlatformAwareDialog.tsx | 29 ++++++++++++++-----
.../overlay/dialog/SearchFilterDialog.tsx | 2 --
2 files changed, 22 insertions(+), 9 deletions(-)
diff --git a/web/src/components/overlay/dialog/PlatformAwareDialog.tsx b/web/src/components/overlay/dialog/PlatformAwareDialog.tsx
index a9aed8a65a..69632a77b8 100644
--- a/web/src/components/overlay/dialog/PlatformAwareDialog.tsx
+++ b/web/src/components/overlay/dialog/PlatformAwareDialog.tsx
@@ -1,4 +1,9 @@
-import { MobilePage, MobilePageContent } from "@/components/mobile/MobilePage";
+import {
+ MobilePage,
+ MobilePageContent,
+ MobilePageHeader,
+ MobilePageTitle,
+} from "@/components/mobile/MobilePage";
import { Button } from "@/components/ui/button";
import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer";
import {
@@ -64,12 +69,22 @@ export function PlatformAwareSheet({
}: PlatformAwareSheetProps) {
if (isMobile) {
return (
-
- {trigger}
-
- {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 c955efb97d..291e8aa5e7 100644
--- a/web/src/components/overlay/dialog/SearchFilterDialog.tsx
+++ b/web/src/components/overlay/dialog/SearchFilterDialog.tsx
@@ -48,8 +48,6 @@ export default function SearchFilterDialog({
// state
- console.log(`the filter is ${JSON.stringify(currentFilter)}`);
-
const [open, setOpen] = useState(false);
const trigger = (
From 0e180cd135e52a194f42a6ccc7e858d7cb124505 Mon Sep 17 00:00:00 2001
From: Nicolas Mowen
Date: Tue, 15 Oct 2024 17:00:57 -0600
Subject: [PATCH 08/10] Fix embeddings access
---
frigate/embeddings/embeddings.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frigate/embeddings/embeddings.py b/frigate/embeddings/embeddings.py
index 19b28c6f88..9ee5088236 100644
--- a/frigate/embeddings/embeddings.py
+++ b/frigate/embeddings/embeddings.py
@@ -178,7 +178,7 @@ def batch_upsert_description(self, event_descriptions: dict[str, str]) -> ndarra
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())
From eff4e66159c5d50beb8162d0426fd2af8692cb95 Mon Sep 17 00:00:00 2001
From: Nicolas Mowen
Date: Tue, 15 Oct 2024 17:05:40 -0600
Subject: [PATCH 09/10] Cleanup
---
web/src/components/overlay/dialog/PlatformAwareDialog.tsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/web/src/components/overlay/dialog/PlatformAwareDialog.tsx b/web/src/components/overlay/dialog/PlatformAwareDialog.tsx
index 69632a77b8..56fa7fb33f 100644
--- a/web/src/components/overlay/dialog/PlatformAwareDialog.tsx
+++ b/web/src/components/overlay/dialog/PlatformAwareDialog.tsx
@@ -4,7 +4,6 @@ import {
MobilePageHeader,
MobilePageTitle,
} from "@/components/mobile/MobilePage";
-import { Button } from "@/components/ui/button";
import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer";
import {
Popover,
From a1abaed9b3ca5a651cf84e1411d52ff6d11ecaea Mon Sep 17 00:00:00 2001
From: Nicolas Mowen
Date: Tue, 15 Oct 2024 17:32:09 -0600
Subject: [PATCH 10/10] Fix scroll
---
web/src/components/overlay/dialog/PlatformAwareDialog.tsx | 4 +---
web/src/components/overlay/dialog/SearchFilterDialog.tsx | 8 ++++++--
2 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/web/src/components/overlay/dialog/PlatformAwareDialog.tsx b/web/src/components/overlay/dialog/PlatformAwareDialog.tsx
index 56fa7fb33f..79ee64f711 100644
--- a/web/src/components/overlay/dialog/PlatformAwareDialog.tsx
+++ b/web/src/components/overlay/dialog/PlatformAwareDialog.tsx
@@ -78,9 +78,7 @@ export function PlatformAwareSheet({
>
More Filters
-
- {content}
-
+ {content}
diff --git a/web/src/components/overlay/dialog/SearchFilterDialog.tsx b/web/src/components/overlay/dialog/SearchFilterDialog.tsx
index 291e8aa5e7..9fd66a1622 100644
--- a/web/src/components/overlay/dialog/SearchFilterDialog.tsx
+++ b/web/src/components/overlay/dialog/SearchFilterDialog.tsx
@@ -16,13 +16,14 @@ import {
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;
@@ -116,7 +117,10 @@ export default function SearchFilterDialog({
{
if (!open) {