This commit is contained in:
Josh Hawkins 2025-03-12 17:26:27 -05:00
parent 3dcef62271
commit 7e4af87c1c
5 changed files with 99 additions and 65 deletions

View File

@ -333,13 +333,18 @@ function ObjectDetailsTab({
} }
}, [search]); }, [search]);
const identifierScore = useMemo(() => { const recognizedLicensePlateScore = useMemo(() => {
if (!search) { if (!search) {
return undefined; return undefined;
} }
if (search.data.identifier && search.data?.identifier_score) { if (
return Math.round((search.data?.identifier_score ?? 0) * 100); search.data.recognized_license_plate &&
search.data?.recognized_license_plate_score
) {
return Math.round(
(search.data?.recognized_license_plate_score ?? 0) * 100,
);
} else { } else {
return undefined; return undefined;
} }
@ -550,13 +555,16 @@ function ObjectDetailsTab({
</Tooltip> </Tooltip>
</div> </div>
</div> </div>
{search?.data.identifier && ( {search?.data.recognized_license_plate && (
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-1.5">
<div className="text-sm text-primary/40">Identifier</div> <div className="text-sm text-primary/40">
Recognized License Plate
</div>
<div className="flex flex-col space-y-0.5 text-sm"> <div className="flex flex-col space-y-0.5 text-sm">
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
{search.data.identifier}{" "} {search.data.recognized_license_plate}{" "}
{identifierScore && ` (${identifierScore}%)`} {recognizedLicensePlateScore &&
` (${recognizedLicensePlateScore}%)`}
</div> </div>
</div> </div>
</div> </div>

View File

@ -86,7 +86,7 @@ export default function SearchFilterDialog({
(currentFilter.max_speed ?? 150) < 150 || (currentFilter.max_speed ?? 150) < 150 ||
(currentFilter.zones?.length ?? 0) > 0 || (currentFilter.zones?.length ?? 0) > 0 ||
(currentFilter.sub_labels?.length ?? 0) > 0 || (currentFilter.sub_labels?.length ?? 0) > 0 ||
(currentFilter.identifier?.length ?? 0) > 0), (currentFilter.recognized_license_plate?.length ?? 0) > 0),
[currentFilter], [currentFilter],
); );
@ -128,10 +128,13 @@ export default function SearchFilterDialog({
setCurrentFilter({ ...currentFilter, sub_labels: newSubLabels }) setCurrentFilter({ ...currentFilter, sub_labels: newSubLabels })
} }
/> />
<IdentifierFilterContent <RecognizedLicensePlatesFilterContent
identifiers={currentFilter.identifier} recognizedLicensePlates={currentFilter.recognized_license_plate}
setIdentifiers={(identifiers) => setRecognizedLicensePlates={(plate) =>
setCurrentFilter({ ...currentFilter, identifier: identifiers }) setCurrentFilter({
...currentFilter,
recognized_license_plate: plate,
})
} }
/> />
<ScoreFilterContent <ScoreFilterContent
@ -207,7 +210,7 @@ export default function SearchFilterDialog({
max_speed: undefined, max_speed: undefined,
has_snapshot: undefined, has_snapshot: undefined,
has_clip: undefined, has_clip: undefined,
identifier: undefined, recognized_license_plate: undefined,
})); }));
}} }}
> >
@ -847,97 +850,109 @@ export function SnapshotClipFilterContent({
); );
} }
type IdentifierFilterContentProps = { type RecognizedLicensePlatesFilterContentProps = {
identifiers: string[] | undefined; recognizedLicensePlates: string[] | undefined;
setIdentifiers: (identifiers: string[] | undefined) => void; setRecognizedLicensePlates: (
recognizedLicensePlates: string[] | undefined,
) => void;
}; };
export function IdentifierFilterContent({ export function RecognizedLicensePlatesFilterContent({
identifiers, recognizedLicensePlates,
setIdentifiers, setRecognizedLicensePlates,
}: IdentifierFilterContentProps) { }: RecognizedLicensePlatesFilterContentProps) {
const { data: allIdentifiers, error } = useSWR<string[]>("identifiers", { const { data: allRecognizedLicensePlates, error } = useSWR<string[]>(
revalidateOnFocus: false, "recognized_license_plates",
}); {
revalidateOnFocus: false,
const [selectedIdentifiers, setSelectedIdentifiers] = useState<string[]>( },
identifiers || [],
); );
const [selectedRecognizedLicensePlates, setSelectedRecognizedLicensePlates] =
useState<string[]>(recognizedLicensePlates || []);
const [inputValue, setInputValue] = useState(""); const [inputValue, setInputValue] = useState("");
useEffect(() => { useEffect(() => {
if (identifiers) { if (recognizedLicensePlates) {
setSelectedIdentifiers(identifiers); setSelectedRecognizedLicensePlates(recognizedLicensePlates);
} else { } else {
setSelectedIdentifiers([]); setSelectedRecognizedLicensePlates([]);
} }
}, [identifiers]); }, [recognizedLicensePlates]);
const handleSelect = (identifier: string) => { const handleSelect = (recognizedLicensePlate: string) => {
const newSelected = selectedIdentifiers.includes(identifier) const newSelected = selectedRecognizedLicensePlates.includes(
? selectedIdentifiers.filter((id) => id !== identifier) // Deselect recognizedLicensePlate,
: [...selectedIdentifiers, identifier]; // Select )
? selectedRecognizedLicensePlates.filter(
(id) => id !== recognizedLicensePlate,
) // Deselect
: [...selectedRecognizedLicensePlates, recognizedLicensePlate]; // Select
setSelectedIdentifiers(newSelected); setSelectedRecognizedLicensePlates(newSelected);
if (newSelected.length === 0) { if (newSelected.length === 0) {
setIdentifiers(undefined); // Clear filter if no identifiers selected setRecognizedLicensePlates(undefined); // Clear filter if no plates selected
} else { } else {
setIdentifiers(newSelected); setRecognizedLicensePlates(newSelected);
} }
}; };
if (!allIdentifiers || allIdentifiers.length === 0) { if (!allRecognizedLicensePlates || allRecognizedLicensePlates.length === 0) {
return null; return null;
} }
const filteredIdentifiers = const filteredRecognizedLicensePlates =
allIdentifiers?.filter((id) => allRecognizedLicensePlates?.filter((id) =>
id.toLowerCase().includes(inputValue.toLowerCase()), id.toLowerCase().includes(inputValue.toLowerCase()),
) || []; ) || [];
return ( return (
<div className="overflow-x-hidden"> <div className="overflow-x-hidden">
<DropdownMenuSeparator className="mb-3" /> <DropdownMenuSeparator className="mb-3" />
<div className="mb-3 text-lg">Identifiers</div> <div className="mb-3 text-lg">Recognized License Plates</div>
{error ? ( {error ? (
<p className="text-sm text-red-500">Failed to load identifiers</p> <p className="text-sm text-red-500">
) : !allIdentifiers ? ( Failed to load recognized license plates.
<p className="text-sm text-muted-foreground">Loading identifiers...</p> </p>
) : !allRecognizedLicensePlates ? (
<p className="text-sm text-muted-foreground">
Loading recognized license plates...
</p>
) : ( ) : (
<> <>
<Command className="border border-input bg-background"> <Command className="border border-input bg-background">
<CommandInput <CommandInput
placeholder="Type to search identifiers..." placeholder="Type to search license plates..."
value={inputValue} value={inputValue}
onValueChange={setInputValue} onValueChange={setInputValue}
/> />
<CommandList className="max-h-[200px] overflow-auto"> <CommandList className="max-h-[200px] overflow-auto">
{filteredIdentifiers.length === 0 && inputValue && ( {filteredRecognizedLicensePlates.length === 0 && inputValue && (
<CommandEmpty>No identifiers found.</CommandEmpty> <CommandEmpty>No license plates found.</CommandEmpty>
)} )}
{filteredIdentifiers.map((identifier) => ( {filteredRecognizedLicensePlates.map((plate) => (
<CommandItem <CommandItem
key={identifier} key={plate}
value={identifier} value={plate}
onSelect={() => handleSelect(identifier)} onSelect={() => handleSelect(plate)}
className="cursor-pointer" className="cursor-pointer"
> >
<LuCheck <LuCheck
className={cn( className={cn(
"mr-2 h-4 w-4", "mr-2 h-4 w-4",
selectedIdentifiers.includes(identifier) selectedRecognizedLicensePlates.includes(plate)
? "opacity-100" ? "opacity-100"
: "opacity-0", : "opacity-0",
)} )}
/> />
{identifier} {plate}
</CommandItem> </CommandItem>
))} ))}
</CommandList> </CommandList>
</Command> </Command>
{selectedIdentifiers.length > 0 && ( {selectedRecognizedLicensePlates.length > 0 && (
<div className="mt-2 flex flex-wrap gap-2"> <div className="mt-2 flex flex-wrap gap-2">
{selectedIdentifiers.map((id) => ( {selectedRecognizedLicensePlates.map((id) => (
<span <span
key={id} key={id}
className="inline-flex items-center rounded bg-selected px-2 py-1 text-sm text-white" className="inline-flex items-center rounded bg-selected px-2 py-1 text-sm text-white"
@ -956,7 +971,7 @@ export function IdentifierFilterContent({
</> </>
)} )}
<p className="mt-1 text-sm text-muted-foreground"> <p className="mt-1 text-sm text-muted-foreground">
Select one or more identifiers from the list. Select one or more plates from the list.
</p> </p>
</div> </div>
); );

View File

@ -105,7 +105,8 @@ export default function Explore() {
cameras: searchSearchParams["cameras"], cameras: searchSearchParams["cameras"],
labels: searchSearchParams["labels"], labels: searchSearchParams["labels"],
sub_labels: searchSearchParams["sub_labels"], sub_labels: searchSearchParams["sub_labels"],
identifier: searchSearchParams["identifier"], recognized_license_plate:
searchSearchParams["recognized_license_plate"],
zones: searchSearchParams["zones"], zones: searchSearchParams["zones"],
before: searchSearchParams["before"], before: searchSearchParams["before"],
after: searchSearchParams["after"], after: searchSearchParams["after"],
@ -141,7 +142,8 @@ export default function Explore() {
cameras: searchSearchParams["cameras"], cameras: searchSearchParams["cameras"],
labels: searchSearchParams["labels"], labels: searchSearchParams["labels"],
sub_labels: searchSearchParams["sub_labels"], sub_labels: searchSearchParams["sub_labels"],
identifier: searchSearchParams["identifier"], recognized_license_plate:
searchSearchParams["recognized_license_plate"],
zones: searchSearchParams["zones"], zones: searchSearchParams["zones"],
before: searchSearchParams["before"], before: searchSearchParams["before"],
after: searchSearchParams["after"], after: searchSearchParams["after"],

View File

@ -58,8 +58,8 @@ export type SearchResult = {
average_estimated_speed: number; average_estimated_speed: number;
velocity_angle: number; velocity_angle: number;
path_data: [number[], number][]; path_data: [number[], number][];
identifier?: string; recognized_license_plate?: string;
identifier_score?: number; recognized_license_plate_score?: number;
}; };
}; };
@ -68,7 +68,7 @@ export type SearchFilter = {
cameras?: string[]; cameras?: string[];
labels?: string[]; labels?: string[];
sub_labels?: string[]; sub_labels?: string[];
identifier?: string[]; recognized_license_plate?: string[];
zones?: string[]; zones?: string[];
before?: number; before?: number;
after?: number; after?: number;
@ -92,7 +92,7 @@ export type SearchQueryParams = {
cameras?: string[]; cameras?: string[];
labels?: string[]; labels?: string[];
sub_labels?: string[]; sub_labels?: string[];
identifier?: string[]; recognized_license_plate?: string[];
zones?: string[]; zones?: string[];
before?: string; before?: string;
after?: string; after?: string;

View File

@ -121,7 +121,9 @@ export default function SearchView({
}, [config, searchFilter]); }, [config, searchFilter]);
const { data: allSubLabels } = useSWR("sub_labels"); const { data: allSubLabels } = useSWR("sub_labels");
const { data: allIdentifiers } = useSWR("identifiers"); const { data: allRecognizedLicensePlates } = useSWR(
"recognized_license_plate",
);
const allZones = useMemo<string[]>(() => { const allZones = useMemo<string[]>(() => {
if (!config) { if (!config) {
@ -161,13 +163,20 @@ export default function SearchView({
max_score: ["100"], max_score: ["100"],
min_speed: ["1"], min_speed: ["1"],
max_speed: ["150"], max_speed: ["150"],
identifier: allIdentifiers, recognized_license_plate: allRecognizedLicensePlates,
has_clip: ["yes", "no"], has_clip: ["yes", "no"],
has_snapshot: ["yes", "no"], has_snapshot: ["yes", "no"],
...(config?.plus?.enabled && ...(config?.plus?.enabled &&
searchFilter?.has_snapshot && { is_submitted: ["yes", "no"] }), searchFilter?.has_snapshot && { is_submitted: ["yes", "no"] }),
}), }),
[config, allLabels, allZones, allSubLabels, allIdentifiers, searchFilter], [
config,
allLabels,
allZones,
allSubLabels,
allRecognizedLicensePlates,
searchFilter,
],
); );
// remove duplicate event ids // remove duplicate event ids