diff --git a/web/src/components/card/AnimatedEventCard.tsx b/web/src/components/card/AnimatedEventCard.tsx
index c5da99aa2..b6e9ddf7c 100644
--- a/web/src/components/card/AnimatedEventCard.tsx
+++ b/web/src/components/card/AnimatedEventCard.tsx
@@ -19,6 +19,8 @@ import { Button } from "../ui/button";
import { FaCircleCheck } from "react-icons/fa6";
import { cn } from "@/lib/utils";
import { useTranslation } from "react-i18next";
+import { getTranslatedLabel } from "@/utils/i18n";
+import { formatList } from "@/utils/stringUtil";
type AnimatedEventCardProps = {
event: ReviewSegment;
@@ -50,26 +52,37 @@ export function AnimatedEventCard({
fetchPreviews: !currentHour,
});
+ const getEventType = useCallback(
+ (text: string) => {
+ if (event.data.sub_labels?.includes(text)) return "manual";
+ if (event.data.audio.includes(text)) return "audio";
+ return "object";
+ },
+ [event],
+ );
+
const tooltipText = useMemo(() => {
if (event?.data?.metadata?.title) {
return event.data.metadata.title;
}
return (
- `${[
- ...new Set([
- ...(event.data.objects || []),
- ...(event.data.sub_labels || []),
- ...(event.data.audio || []),
- ]),
- ]
- .filter((item) => item !== undefined && !item.includes("-verified"))
- .map((text) => text.charAt(0).toUpperCase() + text.substring(1))
- .sort()
- .join(", ")
- .replaceAll("-verified", "")} ` + t("detected")
+ `${formatList(
+ [
+ ...new Set([
+ ...(event.data.objects || []).map((text) =>
+ text.replace("-verified", ""),
+ ),
+ ...(event.data.sub_labels || []),
+ ...(event.data.audio || []),
+ ]),
+ ]
+ .filter((item) => item !== undefined)
+ .map((text) => getTranslatedLabel(text, getEventType(text)))
+ .sort(),
+ )} ` + t("detected")
);
- }, [event, t]);
+ }, [event, getEventType, t]);
// visibility
diff --git a/web/src/components/card/ReviewCard.tsx b/web/src/components/card/ReviewCard.tsx
index b5ba5cfea..89d79dee7 100644
--- a/web/src/components/card/ReviewCard.tsx
+++ b/web/src/components/card/ReviewCard.tsx
@@ -33,13 +33,14 @@ import axios from "axios";
import { toast } from "sonner";
import useKeyboardListener from "@/hooks/use-keyboard-listener";
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
-import { capitalizeFirstLetter } from "@/utils/stringUtil";
import { Button, buttonVariants } from "../ui/button";
import { Trans, useTranslation } from "react-i18next";
import { cn } from "@/lib/utils";
import { LuCircle } from "react-icons/lu";
import { MdAutoAwesome } from "react-icons/md";
import { GenAISummaryDialog } from "../overlay/chip/GenAISummaryChip";
+import { getTranslatedLabel } from "@/utils/i18n";
+import { formatList } from "@/utils/stringUtil";
type ReviewCardProps = {
event: ReviewSegment;
@@ -123,6 +124,12 @@ export default function ReviewCard({
}
}, [bypassDialogRef, onDelete]);
+ const getEventType = (text: string) => {
+ if (event.data.sub_labels?.includes(text)) return "manual";
+ if (event.data.audio.includes(text)) return "audio";
+ return "object";
+ };
+
const content = (
- {[
- ...new Set([
- ...(event.data.objects || []),
- ...(event.data.sub_labels || []),
- ...(event.data.audio || []),
- ]),
- ]
- .filter(
- (item) => item !== undefined && !item.includes("-verified"),
- )
- .map((text) => capitalizeFirstLetter(text))
- .sort()
- .join(", ")
- .replaceAll("-verified", "")}
+ {formatList(
+ [
+ ...new Set([
+ ...(event.data.objects || []).map((text) =>
+ text.replace("-verified", ""),
+ ),
+ ...(event.data.sub_labels || []),
+ ...(event.data.audio || []),
+ ]),
+ ]
+ .filter((item) => item !== undefined)
+ .map((text) => getTranslatedLabel(text, getEventType(text)))
+ .sort(),
+ )}
-
+
{formatList(
[
- ...new Set([
- ...(objects || []).map(({ label, sub_label }) =>
- label.endsWith("verified")
- ? sub_label
- : label.replaceAll("_", " "),
- ),
- ]),
- ]
- .filter((label) => label?.includes("-verified") == false)
- .map((label) =>
- getTranslatedLabel(label.replace("-verified", "")),
- )
- .sort(),
+ ...new Set(
+ (objects || [])
+ .map(({ label, sub_label }) => {
+ const isManual = label.endsWith("verified");
+ const text = isManual ? sub_label : label;
+ const type = isManual ? "manual" : "object";
+ return getTranslatedLabel(text, type);
+ })
+ .filter(
+ (translated) =>
+ translated && !translated.includes("-verified"),
+ ),
+ ),
+ ].sort(),
)}
diff --git a/web/src/components/player/PreviewThumbnailPlayer.tsx b/web/src/components/player/PreviewThumbnailPlayer.tsx
index 42a00b86c..d612a1566 100644
--- a/web/src/components/player/PreviewThumbnailPlayer.tsx
+++ b/web/src/components/player/PreviewThumbnailPlayer.tsx
@@ -28,6 +28,7 @@ import { useTranslation } from "react-i18next";
import { FaExclamationTriangle } from "react-icons/fa";
import { MdOutlinePersonSearch } from "react-icons/md";
import { getTranslatedLabel } from "@/utils/i18n";
+import { formatList } from "@/utils/stringUtil";
type PreviewPlayerProps = {
review: ReviewSegment;
@@ -182,9 +183,8 @@ export default function PreviewThumbnailPlayer({
);
const getEventType = (text: string) => {
- if (review.data.objects.includes(text)) return "object";
- if (review.data.audio.includes(text)) return "audio";
if (review.data.sub_labels?.includes(text)) return "manual";
+ if (review.data.audio.includes(text)) return "audio";
return "object";
};
@@ -268,13 +268,16 @@ export default function PreviewThumbnailPlayer({
className={`flex items-start justify-between space-x-1 ${playingBack ? "hidden" : ""} bg-gradient-to-br ${review.has_been_reviewed ? "bg-green-600 from-green-600 to-green-700" : "bg-gray-500 from-gray-400 to-gray-500"} z-0`}
onClick={() => onClick(review, false, true)}
>
- {review.data.objects.sort().map((object) => {
- return getIconForLabel(
- object,
- "object",
- "size-3 text-white",
- );
- })}
+ {review.data.objects
+ .sort()
+ .map((object, idx) =>
+ getIconForLabel(
+ object,
+ "object",
+ "size-3 text-white",
+ `${object}-${idx}`,
+ ),
+ )}
{review.data.audio.map((audio) => {
return getIconForLabel(
audio,
@@ -288,23 +291,25 @@ export default function PreviewThumbnailPlayer({
-
+
{review.data.metadata
? review.data.metadata.title
- : [
- ...new Set([
- ...(review.data.objects || []),
- ...(review.data.sub_labels || []),
- ...(review.data.audio || []),
- ]),
- ]
- .filter(
- (item) =>
- item !== undefined && !item.includes("-verified"),
- )
- .map((text) => getTranslatedLabel(text, getEventType(text)))
- .sort()
- .join(", ")}
+ : formatList(
+ [
+ ...new Set([
+ ...(review.data.objects || []).map((text) =>
+ text.replace("-verified", ""),
+ ),
+ ...(review.data.sub_labels || []),
+ ...(review.data.audio || []),
+ ]),
+ ]
+ .filter((item) => item !== undefined)
+ .map((text) =>
+ getTranslatedLabel(text, getEventType(text)),
+ )
+ .sort(),
+ )}
{!!(
diff --git a/web/src/utils/iconUtil.tsx b/web/src/utils/iconUtil.tsx
index 8ddf3ea08..04324aabe 100644
--- a/web/src/utils/iconUtil.tsx
+++ b/web/src/utils/iconUtil.tsx
@@ -62,83 +62,86 @@ export function getIconForLabel(
label: string,
type: EventType = "object",
className?: string,
+ key?: string,
) {
+ const iconKey = key || label;
+
if (label.endsWith("-verified")) {
- return getVerifiedIcon(label, className, type);
+ return getVerifiedIcon(label, className, type, iconKey);
} else if (label.endsWith("-plate")) {
- return getRecognizedPlateIcon(label, className, type);
+ return getRecognizedPlateIcon(label, className, type, iconKey);
}
switch (label) {
// objects
case "bear":
- return ;
+ return ;
case "bicycle":
- return ;
+ return ;
case "bird":
- return ;
+ return ;
case "boat":
- return ;
+ return ;
case "bus":
case "school_bus":
- return ;
+ return ;
case "car":
case "vehicle":
- return ;
+ return ;
case "cat":
- return ;
+ return ;
case "deer":
- return ;
+ return ;
case "animal":
case "bark":
case "dog":
- return ;
+ return ;
case "fox":
- return ;
+ return ;
case "goat":
- return ;
+ return ;
case "horse":
- return ;
+ return ;
case "kangaroo":
- return ;
+ return ;
case "license_plate":
- return ;
+ return ;
case "motorcycle":
- return ;
+ return ;
case "mouse":
- return ;
+ return ;
case "package":
- return ;
+ return ;
case "person":
- return ;
+ return ;
case "rabbit":
- return ;
+ return ;
case "raccoon":
- return ;
+ return ;
case "robot_lawnmower":
- return ;
+ return ;
case "sports_ball":
- return ;
+ return ;
case "skunk":
- return ;
+ return ;
case "squirrel":
- return ;
+ return ;
case "umbrella":
- return ;
+ return ;
case "waste_bin":
- return ;
+ return ;
// audio
case "crying":
case "laughter":
case "scream":
case "speech":
case "yell":
- return ;
+ return ;
case "fire_alarm":
- return ;
+ return ;
// sub labels
case "amazon":
- return ;
+ return ;
case "an_post":
case "canada_post":
case "dpd":
@@ -148,20 +151,20 @@ export function getIconForLabel(
case "postnord":
case "purolator":
case "royal_mail":
- return ;
+ return ;
case "dhl":
- return ;
+ return ;
case "fedex":
- return ;
+ return ;
case "ups":
- return ;
+ return ;
case "usps":
- return ;
+ return ;
default:
if (type === "audio") {
- return ;
+ return ;
}
- return ;
+ return ;
}
}
@@ -169,11 +172,12 @@ function getVerifiedIcon(
label: string,
className?: string,
type: EventType = "object",
+ key?: string,
) {
const simpleLabel = label.substring(0, label.lastIndexOf("-"));
return (
-
+
{getIconForLabel(simpleLabel, type, className)}
@@ -184,11 +188,12 @@ function getRecognizedPlateIcon(
label: string,
className?: string,
type: EventType = "object",
+ key?: string,
) {
const simpleLabel = label.substring(0, label.lastIndexOf("-"));
return (
-
+
{getIconForLabel(simpleLabel, type, className)}