feat: change interface of analysis video log

This commit is contained in:
2026-07-02 13:57:18 +05:30
parent 2eb58856af
commit 53fe4e11fe
7 changed files with 126 additions and 53 deletions

View File

@@ -1,10 +1,13 @@
'use client';
import { useEffect, useRef } from 'react';
import { Activity, Film, MapPin } from 'lucide-react';
import { Activity, Film, ImageIcon, MapPin } from 'lucide-react';
import { ScrollArea } from '@/components/ui/scroll-area';
import { DetectionResultLog } from '@/types';
import { Skeleton } from '@/components/ui/skeleton';
import { useProtectedMediaObjectUrl } from '@/hooks/useProtectedMedia';
import { cn } from '@/lib/utils';
import type { DetectionResultLog } from '@/types';
interface DetectionLogsProps {
logs: DetectionResultLog[];
@@ -19,6 +22,47 @@ const formatVideoTime = (seconds: number) => {
return `${minutes}:${secs.toString().padStart(2, '0')}`;
};
function DetectionLogThumbnail({
url,
label,
}: {
url?: string | null;
label: string;
}) {
const { objectUrl, isLoading, isError } = useProtectedMediaObjectUrl(url);
if (!url) {
return (
<div className="flex size-20 shrink-0 items-center justify-center rounded-md border border-dashed border-border/70 bg-muted/20 text-muted-foreground/60">
<ImageIcon className="size-5" />
</div>
);
}
if (isLoading) {
return <Skeleton className="size-20 shrink-0 rounded-md" />;
}
if (isError || !objectUrl) {
return (
<div className="flex size-20 shrink-0 items-center justify-center rounded-md border border-dashed bg-muted/20 text-[10px] text-muted-foreground">
N/A
</div>
);
}
return (
<div className="size-20 shrink-0 overflow-hidden rounded-md border bg-muted/20">
<img
src={objectUrl}
alt={label}
className="size-full object-cover"
loading="lazy"
/>
</div>
);
}
const DetectionLogs = ({ logs, activeLogId, onSeek }: DetectionLogsProps) => {
const activeLogRef = useRef<HTMLButtonElement | null>(null);
@@ -49,6 +93,7 @@ const DetectionLogs = ({ logs, activeLogId, onSeek }: DetectionLogsProps) => {
const timestampSeconds = log.frame.timestamp_seconds;
const { latitude, longitude } = log.location;
const isActive = activeLogId === log.id;
const label = log.detection.display_name;
return (
<button
@@ -57,42 +102,50 @@ const DetectionLogs = ({ logs, activeLogId, onSeek }: DetectionLogsProps) => {
type="button"
onClick={() => onSeek(log)}
className={cn(
'w-full rounded-md border bg-card p-4 text-left transition-colors hover:border-primary',
'w-full rounded-md border bg-card p-3 text-left transition-colors hover:border-primary',
isActive && 'border-primary bg-primary/5',
)}
>
<div className="mb-3 flex items-start justify-between gap-4">
<div>
<div className="text-[13px] font-bold">
{log.detection.display_name} ID: {log.id}
</div>
<div className="text-[11px] font-medium text-muted-foreground">
Frame {log.frame.number}
</div>
</div>
<div className="shrink-0 text-right">
<div className="text-xs font-bold">
{formatVideoTime(timestampSeconds)}
</div>
<div className="text-[11px] text-muted-foreground">
{timestampSeconds.toFixed(2)}s
</div>
</div>
</div>
<div className="flex gap-3">
<DetectionLogThumbnail
url={log.detection.context_image_url}
label={label}
/>
<div className="space-y-2 text-[12px] font-medium text-muted-foreground/90">
<div>
Confidence: {(log.detection.confidence * 100).toFixed(1)}%
</div>
{typeof latitude === 'number' &&
typeof longitude === 'number' && (
<div className="flex items-center gap-1.5">
<MapPin className="h-3 w-3" />
<span>
{latitude.toFixed(8)}, {longitude.toFixed(8)}
</span>
<div className="min-w-0 flex-1">
<div className="mb-2 flex items-start justify-between gap-3">
<div className="min-w-0 space-y-1">
<p className="text-sm font-semibold text-foreground">
{label}
</p>
<p className="text-[11px] text-muted-foreground">
Frame {log.frame.number}
</p>
</div>
)}
<div className="shrink-0 text-right">
<div className="text-xs font-bold">
{formatVideoTime(timestampSeconds)}
</div>
<div className="text-[11px] text-muted-foreground">
{timestampSeconds.toFixed(2)}s
</div>
</div>
</div>
<div className="space-y-1.5 text-[12px] text-muted-foreground">
<p>{(log.detection.confidence * 100).toFixed(1)}%</p>
{typeof latitude === 'number' &&
typeof longitude === 'number' && (
<div className="flex items-center gap-1.5">
<MapPin className="h-3 w-3 shrink-0" />
<span className="truncate font-mono text-[11px]">
{latitude.toFixed(8)}, {longitude.toFixed(8)}
</span>
</div>
)}
</div>
</div>
</div>
</button>
);