From d2852fbf1e3364f5f91a5bc9f073a171a9bbe56b Mon Sep 17 00:00:00 2001 From: tanyar09 Date: Fri, 5 Dec 2025 11:57:02 -0500 Subject: [PATCH] feat: Implement excluded and identified filters in FacesMaintenance component This commit adds functionality to filter faces based on their excluded and identified statuses in the FacesMaintenance component. New state variables and API parameters are introduced to manage these filters, enhancing the user experience. The UI is updated with dropdowns for selecting filter options, and the backend is modified to support these filters in the face listing API. Documentation has been updated to reflect these changes. --- frontend/src/api/faces.ts | 3 + frontend/src/pages/FacesMaintenance.tsx | 105 +++++++++++++++++++++-- frontend/src/pages/Search.tsx | 107 +++++++++++++++++++----- src/web/api/faces.py | 17 ++++ src/web/schemas/faces.py | 1 + 5 files changed, 208 insertions(+), 25 deletions(-) diff --git a/frontend/src/api/faces.ts b/frontend/src/api/faces.ts index b1b3d58..63498ac 100644 --- a/frontend/src/api/faces.ts +++ b/frontend/src/api/faces.ts @@ -168,6 +168,7 @@ export interface MaintenanceFaceItem { quality_score: number person_id: number | null person_name: string | null + excluded: boolean } export interface MaintenanceFacesResponse { @@ -272,6 +273,8 @@ export const facesApi = { page_size?: number min_quality?: number max_quality?: number + excluded_filter?: 'all' | 'excluded' | 'included' + identified_filter?: 'all' | 'identified' | 'unidentified' }): Promise => { const response = await apiClient.get('/api/v1/faces/maintenance', { params, diff --git a/frontend/src/pages/FacesMaintenance.tsx b/frontend/src/pages/FacesMaintenance.tsx index a076ece..ec60867 100644 --- a/frontend/src/pages/FacesMaintenance.tsx +++ b/frontend/src/pages/FacesMaintenance.tsx @@ -2,18 +2,23 @@ import { useEffect, useState, useMemo } from 'react' import facesApi, { MaintenanceFaceItem } from '../api/faces' import { apiClient } from '../api/client' -type SortColumn = 'person_name' | 'quality' +type SortColumn = 'person_name' | 'quality' | 'photo_path' | 'excluded' type SortDir = 'asc' | 'desc' +type ExcludedFilter = 'all' | 'excluded' | 'included' +type IdentifiedFilter = 'all' | 'identified' | 'unidentified' export default function FacesMaintenance() { const [faces, setFaces] = useState([]) const [total, setTotal] = useState(0) const [pageSize, setPageSize] = useState(50) const [minQuality, setMinQuality] = useState(0.0) - const [maxQuality, setMaxQuality] = useState(0.45) + const [maxQuality, setMaxQuality] = useState(1.0) + const [excludedFilter, setExcludedFilter] = useState('all') + const [identifiedFilter, setIdentifiedFilter] = useState('all') const [selectedFaces, setSelectedFaces] = useState>(new Set()) const [loading, setLoading] = useState(false) const [deleting, setDeleting] = useState(false) + const [excluding, setExcluding] = useState(false) const [showDeleteConfirm, setShowDeleteConfirm] = useState(false) const [sortColumn, setSortColumn] = useState(null) const [sortDir, setSortDir] = useState('asc') @@ -26,6 +31,8 @@ export default function FacesMaintenance() { page_size: pageSize, min_quality: minQuality, max_quality: maxQuality, + excluded_filter: excludedFilter, + identified_filter: identifiedFilter, }) setFaces(res.items) setTotal(res.total) @@ -41,7 +48,7 @@ export default function FacesMaintenance() { useEffect(() => { loadFaces() // eslint-disable-next-line react-hooks/exhaustive-deps - }, [pageSize, minQuality, maxQuality]) + }, [pageSize, minQuality, maxQuality, excludedFilter, identifiedFilter]) const toggleSelection = (faceId: number) => { setSelectedFaces(prev => { @@ -88,6 +95,14 @@ export default function FacesMaintenance() { aVal = a.quality_score bVal = b.quality_score break + case 'photo_path': + aVal = a.photo_path + bVal = b.photo_path + break + case 'excluded': + aVal = a.excluded ? 1 : 0 + bVal = b.excluded ? 1 : 0 + break } if (typeof aVal === 'string') { @@ -127,12 +142,33 @@ export default function FacesMaintenance() { } } + const handleExclude = async () => { + if (selectedFaces.size === 0) { + alert('Please select at least one face to exclude.') + return + } + setExcluding(true) + try { + const faceIds = Array.from(selectedFaces) + // Exclude each selected face + await Promise.all(faceIds.map(faceId => facesApi.setExcluded(faceId, true))) + // Reload faces after exclusion + await loadFaces() + alert(`Successfully excluded ${selectedFaces.size} face(s)`) + } catch (error) { + console.error('Error excluding faces:', error) + alert('Error excluding faces. Please try again.') + } finally { + setExcluding(false) + } + } + return (
{/* Controls */}
-
+
{/* Quality Range Selector */}
+ {/* Excluded Faces Filter */} +
+ + +
+ + {/* Identified Filter */} +
+ + +
+ {/* Batch Size */}