Improve recognized license plate filter (#19491)

* Fetch all license plates outside of filter component

If the swr call took a long time, the entire select component may not display. This change moves the fetch to the parent component (like sub labels).

* add loading indicator

* improve query
This commit is contained in:
Josh Hawkins 2025-08-16 08:05:50 -05:00 committed by GitHub
parent d1be614a10
commit 2cde58037d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 26 additions and 30 deletions

View File

@ -20,7 +20,7 @@ from fastapi.encoders import jsonable_encoder
from fastapi.params import Depends
from fastapi.responses import JSONResponse, PlainTextResponse, StreamingResponse
from markupsafe import escape
from peewee import operator
from peewee import SQL, operator
from pydantic import ValidationError
from frigate.api.auth import require_role
@ -685,7 +685,14 @@ def plusModels(request: Request, filterByCurrentModelDetector: bool = False):
@router.get("/recognized_license_plates")
def get_recognized_license_plates(split_joined: Optional[int] = None):
try:
events = Event.select(Event.data).distinct()
query = (
Event.select(
SQL("json_extract(data, '$.recognized_license_plate') AS plate")
)
.where(SQL("json_extract(data, '$.recognized_license_plate') IS NOT NULL"))
.distinct()
)
recognized_license_plates = [row[0] for row in query.tuples()]
except Exception:
return JSONResponse(
content=(
@ -694,14 +701,6 @@ def get_recognized_license_plates(split_joined: Optional[int] = None):
status_code=404,
)
recognized_license_plates = []
for e in events:
if e.data is not None and "recognized_license_plate" in e.data:
recognized_license_plates.append(e.data["recognized_license_plate"])
while None in recognized_license_plates:
recognized_license_plates.remove(None)
if split_joined:
original_recognized_license_plates = recognized_license_plates.copy()
for recognized_license_plate in original_recognized_license_plates:

View File

@ -42,6 +42,7 @@ import {
CommandList,
} from "@/components/ui/command";
import { LuCheck } from "react-icons/lu";
import ActivityIndicator from "@/components/indicators/activity-indicator";
type SearchFilterDialogProps = {
config?: FrigateConfig;
@ -64,6 +65,9 @@ export default function SearchFilterDialog({
const { t } = useTranslation(["components/filter"]);
const [currentFilter, setCurrentFilter] = useState(filter ?? {});
const { data: allSubLabels } = useSWR(["sub_labels", { split_joined: 1 }]);
const { data: allRecognizedLicensePlates } = useSWR<string[]>(
"recognized_license_plates",
);
useEffect(() => {
if (filter) {
@ -130,6 +134,7 @@ export default function SearchFilterDialog({
}
/>
<RecognizedLicensePlatesFilterContent
allRecognizedLicensePlates={allRecognizedLicensePlates}
recognizedLicensePlates={currentFilter.recognized_license_plate}
setRecognizedLicensePlates={(plate) =>
setCurrentFilter({
@ -875,6 +880,7 @@ export function SnapshotClipFilterContent({
}
type RecognizedLicensePlatesFilterContentProps = {
allRecognizedLicensePlates: string[] | undefined;
recognizedLicensePlates: string[] | undefined;
setRecognizedLicensePlates: (
recognizedLicensePlates: string[] | undefined,
@ -882,18 +888,12 @@ type RecognizedLicensePlatesFilterContentProps = {
};
export function RecognizedLicensePlatesFilterContent({
allRecognizedLicensePlates,
recognizedLicensePlates,
setRecognizedLicensePlates,
}: RecognizedLicensePlatesFilterContentProps) {
const { t } = useTranslation(["components/filter"]);
const { data: allRecognizedLicensePlates, error } = useSWR<string[]>(
"recognized_license_plates",
{
revalidateOnFocus: false,
},
);
const [selectedRecognizedLicensePlates, setSelectedRecognizedLicensePlates] =
useState<string[]>(recognizedLicensePlates || []);
const [inputValue, setInputValue] = useState("");
@ -923,7 +923,7 @@ export function RecognizedLicensePlatesFilterContent({
}
};
if (!allRecognizedLicensePlates || allRecognizedLicensePlates.length === 0) {
if (allRecognizedLicensePlates && allRecognizedLicensePlates.length === 0) {
return null;
}
@ -947,15 +947,12 @@ export function RecognizedLicensePlatesFilterContent({
<div className="overflow-x-hidden">
<DropdownMenuSeparator className="mb-3" />
<div className="mb-3 text-lg">{t("recognizedLicensePlates.title")}</div>
{error ? (
<p className="text-sm text-red-500">
{t("recognizedLicensePlates.loadFailed")}
</p>
) : !allRecognizedLicensePlates ? (
<p className="text-sm text-muted-foreground">
{t("recognizedLicensePlates.loading")}
</p>
) : (
{allRecognizedLicensePlates == undefined ? (
<div className="flex flex-col items-center justify-center text-sm text-muted-foreground">
<ActivityIndicator className="mb-3 mr-2 size-5" />
<p>{t("recognizedLicensePlates.loading")}</p>
</div>
) : allRecognizedLicensePlates.length == 0 ? null : (
<>
<Command
className="border border-input bg-background"
@ -1010,11 +1007,11 @@ export function RecognizedLicensePlatesFilterContent({
))}
</div>
)}
<p className="mt-1 text-sm text-muted-foreground">
{t("recognizedLicensePlates.selectPlatesFromList")}
</p>
</>
)}
<p className="mt-1 text-sm text-muted-foreground">
{t("recognizedLicensePlates.selectPlatesFromList")}
</p>
</div>
);
}