Skip to content

feat: batch image deletion with a single embeddings-index rewrite#351

Merged
lstein merged 1 commit into
masterfrom
lstein/feature/batch-delete-images
Jul 16, 2026
Merged

feat: batch image deletion with a single embeddings-index rewrite#351
lstein merged 1 commit into
masterfrom
lstein/feature/batch-delete-images

Conversation

@lstein

@lstein lstein commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Summary

Deleting N starred images from the favorites menu previously issued N serial DELETE /delete_image requests, and each one loaded and atomically rewrote the entire .npz index — O(N × index size) I/O that made multi-select deletes crawl on large albums. Deleting 20 favorites from a 50k-image album meant reading and rewriting the full index 20 times.

This adds a batch path that does the whole selection with one request and one index rewrite, so a multi-select delete now costs the same index I/O as deleting a single image.

Changes

Backend

  • New POST /delete_images/{album_key} endpoint takes {indices, move_to_trash}, resolves every index against one snapshot of the sort order, deletes the files, and drops all their rows in a single rewrite. Per-item failures (out-of-range index, missing file, access denied) are reported in an errors list without failing the batch.
  • For InvokeAI board albums, each file deletion still routes through the InvokeAI API (it owns the files and its DB rows), but the index rewrite is batched into one at the end. A per-image InvokeAI failure keeps that row in the index and deletes the rest.
  • New Embeddings.remove_images_from_embeddings(indices) maps all sorted-order indices through the (modtime, filename) lexsort against one snapshot — callers need no reverse-order shifting — and its single atomic_savez carries over the non-per-image keys (model_id etc., per fix: deleting an image no longer breaks search or drops out of search mode #350) so the encoder stamp survives. The singular remove_image_from_embeddings now delegates to it.

Frontend

  • New deleteImages() helper in index.js.
  • deleteBookmarkedImages() in bookmarks.js sends one batch request instead of a serial per-image loop, and dispatches the existing albumChanged deletion event with the indices the server actually removed.

Testing

  • New backend tests: batch delete happy path (rows dropped, files gone, encoder stamp preserved), per-item error reporting for bad indices, empty-list rejection, sorted-index→raw-index mapping with a deliberately shuffled sort order, and two InvokeAI-board tests (routing through the API, partial-failure keeps the failed row).
  • New Jest tests for deleteImages() request shape and error propagation.
  • Full suites green: 41 backend (test_index + test_invokeai_board_index), 424 frontend; ruff / eslint / prettier clean.
  • Manually verified in the running app: multi-select favorites delete completes in roughly single-delete time.

🤖 Generated with Claude Code

Deleting N starred images from the favorites menu previously issued N
serial delete_image requests, each of which loaded and atomically
rewrote the entire .npz index — O(N x index size) I/O that made
multi-select deletes crawl on large albums.

- New POST /delete_images/{album_key} endpoint deletes the files (or
  routes each through InvokeAI for board albums), reports per-item
  errors, and drops all rows in one rewrite.
- Embeddings.remove_images_from_embeddings() maps all sorted-order
  indices against one snapshot of the (modtime, filename) lexsort, so
  callers need no reverse-order shifting; the singular method now
  delegates to it. The rewrite carries over non-per-image keys
  (model_id etc.) so the encoder stamp survives.
- deleteBookmarkedImages() now sends one request and dispatches the
  albumChanged deletion event with the indices actually removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@lstein
lstein merged commit da258ad into master Jul 16, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant