Miscellaneous Fixes (#21033)

* catch failed image embedding in triggers

* move scrollbar to edge on platform aware dialog drawers

* add i18n key

* show negotiated mse codecs in console on error

* try changing rocm

* Improve toast consistency

* add attribute area and score to detail stream tooltip

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
This commit is contained in:
Josh Hawkins 2025-11-25 07:34:20 -06:00 committed by GitHub
parent 2a9c028f55
commit 3bbe24f5f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 136 additions and 46 deletions

View File

@ -2,7 +2,7 @@ variable "AMDGPU" {
default = "gfx900" default = "gfx900"
} }
variable "ROCM" { variable "ROCM" {
default = "7.1" default = "7.1.0"
} }
variable "HSA_OVERRIDE_GFX_VERSION" { variable "HSA_OVERRIDE_GFX_VERSION" {
default = "" default = ""

View File

@ -232,7 +232,7 @@ When your browser runs into problems playing back your camera streams, it will l
- **mse-decode** - **mse-decode**
- What it means: The browser reported a decoding error while trying to play the stream, which usually is a result of a codec incompatibility or corrupted frames. - What it means: The browser reported a decoding error while trying to play the stream, which usually is a result of a codec incompatibility or corrupted frames.
- What to try: Ensure your camera/restream is using H.264 video and AAC audio (these are the most compatible). If your camera uses a non-standard audio codec, configure `go2rtc` to transcode the stream to AAC. Try another browser (some browsers have stricter MSE/codec support) and, for iPhone, ensure you're on iOS 17.1 or newer. - What to try: Check the browser console for the supported and negotiated codecs. Ensure your camera/restream is using H.264 video and AAC audio (these are the most compatible). If your camera uses a non-standard audio codec, configure `go2rtc` to transcode the stream to AAC. Try another browser (some browsers have stricter MSE/codec support) and, for iPhone, ensure you're on iOS 17.1 or newer.
- Possible console messages from the player code: - Possible console messages from the player code:

View File

@ -1753,7 +1753,7 @@ def create_trigger_embedding(
body.data, (base64.b64encode(thumbnail).decode("ASCII")) body.data, (base64.b64encode(thumbnail).decode("ASCII"))
) )
if embedding is None: if not embedding:
return JSONResponse( return JSONResponse(
content={ content={
"success": False, "success": False,
@ -1888,7 +1888,7 @@ def update_trigger_embedding(
body.data, (base64.b64encode(thumbnail).decode("ASCII")) body.data, (base64.b64encode(thumbnail).decode("ASCII"))
) )
if embedding is None: if not embedding:
return JSONResponse( return JSONResponse(
content={ content={
"success": False, "success": False,

View File

@ -53,6 +53,7 @@
"selectOrExport": "Select or Export", "selectOrExport": "Select or Export",
"toast": { "toast": {
"success": "Successfully started export. View the file in the exports page.", "success": "Successfully started export. View the file in the exports page.",
"view": "View",
"error": { "error": {
"failed": "Failed to start export: {{error}}", "failed": "Failed to start export: {{error}}",
"endTimeMustAfterStartTime": "End time must be after start time", "endTimeMustAfterStartTime": "End time must be after start time",

View File

@ -61,7 +61,8 @@
"header": { "header": {
"zones": "Zones", "zones": "Zones",
"ratio": "Ratio", "ratio": "Ratio",
"area": "Area" "area": "Area",
"score": "Score"
} }
}, },
"annotationSettings": { "annotationSettings": {

View File

@ -87,7 +87,7 @@ export default function ReviewCard({
position: "top-center", position: "top-center",
action: ( action: (
<a href="/export" target="_blank" rel="noopener noreferrer"> <a href="/export" target="_blank" rel="noopener noreferrer">
<Button>View</Button> <Button>{t("export.toast.view")}</Button>
</a> </a>
), ),
}); });

View File

@ -148,7 +148,9 @@ export default function Step3ChooseExamples({
// Step 3: Kick off training // Step 3: Kick off training
await axios.post(`/classification/${step1Data.modelName}/train`); await axios.post(`/classification/${step1Data.modelName}/train`);
toast.success(t("wizard.step3.trainingStarted")); toast.success(t("wizard.step3.trainingStarted"), {
closeButton: true,
});
setIsTraining(true); setIsTraining(true);
}, },
[step1Data, step2Data, t], [step1Data, step2Data, t],

View File

@ -314,11 +314,10 @@ function GeneralFilterButton({
<PlatformAwareDialog <PlatformAwareDialog
trigger={trigger} trigger={trigger}
content={content} content={content}
contentClassName={ contentClassName={cn(
isDesktop "scrollbar-container h-auto overflow-y-auto",
? "scrollbar-container h-auto max-h-[80dvh] overflow-y-auto" isDesktop ? "max-h-[80dvh]" : "px-4",
: "max-h-[75dvh] overflow-hidden p-4" )}
}
open={open} open={open}
onOpenChange={(open) => { onOpenChange={(open) => {
if (!open) { if (!open) {
@ -510,11 +509,10 @@ function SortTypeButton({
<PlatformAwareDialog <PlatformAwareDialog
trigger={trigger} trigger={trigger}
content={content} content={content}
contentClassName={ contentClassName={cn(
isDesktop "scrollbar-container h-auto overflow-y-auto",
? "scrollbar-container h-auto max-h-[80dvh] overflow-y-auto" isDesktop ? "max-h-[80dvh]" : "px-4",
: "max-h-[75dvh] overflow-hidden p-4" )}
}
open={open} open={open}
onOpenChange={(open) => { onOpenChange={(open) => {
if (!open) { if (!open) {

View File

@ -97,7 +97,7 @@ export default function ExportDialog({
position: "top-center", position: "top-center",
action: ( action: (
<a href="/export" target="_blank" rel="noopener noreferrer"> <a href="/export" target="_blank" rel="noopener noreferrer">
<Button>View</Button> <Button>{t("export.toast.view")}</Button>
</a> </a>
), ),
}); });

View File

@ -106,7 +106,9 @@ export default function MobileReviewSettingsDrawer({
position: "top-center", position: "top-center",
action: ( action: (
<a href="/export" target="_blank" rel="noopener noreferrer"> <a href="/export" target="_blank" rel="noopener noreferrer">
<Button>View</Button> <Button>
{t("export.toast.view", { ns: "components/dialog" })}
</Button>
</a> </a>
), ),
}, },

View File

@ -44,8 +44,8 @@ export default function PlatformAwareDialog({
return ( return (
<Drawer open={open} onOpenChange={onOpenChange}> <Drawer open={open} onOpenChange={onOpenChange}>
<DrawerTrigger asChild>{trigger}</DrawerTrigger> <DrawerTrigger asChild>{trigger}</DrawerTrigger>
<DrawerContent className="max-h-[75dvh] overflow-hidden px-4"> <DrawerContent className="max-h-[75dvh] overflow-hidden">
{content} <div className={contentClassName}>{content}</div>
</DrawerContent> </DrawerContent>
</Drawer> </Drawer>
); );

View File

@ -94,12 +94,19 @@ function MSEPlayer({
console.error( console.error(
`${camera} - MSE error '${error}': ${description} See the documentation: https://docs.frigate.video/configuration/live/#live-player-error-messages`, `${camera} - MSE error '${error}': ${description} See the documentation: https://docs.frigate.video/configuration/live/#live-player-error-messages`,
); );
if (mseCodecRef.current) { if (mseCodecRef.current) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error(`${camera} - MSE codec in use: ${mseCodecRef.current}`); console.error(
`${camera} - Browser negotiated codecs: ${mseCodecRef.current}`,
);
// eslint-disable-next-line no-console
console.error(`${camera} - Supported codecs: ${CODECS.join(", ")}`);
} }
onError?.(error); onError?.(error);
}, },
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
[camera, onError], [camera, onError],
); );

View File

@ -136,11 +136,10 @@ export default function ExploreSettings({
<PlatformAwareDialog <PlatformAwareDialog
trigger={trigger} trigger={trigger}
content={content} content={content}
contentClassName={ contentClassName={cn(
isDesktop "scrollbar-container h-auto overflow-y-auto",
? "scrollbar-container h-auto max-h-[80dvh] overflow-y-auto" isDesktop ? "max-h-[80dvh]" : "px-4",
: "max-h-[75dvh] overflow-hidden p-4" )}
}
open={open} open={open}
onOpenChange={(open) => { onOpenChange={(open) => {
setOpen(open); setOpen(open);

View File

@ -681,22 +681,62 @@ function LifecycleItem({
}) })
: ""; : "";
const ratio = const ratio = useMemo(
Array.isArray(item?.data.box) && item?.data.box.length >= 4 () =>
? (aspectRatio * (item?.data.box[2] / item?.data.box[3])).toFixed(2) Array.isArray(item?.data.box) && item?.data.box.length >= 4
: "N/A"; ? (aspectRatio * (item?.data.box[2] / item?.data.box[3])).toFixed(2)
const areaPx = : "N/A",
Array.isArray(item?.data.box) && item?.data.box.length >= 4 [aspectRatio, item],
? Math.round( );
(config?.cameras[item?.camera]?.detect?.width ?? 0) *
(config?.cameras[item?.camera]?.detect?.height ?? 0) * const areaPx = useMemo(
(item?.data.box[2] * item?.data.box[3]), () =>
) Array.isArray(item?.data.box) && item?.data.box.length >= 4
: undefined; ? Math.round(
const areaPct = (config?.cameras[item?.camera]?.detect?.width ?? 0) *
Array.isArray(item?.data.box) && item?.data.box.length >= 4 (config?.cameras[item?.camera]?.detect?.height ?? 0) *
? (item?.data.box[2] * item?.data.box[3]).toFixed(4) (item?.data.box[2] * item?.data.box[3]),
: undefined; )
: undefined,
[config, item],
);
const areaPct = useMemo(
() =>
Array.isArray(item?.data.box) && item?.data.box.length >= 4
? (item?.data.box[2] * item?.data.box[3]).toFixed(4)
: undefined,
[item],
);
const attributeAreaPx = useMemo(
() =>
Array.isArray(item?.data.attribute_box) &&
item?.data.attribute_box.length >= 4
? Math.round(
(config?.cameras[item?.camera]?.detect?.width ?? 0) *
(config?.cameras[item?.camera]?.detect?.height ?? 0) *
(item?.data.attribute_box[2] * item?.data.attribute_box[3]),
)
: undefined,
[config, item],
);
const attributeAreaPct = useMemo(
() =>
Array.isArray(item?.data.attribute_box) &&
item?.data.attribute_box.length >= 4
? (item?.data.attribute_box[2] * item?.data.attribute_box[3]).toFixed(4)
: undefined,
[item],
);
const score = useMemo(() => {
if (item?.data?.score !== undefined) {
return (item.data.score * 100).toFixed(0) + "%";
}
return "N/A";
}, [item?.data?.score]);
return ( return (
<div <div
@ -733,6 +773,13 @@ function LifecycleItem({
<TooltipContent> <TooltipContent>
<div className="mt-1 flex flex-wrap items-start gap-3 text-sm text-secondary-foreground"> <div className="mt-1 flex flex-wrap items-start gap-3 text-sm text-secondary-foreground">
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<div className="flex items-start gap-1">
<span className="text-muted-foreground">
{t("trackingDetails.lifecycleItemDesc.header.score")}
</span>
<span className="font-medium text-foreground">{score}</span>
</div>
<div className="flex items-start gap-1"> <div className="flex items-start gap-1">
<span className="text-muted-foreground"> <span className="text-muted-foreground">
{t("trackingDetails.lifecycleItemDesc.header.ratio")} {t("trackingDetails.lifecycleItemDesc.header.ratio")}
@ -742,7 +789,13 @@ function LifecycleItem({
<div className="flex items-start gap-1"> <div className="flex items-start gap-1">
<span className="text-muted-foreground"> <span className="text-muted-foreground">
{t("trackingDetails.lifecycleItemDesc.header.area")} {t("trackingDetails.lifecycleItemDesc.header.area")}{" "}
{attributeAreaPx !== undefined &&
attributeAreaPct !== undefined && (
<span className="text-muted-foreground">
({getTranslatedLabel(item.data.label)})
</span>
)}
</span> </span>
{areaPx !== undefined && areaPct !== undefined ? ( {areaPx !== undefined && areaPct !== undefined ? (
<span className="font-medium text-foreground"> <span className="font-medium text-foreground">
@ -754,6 +807,26 @@ function LifecycleItem({
<span>N/A</span> <span>N/A</span>
)} )}
</div> </div>
{attributeAreaPx !== undefined &&
attributeAreaPct !== undefined && (
<div className="flex items-start gap-1">
<span className="text-muted-foreground">
{t("trackingDetails.lifecycleItemDesc.header.area")}{" "}
{attributeAreaPx !== undefined &&
attributeAreaPct !== undefined && (
<span className="text-muted-foreground">
({getTranslatedLabel(item.data.attribute)})
</span>
)}
</span>
<span className="font-medium text-foreground">
{attributeAreaPx} {t("pixels", { ns: "common" })}{" "}
<span className="text-secondary-foreground">·</span>{" "}
{attributeAreaPct}%
</span>
</div>
)}
</div> </div>
</div> </div>
</TooltipContent> </TooltipContent>
@ -820,7 +893,7 @@ function ObjectTimeline({
}, [config, fullTimeline, review]); }, [config, fullTimeline, review]);
if (isValidating && (!timeline || timeline.length === 0)) { if (isValidating && (!timeline || timeline.length === 0)) {
return <ActivityIndicator className="ml-2 size-3" />; return <ActivityIndicator className="ml-2.5 size-3" />;
} }
if (!timeline || timeline.length === 0) { if (!timeline || timeline.length === 0) {

View File

@ -808,6 +808,7 @@ function FaceAttemptGroup({
if (resp.status == 200) { if (resp.status == 200) {
toast.success(t("toast.success.trainedFace"), { toast.success(t("toast.success.trainedFace"), {
position: "top-center", position: "top-center",
closeButton: true,
}); });
onRefresh(); onRefresh();
} }

View File

@ -104,12 +104,14 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) {
if (modelState == "complete") { if (modelState == "complete") {
toast.success(t("toast.success.trainedModel"), { toast.success(t("toast.success.trainedModel"), {
position: "top-center", position: "top-center",
closeButton: true,
}); });
setWasTraining(false); setWasTraining(false);
refreshDataset(); refreshDataset();
} else if (modelState == "failed") { } else if (modelState == "failed") {
toast.error(t("toast.error.trainingFailed"), { toast.error(t("toast.error.trainingFailed"), {
position: "top-center", position: "top-center",
closeButton: true,
}); });
setWasTraining(false); setWasTraining(false);
} }
@ -182,6 +184,7 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) {
setWasTraining(true); setWasTraining(true);
toast.success(t("toast.success.trainingModel"), { toast.success(t("toast.success.trainingModel"), {
position: "top-center", position: "top-center",
closeButton: true,
}); });
} }
}) })
@ -193,6 +196,7 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) {
toast.error(t("toast.error.trainingFailedToStart", { errorMessage }), { toast.error(t("toast.error.trainingFailedToStart", { errorMessage }), {
position: "top-center", position: "top-center",
closeButton: true,
}); });
}); });
}, [model, t]); }, [model, t]);

View File

@ -219,7 +219,9 @@ export default function EventView({
position: "top-center", position: "top-center",
action: ( action: (
<a href="/export" target="_blank" rel="noopener noreferrer"> <a href="/export" target="_blank" rel="noopener noreferrer">
<Button>View</Button> <Button>
{t("export.toast.view", { ns: "components/dialog" })}
</Button>
</a> </a>
), ),
}, },