diff --git a/frigate/camera/maintainer.py b/frigate/camera/maintainer.py index 865fe4725..815e650e9 100644 --- a/frigate/camera/maintainer.py +++ b/frigate/camera/maintainer.py @@ -136,6 +136,7 @@ class CameraMaintainer(threading.Thread): self.ptz_metrics[name], self.region_grids[name], self.stop_event, + self.config.logger, ) self.camera_processes[config.name] = camera_process camera_process.start() @@ -156,7 +157,11 @@ class CameraMaintainer(threading.Thread): self.frame_manager.create(f"{config.name}_frame{i}", frame_size) capture_process = CameraCapture( - config, count, self.camera_metrics[name], self.stop_event + config, + count, + self.camera_metrics[name], + self.stop_event, + self.config.logger, ) capture_process.daemon = True self.capture_processes[name] = capture_process diff --git a/frigate/data_processing/post/review_descriptions.py b/frigate/data_processing/post/review_descriptions.py index 9691ac8fd..fadc483c3 100644 --- a/frigate/data_processing/post/review_descriptions.py +++ b/frigate/data_processing/post/review_descriptions.py @@ -132,17 +132,15 @@ class ReviewDescriptionProcessor(PostProcessorApi): if image_source == ImageSourceEnum.recordings: duration = final_data["end_time"] - final_data["start_time"] - buffer_extension = min( - 10, max(2, duration * RECORDING_BUFFER_EXTENSION_PERCENT) - ) + buffer_extension = min(5, duration * RECORDING_BUFFER_EXTENSION_PERCENT) # Ensure minimum total duration for short review items # This provides better context for brief events total_duration = duration + (2 * buffer_extension) if total_duration < MIN_RECORDING_DURATION: - # Expand buffer to reach minimum duration, still respecting max of 10s per side + # Expand buffer to reach minimum duration, still respecting max of 5s per side additional_buffer_per_side = (MIN_RECORDING_DURATION - duration) / 2 - buffer_extension = min(10, additional_buffer_per_side) + buffer_extension = min(5, additional_buffer_per_side) thumbs = self.get_recording_frames( camera, diff --git a/frigate/data_processing/real_time/face.py b/frigate/data_processing/real_time/face.py index db3cb22ed..1901a81e1 100644 --- a/frigate/data_processing/real_time/face.py +++ b/frigate/data_processing/real_time/face.py @@ -424,7 +424,7 @@ class FaceRealTimeProcessor(RealTimeProcessorApi): if not res: return { - "message": "No face was recognized.", + "message": "Model is still training, please try again in a few moments.", "success": False, } diff --git a/frigate/video.py b/frigate/video.py index 739fb5c03..6be4f52a4 100755 --- a/frigate/video.py +++ b/frigate/video.py @@ -16,7 +16,7 @@ from frigate.comms.recordings_updater import ( RecordingsDataSubscriber, RecordingsDataTypeEnum, ) -from frigate.config import CameraConfig, DetectConfig, ModelConfig +from frigate.config import CameraConfig, DetectConfig, LoggerConfig, ModelConfig from frigate.config.camera.camera import CameraTypeEnum from frigate.config.camera.updater import ( CameraConfigUpdateEnum, @@ -539,6 +539,7 @@ class CameraCapture(FrigateProcess): shm_frame_count: int, camera_metrics: CameraMetrics, stop_event: MpEvent, + log_config: LoggerConfig | None = None, ) -> None: super().__init__( stop_event, @@ -549,9 +550,10 @@ class CameraCapture(FrigateProcess): self.config = config self.shm_frame_count = shm_frame_count self.camera_metrics = camera_metrics + self.log_config = log_config def run(self) -> None: - self.pre_run_setup() + self.pre_run_setup(self.log_config) camera_watchdog = CameraWatchdog( self.config, self.shm_frame_count, @@ -577,6 +579,7 @@ class CameraTracker(FrigateProcess): ptz_metrics: PTZMetrics, region_grid: list[list[dict[str, Any]]], stop_event: MpEvent, + log_config: LoggerConfig | None = None, ) -> None: super().__init__( stop_event, @@ -592,9 +595,10 @@ class CameraTracker(FrigateProcess): self.camera_metrics = camera_metrics self.ptz_metrics = ptz_metrics self.region_grid = region_grid + self.log_config = log_config def run(self) -> None: - self.pre_run_setup() + self.pre_run_setup(self.log_config) frame_queue = self.camera_metrics.frame_queue frame_shape = self.config.frame_shape diff --git a/web/public/notifications-worker.js b/web/public/notifications-worker.js index ab8a6ae44..8a0a8de2f 100644 --- a/web/public/notifications-worker.js +++ b/web/public/notifications-worker.js @@ -44,11 +44,16 @@ self.addEventListener("notificationclick", (event) => { switch (event.action ?? "default") { case "markReviewed": if (event.notification.data) { - fetch("/api/reviews/viewed", { - method: "POST", - headers: { "Content-Type": "application/json", "X-CSRF-TOKEN": 1 }, - body: JSON.stringify({ ids: [event.notification.data.id] }), - }); + event.waitUntil( + fetch("/api/reviews/viewed", { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-CSRF-TOKEN": 1, + }, + body: JSON.stringify({ ids: [event.notification.data.id] }), + }) + ); } break; default: @@ -58,7 +63,7 @@ self.addEventListener("notificationclick", (event) => { // eslint-disable-next-line no-undef if (clients.openWindow) { // eslint-disable-next-line no-undef - return clients.openWindow(url); + event.waitUntil(clients.openWindow(url)); } } } diff --git a/web/src/components/card/ClassificationCard.tsx b/web/src/components/card/ClassificationCard.tsx index 4511c22d5..0e1138feb 100644 --- a/web/src/components/card/ClassificationCard.tsx +++ b/web/src/components/card/ClassificationCard.tsx @@ -398,11 +398,7 @@ export function GroupedClassificationCard({ threshold={threshold} selected={false} i18nLibrary={i18nLibrary} - onClick={(data, meta) => { - if (meta || selectedItems.length > 0) { - onClick(data); - } - }} + onClick={() => {}} > {children?.(data)} diff --git a/web/src/components/overlay/detail/SearchDetailDialog.tsx b/web/src/components/overlay/detail/SearchDetailDialog.tsx index ae60ed1c6..8bd90d51f 100644 --- a/web/src/components/overlay/detail/SearchDetailDialog.tsx +++ b/web/src/components/overlay/detail/SearchDetailDialog.tsx @@ -683,6 +683,22 @@ function ObjectDetailsTab({ const mutate = useGlobalMutation(); + // Helper to map over SWR cached search results while preserving + // either paginated format (SearchResult[][]) or flat format (SearchResult[]) + const mapSearchResults = useCallback( + ( + currentData: SearchResult[][] | SearchResult[] | undefined, + fn: (event: SearchResult) => SearchResult, + ) => { + if (!currentData) return currentData; + if (Array.isArray(currentData[0])) { + return (currentData as SearchResult[][]).map((page) => page.map(fn)); + } + return (currentData as SearchResult[]).map(fn); + }, + [], + ); + // users const isAdmin = useIsAdmin(); @@ -810,17 +826,12 @@ function ObjectDetailsTab({ (key.includes("events") || key.includes("events/search") || key.includes("events/explore")), - (currentData: SearchResult[][] | SearchResult[] | undefined) => { - if (!currentData) return currentData; - // optimistic update - return currentData - .flat() - .map((event) => - event.id === search.id - ? { ...event, data: { ...event.data, description: desc } } - : event, - ); - }, + (currentData: SearchResult[][] | SearchResult[] | undefined) => + mapSearchResults(currentData, (event) => + event.id === search.id + ? { ...event, data: { ...event.data, description: desc } } + : event, + ), { optimisticData: true, rollbackOnError: true, @@ -843,7 +854,7 @@ function ObjectDetailsTab({ ); setDesc(search.data.description); }); - }, [desc, search, mutate, t]); + }, [desc, search, mutate, t, mapSearchResults]); const regenerateDescription = useCallback( (source: "snapshot" | "thumbnails") => { @@ -915,9 +926,8 @@ function ObjectDetailsTab({ (key.includes("events") || key.includes("events/search") || key.includes("events/explore")), - (currentData: SearchResult[][] | SearchResult[] | undefined) => { - if (!currentData) return currentData; - return currentData.flat().map((event) => + (currentData: SearchResult[][] | SearchResult[] | undefined) => + mapSearchResults(currentData, (event) => event.id === search.id ? { ...event, @@ -928,8 +938,7 @@ function ObjectDetailsTab({ }, } : event, - ); - }, + ), { optimisticData: true, rollbackOnError: true, @@ -963,7 +972,7 @@ function ObjectDetailsTab({ ); }); }, - [search, apiHost, mutate, setSearch, t], + [search, apiHost, mutate, setSearch, t, mapSearchResults], ); // recognized plate @@ -992,9 +1001,8 @@ function ObjectDetailsTab({ (key.includes("events") || key.includes("events/search") || key.includes("events/explore")), - (currentData: SearchResult[][] | SearchResult[] | undefined) => { - if (!currentData) return currentData; - return currentData.flat().map((event) => + (currentData: SearchResult[][] | SearchResult[] | undefined) => + mapSearchResults(currentData, (event) => event.id === search.id ? { ...event, @@ -1005,8 +1013,7 @@ function ObjectDetailsTab({ }, } : event, - ); - }, + ), { optimisticData: true, rollbackOnError: true, @@ -1040,7 +1047,7 @@ function ObjectDetailsTab({ ); }); }, - [search, apiHost, mutate, setSearch, t], + [search, apiHost, mutate, setSearch, t, mapSearchResults], ); // speech transcription @@ -1102,17 +1109,12 @@ function ObjectDetailsTab({ (key.includes("events") || key.includes("events/search") || key.includes("events/explore")), - (currentData: SearchResult[][] | SearchResult[] | undefined) => { - if (!currentData) return currentData; - // optimistic update - return currentData - .flat() - .map((event) => - event.id === search.id - ? { ...event, plus_id: "new_upload" } - : event, - ); - }, + (currentData: SearchResult[][] | SearchResult[] | undefined) => + mapSearchResults(currentData, (event) => + event.id === search.id + ? { ...event, plus_id: "new_upload" } + : event, + ), { optimisticData: true, rollbackOnError: true, @@ -1120,7 +1122,7 @@ function ObjectDetailsTab({ }, ); }, - [search, mutate], + [search, mutate, mapSearchResults], ); const popoverContainerRef = useRef(null); @@ -1503,7 +1505,7 @@ function ObjectDetailsTab({ ) : (