Miscellaneous Fixes (#20841)
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
CI / AMD64 Build (push) Waiting to run

* show id field when editing zone

* improve zone capitalization

* Update NPU models and docs

* fix mobilepage in tracked object details

* Use thread lock for openvino to avoid concurrent requests with JinaV2

* fix hashing function to avoid collisions

* remove extra flex div causing overflow

* ensure header stays on top of video controls

* don't smart capitalize friendly names

* Fix incorrect object classification crop

* don't display submit to plus if object doesn't have a snapshot

* check for snapshot and clip in actions menu

* frigate plus submission fix

still show frigate+ section if snapshot has already been submitted and run optimistic update, local state was being overridden

* Don't fail to show 0% when showing classification

* Don't fail on file system error

* Improve title and description for review genai

* fix overflowing truncated review item description in detail stream

* catch events with review items that start after the first timeline entry

review items may start later than events within them, so subtract a padding from the start time in the filter so the start of events are not incorrectly filtered out of the list in the detail stream

* also pad on review end_time

* fix

* change order of timeline zoom buttons on mobile

* use grid to ensure genai title does not cause overflow

* small tweaks

* Cleanup

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
This commit is contained in:
Josh Hawkins
2025-11-08 05:44:30 -07:00
committed by GitHub
co-authored by Nicolas Mowen
parent ef19332fe5
commit 01452e4c51
15 changed files with 232 additions and 132 deletions
@@ -148,13 +148,13 @@ export const ClassificationCard = forwardRef<
<div
className={cn(
"flex flex-col items-start text-white",
data.score ? "text-xs" : "text-sm",
data.score != undefined ? "text-xs" : "text-sm",
)}
>
<div className="smart-capitalize">
{data.name == "unknown" ? t("details.unknown") : data.name}
</div>
{data.score && (
{data.score != undefined && (
<div
className={cn(
"",
@@ -55,29 +55,32 @@ export default function DetailActionsMenu({
</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent align="end">
<DropdownMenuItem>
<a
className="w-full"
href={`${baseUrl}api/events/${search.id}/snapshot.jpg?bbox=1`}
download={`${search.camera}_${search.label}.jpg`}
>
<div className="flex cursor-pointer items-center gap-2">
<span>{t("itemMenu.downloadSnapshot.label")}</span>
</div>
</a>
</DropdownMenuItem>
<DropdownMenuItem>
<a
className="w-full"
href={`${baseUrl}api/${search.camera}/${clipTimeRange}/clip.mp4`}
download
>
<div className="flex cursor-pointer items-center gap-2">
<span>{t("itemMenu.downloadVideo.label")}</span>
</div>
</a>
</DropdownMenuItem>
{search.has_snapshot && (
<DropdownMenuItem>
<a
className="w-full"
href={`${baseUrl}api/events/${search.id}/snapshot.jpg?bbox=1`}
download={`${search.camera}_${search.label}.jpg`}
>
<div className="flex cursor-pointer items-center gap-2">
<span>{t("itemMenu.downloadSnapshot.label")}</span>
</div>
</a>
</DropdownMenuItem>
)}
{search.has_clip && (
<DropdownMenuItem>
<a
className="w-full"
href={`${baseUrl}api/${search.camera}/${clipTimeRange}/clip.mp4`}
download
>
<div className="flex cursor-pointer items-center gap-2">
<span>{t("itemMenu.downloadVideo.label")}</span>
</div>
</a>
</DropdownMenuItem>
)}
{config?.semantic_search.enabled &&
setSimilarity != undefined &&
@@ -72,7 +72,12 @@ import {
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer";
import {
Drawer,
DrawerContent,
DrawerTitle,
DrawerTrigger,
} from "@/components/ui/drawer";
import { LuInfo } from "react-icons/lu";
import { TooltipPortal } from "@radix-ui/react-tooltip";
import { FaPencilAlt } from "react-icons/fa";
@@ -126,7 +131,7 @@ function TabsWithActions({
return (
<div className="flex items-center justify-between gap-1">
<ScrollArea className="flex-1 whitespace-nowrap">
<div className="mb-2 flex flex-row md:mb-0">
<div className="mb-2 flex flex-row">
<ToggleGroup
className="*:rounded-md *:px-3 *:py-4"
type="single"
@@ -224,6 +229,7 @@ function AnnotationSettings({
const Overlay = isDesktop ? Popover : Drawer;
const Trigger = isDesktop ? PopoverTrigger : DrawerTrigger;
const Content = isDesktop ? PopoverContent : DrawerContent;
const Title = isDesktop ? "div" : DrawerTitle;
const contentProps = isDesktop
? { align: "end" as const, container: container ?? undefined }
: {};
@@ -248,7 +254,9 @@ function AnnotationSettings({
<PiSlidersHorizontalBold className="size-5" />
</Button>
</Trigger>
<Title className="sr-only">
{t("trackingDetails.adjustAnnotationSettings")}
</Title>
<Content
className={
isDesktop
@@ -306,7 +314,7 @@ function DialogContentComponent({
if (page === "tracking_details") {
return (
<TrackingDetails
className={cn("size-full", !isDesktop && "flex flex-col gap-4")}
className={cn(isDesktop ? "size-full" : "flex flex-col gap-4")}
event={search as unknown as Event}
tabs={
isDesktop ? (
@@ -340,7 +348,7 @@ function DialogContentComponent({
}
/>
) : (
<div className={cn(!isDesktop ? "mb-4 w-full" : "size-full")}>
<div className={cn(!isDesktop ? "mb-4 w-full md:max-w-lg" : "size-full")}>
<img
className="w-full select-none rounded-lg object-contain transition-opacity"
style={
@@ -584,8 +592,13 @@ export default function SearchDetailDialog({
"scrollbar-container overflow-y-auto",
isDesktop &&
"max-h-[95dvh] sm:max-w-xl md:max-w-4xl lg:max-w-[70%]",
isMobile && "px-4",
isMobile && "flex h-full flex-col px-4",
)}
onEscapeKeyDown={(event) => {
if (isPopoverOpen) {
event.preventDefault();
}
}}
onInteractOutside={(e) => {
if (isPopoverOpen) {
e.preventDefault();
@@ -596,7 +609,7 @@ export default function SearchDetailDialog({
}
}}
>
<Header>
<Header className={cn(!isDesktop && "top-0 z-[60] mb-0")}>
<Title>{t("trackedObjectDetails")}</Title>
<Description className="sr-only">
{t("trackedObjectDetails")}
@@ -1078,12 +1091,31 @@ function ObjectDetailsTab({
});
setState("submitted");
setSearch({
...search,
plus_id: "new_upload",
});
mutate(
(key) =>
typeof key === "string" &&
(key.includes("events") ||
key.includes("events/search") ||
key.includes("events/explore")),
(currentData: SearchResult[][] | SearchResult[] | undefined) => {
if (!currentData) return currentData;
// optimistic update
return currentData
.flat()
.map((event) =>
event.id === search.id
? { ...event, plus_id: "new_upload" }
: event,
);
},
{
optimisticData: true,
rollbackOnError: true,
revalidate: false,
},
);
},
[search, setSearch],
[search, mutate],
);
const popoverContainerRef = useRef<HTMLDivElement | null>(null);
@@ -1243,8 +1275,8 @@ function ObjectDetailsTab({
</div>
{search.data.type === "object" &&
!search.plus_id &&
config?.plus?.enabled && (
config?.plus?.enabled &&
search.has_snapshot && (
<div
className={cn(
"my-2 flex w-full flex-col justify-between gap-1.5",
@@ -352,7 +352,8 @@ export function TrackingDetails({
className={cn(
isDesktop
? "flex size-full justify-evenly gap-4 overflow-hidden"
: "flex size-full flex-col gap-2",
: "flex flex-col gap-2",
!isDesktop && cameraAspect === "tall" && "size-full",
className,
)}
>
@@ -453,7 +454,7 @@ export function TrackingDetails({
)}
>
{isDesktop && tabs && (
<div className="mb-4 flex items-center justify-between">
<div className="mb-2 flex items-center justify-between">
<div className="flex-1">{tabs}</div>
</div>
)}
@@ -719,9 +720,13 @@ function LifecycleIconRow({
backgroundColor: `rgb(${color})`,
}}
/>
<span className="smart-capitalize">
{item.data?.zones_friendly_names?.[zidx] ??
zone.replaceAll("_", " ")}
<span
className={cn(
item.data?.zones_friendly_names?.[zidx] === zone &&
"smart-capitalize",
)}
>
{item.data?.zones_friendly_names?.[zidx]}
</span>
</Badge>
);
@@ -576,6 +576,7 @@ export default function ZoneEditPane({
control={form.control}
nameField="friendly_name"
idField="name"
idVisible={(polygon && polygon.name.length > 0) ?? false}
nameLabel={t("masksAndZones.zones.name.title")}
nameDescription={t("masksAndZones.zones.name.tips")}
placeholderName={t("masksAndZones.zones.name.inputPlaceHolder")}
+8 -7
View File
@@ -15,7 +15,7 @@ import useSWR from "swr";
import ActivityIndicator from "../indicators/activity-indicator";
import { Event } from "@/types/event";
import { getIconForLabel } from "@/utils/iconUtil";
import { ReviewSegment } from "@/types/review";
import { REVIEW_PADDING, ReviewSegment } from "@/types/review";
import { LuChevronDown, LuCircle, LuChevronRight } from "react-icons/lu";
import { getTranslatedLabel } from "@/utils/i18n";
import EventMenu from "@/components/timeline/EventMenu";
@@ -391,8 +391,8 @@ function ReviewGroup({
)}
/>
</div>
<div className="mr-3 flex w-full justify-between">
<div className="ml-1 flex flex-col items-start gap-1.5">
<div className="mr-3 grid w-full grid-cols-[1fr_auto] gap-2">
<div className="ml-1 flex min-w-0 flex-col gap-1.5">
<div className="flex flex-row gap-3">
<div className="text-sm font-medium">{displayTime}</div>
<div className="relative flex items-center gap-2 text-white">
@@ -408,7 +408,7 @@ function ReviewGroup({
</div>
<div className="flex flex-col gap-0.5">
{review.data.metadata?.title && (
<div className="mb-1 flex items-center gap-1 text-sm text-primary-variant">
<div className="mb-1 flex min-w-0 items-center gap-1 text-sm text-primary-variant">
<MdAutoAwesome className="size-3 shrink-0" />
<span className="truncate">{review.data.metadata.title}</span>
</div>
@@ -432,7 +432,7 @@ function ReviewGroup({
e.stopPropagation();
setOpen((v) => !v);
}}
className="ml-2 inline-flex items-center justify-center rounded p-1 hover:bg-secondary/10"
className="inline-flex items-center justify-center self-center rounded p-1 hover:bg-secondary/10"
>
{open ? (
<LuChevronDown className="size-4 text-primary-variant" />
@@ -803,8 +803,9 @@ function ObjectTimeline({
return fullTimeline
.filter(
(t) =>
t.timestamp >= review.start_time &&
(review.end_time == undefined || t.timestamp <= review.end_time),
t.timestamp >= review.start_time - REVIEW_PADDING &&
(review.end_time == undefined ||
t.timestamp <= review.end_time + REVIEW_PADDING),
)
.map((event) => ({
...event,
@@ -515,7 +515,7 @@ export function ReviewTimeline({
<div
className={`absolute z-30 flex gap-2 ${
isMobile
? "bottom-4 right-1 flex-col gap-3"
? "bottom-4 right-1 flex-col-reverse gap-3"
: "bottom-2 left-1/2 -translate-x-1/2"
}`}
>