Compare commits

..

No commits in common. "3b2d136665dc17ae8fc5fcf7711725d17bc1f1c4" and "256817d5c2640b4cd1167272ec606d6bbf9eb772" have entirely different histories.

8 changed files with 20 additions and 72 deletions

View File

@ -1,8 +1,5 @@
{ {
"documentTitle": "Classification Models", "documentTitle": "Classification Models",
"details": {
"scoreInfo": "Score represents the average classification confidence across all detections of this object."
},
"button": { "button": {
"deleteClassificationAttempts": "Delete Classification Images", "deleteClassificationAttempts": "Delete Classification Images",
"renameCategory": "Rename Class", "renameCategory": "Rename Class",

View File

@ -6,8 +6,7 @@
}, },
"details": { "details": {
"timestamp": "Timestamp", "timestamp": "Timestamp",
"unknown": "Unknown", "unknown": "Unknown"
"scoreInfo": "Score is a weighted average of all face scores, weighted by the size of the face in each image."
}, },
"documentTitle": "Face Library - Frigate", "documentTitle": "Face Library - Frigate",
"uploadFaceImage": { "uploadFaceImage": {

View File

@ -11,8 +11,7 @@ import { isDesktop, isMobile } from "react-device-detect";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import TimeAgo from "../dynamic/TimeAgo"; import TimeAgo from "../dynamic/TimeAgo";
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; import { LuSearch } from "react-icons/lu";
import { LuSearch, LuInfo } from "react-icons/lu";
import { TooltipPortal } from "@radix-ui/react-tooltip"; import { TooltipPortal } from "@radix-ui/react-tooltip";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { HiSquare2Stack } from "react-icons/hi2"; import { HiSquare2Stack } from "react-icons/hi2";
@ -322,31 +321,14 @@ export function GroupedClassificationCard({
? event.sub_label ? event.sub_label
: t(noClassificationLabel)} : t(noClassificationLabel)}
{event?.sub_label && event.sub_label !== "none" && ( {event?.sub_label && event.sub_label !== "none" && (
<div className="flex items-center gap-1"> <div
<div className={cn(
className={cn( "",
"", bestScoreStatus == "match" && "text-success",
bestScoreStatus == "match" && "text-success", bestScoreStatus == "potential" && "text-orange-400",
bestScoreStatus == "potential" && "text-orange-400", bestScoreStatus == "unknown" && "text-danger",
bestScoreStatus == "unknown" && "text-danger", )}
)} >{`${Math.round((event.data.sub_label_score || 0) * 100)}%`}</div>
>{`${Math.round((event.data.sub_label_score || 0) * 100)}%`}</div>
<Popover>
<PopoverTrigger asChild>
<button
className="focus:outline-none"
aria-label={t("details.scoreInfo", {
ns: i18nLibrary,
})}
>
<LuInfo className="size-3" />
</button>
</PopoverTrigger>
<PopoverContent className="w-80 text-sm">
{t("details.scoreInfo", { ns: i18nLibrary })}
</PopoverContent>
</Popover>
</div>
)} )}
</ContentTitle> </ContentTitle>
<ContentDescription className={cn("", isMobile && "px-2")}> <ContentDescription className={cn("", isMobile && "px-2")}>

View File

@ -37,7 +37,6 @@ import { capitalizeFirstLetter } from "@/utils/stringUtil";
import { Button, buttonVariants } from "../ui/button"; import { Button, buttonVariants } from "../ui/button";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { LuCircle } from "react-icons/lu";
type ReviewCardProps = { type ReviewCardProps = {
event: ReviewSegment; event: ReviewSegment;
@ -143,7 +142,7 @@ export default function ReviewCard({
className={cn( className={cn(
"size-full rounded-lg", "size-full rounded-lg",
activeReviewItem?.id == event.id && activeReviewItem?.id == event.id &&
"outline outline-[3px] -outline-offset-[2.8px] outline-selected duration-200", "outline outline-[3px] outline-offset-1 outline-selected",
imgLoaded ? "visible" : "invisible", imgLoaded ? "visible" : "invisible",
)} )}
src={`${baseUrl}${event.thumb_path.replace("/media/frigate/", "")}`} src={`${baseUrl}${event.thumb_path.replace("/media/frigate/", "")}`}
@ -166,14 +165,6 @@ export default function ReviewCard({
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div className="flex items-center justify-evenly gap-1"> <div className="flex items-center justify-evenly gap-1">
<> <>
<LuCircle
className={cn(
"size-2",
event.severity == "alert"
? "fill-severity_alert text-severity_alert"
: "fill-severity_detection text-severity_detection",
)}
/>
{event.data.objects.map((object) => { {event.data.objects.map((object) => {
return getIconForLabel( return getIconForLabel(
object, object,

View File

@ -8,7 +8,7 @@ import {
FormMessage, FormMessage,
} from "@/components/ui/form"; } from "@/components/ui/form";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { useState, useEffect, useRef } from "react"; import { useState, useEffect } from "react";
import { useFormContext } from "react-hook-form"; import { useFormContext } from "react-hook-form";
import { generateFixedHash, isValidId } from "@/utils/stringUtil"; import { generateFixedHash, isValidId } from "@/utils/stringUtil";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -41,9 +41,8 @@ export default function NameAndIdFields<T extends FieldValues = FieldValues>({
placeholderId, placeholderId,
}: NameAndIdFieldsProps<T>) { }: NameAndIdFieldsProps<T>) {
const { t } = useTranslation(["common"]); const { t } = useTranslation(["common"]);
const { watch, setValue, trigger, formState } = useFormContext<T>(); const { watch, setValue, trigger } = useFormContext<T>();
const [isIdVisible, setIsIdVisible] = useState(false); const [isIdVisible, setIsIdVisible] = useState(false);
const hasUserTypedRef = useRef(false);
const defaultProcessId = (name: string) => { const defaultProcessId = (name: string) => {
const normalized = name.replace(/\s+/g, "_").toLowerCase(); const normalized = name.replace(/\s+/g, "_").toLowerCase();
@ -59,7 +58,6 @@ export default function NameAndIdFields<T extends FieldValues = FieldValues>({
useEffect(() => { useEffect(() => {
const subscription = watch((value, { name }) => { const subscription = watch((value, { name }) => {
if (name === nameField) { if (name === nameField) {
hasUserTypedRef.current = true;
const processedId = effectiveProcessId(value[nameField] || ""); const processedId = effectiveProcessId(value[nameField] || "");
setValue(idField, processedId as PathValue<T, Path<T>>); setValue(idField, processedId as PathValue<T, Path<T>>);
trigger(idField); trigger(idField);
@ -68,14 +66,6 @@ export default function NameAndIdFields<T extends FieldValues = FieldValues>({
return () => subscription.unsubscribe(); return () => subscription.unsubscribe();
}, [watch, setValue, trigger, nameField, idField, effectiveProcessId]); }, [watch, setValue, trigger, nameField, idField, effectiveProcessId]);
// Auto-expand if there's an error on the ID field after user has typed
useEffect(() => {
const idError = formState.errors[idField];
if (idError && hasUserTypedRef.current && !isIdVisible) {
setIsIdVisible(true);
}
}, [formState.errors, idField, isIdVisible]);
return ( return (
<> <>
<FormField <FormField

View File

@ -289,7 +289,6 @@ export default function VideoControls({
}} }}
onUploadFrame={onUploadFrame} onUploadFrame={onUploadFrame}
containerRef={containerRef} containerRef={containerRef}
fullscreen={fullscreen}
/> />
)} )}
{features.fullscreen && toggleFullscreen && ( {features.fullscreen && toggleFullscreen && (
@ -307,7 +306,6 @@ type FrigatePlusUploadButtonProps = {
onClose: () => void; onClose: () => void;
onUploadFrame: () => void; onUploadFrame: () => void;
containerRef?: React.MutableRefObject<HTMLDivElement | null>; containerRef?: React.MutableRefObject<HTMLDivElement | null>;
fullscreen?: boolean;
}; };
function FrigatePlusUploadButton({ function FrigatePlusUploadButton({
video, video,
@ -315,7 +313,6 @@ function FrigatePlusUploadButton({
onClose, onClose,
onUploadFrame, onUploadFrame,
containerRef, containerRef,
fullscreen,
}: FrigatePlusUploadButtonProps) { }: FrigatePlusUploadButtonProps) {
const { t } = useTranslation(["components/player"]); const { t } = useTranslation(["components/player"]);
@ -352,11 +349,7 @@ function FrigatePlusUploadButton({
/> />
</AlertDialogTrigger> </AlertDialogTrigger>
<AlertDialogContent <AlertDialogContent
portalProps={ portalProps={{ container: containerRef?.current }}
fullscreen && containerRef?.current
? { container: containerRef.current }
: undefined
}
className="md:max-w-2xl lg:max-w-3xl xl:max-w-4xl" className="md:max-w-2xl lg:max-w-3xl xl:max-w-4xl"
> >
<AlertDialogHeader> <AlertDialogHeader>

View File

@ -367,11 +367,7 @@ function ReviewGroup({
return ( return (
<div <div
data-review-id={id} data-review-id={id}
className={`mx-1 cursor-pointer rounded-lg bg-secondary px-0 py-3 outline outline-[2px] -outline-offset-[1.8px] ${ className="cursor-pointer rounded-lg bg-secondary py-3"
isActive
? "shadow-selected outline-selected"
: "outline-transparent duration-500"
}`}
> >
<div <div
className={cn( className={cn(
@ -386,10 +382,10 @@ function ReviewGroup({
<div className="ml-4 mr-2 mt-1.5 flex flex-row items-start"> <div className="ml-4 mr-2 mt-1.5 flex flex-row items-start">
<LuCircle <LuCircle
className={cn( className={cn(
"size-3 duration-500", "size-3",
review.severity == "alert" isActive
? "fill-severity_alert text-severity_alert" ? "fill-selected text-selected"
: "fill-severity_detection text-severity_detection", : "fill-muted duration-500 dark:fill-secondary-highlight dark:text-secondary-highlight",
)} )}
/> />
</div> </div>

View File

@ -43,5 +43,5 @@ export function generateFixedHash(name: string, prefix: string = "id"): string {
* @returns True if the name is valid, false otherwise * @returns True if the name is valid, false otherwise
*/ */
export function isValidId(name: string): boolean { export function isValidId(name: string): boolean {
return /^[a-zA-Z0-9_-]+$/.test(name) && !/^\d+$/.test(name); return /^[a-zA-Z0-9_-]+$/.test(name);
} }