fix(suggestions): rejected tags stay rejected per image
Adds a per-image rejection filter to get_suggestions so once the user clicks ✕ on an auto-accepted chip (or rejects a regular suggestion via the existing ✕ button), that tag name stops reappearing in suggestions — including auto_accept candidates. Without this filter, the undo flow was cosmetic: the user's ✕ removed the tag from this image and logged a SuggestionFeedback decision='rejected' row, but the next modal load re-ran auto-accept against the same unchanged WD14 predictions and re-attached the same tag. Rinse, repeat. Implementation: _rejected_names_for_image(image_id) reads distinct tag_names from suggestion_feedback where decision='rejected' for this image. get_suggestions filters merged results against that set alongside the existing blocklist filter. Both auto-accept candidates and the three core categories respect the filter. Tested on image 222 at threshold=0.9: first load auto-applied 23 general tags; rejected '1girl'; second load auto_accepted.count=0, general suggestions had 10 new items, '1girl' absent from both. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -114,6 +114,27 @@ def _blocklisted_names() -> set[str]:
|
||||
return {row[0] for row in db.session.query(TagSuggestionBlocklistEntry.name).all()}
|
||||
|
||||
|
||||
def _rejected_names_for_image(image_id: int) -> set[str]:
|
||||
"""Tag names the user has rejected for this specific image. Includes
|
||||
both explicit ✕ rejections on raw suggestions and the ✕/⊘ undo actions
|
||||
on auto-accepted tags (both go through /suggestions/reject which writes
|
||||
decision='rejected' rows). Once rejected for an image, a name stays
|
||||
suppressed for that image so auto-accept doesn't re-apply it on the
|
||||
next modal open."""
|
||||
from app.models import SuggestionFeedback
|
||||
rows = (
|
||||
db.session
|
||||
.query(SuggestionFeedback.tag_name)
|
||||
.filter(
|
||||
SuggestionFeedback.image_id == image_id,
|
||||
SuggestionFeedback.decision == 'rejected',
|
||||
)
|
||||
.distinct()
|
||||
.all()
|
||||
)
|
||||
return {r[0] for r in rows}
|
||||
|
||||
|
||||
|
||||
|
||||
def _wd14_suggestions(image_id: int, cfg: dict, already: set[str]) -> list[dict]:
|
||||
@@ -247,6 +268,14 @@ def get_suggestions(
|
||||
if blocklist:
|
||||
merged = [s for s in merged if s['name'] not in blocklist]
|
||||
|
||||
# Drop names the user has already rejected for THIS image. Without this,
|
||||
# auto-accept re-applies the same WD14 prediction every time the modal
|
||||
# loads, and the user's ✕ click on an auto-accepted chip is immediately
|
||||
# undone on the next view.
|
||||
per_image_rejected = _rejected_names_for_image(image_id)
|
||||
if per_image_rejected:
|
||||
merged = [s for s in merged if s['name'] not in per_image_rejected]
|
||||
|
||||
if existing_all is None:
|
||||
existing_all = _existing_tag_names()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user