UI improvements (#17684)

* Add sub label to explore chip

* Add bird classification to settings

* Remove score

* Cleanup
This commit is contained in:
Nicolas Mowen 2025-04-13 12:06:59 -06:00 committed by GitHub
parent 4abf945b71
commit ea98785097
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 92 additions and 3 deletions

View File

@ -83,6 +83,10 @@
}, },
"classification": { "classification": {
"title": "Classification Settings", "title": "Classification Settings",
"birdClassification": {
"title": "Bird Classification",
"desc": "Bird classification identifies known birds using a quantized Tensorflow model. When a known bird is recognized, its common name will be added as a sub_label. This information is included in the UI, filters, as well as in notifications."
},
"semanticSearch": { "semanticSearch": {
"title": "Semantic Search", "title": "Semantic Search",
"desc": "Semantic Search in Frigate allows you to find tracked objects within your review items using either the image itself, a user-defined text description, or an automatically generated one.", "desc": "Semantic Search in Frigate allows you to find tracked objects within your review items using either the image itself, a user-defined text description, or an automatically generated one.",

View File

@ -69,6 +69,30 @@ export default function SearchThumbnail({
return `${searchResult.label}-verified`; return `${searchResult.label}-verified`;
}, [config, hasRecognizedPlate, searchResult]); }, [config, hasRecognizedPlate, searchResult]);
const objectDetail = useMemo(() => {
if (!config) {
return undefined;
}
if (!searchResult.sub_label) {
if (hasRecognizedPlate) {
return `(${searchResult.data.recognized_license_plate})`;
}
return undefined;
}
if (
config.model.attributes_map[searchResult.label]?.includes(
searchResult.sub_label,
)
) {
return "";
}
return `(${searchResult.sub_label})`;
}, [config, hasRecognizedPlate, searchResult]);
return ( return (
<div <div
className="relative size-full cursor-pointer" className="relative size-full cursor-pointer"
@ -107,7 +131,7 @@ export default function SearchThumbnail({
<TooltipTrigger asChild> <TooltipTrigger asChild>
<div className="mx-3 pb-1 text-sm text-white"> <div className="mx-3 pb-1 text-sm text-white">
<Chip <Chip
className={`z-0 flex items-center justify-between gap-1 space-x-1 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500 text-xs`} className={`z-0 flex items-center justify-between gap-1 space-x-1 bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500 text-xs capitalize`}
onClick={() => onClick(searchResult, false, true)} onClick={() => onClick(searchResult, false, true)}
> >
{getIconForLabel(objectLabel, "size-3 text-white")} {getIconForLabel(objectLabel, "size-3 text-white")}
@ -116,7 +140,7 @@ export default function SearchThumbnail({
searchResult.data.top_score ?? searchResult.data.top_score ??
searchResult.top_score) * 100, searchResult.top_score) * 100,
)} )}
% % {objectDetail}
</Chip> </Chip>
</div> </div>
</TooltipTrigger> </TooltipTrigger>

View File

@ -298,6 +298,13 @@ export interface FrigateConfig {
[cameraName: string]: CameraConfig; [cameraName: string]: CameraConfig;
}; };
classification: {
bird: {
enabled: boolean;
threshold: number;
};
};
database: { database: {
path: string; path: string;
}; };

View File

@ -45,6 +45,9 @@ type ClassificationSettings = {
lpr: { lpr: {
enabled?: boolean; enabled?: boolean;
}; };
bird: {
enabled?: boolean;
};
}; };
type ClassificationSettingsViewProps = { type ClassificationSettingsViewProps = {
@ -67,6 +70,7 @@ export default function ClassificationSettingsView({
search: { enabled: undefined, model_size: undefined }, search: { enabled: undefined, model_size: undefined },
face: { enabled: undefined, model_size: undefined }, face: { enabled: undefined, model_size: undefined },
lpr: { enabled: undefined }, lpr: { enabled: undefined },
bird: { enabled: undefined },
}); });
const [origSearchSettings, setOrigSearchSettings] = const [origSearchSettings, setOrigSearchSettings] =
@ -74,6 +78,7 @@ export default function ClassificationSettingsView({
search: { enabled: undefined, model_size: undefined }, search: { enabled: undefined, model_size: undefined },
face: { enabled: undefined, model_size: undefined }, face: { enabled: undefined, model_size: undefined },
lpr: { enabled: undefined }, lpr: { enabled: undefined },
bird: { enabled: undefined },
}); });
useEffect(() => { useEffect(() => {
@ -89,6 +94,9 @@ export default function ClassificationSettingsView({
model_size: config.face_recognition.model_size, model_size: config.face_recognition.model_size,
}, },
lpr: { enabled: config.lpr.enabled }, lpr: { enabled: config.lpr.enabled },
bird: {
enabled: config.classification.bird.enabled,
},
}); });
} }
@ -102,6 +110,7 @@ export default function ClassificationSettingsView({
model_size: config.face_recognition.model_size, model_size: config.face_recognition.model_size,
}, },
lpr: { enabled: config.lpr.enabled }, lpr: { enabled: config.lpr.enabled },
bird: { enabled: config.classification.bird.enabled },
}); });
} }
// we know that these deps are correct // we know that these deps are correct
@ -115,6 +124,7 @@ export default function ClassificationSettingsView({
search: { ...prevConfig.search, ...newConfig.search }, search: { ...prevConfig.search, ...newConfig.search },
face: { ...prevConfig.face, ...newConfig.face }, face: { ...prevConfig.face, ...newConfig.face },
lpr: { ...prevConfig.lpr, ...newConfig.lpr }, lpr: { ...prevConfig.lpr, ...newConfig.lpr },
bird: { ...prevConfig.bird, ...newConfig.bird },
})); }));
setUnsavedChanges(true); setUnsavedChanges(true);
setChangedValue(true); setChangedValue(true);
@ -125,7 +135,7 @@ export default function ClassificationSettingsView({
axios axios
.put( .put(
`config/set?semantic_search.enabled=${classificationSettings.search.enabled ? "True" : "False"}&semantic_search.model_size=${classificationSettings.search.model_size}&face_recognition.enabled=${classificationSettings.face.enabled ? "True" : "False"}&face_recognition.model_size=${classificationSettings.face.model_size}&lpr.enabled=${classificationSettings.lpr.enabled ? "True" : "False"}`, `config/set?semantic_search.enabled=${classificationSettings.search.enabled ? "True" : "False"}&semantic_search.model_size=${classificationSettings.search.model_size}&face_recognition.enabled=${classificationSettings.face.enabled ? "True" : "False"}&face_recognition.model_size=${classificationSettings.face.model_size}&lpr.enabled=${classificationSettings.lpr.enabled ? "True" : "False"}&classification.bird.enabled=${classificationSettings.bird.enabled ? "True" : "False"}`,
{ requires_restart: 0 }, { requires_restart: 0 },
) )
.then((res) => { .then((res) => {
@ -526,6 +536,50 @@ export default function ClassificationSettingsView({
<Separator className="my-2 flex bg-secondary" /> <Separator className="my-2 flex bg-secondary" />
<Heading as="h4" className="my-2">
{t("classification.birdClassification.title")}
</Heading>
<div className="max-w-6xl">
<div className="mb-5 mt-2 flex max-w-5xl flex-col gap-2 text-sm text-primary-variant">
<p>{t("classification.birdClassification.desc")}</p>
<div className="flex items-center text-primary">
<Link
to="https://docs.frigate.video/configuration/license_plate_recognition"
target="_blank"
rel="noopener noreferrer"
className="inline"
>
{t("classification.semanticSearch.readTheDocumentation")}
<LuExternalLink className="ml-2 inline-flex size-3" />
</Link>
</div>
</div>
</div>
<div className="flex w-full max-w-lg flex-col space-y-6">
<div className="flex flex-row items-center">
<Switch
id="enabled"
className="mr-3"
disabled={classificationSettings.bird.enabled === undefined}
checked={classificationSettings.bird.enabled === true}
onCheckedChange={(isChecked) => {
handleClassificationConfigChange({
bird: { enabled: isChecked },
});
}}
/>
<div className="space-y-0.5">
<Label htmlFor="enabled">
{t("button.enabled", { ns: "common" })}
</Label>
</div>
</div>
</div>
<Separator className="my-2 flex bg-secondary" />
<div className="flex w-full flex-row items-center gap-2 pt-2 md:w-[25%]"> <div className="flex w-full flex-row items-center gap-2 pt-2 md:w-[25%]">
<Button <Button
className="flex flex-1" className="flex flex-1"