mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-14 15:15:22 +03:00
Merge branch 'fastapi-poc' into fastapi-poc-media-endpoints
This commit is contained in:
commit
1db3451642
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user