Quick fixes (#17639)

* Use mobile drawer for face selection

* Convert face selection to separate component

* Cleanup dialogs

* Add FAQ for record resolution

* Update image name

* Remove unused

* Cleanup
This commit is contained in:
Nicolas Mowen 2025-04-10 15:33:51 -06:00 committed by GitHub
parent 64db518837
commit 30ac868757
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 151 additions and 75 deletions

View File

@ -129,3 +129,10 @@ The Frigate considers the recognition scores across all recognition attempts for
### Can I use other face recognition software like DoubleTake at the same time as the built in face recognition?
No, using another face recognition service will interfere with Frigate's built in face recognition. When using double-take the sub_label feature must be disabled if the built in face recognition is also desired.
### Does face recognition run on the recording stream?
Face recognition does not run on the recording stream, this would be suboptimal for many reasons:
1. The latency of accessing the recordings means the notifications would not include the names of recognized people because recognition would not complete until after.
2. The embedding models used run on a set image size, so larger images will be scaled down to match this anyway.
3. Motion clarity is much more important than extra pixels, over-compression and motion blur are much more detrimental to results than resolution.

View File

@ -295,8 +295,7 @@ These instructions were originally based on the [Jellyfin documentation](https:/
## NVIDIA Jetson (Orin AGX, Orin NX, Orin Nano\*, Xavier AGX, Xavier NX, TX2, TX1, Nano)
A separate set of docker images is available that is based on Jetpack/L4T. They come with an `ffmpeg` build
with codecs that use the Jetson's dedicated media engine. If your Jetson host is running Jetpack 5.0+ use the `stable-tensorrt-jp5`
tagged image, or if your Jetson host is running Jetpack 6.0+ use the `stable-tensorrt-jp6` tagged image. Note that the Orin Nano has no video encoder, so frigate will use software encoding on this platform, but the image will still allow hardware decoding and tensorrt object detection.
with codecs that use the Jetson's dedicated media engine. If your Jetson host is running Jetpack 6.0+ use the `stable-tensorrt-jp6` tagged image. Note that the Orin Nano has no video encoder, so frigate will use software encoding on this platform, but the image will still allow hardware decoding and tensorrt object detection.
You will need to use the image with the nvidia container runtime:
@ -306,7 +305,7 @@ You will need to use the image with the nvidia container runtime:
docker run -d \
...
--runtime nvidia
ghcr.io/blakeblackshear/frigate:stable-tensorrt-jp5
ghcr.io/blakeblackshear/frigate:stable-tensorrt-jp6
```
### Docker Compose - Jetson
@ -315,7 +314,7 @@ docker run -d \
services:
frigate:
...
image: ghcr.io/blakeblackshear/frigate:stable-tensorrt-jp5
image: ghcr.io/blakeblackshear/frigate:stable-tensorrt-jp6
runtime: nvidia # Add this
```

View File

@ -27,7 +27,7 @@ Frigate supports multiple different detectors that work on different types of ha
**Nvidia**
- [TensortRT](#nvidia-tensorrt-detector): TensorRT can run on Nvidia GPUs and Jetson devices, using one of many default models.
- [ONNX](#onnx): TensorRT will automatically be detected and used as a detector in the `-tensorrt` or `-tensorrt-jp(4/5)` Frigate images when a supported ONNX model is configured.
- [ONNX](#onnx): TensorRT will automatically be detected and used as a detector in the `-tensorrt` or `-tensorrt-jp6` Frigate images when a supported ONNX model is configured.
**Rockchip**

View File

@ -0,0 +1,117 @@
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/components/ui/drawer";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { isDesktop, isMobile } from "react-device-detect";
import { LuPlus, LuScanFace } from "react-icons/lu";
import { useTranslation } from "react-i18next";
import { cn } from "@/lib/utils";
import React, { ReactNode, useMemo, useState } from "react";
import TextEntryDialog from "./dialog/TextEntryDialog";
import { Button } from "../ui/button";
type FaceSelectionDialogProps = {
className?: string;
faceNames: string[];
onTrainAttempt: (name: string) => void;
children: ReactNode;
};
export default function FaceSelectionDialog({
className,
faceNames,
onTrainAttempt,
children,
}: FaceSelectionDialogProps) {
const { t } = useTranslation(["views/faceLibrary"]);
const isChildButton = useMemo(
() => React.isValidElement(children) && children.type === Button,
[children],
);
// control
const [newFace, setNewFace] = useState(false);
// components
const Selector = isDesktop ? DropdownMenu : Drawer;
const SelectorTrigger = isDesktop ? DropdownMenuTrigger : DrawerTrigger;
const SelectorContent = isDesktop ? DropdownMenuContent : DrawerContent;
const SelectorItem = isDesktop ? DropdownMenuItem : DrawerClose;
return (
<div className={className ?? ""}>
{newFace && (
<TextEntryDialog
open={true}
setOpen={setNewFace}
title={t("createFaceLibrary.new")}
onSave={(newName) => onTrainAttempt(newName)}
/>
)}
<Tooltip>
<Selector>
<SelectorTrigger asChild>
<TooltipTrigger asChild={isChildButton}>{children}</TooltipTrigger>
</SelectorTrigger>
<SelectorContent
className={cn(
"max-h-[75dvh] overflow-hidden",
isMobile && "mx-1 gap-2 rounded-t-2xl px-4",
)}
>
{isMobile && (
<DrawerHeader className="sr-only">
<DrawerTitle>Log Details</DrawerTitle>
<DrawerDescription>Log details</DrawerDescription>
</DrawerHeader>
)}
<DropdownMenuLabel>{t("trainFaceAs")}</DropdownMenuLabel>
<div
className={cn(
"flex flex-col",
isMobile && "gap-2 overflow-y-auto pb-4",
)}
>
<SelectorItem
className="flex cursor-pointer gap-2 capitalize"
onClick={() => setNewFace(true)}
>
<LuPlus />
{t("createFaceLibrary.new")}
</SelectorItem>
{faceNames.map((faceName) => (
<SelectorItem
key={faceName}
className="flex cursor-pointer gap-2 capitalize"
onClick={() => onTrainAttempt(faceName)}
>
<LuScanFace />
{faceName}
</SelectorItem>
))}
</div>
</SelectorContent>
</Selector>
<TooltipContent>{t("trainFace")}</TooltipContent>
</Tooltip>
</div>
);
}

View File

@ -57,7 +57,6 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
@ -77,6 +76,7 @@ import TextEntryDialog from "@/components/overlay/dialog/TextEntryDialog";
import { useTranslation } from "react-i18next";
import { TbFaceId } from "react-icons/tb";
import { useIsAdmin } from "@/hooks/use-is-admin";
import FaceSelectionDialog from "../FaceSelectionDialog";
const SEARCH_TABS = [
"details",
@ -844,30 +844,18 @@ function ObjectDetailsTab({
</Button>
)}
{hasFace && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button className="w-full">
<div className="flex gap-1">
<TbFaceId />
{t("trainFace", { ns: "views/faceLibrary" })}
</div>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>
{t("trainFaceAs", { ns: "views/faceLibrary" })}
</DropdownMenuLabel>
{faceNames.map((faceName) => (
<DropdownMenuItem
key={faceName}
className="cursor-pointer capitalize"
onClick={() => onTrainFace(faceName)}
>
{faceName}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
<FaceSelectionDialog
className="w-full"
faceNames={faceNames}
onTrainAttempt={onTrainFace}
>
<Button className="w-full">
<div className="flex gap-1">
<TbFaceId />
{t("trainFace", { ns: "views/faceLibrary" })}
</div>
</Button>
</FaceSelectionDialog>
)}
</div>
</div>

View File

@ -8,6 +8,8 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { cn } from "@/lib/utils";
import { isMobile } from "react-device-detect";
import { useTranslation } from "react-i18next";
type TextEntryDialogProps = {
@ -43,7 +45,7 @@ export default function TextEntryDialog({
allowEmpty={allowEmpty}
onSave={onSave}
>
<DialogFooter className="pt-4">
<DialogFooter className={cn("pt-4", isMobile && "gap-2")}>
<Button type="button" onClick={() => setOpen(false)}>
{t("button.cancel")}
</Button>

View File

@ -3,8 +3,8 @@ import TimeAgo from "@/components/dynamic/TimeAgo";
import AddFaceIcon from "@/components/icons/AddFaceIcon";
import ActivityIndicator from "@/components/indicators/activity-indicator";
import CreateFaceWizardDialog from "@/components/overlay/detail/FaceCreateWizardDialog";
import TextEntryDialog from "@/components/overlay/dialog/TextEntryDialog";
import UploadImageDialog from "@/components/overlay/dialog/UploadImageDialog";
import FaceSelectionDialog from "@/components/overlay/FaceSelectionDialog";
import { Button } from "@/components/ui/button";
import {
Dialog,
@ -17,7 +17,6 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuTrigger,
DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu";
@ -42,7 +41,6 @@ import { isDesktop, isMobile } from "react-device-detect";
import { useTranslation } from "react-i18next";
import {
LuImagePlus,
LuPlus,
LuRefreshCw,
LuScanFace,
LuSearch,
@ -783,8 +781,6 @@ function FaceAttempt({
// interaction
const [newFace, setNewFace] = useState(false);
const imgRef = useRef<HTMLImageElement | null>(null);
useContextMenu(imgRef, () => {
@ -844,15 +840,6 @@ function FaceAttempt({
return (
<>
{newFace && (
<TextEntryDialog
open={true}
setOpen={setNewFace}
title={t("createFaceLibrary.new")}
onSave={(newName) => onTrainAttempt(newName)}
/>
)}
<div
className={cn(
"relative flex cursor-pointer flex-col rounded-lg outline outline-[3px]",
@ -895,36 +882,12 @@ function FaceAttempt({
</div>
</div>
<div className="flex flex-row items-start justify-end gap-5 md:gap-4">
<Tooltip>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<TooltipTrigger>
<AddFaceIcon className="size-5 cursor-pointer text-primary-variant hover:text-primary" />
</TooltipTrigger>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>{t("trainFaceAs")}</DropdownMenuLabel>
<DropdownMenuItem
className="flex cursor-pointer gap-2 capitalize"
onClick={() => setNewFace(true)}
>
<LuPlus />
{t("createFaceLibrary.new")}
</DropdownMenuItem>
{faceNames.map((faceName) => (
<DropdownMenuItem
key={faceName}
className="flex cursor-pointer gap-2 capitalize"
onClick={() => onTrainAttempt(faceName)}
>
<LuScanFace />
{faceName}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
<TooltipContent>{t("trainFace")}</TooltipContent>
</Tooltip>
<FaceSelectionDialog
faceNames={faceNames}
onTrainAttempt={onTrainAttempt}
>
<AddFaceIcon className="size-5 cursor-pointer text-primary-variant hover:text-primary" />
</FaceSelectionDialog>
<Tooltip>
<TooltipTrigger>
<LuRefreshCw