forked from Github/frigate
UI tweaks (#13633)
* Object lifecycle and semantic search UI tweaks * prevent console errors for sheet component
This commit is contained in:
@@ -1,17 +1,26 @@
|
||||
import SearchFilterGroup from "@/components/filter/SearchFilterGroup";
|
||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||
import Chip from "@/components/indicators/Chip";
|
||||
import SearchDetailDialog from "@/components/overlay/detail/SearchDetailDialog";
|
||||
import SearchThumbnailPlayer from "@/components/player/SearchThumbnailPlayer";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Preview } from "@/types/preview";
|
||||
import { SearchFilter, SearchResult } from "@/types/search";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { isMobileOnly } from "react-device-detect";
|
||||
import {
|
||||
LuExternalLink,
|
||||
LuImage,
|
||||
LuSearchCheck,
|
||||
LuSearchX,
|
||||
LuText,
|
||||
LuXCircle,
|
||||
} from "react-icons/lu";
|
||||
import { Link } from "react-router-dom";
|
||||
@@ -40,6 +49,15 @@ export default function SearchView({
|
||||
onUpdateFilter,
|
||||
onOpenSearch,
|
||||
}: SearchViewProps) {
|
||||
// remove duplicate event ids
|
||||
|
||||
const uniqueResults = useMemo(() => {
|
||||
return searchResults?.filter(
|
||||
(value, index, self) =>
|
||||
index === self.findIndex((v) => v.id === value.id),
|
||||
);
|
||||
}, [searchResults]);
|
||||
|
||||
// detail
|
||||
|
||||
const [searchDetail, setSearchDetail] = useState<SearchResult>();
|
||||
@@ -57,6 +75,25 @@ export default function SearchView({
|
||||
[onOpenSearch],
|
||||
);
|
||||
|
||||
// confidence score - probably needs tweaking
|
||||
|
||||
const zScoreToConfidence = (score: number, source: string) => {
|
||||
let midpoint, scale;
|
||||
|
||||
if (source === "thumbnail") {
|
||||
midpoint = 2;
|
||||
scale = 0.5;
|
||||
} else {
|
||||
midpoint = 0.5;
|
||||
scale = 1.5;
|
||||
}
|
||||
|
||||
// Sigmoid function: 1 / (1 + e^x)
|
||||
const confidence = 1 / (1 + Math.exp((score - midpoint) * scale));
|
||||
|
||||
return Math.round(confidence * 100);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex size-full flex-col pt-2 md:py-2">
|
||||
<Toaster closeButton={true} />
|
||||
@@ -69,10 +106,12 @@ export default function SearchView({
|
||||
/>
|
||||
|
||||
<div className="relative mb-2 flex h-11 items-center justify-between pl-2 pr-2 md:pl-3">
|
||||
<div className="relative w-full md:w-1/3">
|
||||
<div className="relative mr-3 w-full md:w-1/3">
|
||||
<Input
|
||||
className="text-md w-full bg-muted pr-10"
|
||||
placeholder="Search for a specific detected object..."
|
||||
placeholder={
|
||||
isMobileOnly ? "Search" : "Search for a detected object..."
|
||||
}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
@@ -124,8 +163,8 @@ export default function SearchView({
|
||||
)}
|
||||
|
||||
<div className="grid w-full gap-2 px-1 sm:grid-cols-2 md:mx-2 md:grid-cols-4 md:gap-4 3xl:grid-cols-6">
|
||||
{searchResults &&
|
||||
searchResults.map((value) => {
|
||||
{uniqueResults &&
|
||||
uniqueResults.map((value) => {
|
||||
const selected = false;
|
||||
|
||||
return (
|
||||
@@ -145,6 +184,34 @@ export default function SearchView({
|
||||
scrollLock={false}
|
||||
onClick={onSelectSearch}
|
||||
/>
|
||||
<div className={cn("absolute right-2 top-2 z-40")}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Chip
|
||||
className={`flex select-none items-center justify-between space-x-1 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500 text-xs capitalize text-white`}
|
||||
>
|
||||
{value.search_source == "thumbnail" ? (
|
||||
<LuImage className="mr-1 size-3" />
|
||||
) : (
|
||||
<LuText className="mr-1 size-3" />
|
||||
)}
|
||||
{zScoreToConfidence(
|
||||
value.search_distance,
|
||||
value.search_source,
|
||||
)}
|
||||
%
|
||||
</Chip>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
Matched {value.search_source} at{" "}
|
||||
{zScoreToConfidence(
|
||||
value.search_distance,
|
||||
value.search_source,
|
||||
)}
|
||||
%
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`review-item-ring pointer-events-none absolute inset-0 z-10 size-full rounded-lg outline outline-[3px] -outline-offset-[2.8px] ${selected ? `shadow-severity_alert outline-severity_alert` : "outline-transparent duration-500"}`}
|
||||
|
||||
Reference in New Issue
Block a user