mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-01-26 22: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"
|
"label": "Score"
|
||||||
},
|
},
|
||||||
"recognizedLicensePlate": "Recognized License Plate",
|
"recognizedLicensePlate": "Recognized License Plate",
|
||||||
|
"attributes": "Classification Attributes",
|
||||||
"estimatedSpeed": "Estimated Speed",
|
"estimatedSpeed": "Estimated Speed",
|
||||||
"objects": "Objects",
|
"objects": "Objects",
|
||||||
"camera": "Camera",
|
"camera": "Camera",
|
||||||
|
|||||||
@ -678,6 +678,15 @@ function ObjectDetailsTab({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const apiHost = useApiHost();
|
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
|
// mutation / revalidation
|
||||||
|
|
||||||
@ -807,6 +816,24 @@ function ObjectDetailsTab({
|
|||||||
}
|
}
|
||||||
}, [search]);
|
}, [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 isEventsKey = useCallback((key: unknown): boolean => {
|
||||||
const candidate = Array.isArray(key) ? key[0] : key;
|
const candidate = Array.isArray(key) ? key[0] : key;
|
||||||
const EVENTS_KEY_PATTERNS = ["events", "events/search", "events/explore"];
|
const EVENTS_KEY_PATTERNS = ["events", "events/search", "events/explore"];
|
||||||
@ -1295,6 +1322,15 @@ function ObjectDetailsTab({
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -65,7 +65,13 @@ export default function SearchFilterDialog({
|
|||||||
const { t } = useTranslation(["components/filter"]);
|
const { t } = useTranslation(["components/filter"]);
|
||||||
const [currentFilter, setCurrentFilter] = useState(filter ?? {});
|
const [currentFilter, setCurrentFilter] = useState(filter ?? {});
|
||||||
const { data: allSubLabels } = useSWR(["sub_labels", { split_joined: 1 }]);
|
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[]>(
|
const { data: allRecognizedLicensePlates } = useSWR<string[]>(
|
||||||
"recognized_license_plates",
|
"recognized_license_plates",
|
||||||
);
|
);
|
||||||
@ -92,9 +98,10 @@ 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.attributes?.length ?? 0) > 0 ||
|
(hasCustomClassificationModels &&
|
||||||
|
(currentFilter.attributes?.length ?? 0) > 0) ||
|
||||||
(currentFilter.recognized_license_plate?.length ?? 0) > 0),
|
(currentFilter.recognized_license_plate?.length ?? 0) > 0),
|
||||||
[currentFilter],
|
[currentFilter, hasCustomClassificationModels],
|
||||||
);
|
);
|
||||||
|
|
||||||
const trigger = (
|
const trigger = (
|
||||||
@ -135,13 +142,15 @@ export default function SearchFilterDialog({
|
|||||||
setCurrentFilter({ ...currentFilter, sub_labels: newSubLabels })
|
setCurrentFilter({ ...currentFilter, sub_labels: newSubLabels })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<AttributeFilterContent
|
{hasCustomClassificationModels && (
|
||||||
allAttributes={allAttributes}
|
<AttributeFilterContent
|
||||||
attributes={currentFilter.attributes}
|
allAttributes={allAttributes}
|
||||||
setAttributes={(newAttributes) =>
|
attributes={currentFilter.attributes}
|
||||||
setCurrentFilter({ ...currentFilter, attributes: newAttributes })
|
setAttributes={(newAttributes) =>
|
||||||
}
|
setCurrentFilter({ ...currentFilter, attributes: newAttributes })
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<RecognizedLicensePlatesFilterContent
|
<RecognizedLicensePlatesFilterContent
|
||||||
allRecognizedLicensePlates={allRecognizedLicensePlates}
|
allRecognizedLicensePlates={allRecognizedLicensePlates}
|
||||||
recognizedLicensePlates={currentFilter.recognized_license_plate}
|
recognizedLicensePlates={currentFilter.recognized_license_plate}
|
||||||
@ -225,6 +234,7 @@ export default function SearchFilterDialog({
|
|||||||
max_speed: undefined,
|
max_speed: undefined,
|
||||||
has_snapshot: undefined,
|
has_snapshot: undefined,
|
||||||
has_clip: undefined,
|
has_clip: undefined,
|
||||||
|
...(hasCustomClassificationModels && { attributes: undefined }),
|
||||||
recognized_license_plate: undefined,
|
recognized_license_plate: undefined,
|
||||||
}));
|
}));
|
||||||
}}
|
}}
|
||||||
@ -1098,7 +1108,7 @@ export function RecognizedLicensePlatesFilterContent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AttributeFilterContentProps = {
|
type AttributeFilterContentProps = {
|
||||||
allAttributes: string[];
|
allAttributes?: string[];
|
||||||
attributes: string[] | undefined;
|
attributes: string[] | undefined;
|
||||||
setAttributes: (labels: string[] | undefined) => void;
|
setAttributes: (labels: string[] | undefined) => void;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -143,7 +143,13 @@ export default function SearchView({
|
|||||||
}, [config, searchFilter, allowedCameras]);
|
}, [config, searchFilter, allowedCameras]);
|
||||||
|
|
||||||
const { data: allSubLabels } = useSWR("sub_labels");
|
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(
|
const { data: allRecognizedLicensePlates } = useSWR(
|
||||||
"recognized_license_plates",
|
"recognized_license_plates",
|
||||||
);
|
);
|
||||||
@ -183,7 +189,7 @@ export default function SearchView({
|
|||||||
labels: Object.values(allLabels || {}),
|
labels: Object.values(allLabels || {}),
|
||||||
zones: Object.values(allZones || {}),
|
zones: Object.values(allZones || {}),
|
||||||
sub_labels: allSubLabels,
|
sub_labels: allSubLabels,
|
||||||
attributes: allAttributes,
|
...(hasCustomClassificationModels && { attributes: allAttributes }),
|
||||||
search_type: ["thumbnail", "description"] as SearchSource[],
|
search_type: ["thumbnail", "description"] as SearchSource[],
|
||||||
time_range:
|
time_range:
|
||||||
config?.ui.time_format == "24hour"
|
config?.ui.time_format == "24hour"
|
||||||
@ -210,6 +216,7 @@ export default function SearchView({
|
|||||||
allRecognizedLicensePlates,
|
allRecognizedLicensePlates,
|
||||||
searchFilter,
|
searchFilter,
|
||||||
allowedCameras,
|
allowedCameras,
|
||||||
|
hasCustomClassificationModels,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user