Merge branch 'fastapi-poc' into fastapi-poc-media-endpoints

This commit is contained in:
Rui Alves 2024-09-10 21:02:00 +01:00
commit 1db3451642
5 changed files with 86 additions and 10 deletions

View File

@ -25,7 +25,7 @@ import { cn } from "@/lib/utils";
import { FrigatePlusDialog } from "../dialog/FrigatePlusDialog"; import { FrigatePlusDialog } from "../dialog/FrigatePlusDialog";
import ObjectLifecycle from "./ObjectLifecycle"; import ObjectLifecycle from "./ObjectLifecycle";
import Chip from "@/components/indicators/Chip"; import Chip from "@/components/indicators/Chip";
import { FaDownload } from "react-icons/fa"; import { FaDownload, FaImages } from "react-icons/fa";
import FrigatePlusIcon from "@/components/icons/FrigatePlusIcon"; import FrigatePlusIcon from "@/components/icons/FrigatePlusIcon";
import { FaArrowsRotate } from "react-icons/fa6"; import { FaArrowsRotate } from "react-icons/fa6";
import { import {
@ -33,6 +33,7 @@ import {
TooltipContent, TooltipContent,
TooltipTrigger, TooltipTrigger,
} from "@/components/ui/tooltip"; } from "@/components/ui/tooltip";
import { useNavigate } from "react-router-dom";
type ReviewDetailDialogProps = { type ReviewDetailDialogProps = {
review?: ReviewSegment; review?: ReviewSegment;
@ -234,6 +235,8 @@ function EventItem({
const [hovered, setHovered] = useState(isMobile); const [hovered, setHovered] = useState(isMobile);
const navigate = useNavigate();
return ( return (
<> <>
<div <div
@ -328,6 +331,27 @@ function EventItem({
<TooltipContent>View Object Lifecycle</TooltipContent> <TooltipContent>View Object Lifecycle</TooltipContent>
</Tooltip> </Tooltip>
)} )}
{event.has_snapshot && config?.semantic_search.enabled && (
<Tooltip>
<TooltipTrigger>
<Chip
className="cursor-pointer rounded-md bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500"
onClick={() => {
const similaritySearchParams = new URLSearchParams({
search_type: "similarity",
event_id: event.id,
}).toString();
navigate(`/search?${similaritySearchParams}`);
}}
>
<FaImages className="size-4 text-white" />
</Chip>
</TooltipTrigger>
<TooltipContent>Find Similar</TooltipContent>
</Tooltip>
)}
</div> </div>
</div> </div>
)} )}

View File

@ -20,7 +20,7 @@ import { useFormattedTimestamp } from "@/hooks/use-date-utils";
import { getIconForLabel } from "@/utils/iconUtil"; import { getIconForLabel } from "@/utils/iconUtil";
import { useApiHost } from "@/api"; import { useApiHost } from "@/api";
import { Button } from "../../ui/button"; import { Button } from "../../ui/button";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import axios from "axios"; import axios from "axios";
import { toast } from "sonner"; import { toast } from "sonner";
import { Textarea } from "../../ui/textarea"; import { Textarea } from "../../ui/textarea";
@ -55,6 +55,28 @@ export default function SearchDetailDialog({
: "%b %-d %Y, %I:%M %p", : "%b %-d %Y, %I:%M %p",
); );
const score = useMemo(() => {
if (!search) {
return 0;
}
const value = search.score ?? search.data.top_score;
return Math.round(value * 100);
}, [search]);
const subLabelScore = useMemo(() => {
if (!search) {
return undefined;
}
if (search.sub_label) {
return Math.round((search.data?.top_score ?? 0) * 100);
} else {
return undefined;
}
}, [search]);
// api // api
const updateDescription = useCallback(() => { const updateDescription = useCallback(() => {
@ -120,9 +142,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">Score</div> <div className="text-sm text-primary/40">Score</div>
<div className="text-sm"> <div className="text-sm">
{Math.round(search.data.top_score * 100)}% {score}%{subLabelScore && ` (${subLabelScore}%)`}
{search.sub_label &&
` (${Math.round((search.data.sub_label_score ?? 0) * 100)}%)`}
</div> </div>
</div> </div>
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-1.5">

View File

@ -3,7 +3,11 @@ import { useCameraPreviews } from "@/hooks/use-camera-previews";
import { useOverlayState } from "@/hooks/use-overlay-state"; import { useOverlayState } from "@/hooks/use-overlay-state";
import { FrigateConfig } from "@/types/frigateConfig"; import { FrigateConfig } from "@/types/frigateConfig";
import { RecordingStartingPoint } from "@/types/record"; import { RecordingStartingPoint } from "@/types/record";
import { SearchFilter, SearchResult } from "@/types/search"; import {
PartialSearchResult,
SearchFilter,
SearchResult,
} from "@/types/search";
import { TimeRange } from "@/types/timeline"; import { TimeRange } from "@/types/timeline";
import { RecordingView } from "@/views/recording/RecordingView"; import { RecordingView } from "@/views/recording/RecordingView";
import SearchView from "@/views/search/SearchView"; import SearchView from "@/views/search/SearchView";
@ -38,7 +42,27 @@ export default function Search() {
// search api // search api
const [similaritySearch, setSimilaritySearch] = useState<SearchResult>(); const [similaritySearch, setSimilaritySearch] =
useState<PartialSearchResult>();
useEffect(() => {
if (
config?.semantic_search.enabled &&
searchSearchParams["search_type"] == "similarity" &&
searchSearchParams["event_id"]?.length != 0 &&
searchFilter
) {
setSimilaritySearch({
id: searchSearchParams["event_id"],
});
// remove event id from url params
const { event_id: _event_id, ...newFilter } = searchFilter;
setSearchFilter(newFilter);
}
// only run similarity search with event_id in the url when coming from review
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => { useEffect(() => {
if (similaritySearch) { if (similaritySearch) {

View File

@ -25,6 +25,9 @@ export type SearchResult = {
}; };
}; };
export type PartialSearchResult = Partial<SearchResult> & { id: string };
export type SearchFilter = { export type SearchFilter = {
cameras?: string[]; cameras?: string[];
labels?: string[]; labels?: string[];
@ -33,4 +36,5 @@ export type SearchFilter = {
before?: number; before?: number;
after?: number; after?: number;
search_type?: SearchSource[]; search_type?: SearchSource[];
event_id?: string;
}; };

View File

@ -13,7 +13,11 @@ import {
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { FrigateConfig } from "@/types/frigateConfig"; import { FrigateConfig } from "@/types/frigateConfig";
import { Preview } from "@/types/preview"; import { Preview } from "@/types/preview";
import { SearchFilter, SearchResult } from "@/types/search"; import {
PartialSearchResult,
SearchFilter,
SearchResult,
} from "@/types/search";
import { useCallback, useMemo, useState } from "react"; import { useCallback, useMemo, useState } from "react";
import { isMobileOnly } from "react-device-detect"; import { isMobileOnly } from "react-device-detect";
import { LuImage, LuSearchX, LuText, LuXCircle } from "react-icons/lu"; import { LuImage, LuSearchX, LuText, LuXCircle } from "react-icons/lu";
@ -26,7 +30,7 @@ type SearchViewProps = {
searchResults?: SearchResult[]; searchResults?: SearchResult[];
allPreviews?: Preview[]; allPreviews?: Preview[];
isLoading: boolean; isLoading: boolean;
similaritySearch?: SearchResult; similaritySearch?: PartialSearchResult;
setSearch: (search: string) => void; setSearch: (search: string) => void;
setSimilaritySearch: (search: SearchResult) => void; setSimilaritySearch: (search: SearchResult) => void;
onUpdateFilter: (filter: SearchFilter) => void; onUpdateFilter: (filter: SearchFilter) => void;
@ -186,7 +190,7 @@ export default function SearchView({
scrollLock={false} scrollLock={false}
onClick={onSelectSearch} onClick={onSelectSearch}
/> />
{searchTerm && ( {(searchTerm || similaritySearch) && (
<div className={cn("absolute right-2 top-2 z-40")}> <div className={cn("absolute right-2 top-2 z-40")}>
<Tooltip> <Tooltip>
<TooltipTrigger> <TooltipTrigger>