Compare commits

...
Author SHA1 Message Date
dependabot[bot]andGitHub 63665ded9b Bump the web-minor-patch group in /web with 12 updates
Bumps the web-minor-patch group in /web with 12 updates:

| Package | From | To |
| --- | --- | --- |
| [@hookform/resolvers](https://github.com/react-hook-form/resolvers) | `7.0.3` | `7.0.6` |
| [apexcharts](https://github.com/apexcharts/apexcharts.js) | `8.7.0` | `8.8.1` |
| [axios](https://github.com/axios/axios) | `1.21.0` | `1.21.1` |
| [framer-motion](https://github.com/motiondivision/motion) | `14.10.0` | `14.12.0` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `1.70.0` | `1.72.0` |
| [react-dropzone](https://github.com/react-dropzone/react-dropzone) | `20.1.4` | `20.2.0` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.91.0` | `7.92.0` |
| [scroll-into-view-if-needed](https://github.com/scroll-into-view/scroll-into-view-if-needed) | `3.1.0` | `3.2.0` |
| [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `6.2.1` | `6.3.0` |
| [prettier](https://github.com/prettier/prettier) | `3.9.10` | `3.10.1` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.72.1` | `8.72.2` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `8.3.6` | `8.3.8` |


Updates `@hookform/resolvers` from 7.0.3 to 7.0.6
- [Release notes](https://github.com/react-hook-form/resolvers/releases)
- [Commits](https://github.com/react-hook-form/resolvers/compare/v7.0.3...v7.0.6)

Updates `apexcharts` from 8.7.0 to 8.8.1
- [Release notes](https://github.com/apexcharts/apexcharts.js/releases)
- [Commits](https://github.com/apexcharts/apexcharts.js/compare/v8.7.0...v8.8.1)

Updates `axios` from 1.21.0 to 1.21.1
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.21.0...v1.21.1)

Updates `framer-motion` from 14.10.0 to 14.12.0
- [Changelog](https://github.com/motiondivision/motion/blob/main/CHANGELOG.md)
- [Commits](https://github.com/motiondivision/motion/compare/v14.10.0...v14.12.0)

Updates `lucide-react` from 1.70.0 to 1.72.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.72.0/packages/lucide-react)

Updates `react-dropzone` from 20.1.4 to 20.2.0
- [Release notes](https://github.com/react-dropzone/react-dropzone/releases)
- [Commits](https://github.com/react-dropzone/react-dropzone/compare/v20.1.4...v20.2.0)

Updates `react-hook-form` from 7.91.0 to 7.92.0
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.91.0...v7.92.0)

Updates `scroll-into-view-if-needed` from 3.1.0 to 3.2.0
- [Release notes](https://github.com/scroll-into-view/scroll-into-view-if-needed/releases)
- [Changelog](https://github.com/scroll-into-view/scroll-into-view-if-needed/blob/main/CHANGELOG.md)
- [Commits](https://github.com/scroll-into-view/scroll-into-view-if-needed/compare/v3.1.0...v3.2.0)

Updates `@vitejs/plugin-react` from 6.2.1 to 6.3.0
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.3.0/packages/plugin-react)

Updates `prettier` from 3.9.10 to 3.10.1
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.9.10...3.10.1)

Updates `typescript-eslint` from 8.72.1 to 8.72.2
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.72.2/packages/typescript-eslint)

Updates `vite` from 8.3.6 to 8.3.8
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.3.8/packages/vite)

---
updated-dependencies:
- dependency-name: "@hookform/resolvers"
  dependency-version: 7.0.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: web-minor-patch
- dependency-name: apexcharts
  dependency-version: 8.8.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: web-minor-patch
- dependency-name: axios
  dependency-version: 1.21.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: web-minor-patch
- dependency-name: framer-motion
  dependency-version: 14.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: web-minor-patch
- dependency-name: lucide-react
  dependency-version: 1.72.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: web-minor-patch
- dependency-name: react-dropzone
  dependency-version: 20.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: web-minor-patch
- dependency-name: react-hook-form
  dependency-version: 7.92.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: web-minor-patch
- dependency-name: scroll-into-view-if-needed
  dependency-version: 3.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: web-minor-patch
- dependency-name: "@vitejs/plugin-react"
  dependency-version: 6.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: web-minor-patch
- dependency-name: prettier
  dependency-version: 3.10.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: web-minor-patch
- dependency-name: typescript-eslint
  dependency-version: 8.72.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: web-minor-patch
- dependency-name: vite
  dependency-version: 8.3.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: web-minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-11-15 11:34:38 +00:00
Josh HawkinsandGitHub 7fd81171f0 Miscellaneous fixes (#24970)
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Build (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / AMD64 Smoke Test (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
* analytics notifications flag and device grant warnings

- report notifications as on when any camera enables them, not only the global setting
- don't warn about device nodes the runtime users can already use, such as world-rw nodes in an unprivileged LXC

* clarify which stream the All Cameras dashboard uses

* add exception catchall for autotracking calibration

* only default review api after to 24 hours when no bounds are given

The review endpoint replaced a missing or zero `after` with 24 hours ago, so a `before`-only query older than 24 hours returned nothing and `after=0` couldn't mean no lower bound. The default now applies only when neither `before` nor `after` is sent, a `before`-only query has no lower bound, and both bounds use `is not None` checks. The Live dashboard alerts bar sends no bounds and keeps the 24 hour window.
2026-11-14 07:34:17 -07:00
Josh HawkinsandGitHub 0e3dca5915 Live view improvements (#24967)
CI / AMD64 Build (push) Canceled after 0s
CI / AMD64 Smoke Test (push) Canceled after 0s
CI / ARM Build (push) Canceled after 0s
CI / Jetson Jetpack 6 (push) Canceled after 0s
CI / AMD64 Extra Build (push) Canceled after 0s
CI / ARM Extra Build (push) Canceled after 0s
CI / Synaptics Build (push) Canceled after 0s
CI / Assemble and push default build (push) Canceled after 0s
* hold the last frame on screen while switching live streams

Switching streams in the single camera view blanked the player until the new stream played, because the old player unmounts in the same render and the hidden new player gave the card no height. The outgoing video's last frame is now drawn to a canvas before the remount and covers the gap until the new stream plays, and the card takes the camera's aspect ratio so it keeps its size while the video is hidden.

* show live streams as a one-tap grid in the mobile drawer

Switching streams on mobile meant scrolling past the camera toggles, opening a dropdown, and picking a stream. The drawer now opens with the stream section, and streams are a wrapping grid of toggles sized to their names, so a switch is two taps. A single stream shows as plain text. The audio and two-way talk lines show a spinner while the stream's go2rtc metadata loads instead of reporting audio as unavailable, which showed on the first switch to a transcoded stream.

* don't capture the switch frame on tiles that show the camera still
2026-11-13 09:51:16 -07:00
Josh HawkinsandGitHub 9276502e4f Switch Debug Replay scenes at runtime (#24958)
* allow switching the detect scene of a debug replay camera without a restart

- show the scene field in the replay detect settings
- drop the replay camera's cached model on a detect change so the refresh recycle picks up the new scene's model
- recreate the tracked object state on refresh when the camera's model changed
- reject a scene no model can serve with a 400 instead of crashing the tracker and maintainer threads
- size each camera's detection input shm for the largest model and recreate leftover smaller segments, so a camera re-added under a larger model no longer overflows it
- ignore event ends for objects already dropped by a state reset

* end tracked events before replacing replay camera state on a scene switch

- test the replay model rebind against two configured models
2026-11-13 09:08:29 -07:00
23 changed files with 741 additions and 360 deletions
@@ -57,6 +57,17 @@ done
granted=0
failed=0
reachable() {
local node="$1" user
local access=(-r "$node" -a -w "$node")
if [[ -d "$node" ]]; then
access=(-r "$node" -a -x "$node")
fi
for user in frigate go2rtc; do
s6-setuidgid "$user" test "${access[@]}" || return 1
done
}
grant() {
local node="$1"
# nullglob only drops patterns that hold a metacharacter, so a literal
@@ -72,6 +83,11 @@ grant() {
fi
if setfacl -m "$spec" "$node" 2>/dev/null; then
granted=$((granted + 1))
# an unprivileged LXC can't set ACLs on its idmapped nodes, which are often
# already world-rw
elif reachable "$node"; then
return 0
else
failed=$((failed + 1))
echo "[WARN] could not grant device access on ${node}; see EXTRA_GROUPS in the non-root docs for the fallback"
+1 -1
View File
@@ -98,7 +98,7 @@ Additionally, when creating and editing camera groups in the UI, you can choose
:::note
Frigate's default dashboard ("All Cameras") will always use the first entry you've defined in streams when playing live streams from your cameras.
Frigate's default dashboard ("All Cameras") always uses the first entry in `streams`. That should be your highest quality stream, because Auto starts on the first stream and only steps down from there. To show a lower quality stream on a dashboard, create a camera group and choose the stream in each camera's [streaming settings](#streaming-options-on-camera-group-dashboards) for that group.
:::
+2 -1
View File
@@ -139,7 +139,8 @@ def collect(ctx: ReportContext) -> FeaturesSection:
restream=config.birdseye.restream,
),
mqtt=config.mqtt.enabled,
notifications=config.notifications.enabled,
notifications=config.notifications.enabled
or any(camera.notifications.enabled for camera in cameras),
auth=config.auth.enabled,
proxy_auth=config.proxy.header_map.user is not None,
tls=config.tls.enabled,
+17 -1
View File
@@ -45,7 +45,7 @@ from frigate.config.camera.updater import (
CameraConfigUpdateEnum,
CameraConfigUpdateTopic,
)
from frigate.const import REDACTED_CREDENTIAL_SENTINEL
from frigate.const import REDACTED_CREDENTIAL_SENTINEL, REPLAY_CAMERA_PREFIX
from frigate.ffmpeg_presets import FFMPEG_HWACCEL_VAAPI, _gpu_selector
from frigate.genai import PROVIDERS, load_providers
from frigate.jobs.media_sync import (
@@ -979,6 +979,17 @@ def _config_set_in_memory(request: Request, body: AppConfigSetBody) -> JSONRespo
status_code=400,
)
# an unresolvable scene would crash the threads that look up the model
detect_update = sections.get("detect")
if isinstance(detect_update, dict) and "scene" in detect_update:
try:
config._resolve_camera_model(cam_name, detect_update["scene"])
except (TypeError, ValueError):
return JSONResponse(
content={"success": False, "message": "Invalid detect scene"},
status_code=400,
)
for section_name, update in sections.items():
err = apply_section_update(camera_config, section_name, update)
if err is not None:
@@ -987,6 +998,11 @@ def _config_set_in_memory(request: Request, body: AppConfigSetBody) -> JSONRespo
status_code=400,
)
# the model is resolved again from the scene when the replay
# camera is recycled by the refresh below
if "detect" in sections and cam_name.startswith(REPLAY_CAMERA_PREFIX):
config.drop_camera_model(cam_name)
# Publish ZMQ updates so processing threads pick up changes
if body.update_topic and body.update_topic.startswith("config/cameras/"):
_, _, camera, field = body.update_topic.split("/")
+11 -4
View File
@@ -82,12 +82,19 @@ async def review(
reviewed = params.reviewed
limit = params.limit
severity = params.severity
before = params.before or datetime.datetime.now().timestamp()
after = (
params.after
or (datetime.datetime.now() - datetime.timedelta(hours=24)).timestamp()
before = (
params.before
if params.before is not None
else datetime.datetime.now().timestamp()
)
if params.after is not None:
after = params.after
elif params.before is not None:
after = 0
else:
after = (datetime.datetime.now() - datetime.timedelta(hours=24)).timestamp()
clauses = [
(ReviewSegment.start_time < before)
& ((ReviewSegment.end_time.is_null(True)) | (ReviewSegment.end_time > after))
+2 -9
View File
@@ -76,7 +76,7 @@ from frigate.models import (
from frigate.notices import install_registry
from frigate.notices.registry import NoticeRegistry
from frigate.object_detection.base import ObjectDetectProcess
from frigate.object_detection.util import detection_frame_size
from frigate.object_detection.util import create_detection_input_shm
from frigate.output.output import OutputProcess
from frigate.ptz.autotrack import PtzAutoTracker
from frigate.ptz.onvif import OnvifController
@@ -399,14 +399,7 @@ class FrigateApp:
model = self.config.model_for_camera(name)
model_cameras[model.scene].append(name)
try:
shm_in = UntrackedSharedMemory(
name=name,
create=True,
size=detection_frame_size(model),
)
except FileExistsError:
shm_in = UntrackedSharedMemory(name=name)
shm_in = create_detection_input_shm(name, self.config.models)
try:
shm_out = UntrackedSharedMemory(
+3 -6
View File
@@ -16,7 +16,7 @@ from frigate.config.camera.updater import (
)
from frigate.const import REPLAY_CAMERA_PREFIX
from frigate.models import Regions
from frigate.object_detection.util import detection_frame_size
from frigate.object_detection.util import create_detection_input_shm
from frigate.util.builtin import empty_and_close_queue
from frigate.util.image import SharedMemoryFrameManager, UntrackedSharedMemory
from frigate.util.object import get_camera_regions_grid
@@ -127,13 +127,10 @@ class CameraMaintainer(threading.Thread):
max(model.width, model.height),
)
create_detection_input_shm(name, self.config.models)
try:
UntrackedSharedMemory(name=f"out-{name}", create=True, size=20 * 6 * 4)
UntrackedSharedMemory(
name=name,
create=True,
size=detection_frame_size(model),
)
except FileExistsError:
pass
+2 -1
View File
@@ -296,7 +296,8 @@ class CameraState:
return frame_copy
def finished(self, obj_id: str) -> None:
del self.tracked_objects[obj_id]
# state rebuilt after a resolution or scene change no longer has the object
self.tracked_objects.pop(obj_id, None)
def on(self, event_type: str, callback: Callable[..., Any]) -> None:
self.callbacks[event_type].append(callback)
+23 -14
View File
@@ -113,6 +113,23 @@ class AsyncLocalObjectDetector(BaseLocalDetector):
return self.detect_api.receive_output()
def map_input_frame(
frame_manager: SharedMemoryFrameManager, camera: str, model: ModelConfig
) -> np.ndarray | None:
"""Map a camera's detection input segment as this model's input tensor.
The segment is sized for the largest configured model, so it can be larger
than this model's input.
"""
buf = frame_manager.write(camera)
shape = (1, model.height, model.width, 3)
if buf is None or len(buf) < model.height * model.width * 3:
return None
return np.ndarray(shape, dtype=np.uint8, buffer=buf)
class DetectorRunner(FrigateProcess):
def __init__(
self,
@@ -154,14 +171,10 @@ class DetectorRunner(FrigateProcess):
connection_id = self.detection_queue.get(timeout=1)
except queue.Empty:
continue
input_frame = frame_manager.get(
input_frame = map_input_frame(
frame_manager,
connection_id,
(
1,
self.detector_config.model.height, # type: ignore[union-attr]
self.detector_config.model.width, # type: ignore[union-attr]
3,
),
self.detector_config.model, # type: ignore[arg-type]
)
if input_frame is None:
@@ -227,14 +240,10 @@ class AsyncDetectorRunner(FrigateProcess):
continue
assert self._frame_manager is not None
input_frame = self._frame_manager.get(
input_frame = map_input_frame(
self._frame_manager,
connection_id,
(
1,
self.detector_config.model.height, # type: ignore[union-attr]
self.detector_config.model.width, # type: ignore[union-attr]
3,
),
self.detector_config.model, # type: ignore[arg-type]
)
if input_frame is None:
+26 -5
View File
@@ -6,18 +6,39 @@ import threading
from numpy import ndarray
from frigate.detectors.detector_config import InputTensorEnum, ModelConfig
from frigate.util.image import UntrackedSharedMemory
def detection_frame_size(model: ModelConfig) -> int:
"""Get the shared memory size a camera needs to hand frames to a model.
def create_detection_input_shm(
camera: str, models: list[ModelConfig]
) -> UntrackedSharedMemory:
"""Create the shared memory a camera hands detection frames through.
It is sized for the largest model so a camera re-added under the same name
with another scene can reuse it. Detectors keep mappings of it, so it is
never unlinked while they run.
Args:
model: The model the camera runs on
camera: Name of the camera
models: Every configured model
Returns:
Size in bytes of one model input frame
The camera's detection input segment
"""
return model.height * model.width * 3
size = max(model.height * model.width * 3 for model in models)
try:
return UntrackedSharedMemory(name=camera, create=True, size=size)
except FileExistsError:
shm = UntrackedSharedMemory(name=camera)
if shm.size >= size:
return shm
# left by an earlier run with smaller models, so no detector maps it yet
shm.close()
shm.unlink()
return UntrackedSharedMemory(name=camera, create=True, size=size)
class RequestStore:
+7 -1
View File
@@ -318,7 +318,13 @@ class PtzAutoTracker(threading.Thread):
) = map(float, camera_config.onvif.autotracking.movement_weights)
if startup and camera_config.onvif.autotracking.calibrate_on_startup:
await self._calibrate_camera(camera)
try:
await self._calibrate_camera(camera)
except Exception:
logger.exception(f"{camera}: Autotracker calibration failed")
self.calibrating[camera] = False
self._disable(camera, "calibration failed")
return
self.dispatcher.publish(f"{camera}/ptz_autotracker/active", "OFF", retain=False)
self.autotracker_init[camera] = True
+40
View File
@@ -98,6 +98,46 @@ class TestHttpApp(BaseTestHttp):
assert response.status_code == 200
assert app.frigate_config.cameras["front_door"].objects.track == ["person"]
@patch("frigate.detectors.detector_config.load_labels")
def test_config_set_in_memory_rebinds_replay_model_on_scene_change(
self, mock_labels
):
mock_labels.return_value = {}
replay = "_replay_front_door"
self.minimal_config["models"] = [
{"devices": ["cpu"]},
{"scene": "thermal", "devices": ["openvino:CPU"], "width": 300},
]
self.minimal_config["cameras"][replay] = self.minimal_config["cameras"].pop(
"front_door"
)
app = super().create_app()
app.config_publisher = Mock()
assert app.frigate_config.model_for_camera(replay).scene == "default"
with AuthTestClient(app) as client:
response = client.put(
"/config/set",
json={
"requires_restart": 0,
"skip_save": True,
"update_topic": f"config/cameras/{replay}/detect",
"config_data": {
"cameras": {replay: {"detect": {"scene": "thermal"}}}
},
},
)
assert response.status_code == 200
assert app.frigate_config.cameras[replay].detect.scene == "thermal"
assert app.frigate_config.model_for_camera(replay).scene == "thermal"
topics = [
call.args[0].update_type.name
for call in app.config_publisher.publish_update.call_args_list
]
assert topics[0] == "detect"
assert topics[-1] == "refresh"
####################################################################################################################
################################### Credential redaction sentinel ################################################
####################################################################################################################
+31
View File
@@ -89,6 +89,37 @@ class TestHttpReview(BaseTestHttp):
assert response_json[0]["id"] == id
assert response_json[0]["has_been_reviewed"] == False
def test_get_review_before_only_has_no_lower_bound(self):
now = datetime.now().timestamp()
with AuthTestClient(self.app) as client:
id = "123456.random"
super().insert_mock_review_segment(id, now - 72 * 3600, now - 71 * 3600)
response = client.get("/review", params={"before": now - 48 * 3600})
assert response.status_code == 200
assert [r["id"] for r in response.json()] == [id]
def test_get_review_after_zero_is_not_defaulted(self):
now = datetime.now().timestamp()
with AuthTestClient(self.app) as client:
id = "123456.random"
super().insert_mock_review_segment(id, now - 72 * 3600, now - 71 * 3600)
response = client.get("/review", params={"after": 0})
assert response.status_code == 200
assert [r["id"] for r in response.json()] == [id]
def test_get_review_no_bounds_defaults_to_last_24_hours(self):
now = datetime.now().timestamp()
with AuthTestClient(self.app) as client:
super().insert_mock_review_segment(
"123456.random", now - 72 * 3600, now - 71 * 3600
)
response = client.get("/review", params={"limit": 10})
assert response.status_code == 200
assert response.json() == []
def test_get_review_with_time_filter_no_matches(self):
"""Test that review items outside the range are not returned."""
now = datetime.now().timestamp()
@@ -100,6 +100,17 @@ class TestFeaturesCollector(unittest.TestCase):
self.assertEqual(section.users, {"admin": 1, "viewer": 1, "custom": 1})
self.assertEqual((section.camera_groups, section.profiles), (1, 0))
self.assertFalse(section.plus_api_key)
self.assertFalse(section.notifications)
def test_camera_notifications_count_as_on(self):
config = {
**CONFIG,
"cameras": {"front": {**FRONT_CAMERA, "notifications": {"enabled": True}}},
}
section = features.collect(make_context(make_config(config), {}))
self.assertTrue(section.notifications)
class TestFeatureMappings(unittest.TestCase):
+29
View File
@@ -193,6 +193,35 @@ class TestAutotrackerPrediction(unittest.TestCase):
self.assertEqual(self._target_box([-1, -2, 1, 2], 0), 0.2)
class TestAutotrackerCalibrationFailure(unittest.IsolatedAsyncioTestCase):
async def test_calibration_failure_disables_autotracking(self) -> None:
tracker = _make_tracker()
camera_config = tracker.config.cameras[CAMERA]
camera_config.onvif.autotracking.calibrate_on_startup = True
tracker.tracked_object_history = {}
tracker.tracked_object_metrics = {}
tracker.move_metrics = {}
tracker.intercept = {}
tracker.move_coefficients = {}
tracker.zoom_time = {}
tracker.move_queues = {}
tracker.move_queue_locks = {}
tracker.onvif.loop = asyncio.get_running_loop()
tracker.onvif.cams = {CAMERA: {"init": True, "features": ["pt-r-fov"]}}
tracker.onvif.get_service_capabilities = AsyncMock(return_value=True)
tracker.onvif.get_camera_status = AsyncMock()
tracker._process_move_queue = AsyncMock()
tracker._calibrate_camera = AsyncMock(side_effect=RuntimeError("disconnected"))
tracker.autotracker_init[CAMERA] = False
with self.assertLogs("frigate.ptz.autotrack", level="ERROR"):
await tracker._autotracker_setup(camera_config, CAMERA, startup=True)
self.assertFalse(camera_config.onvif.autotracking.enabled)
self.assertFalse(tracker.autotracker_init[CAMERA])
self.assertFalse(tracker.calibrating[CAMERA])
class TestAutotrackerMoveQueue(unittest.IsolatedAsyncioTestCase):
async def test_absolute_zoom_does_not_overwrite_the_pan_tilt_sample(self) -> None:
tracker = _make_tracker()
+16
View File
@@ -91,6 +91,7 @@ class TrackedObjectProcessor(threading.Thread):
CameraConfigUpdateEnum.enabled,
CameraConfigUpdateEnum.motion,
CameraConfigUpdateEnum.objects,
CameraConfigUpdateEnum.refresh,
CameraConfigUpdateEnum.remove,
CameraConfigUpdateEnum.timestamp_style,
CameraConfigUpdateEnum.zones,
@@ -784,6 +785,21 @@ class TrackedObjectProcessor(threading.Thread):
self.requestor.send_data(UPDATE_CAMERA_ACTIVITY, self.camera_activity)
# a replay camera recycled onto another scene's model needs new state
for camera in updated_topics.get("refresh", []):
camera_state = self.camera_states.get(camera)
if (
camera_state is None
or camera_state.model is self.config.model_for_camera(camera)
):
continue
self.force_end_all_events(camera, camera_state)
camera_state.shutdown()
self.frame_manager.close_camera(camera)
self.create_camera_state(camera)
# manage camera disabled state
for camera, config in self.config.cameras.items():
if not config.enabled_in_config:
+275 -184
View File
@@ -10,7 +10,7 @@
"hasInstallScript": true,
"dependencies": {
"@cycjimmy/jsmpeg-player": "^7.0.0",
"@hookform/resolvers": "^7.0.3",
"@hookform/resolvers": "^7.0.6",
"@radix-ui/react-alert-dialog": "^1.2.1",
"@radix-ui/react-aspect-ratio": "^1.2.0",
"@radix-ui/react-checkbox": "^1.4.1",
@@ -37,15 +37,15 @@
"@rjsf/shadcn": "^6.11.1",
"@rjsf/utils": "^6.10.1",
"@rjsf/validator-ajv8": "^6.11.1",
"apexcharts": "^8.7.0",
"axios": "^1.21.0",
"apexcharts": "^8.8.1",
"axios": "^1.21.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"copy-to-clipboard": "^4.0.2",
"date-fns": "^4.4.0",
"date-fns-tz": "^3.2.0",
"framer-motion": "^14.10.0",
"framer-motion": "^14.12.0",
"hls.js": "^1.7.4",
"i18next": "^26.7.2",
"i18next-http-backend": "^4.0.10",
@@ -53,7 +53,7 @@
"js-yaml": "^5.5.0",
"konva": "^10.9.1",
"lodash": "^4.18.1",
"lucide-react": "^1.70.0",
"lucide-react": "^1.72.0",
"monaco-yaml": "^5.5.1",
"next-themes": "^0.4.6",
"nosleep.js": "^0.12.0",
@@ -62,9 +62,9 @@
"react-day-picker": "^10.0.2",
"react-device-detect": "^2.2.3",
"react-dom": "^19.3.1",
"react-dropzone": "^20.1.4",
"react-dropzone": "^20.2.0",
"react-grid-layout": "^2.3.0",
"react-hook-form": "^7.91.0",
"react-hook-form": "^7.92.0",
"react-i18next": "^17.1.13",
"react-icons": "^5.7.0",
"react-konva": "^19.3.2",
@@ -73,7 +73,7 @@
"react-swipeable": "^7.0.2",
"react-zoom-pan-pinch": "3.4.4",
"remark-gfm": "^4.0.0",
"scroll-into-view-if-needed": "^3.1.0",
"scroll-into-view-if-needed": "^3.2.0",
"sonner": "^2.0.8",
"swr": "^2.5.1",
"tailwind-merge": "^2.6.1",
@@ -93,7 +93,7 @@
"@types/node": "^26.6.6",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"@vitejs/plugin-react": "^6.2.1",
"@vitejs/plugin-react": "^6.3.0",
"autoprefixer": "^10.6.2",
"esbuild": "^0.28.2",
"eslint": "^10.14.0",
@@ -106,12 +106,12 @@
"monaco-editor": "^0.53.0",
"patch-package": "^8.0.1",
"postcss": "^8.5.32",
"prettier": "^3.9.10",
"prettier": "^3.10.1",
"prettier-plugin-tailwindcss": "^0.8.1",
"tailwindcss": "^3.4.19",
"typescript": "^6.0.3",
"typescript-eslint": "^8.72.1",
"vite": "^8.3.6"
"typescript-eslint": "^8.72.2",
"vite": "^8.3.8"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -450,6 +450,40 @@
"url": "https://github.com/sponsors/JounQin"
}
},
"node_modules/@emnapi/core": {
"version": "2.0.0-beta.2",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-2.0.0-beta.2.tgz",
"integrity": "sha512-MzCVoWtcg4Pm2B3je5mMvYMvj3MmBKw+fU7vbsJc9/iT8ip6/eYIHo/Dsm/mr+wsHB6SEyJDuxB09Yy6r/8+4A==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "2.2.0",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "2.0.0-beta.2",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-2.0.0-beta.2.tgz",
"integrity": "sha512-JcrSPzPvZuNZvJqPTF9Fw25LbOJ8avzFVlaf2grmH4Cku0oGP47tRgqPZadcfj3YoZMHkFVnCYFbQM8I+i5QEw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/wasi-threads": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-2.2.0.tgz",
"integrity": "sha512-JP7OYH38Q7+pyTAe14raZHLQIK7BDKbdwji+Xi3pcd/6QkJYQH3SX4wu6EplGrGJWRepoNzedfm0G3B9Z4e9Rw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
@@ -1059,9 +1093,9 @@
"license": "MIT"
},
"node_modules/@hookform/resolvers": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-7.0.3.tgz",
"integrity": "sha512-beGY1JY6K5cyWlswD4BVf7UTb5MxSjKkjljrRyLPT3KIQzsTU0jOibBKqtdcKqIZYQUUyURed7roLWZtSdAtiQ==",
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-7.0.6.tgz",
"integrity": "sha512-LSTMwW4tmRWtM7pS5HkSqahi71USbhrmjc9pNHNa7UyYTbKIK/e0G1EAw6xsEnp9GahTjXhunxRJp0Bd8E6NZA==",
"license": "MIT",
"dependencies": {
"@standard-schema/utils": "^0.3.0"
@@ -1071,7 +1105,7 @@
"@vinejs/vine": "^2.0.0 || ^3.0.0 || ^4.0.0",
"ajv": "^8.12.0",
"ajv-errors": "^3.0.0",
"ajv-formats": "^2.1.1",
"ajv-formats": "^2.1.1 || ^3.0.0",
"arktype": "^2.0.0",
"ata-validator": "^1.23.0",
"class-transformer": ">=0.4.0",
@@ -1658,6 +1692,28 @@
"dev": true,
"license": "MIT"
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.6.tgz",
"integrity": "sha512-mRyDmEw9/fCDXCGfL1sbgIw/GUK0PVxRW4YwgPN61F3LQ+fSyoBFarJVzgGyhF6kRzEQZ0FejDqm4WLXSeqQmw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@tybys/wasm-util": "^0.10.3"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=23.5.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
},
"peerDependencies": {
"@emnapi/core": "^1.7.1 || 2.0.0-beta.2",
"@emnapi/runtime": "^1.7.1 || 2.0.0-beta.2"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -3018,9 +3074,9 @@
}
},
"node_modules/@rolldown/binding-android-arm-eabi": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.16.tgz",
"integrity": "sha512-ia3DwAs0OsMGS7+IConptSiyk9pEXkOx/AbJ2n/lAGxH3QkWQus1t8d7gnAT38FCU2sQu8lUbeR6AiZFv4JxaA==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.19.tgz",
"integrity": "sha512-e+Ebe0WT+9tbXmNKPIiC8YrWAHYwug3m6i4p3rZGGulfZri1jtI4HslT+OZnFf/OIHowWF71DTlHwXBP2SiA7A==",
"cpu": [
"arm"
],
@@ -3035,9 +3091,9 @@
}
},
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.16.tgz",
"integrity": "sha512-kx5wBiMB6E7TEvpl2B9gjLU004BkvEm2Dr941BFjx/SontPwSzNuQUIoVKxHHoSjpFIfHMmcguyLOtyn0hObRg==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.19.tgz",
"integrity": "sha512-hOombrTJ+szt2ciEzS7oCA78lMPBgF5UMQWCJIyNfnrI7GLl8HYoFqCnpL2gcPOaXMqryGCU5ExdQKyDDcC+yA==",
"cpu": [
"arm64"
],
@@ -3052,9 +3108,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.16.tgz",
"integrity": "sha512-qslZrXnKJU4cUpjoAmtAo/KQM9e2IRMlmpig6Zpwj+8KWxW8v+nkAf9216Bsek9lOC1jvg2yCjygCFo2f83msQ==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.19.tgz",
"integrity": "sha512-XoMA2pwCKGTQzUjPgtRwkedH4WmXhdIPKT6l6oOxd1PUbNodGI6DcBekgN7EC//+x3pfBYX/qL1gNmZAqivw5g==",
"cpu": [
"arm64"
],
@@ -3069,9 +3125,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.16.tgz",
"integrity": "sha512-FdF+y48ND13eAyM2mQn+85kvAqNlvRWROy2rhgA463X/0Gv4vGtazZtfaF1mRBLscYvtOlh4QnLjOWiFzX69OQ==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.19.tgz",
"integrity": "sha512-zo8EsSSwMZCY/e+fRnGaX02YGgvJD0yvhNi/9hpGPA2MJ9ZF8dPCw75UGfpI6OpQ/myR/dIYmogRvqiZD0TrXg==",
"cpu": [
"x64"
],
@@ -3086,9 +3142,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.16.tgz",
"integrity": "sha512-ddjqFM5e95AgLbUPtKIS7V1KTVlA0RUgCVr5mn+/IEhsdy3pr+Vuj/HZ7nlMN2FyxIwUktOH1LEPIQXO1v9y1Q==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.19.tgz",
"integrity": "sha512-O8ISAOfI1nj7AuBPd30FSF47z7f7lzlk5poQVGgiikvQuGEBFYvW8ki+UEnT+YJPnNGjQzn304klt4O9FUE7mA==",
"cpu": [
"x64"
],
@@ -3103,9 +3159,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.16.tgz",
"integrity": "sha512-tHdKWbFkFBI687iim3LIyIvyL7j66VvQYkDPss5Stn/qyE2tWkR1YD0dXmXPFKKqT/o5ZEJVsaRsWS2meQwGOA==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.19.tgz",
"integrity": "sha512-/lYCba5NO0yq4S0jGMH8QTBtkvQxoXjoQWzF5BcWp9MjB9jxVRD1GQDNofkVjcOj30DWyhLIN/4Oe60JKzaONw==",
"cpu": [
"arm"
],
@@ -3120,9 +3176,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.16.tgz",
"integrity": "sha512-FJiLGAblPBMlBEdbOqw6kW6QcXDWAf/oJu7gYs9ZJZwmg+K+/zjFLxPTIY9izJUzxzc24FHvmseyA0IvJSl17w==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.19.tgz",
"integrity": "sha512-pm/apFMBnSbFb3O6nG5pHO1iqHhZrjGDFEjSm+ot9J17XXPvH9L90y5h+jgfHCE+Qdlsvy8qKEcU4VKxgjD+vA==",
"cpu": [
"arm64"
],
@@ -3140,9 +3196,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.16.tgz",
"integrity": "sha512-hrJpblMsrQ58U9veWpbXzkhV5zStunds6HWLprqq1qauvgBr1E6P+24kLj6lSLI9vjRn/RYpjprhkHQPKGWpNg==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.19.tgz",
"integrity": "sha512-K1SmQp3FFichn8oqrNIwvFoZ5wcvjC7goBzpaMNvLf9FZc+QB/QvLmnpJZgAAGHiD980tP8FZZnXk2K94rvlxg==",
"cpu": [
"arm64"
],
@@ -3160,9 +3216,9 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.16.tgz",
"integrity": "sha512-qJd/dC6j6CwLG9/s/RP2nb1RZ0GIgj7Ioj7x/SYa3RzjVrlzrmH0fhIOpfvqQDeIsdP0loi+kHaMhJzY1PBIaA==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.19.tgz",
"integrity": "sha512-I0xwD57jP/3A7+5UPZin2w8Y7XHEmEM5zqpYZOcQ45MlNW6tUJUziymV3yRr7rL4vwyfEcRsRksC4a7Kc08p5Q==",
"cpu": [
"ppc64"
],
@@ -3180,9 +3236,9 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.16.tgz",
"integrity": "sha512-Vx7Xg5uUJuNcujJjI70OICaH0EYj80BtOLZTSN7uOYg8QOLlyKnaG0IW7Fn/J0IpJsaKzmDKMhOQv1JBM9m9vg==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.19.tgz",
"integrity": "sha512-BrtEHT24SNXj9HiXqy+s/kXiSiQGt6EKCPUlRirAoIioVxKGnbXBUEkbZASAei7iZJB0/Ynm+0JP00Ipx7AnSw==",
"cpu": [
"s390x"
],
@@ -3200,9 +3256,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.16.tgz",
"integrity": "sha512-TNKhgE3uJTsTzatUSSYt0Kgo+Oa2uSlSjz4r1aEIea7HX6ULBym4fAlYUhG1i6pjNf2njfCUAfKwr6C3E9Xg1w==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.19.tgz",
"integrity": "sha512-rm75/9DkHKeU2WnA8w4X94WGYgLlDCFCfkQC+4bguVJmC/jvDRv2B+2wLOYhyE0fVuv7//Ckk39OoOufyCu76Q==",
"cpu": [
"x64"
],
@@ -3220,9 +3276,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.16.tgz",
"integrity": "sha512-7chXdpjXZ8qG0+mIxECN3cT9kvhZ3/oZhMX6Pp876P7CDrIy8nJswva6Mq357JR7eYXfuskwZxMeV1SzoZdHrA==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.19.tgz",
"integrity": "sha512-irh/4+iYFM4GAzpluDErgjz89bj6NDtqqMAG/6O1LG38Ed3NpyebzCpcVNkFW8uhy+/3/BocUvnXDR33CXWe9A==",
"cpu": [
"x64"
],
@@ -3240,9 +3296,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.16.tgz",
"integrity": "sha512-Kwjt+7uNTJK5/MDkGWGVO2KQPnj8yAV75dMpfwyHZrJSXWHrniOB0aUEWlVYf3H1hOqFLIStLdM5QXq6bnKmOQ==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.19.tgz",
"integrity": "sha512-+KWTWNoPt+CfSq94CiceWu3yPE+QzkaG+phPyz9G60tmZLwfQkcA4AeksafzvJAw4pvg1mcd5a1TkUqVv0s4JA==",
"cpu": [
"arm64"
],
@@ -3256,10 +3312,29 @@
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-wasm32-wasi": {
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.2.19.tgz",
"integrity": "sha512-7EH+skOsl5qpWnK0vzamgJFhOv1cwxQpsxm8tI883K9fur5c27p8q87fzEO6BbBPabA2/+s/zR63/pLc137iOg==",
"cpu": [
"wasm32"
],
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "2.0.0-beta.2",
"@emnapi/runtime": "2.0.0-beta.2",
"@napi-rs/wasm-runtime": "1.2.6"
},
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=23.5.0"
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.16.tgz",
"integrity": "sha512-/LH6WfjDRXXJz/p5xl4TmU+jpcnwcxbKEQBW/GZE0JHfN0lGIyTtsUEGaQ/q/GikkGJ2yLKe6pwm7AnEWO8pdw==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.19.tgz",
"integrity": "sha512-3Q1Yg1Q7tEvBWoZf5rFvG/GKZQPgwfru+p4wBajDfJFBs0SHCpL++dfTjW9gyAKd1T7ZpIYvdaUax7Pa/IhFVQ==",
"cpu": [
"arm64"
],
@@ -3274,9 +3349,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.16.tgz",
"integrity": "sha512-1lv1KvX0Z871zfk7vw+dnXMMnHNyDwMzWG8Kv3HxDXMOhzCX4SHc7HK319fInP0i+0p9D8brkfzL7pJOaGl+tw==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.19.tgz",
"integrity": "sha512-y/sqRRc6d7hECzOFc5n3LSeqctmIp9mMjY1Kq4s14uLPd/LZ4z7i1zrW6fMtp2mPeuQIC4e6ULMcwQzMomqQag==",
"cpu": [
"x64"
],
@@ -3549,6 +3624,17 @@
"@swc/counter": "^0.1.3"
}
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.4",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz",
"integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@types/debug": {
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
@@ -3675,17 +3761,17 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.72.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.72.1.tgz",
"integrity": "sha512-5CokTWXpmbylfFabsbvHC0CAw3XLaGnoa+nFJX5FR592h/LoZsENUE6p/rg5xvkKrFEXz6pYmgI29AgXc2N3BQ==",
"version": "8.72.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.72.2.tgz",
"integrity": "sha512-222PKFXKihmFpi/pS5njoBgdAqsPhj2/GBmFbrNw8Tq8gEG9Sv+kSlKFy4u2q+mJv9ZzJgM2Gkv/bUzRZwZw5g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.12.2",
"@typescript-eslint/scope-manager": "8.72.1",
"@typescript-eslint/type-utils": "8.72.1",
"@typescript-eslint/utils": "8.72.1",
"@typescript-eslint/visitor-keys": "8.72.1",
"@typescript-eslint/scope-manager": "8.72.2",
"@typescript-eslint/type-utils": "8.72.2",
"@typescript-eslint/utils": "8.72.2",
"@typescript-eslint/visitor-keys": "8.72.2",
"ignore": "^7.0.5",
"natural-compare": "^1.4.0",
"ts-api-utils": "^2.5.0"
@@ -3698,7 +3784,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.72.1",
"@typescript-eslint/parser": "^8.72.2",
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
@@ -3714,16 +3800,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.72.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.72.1.tgz",
"integrity": "sha512-ZGwZP4OCfyA0AQTpEAMWz+rtX01DRhQWW5hB0ucKDEIX5U7jdmPSaMx94rQfaPAQzIYz2BimOgKOJtjfpRFQ9g==",
"version": "8.72.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.72.2.tgz",
"integrity": "sha512-llP+T1YuJOLlxEciP6OyQGtz5SMxyn+9LA2BvZw5RmToGVa1y4beEDVQL6Gje7T5WFYh19WREjDCN9Ev4gm5AA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.72.1",
"@typescript-eslint/types": "8.72.1",
"@typescript-eslint/typescript-estree": "8.72.1",
"@typescript-eslint/visitor-keys": "8.72.1",
"@typescript-eslint/scope-manager": "8.72.2",
"@typescript-eslint/types": "8.72.2",
"@typescript-eslint/typescript-estree": "8.72.2",
"@typescript-eslint/visitor-keys": "8.72.2",
"debug": "^4.4.3"
},
"engines": {
@@ -3739,14 +3825,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.72.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.72.1.tgz",
"integrity": "sha512-fAuVMkwdO/huI8nyo/WKnNKf6Gag93S56Rh3Ts5mGcavwSdaN8tRg1gCeIGX8PF+nzp1BjGfO4gHhGzW1J6jTg==",
"version": "8.72.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.72.2.tgz",
"integrity": "sha512-6y5dLTZdHhI8Eh3yIQ/bfQhRKwn5e+TpJj64o18Cngc0iW/BCrVvrr6zGYqXPnYH31u6YXAYiFh0+qxX5mybJw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.72.1",
"@typescript-eslint/types": "^8.72.1",
"@typescript-eslint/tsconfig-utils": "^8.72.2",
"@typescript-eslint/types": "^8.72.2",
"debug": "^4.4.3"
},
"engines": {
@@ -3761,14 +3847,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.72.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.72.1.tgz",
"integrity": "sha512-lC/t6eMCZZQAAKZEYGL6RDVSuFehpdmMT6h+nJIjIa6oylN/PwDgDVtEIbB7O9xFfZYtZAYLoLlDNWKJhtF+uQ==",
"version": "8.72.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.72.2.tgz",
"integrity": "sha512-iIoMJ6/8c1/QpFNyFjU0d1YzPco4MsZjQw0k3V+vfzNWAFmUmvhlyMoYEoYYDQZXc+J6Gi1X5XcxlzW2taIIGQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.72.1",
"@typescript-eslint/visitor-keys": "8.72.1"
"@typescript-eslint/types": "8.72.2",
"@typescript-eslint/visitor-keys": "8.72.2"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3779,9 +3865,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.72.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.72.1.tgz",
"integrity": "sha512-e4D5R5vpeAEPBxT2BAzsFQL3sbwyeeeo5+aczgNSRK1V734jE7pTqaGFU+fXapWEQwnZbpFaTn32+Z8DwS6OTg==",
"version": "8.72.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.72.2.tgz",
"integrity": "sha512-+h3iCgZXuTNtoLXcnnjP12ZUHLMqoUcIHPfQrFEXTUqvb9dF8QK9b5nv5VtSo5fLkdHuks8ehbTzZj3rImAm9w==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3796,15 +3882,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.72.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.72.1.tgz",
"integrity": "sha512-iY2wlmyrXJmx4yxUGP3aYCFF3cA/6ShddOKmLU2dx7LnEykbAM8WThlxZEfd1NddqceF6eJmZUayxx9oKIYNlg==",
"version": "8.72.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.72.2.tgz",
"integrity": "sha512-STfkR38qIdhfkLpgtXnUjAzsNy4xouqiBO0lrVc/Wpk9Kem5/aHXUx1J8dGC16zpdYN1+yc2ILQ28er1kXX7ag==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.72.1",
"@typescript-eslint/typescript-estree": "8.72.1",
"@typescript-eslint/utils": "8.72.1",
"@typescript-eslint/types": "8.72.2",
"@typescript-eslint/typescript-estree": "8.72.2",
"@typescript-eslint/utils": "8.72.2",
"debug": "^4.4.3",
"ts-api-utils": "^2.5.0"
},
@@ -3821,9 +3907,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.72.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.72.1.tgz",
"integrity": "sha512-LmE5IhQet1BWOWEXqBrVtBPsMAOQ6Nzk35d+WLh3mk4EUF33sqlfxjio1MdYeSvKSG5skactKKuixBIKXhL4Rw==",
"version": "8.72.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.72.2.tgz",
"integrity": "sha512-Xfn37OLZx03+PCU7qWx9DTv2FCRh1E6fk2+wONeNY15HXIwtcwuveaSwVpGAUptkNmPQegRxW5JyRel9T7wrog==",
"dev": true,
"license": "MIT",
"engines": {
@@ -3835,16 +3921,16 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.72.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.72.1.tgz",
"integrity": "sha512-DAlUoUV93hIvrtIQLwCbmZQYVZwOMhiHJrOVUUIWicFZaIYjTRqlY3I/NZevfkLmSqkNHRu2qYDcq9lrn16oOQ==",
"version": "8.72.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.72.2.tgz",
"integrity": "sha512-pQEGqIK/cqEvKivVWaFLYpe7XYejFxfuAOi4mu1oAWFvIo36aRHI9esRFTUxcIy/z8N482RJyH+LpZB0ZIU7Ow==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.72.1",
"@typescript-eslint/tsconfig-utils": "8.72.1",
"@typescript-eslint/types": "8.72.1",
"@typescript-eslint/visitor-keys": "8.72.1",
"@typescript-eslint/project-service": "8.72.2",
"@typescript-eslint/tsconfig-utils": "8.72.2",
"@typescript-eslint/types": "8.72.2",
"@typescript-eslint/visitor-keys": "8.72.2",
"debug": "^4.4.3",
"minimatch": "^10.2.2",
"semver": "^7.7.3",
@@ -3863,16 +3949,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.72.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.72.1.tgz",
"integrity": "sha512-5v5liypurJBFHCBNqh/RjsymAD+VWcE0djuGY/BUqp5fC3Qn+luWDEksjuTtWjPn6i5zQlUtkZCRG/CYG0yCkA==",
"version": "8.72.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.72.2.tgz",
"integrity": "sha512-hv3eRYbpPbbpezuax1EfGrzpa3C4ySgNZkPUkLOWOAjU8HsOKowEDAr8C7nsNl1C+SBSKDpvno6VKHnlhvp9iA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/scope-manager": "8.72.1",
"@typescript-eslint/types": "8.72.1",
"@typescript-eslint/typescript-estree": "8.72.1"
"@typescript-eslint/scope-manager": "8.72.2",
"@typescript-eslint/types": "8.72.2",
"@typescript-eslint/typescript-estree": "8.72.2"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3887,13 +3973,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.72.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.72.1.tgz",
"integrity": "sha512-SB8xqipSzZHUePCCLNeYxqnxq83X61/qzNhjQGbTfjxGZIeOEP7tBgWYX05Tcd6CgE02WdTJnh5b8kMZJ/+bnQ==",
"version": "8.72.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.72.2.tgz",
"integrity": "sha512-NAGrjn0Pitjqzq/GkgMC4kBYm233PQ8GffyQ9+kTA3AlNQYEzOf25EIM02BAHZv5FRJSpHpJk+OGUxVkOB93GQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.72.1",
"@typescript-eslint/types": "8.72.2",
"eslint-visitor-keys": "^5.0.0"
},
"engines": {
@@ -3911,9 +3997,9 @@
"license": "ISC"
},
"node_modules/@vitejs/plugin-react": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.2.1.tgz",
"integrity": "sha512-9KyWCij3r0+qLG3Q62vBmv0VyevmYibAtxaCIVK9o0k6CXgaajZRIe8tpfchQcdZArJ5b/7OrIz4W3b2uxVRDA==",
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.3.0.tgz",
"integrity": "sha512-H9E955aVvD2F4zNHCw4eIZbIfF6xKldlh7MNu+hSU7xwQzh65lI/N+CJWqmfhi0PEjDXIt/H7T7J/4wU63KJiw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4075,18 +4161,18 @@
}
},
"node_modules/apex-commons": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/apex-commons/-/apex-commons-0.8.3.tgz",
"integrity": "sha512-WdU5lblyL1ByI/nkFOJkSgJYenCsHmay0P3UIcnSeUiCyF1b30SvPSZLbyxOCUOry8IlL7M0/3GvsGbZTMm8FA==",
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/apex-commons/-/apex-commons-0.10.0.tgz",
"integrity": "sha512-mBXqK6wykMnWV4TzT9636k4aaeEvE8PmBIfa2bHx91eQoit8RjJrOlBpGxNGz1Bmt6gq2U3CoqPAUZftBbYdLQ==",
"license": "SEE LICENSE IN LICENSE.md"
},
"node_modules/apexcharts": {
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-8.7.0.tgz",
"integrity": "sha512-HHpMlM4QvLXFLL6hyn1iivF+3IyoCrBEXYcV9elJuT+Dc4At9P5+RAaSYA1onOrcXg59+VTAsnyUDdm4GRhUfA==",
"version": "8.8.1",
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-8.8.1.tgz",
"integrity": "sha512-Aeha39Wdrg4pTdJTpM+QsQemLoCRpwC5tEgwHYzbvtRxo4E9EmQ6zdTZbhcYia24HyPhqx3IATd1evgS0lRtzw==",
"license": "SEE LICENSE IN LICENSE.md",
"dependencies": {
"apex-commons": "^0.8.3"
"apex-commons": "^0.10.0"
},
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
@@ -4131,9 +4217,9 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/attr-accept": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-4.0.0.tgz",
"integrity": "sha512-hmCnJClmeKNKlsBHgbM8yLZRiQZ4/20UXbLJb6OUT16eWcM5/xNZerr80a/zCYob768KIGq++aLrQNTuwPsIOQ==",
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-4.0.1.tgz",
"integrity": "sha512-rOKAGOwbnTUvwTL8rNA0hWnU3uX9tOI20di4KjR5I1ez/RJmnA37W3zz4EMxGC2YnI289yPjRy63CdAZeQFZ4Q==",
"license": "MIT",
"engines": {
"node": ">= 22"
@@ -4177,12 +4263,12 @@
}
},
"node_modules/axios": {
"version": "1.21.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.21.0.tgz",
"integrity": "sha512-YCzXMB53Q9PZBsh7yN7wtapit4WepwcHS2fP8QhGsBxSz5YtgYobV7GGlWlAJi+RxfU77GQTQi2DSLUma42EYA==",
"version": "1.21.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.21.1.tgz",
"integrity": "sha512-6eI5Q/Y4u/Um9dDlsRoq5pY2rxnUTv+L4PpLitnbk/i4jb3MxLZ5CsN5Ih3IysbeRYK4pR+P7qfMsOQl9fwwFQ==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.16.0",
"follow-redirects": "^1.16.2",
"form-data": "^4.0.6",
"https-proxy-agent": "^5.0.1",
"proxy-from-env": "^2.1.0"
@@ -5596,12 +5682,12 @@
}
},
"node_modules/framer-motion": {
"version": "14.10.0",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-14.10.0.tgz",
"integrity": "sha512-e8MjIet+IJKohYxBj8TAk9Mg5cD0HiOX87qde1YTPL67gI2ao074O6yBtQnIcWCk+bTX7XX4litepufLiYPrLg==",
"version": "14.12.0",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-14.12.0.tgz",
"integrity": "sha512-4m+10xYan0ic8nFI8zyB5hJkW5F8F4Rer617UecOs8esw0he9unDgXaEZ0rUX7oztz1w4iuvyPWhk4jltMX5iw==",
"license": "MIT",
"dependencies": {
"motion-dom": "14.10.0",
"motion-dom": "14.12.0",
"motion-utils": "14.7.0",
"tslib": "^2.4.0"
},
@@ -6996,9 +7082,9 @@
}
},
"node_modules/lucide-react": {
"version": "1.70.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.70.0.tgz",
"integrity": "sha512-gCTPPN1MlQUBtUgdsUv+0CAUj+X8hifRUiRBw4E5v7lvDIOIpx0EZezdGQMSimS8pKA6esvullJub4GYr0mCHA==",
"version": "1.72.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.72.0.tgz",
"integrity": "sha512-fbF5i1GU8GODETa3Q7rXqrJrcx3iPYrQtxthom103zGx+Z0BGwKYAW9s+9435ohYJ7hQm0av0PoZcqI1CRwNrg==",
"license": "ISC",
"peerDependencies": {
"@types/react": "*",
@@ -8073,9 +8159,9 @@
}
},
"node_modules/motion-dom": {
"version": "14.10.0",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-14.10.0.tgz",
"integrity": "sha512-k0LwJZ1R5xCvqrScszO4ywuZP52lRcT3QO0IUovgczom9ZHb0BaRLOX6M2O40q7TrSLvzX/fIHHmDm8TUMt3fg==",
"version": "14.12.0",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-14.12.0.tgz",
"integrity": "sha512-YnAY17g8mXRVoXGZbH+v/O2SkNBJqz2TMuqL9yek9Omfhsdjw7gJwNgI5nsGZFpbbm3vINyOo+VtGPryIslatw==",
"license": "MIT",
"dependencies": {
"motion-utils": "14.7.0"
@@ -8714,9 +8800,9 @@
}
},
"node_modules/prettier": {
"version": "3.9.10",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.10.tgz",
"integrity": "sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==",
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.10.1.tgz",
"integrity": "sha512-70vp3ZUYoPlFbF9uiTdXHUUCGEIp7d4+6p+8DYbkm2jP7x/3i/paXVF6dopSI3zd/CEitrZN1rc4i85xWmmXlg==",
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
@@ -9015,12 +9101,12 @@
}
},
"node_modules/react-dropzone": {
"version": "20.1.4",
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-20.1.4.tgz",
"integrity": "sha512-LyjT9iQySNkJGC6yL+XTlmfTRxHhPx0eSoTh+WlR0Ge+W35rLcRH/oH7w5bzijfPHeCFcLnO9G7i+hX+LINAAA==",
"version": "20.2.0",
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-20.2.0.tgz",
"integrity": "sha512-FRlcSp6UFT9Etg57U4+g6O6qF/jH7sUh2Hz67lQlrZJa/3FjfAFvw7DwDSItiG2vBUXTdh2wroEwXPPg7o/jkg==",
"license": "MIT",
"dependencies": {
"attr-accept": "^4.0.0",
"attr-accept": "^4.0.1",
"file-selector": "^5.0.1"
},
"engines": {
@@ -9055,9 +9141,9 @@
}
},
"node_modules/react-hook-form": {
"version": "7.91.0",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.91.0.tgz",
"integrity": "sha512-5BEBUnCBwa28AFXYTYmOaB/+CSpJ/+CWVASHEvIGUzFdHyUiPHeRhG7r5hOZ8nZi0yP7pbHdFNXO/p31EKTJFQ==",
"version": "7.92.0",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.92.0.tgz",
"integrity": "sha512-O5ILyY5fYHN7N13Pfxpe55KboyNFsJdM03BHpshQkClJd8raugiAUGY7VjifITrfhVxyx81ko25azXCoayhf1w==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
@@ -9495,9 +9581,9 @@
}
},
"node_modules/rolldown": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.16.tgz",
"integrity": "sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw==",
"version": "1.2.19",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.19.tgz",
"integrity": "sha512-QIiV1J9d0pzN4jeeTH4sjPy0AZ6cAy2cG0ExeoE1+U8Rvj+hR3gYkokx0YbeUNGlZqiQJJ2utcETe0oKXwzsuA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -9511,21 +9597,22 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
"@rolldown/binding-android-arm-eabi": "1.2.16",
"@rolldown/binding-android-arm64": "1.2.16",
"@rolldown/binding-darwin-arm64": "1.2.16",
"@rolldown/binding-darwin-x64": "1.2.16",
"@rolldown/binding-freebsd-x64": "1.2.16",
"@rolldown/binding-linux-arm-gnueabihf": "1.2.16",
"@rolldown/binding-linux-arm64-gnu": "1.2.16",
"@rolldown/binding-linux-arm64-musl": "1.2.16",
"@rolldown/binding-linux-ppc64-gnu": "1.2.16",
"@rolldown/binding-linux-s390x-gnu": "1.2.16",
"@rolldown/binding-linux-x64-gnu": "1.2.16",
"@rolldown/binding-linux-x64-musl": "1.2.16",
"@rolldown/binding-openharmony-arm64": "1.2.16",
"@rolldown/binding-win32-arm64-msvc": "1.2.16",
"@rolldown/binding-win32-x64-msvc": "1.2.16"
"@rolldown/binding-android-arm-eabi": "1.2.19",
"@rolldown/binding-android-arm64": "1.2.19",
"@rolldown/binding-darwin-arm64": "1.2.19",
"@rolldown/binding-darwin-x64": "1.2.19",
"@rolldown/binding-freebsd-x64": "1.2.19",
"@rolldown/binding-linux-arm-gnueabihf": "1.2.19",
"@rolldown/binding-linux-arm64-gnu": "1.2.19",
"@rolldown/binding-linux-arm64-musl": "1.2.19",
"@rolldown/binding-linux-ppc64-gnu": "1.2.19",
"@rolldown/binding-linux-s390x-gnu": "1.2.19",
"@rolldown/binding-linux-x64-gnu": "1.2.19",
"@rolldown/binding-linux-x64-musl": "1.2.19",
"@rolldown/binding-openharmony-arm64": "1.2.19",
"@rolldown/binding-wasm32-wasi": "1.2.19",
"@rolldown/binding-win32-arm64-msvc": "1.2.19",
"@rolldown/binding-win32-x64-msvc": "1.2.19"
}
},
"node_modules/run-async": {
@@ -9583,11 +9670,15 @@
"license": "MIT"
},
"node_modules/scroll-into-view-if-needed": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz",
"integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.2.0.tgz",
"integrity": "sha512-mnlrulRcw9gQR/HI0SlT56kmyCS/Z1tH/By43m8v8kHBjD8XecscweRcjKAk3kt4WLeyJQaSSQ8efy5f+r/dqA==",
"license": "MIT",
"dependencies": {
"compute-scroll-into-view": "^3.0.2"
},
"engines": {
"node": ">=26"
}
},
"node_modules/semver": {
@@ -10080,16 +10171,16 @@
}
},
"node_modules/typescript-eslint": {
"version": "8.72.1",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.72.1.tgz",
"integrity": "sha512-3Li9FfPmQp45nxEtFD1ZCrYFI3cYK5kpyNtmm1H7lMagm8u9sy6PTjy3ytwQZs7KNukOyKFHAZ6z6GjYFeaeXw==",
"version": "8.72.2",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.72.2.tgz",
"integrity": "sha512-c/+AKMW1KB81r28Sp0AAdcyXj5dzNmAsZFcsrxKbZTKmcBK2xIwBR31dcSYSDu3bKmzXe8GKrSxO+eDQ401Uzg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.72.1",
"@typescript-eslint/parser": "8.72.1",
"@typescript-eslint/typescript-estree": "8.72.1",
"@typescript-eslint/utils": "8.72.1"
"@typescript-eslint/eslint-plugin": "8.72.2",
"@typescript-eslint/parser": "8.72.2",
"@typescript-eslint/typescript-estree": "8.72.2",
"@typescript-eslint/utils": "8.72.2"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -10420,16 +10511,16 @@
}
},
"node_modules/vite": {
"version": "8.3.6",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.6.tgz",
"integrity": "sha512-UI7dK29NnS54Fk+mmo7o+jI6U8Fp7hDl+X5Q1k45WH+d4dSlbLVUETGQ+hfkNgvLQGyoElPj837nZyv4MBc0VA==",
"version": "8.3.8",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.8.tgz",
"integrity": "sha512-LzOn3h9rG/0vhLdXrV4yJ4QFA9eEoqCl+Yx+kGJd7aB9w6nfNgVHKSYwLPZUHrz4ZGa+t+kXjV9itvcHDLQouA==",
"dev": true,
"license": "MIT",
"dependencies": {
"lightningcss": "^1.33.0",
"picomatch": "^4.0.7",
"postcss": "^8.5.32",
"rolldown": "~1.2.16",
"rolldown": "~1.2.18",
"tinyglobby": "^0.2.17"
},
"bin": {
+12 -12
View File
@@ -22,7 +22,7 @@
},
"dependencies": {
"@cycjimmy/jsmpeg-player": "^7.0.0",
"@hookform/resolvers": "^7.0.3",
"@hookform/resolvers": "^7.0.6",
"@radix-ui/react-alert-dialog": "^1.2.1",
"@radix-ui/react-aspect-ratio": "^1.2.0",
"@radix-ui/react-checkbox": "^1.4.1",
@@ -49,15 +49,15 @@
"@rjsf/shadcn": "^6.11.1",
"@rjsf/utils": "^6.10.1",
"@rjsf/validator-ajv8": "^6.11.1",
"apexcharts": "^8.7.0",
"axios": "^1.21.0",
"apexcharts": "^8.8.1",
"axios": "^1.21.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"copy-to-clipboard": "^4.0.2",
"date-fns": "^4.4.0",
"date-fns-tz": "^3.2.0",
"framer-motion": "^14.10.0",
"framer-motion": "^14.12.0",
"hls.js": "^1.7.4",
"i18next": "^26.7.2",
"i18next-http-backend": "^4.0.10",
@@ -65,7 +65,7 @@
"js-yaml": "^5.5.0",
"konva": "^10.9.1",
"lodash": "^4.18.1",
"lucide-react": "^1.70.0",
"lucide-react": "^1.72.0",
"monaco-yaml": "^5.5.1",
"next-themes": "^0.4.6",
"nosleep.js": "^0.12.0",
@@ -74,9 +74,9 @@
"react-day-picker": "^10.0.2",
"react-device-detect": "^2.2.3",
"react-dom": "^19.3.1",
"react-dropzone": "^20.1.4",
"react-dropzone": "^20.2.0",
"react-grid-layout": "^2.3.0",
"react-hook-form": "^7.91.0",
"react-hook-form": "^7.92.0",
"react-i18next": "^17.1.13",
"react-icons": "^5.7.0",
"react-konva": "^19.3.2",
@@ -85,7 +85,7 @@
"react-swipeable": "^7.0.2",
"react-zoom-pan-pinch": "3.4.4",
"remark-gfm": "^4.0.0",
"scroll-into-view-if-needed": "^3.1.0",
"scroll-into-view-if-needed": "^3.2.0",
"sonner": "^2.0.8",
"swr": "^2.5.1",
"tailwind-merge": "^2.6.1",
@@ -105,7 +105,7 @@
"@types/node": "^26.6.6",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"@vitejs/plugin-react": "^6.2.1",
"@vitejs/plugin-react": "^6.3.0",
"autoprefixer": "^10.6.2",
"esbuild": "^0.28.2",
"eslint": "^10.14.0",
@@ -118,12 +118,12 @@
"monaco-editor": "^0.53.0",
"patch-package": "^8.0.1",
"postcss": "^8.5.32",
"prettier": "^3.9.10",
"prettier": "^3.10.1",
"prettier-plugin-tailwindcss": "^0.8.1",
"tailwindcss": "^3.4.19",
"typescript": "^6.0.3",
"typescript-eslint": "^8.72.1",
"vite": "^8.3.6"
"typescript-eslint": "^8.72.2",
"vite": "^8.3.8"
},
"overrides": {
"@radix-ui/react-compose-refs": "1.1.2",
+3
View File
@@ -173,6 +173,9 @@
"label": "Play in background",
"tips": "Enable this option to continue streaming when the player is hidden."
},
"metadata": {
"loading": "Fetching stream metadata…"
},
"mode": "Streaming Technology",
"technology": {
"description": "Choose your preferred streaming technology. Frigate may still fall back to low bandwidth mode on playback or network errors.",
@@ -240,14 +240,14 @@ const detect: SectionConfigOverrides = {
},
replay: {
restartRequired: [],
fieldOrder: ["width", "height", "fps"],
fieldOrder: ["scene", "width", "height", "fps"],
fieldGroups: {
resolution: ["width", "height", "fps"],
},
hiddenFields: [
"enabled",
"enabled_in_config",
"scene",
hideSceneWithoutChoice,
"min_initialized",
"max_disappeared",
"annotation_offset",
+34
View File
@@ -248,10 +248,21 @@ export default function LivePlayer({
// remounts them in the same render and the new player is hidden until it
// plays. jsmpeg plays the camera, not the stream, and keeps playing
const [renderedStream, setRenderedStream] = useState(streamName);
const [switchFrame, setSwitchFrame] = useState<HTMLCanvasElement>();
if (renderedStream !== streamName) {
setRenderedStream(streamName);
if (preferredLiveMode !== "jsmpeg") {
setLiveReady(false);
// the old video is still in the DOM until this render commits, so its
// last frame can cover the gap until the new stream plays. Tiles that
// show the camera still already cover it
if (!showStillWithoutActivity) {
const frame = captureVideoFrame(overlayRef.current);
if (frame) {
setSwitchFrame(frame);
}
}
}
}
@@ -263,6 +274,7 @@ export default function LivePlayer({
const playerIsPlaying = useCallback(() => {
setLiveReady(true);
setSwitchFrame(undefined);
}, []);
// enabled states
@@ -416,6 +428,12 @@ export default function LivePlayer({
</div>
)}
{player}
{switchFrame && !liveReady && (
<div
className="absolute inset-0"
ref={(node) => node?.replaceChildren(switchFrame)}
/>
)}
{cameraEnabled &&
!offline &&
(!showStillWithoutActivity || isReEnabling) &&
@@ -582,3 +600,19 @@ export default function LivePlayer({
</div>
);
}
function captureVideoFrame(
container: HTMLElement | null,
): HTMLCanvasElement | undefined {
const video = container?.querySelector("video");
if (!video || video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) {
return undefined;
}
const canvas = document.createElement("canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.className = "size-full object-contain";
canvas.getContext("2d")?.drawImage(video, 0, 0);
return canvas;
}
+74 -38
View File
@@ -12,11 +12,15 @@ import { useTranslation } from "react-i18next";
import { LuInfo } from "react-icons/lu";
import { MdOutlineRestartAlt } from "react-icons/md";
import { Button } from "@/components/ui/button";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
// Radix Select needs a non-empty value. Auto itself is stored as the
// absence of a pinned stream
const AUTO_VALUE = "__frigate_auto__";
const GRID_ITEM =
"h-auto min-h-10 min-w-[calc((100%-1rem)/3)] grow whitespace-normal bg-secondary [overflow-wrap:anywhere] px-2 py-2 text-secondary-foreground hover:bg-secondary hover:text-secondary-foreground data-[state=on]:bg-selected data-[state=on]:text-selected-foreground data-[state=on]:hover:bg-selected data-[state=on]:hover:text-selected-foreground";
type LiveStreamSelectProps = {
// friendly name to go2rtc stream name, in ladder order
streams: Record<string, string>;
@@ -29,6 +33,7 @@ type LiveStreamSelectProps = {
// sends auto back to the top of the ladder
onRetry: () => void;
disabled: boolean;
variant?: "select" | "grid";
};
export default function LiveStreamSelect({
@@ -40,6 +45,7 @@ export default function LiveStreamSelect({
onSelect,
onRetry,
disabled,
variant = "select",
}: LiveStreamSelectProps) {
const { t } = useTranslation(["views/live"]);
@@ -52,48 +58,78 @@ export default function LiveStreamSelect({
const auto = autoAvailable && pinnedStream == undefined;
const value = auto ? AUTO_VALUE : (pinnedStream ?? playingStream);
const autoLabel = auto
? t("stream.auto.selected", {
stream:
autoReason === "floor"
? t("stream.auto.floorLabel")
: labelFor(playingStream),
})
: t("stream.auto.label");
return (
<>
<Select
value={value}
disabled={disabled}
onValueChange={(next) =>
onSelect(next === AUTO_VALUE ? undefined : next)
}
>
<SelectTrigger className="w-full">
<SelectValue>
{auto
? t("stream.auto.selected", {
stream:
autoReason === "floor"
? t("stream.auto.floorLabel")
: labelFor(playingStream),
})
: labelFor(value)}
</SelectValue>
</SelectTrigger>
{variant === "grid" &&
!autoAvailable &&
Object.keys(streams).length <= 1 ? (
<div className="text-sm">{labelFor(playingStream)}</div>
) : variant === "grid" ? (
<ToggleGroup
type="single"
className="flex flex-wrap items-stretch gap-2"
value={value}
disabled={disabled}
onValueChange={(next) => {
// radix sends an empty value when the active item is tapped again
if (next) {
onSelect(next === AUTO_VALUE ? undefined : next);
}
}}
>
{autoAvailable && (
<ToggleGroupItem value={AUTO_VALUE} className={GRID_ITEM}>
{autoLabel}
</ToggleGroupItem>
)}
{Object.entries(streams).map(([label, name]) => (
<ToggleGroupItem key={label} value={name} className={GRID_ITEM}>
{label}
</ToggleGroupItem>
))}
</ToggleGroup>
) : (
<Select
value={value}
disabled={disabled}
onValueChange={(next) =>
onSelect(next === AUTO_VALUE ? undefined : next)
}
>
<SelectTrigger className="w-full">
<SelectValue>{auto ? autoLabel : labelFor(value)}</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectGroup>
{autoAvailable && (
<SelectItem className="cursor-pointer" value={AUTO_VALUE}>
<span className="flex flex-col gap-0.5 whitespace-normal">
<span>{t("stream.auto.label")}</span>
<span className="text-xs text-muted-foreground">
{t("stream.auto.tips")}
<SelectContent>
<SelectGroup>
{autoAvailable && (
<SelectItem className="cursor-pointer" value={AUTO_VALUE}>
<span className="flex flex-col gap-0.5 whitespace-normal">
<span>{t("stream.auto.label")}</span>
<span className="text-xs text-muted-foreground">
{t("stream.auto.tips")}
</span>
</span>
</span>
</SelectItem>
)}
{Object.entries(streams).map(([label, name]) => (
<SelectItem key={label} className="cursor-pointer" value={name}>
{label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</SelectItem>
)}
{Object.entries(streams).map(([label, name]) => (
<SelectItem key={label} className="cursor-pointer" value={name}>
{label}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
)}
{auto && autoReason && !disabled && (
<div className="flex flex-col items-center gap-3">
+104 -81
View File
@@ -230,15 +230,16 @@ export default function LiveCameraView({
}
}, [streamNameLoaded, liveStreams, pinnedStream, clearPinnedStream]);
const { data: cameraMetadata } = useSWR<LiveStreamMetadata>(
isRestreamed ? `go2rtc/streams/${streamName}` : null,
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
revalidateIfStale: false,
dedupingInterval: 60000,
},
);
const { data: cameraMetadata, isLoading: cameraMetadataLoading } =
useSWR<LiveStreamMetadata>(
isRestreamed ? `go2rtc/streams/${streamName}` : null,
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
revalidateIfStale: false,
dedupingInterval: 60000,
},
);
const webRTCAvailability = useWebRTCAvailableForStream(
cameraMetadata,
@@ -847,6 +848,7 @@ export default function LiveCameraView({
onResetStream={resetStream}
supportsAudioOutput={supportsAudioOutput}
supports2WayTalk={supports2WayTalk}
streamMetadataLoading={cameraMetadataLoading}
cameraEnabled={cameraEnabled}
debug={debug}
setDebug={setDebug}
@@ -873,9 +875,12 @@ export default function LiveCameraView({
growClassName,
)}
ref={clickOverlayRef}
style={{
aspectRatio: constrainedAspectRatio,
}}
style={
{
aspectRatio: constrainedAspectRatio,
"--camera-aspect": cameraAspectRatio,
} as React.CSSProperties
}
>
{clickOverlay && overlaySize.width > 0 && (
<div
@@ -914,7 +919,10 @@ export default function LiveCameraView({
)}
<LivePlayer
key={camera.name}
className={fullscreen ? "rounded-none" : ""}
className={cn(
"aspect-[var(--camera-aspect)]",
fullscreen && "rounded-none",
)}
windowVisible
showStillWithoutActivity={false}
alwaysShowCameraName={false}
@@ -1007,6 +1015,7 @@ type FrigateCameraFeaturesProps = {
onResetStream: () => void;
supportsAudioOutput: boolean;
supports2WayTalk: boolean;
streamMetadataLoading: boolean;
cameraEnabled: boolean;
debug: boolean;
setDebug: (debug: boolean) => void;
@@ -1038,6 +1047,7 @@ function FrigateCameraFeatures({
onResetStream,
supportsAudioOutput,
supports2WayTalk,
streamMetadataLoading,
cameraEnabled,
debug,
setDebug,
@@ -1434,7 +1444,12 @@ function FrigateCameraFeatures({
!debug &&
isRestreamed && (
<div className="flex flex-row items-center gap-1 text-sm text-muted-foreground">
{supportsAudioOutput ? (
{streamMetadataLoading ? (
<>
<ActivityIndicator className="size-4" />
<div>{t("stream.metadata.loading")}</div>
</>
) : supportsAudioOutput ? (
<>
<LuCheck className="size-4 text-success" />
<div>{t("stream.audio.available")}</div>
@@ -1693,72 +1708,7 @@ function FrigateCameraFeatures({
<DrawerContent className="max-h-[75dvh] overflow-hidden rounded-2xl">
<div className="scrollbar-container mt-2 flex h-auto flex-col gap-2 overflow-y-auto px-2 py-4">
<>
{isAdmin && (
<>
<FilterSwitch
label={t("cameraSettings.camera")}
isChecked={enabledState == "ON"}
onCheckedChange={() =>
sendEnabled(enabledState == "ON" ? "OFF" : "ON")
}
/>
<FilterSwitch
label={t("cameraSettings.objectDetection")}
isChecked={detectState == "ON"}
onCheckedChange={() =>
sendDetect(detectState == "ON" ? "OFF" : "ON")
}
/>
{recordingEnabled && (
<FilterSwitch
label={t("cameraSettings.recording")}
isChecked={recordState == "ON"}
onCheckedChange={() =>
sendRecord(recordState == "ON" ? "OFF" : "ON")
}
/>
)}
<FilterSwitch
label={t("cameraSettings.snapshots")}
isChecked={snapshotState == "ON"}
onCheckedChange={() =>
sendSnapshot(snapshotState == "ON" ? "OFF" : "ON")
}
/>
{audioDetectEnabled && (
<FilterSwitch
label={t("cameraSettings.audioDetection")}
isChecked={audioState == "ON"}
onCheckedChange={() =>
sendAudio(audioState == "ON" ? "OFF" : "ON")
}
/>
)}
{audioDetectEnabled && transcriptionEnabled && (
<FilterSwitch
label={t("cameraSettings.transcription")}
disabled={audioState == "OFF"}
isChecked={transcriptionState == "ON"}
onCheckedChange={() =>
sendTranscription(
transcriptionState == "ON" ? "OFF" : "ON",
)
}
/>
)}
{autotrackingEnabled && (
<FilterSwitch
label={t("cameraSettings.autotracking")}
isChecked={autotrackingState == "ON"}
onCheckedChange={() =>
sendAutotracking(autotrackingState == "ON" ? "OFF" : "ON")
}
/>
)}
</>
)}
<div className="mt-3 flex flex-col gap-5">
<div className="mb-3 flex flex-col gap-5">
{!isRestreamed && (
<div className="flex flex-col gap-2 p-2">
<Label>{t("stream.title")}</Label>
@@ -1811,6 +1761,7 @@ function FrigateCameraFeatures({
onSelect={onSelectStream}
onRetry={onResetStream}
disabled={debug || forceLowBandwidth}
variant="grid"
/>
{debug && (
@@ -1826,7 +1777,12 @@ function FrigateCameraFeatures({
!debug &&
isRestreamed && (
<div className="mt-1 flex flex-row items-center gap-1 text-sm text-muted-foreground">
{supportsAudioOutput ? (
{streamMetadataLoading ? (
<>
<ActivityIndicator className="size-4" size={14} />
<div>{t("stream.metadata.loading")}</div>
</>
) : supportsAudioOutput ? (
<>
<LuCheck className="size-4 text-success" />
<div>{t("stream.audio.available")}</div>
@@ -1955,6 +1911,73 @@ function FrigateCameraFeatures({
</p>
</div>
)}
</div>
{isAdmin && (
<>
<FilterSwitch
label={t("cameraSettings.camera")}
isChecked={enabledState == "ON"}
onCheckedChange={() =>
sendEnabled(enabledState == "ON" ? "OFF" : "ON")
}
/>
<FilterSwitch
label={t("cameraSettings.objectDetection")}
isChecked={detectState == "ON"}
onCheckedChange={() =>
sendDetect(detectState == "ON" ? "OFF" : "ON")
}
/>
{recordingEnabled && (
<FilterSwitch
label={t("cameraSettings.recording")}
isChecked={recordState == "ON"}
onCheckedChange={() =>
sendRecord(recordState == "ON" ? "OFF" : "ON")
}
/>
)}
<FilterSwitch
label={t("cameraSettings.snapshots")}
isChecked={snapshotState == "ON"}
onCheckedChange={() =>
sendSnapshot(snapshotState == "ON" ? "OFF" : "ON")
}
/>
{audioDetectEnabled && (
<FilterSwitch
label={t("cameraSettings.audioDetection")}
isChecked={audioState == "ON"}
onCheckedChange={() =>
sendAudio(audioState == "ON" ? "OFF" : "ON")
}
/>
)}
{audioDetectEnabled && transcriptionEnabled && (
<FilterSwitch
label={t("cameraSettings.transcription")}
disabled={audioState == "OFF"}
isChecked={transcriptionState == "ON"}
onCheckedChange={() =>
sendTranscription(
transcriptionState == "ON" ? "OFF" : "ON",
)
}
/>
)}
{autotrackingEnabled && (
<FilterSwitch
label={t("cameraSettings.autotracking")}
isChecked={autotrackingState == "ON"}
onCheckedChange={() =>
sendAutotracking(autotrackingState == "ON" ? "OFF" : "ON")
}
/>
)}
</>
)}
<div className="mt-3 flex flex-col gap-5">
{isRestreamed &&
Object.values(camera.live.streams).length > 0 && (
<div className="flex flex-col gap-2">