fix(annotations): drop suggestion names that Save would reject

A JoyCaption part over 50 characters or holding a newline came back as a
preselected chip, so the first Save of the dialog failed with a 400 and
nothing was stored. generate_tag_suggestions now applies the vocabulary
tag-name rules before the case-insensitive dedupe and the limit count, so
the requested number of valid chips still comes back.
This commit is contained in:
Vito Sansevero
2026-10-22 09:47:25 -07:00
parent 2e6e470aff
commit 4c8b24bac3
2 changed files with 31 additions and 3 deletions
+15 -3
View File
@@ -16,6 +16,12 @@ from PIL import Image
# A loader takes (model_type, use_gpu) and returns ("gguf" | "wd14" | "hf", backend)
ModelLoader = Callable[[str, bool], Tuple[str, Any]]
# Suggestions keep only names a Save accepts (the vocabulary tag rules).
try:
from ..database.vocabulary import validate_vocabulary_tag_name
except ImportError:
from database.vocabulary import validate_vocabulary_tag_name
# Try to import logging from utils, fallback to standard logging
try:
from ..utils.logging_config import get_logger
@@ -795,8 +801,10 @@ class AutoTagService:
WD14 pairs carry the real probability, best first; language models
(GGUF, HF) have no score, so their tags keep the output order with
``None``. Names are distinct case-insensitively (first one wins).
``prompt`` is used for this call only; ``custom_prompt`` is never
changed.
A name Save would reject (over 50 characters, a control character
such as a newline, a comma) is skipped before the dedupe and the
``limit`` count. ``prompt`` is used for this call only;
``custom_prompt`` is never changed.
"""
if type(limit) is not int or not 1 <= limit <= MAX_SUGGESTION_LIMIT:
raise ValueError(
@@ -807,7 +815,11 @@ class AutoTagService:
)
suggestions: List[Tuple[str, Optional[float]]] = []
seen = set()
for name, score in scored:
for raw, score in scored:
try:
name = validate_vocabulary_tag_name(raw)
except ValueError:
continue
key = name.casefold()
if key in seen:
continue
+16
View File
@@ -387,6 +387,22 @@ class TestTagSuggestions(EngineTestCase):
suggestions, [("zebra", None), ("apple", None), ("mango_tree", None)]
)
def test_suggestions_skip_names_save_would_reject_before_the_limit(self):
from database.annotations import normalize_image_tags
too_long = "x" * 60
service = self._gguf(f"{too_long}, sky\ncloud, zebra, apple, mango, kiwi, plum")
suggestions = service.generate_tag_suggestions(
str(self.image_path), limit=3, prompt="tags please"
)
# The 60-character part and the part holding a newline are dropped
# before the count, so three valid chips still come back.
names = [name for name, _ in suggestions]
self.assertEqual(names, ["zebra", "apple", "mango"])
self.assertEqual(normalize_image_tags(names), names)
def test_generate_tags_return_shape_unchanged(self):
service = self._wd14()
self.assertEqual(