feat: auto-chain vision backfill + face recluster after scan, show progress in UI
Scan now automatically queues backfill_vision (+90s) and recluster_faces (+300s) after dispatching folder scans. Face extraction also schedules a debounced recluster via Redis so incremental file-watcher imports get clustered without manual intervention. The ScanProgress widget now tracks worker queue activity beyond the scan phase, showing a "Processing Photos" indicator with vision queue counts while background tasks (embeddings, faces, tags, OCR) are running. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -348,13 +348,40 @@ def _save_faces(photo_id: str, faces) -> dict:
|
||||
|
||||
if faces:
|
||||
logger.info("Extracted %d verified face(s) from photo %s", len(faces), photo_id)
|
||||
_schedule_recluster_debounced()
|
||||
return {'status': 'success', 'photo_id': photo_id, 'faces': len(faces)}
|
||||
|
||||
|
||||
RECLUSTER_DEBOUNCE_KEY = "mule:recluster_faces:pending"
|
||||
RECLUSTER_DELAY = 120 # seconds after last face extraction
|
||||
|
||||
|
||||
def _schedule_recluster_debounced():
|
||||
"""Schedule a recluster_faces run, debounced so rapid-fire face
|
||||
extractions don't spawn hundreds of redundant cluster jobs."""
|
||||
try:
|
||||
import redis as _redis
|
||||
r = _redis.from_url(settings.redis_url)
|
||||
already_pending = r.set(RECLUSTER_DEBOUNCE_KEY, "1",
|
||||
ex=RECLUSTER_DELAY, nx=True)
|
||||
if already_pending:
|
||||
recluster_faces.apply_async(countdown=RECLUSTER_DELAY)
|
||||
logger.info("Scheduled debounced recluster_faces in %ds", RECLUSTER_DELAY)
|
||||
except Exception as e:
|
||||
logger.debug("recluster debounce check failed: %s", e)
|
||||
|
||||
|
||||
@shared_task(name='recluster_faces', queue='vision')
|
||||
def recluster_faces():
|
||||
"""Run DBSCAN clustering over all face embeddings and assign/create
|
||||
Tag(kind=face_cluster) entries."""
|
||||
# Clear debounce key so new face extractions can schedule another round.
|
||||
try:
|
||||
import redis as _redis
|
||||
_redis.from_url(settings.redis_url).delete(RECLUSTER_DEBOUNCE_KEY)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not settings.vision.enabled or not settings.vision.faces.enabled:
|
||||
return {'status': 'skipped', 'reason': 'faces disabled'}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user