feat: ML tag suggestions, character/fandom integrity, underscores, modal polish

Consolidated merge of feat/tag-suggestions branch. Original 64-commit history
was lost to git-object corruption in a Nextcloud-synced checkout; this single
commit captures the equivalent diff.

Includes:
- pgvector-backed tag suggestion infra (WD14 + SigLIP centroids, ml-worker
  container, Celery tasks, suggestion service, accept/reject endpoints + modal
  UI with green/red chip buttons)
- Character/fandom integrity: title-case normalization on every write path,
  fandom-id backfill, maintenance task + settings button, migrations g26041901
  + h26041901 to canonicalize legacy rows with case-only duplicate merging
- Tag-underscores + modal polish: WD14 name canonicalization at emit + accept
  + add/bulk-add paths, migration i26041901 for legacy-row rename-or-merge
  across character/fandom/NULL kinds, suggestion-accept refresh parity via
  awaited loadTags, persistent chip tint
This commit is contained in:
2026-04-19 19:50:58 -04:00
parent 69b3ddcbd0
commit 0f35a0c484
37 changed files with 8642 additions and 30 deletions
+29
View File
@@ -436,6 +436,35 @@
</p>
</div>
</div>
<section class="settings-section">
<h3>ML tag suggestions</h3>
<p class="settings-hint">
Run inference on all images missing predictions or embeddings for the current model versions.
Resumable and safe to re-run. Backfill enqueues work on the <code>ml</code> queue; monitor progress in the ml-worker logs.
</p>
<form action="{{ url_for('main.trigger_ml_backfill') }}" method="post" onsubmit="return confirm('Enqueue ML backfill for all unprocessed images?');">
<button type="submit" class="btn secondary-btn">Run ML backfill</button>
</form>
<p class="settings-hint" style="margin-top: 1em;">
Recompute centroids for all eligible tags (character, fandom, and general/topic) that have at least
the minimum number of reference images. Run this after the initial backfill, or any time you've
applied many tags manually and want the suggestions to catch up.
</p>
<form action="{{ url_for('main.trigger_recompute_all_centroids') }}" method="post" onsubmit="return confirm('Enqueue centroid recompute for all eligible tags?');">
<button type="submit" class="btn secondary-btn">Recompute all centroids</button>
</form>
<p class="settings-hint" style="margin-top: 1em;">
Attach each character's fandom to every image already tagged with that character.
Additive only — existing fandom tags are never removed. Run after you've set a
fandom on an existing character, or whenever you suspect drift.
</p>
<form action="{{ url_for('main.trigger_sync_character_fandoms') }}" method="post" onsubmit="return confirm('Enqueue character↔fandom sync for all tagged images?');">
<button type="submit" class="btn secondary-btn">Sync character fandoms to images</button>
</form>
</section>
</div>
</div>
</div><!-- end maintenance tab panel -->