feat(modal): autofocus tag input, expand general suggestions, retire copyright/artist categories
CI / lint (push) Successful in 2s
CI / backend-lint-and-test (push) Successful in 17s
CI / frontend-build (push) Successful in 18s
CI / intimp (push) Successful in 3m48s
CI / intapi (push) Successful in 7m46s
CI / intcore (push) Failing after 8m18s

Four coupled operator-asked changes to the view modal (Scribe plan #509):

1. **Autofocus tag entry on modal open** — TagAutocomplete grabs focus
   in onMounted/nextTick so the caret is in the input the moment the
   modal renders. No click needed to start typing.

2. **General suggestions expanded by default** — SuggestionsPanel's
   general-category group now mounts with `:default-open="true"`.
   Operator can collapse if too noisy, but the v1 frame shows them.

3. **Lower general threshold default 0.95 → 0.50** — MLSettings.
   suggestion_threshold_general default matches character. Alembic
   0029 also bumps the existing singleton row's value if it's still
   at the old 0.95. Operator can re-tune from Settings → ML.

4. **Retire `copyright` + `artist` as ML suggestion categories** —
   neither feeds a Tag.kind (`artist` retired in FC-2d-vii-c, never
   really existed as a copyright tag-kind). They were surfaced in the
   suggestions pipeline + threshold settings UI but had no follow-
   through. Drop from SURFACED_CATEGORIES, suggestions._threshold_for,
   ml_admin GET/PATCH allowlist, MLSettings columns (alembic 0029
   drops the two columns), frontend CATEGORY_ORDER + CATEGORY_LABELS,
   SuggestionsPanel.peopleCats, AliasPickerDialog kind-check, and
   MLThresholdSliders rows.

Out of scope (intentional): `tag_kind` Postgres enum still includes
`artist` for historic Tag row queryability (per the model comment);
no operator pain reported, no enum-shrink needed.

Tests:
- test_surfaced_categories asserts {character, general}, excludes
  artist + copyright.
- test_threshold_for_artist_is_unsurfaced extended to cover copyright.
- test_get_and_patch_settings asserts new 0.50 default and the absent
  artist + copyright keys in the GET payload.
This commit is contained in:
2026-06-01 02:08:10 -04:00
parent 8de7ccd07d
commit af7b5c95e9
13 changed files with 125 additions and 37 deletions
-4
View File
@@ -9,9 +9,7 @@ ml_admin_bp = Blueprint("ml_admin", __name__, url_prefix="/api/ml")
_EDITABLE = (
"suggestion_threshold_artist",
"suggestion_threshold_character",
"suggestion_threshold_copyright",
"suggestion_threshold_general",
"centroid_similarity_threshold",
"min_reference_images",
@@ -28,9 +26,7 @@ async def get_settings():
).scalar_one()
return jsonify(
{
"suggestion_threshold_artist": s.suggestion_threshold_artist,
"suggestion_threshold_character": s.suggestion_threshold_character,
"suggestion_threshold_copyright": s.suggestion_threshold_copyright,
"suggestion_threshold_general": s.suggestion_threshold_general,
"centroid_similarity_threshold": s.centroid_similarity_threshold,
"min_reference_images": s.min_reference_images,
+4 -7
View File
@@ -15,17 +15,14 @@ class MLSettings(Base):
__table_args__ = (CheckConstraint("id = 1", name="singleton"),)
id: Mapped[int] = mapped_column(Integer, primary_key=True)
suggestion_threshold_artist: Mapped[float] = mapped_column(
Float, nullable=False, default=0.30
)
suggestion_threshold_character: Mapped[float] = mapped_column(
Float, nullable=False, default=0.50
)
suggestion_threshold_copyright: Mapped[float] = mapped_column(
Float, nullable=False, default=0.50
)
# Default lowered 0.95 → 0.50 on 2026-06-01 — operator-flagged that
# 0.95 hid most general suggestions. Operator-tunable via Settings →
# ML if too noisy.
suggestion_threshold_general: Mapped[float] = mapped_column(
Float, nullable=False, default=0.95
Float, nullable=False, default=0.50
)
centroid_similarity_threshold: Mapped[float] = mapped_column(
Float, nullable=False, default=0.55
+3 -3
View File
@@ -48,11 +48,11 @@ class SuggestionService:
).scalar_one()
def _threshold_for(self, s: MLSettings, category: str) -> float:
# 'artist' intentionally absent (FC-2d-vii-c) — falls through to
# the 1.01 "never surfaces" default like any unsurfaced category.
# 'artist' (FC-2d-vii-c) and 'copyright' (2026-06-01) retired;
# both fall through to the 1.01 "never surfaces" default like any
# unsurfaced category.
return {
"character": s.suggestion_threshold_character,
"copyright": s.suggestion_threshold_copyright,
"general": s.suggestion_threshold_general,
}.get(category, 1.01)
+7 -4
View File
@@ -38,10 +38,13 @@ STORE_FLOOR = float(os.environ.get("TAGGER_STORE_FLOOR", "0.05"))
# The categories FC-2b surfaces in the UI. Others (meta/rating/year) are
# still stored but the suggestion service filters them out.
# FC-2d-vii-c: 'artist' retired — artist identity is acquisition-derived
# (image_record.artist_id), never ML-inferred. Raw predictions are still
# stored at STORE_FLOOR but artist never surfaces.
SURFACED_CATEGORIES = {"character", "copyright", "general"}
# 'artist' retired in FC-2d-vii-c — artist identity is acquisition-derived
# (image_record.artist_id), never ML-inferred. 'copyright' retired
# 2026-06-01 — operator doesn't use the copyright tag-kind; fandom is
# this app's franchise/series concept (per TagsView.vue's doc comment).
# Raw predictions for both categories still get stored at STORE_FLOOR but
# don't surface in suggestions.
SURFACED_CATEGORIES = {"character", "general"}
# ImageNet preprocessing constants (per Camie v2 onnx_inference.py).
_IMAGENET_MEAN = np.array([0.485, 0.456, 0.406], dtype=np.float32)