feat(bulk): consensus ML suggestions + frontend polish

Bulk editor now loads consensus ML suggestions across the current
selection via a new POST /api/suggestions/bulk endpoint, powered by
get_bulk_suggestions() in tag_suggestions.py. A tag is surfaced only if
it was suggested for or already applied to >= 80% of the selection;
coverage counts include images that already have the tag so a near-
universal tag isn't penalized for dropping out of suggestion lists.
Accept-only chips (no reject) match the rest of the suggestions UX.

Frontend polish in the same commit:
- Showcase session dedup (exclude seen ids, reset on second lap) and
  aspect-ratio-aware placeholder heights for better column balancing
- Modal touch-swipe navigation on the image wrapper, auto-focus of the
  tag input on desktop (>=768px), and autocomplete flip-up when the
  dropdown would cover the Suggestions section
- Settings template inline styles replaced with utility classes
- Inline series-editor script extracted to gallery-series-editor.js

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-20 18:39:47 -04:00
parent bc6b0a4a58
commit 3944605d33
9 changed files with 693 additions and 158 deletions
+2
View File
@@ -42,6 +42,8 @@
"thumb_url": "{{ url_for('main.serve_image', filename=(image.thumb_path or image.filepath) | replace('/images/', '')) }}",
"full_url": "{{ url_for('main.serve_image', filename=image.filepath | replace('/images/', '')) }}",
"is_video": {{ 'true' if image.filename.lower().endswith(('.mp4', '.mov')) else 'false' }},
"width": {{ image.width if image.width else 'null' }},
"height": {{ image.height if image.height else 'null' }},
"tags": [{% for t in image.tags %}{"name": "{{ t.name | e }}", "kind": "{{ t.kind }}"}{% if not loop.last %}, {% endif %}{% endfor %}]
}{% if not loop.last %},{% endif %}
{% endfor %}]