chore: update eslint and prettier configuration
This commit is contained in:
@@ -1,21 +1,44 @@
|
||||
'use client';
|
||||
|
||||
import { useMemo, useState, useCallback, useRef, useReducer, useEffect } from 'react';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import {
|
||||
useMemo,
|
||||
useState,
|
||||
useCallback,
|
||||
useRef,
|
||||
useReducer,
|
||||
useEffect,
|
||||
} from 'react';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import { Film } from 'lucide-react';
|
||||
|
||||
import { DetectionData, DetectionType, DetectionCounts, DetectionLogEntry } from '@/types';
|
||||
import {
|
||||
DetectionData,
|
||||
DetectionType,
|
||||
DetectionCounts,
|
||||
DetectionLogEntry,
|
||||
} from '@/types';
|
||||
import { useGpsMap } from '@/hooks/use-gps-map';
|
||||
import { useFrameDetectionMap } from '@/hooks/use-frame-detection-map';
|
||||
import { useCumulativeCounts } from '@/hooks/use-cumulative-counts';
|
||||
import { useVideoDetectionLoop } from '@/hooks/use-video-detection-loop';
|
||||
|
||||
import VideoCanvasPlayer, { VideoCanvasPlayerRef } from './video/video-canvas-player';
|
||||
import VideoCanvasPlayer, {
|
||||
VideoCanvasPlayerRef,
|
||||
} from './video/video-canvas-player';
|
||||
import DetectionStatsBar from './video/detection-stats-bar';
|
||||
import DetectionLogs from './video/detection-logs';
|
||||
import SummarySection from './video/summary-section';
|
||||
import DetailedSummarySection from './video/detailed-summary-section';
|
||||
import { getDetectionModeConfig, getEnabledDetectionTypes } from '@/constants/detectionModeConfig';
|
||||
import {
|
||||
getDetectionModeConfig,
|
||||
getEnabledDetectionTypes,
|
||||
} from '@/constants/detectionModeConfig';
|
||||
|
||||
type VideoPlayerSectionProps = {
|
||||
data: DetectionData;
|
||||
@@ -27,13 +50,20 @@ type VideoPlayerSectionProps = {
|
||||
|
||||
const MAX_LOGS = 50;
|
||||
|
||||
type LogAction = { type: 'ADD_LOG'; payload: DetectionLogEntry } | { type: 'CLEAR' };
|
||||
type LogAction =
|
||||
| { type: 'ADD_LOG'; payload: DetectionLogEntry }
|
||||
| { type: 'CLEAR' };
|
||||
|
||||
function logsReducer(state: DetectionLogEntry[], action: LogAction): DetectionLogEntry[] {
|
||||
function logsReducer(
|
||||
state: DetectionLogEntry[],
|
||||
action: LogAction,
|
||||
): DetectionLogEntry[] {
|
||||
switch (action.type) {
|
||||
case 'ADD_LOG':
|
||||
// Move to top if already exists, or just add to top
|
||||
const filtered = state.filter((log) => log.frame !== action.payload.frame);
|
||||
const filtered = state.filter(
|
||||
(log) => log.frame !== action.payload.frame,
|
||||
);
|
||||
return [action.payload, ...filtered].slice(0, MAX_LOGS);
|
||||
case 'CLEAR':
|
||||
return [];
|
||||
@@ -81,7 +111,8 @@ export default function VideoPlayerSection({
|
||||
return counts as DetectionCounts;
|
||||
}, [enabledTypes]);
|
||||
|
||||
const [currentFrameCounts, setCurrentFrameCounts] = useState<DetectionCounts>(initialCounts);
|
||||
const [currentFrameCounts, setCurrentFrameCounts] =
|
||||
useState<DetectionCounts>(initialCounts);
|
||||
|
||||
// Logs Reducer
|
||||
const [logs, dispatchLogs] = useReducer(logsReducer, []);
|
||||
@@ -95,10 +126,10 @@ export default function VideoPlayerSection({
|
||||
// Synthesize frames from lists if not present (specifically for gemini_video)
|
||||
// Other models like YOLO return data.frames directly
|
||||
const framesMap = new Map<number, any>();
|
||||
|
||||
|
||||
// Sort all detections by their first_detected_frame
|
||||
const allDetections: any[] = [];
|
||||
enabledTypes.forEach(type => {
|
||||
enabledTypes.forEach((type) => {
|
||||
const list = (data as any)[type.listKey];
|
||||
if (list && Array.isArray(list)) {
|
||||
list.forEach((item: any) => {
|
||||
@@ -107,50 +138,57 @@ export default function VideoPlayerSection({
|
||||
}
|
||||
});
|
||||
|
||||
allDetections.sort((a, b) => (a.first_detected_frame || 0) - (b.first_detected_frame || 0));
|
||||
allDetections.sort(
|
||||
(a, b) => (a.first_detected_frame || 0) - (b.first_detected_frame || 0),
|
||||
);
|
||||
|
||||
// Current counts for sticky stats
|
||||
const currentCounts: Record<string, number> = {};
|
||||
enabledTypes.forEach(t => { currentCounts[t.frameCountKey] = 0; });
|
||||
enabledTypes.forEach((t) => {
|
||||
currentCounts[t.frameCountKey] = 0;
|
||||
});
|
||||
|
||||
allDetections.forEach(det => {
|
||||
allDetections.forEach((det) => {
|
||||
const frameId = det.first_detected_frame || det.frame_number || 0;
|
||||
|
||||
|
||||
// Spread detection across multiple frames so it stays visible (persistence)
|
||||
// Gemini detections are sparse, so showing them for ~1 second (30 frames) helps
|
||||
const persistenceFrames = 30;
|
||||
|
||||
const persistenceFrames = 30;
|
||||
|
||||
for (let i = 0; i < persistenceFrames; i++) {
|
||||
const targetFrame = frameId + i;
|
||||
|
||||
|
||||
if (!framesMap.has(targetFrame)) {
|
||||
framesMap.set(targetFrame, { frame_id: targetFrame, detections: [] });
|
||||
}
|
||||
|
||||
|
||||
const frameData = framesMap.get(targetFrame);
|
||||
|
||||
|
||||
// Update cumulative counts only on the first detected frame
|
||||
if (i === 0) {
|
||||
const typeConfig = enabledTypes.find(t => t.id === det._detType);
|
||||
const typeConfig = enabledTypes.find((t) => t.id === det._detType);
|
||||
if (typeConfig) {
|
||||
currentCounts[typeConfig.frameCountKey] = (currentCounts[typeConfig.frameCountKey] || 0) + 1;
|
||||
currentCounts[typeConfig.frameCountKey] =
|
||||
(currentCounts[typeConfig.frameCountKey] || 0) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const countCopy = { ...currentCounts };
|
||||
|
||||
|
||||
frameData.detections.push({
|
||||
...det,
|
||||
type: det.type || det._detType,
|
||||
detection_id: det.detection_id || det.id,
|
||||
count: countCopy
|
||||
count: countCopy,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
...data,
|
||||
frames: Array.from(framesMap.values()).sort((a, b) => (a.frame_id || 0) - (b.frame_id || 0))
|
||||
frames: Array.from(framesMap.values()).sort(
|
||||
(a, b) => (a.frame_id || 0) - (b.frame_id || 0),
|
||||
),
|
||||
};
|
||||
}, [data, enabledTypes]);
|
||||
|
||||
@@ -160,7 +198,9 @@ export default function VideoPlayerSection({
|
||||
normalizedData,
|
||||
detectionType,
|
||||
);
|
||||
const { getStickyCounts, sortedFrameIndices } = useCumulativeCounts(normalizedData.frames || []);
|
||||
const { getStickyCounts, sortedFrameIndices } = useCumulativeCounts(
|
||||
normalizedData.frames || [],
|
||||
);
|
||||
|
||||
// Memoized video URL
|
||||
const videoUrl = useMemo(() => {
|
||||
@@ -193,7 +233,8 @@ export default function VideoPlayerSection({
|
||||
|
||||
// Logging logic
|
||||
if (dets && dets.length > 0) {
|
||||
const firstDetId = dets[0].pothole_id ?? dets[0].signboard_id ?? dets[0].detection_id;
|
||||
const firstDetId =
|
||||
dets[0].pothole_id ?? dets[0].signboard_id ?? dets[0].detection_id;
|
||||
const coords = gpsMap.get(firstDetId);
|
||||
|
||||
if (coords) {
|
||||
@@ -211,14 +252,24 @@ export default function VideoPlayerSection({
|
||||
type: d.type || d._detType,
|
||||
bbox: d.bbox,
|
||||
confidence: d.confidence,
|
||||
latitude: gpsMap.get(d.pothole_id ?? d.signboard_id ?? d.detection_id)?.lat,
|
||||
longitude: gpsMap.get(d.pothole_id ?? d.signboard_id ?? d.detection_id)?.lng,
|
||||
latitude: gpsMap.get(
|
||||
d.pothole_id ?? d.signboard_id ?? d.detection_id,
|
||||
)?.lat,
|
||||
longitude: gpsMap.get(
|
||||
d.pothole_id ?? d.signboard_id ?? d.detection_id,
|
||||
)?.lng,
|
||||
})),
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
[data.video_info.fps, getNearestDetections, getStickyCounts, gpsMap, sortedDetectionIndices],
|
||||
[
|
||||
data.video_info.fps,
|
||||
getNearestDetections,
|
||||
getStickyCounts,
|
||||
gpsMap,
|
||||
sortedDetectionIndices,
|
||||
],
|
||||
);
|
||||
|
||||
// Playback loop hook
|
||||
@@ -248,9 +299,14 @@ export default function VideoPlayerSection({
|
||||
const currentFrame = Math.round(video.currentTime * fps);
|
||||
|
||||
// Snap to the next available frame index that has detections
|
||||
const nextDetectionFrame = sortedDetectionIndices.find((f) => f >= currentFrame);
|
||||
const nextDetectionFrame = sortedDetectionIndices.find(
|
||||
(f) => f >= currentFrame,
|
||||
);
|
||||
|
||||
if (nextDetectionFrame !== undefined && nextDetectionFrame !== currentFrame) {
|
||||
if (
|
||||
nextDetectionFrame !== undefined &&
|
||||
nextDetectionFrame !== currentFrame
|
||||
) {
|
||||
video.currentTime = nextDetectionFrame / fps;
|
||||
return;
|
||||
}
|
||||
@@ -276,7 +332,9 @@ export default function VideoPlayerSection({
|
||||
<Film className="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="text-lg font-bold">Detection Playback</CardTitle>
|
||||
<CardTitle className="text-lg font-bold">
|
||||
Detection Playback
|
||||
</CardTitle>
|
||||
<CardDescription className="text-xs">
|
||||
Real-time object detection analysis
|
||||
</CardDescription>
|
||||
@@ -297,7 +355,10 @@ export default function VideoPlayerSection({
|
||||
onLoadedData={handleLoadedData}
|
||||
onEnded={handleVideoEnded}
|
||||
onSeeked={handleVideoSeeked}
|
||||
currentDetections={getNearestDetections(currentFrame, sortedDetectionIndices) || []}
|
||||
currentDetections={
|
||||
getNearestDetections(currentFrame, sortedDetectionIndices) ||
|
||||
[]
|
||||
}
|
||||
/>
|
||||
|
||||
<DetectionStatsBar
|
||||
@@ -316,7 +377,11 @@ export default function VideoPlayerSection({
|
||||
|
||||
{showSummary && (
|
||||
<div className="space-y-6 pt-4 animate-in fade-in slide-in-from-bottom-4 duration-500">
|
||||
<SummarySection data={data} show={showSummary} detectionType={detectionType} />
|
||||
<SummarySection
|
||||
data={data}
|
||||
show={showSummary}
|
||||
detectionType={detectionType}
|
||||
/>
|
||||
<DetailedSummarySection
|
||||
projectId={projectId || ''}
|
||||
videoId={videoId}
|
||||
|
||||
Reference in New Issue
Block a user