6055 Commits

Author SHA1 Message Date
patrykorwat
410696ade3 fix(axelera): overload submits degrade to zero-rows, not raise
When the input queue is full, drop the frame's INFERENCE but keep the
1 submission : 1 result contract of AsyncDetectorRunner by emitting an
empty (20, 6) result for that connection_id (same pattern the producer
already uses when the buffer pool is starved). Raising here used to let a
burst of offered frames kill the detect worker and force a restart.
Test flipped: queue-full now asserts the zero-row drop instead of the raise.
2026-08-24 10:44:22 +00:00
patrykorwat
97705e7df2 perf(axelera): greedy NMS via cv2.dnn.NMSBoxes
Replace the pure-Python O(n^2) _nms_xyxy loop with the framework's own
C++ NMS (xyxy -> xywh first, same as frigate.util.model). Same contract:
kept indices in descending score order; empty-safe. Removes a per-frame
CPU hot spot on dense daytime frames without changing decode results.
2026-08-24 10:43:43 +00:00
patrykorwat
0d0581494f style(axelera): ruff format under the CI-pinned 0.15.20
Pre-existing drift in HEAD: the file failed 'ruff format --check' as-is,
so CI's format gate was already red. Line-joins only, no behavior change.
2026-08-24 10:43:12 +00:00
patrykorwat
4e33dda6da feat(axelera): drive decoder geometry from the compiled model manifest
Read the padded/original input shapes, the exact content offset inside the
padded input (n_padded_ch_inputs), per-output padded + real channel counts
and dequantize (scale, zero-point) from 1/manifest.json (fallback
quantized/manifest.json) instead of hardcoding YOLO-NAS constants, so the
decoder works for any compiled YOLO-family model shipping a postprocess
graph. Map each postprocess input to its model output by (C, H, W) shape
(the compile order of heads is not guaranteed to match the graph's input
order). Accept two postprocess output shapes: two outputs (scores + xyxy
boxes, YOLO-NAS) or one fused output [cx, cy, w, h, (objectness), C class
scores] in CENTER format (YOLOX/SDK DecodeYolo), converted to xyxy and
scaled by objectness. Verified on hardware against both YOLO-NAS M and
YOLOX-S: identical boxes on the same frames, YOLOX-S yields the same
bus/persons as the SDK stream reference.
2026-08-23 23:09:00 +00:00
patrykorwat
86637985ea perf(axelera): pipeline the CPU postprocess behind the NPU run
Split the single worker into a producer/consumer pipeline: the producer
letterboxes + quantizes + runs the model into a pooled, ping-ponged buffer
set, the consumer dequantises + postprocesses the previous frame's outputs
while the NPU is already busy on the next one (all run() calls stay on the
one producer thread, so in-flight frames never execute the model in
parallel). Measured on the Metis: ~24.9 ms/frame (~40 fps) vs ~28.9 ms
serial, giving real headroom over the ~35 fps offered by 7 cameras.
2026-08-23 21:07:11 +00:00
patrykorwat
44cc8bf4fc perf(axelera): quantize uint8->int8 in one wrapping pass
resized - 128 in uint8 wraps mod 256, then astype(int8) yields exactly
px - 128 (verified for all 0..255, equivalent to the int16+clip path)
without the intermediate int16 array or clip pass.
2026-08-23 20:51:44 +00:00
patrykorwat
92112cf909 feat(axelera): run inference via the axelera.runtime API instead of the SDK stream
The GStreamer-based create_inference_stream pipeline is depth-4 and clocked by
input arrival, so a frame's result round-trips in ~4x the input gap (~150 ms at
~35 fps) even though the accelerator computes a frame in ~21 ms back-to-back.
Switch to the vendor's direct ModelInstance.run() runtime API (the same
integration pattern as hailo8l/memryx): a synchronous run() with no video
pipeline, so per-frame latency is the flat model time (~21 ms measured for
YOLO-NAS M 704x576 at 1 instance), independent of scene activity.

- _AxeleraRuntimeInference: single ModelInstance plus a worker thread that pops
  frames strictly FIFO, letterboxes + quantizes (int8, BGR->RGB), runs the
  model, feeds the six int8 heads through the compiled model's own baked
  postprocess_graph.onnx (dequant, crop per TensorInfo.padding[3], transpose)
  and emits (class_id, score, box) rows after a conf threshold + NMS.
- Keeps the async contract (send_input/receive_output/shutdown); requests are
  paired to results by construction through the FIFO queue, so no pixel tokens
  and no keep-alives.
- Model resolution unchanged (preset under AXELERA_BUILD_ROOT, local path, or
  URL); labels mapped by name via the merged labelmap.
2026-08-23 20:09:21 +00:00
patrykorwat
65a429ab33 Revert "fix(object_detection): report per-frame time, not round-trip, for async detectors"
This reverts commit 92feefc113ca9fcb8c6d9d8e2a3c25730ee50208.
2026-08-23 17:30:02 +00:00
patrykorwat
92feefc113 fix(object_detection): report per-frame time, not round-trip, for async detectors
AsyncDetectorRunner derived inference_speed from the send_input-to-receive_output
round-trip, which for stream-based detectors (memryx, axelera) is gated by the
SDK pipeline depth and the input cadence (latency ~= depth x input gap): at a
realistic ~35 fps aggregate it reads ~150-200 ms even though the accelerator
handles a frame in ~14 ms back-to-back, making a fast detector look ~10x slower
than sync detectors (which report a single detect_raw duration).

Derive inference_speed from result cadence instead: the rolling average of the
time between completed results equals 1000 / detection_fps, the per-frame
processing time, comparable across detectors. Idle gaps between results are not
folded in, so a quiet stream never inflates the reported speed. The per-send
timestamp deque is removed (it existed only for the old metric).
2026-08-23 13:40:39 +00:00
patrykorwat
3959640861 fix(axelera): pair results by recorded FIFO order instead of pixel tokens
Replace the content-based pixel-token pairing with the reviewer's scheme:
the feeder registers every yielded frame on a pending deque (None for
primer/keep-alive synthetics, the connection id for real frames) and
collect() pops exactly one entry per result, so synthetics are dropped and
real results always reach the connection that owns them - without reading
the returned image at all.

Validated on hardware first: the SDK InferenceStream returns results
strictly in input order, one per yielded frame (200/200 frames in order,
no drops or duplicates, synthetics interleaved included), and the per-frame
image read (img.asarray + band decode) measured 0.58 ms against a 14.36 ms
back-to-back round-trip, i.e. not the driver of the System Metrics latency.

A desync guard raises (detector restarts) if the SDK ever emits a result
with no registered frame, instead of silently misattributing everything
after it (the P1 failure mode). The pixel-token path and its 200/200 FIFO
probe stay in git history for rollback.
2026-08-22 08:56:39 +00:00
patrykorwat
bc90ab343b fix(axelera): InferenceStream is iterable, not an iterator
next(stream) raises TypeError ('InferenceStream' object is not an
iterator), killing the result worker on the very first collect() (the
older sync pump used a for-loop). Grab iter(stream) once at init and
drive collect() through that iterator. Regression test: a stream that
is iterable-without-__next__ like the real SDK.
2026-08-21 13:03:28 +00:00
patrykorwat
639b229d94 fix(axelera): keep SDK source fed during init with sparse safety keep-alives
create_inference_stream consumes the source generator from the very
start; a blocking queue.get() in the feed left the source empty during
stream init, so the detector process hung before the async worker
threads started (no events). Re-add a sparse keep-alive zero (0.1 s)
that fires only while the incoming queue is momentarily empty and
carries no request token (dropped by collect); steady-state real
frames still clock the pipeline back-to-back via AsyncDetectorRunner.
2026-08-21 12:55:57 +00:00
patrykorwat
387fdd3f38 feat(axelera): use Frigate's AsyncDetectorRunner; drop lock and keep-alives
The synchronous DetectorRunner path serialized run() and needed
20 Hz keep-alive zero frames just to keep the Voyager SDK pipeline
rotating: the SDK pipeline is clocked by the input arrival interval
(measured: 1 s gaps stretch a real frame's latency to ~4 s on the
depth-4 pipeline), and one-frame-in-flight caps multi-camera
throughput. AsyncDetectorRunner (as memryx) sends from the detect
thread and receives on the result thread, and closes the input SHM
buffer on the result side, so the frame copy in the plugin goes away.

- plugin implements send_input()/receive_output()/shutdown() and a
  detect_raw() stub (memryx precedent); content-based pixel-token
  pairing is preserved (primer results carry no token and are dropped).
- object_detection/base.py: route axelera to AsyncDetectorRunner.

With real frames clocking the pipeline, no keep-alives are needed;
the throughput ceiling imposed by one frame in flight is gone.
2026-08-21 12:35:12 +00:00
patrykorwat
2c31cb4550 fix(axelera): accept scheme-less host URLs in model.path
is_url() required an http(s) scheme, so a bare host like
www.example.com/model.axm fell through to the filesystem branch and
raised FileNotFoundError, blocking detector init on a valid remote
model (reviewer P1). A value matching a leading dotted host (or
localhost) with a path is now recognised as a URL and http:// is
prepended at fetch time; relative paths and preset names behave as
before.
2026-08-21 11:46:03 +00:00
patrykorwat
9146350dbe fix(axelera): keep SDK pipeline hot with fast keep-alives
The SDK pipeline is clocked by the input arrival interval: measured ~4 s
real-frame latency at 1 s keep-alive spacing vs ~58 ms back-to-back
(depth-4 pipeline x arrival gap). Gating keep-alives while a request is
outstanding also starved the SDK input source (it waited instead of
yielding) and broke the stream. Short 50 ms keep-alives keep the ring
rotating, so real frames get results in tens of ms; the zeros carry no
request token and are dropped by the content-based pump.
2026-08-21 09:53:34 +00:00
patrykorwat
a50f535238 fix(axelera): no keep-alives while a real frame is outstanding
Keep-alive zeros injected during a pending request queue in the SDK
pipeline ahead of the real frame and delay its result by one slot each:
observed 3 s real-frame latency with 1 s keep-alive spacing vs 14 ms
for back-to-back real frames (and 10 ms delivered by the FIFO path).
The feeder now skips keep-alives whenever a request id is still
outstanding, so real frames are processed back-to-back and true-idle
behaviour (stream kept alive) is preserved.
2026-08-21 09:38:17 +00:00
patrykorwat
fc28ee1892 fix(axelera): content-based pixel-token pairing for results (P1)
The SDK does not tag results with a per-frame id (image_id is always
empty, src_timestamp is a pipeline-clock value), so pairing relied on
FIFO ordering, which a buffered primer/keep-alive result could break:
the stale result would pop the pending id and be delivered to the
wrong camera/crop (synthetic results consuming real IDs).

Every real frame now carries a 32-bit request token written into a
small top-left pixel band; the SDK returns the source frame back 1:1
(verified on hardware: BGR pass-through exact, BGRA), so the pump
decodes the token from the result image and delivers the outcome only
to the frame that carries it (content-based, no ordering assumption).
Primer/keep-alive frames carry no token; their results are dropped
even when they surface while a request is pending.
2026-08-21 08:50:21 +00:00
patrykorwat
780ae97c26 fix(axelera): revert token-based pairing; restore stable 7ms inference
The token-based 1:1 pairing (None token per synthetic frame) is correct in
theory but regressed in production: inference latency went to ~3s and
detections dropped to ~0. Revert to the stable approach that has been
running fine (lock + FIFO + keep-alive): ~7ms inference, detections flowing.

P1 (synthetic results stealing real ids) remains a theoretical concern
under this scheme, but the observed production behavior with it was far
worse. Investigate the regression with per-frame diagnostics before
re-attempting.
2026-08-21 06:51:12 +00:00
patrykorwat
6fd5b136b6 fix(axelera): stop keep-alive frames after pipeline init
Misattribution reproduced at night: when Frigate stops sending frames
during quiet periods the queue empties and keep-alive frames are emitted;
a pending real-frame result then races the keep-alive's (empty) result,
which steals the real id (frame/detections shown under the WRONG camera).

Fix: synthesize keep-alive frames ONLY until the first stream result
(_primed), then never again - no unregistered result can exist in the
pipeline afterwards. Queue-empty after init just waits; under Frigate's
continuous feed this branch is only hit during true quiet periods, where
there is no pending request to misattribute anyway.
2026-08-21 06:08:17 +00:00
patrykorwat
9d8a1a10a4 fix(axelera): revert primer-index-drain; it wedged production startup
The primer 'drain by run index' was too fragile under Frigate's real
workload: with 7 cameras starting simultaneously the first few real frames
arrive before the SDK finishes emitting primer results, their ids were
drained without a result, run() hit the timeout and Frigate restarted the
detect process every ~15s (zero detections).

Revert to the stable approach: keep-alive frames sustain the SDK during
quiet/init, _pump never consumes a pending id while the queue is empty, and
run() is serialized (lock) so at most one real frame is in flight. The
remaining theoretical primer/keep-alive misattribution window only exists
when the queue is empty mid-run, which does not happen under Frigate's
continuous feed.
2026-08-20 21:19:04 +00:00
patrykorwat
13215b0997 fix(axelera): never attribute primer/keep-alive results to a real request
The SDK can buffer primer results and emit them AFTER the first real frame
is already pending; the FIFO pump then popped a real id and stored the
skipped/buffered (empty) result under it, dropping the real one. This
misattributes detections across cameras even with run() serialized.

- eliminate keep-alive frames entirely (Frigate feeds the queue
  continuously; nothing to keep alive) - a synthetic frame's result can
  arrive during a pending real id,
- drain the primer results by index BEFORE any pending-id bookkeeping, so
  they can never consume a request id; the first run() waits up to 0.5s
  for the primer drain.
2026-08-20 20:34:04 +00:00
patrykorwat
71ccf55927 fix(axelera): serialize run() to prevent cross-camera result misattribution
Voyager SDK does not tag results with a per-frame id and can reorder or
buffer stream results under sustained load (observed on live with many
thousands of frames: camera A started receiving boxes/paths from camera B).
With several frames in flight, the FIFO pairing can store a result under a
different frame's id.

run() now holds a lock so at most one frame is in flight; the next stream
result can only belong to the frame just pushed, making pairing exact
regardless of SDK reordering. Pipeline throughput stays far below limits
(~9ms per inference vs 28ms available per frame at 7x5fps).
2026-08-20 19:49:05 +00:00
patrykorwat
8a3483c7a2 fix(axelera): address review comments on frame pairing and model handling
- strict per-frame request/result pairing: the feeder registers a queue of
  pending frame ids, the pump pops one per stream result and stores the
  outcome under it, and run() waits for its own frame's id. Primer and
  keep-alive results are dropped instead of being served to callers.
- copy frames out of shared memory before queuing them (detect_raw returns
  before the feeder consumes the frame, unmapping the camera buffer).
- raise on inference timeout so the watchdog can restart a stalled stream
  instead of silently serving zeros.
- use ModelDownloader.download_from_url for URL models (.part file, atomic
  rename, progress events); drop the unused AX_DEFAULT_URL constant.
- is_url() requires a scheme; treat www.-style values as paths and fail with
  a clear FileNotFoundError instead of a deep urlretrieve error, mirroring
  hailo8l behavior for missing model files.
- guard detections against numpy arrays, drop the dead score initialization,
  floor normalized box coords at 0, move the model-required check before
  super().__init__(), and document detect_raw/close.
- deduplicate the model-compilation walkthrough in docs (keep installation.md
  as the single source; object_detectors.md links to it).
2026-08-20 07:34:40 +00:00
patrykorwat
7bf9cb4631 fix(axelera): correct preset name to yolox-s-coco-onnx
The SDK model-zoo network name in create_inference_stream is
yolox-s-coco-onnx (yolox-s-coco is only the axzoo recipe tag).
2026-08-19 21:15:48 +00:00
patrykorwat
0526fcc933 docs(axelera): switch model dropdown to yolox-s-coco (Apache-2.0) 2026-08-19 21:04:37 +00:00
patrykorwat
2bfd8f5707 fix(axelera): default to Apache-2.0 YOLOX model, drop AGPL YOLO references
Frigate (MIT) cannot reference Ultralytics YOLO models (AGPL-3.0), see
blakeblackshear/frigate#10717. The plugin now defaults to the SDK model-zoo
preset yolox-s-coco (Megvii YOLOX, Apache-2.0), docs updated accordingly.
2026-08-19 20:45:38 +00:00
patrykorwat
c84e1e1952 Revert "feat(axelera): busy time as npu usage in npu_usages"
This reverts commit 1ae0843c00a699a510d7dcffd5ecb0c50764b564.
2026-08-19 20:18:01 +00:00
patrykorwat
1ae0843c00 feat(axelera): busy time as npu usage in npu_usages
The Metis has no utilization readout without the axsystemserver broker,
which cannot run alongside a live detector. Report the worker busy time
(fps * inference time) as a real load indicator instead, plus the board
temperature, under npu_usages[axelera].
2026-08-19 20:05:58 +00:00
patrykorwat
24dc4edf51 feat(axelera): include Metis board temperature in npu_usages
Exposes the Metis temperature read (axcmd --board-temp) under
npu_usages so the /api/stats endpoint lists the NPU instead of
returning null. There is no utilization readout without the
axsystemserver broker, which cannot run alongside a live detector.
2026-08-19 13:11:46 +00:00
patrykorwat
f1eef0da66 feat(axelera): report Metis board temperature in detector stats
Use the SDK's axcmd --board-temp through the board controller, which is
safe to call while an inference stream is active. Matches the hailo8l
pattern of exposing temperature via detectors.*.temperature.
2026-08-19 11:35:26 +00:00
patrykorwat
ed1329c8de fix(axelera): fail fast instead of blocking when inference stream wedges
Memory audit: all hot-path structures are bounded (frame queue maxsize 64,
latest result is a single 20x6 buffer, rows capped at MAX_ROWS), so there is
no unbounded growth in steady state.

One resource hazard: run() used a blocking queue.put() with no guard. If the
pump thread dies (SDK stream exception, e.g. DEVICE_LOST), the SDK side stops
draining the queue, the bounded queue fills, and every subsequent run() blocks
forever on put, permanently wedging the detector (Frigate does not restart the
detector process on its own).

Fix: check thread liveness before put and raise immediately, and use put_nowait
(backpressure: drop the frame on a full queue) so run() always falls through to
the latest-result wait / timeout instead of blocking.
2026-08-18 11:13:39 +00:00
patrykorwat
2e20beee95 docs(axelera): concrete Docker image recipe and model download guide
* installation.md: replace the hand-waved 'custom image' note with a
  reference Dockerfile that installs the Voyager SDK into the image
  (apt deps, axelera-rt/devkit from the Axelera index, make operators)
  and keeps the /dev/metis passthrough instructions.
* object_detectors.md: document axelera model sourcing under
  'Downloading YOLO Models' (model-zoo preset, axzoo/axcompile, HF
  hosting, default model auto-download).
2026-08-18 10:58:15 +00:00
patrykorwat
c43a95bfe2 style(axelera): collapse single-line raise per ruff format
Matches the pinned ruff 0.15.20 used by upstream CI
(docker/main/requirements-dev.txt).
2026-08-18 10:37:37 +00:00
patrykorwat
5fa8bf75dd test: drop axelera detector unit tests to match plugin conventions
Other detector plugins shipping in this tree do not carry unit tests, and
the fixed-sleep timing in these tests (primer, keepalive, pump teardown)
is prone to flaking on CI runners. The regression coverage stays out of
tree beside the detector host worker.
2026-08-18 10:24:14 +00:00
patrykorwat
10b4394879 fix(axelera): emit normalized box coordinates (0..1)
Frigate detect() interprets detector boxes as fractions of the region and
discards any box resolving outside the frame. The plugin returned pixel
coordinates, so every detection was silently filtered out and no events
were ever created despite a working inference chain. Normalize by model
width/height (same contract as memryx/onnx). Live: 0 events -> 27 events
in ~70 s after the fix.
2026-08-18 06:48:23 +00:00
patrykorwat
f17b5e95e3 fix(axelera): return latest completed result instead of strict request pairing
Per-frame request/result pairing (RequestStore/ResponseStore FIFO)
desynchronises on the live chain once keep-alive results interleave with
real frames: a request could receive a zero-frame result, and real results
got attributed to the wrong frame (recurring identical scores across
different frames in production). The pump now keeps the most recent
completed result and run() waits for a result newer than the sequence at
call time, then returns it. Frigate correlates objects across frames, so
per-frame identity is not needed. Worker engine mirrored.
2026-08-17 20:56:31 +00:00
patrykorwat
f3b97c27e9 fix(axelera): tolerate numpy-array boxes from SDK
The SDK returns detection boxes as numpy arrays on some frames; the
'list(box or [])' idiom evaluated array truthiness and raised ValueError,
killing the pump thread mid-detection. Extract boxes defensively. Live
repro: worker crashed the moment a real detection fired, so no event was
ever recorded.
2026-08-17 20:20:00 +00:00
patrykorwat
9b731a14e9 fix(axelera): accept batched NHWC frames (1, H, W, C) from Frigate
Frigate feeds detect_raw() a batched NHWC frame (1, H, W, C); the plugin
raised ValueError for ndim != 3, so every real inference failed. Squeeze
the leading batch dim when present (same contract as other IPU plugins,
e.g. memryx). Regression test added.
2026-08-17 19:58:08 +00:00
patrykorwat
d0537684c1 Add Axelera Metis NPU detector plugin (type: axelera)
Community plugin for the Axelera AI Metis PCIe/M.2 NPU, following the same
conventions as axengine.py / hailo8l.py (lazy SDK import,
RequestStore/ResponseStore, model path as URL/local-file/model-zoo preset,
type_key auto-registration).

Inference runs through a single Voyager SDK stream
(create_inference_stream(network=..., sources=[...])) fed with numpy frames;
detection labels are mapped to class ids by name via the merged labelmap.
Compiled .axm models can be downloaded into the model cache from a URL, used
from a local path, or omitted to fall back to the yolov8n-coco zoo preset.

Includes unit tests (unittest, no SDK/hardware required) covering the engine
round-trip, timeout, dead-stream and orphan behaviour plus model resolution,
and docs for the object detector page and the "Extra steps for specific
hardware" installation page.
2026-08-17 18:39:56 +00:00
Josh Hawkins
8425a76558
Miscellaneous fixes (0.18 beta) (#23993)
* subscribe to add in webpush

* add docs for detector cpu usage

* rebuild notification camera access when a camera is added at runtime

* document how frigate shows CPU usage metrics

* add faq about version key in config
2026-08-16 12:39:28 -06:00
Josh Hawkins
11f8786459
sanitize user-supplied path components (#23990)
sanitize_filename leaves ".." intact and collapses variants like "..:" and "..*" to "..", so filesystem paths built from face names, classification model/category names, image ids, and trigger data could escape their base directory. Route every such site through new frigate/util/path.py helpers (safe_join, sanitize_path_component, sanitize_contained_path), which reject traversal and verify containment.

Worst case was DELETE /classification/{name}, which rmtree'd /media/frigate and /config while returning 200.

Important to note that all affected endpoints already require admin permission, so this sould be considered hardening rather than fixing exploitable code.
2026-08-13 21:59:46 -05:00
Josh Hawkins
812e5308a3
fix notification suspend state lost on page reload (#23989)
<camera>/notifications/suspended arrives as a string over the live connection but as a number in the camera_activity snapshot, and the truthiness guard dropped the numeric 0, so a camera with notifications off rendered as active after a reload. Normalize to a string and derive isSuspended instead of storing it.
2026-08-13 16:51:44 -06:00
Josh Hawkins
fd98977506
Categorize manual events as alerts when their label is an alert label (#23981)
* Categorize manual events as alerts when their label is an alert label

* tweak docs
2026-08-13 11:16:02 -06:00
Larosen
6816050a46
fix(audio): correct sodeling typo to yodeling (#23946)
* fix(audio): correct sodeling typo to yodeling

Fixes a typo in audio-labelmap.txt where the yodeling class was
misspelled as "sodeling".

* fix(i18n): remove duplicate sodeling key in en audio.json

The en audio.json already contains a correct "yodeling" key. Remove
the duplicate/misspelled "sodeling" entry to avoid ambiguity.
2026-08-13 07:02:38 -05:00
Josh Hawkins
c70a0802b8
filter dedicated LPR plates before creating the event (#23977) 2026-08-13 05:44:31 -06:00
Josh Hawkins
aff9799451
Don't require a restart to enable GenAI descriptions (#23964)
* create GenAI post processors when a camera enables GenAI at runtime

* fix types
2026-08-12 08:55:34 -05:00
Josh Hawkins
c75611b4df
Multi-export UI fixes (#23959)
* multi export fixes

* i18n

* new tests
2026-08-11 10:11:47 -06:00
Josh Hawkins
0735a8ac75
Docs updates (#23947)
* misc docs updates

* add warning about proxies to 5000 for notifications
2026-08-10 15:54:41 -06:00
Josh Hawkins
2599795ab0
add faq to notifications docs (#23939) 2026-08-08 11:12:13 -06:00
Josh Hawkins
344efb6bc1
Miscellaneous fixes (0.18 beta) (#23934)
* add host npu requirements to docs

* allow toggling live audio transcription via mqtt

* improve spacing consistency on mobile drawers

* fix clearing the region grid not surviving a restart
v0.18.0-beta3
2026-08-08 07:20:09 -06:00