Miscellaneous Fixes (0.17 beta) (#21355)

* remove footer messages and add update topic to motion tuner view

restart after changing values is no longer required

* add cache key and activity indicator for loading classification wizard images

* Always mark model as untrained when a classname is changed

* clarify object classification docs

* add debug logs for individual lpr replace_rules

* update memray docs

* memray tweaks

* Don't fail for audio transcription when semantic search is not enabled

* Fix incorrect mismatch for object vs sub label

* Check if the video is currently playing when deciding to seek due to misalignment

* Refactor timeline event handling to allow multiple timeline entries per update

* Check if zones have actually changed (not just count) for event state update

* show event icon on mobile

* move div inside conditional

---------

Co-authored-by: Nicolas Mowen <nickmowen213@gmail.com>
This commit is contained in:
Josh Hawkins
2025-12-19 18:59:26 -06:00
committed by GitHub
parent e636449d56
commit 60052e5f9f
11 changed files with 139 additions and 91 deletions

View File

@@ -233,7 +233,7 @@ export function GroupedClassificationCard({
});
if (!best) {
return group.at(-1);
best = group.at(-1)!;
}
const bestTyped: ClassificationItemData = best;
@@ -377,30 +377,34 @@ export function GroupedClassificationCard({
)}
</ContentDescription>
</div>
{isDesktop && (
<div className="flex flex-row justify-between">
{classifiedEvent && (
<Tooltip>
<TooltipTrigger asChild>
<div
className="cursor-pointer"
tabIndex={-1}
onClick={() => {
navigate(`/explore?event_id=${classifiedEvent.id}`);
}}
>
<LuSearch className="size-4 text-secondary-foreground" />
</div>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
{t("details.item.button.viewInExplore", {
ns: "views/explore",
})}
</TooltipContent>
</TooltipPortal>
</Tooltip>
{classifiedEvent && (
<div
className={cn(
"flex",
isDesktop && "flex-row justify-between",
isMobile && "absolute right-4 top-8",
)}
>
<Tooltip>
<TooltipTrigger asChild>
<div
className="cursor-pointer"
tabIndex={-1}
onClick={() => {
navigate(`/explore?event_id=${classifiedEvent.id}`);
}}
>
<LuSearch className="size-4 text-secondary-foreground" />
</div>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
{t("details.item.button.viewInExplore", {
ns: "views/explore",
})}
</TooltipContent>
</TooltipPortal>
</Tooltip>
</div>
)}
</Header>

View File

@@ -45,6 +45,12 @@ export default function Step3ChooseExamples({
const [isProcessing, setIsProcessing] = useState(false);
const [currentClassIndex, setCurrentClassIndex] = useState(0);
const [selectedImages, setSelectedImages] = useState<Set<string>>(new Set());
const [cacheKey, setCacheKey] = useState<number>(Date.now());
const [loadedImages, setLoadedImages] = useState<Set<string>>(new Set());
const handleImageLoad = useCallback((imageName: string) => {
setLoadedImages((prev) => new Set(prev).add(imageName));
}, []);
const { data: trainImages, mutate: refreshTrainImages } = useSWR<string[]>(
hasGenerated ? `classification/${step1Data.modelName}/train` : null,
@@ -332,6 +338,8 @@ export default function Step3ChooseExamples({
setHasGenerated(true);
toast.success(t("wizard.step3.generateSuccess"));
// Update cache key to force image reload
setCacheKey(Date.now());
await refreshTrainImages();
} catch (error) {
const axiosError = error as {
@@ -565,10 +573,16 @@ export default function Step3ChooseExamples({
)}
onClick={() => toggleImageSelection(imageName)}
>
{!loadedImages.has(imageName) && (
<div className="flex h-full items-center justify-center">
<ActivityIndicator className="size-6" />
</div>
)}
<img
src={`${baseUrl}clips/${step1Data.modelName}/train/${imageName}`}
src={`${baseUrl}clips/${step1Data.modelName}/train/${imageName}?t=${cacheKey}`}
alt={`Example ${index + 1}`}
className="h-full w-full object-cover"
onLoad={() => handleImageLoad(imageName)}
/>
</div>
);

View File

@@ -309,7 +309,10 @@ export function RecordingView({
currentTimeRange.after <= currentTime &&
currentTimeRange.before >= currentTime
) {
mainControllerRef.current?.seekToTimestamp(currentTime, true);
mainControllerRef.current?.seekToTimestamp(
currentTime,
mainControllerRef.current.isPlaying(),
);
} else {
updateSelectedSegment(currentTime, true);
}

View File

@@ -4,7 +4,7 @@ import useSWR from "swr";
import axios from "axios";
import ActivityIndicator from "@/components/indicators/activity-indicator";
import AutoUpdatingCameraImage from "@/components/camera/AutoUpdatingCameraImage";
import { useCallback, useContext, useEffect, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { Slider } from "@/components/ui/slider";
import { Label } from "@/components/ui/label";
import {
@@ -20,7 +20,6 @@ import { toast } from "sonner";
import { Separator } from "@/components/ui/separator";
import { Link } from "react-router-dom";
import { LuExternalLink } from "react-icons/lu";
import { StatusBarMessagesContext } from "@/context/statusbar-provider";
import { Trans, useTranslation } from "react-i18next";
import { useDocDomain } from "@/hooks/use-doc-domain";
import { cn } from "@/lib/utils";
@@ -48,8 +47,6 @@ export default function MotionTunerView({
const [changedValue, setChangedValue] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const { addMessage, removeMessage } = useContext(StatusBarMessagesContext)!;
const { send: sendMotionThreshold } = useMotionThreshold(selectedCamera);
const { send: sendMotionContourArea } = useMotionContourArea(selectedCamera);
const { send: sendImproveContrast } = useImproveContrast(selectedCamera);
@@ -119,7 +116,10 @@ export default function MotionTunerView({
axios
.put(
`config/set?cameras.${selectedCamera}.motion.threshold=${motionSettings.threshold}&cameras.${selectedCamera}.motion.contour_area=${motionSettings.contour_area}&cameras.${selectedCamera}.motion.improve_contrast=${motionSettings.improve_contrast ? "True" : "False"}`,
{ requires_restart: 0 },
{
requires_restart: 0,
update_topic: `config/cameras/${selectedCamera}/motion`,
},
)
.then((res) => {
if (res.status === 200) {
@@ -164,23 +164,7 @@ export default function MotionTunerView({
const onCancel = useCallback(() => {
setMotionSettings(origMotionSettings);
setChangedValue(false);
removeMessage("motion_tuner", `motion_tuner_${selectedCamera}`);
}, [origMotionSettings, removeMessage, selectedCamera]);
useEffect(() => {
if (changedValue) {
addMessage(
"motion_tuner",
t("motionDetectionTuner.unsavedChanges", { camera: selectedCamera }),
undefined,
`motion_tuner_${selectedCamera}`,
);
} else {
removeMessage("motion_tuner", `motion_tuner_${selectedCamera}`);
}
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [changedValue, selectedCamera]);
}, [origMotionSettings]);
useEffect(() => {
document.title = t("documentTitle.motionTuner");