Commit Graph

34 Commits

Author SHA1 Message Date
LyAhn a34d38d9d3 feat: notification batching, per-folder mute, and global pause
Debounce embedding/tagging completion notifications per folder (6s window)
so rapid file additions from downloaders fire one notification instead of
one per file. Add per-folder mute via the sidebar context menu and a global
pause toggle in Settings > General, both persisted across restarts.
2026-06-11 06:28:16 +01:00
LyAhn b89e7406e9 fix(settings): improve thumbnail cleanup UX with live state and time warning
Clear stale stats during cleanup, show zero when done, and warn when the
file count is large enough to take a few minutes.
2026-06-09 06:45:24 +01:00
LyAhn ceb51f8fad feat: smart thumbnail cleanup and rename-aware file tracking
Delete thumbnails alongside DB rows in all three removal paths (watcher
detect, delete-from-disk command, remove-folder command) so orphans no
longer accumulate passively.

Intercept RenameMode::Both watcher events and handle them as in-place
path updates: renames the thumbnail file to the new hash and updates the
DB row without touching the embedding, avoiding a full rebuild on move.
Falls back to thumbnail regeneration if the file rename fails.
2026-06-09 01:16:12 +01:00
LyAhn 8eaa0bd8e8 feat: settings modal overhaul with General section and maintenance tools
- Remove Workers section (status-only, not settings)
- Remove Captioning coming-soon placeholder
- Add inline validation errors for threshold and batch size inputs
- Persist tagging queue scope and folder selection across app restarts
- Fix acceleration save error routing to correct UI location
- Add General section: open app data folder button
- Add Compact database card: live size metrics and VACUUM on demand
- Add Thumbnail cache card: orphaned thumbnail count, size, and cleanup
2026-06-09 01:09:40 +01:00
LyAhn 3707a35cc4 feat(settings): add orphaned thumbnail cleanup to General section
Scans the thumbnails directory and cross-references against thumbnail_path
values in the images table, deleting any files not linked to an indexed image.
Surfaces count and reclaimable MB in the General section alongside the
existing Compact database card.
2026-06-09 01:09:29 +01:00
LyAhn d1eb75a4f5 feat(settings): add compact database card to General section
Adds get_database_info and vacuum_database Tauri commands. The General
section now shows current DB size and reclaimable space on load, with a
Compact now button that runs PRAGMA wal_checkpoint(FULL) + VACUUM and
reports how many MB were freed. Button disables automatically when the
database is already compact (< 0.5 MB reclaimable).
2026-06-09 00:40:05 +01:00
LyAhn fbdd43d9d9 fix(settings): correct invoke payload keys and error handler routing
- folder_ids: use snake_case in both set_tagging_queue_folder_ids invoke
  calls (toggleTaggingQueueFolder and setTaggingQueueFolderIds) to match
  the Rust SetTaggingQueueFolderIdsParams serde field name
- threshold save error: route setTaggerThreshold() catch to
  setTaggerThresholdError instead of setTaggerQueueStatus so the error
  surfaces next to the threshold input, not the queue targets card
2026-06-09 00:22:30 +01:00
LyAhn 33fb3c6c77 feat(settings): overhaul Settings modal with improvements and General section
- Remove Workers section (read-only status, belongs in background tasks panel)
- Remove Captioning coming-soon placeholder
- Add General section with Open data folder button (tauri-plugin-opener)
- Persist queue scope and folder selection across sessions via settings files
- Show inline validation errors on threshold/batch size inputs instead of silent revert
- Fix acceleration save errors appearing in wrong card
- Disable folder selection controls when target scope is All media
2026-06-09 00:14:49 +01:00
LyAhn a40e5c2771 feat: watchdog, EXIF capture date, and timeline view
Merges feat/watchdog-exif-timeline into main.

- Adaptive filesystem watchdog: switches between inotify/ReadDirectoryChanges
  and polling with zero CPU when idle; watcher events update sidebar counts live
- EXIF taken_at: extracted at index time and stored as a sortable column;
  new taken_asc/taken_desc sort orders; migration is safe on existing DBs
- Timeline view: virtualised month-grouped grid (react-virtual v3), reuses
  ImageTile/ContextMenu, Calendar icon in Sidebar, defaults to taken_asc sort
- Bug fixes: startup migration panic on existing DBs without taken_at column,
  "Unknown Date" grouping due to null created_at fallback, watcher not updating
  folder image counts, cols overflow in Timeline grid
2026-06-08 22:54:33 +01:00
LyAhn a2804d8c1b fix(timeline): CodeRabbit review corrections
- Fix cols overflow: use (containerWidth - GAP) formula so the grid
  never exceeds the container width
- Explicitly pin "unknown" date keys to the end of the sort rather than
  relying on alphabetical accident ("u" > digits)
- Guard buildLabel against non-YYYY-MM keys with finite/range checks
  and an isNaN fallback to "Unknown Date"
- Clear similarSourceFolderId, similarFolderId, and similarCrop when
  switching to the timeline view so all similar-state is consistently
  reset
2026-06-08 22:26:28 +01:00
LyAhn 0ab156d2d9 fix: startup crash, watcher count, timeline date fallback
db.rs:
- Move idx_images_taken_at creation to after ensure_column so it never
  runs against a schema where taken_at doesn't exist yet; this was causing
  a startup panic on any DB that predates Phase 1
- COALESCE(taken_at, created_at) → COALESCE(taken_at, modified_at) since
  created_at is never populated (modified_at is always set)
- Remove dead is_tagging_job_cancelled function (superseded by
  is_tagging_job_processing) and unused thumbnail_path from ImagePathRecord

indexer.rs:
- Watcher create path now calls update_folder_count after commit_batch and
  emits folder-counts-changed so the sidebar count stays in sync; the
  notification is only emitted when the DB write actually succeeds

store.ts / Timeline.tsx:
- compareImages taken_asc/taken_desc: fall back to modified_at not
  created_at (created_at is always null)
- Timeline groupImages: same fallback fix so images group by month
  correctly for libraries not yet re-indexed for EXIF
- subscribeToProgress: listen for folder-counts-changed and call
  loadFolders() to keep the sidebar image count live
2026-06-08 22:09:50 +01:00
LyAhn 9ace1f6778 chore: remove plan handoff document 2026-06-08 20:06:17 +01:00
LyAhn ec6be96c6a feat(timeline): add virtualised month-grouped timeline view
- New Timeline view groups all media by YYYY-MM using taken_at ?? created_at
- @tanstack/react-virtual (group-level virtualiser) keeps scroll smooth on
  large libraries; estimateSize is exact so no measureElement needed
- ResizeObserver tracks container width; virtualizer.measure() is called on
  cols change to prevent stale position cache after window resize
- setView("timeline") in store auto-sets sort:"taken_asc", resets images,
  and triggers a fresh load
- Calendar nav item added to Sidebar between Explore and Duplicates
- ContextMenu and ImageTile exported from Gallery for reuse in Timeline
2026-06-08 18:19:01 +01:00
LyAhn ae9e806e61 feat(watcher): adaptive filesystem watchdog with zero CPU when idle
Monitors all registered folders using OS-native events (ReadDirectoryChangesW
on Windows) so new, modified, and deleted files are reflected in the gallery
automatically without manual reindexing.

Key design points:
- Adaptive blocking: recv() when no events pending (zero CPU), switches to
  recv_timeout(earliest_deadline) only when debounce timers are running — wakes
  exactly when the soonest event is ready, no busy-polling
- 500 ms per-path debounce coalesces rapid OS event bursts into one action
- Change detection preserved: build_record skips upsert if file_size + mtime
  unchanged, preventing spurious thumbnail/embedding re-queues and avoiding
  clobbering of existing metadata like thumbnail_path
- Access events (reads) filtered out; only Create/Modify/Remove trigger work
- Deletion path: emits watcher-deleted event with image IDs; frontend removes
  those images from state and clears selectedImage if it was deleted

WatcherHandle stored in app state; add_folder / remove_folder / update_folder_path
commands keep the watched path set in sync with the DB.
2026-06-08 06:50:01 +01:00
LyAhn 9ee5b08c93 feat(exif): extract capture date and surface as sortable taken_at field
Adds EXIF date extraction during indexing so photos can be sorted by when
they were actually taken, not just when the file was modified on disk.

- Add `taken_at` (nullable TEXT, ISO 8601) column to the images table via
  ensure_column migration and a new idx_images_taken_at index
- Populate taken_at in build_record via extract_exif_date, which tries
  DateTimeOriginal → DateTimeDigitized → DateTime tags and rejects all-zero
  sentinel dates written by uninitialised cameras
- upsert_image uses taken_at = excluded.taken_at so a re-indexed file gets
  fresh EXIF; stale dates from replaced files are not preserved
- Add taken_asc / taken_desc sort using COALESCE(taken_at, created_at) so
  images without EXIF gracefully fall back to their creation date
- Surface as "Taken: newest / oldest" in the Toolbar sort dropdown
- Shift map_image_row column indices after the new taken_at (index 10)
2026-06-08 00:26:36 +01:00
LyAhn 0ca4d142d8 feat(discovery): AI tagging, semantic search, similarity, duplicate finder, folder management
Adds a full discovery and organisation layer to the gallery.

## AI Tagger
- WD tagger (ONNX via ort) with DirectML/CPU acceleration
- Per-image and per-folder tag queuing; configurable batch size and
  confidence threshold; model downloaded on first use via ureq/zip
- Tags stored with source ('ai' | 'user'); user tags are never
  overwritten by AI; AI tags protected from accidental promotion
- Tagger pause/resume per folder; in-flight batches discarded cleanly
  on pause or cancellation without leaving jobs stuck in processing

## Semantic & Tag Search
- CLIP text-query embedding via candle (HuggingFace hub model)
- Progressive candidate doubling with filter post-processing so
  folder/rating/media-kind filters do not silently under-return results
- Tag search with DB-backed pagination (offset + COUNT total)
- Filename search unchanged; prefix syntax: s:, t:, f: in search bar

## Similarity Search
- HNSW in-memory index (hnsw_rs) with monotonic embedding_revision
  counter for cache invalidation; build_index retries if a concurrent
  write advances the revision during construction
- Folder-scoped similar search uses brute-force cosine scan (no k limit)
- Region-based similarity: crop an arbitrary rectangle in the lightbox,
  embed it with CLIP, find the nearest images globally or within folder
- Pagination for both similar and region results; scope toggle
  (all media / current folder) re-runs the query without reopening image

## Duplicate Finder
- Three-phase detection: stat (size) → sample hash (4×16 KB windows)
  → full-file hash (xxh3, large files only) to eliminate false positives
- Filesystem-first deletion: only removes DB rows for files successfully
  deleted from disk; failed files remain visible for retry
- Persisted scan cache (SQLite) with invalidation on reindex and deletion;
  both global and per-folder scopes invalidated after any deletion

## Tag Cloud & Explore
- Visual cluster explore: k-means over CLIP embeddings, configurable k,
  representative thumbnail per cluster; cache keyed on xxh3 of embedding set
- Tag explore: ranked tag list with image counts and representative
  thumbnail per tag; invalidated when AI tagging completes or folder removed
- Tag autocomplete for search bar

## Folder Management
- Inline rename (display name only, not OS rename)
- Relocate: file-picker to update folder path; all image paths rewritten
  using SUBSTR prefix replacement to avoid corrupting paths that contain
  the folder name as a substring
- Missing-folder recovery banner: Locate or Remove, never silent deletion
- Right-click context menu on sidebar items: Reindex, Rename, Locate,
  Remove; hover buttons preserved alongside context menu

## Background Tasks
- Per-folder progress panel with embedding, tagging, caption, and
  thumbnail stage breakdown
- Retry button per task for failed embeddings and tagging jobs
- Completed-task notifications (system tray via tauri-plugin-notification)
- Scan errors shown inline; WalkDir permission errors protect existing
  records from deletion rather than cascading data loss

## Backend correctness
- sqlite-vec DML performed outside transactions throughout (virtual-table
  limitation); embedding revision incremented on delete as well as insert
- Tagging job discard checks status = 'processing' so paused jobs reset
  to 'pending' are also skipped, not just cancelled ones
- Stale async responses in Lightbox guarded with cancellation flags and
  currentImageIdRef for both tag-load and tag-add callbacks
- Duplicate cache cleared on reindex; folder-removal clears vector rows
  outside transaction before deleting the folder row
2026-06-07 22:43:16 +00:00
LyAhn 8905baf4a5 Merge pull request #7 from JezzWTF/feat/custom-titlebar
feat: custom TitleBar with window controls (no native decorations)
2026-04-06 21:02:45 +01:00
LyAhn d0b41119c6 Surface failed embeddings and add filter for affected files
- Fix root cause: embedding_source_path() returns Result<PathBuf>, returning
  Err for videos without a thumbnail instead of silently falling back to the
  raw .mp4 path that CLIP cannot decode
- indexer: split embedding batch into pre-failed (no source) and embeddable
  jobs; pre-failed are marked immediately without hitting the CLIP model
- db: retry_failed_embedding_jobs skips videos still without a thumbnail so
  they no longer re-fail immediately on retry
- Add get_failed_embedding_images command listing failed files + error per folder
- Gallery: amber warning badge on tiles with embedding_status = 'failed'
- BackgroundTasks: fetch and show failed filenames/errors in expanded panel
- Toolbar: conditional 'Failed Embeddings' amber filter pill shown when any
  folder has embedding_failed > 0; filters at DB level via new
  embedding_failed_only param on get_images / count_images
- TagCloud: replaced vocabulary/dictionary label system with representative
  image thumbnails per cluster; results cached in SQLite by image-id hash
2026-04-06 16:54:03 +01:00
LyAhn ce5c3d0033 fix: add data-tauri-drag-region attribute alongside WebkitAppRegion CSS 2026-04-06 15:01:15 +01:00
LyAhn ab8fba2e3f fix: remove unused window permissions (allow-maximize, allow-unmaximize, allow-start-dragging) 2026-04-06 15:00:52 +01:00
LyAhn 2807240fab feat: integrate TitleBar into App layout 2026-04-06 14:08:21 +01:00
LyAhn 7779fe0696 feat: add custom TitleBar component with window controls 2026-04-06 14:05:45 +01:00
LyAhn aed8fcfd67 feat: add window management permissions for custom titlebar 2026-04-06 14:05:19 +01:00
LyAhn 53f21580a2 feat: disable native decorations for custom titlebar 2026-04-06 14:05:07 +01:00
LyAhn 6c3fd449ce Add tag cloud feature with k-means clustering and CUDA support
Introduces an Explore view with a tag cloud that clusters image embeddings
using cosine k-means and labels clusters via vocabulary-nearest-neighbour
CLIP matching. Vocabulary embeddings are disk-cached (FNV hash-keyed) to
avoid redundant inference. Enables CUDA for candle dependencies and adds a
build.rs check that surfaces a clear error when the toolkit is missing.
2026-04-06 12:43:44 +01:00
LyAhn 35c1dafd65 Ignore local patch artifacts
- ignore local .diff and .patch files
- ignore the local /staging workspace directory
2026-04-06 02:28:52 +01:00
LyAhn da9a20f8f7 Refine semantic search and CPU fallback
- make Candle CUDA support opt-in so default builds work on CPU-only machines
- improve semantic search loading and empty states in the gallery
- keep semantic search UX clearer when no matches are found

Refs: #4
2026-04-06 02:26:43 +01:00
LyAhn c6a66d1ba9 Polish search and embedding UX
- add semantic text search with toolbar mode switching and sqlite-vec query support
- improve embedding progress visibility, failure recovery, and similar-image affordances
- add search clearing and keyboard controls for filename vs semantic search modes
- refine background task interactions and gallery/lightbox embedding states

Refs: #4
2026-04-06 01:45:25 +01:00
LyAhn 51e4c2c1f7 Add CLIP embeddings and similar-image search
- add Candle + HF Hub CLIP image embedding pipeline with background embedding worker
- write image embeddings into sqlite-vec and expose similar-image lookup through a new backend command
- surface embedding progress and recovery in the UI, including retries for failed embeddings
- improve gallery/lightbox embedding UX and make similar-image actions directly accessible

Refs: #3, #4
2026-04-06 00:54:14 +01:00
LyAhn 76ec424167 Optimize thumbnail workers and adaptive indexing 2026-04-05 20:36:11 +01:00
LyAhn 8b391059ba Add project roadmap 2026-04-05 19:12:48 +01:00
LyAhn c4036140e6 Improve media indexing and processing flow 2026-04-05 18:59:17 +01:00
LyAhn c299c7d452 Improve indexing updates and thumbnail streaming 2026-04-05 16:52:31 +01:00
LyAhn c5e9c83ac9 Initial commit — Tauri image gallery with sqlite-vec
Sets up Tauri v2 + React frontend with SQLite metadata store,
sqlite-vec for CLIP embedding infrastructure, and r2d2 connection
pool replacing the single Arc<Mutex<Connection>>. Indexer now uses
rayon for parallel file metadata collection.
2026-04-05 15:59:48 +01:00