0ca4d142d8
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
102 lines
3.2 KiB
TOML
102 lines
3.2 KiB
TOML
[package]
|
||
name = "phokus"
|
||
version = "0.1.0"
|
||
description = "A performant image gallery application"
|
||
authors = ["JezzWTF"]
|
||
edition = "2021"
|
||
|
||
[lib]
|
||
name = "phokus_lib"
|
||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||
|
||
[build-dependencies]
|
||
tauri-build = { version = "2", features = [] }
|
||
|
||
[features]
|
||
default = []
|
||
candle-cuda = ["candle-core/cuda", "candle-nn/cuda", "candle-transformers/cuda"]
|
||
|
||
[dependencies]
|
||
tauri = { version = "2", features = ["protocol-asset"] }
|
||
tauri-plugin-opener = "2"
|
||
tauri-plugin-dialog = "2"
|
||
tauri-plugin-fs = "2"
|
||
serde = { version = "1", features = ["derive"] }
|
||
serde_json = "1"
|
||
rusqlite = { version = "0.32", features = ["bundled"] }
|
||
r2d2 = "0.8"
|
||
r2d2_sqlite = "0.25"
|
||
sqlite-vec = "=0.1.9"
|
||
hnsw_rs = "0.3.4"
|
||
image = { version = "0.25", default-features = false, features = ["jpeg", "png", "webp", "gif", "bmp", "tiff"] }
|
||
fast_image_resize = { version = "6.0.0", features = ["image"] }
|
||
walkdir = "2"
|
||
rayon = "1"
|
||
tokio = { version = "1", features = ["full"] }
|
||
chrono = { version = "0.4", features = ["serde"] }
|
||
uuid = { version = "1", features = ["v4"] }
|
||
anyhow = "1"
|
||
log = "0.4"
|
||
ffmpeg-sidecar = "2.5.0"
|
||
xxhash-rust = { version = "0.8", features = ["xxh3"] }
|
||
memmap2 = "0.9"
|
||
sysinfo = "0.38.4"
|
||
candle-core = { version = "0.10.2", features = ["cuda"] }
|
||
candle-nn = { version = "0.10.2", features = ["cuda"] }
|
||
candle-transformers = { version = "0.10.2", features = ["cuda"] }
|
||
hf-hub = { version = "0.5.0", default-features = false, features = ["ureq", "native-tls"] }
|
||
tokenizers = "0.22.1"
|
||
ort = { version = "2.0.0-rc.12", default-features = false, features = ["std", "ndarray", "download-binaries", "copy-dylibs", "load-dynamic", "directml", "api-24", "tls-native"] }
|
||
ureq = { version = "3.3.0", default-features = false, features = ["native-tls"] }
|
||
zip = { version = "4.6.1", default-features = false, features = ["deflate"] }
|
||
csv = "1"
|
||
tauri-plugin-notification = "2"
|
||
|
||
# ── Dev-mode performance ────────────────────────────────────────────────────
|
||
# opt-level=1 on the main crate keeps incremental compile short.
|
||
# Only the packages that are genuine hot-path bottlenecks in dev get opt-level=3
|
||
# (using "*" caused cargo to recheck all dependency fingerprints too aggressively,
|
||
# which caused spurious full rebuilds and linker conflicts).
|
||
[profile.dev]
|
||
opt-level = 1
|
||
|
||
# ML inference — without opt these run 20-50× slower than release
|
||
[profile.dev.package.candle-core]
|
||
opt-level = 3
|
||
[profile.dev.package.candle-nn]
|
||
opt-level = 3
|
||
[profile.dev.package.candle-transformers]
|
||
opt-level = 3
|
||
|
||
# ONNX runtime (WD tagger)
|
||
[profile.dev.package.ort]
|
||
opt-level = 3
|
||
[profile.dev.package.ort-sys]
|
||
opt-level = 3
|
||
|
||
# Image decode/resize workers
|
||
[profile.dev.package.image]
|
||
opt-level = 3
|
||
[profile.dev.package.fast_image_resize]
|
||
opt-level = 3
|
||
|
||
# Parallel work scheduler
|
||
[profile.dev.package.rayon]
|
||
opt-level = 3
|
||
[profile.dev.package.rayon-core]
|
||
opt-level = 3
|
||
|
||
# Tokenisation (embedding model pre-processing)
|
||
[profile.dev.package.tokenizers]
|
||
opt-level = 3
|
||
|
||
# Hashing (duplicate finder)
|
||
[profile.dev.package.xxhash-rust]
|
||
opt-level = 3
|
||
|
||
# SQLite (frequent db calls in workers)
|
||
[profile.dev.package.rusqlite]
|
||
opt-level = 3
|
||
[profile.dev.package.libsqlite3-sys]
|
||
opt-level = 3
|