mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-01-22 20:18:30 +03:00
add attributes to tracked object details pane
This commit is contained in:
parent
3820c511d9
commit
0a28bd7425
@ -136,6 +136,7 @@
|
||||
"label": "Score"
|
||||
},
|
||||
"recognizedLicensePlate": "Recognized License Plate",
|
||||
"attributes": "Classification Attributes",
|
||||
"estimatedSpeed": "Estimated Speed",
|
||||
"objects": "Objects",
|
||||
"camera": "Camera",
|
||||
|
||||
@ -678,6 +678,15 @@ function ObjectDetailsTab({
|
||||
]);
|
||||
|
||||
const apiHost = useApiHost();
|
||||
const hasCustomClassificationModels = useMemo(
|
||||
() => Object.keys(config?.classification?.custom ?? {}).length > 0,
|
||||
[config],
|
||||
);
|
||||
const { data: allowedAttributes } = useSWR<string[]>(
|
||||
hasCustomClassificationModels && search
|
||||
? `classification/attributes?object_type=${encodeURIComponent(search.label)}`
|
||||
: null,
|
||||
);
|
||||
|
||||
// mutation / revalidation
|
||||
|
||||
@ -807,6 +816,24 @@ function ObjectDetailsTab({
|
||||
}
|
||||
}, [search]);
|
||||
|
||||
const eventAttributes = useMemo(() => {
|
||||
if (!search || !allowedAttributes || allowedAttributes.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const collected = new Set<string>();
|
||||
const dataAny = search.data as Record<string, unknown>;
|
||||
|
||||
// Check top-level keys in data that match allowed attributes
|
||||
allowedAttributes.forEach((attr) => {
|
||||
if (dataAny[attr] !== undefined && dataAny[attr] !== null) {
|
||||
collected.add(attr);
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(collected).sort((a, b) => a.localeCompare(b));
|
||||
}, [search, allowedAttributes]);
|
||||
|
||||
const isEventsKey = useCallback((key: unknown): boolean => {
|
||||
const candidate = Array.isArray(key) ? key[0] : key;
|
||||
const EVENTS_KEY_PATTERNS = ["events", "events/search", "events/explore"];
|
||||
@ -1295,6 +1322,15 @@ function ObjectDetailsTab({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{eventAttributes.length > 0 && (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="text-sm text-primary/40">
|
||||
{t("details.attributes")}
|
||||
</div>
|
||||
<div className="text-sm">{eventAttributes.join(", ")}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -65,7 +65,13 @@ 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: allAttributes } = useSWR("classification/attributes");
|
||||
const hasCustomClassificationModels = useMemo(
|
||||
() => Object.keys(config?.classification?.custom ?? {}).length > 0,
|
||||
[config],
|
||||
);
|
||||
const { data: allAttributes } = useSWR(
|
||||
hasCustomClassificationModels ? "classification/attributes" : null,
|
||||
);
|
||||
const { data: allRecognizedLicensePlates } = useSWR<string[]>(
|
||||
"recognized_license_plates",
|
||||
);
|
||||
@ -92,9 +98,10 @@ export default function SearchFilterDialog({
|
||||
(currentFilter.max_speed ?? 150) < 150 ||
|
||||
(currentFilter.zones?.length ?? 0) > 0 ||
|
||||
(currentFilter.sub_labels?.length ?? 0) > 0 ||
|
||||
(currentFilter.attributes?.length ?? 0) > 0 ||
|
||||
(hasCustomClassificationModels &&
|
||||
(currentFilter.attributes?.length ?? 0) > 0) ||
|
||||
(currentFilter.recognized_license_plate?.length ?? 0) > 0),
|
||||
[currentFilter],
|
||||
[currentFilter, hasCustomClassificationModels],
|
||||
);
|
||||
|
||||
const trigger = (
|
||||
@ -135,13 +142,15 @@ export default function SearchFilterDialog({
|
||||
setCurrentFilter({ ...currentFilter, sub_labels: newSubLabels })
|
||||
}
|
||||
/>
|
||||
<AttributeFilterContent
|
||||
allAttributes={allAttributes}
|
||||
attributes={currentFilter.attributes}
|
||||
setAttributes={(newAttributes) =>
|
||||
setCurrentFilter({ ...currentFilter, attributes: newAttributes })
|
||||
}
|
||||
/>
|
||||
{hasCustomClassificationModels && (
|
||||
<AttributeFilterContent
|
||||
allAttributes={allAttributes}
|
||||
attributes={currentFilter.attributes}
|
||||
setAttributes={(newAttributes) =>
|
||||
setCurrentFilter({ ...currentFilter, attributes: newAttributes })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<RecognizedLicensePlatesFilterContent
|
||||
allRecognizedLicensePlates={allRecognizedLicensePlates}
|
||||
recognizedLicensePlates={currentFilter.recognized_license_plate}
|
||||
@ -225,6 +234,7 @@ export default function SearchFilterDialog({
|
||||
max_speed: undefined,
|
||||
has_snapshot: undefined,
|
||||
has_clip: undefined,
|
||||
...(hasCustomClassificationModels && { attributes: undefined }),
|
||||
recognized_license_plate: undefined,
|
||||
}));
|
||||
}}
|
||||
@ -1098,7 +1108,7 @@ export function RecognizedLicensePlatesFilterContent({
|
||||
}
|
||||
|
||||
type AttributeFilterContentProps = {
|
||||
allAttributes: string[];
|
||||
allAttributes?: string[];
|
||||
attributes: string[] | undefined;
|
||||
setAttributes: (labels: string[] | undefined) => void;
|
||||
};
|
||||
|
||||
@ -143,7 +143,13 @@ export default function SearchView({
|
||||
}, [config, searchFilter, allowedCameras]);
|
||||
|
||||
const { data: allSubLabels } = useSWR("sub_labels");
|
||||
const { data: allAttributes } = useSWR("classification/attributes");
|
||||
const hasCustomClassificationModels = useMemo(
|
||||
() => Object.keys(config?.classification?.custom ?? {}).length > 0,
|
||||
[config],
|
||||
);
|
||||
const { data: allAttributes } = useSWR(
|
||||
hasCustomClassificationModels ? "classification/attributes" : null,
|
||||
);
|
||||
const { data: allRecognizedLicensePlates } = useSWR(
|
||||
"recognized_license_plates",
|
||||
);
|
||||
@ -183,7 +189,7 @@ export default function SearchView({
|
||||
labels: Object.values(allLabels || {}),
|
||||
zones: Object.values(allZones || {}),
|
||||
sub_labels: allSubLabels,
|
||||
attributes: allAttributes,
|
||||
...(hasCustomClassificationModels && { attributes: allAttributes }),
|
||||
search_type: ["thumbnail", "description"] as SearchSource[],
|
||||
time_range:
|
||||
config?.ui.time_format == "24hour"
|
||||
@ -210,6 +216,7 @@ export default function SearchView({
|
||||
allRecognizedLicensePlates,
|
||||
searchFilter,
|
||||
allowedCameras,
|
||||
hasCustomClassificationModels,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user