- TypeScript 56.5%
- Rust 38.6%
- JavaScript 2.9%
- Shell 1.2%
- CSS 0.7%
| .github/workflows | ||
| docs | ||
| results | ||
| samples | ||
| scripts | ||
| src | ||
| src-tauri | ||
| tests | ||
| .gitattributes | ||
| .gitignore | ||
| .prettierrc.json | ||
| AGENTS.md | ||
| app-icon.svg | ||
| components.json | ||
| EXPERIMENT.md | ||
| index.html | ||
| package.json | ||
| playwright.config.ts | ||
| pnpm-lock.yaml | ||
| README.md | ||
| tsconfig.json | ||
| vite.config.ts | ||
Image Tag Manager
A local image library for Windows and Linux. Add multiple folders, let Qwen generate searchable tags and captions, and find images through a SQLite full-text index.
The application is React + TypeScript + Tailwind + shadcn-style Radix components, inside Tauri. Rust handles SQLite, folder scanning, thumbnails, model downloads, and llama.cpp. No Python is used to build or run the application. The original classifier evaluation report, sample images, and results remain in EXPERIMENT.md.
Install on Linux
Run pnpm install:linux from this checkout after installing the
build prerequisites. It builds a release and installs Image Tag Manager for your current
user, including the model runtime, icons, and an application-menu entry. No sudo
is needed. To install an already-built release, use pnpm install:linux --no-build.
Launch Image Tag Manager from your application menu or run ~/.local/bin/image-tag-manager.
The installer uses a user-writable AppImage, enabling in-app updates. The installed files live in
${XDG_DATA_HOME:-~/.local/share}/image-tag-manager-desktop; the desktop entry and icons
use the same data directory. Your indexed library, settings, and downloaded models
live in the separate ninja.domi.image-tag-manager data directory. On first launch,
the app moves the previous com.domi.imageshelf directory to this location,
preserving your library and models without copying them. Close the old app before
launching the new version. The installed app does
not depend on this checkout remaining in place.
Use
- Add folders using the plus button. Subfolders are included automatically. Enabled folders are rescanned at startup and every hour while the app is open, even with automatic classification turned off. A scheduled scan waits for any current task to finish; you can also use Scan folders at any time.
- Download the model once with Download model. It downloads 2.65 GB from Qwen's official repository, checks SHA-256, and resumes interrupted downloads.
- Choose Classify pending. If you close the app mid-batch, it resumes after the next launch and startup scan. Classify automatically is in Options; it checks the pending queue every 30 seconds while the app is open.
- Search tags, captions, filenames, and categories. Words use prefix matching and
combine with AND. Click a tag to insert it into the search field as
#tag. Type#followed by a name for autocomplete. Names with spaces use#"green trees"; selecting a suggestion adds the quotes for you. Hashtags match exact tags and combine with each other and ordinary search words. Clear the field to remove tag filters. Filter by folder or status. - Open an image in a nearly full-window viewer. The image fits the available space without cropping; Hide details gives it the full width. Edit tags with autocomplete and click a tag button to remove it. Use Left/Right arrows or the previous/next buttons to browse the current search results. Arrow keys in text inputs keep editing the text. Leaving an image with unsaved changes asks before discarding them. The viewer preloads and decodes up to 10 results on either side. While a preview loads, the previous image stays visible beneath a spinner. The cache follows the current result and is released when the viewer closes; background loading is limited to two requests at a time. Scroll over the image to zoom toward the pointer, then drag to pan. Double-click toggles fit/2×; the image toolbar provides zoom and Fit controls. With the image focused, use + / - to zoom, 0 to fit, and Shift+arrows to pan. Zoom resets when changing images and is limited to 8× the fitted size. Trash or Delete moves the original file immediately to the system trash (Recycle Bin on Windows), without confirmation, then shows the next result or the previous result at the end. Unsaved edits are discarded when trashing. Delete in a text input edits text; holding Delete does not trash successive images. Restore files through your system trash, then rescan to index them again. Pale blue tags come from manual edits or folder names; hover for the exact source. AI tags stay neutral. Edits update search immediately.
- Use the settings button beside Tags to manage the shared tag catalog. Search, rename or merge tags, see image-use counts, filter unused tags, and purge orphans. Hide from search keeps a tag on its images but removes its name from text and hashtag search, the sidebar, search suggestions, and grid tag links. The image editor still shows it. Do not tag images removes existing assignments and blocks folder, AI, and manual assignments until unchecked. Tags with either setting stay in the catalog when unused tags are purged. Delete requires an unused tag with both settings unchecked. Purge never removes an image's assigned tags.
- Open Options to find exact file duplicates, change the CPU thread count, or check for updates. The duplicate list has a Trash button for each file. After classifying images at different settings, compare image counts, median times, and average times there. The thread setting applies to the next batch.
Resize thumbnails with Ctrl+scroll over the image grid or Ctrl+plus/minus. The footer has matching buttons and shows the width. Sizes range from 160 to 640 pixels and are remembered on this device. Ordinary scrolling and text size stay unchanged.
The grid shows edge-to-edge images without labels or icons. Hover over an image or focus it with the keyboard to show its filename, category, and clickable tags. These details are loaded with the grid and appear immediately. Pending thumbnails show a loading indicator until the image is ready to display; failed previews show an explicit message. Loaded tiles have no permanent decorations. Scroll continuously through the library. Only visible rows and two extra rows on each side stay mounted. Offscreen thumbnails and unused result pages leave the memory cache; scrolling back reloads them. Thumbnail decoding runs at most four requests at a time, and queued requests are canceled when their tiles leave view.
Each nested directory below a configured folder contributes tags from its name.
Spaces split words; dots, dashes, and underscores stay in the tag. With a root of
Photos, Photos/Alpine Meadows/scifi.clothes/image.jpg gets alpine, meadows,
and scifi.clothes.
The configured root and its parents do not become tags. Folder and
manual tags survive AI classification and file-content changes. Removing a folder
tag from an image suppresses it on later scans, even after orphan cleanup.
Renaming a tag updates all its images as a user edit; naming an existing tag merges
them. Files and folders on disk are not renamed.
Folder settings allow a display name, pause/resume, optional category list, and extra classification instructions. Categories and instructions apply to future classifications. Qwen still generates free-form tags when categories are supplied. Unknown categories become null. Tagging uses a schema-constrained JSON response.
Originals are never moved, renamed, or deleted. Removing a folder deletes only its index entries. Changed files return to the queue; files removed from a successfully scanned folder disappear from the index. An unavailable folder retains its entries and reports an error; disable it or restore access before running classification. Overlapping roots are rejected to avoid duplicate indexing. Symlinks are not followed.
Pause finishes the current image before stopping. Pending work survives restarts. Failed images remain visible with their errors and can be retried. Three consecutive inference failures stop the batch. Closing the app stops its native model process. Only one app instance is allowed.
Supported input: JPEG, PNG, WebP, GIF, BMP, TIFF. Animated formats use a still frame. Images over 100 MB or decoder safety limits produce an error. RAW and HEIC are not currently supported. EXIF orientation is respected. Preview thumbnails are cached.
Model and data
The app uses Qwen3-VL-2B-Instruct,
Q8_0 text weights and the F16 vision projector. This is an 8-bit conversion of the
previously tested Qwen model; it is not the original Transformers/BF16 runtime.
The model revision and checksums are pinned in src-tauri/src/inference.rs.
The native runtime is llama.cpp b11384, pinned in scripts/prepare-runtime.mjs.
Inference and prompt processing use 8 CPU threads by default. Change the count in Options. GPU offload is disabled, and images are processed one at a time. The model stays loaded between batches at the selected thread count. The local server binds to a random loopback port with a per-run API key; the web UI has no network model access. Only initial model/runtime downloads use the internet. Do not interpret generated tags as verified facts; edit questionable details.
Tauri's per-user application data directory stores:
index.sqlite3: folders, image metadata, tags, captions, categories, settings, classification timings, FTS5.models/: downloaded GGUF weights and verification markers.thumbnails/: cached JPEG previews.llama-server.log: native inference diagnostics.
Typical Linux path: ~/.local/share/ninja.domi.image-tag-manager/.
Typical Windows path: %APPDATA%\ninja.domi.image-tag-manager\.
Tags are unique entities in tags, connected to images through image_tags.
Each assignment records its source: AI, user, or folder. Repeated names are
trimmed, lowercased, and deduplicated; use counts count distinct images. A tag can
have several sources on one image. images.tags_text is a derived FTS cache of tags
that are not hidden from search. Relationship triggers maintain it; tag assignments
are the authoritative store.
Database changes use Refinery, with numbered SQL
files embedded from src-tauri/migrations/. Applied versions, names, checksums, and
timestamps live in refinery_schema_history. Pending migrations run together in
one transaction. Failed upgrades roll back; changed migration checksums, missing
migrations, and databases from newer app versions stop startup without resetting data.
The two pre-Refinery prototype schemas are discarded once, including folder settings and indexed classifications. Re-add folders and classify again. Image files and model weights are untouched. There is no string-tag conversion, legacy source, or automatic backup of these disposable prototype indexes. Unknown schemas are never reset.
To change the database, add the next V<number>__description.sql migration and run
cargo test --manifest-path src-tauri/Cargo.toml --lib. Never edit, remove, or renumber
an applied migration. See migration conventions.
The SQLite index uses WAL and foreign-key cascades. Search is paginated at 48 images; the UI never loads a whole large library into memory. Thumbnails are decoded serially to bound memory usage.
Developer docs
See architecture for the components and their data flow, and development for commands, builds, and releases.