mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-22 08:57:20 -06:00
UI tweaks (#13633)
* Object lifecycle and semantic search UI tweaks * prevent console errors for sheet component
This commit is contained in:
parent
8be139d4d1
commit
f143fceceb
@ -379,7 +379,12 @@ def events_search():
|
|||||||
n_results=limit,
|
n_results=limit,
|
||||||
where=where,
|
where=where,
|
||||||
)
|
)
|
||||||
thumb_ids = dict(zip(thumb_result["ids"][0], thumb_result["distances"][0]))
|
thumb_ids = dict(
|
||||||
|
zip(
|
||||||
|
thumb_result["ids"][0],
|
||||||
|
context.thumb_stats.normalize(thumb_result["distances"][0]),
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
thumb_result = context.embeddings.thumbnail.query(
|
thumb_result = context.embeddings.thumbnail.query(
|
||||||
query_texts=[query],
|
query_texts=[query],
|
||||||
|
@ -1,7 +1,19 @@
|
|||||||
import { LogLine } from "@/types/log";
|
import { LogLine } from "@/types/log";
|
||||||
import { isDesktop } from "react-device-detect";
|
import { isDesktop } from "react-device-detect";
|
||||||
import { Sheet, SheetContent } from "../ui/sheet";
|
import {
|
||||||
import { Drawer, DrawerContent } from "../ui/drawer";
|
Sheet,
|
||||||
|
SheetContent,
|
||||||
|
SheetDescription,
|
||||||
|
SheetHeader,
|
||||||
|
SheetTitle,
|
||||||
|
} from "../ui/sheet";
|
||||||
|
import {
|
||||||
|
Drawer,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerDescription,
|
||||||
|
DrawerHeader,
|
||||||
|
DrawerTitle,
|
||||||
|
} from "../ui/drawer";
|
||||||
import { LogChip } from "../indicators/Chip";
|
import { LogChip } from "../indicators/Chip";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
@ -16,6 +28,9 @@ export default function LogInfoDialog({
|
|||||||
}: LogInfoDialogProps) {
|
}: LogInfoDialogProps) {
|
||||||
const Overlay = isDesktop ? Sheet : Drawer;
|
const Overlay = isDesktop ? Sheet : Drawer;
|
||||||
const Content = isDesktop ? SheetContent : DrawerContent;
|
const Content = isDesktop ? SheetContent : DrawerContent;
|
||||||
|
const Header = isDesktop ? SheetHeader : DrawerHeader;
|
||||||
|
const Title = isDesktop ? SheetTitle : DrawerTitle;
|
||||||
|
const Description = isDesktop ? SheetDescription : DrawerDescription;
|
||||||
|
|
||||||
const helpfulLinks = useHelpfulLinks(logLine?.content);
|
const helpfulLinks = useHelpfulLinks(logLine?.content);
|
||||||
|
|
||||||
@ -31,6 +46,10 @@ export default function LogInfoDialog({
|
|||||||
<Content
|
<Content
|
||||||
className={isDesktop ? "" : "max-h-[75dvh] overflow-hidden p-2 pb-4"}
|
className={isDesktop ? "" : "max-h-[75dvh] overflow-hidden p-2 pb-4"}
|
||||||
>
|
>
|
||||||
|
<Header className="sr-only">
|
||||||
|
<Title>Log Details</Title>
|
||||||
|
<Description>Log details</Description>
|
||||||
|
</Header>
|
||||||
{logLine && (
|
{logLine && (
|
||||||
<div className="flex size-full flex-col gap-5">
|
<div className="flex size-full flex-col gap-5">
|
||||||
<div className="flex w-min flex-col gap-1.5">
|
<div className="flex w-min flex-col gap-1.5">
|
||||||
|
@ -44,6 +44,7 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { AnnotationSettingsPane } from "./AnnotationSettingsPane";
|
import { AnnotationSettingsPane } from "./AnnotationSettingsPane";
|
||||||
|
import { TooltipPortal } from "@radix-ui/react-tooltip";
|
||||||
|
|
||||||
type ObjectLifecycleProps = {
|
type ObjectLifecycleProps = {
|
||||||
review: ReviewSegment;
|
review: ReviewSegment;
|
||||||
@ -185,7 +186,6 @@ export default function ObjectLifecycle({
|
|||||||
if (!mainApi || !thumbnailApi) {
|
if (!mainApi || !thumbnailApi) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
thumbnailApi.scrollTo(index);
|
|
||||||
mainApi.scrollTo(index);
|
mainApi.scrollTo(index);
|
||||||
setCurrent(index);
|
setCurrent(index);
|
||||||
};
|
};
|
||||||
@ -210,18 +210,10 @@ export default function ObjectLifecycle({
|
|||||||
thumbnailApi.scrollTo(selected);
|
thumbnailApi.scrollTo(selected);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBottomSelect = () => {
|
mainApi.on("select", handleTopSelect).on("reInit", handleTopSelect);
|
||||||
const selected = thumbnailApi.selectedScrollSnap();
|
|
||||||
setCurrent(selected);
|
|
||||||
mainApi.scrollTo(selected);
|
|
||||||
};
|
|
||||||
|
|
||||||
mainApi.on("select", handleTopSelect);
|
|
||||||
thumbnailApi.on("select", handleBottomSelect);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
mainApi.off("select", handleTopSelect);
|
mainApi.off("select", handleTopSelect);
|
||||||
thumbnailApi.off("select", handleBottomSelect);
|
|
||||||
};
|
};
|
||||||
// we know that these deps are correct
|
// we know that these deps are correct
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@ -467,15 +459,22 @@ export default function ObjectLifecycle({
|
|||||||
<Carousel
|
<Carousel
|
||||||
opts={{
|
opts={{
|
||||||
align: "center",
|
align: "center",
|
||||||
|
containScroll: "keepSnaps",
|
||||||
|
dragFree: true,
|
||||||
}}
|
}}
|
||||||
className="w-full max-w-[72%] md:max-w-[85%]"
|
className="w-full max-w-[72%] md:max-w-[85%]"
|
||||||
setApi={setThumbnailApi}
|
setApi={setThumbnailApi}
|
||||||
>
|
>
|
||||||
<CarouselContent className="flex flex-row justify-center">
|
<CarouselContent
|
||||||
|
className={cn(
|
||||||
|
"-ml-1 flex select-none flex-row",
|
||||||
|
eventSequence.length > 4 ? "justify-start" : "justify-center",
|
||||||
|
)}
|
||||||
|
>
|
||||||
{eventSequence.map((item, index) => (
|
{eventSequence.map((item, index) => (
|
||||||
<CarouselItem
|
<CarouselItem
|
||||||
key={index}
|
key={index}
|
||||||
className={cn("basis-1/4 cursor-pointer md:basis-[10%]")}
|
className={cn("basis-1/4 cursor-pointer pl-1 md:basis-[10%]")}
|
||||||
onClick={() => handleThumbnailClick(index)}
|
onClick={() => handleThumbnailClick(index)}
|
||||||
>
|
>
|
||||||
<div className="p-1">
|
<div className="p-1">
|
||||||
@ -486,15 +485,24 @@ export default function ObjectLifecycle({
|
|||||||
index === current && "bg-selected",
|
index === current && "bg-selected",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<LifecycleIcon
|
<Tooltip>
|
||||||
className={cn(
|
<TooltipTrigger>
|
||||||
"size-8",
|
<LifecycleIcon
|
||||||
index === current
|
className={cn(
|
||||||
? "bg-selected text-white"
|
"size-8",
|
||||||
: "text-muted-foreground",
|
index === current
|
||||||
)}
|
? "bg-selected text-white"
|
||||||
lifecycleItem={item}
|
: "text-muted-foreground",
|
||||||
/>
|
)}
|
||||||
|
lifecycleItem={item}
|
||||||
|
/>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipPortal>
|
||||||
|
<TooltipContent className="capitalize">
|
||||||
|
{getLifecycleItemDescription(item)}
|
||||||
|
</TooltipContent>
|
||||||
|
</TooltipPortal>
|
||||||
|
</Tooltip>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,18 @@
|
|||||||
import { isDesktop, isIOS, isMobile } from "react-device-detect";
|
import { isDesktop, isIOS, isMobile } from "react-device-detect";
|
||||||
import { Sheet, SheetContent } from "../../ui/sheet";
|
import {
|
||||||
import { Drawer, DrawerContent } from "../../ui/drawer";
|
Sheet,
|
||||||
|
SheetContent,
|
||||||
|
SheetDescription,
|
||||||
|
SheetHeader,
|
||||||
|
SheetTitle,
|
||||||
|
} from "../../ui/sheet";
|
||||||
|
import {
|
||||||
|
Drawer,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerDescription,
|
||||||
|
DrawerHeader,
|
||||||
|
DrawerTitle,
|
||||||
|
} from "../../ui/drawer";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { FrigateConfig } from "@/types/frigateConfig";
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
import { useFormattedTimestamp } from "@/hooks/use-date-utils";
|
import { useFormattedTimestamp } from "@/hooks/use-date-utils";
|
||||||
@ -66,6 +78,9 @@ export default function ReviewDetailDialog({
|
|||||||
|
|
||||||
const Overlay = isDesktop ? Sheet : Drawer;
|
const Overlay = isDesktop ? Sheet : Drawer;
|
||||||
const Content = isDesktop ? SheetContent : DrawerContent;
|
const Content = isDesktop ? SheetContent : DrawerContent;
|
||||||
|
const Header = isDesktop ? SheetHeader : DrawerHeader;
|
||||||
|
const Title = isDesktop ? SheetTitle : DrawerTitle;
|
||||||
|
const Description = isDesktop ? SheetDescription : DrawerDescription;
|
||||||
|
|
||||||
if (!review) {
|
if (!review) {
|
||||||
return;
|
return;
|
||||||
@ -102,6 +117,10 @@ export default function ReviewDetailDialog({
|
|||||||
: "max-h-[80dvh] overflow-hidden p-2 pb-4",
|
: "max-h-[80dvh] overflow-hidden p-2 pb-4",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
<Header className="sr-only">
|
||||||
|
<Title>Review Item Details</Title>
|
||||||
|
<Description>Review item details</Description>
|
||||||
|
</Header>
|
||||||
{pane == "overview" && (
|
{pane == "overview" && (
|
||||||
<div className="scrollbar-container mt-3 flex size-full flex-col gap-5 overflow-y-auto">
|
<div className="scrollbar-container mt-3 flex size-full flex-col gap-5 overflow-y-auto">
|
||||||
<div className="flex w-full flex-row">
|
<div className="flex w-full flex-row">
|
||||||
|
@ -1,6 +1,18 @@
|
|||||||
import { isDesktop, isIOS } from "react-device-detect";
|
import { isDesktop, isIOS } from "react-device-detect";
|
||||||
import { Sheet, SheetContent } from "../../ui/sheet";
|
import {
|
||||||
import { Drawer, DrawerContent } from "../../ui/drawer";
|
Sheet,
|
||||||
|
SheetContent,
|
||||||
|
SheetDescription,
|
||||||
|
SheetHeader,
|
||||||
|
SheetTitle,
|
||||||
|
} from "../../ui/sheet";
|
||||||
|
import {
|
||||||
|
Drawer,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerDescription,
|
||||||
|
DrawerHeader,
|
||||||
|
DrawerTitle,
|
||||||
|
} from "../../ui/drawer";
|
||||||
import { SearchResult } from "@/types/search";
|
import { SearchResult } from "@/types/search";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { FrigateConfig } from "@/types/frigateConfig";
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
@ -71,6 +83,9 @@ export default function SearchDetailDialog({
|
|||||||
|
|
||||||
const Overlay = isDesktop ? Sheet : Drawer;
|
const Overlay = isDesktop ? Sheet : Drawer;
|
||||||
const Content = isDesktop ? SheetContent : DrawerContent;
|
const Content = isDesktop ? SheetContent : DrawerContent;
|
||||||
|
const Header = isDesktop ? SheetHeader : DrawerHeader;
|
||||||
|
const Title = isDesktop ? SheetTitle : DrawerTitle;
|
||||||
|
const Description = isDesktop ? SheetDescription : DrawerDescription;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Overlay
|
<Overlay
|
||||||
@ -86,6 +101,10 @@ export default function SearchDetailDialog({
|
|||||||
isDesktop ? "sm:max-w-xl" : "max-h-[75dvh] overflow-hidden p-2 pb-4"
|
isDesktop ? "sm:max-w-xl" : "max-h-[75dvh] overflow-hidden p-2 pb-4"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
<Header className="sr-only">
|
||||||
|
<Title>Tracked Object Details</Title>
|
||||||
|
<Description>Tracked object details</Description>
|
||||||
|
</Header>
|
||||||
{search && (
|
{search && (
|
||||||
<div className="mt-3 flex size-full flex-col gap-5 md:mt-0">
|
<div className="mt-3 flex size-full flex-col gap-5 md:mt-0">
|
||||||
<div className="flex w-full flex-row">
|
<div className="flex w-full flex-row">
|
||||||
@ -93,7 +112,7 @@ export default function SearchDetailDialog({
|
|||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<div className="text-sm text-primary/40">Label</div>
|
<div className="text-sm text-primary/40">Label</div>
|
||||||
<div className="flex flex-row items-center gap-2 text-sm capitalize">
|
<div className="flex flex-row items-center gap-2 text-sm capitalize">
|
||||||
{getIconForLabel(search.label, "size-4 text-white")}
|
{getIconForLabel(search.label, "size-4 text-primary")}
|
||||||
{search.label}
|
{search.label}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -287,17 +287,6 @@ function PreviewContent({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (isCurrentHour(searchResult.start_time)) {
|
} else if (isCurrentHour(searchResult.start_time)) {
|
||||||
return (
|
return <div />;
|
||||||
/*<InProgressPreview
|
|
||||||
review={review}
|
|
||||||
timeRange={timeRange}
|
|
||||||
setIgnoreClick={setIgnoreClick}
|
|
||||||
isPlayingBack={isPlayingBack}
|
|
||||||
onTimeUpdate={onTimeUpdate}
|
|
||||||
windowVisible={true}
|
|
||||||
setReviewed={() => { }}
|
|
||||||
/>*/
|
|
||||||
<div />
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ export default function Search() {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setSearchTimeout(undefined);
|
setSearchTimeout(undefined);
|
||||||
setSearchTerm(search);
|
setSearchTerm(search);
|
||||||
}, 500),
|
}, 750),
|
||||||
);
|
);
|
||||||
// we only want to update the searchTerm when search changes
|
// we only want to update the searchTerm when search changes
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
@ -12,6 +12,7 @@ export type SearchResult = {
|
|||||||
thumb_path?: string;
|
thumb_path?: string;
|
||||||
zones: string[];
|
zones: string[];
|
||||||
search_source: SearchSource;
|
search_source: SearchSource;
|
||||||
|
search_distance: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SearchFilter = {
|
export type SearchFilter = {
|
||||||
|
@ -1,17 +1,26 @@
|
|||||||
import SearchFilterGroup from "@/components/filter/SearchFilterGroup";
|
import SearchFilterGroup from "@/components/filter/SearchFilterGroup";
|
||||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||||
|
import Chip from "@/components/indicators/Chip";
|
||||||
import SearchDetailDialog from "@/components/overlay/detail/SearchDetailDialog";
|
import SearchDetailDialog from "@/components/overlay/detail/SearchDetailDialog";
|
||||||
import SearchThumbnailPlayer from "@/components/player/SearchThumbnailPlayer";
|
import SearchThumbnailPlayer from "@/components/player/SearchThumbnailPlayer";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Preview } from "@/types/preview";
|
import { Preview } from "@/types/preview";
|
||||||
import { SearchFilter, SearchResult } from "@/types/search";
|
import { SearchFilter, SearchResult } from "@/types/search";
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useMemo, useState } from "react";
|
||||||
|
import { isMobileOnly } from "react-device-detect";
|
||||||
import {
|
import {
|
||||||
LuExternalLink,
|
LuExternalLink,
|
||||||
|
LuImage,
|
||||||
LuSearchCheck,
|
LuSearchCheck,
|
||||||
LuSearchX,
|
LuSearchX,
|
||||||
|
LuText,
|
||||||
LuXCircle,
|
LuXCircle,
|
||||||
} from "react-icons/lu";
|
} from "react-icons/lu";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
@ -40,6 +49,15 @@ export default function SearchView({
|
|||||||
onUpdateFilter,
|
onUpdateFilter,
|
||||||
onOpenSearch,
|
onOpenSearch,
|
||||||
}: SearchViewProps) {
|
}: SearchViewProps) {
|
||||||
|
// remove duplicate event ids
|
||||||
|
|
||||||
|
const uniqueResults = useMemo(() => {
|
||||||
|
return searchResults?.filter(
|
||||||
|
(value, index, self) =>
|
||||||
|
index === self.findIndex((v) => v.id === value.id),
|
||||||
|
);
|
||||||
|
}, [searchResults]);
|
||||||
|
|
||||||
// detail
|
// detail
|
||||||
|
|
||||||
const [searchDetail, setSearchDetail] = useState<SearchResult>();
|
const [searchDetail, setSearchDetail] = useState<SearchResult>();
|
||||||
@ -57,6 +75,25 @@ export default function SearchView({
|
|||||||
[onOpenSearch],
|
[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 (
|
return (
|
||||||
<div className="flex size-full flex-col pt-2 md:py-2">
|
<div className="flex size-full flex-col pt-2 md:py-2">
|
||||||
<Toaster closeButton={true} />
|
<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 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
|
<Input
|
||||||
className="text-md w-full bg-muted pr-10"
|
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}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
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">
|
<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 &&
|
{uniqueResults &&
|
||||||
searchResults.map((value) => {
|
uniqueResults.map((value) => {
|
||||||
const selected = false;
|
const selected = false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -145,6 +184,34 @@ export default function SearchView({
|
|||||||
scrollLock={false}
|
scrollLock={false}
|
||||||
onClick={onSelectSearch}
|
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>
|
||||||
<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"}`}
|
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"}`}
|
||||||
|
Loading…
Reference in New Issue
Block a user