Update deps (#24623)

* bump backend and frontend dependencies

Bumps every outdated dependency that passed testing, with the code changes each one needs.

- peewee 4.5 and peewee_migrate 2.3: use SqliteDatabase in place of the removed SqliteExtDatabase, and fix types for peewee's bundled stubs
- mypy 2.4.0: remove unused type ignores and add the annotations mypy 2 needs
- pandas 3.0: convert the motion activity index with as_unit("s"), because pandas 3 infers second resolution for whole-second timestamps and the old // 10**9 returned 1
- transformers 5.19: load Jina tokenizers from the saved directory with trust_remote_code off and use CLIPImageProcessor, since v5 removed CLIPFeatureExtractor and remote code pulled in torch
- tensorflow-cpu 2.21: pin protobuf to 7.36 in the TensorRT amd64 image, because the old 3.20.3 pin was installed over TensorFlow and broke its import
- pydantic 2.13 and google-genai 2.29: regenerate the API spec
- ruff 0.16: set an explicit lint select, since 0.16 widened the default rules
- react-router-dom 7: set useTransitions={false} on BrowserRouter to keep v6 update timing, which useSearchEffect relies on
- typescript 6: drop baseUrl and add node types in tsconfig
- radix: update to the latest release and remove the slot patch, which upstream now includes
- apexcharts 7.8: guard the tooltip formatter against null values
- cryptography 50, joserfc 1.7, pywebpush 2.5, openai 3.26, aiohttp, uvicorn, onvif-zeep-async, scipy, framer-motion 14, react-day-picker 10 and other minor and patch bumps

* silence httpx2 request logging

* redownload an incomplete jina tokenizer

An interrupted tokenizer download left the Hugging Face cache directory without the saved tokenizer files, and since the download is skipped whenever that directory exists, every restart failed to load the tokenizer. The Jina constructors now remove a tokenizer directory that has no tokenizer_config.json so the download runs again.
This commit is contained in:
Josh Hawkins
2026-10-10 15:01:33 -05:00
committed by GitHub
parent e8e6326d37
commit 79177db969
39 changed files with 953 additions and 1143 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
ruff == 0.15.20 ruff == 0.16.10
# types # types
types-peewee == 4.0.* types-peewee == 4.0.*
+29 -29
View File
@@ -1,66 +1,66 @@
aiofiles == 25.1.* aiofiles == 25.1.*
click == 8.5.* click == 8.5.*
# FastAPI # FastAPI
aiohttp == 3.12.* aiohttp == 3.14.*
starlette == 1.7.* starlette == 1.7.*
starlette-context == 0.5.* starlette-context == 0.5.*
fastapi[standard-no-fastapi-cloud-cli] == 0.142.* fastapi[standard-no-fastapi-cloud-cli] == 0.142.*
uvicorn == 0.52.* uvicorn == 0.54.*
slowapi == 0.1.* slowapi == 0.1.*
joserfc == 1.6.* joserfc == 1.7.*
cryptography == 46.0.* cryptography == 50.0.*
pathvalidate == 3.3.* pathvalidate == 3.3.*
markupsafe == 3.0.* markupsafe == 3.0.*
python-multipart == 0.0.31 python-multipart == 0.0.32
# Classification Model Training # Classification Model Training
tensorflow == 2.19.* ; platform_machine == 'aarch64' tensorflow == 2.19.* ; platform_machine == 'aarch64'
tensorflow-cpu == 2.19.* ; platform_machine == 'x86_64' tensorflow-cpu == 2.21.* ; platform_machine == 'x86_64'
# General # General
mypy == 1.6.1 mypy == 2.4.0
onvif-zeep-async == 4.0.* onvif-zeep-async == 4.3.*
paho-mqtt == 2.1.* paho-mqtt == 2.1.*
pandas == 2.2.* pandas == 3.0.*
peewee == 3.17.* peewee == 4.5.*
peewee_migrate == 1.14.* peewee_migrate == 2.3.*
psutil == 7.1.* psutil == 7.2.*
pydantic == 2.10.* pydantic == 2.13.*
git+https://github.com/fbcotter/py3nvml#egg=py3nvml git+https://github.com/fbcotter/py3nvml#egg=py3nvml
pytz == 2025.* pytz == 2026.*
pyzmq == 27.1.* pyzmq == 27.2.*
ruamel.yaml == 0.19.* ruamel.yaml == 0.19.*
tzlocal == 5.2 tzlocal == 5.4.4
requests == 2.33.* requests == 2.34.*
types-requests == 2.32.* types-requests == 2.33.*
norfair == 2.3.* norfair == 2.3.*
setproctitle == 1.3.* setproctitle == 1.3.*
ws4py == 0.5.* ws4py == 0.6.*
unidecode == 1.4.* unidecode == 1.4.*
titlecase == 2.4.* titlecase == 2.4.*
# Image Manipulation # Image Manipulation
numpy == 1.26.* numpy == 1.26.*
opencv-python-headless == 4.11.0.* opencv-python-headless == 4.11.0.*
opencv-contrib-python == 4.11.0.* opencv-contrib-python == 4.11.0.*
scipy == 1.16.* scipy == 1.17.*
# OpenVino & ONNX # OpenVino & ONNX
openvino == 2025.4.* openvino == 2025.4.*
onnxruntime == 1.30.* onnxruntime == 1.30.*
# Embeddings # Embeddings
transformers == 4.45.* transformers == 5.19.*
# Generative AI # Generative AI
google-genai == 1.58.* google-genai == 2.29.*
ollama == 0.6.* ollama == 0.6.*
openai == 1.65.* openai == 3.26.*
# push notifications # push notifications
py-vapid == 1.9.4 py-vapid == 1.9.4
pywebpush == 2.0.* pywebpush == 2.5.*
# alpr # alpr
pyclipper == 1.4.* pyclipper == 1.4.*
shapely == 2.0.* shapely == 2.2.*
rapidfuzz==3.12.* rapidfuzz==3.14.*
# HailoRT # HailoRT
argcomplete==3.7.* argcomplete==3.7.*
contextlib2==0.6.* contextlib2==21.6.*
future==0.18.* future==1.0.*
netaddr==1.3.* netaddr==1.3.*
netifaces==0.10.* netifaces==0.10.*
prometheus-client == 0.26.* prometheus-client == 0.26.*
@@ -71,6 +71,6 @@ tflite_runtime @ https://github.com/feranick/TFlite-builds/releases/download/v2.
sherpa-onnx==1.13.* sherpa-onnx==1.13.*
faster-whisper==1.2.* faster-whisper==1.2.*
librosa==0.11.* librosa==0.11.*
soundfile==0.13.* soundfile==0.14.*
# Memory profiling # Memory profiling
memray == 1.20.* memray == 1.20.*
+1 -1
View File
@@ -14,4 +14,4 @@ nvidia-nccl-cu12==2.26.2.post1; platform_machine == 'x86_64'
nvidia-nvjitlink-cu12==12.8.93; platform_machine == 'x86_64' nvidia-nvjitlink-cu12==12.8.93; platform_machine == 'x86_64'
onnx==1.16.*; platform_machine == 'x86_64' onnx==1.16.*; platform_machine == 'x86_64'
onnxruntime-gpu==1.24.*; platform_machine == 'x86_64' onnxruntime-gpu==1.24.*; platform_machine == 'x86_64'
protobuf==3.20.3; platform_machine == 'x86_64' protobuf==7.36.*; platform_machine == 'x86_64'
+24 -3
View File
@@ -1088,6 +1088,7 @@ paths:
application/json: application/json:
schema: schema:
type: object type: object
additionalProperties: true
title: Body title: Body
responses: responses:
'200': '200':
@@ -1131,6 +1132,7 @@ paths:
application/json: application/json:
schema: schema:
type: object type: object
additionalProperties: true
title: Body title: Body
responses: responses:
'200': '200':
@@ -1285,6 +1287,7 @@ paths:
application/json: application/json:
schema: schema:
type: object type: object
additionalProperties: true
title: Body title: Body
responses: responses:
'200': '200':
@@ -1644,6 +1647,7 @@ paths:
application/json: application/json:
schema: schema:
type: object type: object
additionalProperties: true
title: Body title: Body
responses: responses:
'200': '200':
@@ -1691,6 +1695,7 @@ paths:
application/json: application/json:
schema: schema:
type: object type: object
additionalProperties: true
title: Body title: Body
responses: responses:
'200': '200':
@@ -1738,6 +1743,7 @@ paths:
application/json: application/json:
schema: schema:
type: object type: object
additionalProperties: true
title: Body title: Body
responses: responses:
'200': '200':
@@ -1779,6 +1785,7 @@ paths:
application/json: application/json:
schema: schema:
type: object type: object
additionalProperties: true
title: Body title: Body
responses: responses:
'200': '200':
@@ -1862,6 +1869,7 @@ paths:
application/json: application/json:
schema: schema:
type: object type: object
additionalProperties: true
title: Body title: Body
responses: responses:
'200': '200':
@@ -3483,6 +3491,7 @@ paths:
application/json: application/json:
schema: schema:
type: object type: object
additionalProperties: true
title: Body title: Body
responses: responses:
'200': '200':
@@ -5652,6 +5661,7 @@ paths:
application/json: application/json:
schema: schema:
type: object type: object
additionalProperties: true
title: Response Create Trigger Embedding Trigger Embedding Post title: Response Create Trigger Embedding Trigger Embedding Post
'422': '422':
description: Validation Error description: Validation Error
@@ -5700,6 +5710,7 @@ paths:
application/json: application/json:
schema: schema:
type: object type: object
additionalProperties: true
title: Response Update Trigger Embedding Trigger Embedding title: Response Update Trigger Embedding Trigger Embedding
Camera Name Name Put Camera Name Name Put
'422': '422':
@@ -5742,6 +5753,7 @@ paths:
application/json: application/json:
schema: schema:
type: object type: object
additionalProperties: true
title: Response Delete Trigger Embedding Trigger Embedding title: Response Delete Trigger Embedding Trigger Embedding
Camera Name Name Delete Camera Name Name Delete
'422': '422':
@@ -5778,6 +5790,7 @@ paths:
application/json: application/json:
schema: schema:
type: object type: object
additionalProperties: true
title: Response Get Triggers Status Triggers Status Camera Name title: Response Get Triggers Status Triggers Status Camera Name
Get Get
'422': '422':
@@ -7574,6 +7587,7 @@ paths:
type: array type: array
items: items:
type: object type: object
additionalProperties: true
title: Response No Recordings Recordings Unavailable Get title: Response No Recordings Recordings Unavailable Get
'422': '422':
description: Validation Error description: Validation Error
@@ -7777,7 +7791,8 @@ components:
title: Update Topic title: Update Topic
config_data: config_data:
anyOf: anyOf:
- type: object - additionalProperties: true
type: object
- type: 'null' - type: 'null'
title: Config Data title: Config Data
skip_save: skip_save:
@@ -8125,6 +8140,7 @@ components:
tool_calls: tool_calls:
anyOf: anyOf:
- items: - items:
additionalProperties: true
type: object type: object
type: array type: array
- type: 'null' - type: 'null'
@@ -8420,6 +8436,7 @@ components:
- type: 'null' - type: 'null'
title: Model Type title: Model Type
data: data:
additionalProperties: true
type: object type: object
title: Data title: Data
type: object type: object
@@ -8491,7 +8508,8 @@ components:
default: true default: true
draw: draw:
anyOf: anyOf:
- type: object - additionalProperties: true
type: object
- type: 'null' - type: 'null'
title: Draw title: Draw
default: {} default: {}
@@ -8734,7 +8752,8 @@ components:
description: Error message for failed jobs description: Error message for failed jobs
results: results:
anyOf: anyOf:
- type: object - additionalProperties: true
type: object
- type: 'null' - type: 'null'
title: Results title: Results
description: Result metadata for completed jobs description: Result metadata for completed jobs
@@ -8984,6 +9003,7 @@ components:
- type: 'null' - type: 'null'
title: Base Url title: Base Url
provider_options: provider_options:
additionalProperties: true
type: object type: object
title: Provider Options title: Provider Options
type: object type: object
@@ -9524,6 +9544,7 @@ components:
type: string type: string
title: Tool Name title: Tool Name
arguments: arguments:
additionalProperties: true
type: object type: object
title: Arguments title: Arguments
type: object type: object
+1 -1
View File
@@ -702,7 +702,7 @@ def motion_activity(
df = df[df["camera"] != ""] df = df[df["camera"] != ""]
# change types for output # change types for output
df.index = df.index.astype(int) // (10**9) df.index = df.index.as_unit("s").astype(int)
normalized = df.reset_index().to_dict("records") normalized = df.reset_index().to_dict("records")
return JSONResponse(content=normalized) return JSONResponse(content=normalized)
+3 -3
View File
@@ -12,8 +12,8 @@ from pathlib import Path
import psutil import psutil
import uvicorn import uvicorn
from peewee import SqliteDatabase
from peewee_migrate import Router from peewee_migrate import Router
from playhouse.sqlite_ext import SqliteExtDatabase
from frigate.api.auth import hash_password from frigate.api.auth import hash_password
from frigate.api.fastapi_app import create_fastapi_app from frigate.api.fastapi_app import create_fastapi_app
@@ -186,7 +186,7 @@ class FrigateApp:
self.timeline_queue: Queue = mp.Queue() self.timeline_queue: Queue = mp.Queue()
def init_database(self) -> None: def init_database(self) -> None:
def vacuum_db(db: SqliteExtDatabase) -> None: def vacuum_db(db: SqliteDatabase) -> None:
logger.info("Running database vacuum") logger.info("Running database vacuum")
db.execute_sql("VACUUM;") db.execute_sql("VACUUM;")
@@ -197,7 +197,7 @@ class FrigateApp:
logger.error("Unable to write to /config to save DB state") logger.error("Unable to write to /config to save DB state")
# Migrate DB schema # Migrate DB schema
migrate_db = SqliteExtDatabase(self.config.database.path) migrate_db = SqliteDatabase(self.config.database.path)
# Run migrations # Run migrations
del logging.getLogger("peewee_migrate").handlers[:] del logging.getLogger("peewee_migrate").handlers[:]
+6 -6
View File
@@ -54,12 +54,12 @@ class PTZMetrics:
reset: Event reset: Event
def __init__(self) -> None: def __init__(self) -> None:
self.start_time = mp.Value("d", 0) # type: ignore[assignment] self.start_time = mp.Value("d", 0)
self.stop_time = mp.Value("d", 0) # type: ignore[assignment] self.stop_time = mp.Value("d", 0)
self.frame_time = mp.Value("d", 0) # type: ignore[assignment] self.frame_time = mp.Value("d", 0)
self.zoom_level = mp.Value("d", 0) # type: ignore[assignment] self.zoom_level = mp.Value("d", 0)
self.max_zoom = mp.Value("d", 0) # type: ignore[assignment] self.max_zoom = mp.Value("d", 0)
self.min_zoom = mp.Value("d", 0) # type: ignore[assignment] self.min_zoom = mp.Value("d", 0)
self.motor_stopped = mp.Event() self.motor_stopped = mp.Event()
self.reset = mp.Event() self.reset = mp.Event()
+2 -2
View File
@@ -6,7 +6,7 @@ import logging
import os import os
import queue import queue
import threading import threading
from collections.abc import Callable from collections.abc import Callable, Iterator
from dataclasses import dataclass from dataclasses import dataclass
from multiprocessing.synchronize import Event as MpEvent from multiprocessing.synchronize import Event as MpEvent
from typing import Any from typing import Any
@@ -72,7 +72,7 @@ class WebPushClient(Communicator):
# Pull keys from PEM or generate if they do not exist # Pull keys from PEM or generate if they do not exist
self.vapid = Vapid01.from_file(os.path.join(CONFIG_DIR, "notifications.pem")) self.vapid = Vapid01.from_file(os.path.join(CONFIG_DIR, "notifications.pem"))
users: list[dict[str, Any]] = ( users: Iterator[dict[str, Any]] = (
User.select(User.username, User.notification_tokens).dicts().iterator() User.select(User.username, User.notification_tokens).dicts().iterator()
) )
for user in users: for user in users:
@@ -580,8 +580,8 @@ class LicensePlateProcessingMixin:
boxes = [] boxes = []
scores = [] scores = []
for index in range(len(contours)): # type: ignore[arg-type] for index in range(len(contours)):
contour = contours[index] # type: ignore[index] contour = contours[index]
# get minimum bounding box (rotated rectangle) around the contour and the smallest side length. # get minimum bounding box (rotated rectangle) around the contour and the smallest side length.
points, sside = self._get_min_boxes(contour) points, sside = self._get_min_boxes(contour)
@@ -1222,7 +1222,7 @@ class LicensePlateProcessingMixin:
"""Look for license plates in image.""" """Look for license plates in image."""
self.metrics.alpr_pps.value = self.plates_rec_second.eps() self.metrics.alpr_pps.value = self.plates_rec_second.eps()
self.metrics.yolov9_lpr_pps.value = self.plates_det_second.eps() self.metrics.yolov9_lpr_pps.value = self.plates_det_second.eps()
camera = obj_data if dedicated_lpr else obj_data["camera"] camera: str = obj_data if dedicated_lpr else obj_data["camera"] # type: ignore[assignment]
current_time = int(datetime.datetime.now().timestamp()) current_time = int(datetime.datetime.now().timestamp())
debug_frame_id = int(datetime.datetime.now().timestamp() * 1000) debug_frame_id = int(datetime.datetime.now().timestamp() * 1000)
+1 -1
View File
@@ -365,7 +365,7 @@ class FaceRealTimeProcessor(RealTimeProcessorApi):
img = cv2.imread(current_file) img = cv2.imread(current_file)
if img is None: if img is None:
return { # type: ignore[unreachable] return {
"message": "Invalid image file.", "message": "Invalid image file.",
"success": False, "success": False,
} }
+13 -13
View File
@@ -2,10 +2,10 @@
import logging import logging
import os import os
import shutil
import threading import threading
import warnings
from transformers import AutoFeatureExtractor, AutoTokenizer from transformers import AutoTokenizer, CLIPImageProcessor
from transformers.utils.logging import disable_progress_bar from transformers.utils.logging import disable_progress_bar
from frigate.comms.inter_process import InterProcessRequestor from frigate.comms.inter_process import InterProcessRequestor
@@ -21,12 +21,6 @@ from frigate.util.downloader import ModelDownloader
from .base_embedding import BaseEmbedding from .base_embedding import BaseEmbedding
warnings.filterwarnings(
"ignore",
category=FutureWarning,
message="The class CLIPFeatureExtractor is deprecated",
)
# disables the progress bar for downloading tokenizers and feature extractors # disables the progress bar for downloading tokenizers and feature extractors
disable_progress_bar() disable_progress_bar()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -58,6 +52,13 @@ class JinaV1TextEmbedding(BaseEmbedding):
self._lock = threading.Lock() self._lock = threading.Lock()
files_names = list(self.download_urls.keys()) + [self.tokenizer_file] files_names = list(self.download_urls.keys()) + [self.tokenizer_file]
# an interrupted download leaves the hub cache without the saved tokenizer
tokenizer_path = os.path.join(self.download_path, self.tokenizer_file)
if os.path.isdir(tokenizer_path) and not os.path.exists(
os.path.join(tokenizer_path, "tokenizer_config.json")
):
shutil.rmtree(tokenizer_path)
if not all( if not all(
os.path.exists(os.path.join(self.download_path, n)) for n in files_names os.path.exists(os.path.join(self.download_path, n)) for n in files_names
): ):
@@ -92,7 +93,7 @@ class JinaV1TextEmbedding(BaseEmbedding):
tokenizer = AutoTokenizer.from_pretrained( tokenizer = AutoTokenizer.from_pretrained(
self.model_name, self.model_name,
trust_remote_code=True, trust_remote_code=False,
cache_dir=f"{MODEL_CACHE_DIR}/{self.model_name}/tokenizer", cache_dir=f"{MODEL_CACHE_DIR}/{self.model_name}/tokenizer",
clean_up_tokenization_spaces=True, clean_up_tokenization_spaces=True,
) )
@@ -123,9 +124,8 @@ class JinaV1TextEmbedding(BaseEmbedding):
f"{MODEL_CACHE_DIR}/{self.model_name}/tokenizer" f"{MODEL_CACHE_DIR}/{self.model_name}/tokenizer"
) )
self.tokenizer = AutoTokenizer.from_pretrained( self.tokenizer = AutoTokenizer.from_pretrained(
self.model_name, tokenizer_path,
cache_dir=tokenizer_path, trust_remote_code=False,
trust_remote_code=True,
clean_up_tokenization_spaces=True, clean_up_tokenization_spaces=True,
) )
@@ -209,7 +209,7 @@ class JinaV1ImageEmbedding(BaseEmbedding):
if self.downloader: if self.downloader:
self.downloader.wait_for_download() self.downloader.wait_for_download()
self.feature_extractor = AutoFeatureExtractor.from_pretrained( self.feature_extractor = CLIPImageProcessor.from_pretrained(
f"{MODEL_CACHE_DIR}/{self.model_name}", f"{MODEL_CACHE_DIR}/{self.model_name}",
) )
+12 -4
View File
@@ -3,6 +3,7 @@
import io import io
import logging import logging
import os import os
import shutil
import threading import threading
import numpy as np import numpy as np
@@ -60,6 +61,14 @@ class JinaV2Embedding(BaseEmbedding):
# download the model and tokenizer # download the model and tokenizer
files_names = list(self.download_urls.keys()) + [self.tokenizer_file] files_names = list(self.download_urls.keys()) + [self.tokenizer_file]
# an interrupted download leaves the hub cache without the saved tokenizer
tokenizer_path = os.path.join(self.download_path, self.tokenizer_file)
if os.path.isdir(tokenizer_path) and not os.path.exists(
os.path.join(tokenizer_path, "tokenizer_config.json")
):
shutil.rmtree(tokenizer_path)
if not all( if not all(
os.path.exists(os.path.join(self.download_path, n)) for n in files_names os.path.exists(os.path.join(self.download_path, n)) for n in files_names
): ):
@@ -97,7 +106,7 @@ class JinaV2Embedding(BaseEmbedding):
tokenizer = AutoTokenizer.from_pretrained( tokenizer = AutoTokenizer.from_pretrained(
self.model_name, self.model_name,
trust_remote_code=True, trust_remote_code=False,
cache_dir=os.path.join( cache_dir=os.path.join(
MODEL_CACHE_DIR, self.model_name, "tokenizer" MODEL_CACHE_DIR, self.model_name, "tokenizer"
), ),
@@ -129,9 +138,8 @@ class JinaV2Embedding(BaseEmbedding):
f"{MODEL_CACHE_DIR}/{self.model_name}/tokenizer" f"{MODEL_CACHE_DIR}/{self.model_name}/tokenizer"
) )
self.tokenizer = AutoTokenizer.from_pretrained( self.tokenizer = AutoTokenizer.from_pretrained(
self.model_name, tokenizer_path,
cache_dir=tokenizer_path, trust_remote_code=False,
trust_remote_code=True,
clean_up_tokenization_spaces=True, clean_up_tokenization_spaces=True,
) )
+7 -9
View File
@@ -82,7 +82,7 @@ class EventCleanup(threading.Thread):
datetime.datetime.now() - datetime.timedelta(days=expire_days) datetime.datetime.now() - datetime.timedelta(days=expire_days)
).timestamp() ).timestamp()
# grab all events after specific time # grab all events after specific time
expired_events: list[Event] = ( expired_events: list[Event] = list(
Event.select( Event.select(
Event.id, Event.id,
Event.camera, Event.camera,
@@ -97,7 +97,6 @@ class EventCleanup(threading.Thread):
.namedtuples() .namedtuples()
.iterator() .iterator()
) )
expired_events = list(expired_events)
logger.debug(f"{len(expired_events)} events can be expired") logger.debug(f"{len(expired_events)} events can be expired")
# delete the media from disk # delete the media from disk
@@ -159,7 +158,7 @@ class EventCleanup(threading.Thread):
datetime.datetime.now() - datetime.timedelta(days=expire_days) datetime.datetime.now() - datetime.timedelta(days=expire_days)
).timestamp() ).timestamp()
# grab all events after specific time # grab all events after specific time
expired_events = ( camera_events = (
Event.select( Event.select(
Event.id, Event.id,
Event.camera, Event.camera,
@@ -178,7 +177,7 @@ class EventCleanup(threading.Thread):
# delete the grabbed clips from disk # delete the grabbed clips from disk
# only snapshots are stored in /clips # only snapshots are stored in /clips
# so no need to delete mp4 files # so no need to delete mp4 files
for event in expired_events: for event in camera_events:
events_to_update.append(str(event.id)) events_to_update.append(str(event.id))
deleted = delete_event_snapshot(event) deleted = delete_event_snapshot(event)
@@ -212,7 +211,7 @@ class EventCleanup(threading.Thread):
datetime.datetime.now() - datetime.timedelta(days=expire_days) datetime.datetime.now() - datetime.timedelta(days=expire_days)
).timestamp() ).timestamp()
# grab all events after specific time # grab all events after specific time
expired_events: list[Event] = ( expired_events: list[Event] = list(
Event.select( Event.select(
Event.id, Event.id,
Event.camera, Event.camera,
@@ -225,7 +224,6 @@ class EventCleanup(threading.Thread):
.namedtuples() .namedtuples()
.iterator() .iterator()
) )
expired_events = list(expired_events)
logger.debug(f"{len(expired_events)} events can be expired") logger.debug(f"{len(expired_events)} events can be expired")
# delete the media from disk # delete the media from disk
for expired in expired_events: for expired in expired_events:
@@ -235,7 +233,7 @@ class EventCleanup(threading.Thread):
try: try:
media_path.unlink(missing_ok=True) media_path.unlink(missing_ok=True)
if file_extension == "jpg": if file_extension == "jpg":
media_path = Path( media_path = Path( # type: ignore[unreachable]
f"{os.path.join(CLIPS_DIR, media_name)}-clean.webp" f"{os.path.join(CLIPS_DIR, media_name)}-clean.webp"
) )
media_path.unlink(missing_ok=True) media_path.unlink(missing_ok=True)
@@ -289,7 +287,7 @@ class EventCleanup(threading.Thread):
now - datetime.timedelta(days=camera.record.effective_detection_days) now - datetime.timedelta(days=camera.record.effective_detection_days)
).timestamp() ).timestamp()
# grab all events after specific time # grab all events after specific time
expired_events = ( camera_events = (
Event.select( Event.select(
Event.id, Event.id,
Event.camera, Event.camera,
@@ -316,7 +314,7 @@ class EventCleanup(threading.Thread):
# delete the grabbed clips from disk # delete the grabbed clips from disk
# only snapshots are stored in /clips # only snapshots are stored in /clips
# so no need to delete mp4 files # so no need to delete mp4 files
for event in expired_events: for event in camera_events:
events_to_update.append(event.id) events_to_update.append(event.id)
# update the clips attribute for the db entry # update the clips attribute for the db entry
+1 -1
View File
@@ -94,7 +94,7 @@ class EventProcessor(threading.Thread):
if update == None: if update == None:
continue continue
source_type, event_type, camera, _, event_data = update # type: ignore[misc] source_type, event_type, camera, _, event_data = update
logger.debug( logger.debug(
f"Event received: {source_type} {event_type} {camera} {event_data['id']}" f"Event received: {source_type} {event_type} {camera} {event_data['id']}"
+1 -1
View File
@@ -38,7 +38,7 @@ __all__ = [
"register_genai_provider", "register_genai_provider",
] ]
PROVIDERS = {} PROVIDERS: dict[GenAIProviderEnum, type["GenAIClient"]] = {}
def register_genai_provider(key: GenAIProviderEnum) -> Callable: def register_genai_provider(key: GenAIProviderEnum) -> Callable:
+1
View File
@@ -85,6 +85,7 @@ def apply_log_levels(default: str, log_levels: dict[str, LogLevel]) -> None:
log_levels = { log_levels = {
"absl": LogLevel.error, "absl": LogLevel.error,
"httpx": LogLevel.error, "httpx": LogLevel.error,
"httpx2": LogLevel.error,
"h5py": LogLevel.error, "h5py": LogLevel.error,
"keras": LogLevel.error, "keras": LogLevel.error,
"matplotlib": LogLevel.error, "matplotlib": LogLevel.error,
+2 -2
View File
@@ -360,7 +360,7 @@ class ObjectDetectProcess:
# detection_start is set only after detection_queue.get() # detection_start is set only after detection_queue.get()
# returns. If it was reset during the grace period, the process # returns. If it was reset during the grace period, the process
# recovered and may be waiting on the shared queue again. # recovered and may be waiting on the shared queue again.
if self.detection_start.value == 0.0: # type: ignore[attr-defined] if self.detection_start.value == 0.0:
logging.info("Detection process recovered before restart") logging.info("Detection process recovered before restart")
return return
@@ -369,7 +369,7 @@ class ObjectDetectProcess:
self.detect_process.join() self.detect_process.join()
logging.info("Detection process has exited...") logging.info("Detection process has exited...")
self.detection_start.value = 0.0 # type: ignore[attr-defined] self.detection_start.value = 0.0
# Async path for MemryX # Async path for MemryX
if self.detector_config.type == "memryx": if self.detector_config.type == "memryx":
+2 -2
View File
@@ -10,7 +10,7 @@ from multiprocessing.synchronize import Event as MpEvent
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
from playhouse.sqlite_ext import SqliteExtDatabase from peewee import SqliteDatabase
from frigate.config import CameraConfig, FrigateConfig, RetainModeEnum from frigate.config import CameraConfig, FrigateConfig, RetainModeEnum
from frigate.const import ( from frigate.const import (
@@ -88,7 +88,7 @@ class RecordingCleanup(threading.Thread):
if ( if (
os.stat(f"{self.config.database.path}-wal").st_size / (1024 * 1024) os.stat(f"{self.config.database.path}-wal").st_size / (1024 * 1024)
) > MAX_WAL_SIZE: ) > MAX_WAL_SIZE:
db = SqliteExtDatabase(self.config.database.path) db = SqliteDatabase(self.config.database.path)
db.execute_sql("PRAGMA wal_checkpoint(TRUNCATE);") db.execute_sql("PRAGMA wal_checkpoint(TRUNCATE);")
db.close() db.close()
+1 -1
View File
@@ -16,7 +16,7 @@ from enum import Enum
from pathlib import Path from pathlib import Path
from typing import Any, cast from typing import Any, cast
import pytz # type: ignore[import-untyped] import pytz
from pathvalidate import sanitize_filename from pathvalidate import sanitize_filename
from peewee import DoesNotExist from peewee import DoesNotExist
+1 -1
View File
@@ -859,7 +859,7 @@ class RecordingMaintainer(threading.Thread):
for box in motion_boxes: for box in motion_boxes:
if len(box) < 4: if len(box) < 4:
continue continue # type: ignore[unreachable]
x1, y1, x2, y2 = box x1, y1, x2, y2 = box
# Convert pixel coordinates to grid cells # Convert pixel coordinates to grid cells
+2 -2
View File
@@ -6,8 +6,8 @@ from unittest.mock import patch
from fastapi import Request from fastapi import Request
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from peewee import SqliteDatabase
from peewee_migrate import Router from peewee_migrate import Router
from playhouse.sqlite_ext import SqliteExtDatabase
from playhouse.sqliteq import SqliteQueueDatabase from playhouse.sqliteq import SqliteQueueDatabase
from pydantic import Json from pydantic import Json
@@ -38,7 +38,7 @@ class AuthTestClient(TestClient):
class BaseTestHttp(unittest.TestCase): class BaseTestHttp(unittest.TestCase):
def setUp(self, models): def setUp(self, models):
# setup clean database for each test run # setup clean database for each test run
migrate_db = SqliteExtDatabase("test.db") migrate_db = SqliteDatabase("test.db")
del logging.getLogger("peewee_migrate").handlers[:] del logging.getLogger("peewee_migrate").handlers[:]
router = Router(migrate_db) router = Router(migrate_db)
router.run() router.run()
@@ -7,7 +7,7 @@ import unittest
from types import SimpleNamespace from types import SimpleNamespace
from unittest.mock import MagicMock from unittest.mock import MagicMock
from playhouse.sqlite_ext import SqliteExtDatabase from peewee import SqliteDatabase
from frigate.api.chat import ( from frigate.api.chat import (
_execute_find_similar_objects, _execute_find_similar_objects,
@@ -98,7 +98,7 @@ class TestExecuteFindSimilarObjects(unittest.TestCase):
def setUp(self): def setUp(self):
self.tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False) self.tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
self.tmp.close() self.tmp.close()
self.db = SqliteExtDatabase(self.tmp.name) self.db = SqliteDatabase(self.tmp.name)
Event.bind(self.db, bind_refs=False, bind_backrefs=False) Event.bind(self.db, bind_refs=False, bind_backrefs=False)
self.db.connect() self.db.connect()
self.db.create_tables([Event]) self.db.create_tables([Event])
+2 -2
View File
@@ -12,7 +12,7 @@ from unittest.mock import AsyncMock, patch
import cv2 import cv2
import numpy as np import numpy as np
from playhouse.sqlite_ext import SqliteExtDatabase from peewee import SqliteDatabase
from frigate.api.chat import ( from frigate.api.chat import (
TOOL_REJECTED_RESULT, TOOL_REJECTED_RESULT,
@@ -214,7 +214,7 @@ class DatabaseTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
self.tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False) self.tmp = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
self.tmp.close() self.tmp.close()
self.db = SqliteExtDatabase(self.tmp.name) self.db = SqliteDatabase(self.tmp.name)
for model in self.models: for model in self.models:
model.bind(self.db, bind_refs=False, bind_backrefs=False) model.bind(self.db, bind_refs=False, bind_backrefs=False)
self.db.connect() self.db.connect()
@@ -3,7 +3,7 @@
import unittest import unittest
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from playhouse.sqlite_ext import SqliteExtDatabase from peewee import SqliteDatabase
from frigate.comms.dispatcher import Dispatcher from frigate.comms.dispatcher import Dispatcher
from frigate.const import INSERT_MANY_RECORDINGS from frigate.const import INSERT_MANY_RECORDINGS
@@ -30,7 +30,7 @@ class TestInsertManyRecordings(unittest.TestCase):
"""A duplicate path must not cost the rest of the batch.""" """A duplicate path must not cost the rest of the batch."""
def setUp(self): def setUp(self):
self.db = SqliteExtDatabase(":memory:") self.db = SqliteDatabase(":memory:")
self.db.bind([Recordings]) self.db.bind([Recordings])
self.db.create_tables([Recordings]) self.db.create_tables([Recordings])
+2 -2
View File
@@ -4,7 +4,7 @@ import datetime
import unittest import unittest
from unittest.mock import MagicMock from unittest.mock import MagicMock
from playhouse.sqlite_ext import SqliteExtDatabase from peewee import SqliteDatabase
from frigate.config import FrigateConfig from frigate.config import FrigateConfig
from frigate.events.cleanup import EventCleanup from frigate.events.cleanup import EventCleanup
@@ -15,7 +15,7 @@ class TestEventCleanupSubRetention(unittest.TestCase):
def setUp(self): def setUp(self):
# in-memory database keeps these tests isolated from the shared # in-memory database keeps these tests isolated from the shared
# on-disk test.db used by the http api tests # on-disk test.db used by the http api tests
self.db = SqliteExtDatabase(":memory:") self.db = SqliteDatabase(":memory:")
models = [Event, Timeline] models = [Event, Timeline]
self.db.bind(models) self.db.bind(models)
self.db.create_tables(models) self.db.create_tables(models)
+2 -2
View File
@@ -9,8 +9,8 @@ import logging
import os import os
import unittest import unittest
from peewee import SqliteDatabase
from peewee_migrate import Router from peewee_migrate import Router
from playhouse.sqlite_ext import SqliteExtDatabase
from playhouse.sqliteq import SqliteQueueDatabase from playhouse.sqliteq import SqliteQueueDatabase
from frigate.api.media_auth import ( from frigate.api.media_auth import (
@@ -208,7 +208,7 @@ class TestExportResolution(unittest.TestCase):
"""Export resolution requires a DB lookup.""" """Export resolution requires a DB lookup."""
def setUp(self): def setUp(self):
migrate_db = SqliteExtDatabase("test.db") migrate_db = SqliteDatabase("test.db")
del logging.getLogger("peewee_migrate").handlers[:] del logging.getLogger("peewee_migrate").handlers[:]
Router(migrate_db).run() Router(migrate_db).run()
migrate_db.close() migrate_db.close()
+2 -2
View File
@@ -5,8 +5,8 @@ import os
import unittest import unittest
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from peewee import SqliteDatabase
from peewee_migrate import Router from peewee_migrate import Router
from playhouse.sqlite_ext import SqliteExtDatabase
from playhouse.sqliteq import SqliteQueueDatabase from playhouse.sqliteq import SqliteQueueDatabase
from frigate.models import Notice, NoticeStats from frigate.models import Notice, NoticeStats
@@ -49,7 +49,7 @@ class TestNoticeKinds(unittest.TestCase):
class RegistryTestCase(unittest.TestCase): class RegistryTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
migrate_db = SqliteExtDatabase("test.db") migrate_db = SqliteDatabase("test.db")
del logging.getLogger("peewee_migrate").handlers[:] del logging.getLogger("peewee_migrate").handlers[:]
router = Router(migrate_db) router = Router(migrate_db)
router.run() router.run()
+2 -2
View File
@@ -4,7 +4,7 @@ import datetime
import unittest import unittest
from unittest.mock import MagicMock from unittest.mock import MagicMock
from playhouse.sqlite_ext import SqliteExtDatabase from peewee import SqliteDatabase
from frigate.config import FrigateConfig from frigate.config import FrigateConfig
from frigate.models import Previews, Recordings, ReviewSegment, UserReviewStatus from frigate.models import Previews, Recordings, ReviewSegment, UserReviewStatus
@@ -15,7 +15,7 @@ class TestRecordingCleanupSubRetention(unittest.TestCase):
def setUp(self): def setUp(self):
# in-memory database keeps these tests isolated from the shared # in-memory database keeps these tests isolated from the shared
# on-disk test.db used by the http api tests # on-disk test.db used by the http api tests
self.db = SqliteExtDatabase(":memory:") self.db = SqliteDatabase(":memory:")
models = [Previews, Recordings, ReviewSegment, UserReviewStatus] models = [Previews, Recordings, ReviewSegment, UserReviewStatus]
self.db.bind(models) self.db.bind(models)
self.db.create_tables(models) self.db.create_tables(models)
+2 -2
View File
@@ -8,7 +8,7 @@ import unittest
from collections import defaultdict from collections import defaultdict
from unittest.mock import AsyncMock, MagicMock, patch from unittest.mock import AsyncMock, MagicMock, patch
from playhouse.sqlite_ext import SqliteExtDatabase from peewee import SqliteDatabase
from frigate.config import FrigateConfig from frigate.config import FrigateConfig
from frigate.models import Recordings from frigate.models import Recordings
@@ -719,7 +719,7 @@ class TestSegmentChainSeeding(unittest.IsolatedAsyncioTestCase):
T0 = datetime.datetime(2026, 6, 10, 14, 30, 22, tzinfo=datetime.UTC).timestamp() T0 = datetime.datetime(2026, 6, 10, 14, 30, 22, tzinfo=datetime.UTC).timestamp()
def setUp(self): def setUp(self):
self.db = SqliteExtDatabase(":memory:") self.db = SqliteDatabase(":memory:")
self.db.bind([Recordings]) self.db.bind([Recordings])
self.db.create_tables([Recordings]) self.db.create_tables([Recordings])
+2 -2
View File
@@ -5,7 +5,7 @@ import json
import unittest import unittest
from types import SimpleNamespace from types import SimpleNamespace
from playhouse.sqlite_ext import SqliteExtDatabase from peewee import SqliteDatabase
from frigate.api.media import _vod_response from frigate.api.media import _vod_response
from frigate.const import MAX_SEGMENT_DURATION from frigate.const import MAX_SEGMENT_DURATION
@@ -24,7 +24,7 @@ class CoverageDbTestCase(unittest.TestCase):
def setUp(self): def setUp(self):
# in-memory database keeps these tests isolated from the shared # in-memory database keeps these tests isolated from the shared
# on-disk test.db used by the http api tests # on-disk test.db used by the http api tests
self.db = SqliteExtDatabase(":memory:") self.db = SqliteDatabase(":memory:")
models = [Recordings] models = [Recordings]
self.db.bind(models) self.db.bind(models)
self.db.create_tables(models) self.db.create_tables(models)
+2 -3
View File
@@ -5,9 +5,8 @@ import tempfile
import unittest import unittest
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
from peewee import DoesNotExist from peewee import DoesNotExist, SqliteDatabase
from peewee_migrate import Router from peewee_migrate import Router
from playhouse.sqlite_ext import SqliteExtDatabase
from playhouse.sqliteq import SqliteQueueDatabase from playhouse.sqliteq import SqliteQueueDatabase
from frigate.config import FrigateConfig from frigate.config import FrigateConfig
@@ -20,7 +19,7 @@ from frigate.test.const import TEST_DB, TEST_DB_CLEANUPS
class TestHttp(unittest.TestCase): class TestHttp(unittest.TestCase):
def setUp(self): def setUp(self):
# setup clean database for each test run # setup clean database for each test run
migrate_db = SqliteExtDatabase("test.db") migrate_db = SqliteDatabase("test.db")
del logging.getLogger("peewee_migrate").handlers[:] del logging.getLogger("peewee_migrate").handlers[:]
router = Router(migrate_db) router = Router(migrate_db)
router.run() router.run()
+1 -1
View File
@@ -116,7 +116,7 @@ class FrigateWatchdog(threading.Thread):
now = datetime.datetime.now().timestamp() now = datetime.datetime.now().timestamp()
for name, detector in self.detectors.items(): for name, detector in self.detectors.items():
detection_start = detector.detection_start.value # type: ignore[attr-defined] detection_start = detector.detection_start.value
# issue https://github.com/python/typeshed/issues/8799 # issue https://github.com/python/typeshed/issues/8799
# from mypy 0.981 onwards # from mypy 0.981 onwards
if detection_start > 0.0 and now - detection_start > 10: if detection_start > 0.0 and now - detection_start > 10:
+1
View File
@@ -2,5 +2,6 @@
target-version = "py311" target-version = "py311"
[tool.ruff.lint] [tool.ruff.lint]
select = ["E4", "E7", "E9", "F"]
ignore = ["E501","E711","E712","UP031","UP032","UP042","G004"] ignore = ["E501","E711","E712","UP031","UP032","UP042","G004"]
extend-select = ["I", "UP", "G", "ASYNC210", "B904"] extend-select = ["I", "UP", "G", "ASYNC210", "B904"]
+763 -935
View File
File diff suppressed because it is too large Load Diff
+51 -51
View File
@@ -23,33 +23,33 @@
"dependencies": { "dependencies": {
"@cycjimmy/jsmpeg-player": "^6.1.2", "@cycjimmy/jsmpeg-player": "^6.1.2",
"@hookform/resolvers": "^5.9.1", "@hookform/resolvers": "^5.9.1",
"@radix-ui/react-alert-dialog": "^1.1.23", "@radix-ui/react-alert-dialog": "^1.1.24",
"@radix-ui/react-aspect-ratio": "^1.1.15", "@radix-ui/react-aspect-ratio": "^1.1.16",
"@radix-ui/react-checkbox": "^1.3.11", "@radix-ui/react-checkbox": "^1.3.12",
"@radix-ui/react-collapsible": "^1.1.20", "@radix-ui/react-collapsible": "^1.1.21",
"@radix-ui/react-context-menu": "^2.3.7", "@radix-ui/react-context-menu": "^2.3.8",
"@radix-ui/react-dialog": "^1.1.23", "@radix-ui/react-dialog": "^1.2.0",
"@radix-ui/react-dropdown-menu": "^2.1.24", "@radix-ui/react-dropdown-menu": "^2.1.25",
"@radix-ui/react-hover-card": "^1.1.23", "@radix-ui/react-hover-card": "^1.1.24",
"@radix-ui/react-label": "^2.1.15", "@radix-ui/react-label": "^2.1.16",
"@radix-ui/react-popover": "^1.1.23", "@radix-ui/react-popover": "^1.2.0",
"@radix-ui/react-progress": "^1.1.16", "@radix-ui/react-progress": "^1.1.17",
"@radix-ui/react-radio-group": "^1.4.7", "@radix-ui/react-radio-group": "^1.4.8",
"@radix-ui/react-scroll-area": "^1.2.18", "@radix-ui/react-scroll-area": "^1.3.0",
"@radix-ui/react-select": "^2.3.7", "@radix-ui/react-select": "^2.3.8",
"@radix-ui/react-separator": "^1.1.15", "@radix-ui/react-separator": "^1.1.16",
"@radix-ui/react-slider": "^1.4.7", "@radix-ui/react-slider": "^1.5.0",
"@radix-ui/react-slot": "1.2.4", "@radix-ui/react-slot": "1.4.0",
"@radix-ui/react-switch": "^1.3.7", "@radix-ui/react-switch": "^1.3.8",
"@radix-ui/react-tabs": "^1.1.21", "@radix-ui/react-tabs": "^1.1.22",
"@radix-ui/react-toggle": "^1.1.18", "@radix-ui/react-toggle": "^1.1.19",
"@radix-ui/react-toggle-group": "^1.1.19", "@radix-ui/react-toggle-group": "^1.1.20",
"@radix-ui/react-tooltip": "^1.2.16", "@radix-ui/react-tooltip": "^1.3.0",
"@rjsf/core": "^6.10.0", "@rjsf/core": "^6.10.1",
"@rjsf/shadcn": "^6.10.0", "@rjsf/shadcn": "^6.10.1",
"@rjsf/utils": "^6.10.0", "@rjsf/utils": "^6.10.1",
"@rjsf/validator-ajv8": "^6.10.0", "@rjsf/validator-ajv8": "^6.10.1",
"apexcharts": "^7.3.0", "apexcharts": "^7.8.0",
"axios": "^1.20.0", "axios": "^1.20.0",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
@@ -57,31 +57,31 @@
"copy-to-clipboard": "^4.0.2", "copy-to-clipboard": "^4.0.2",
"date-fns": "^4.4.0", "date-fns": "^4.4.0",
"date-fns-tz": "^3.2.0", "date-fns-tz": "^3.2.0",
"framer-motion": "^13.3.0", "framer-motion": "^14.0.0",
"hls.js": "^1.7.3", "hls.js": "^1.7.3",
"i18next": "^26.4.2", "i18next": "^26.4.2",
"i18next-http-backend": "^4.0.2", "i18next-http-backend": "^4.0.2",
"idb-keyval": "^6.3.0", "idb-keyval": "^6.3.0",
"js-yaml": "^5.4.2", "js-yaml": "^5.4.3",
"konva": "^10.5.0", "konva": "^10.7.1",
"lodash": "^4.18.1", "lodash": "^4.18.1",
"lucide-react": "^1.46.0", "lucide-react": "^1.52.0",
"monaco-yaml": "^5.5.1", "monaco-yaml": "^5.5.1",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"nosleep.js": "^0.12.0", "nosleep.js": "^0.12.0",
"react": "^19.3.0", "react": "^19.3.0",
"react-apexcharts": "^2.1.1", "react-apexcharts": "^2.1.1",
"react-day-picker": "^9.14.0", "react-day-picker": "^10.0.2",
"react-device-detect": "^2.2.3", "react-device-detect": "^2.2.3",
"react-dom": "^19.3.0", "react-dom": "^19.3.0",
"react-dropzone": "^20.1.2", "react-dropzone": "^20.1.2",
"react-grid-layout": "^2.2.4", "react-grid-layout": "^2.3.0",
"react-hook-form": "^7.88.0", "react-hook-form": "^7.89.0",
"react-i18next": "^17.0.14", "react-i18next": "^17.0.16",
"react-icons": "^5.7.0", "react-icons": "^5.7.0",
"react-konva": "^19.2.7", "react-konva": "^19.3.0",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-router-dom": "^6.30.6", "react-router-dom": "^7.18.4",
"react-swipeable": "^7.0.2", "react-swipeable": "^7.0.2",
"react-zoom-pan-pinch": "3.4.4", "react-zoom-pan-pinch": "3.4.4",
"remark-gfm": "^4.0.0", "remark-gfm": "^4.0.0",
@@ -93,41 +93,41 @@
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"use-long-press": "^3.3.0", "use-long-press": "^3.3.0",
"vaul": "^1.1.2", "vaul": "^1.1.2",
"virtua": "^0.51.3", "virtua": "^0.53.3",
"vite-plugin-monaco-editor": "^1.1.0", "vite-plugin-monaco-editor": "^1.1.0",
"zod": "^3.25.76" "zod": "^3.25.76"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@playwright/test": "^1.63.0", "@playwright/test": "^1.64.0",
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",
"@types/lodash": "^4.17.25", "@types/lodash": "^4.17.25",
"@types/node": "^26.5.1", "@types/node": "^26.6.4",
"@types/react": "^19.3.0", "@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0", "@types/react-dom": "^19.3.0",
"@vitejs/plugin-react": "^6.1.1", "@vitejs/plugin-react": "^6.1.2",
"autoprefixer": "^10.6.0", "autoprefixer": "^10.6.1",
"esbuild": "^0.28.2", "esbuild": "^0.28.2",
"eslint": "^10.10.0", "eslint": "^10.12.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.6", "eslint-plugin-prettier": "^5.5.6",
"eslint-plugin-react-hooks": "^7.1.1", "eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.7", "eslint-plugin-react-refresh": "^0.5.7",
"globals": "^17.12.0", "globals": "^17.13.0",
"i18next-cli": "^1.5.11", "i18next-cli": "^1.5.11",
"monaco-editor": "^0.52.2", "monaco-editor": "^0.52.2",
"patch-package": "^8.0.1", "patch-package": "^8.0.1",
"postcss": "^8.5.12", "postcss": "^8.5.29",
"prettier": "^3.9.6", "prettier": "^3.9.9",
"prettier-plugin-tailwindcss": "^0.8.1", "prettier-plugin-tailwindcss": "^0.8.1",
"tailwindcss": "^3.4.9", "tailwindcss": "^3.4.19",
"typescript": "^5.9.3", "typescript": "^6.0.3",
"typescript-eslint": "^8.70.0", "typescript-eslint": "^8.71.1",
"vite": "^8.3.0" "vite": "^8.3.3"
}, },
"overrides": { "overrides": {
"@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-popper": "1.2.8", "@radix-ui/react-popper": "1.2.8",
"@radix-ui/react-slot": "1.2.4" "@radix-ui/react-slot": "1.4.0"
} }
} }
@@ -1,46 +0,0 @@
diff --git a/node_modules/@radix-ui/react-slot/dist/index.js b/node_modules/@radix-ui/react-slot/dist/index.js
index 3691205..3b62ea8 100644
--- a/node_modules/@radix-ui/react-slot/dist/index.js
+++ b/node_modules/@radix-ui/react-slot/dist/index.js
@@ -85,11 +85,12 @@ function createSlotClone(ownerName) {
if (isLazyComponent(children) && typeof use === "function") {
children = use(children._payload);
}
+ const childrenRef = React.isValidElement(children) ? getElementRef(children) : null;
+ const composedRef = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, childrenRef);
if (React.isValidElement(children)) {
- const childrenRef = getElementRef(children);
const props2 = mergeProps(slotProps, children.props);
if (children.type !== React.Fragment) {
- props2.ref = forwardedRef ? (0, import_react_compose_refs.composeRefs)(forwardedRef, childrenRef) : childrenRef;
+ props2.ref = forwardedRef ? composedRef : childrenRef;
}
return React.cloneElement(children, props2);
}
diff --git a/node_modules/@radix-ui/react-slot/dist/index.mjs b/node_modules/@radix-ui/react-slot/dist/index.mjs
index d7ea374..a990150 100644
--- a/node_modules/@radix-ui/react-slot/dist/index.mjs
+++ b/node_modules/@radix-ui/react-slot/dist/index.mjs
@@ -1,6 +1,6 @@
// src/slot.tsx
import * as React from "react";
-import { composeRefs } from "@radix-ui/react-compose-refs";
+import { composeRefs, useComposedRefs } from "@radix-ui/react-compose-refs";
import { Fragment as Fragment2, jsx } from "react/jsx-runtime";
var REACT_LAZY_TYPE = Symbol.for("react.lazy");
var use = React[" use ".trim().toString()];
@@ -45,11 +45,12 @@ function createSlotClone(ownerName) {
if (isLazyComponent(children) && typeof use === "function") {
children = use(children._payload);
}
+ const childrenRef = React.isValidElement(children) ? getElementRef(children) : null;
+ const composedRef = useComposedRefs(forwardedRef, childrenRef);
if (React.isValidElement(children)) {
- const childrenRef = getElementRef(children);
const props2 = mergeProps(slotProps, children.props);
if (children.type !== React.Fragment) {
- props2.ref = forwardedRef ? composeRefs(forwardedRef, childrenRef) : childrenRef;
+ props2.ref = forwardedRef ? composedRef : childrenRef;
}
return React.cloneElement(children, props2);
}
+1 -1
View File
@@ -44,7 +44,7 @@ function App() {
return ( return (
<Providers> <Providers>
<BrowserRouter basename={window.baseUrl}> <BrowserRouter basename={window.baseUrl} useTransitions={false}>
<Wrapper> <Wrapper>
{config?.safe_mode ? <SafeAppView /> : <DefaultAppView />} {config?.safe_mode ? <SafeAppView /> : <DefaultAppView />}
</Wrapper> </Wrapper>
@@ -155,7 +155,7 @@ export function CombinedStorageGraph({
formatter: function (val, opts) { formatter: function (val, opts) {
const entry = opts ? series[opts.seriesIndex] : undefined; const entry = opts ? series[opts.seriesIndex] : undefined;
if (entry) { if (entry) {
return `${getUnitSize(entry.usage)} (${val.toFixed(2)}%)`; return `${getUnitSize(entry.usage)} (${(val ?? 0).toFixed(2)}%)`;
} }
}, },
}, },
+1 -1
View File
@@ -5,7 +5,7 @@
"lib": ["ES2022", "DOM", "DOM.Iterable"], "lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext", "module": "ESNext",
"skipLibCheck": true, "skipLibCheck": true,
"baseUrl": ".", "types": ["node"],
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": ["./src/*"]
}, },