feat: Implement auto-match automation plan with enhanced API and frontend support

This commit introduces a comprehensive auto-match automation plan that automates the face matching process in the application. Key features include the ability to automatically identify faces based on pose and similarity thresholds, with configurable options for auto-acceptance. The API has been updated to support new parameters for auto-acceptance and pose filtering, while the frontend has been enhanced to allow users to set an auto-accept threshold and view results. Documentation has been updated to reflect these changes, improving user experience and functionality.
This commit is contained in:
tanyar09
2025-11-04 14:55:05 -05:00
parent 0dcfe327cd
commit e2cadf3232
6 changed files with 913 additions and 11 deletions
+8
View File
@@ -76,6 +76,8 @@ export interface BatchUnmatchResponse {
export interface AutoMatchRequest {
tolerance: number
auto_accept?: boolean
auto_accept_threshold?: number
}
export interface AutoMatchFaceItem {
@@ -86,6 +88,7 @@ export interface AutoMatchFaceItem {
quality_score: number
similarity: number // Confidence percentage (0-100)
distance: number
pose_mode?: string
}
export interface AutoMatchPersonItem {
@@ -95,6 +98,7 @@ export interface AutoMatchPersonItem {
reference_photo_id: number
reference_photo_filename: string
reference_location: string
reference_pose_mode?: string
face_count: number
matches: AutoMatchFaceItem[]
total_matches: number
@@ -104,6 +108,10 @@ export interface AutoMatchResponse {
people: AutoMatchPersonItem[]
total_people: number
total_matches: number
auto_accepted?: boolean
auto_accepted_faces?: number
skipped_persons?: number
skipped_matches?: number
}
export interface AcceptMatchesRequest {
+99 -8
View File
@@ -7,6 +7,7 @@ const DEFAULT_TOLERANCE = 0.6
export default function AutoMatch() {
const [tolerance, setTolerance] = useState(DEFAULT_TOLERANCE)
const [autoAcceptThreshold, setAutoAcceptThreshold] = useState(70)
const [isActive, setIsActive] = useState(false)
const [people, setPeople] = useState<AutoMatchPersonItem[]>([])
const [filteredPeople, setFilteredPeople] = useState<AutoMatchPersonItem[]>([])
@@ -16,6 +17,8 @@ export default function AutoMatch() {
const [originalSelectedFaces, setOriginalSelectedFaces] = useState<Record<number, boolean>>({})
const [busy, setBusy] = useState(false)
const [saving, setSaving] = useState(false)
const [hasNoResults, setHasNoResults] = useState(false)
const [isRefreshing, setIsRefreshing] = useState(false)
const currentPerson = useMemo(() => {
const activePeople = filteredPeople.length > 0 ? filteredPeople : people
@@ -26,9 +29,48 @@ export default function AutoMatch() {
return currentPerson?.matches || []
}, [currentPerson])
// Auto-start auto-match when component mounts or tolerance changes
// Shared function for auto-load and refresh
const loadAutoMatch = async () => {
if (tolerance < 0 || tolerance > 1) {
return
}
setBusy(true)
setIsRefreshing(true)
try {
const response = await facesApi.autoMatch({
tolerance,
auto_accept: false // Don't auto-accept on load/refresh, only on button click
})
if (response.people.length === 0) {
setHasNoResults(true)
setPeople([])
setFilteredPeople([])
setIsActive(false)
setBusy(false)
setIsRefreshing(false)
return
}
setHasNoResults(false)
setPeople(response.people)
setFilteredPeople([])
setCurrentIndex(0)
setSelectedFaces({})
setOriginalSelectedFaces({})
setIsActive(true)
} catch (error) {
console.error('Auto-match failed:', error)
} finally {
setBusy(false)
setIsRefreshing(false)
}
}
// Auto-start auto-match when component mounts or tolerance changes (without auto-accept)
useEffect(() => {
startAutoMatch()
loadAutoMatch()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [tolerance])
@@ -64,16 +106,43 @@ export default function AutoMatch() {
return
}
if (autoAcceptThreshold < 0 || autoAcceptThreshold > 100) {
alert('Please enter a valid auto-accept threshold between 0 and 100.')
return
}
setBusy(true)
try {
const response = await facesApi.autoMatch({ tolerance })
const response = await facesApi.autoMatch({
tolerance,
auto_accept: true,
auto_accept_threshold: autoAcceptThreshold
})
// Show summary if auto-accept was performed
if (response.auto_accepted) {
const summary = [
`✅ Auto-matched ${response.auto_accepted_faces || 0} faces`,
response.skipped_persons ? `⚠️ Skipped ${response.skipped_persons} persons (non-frontal reference)` : '',
response.skipped_matches ? `️ Skipped ${response.skipped_matches} matches (didn't meet criteria)` : ''
].filter(Boolean).join('\n')
if (summary) {
alert(summary)
}
}
if (response.people.length === 0) {
alert('🔍 No similar faces found for auto-identification')
setHasNoResults(true)
setPeople([])
setFilteredPeople([])
setIsActive(false)
setBusy(false)
return
}
setHasNoResults(false)
setPeople(response.people)
setFilteredPeople([])
setCurrentIndex(0)
@@ -180,11 +249,11 @@ export default function AutoMatch() {
<div className="bg-white rounded-lg shadow p-4 mb-4">
<div className="flex items-center gap-4">
<button
onClick={startAutoMatch}
disabled={busy || isActive}
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
onClick={loadAutoMatch}
disabled={busy}
className="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
>
{busy ? 'Processing...' : '🚀 Start Auto-Match'}
{isRefreshing ? 'Refreshing...' : '🔄 Refresh'}
</button>
<div className="flex items-center gap-2">
<label className="text-sm font-medium text-gray-700">Tolerance:</label>
@@ -195,11 +264,33 @@ export default function AutoMatch() {
step="0.1"
value={tolerance}
onChange={(e) => setTolerance(parseFloat(e.target.value) || 0)}
disabled={isActive}
disabled={busy}
className="w-20 px-2 py-1 border border-gray-300 rounded text-sm"
/>
<span className="text-xs text-gray-500">(lower = stricter matching)</span>
</div>
<button
onClick={startAutoMatch}
disabled={busy || hasNoResults}
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed"
title={hasNoResults ? 'No matches found. Adjust tolerance or process more photos.' : ''}
>
{busy ? 'Processing...' : hasNoResults ? 'No Matches Available' : '🚀 Start Auto-Match'}
</button>
<div className="flex items-center gap-2">
<label className="text-sm font-medium text-gray-700">Auto-Accept Threshold:</label>
<input
type="number"
min="0"
max="100"
step="5"
value={autoAcceptThreshold}
onChange={(e) => setAutoAcceptThreshold(parseInt(e.target.value) || 70)}
disabled={busy || hasNoResults}
className="w-20 px-2 py-1 border border-gray-300 rounded text-sm"
/>
<span className="text-xs text-gray-500">% (min similarity)</span>
</div>
</div>
</div>