diff --git a/web/public/locales/en/views/events.json b/web/public/locales/en/views/events.json index 98bc7c422..77c626adf 100644 --- a/web/public/locales/en/views/events.json +++ b/web/public/locales/en/views/events.json @@ -34,5 +34,7 @@ "selected_one": "{{count}} selected", "selected_other": "{{count}} selected", "camera": "Camera", - "detected": "detected" + "detected": "detected", + "suspiciousActivity": "Suspicious Activity", + "threateningActivity": "Threatening Activity" } diff --git a/web/public/locales/en/views/explore.json b/web/public/locales/en/views/explore.json index d754fee77..3e5e753d9 100644 --- a/web/public/locales/en/views/explore.json +++ b/web/public/locales/en/views/explore.json @@ -132,6 +132,9 @@ "label": "Top Score", "info": "The top score is the highest median score for the tracked object, so this may differ from the score shown on the search result thumbnail." }, + "score": { + "label": "Score" + }, "recognizedLicensePlate": "Recognized License Plate", "estimatedSpeed": "Estimated Speed", "objects": "Objects", @@ -213,5 +216,11 @@ "error": "Failed to delete tracked object: {{errorMessage}}" } } + }, + "aiAnalysis": { + "title": "AI Analysis" + }, + "concerns": { + "label": "Concerns" } } diff --git a/web/src/components/menu/GeneralSettings.tsx b/web/src/components/menu/GeneralSettings.tsx index c4ccdff84..f747f75ab 100644 --- a/web/src/components/menu/GeneralSettings.tsx +++ b/web/src/components/menu/GeneralSettings.tsx @@ -65,6 +65,7 @@ import { useTranslation } from "react-i18next"; import { supportedLanguageKeys } from "@/lib/const"; import { useDocDomain } from "@/hooks/use-doc-domain"; +import { MdCategory } from "react-icons/md"; type GeneralSettingsProps = { className?: string; @@ -315,6 +316,19 @@ export default function GeneralSettings({ className }: GeneralSettingsProps) { )} + {isAdmin && isMobile && ( + <> + + + + {t("menu.classification")} + + + + )} {t("menu.appearance")} diff --git a/web/src/components/overlay/detail/ReviewDetailDialog.tsx b/web/src/components/overlay/detail/ReviewDetailDialog.tsx index 1f647fb04..2630fa2f9 100644 --- a/web/src/components/overlay/detail/ReviewDetailDialog.tsx +++ b/web/src/components/overlay/detail/ReviewDetailDialog.tsx @@ -86,10 +86,10 @@ export default function ReviewDetailDialog({ let concerns = ""; switch (aiAnalysis.potential_threat_level) { case ThreatLevel.SUSPICIOUS: - concerns = "• Suspicious Activity\n"; + concerns = `• ${t("suspiciousActivity", { ns: "views/events" })}\n`; break; case ThreatLevel.DANGER: - concerns = "• Danger\n"; + concerns = `• ${t("threateningActivity", { ns: "views/events" })}\n`; break; } @@ -98,7 +98,7 @@ export default function ReviewDetailDialog({ }); return concerns || "None"; - }, [aiAnalysis]); + }, [aiAnalysis, t]); const hasMismatch = useMemo(() => { if (!review || !events) { @@ -270,12 +270,18 @@ export default function ReviewDetailDialog({ isDesktop && "m-2 w-[90%]", )} > - AI Analysis -
Description
+ {t("aiAnalysis.title")} +
+ {t("details.description.label")} +
{aiAnalysis.scene}
-
Score
+
+ {t("details.score.label")} +
{aiAnalysis.confidence * 100}%
-
Concerns
+
+ {t("concerns.label")} +
{aiThreatLevel}
)} diff --git a/web/src/components/overlay/dialog/TrainFilterDialog.tsx b/web/src/components/overlay/dialog/TrainFilterDialog.tsx index 56037ec0a..f4ccf41e1 100644 --- a/web/src/components/overlay/dialog/TrainFilterDialog.tsx +++ b/web/src/components/overlay/dialog/TrainFilterDialog.tsx @@ -60,7 +60,7 @@ export default function TrainFilterDialog({ moreFiltersSelected ? "text-white" : "text-secondary-foreground", )} /> - {t("more")} + {isDesktop && t("more")} ); const content = ( diff --git a/web/src/components/player/PreviewThumbnailPlayer.tsx b/web/src/components/player/PreviewThumbnailPlayer.tsx index 0e66b3c26..9d84a6697 100644 --- a/web/src/components/player/PreviewThumbnailPlayer.tsx +++ b/web/src/components/player/PreviewThumbnailPlayer.tsx @@ -22,6 +22,8 @@ import { InProgressPreview, VideoPreview } from "../preview/ScrubbablePreview"; import { Preview } from "@/types/preview"; import { baseUrl } from "@/api/baseUrl"; import { useTranslation } from "react-i18next"; +import { FaExclamationTriangle } from "react-icons/fa"; +import { MdOutlinePersonSearch } from "react-icons/md"; type PreviewPlayerProps = { review: ReviewSegment; @@ -234,7 +236,12 @@ export default function PreviewThumbnailPlayer({ )} /> )} -
+
setTooltipHovering(false)} > -
+
{(review.severity == "alert" || review.severity == "detection") && ( <> @@ -279,6 +286,45 @@ export default function PreviewThumbnailPlayer({ .replaceAll("-verified", "")} + {!!( + review.data.metadata?.potential_threat_level && + !review.has_been_reviewed + ) && ( + +
setTooltipHovering(true)} + onMouseLeave={() => setTooltipHovering(false)} + > + +
+ {(review.severity == "alert" || + review.severity == "detection") && ( + <> + onClick(review, false, true)} + > + {review.data.metadata.potential_threat_level == 1 ? ( + + ) : ( + + )} + + + )} +
+
+
+ + {review.data.metadata.potential_threat_level == 1 ? ( + <>{t("suspiciousActivity", { ns: "views/events" })} + ) : ( + <>{t("threateningActivity", { ns: "views/events" })} + )} + +
+ )}
{!playingBack && (
onClick()} > -
+
{Object.entries(coverImages).map(([key, image]) => ( )} - {t("button.trainModel")} + {isDesktop && t("button.trainModel")}
)} @@ -713,7 +713,7 @@ function TrainGrid({ if ( trainFilter.max_score && - trainFilter.max_score <= data.score / 100.0 + trainFilter.max_score < data.score / 100.0 ) { return false; } @@ -725,7 +725,12 @@ function TrainGrid({ ); return ( -
+
{trainData?.map((data) => (
{ e.stopPropagation();