from copy import deepcopy from src.prompt_parser import ParsedPrompt from src.tag_warehouse import TagWarehouse from src.synonym_data import _load_groups, _find_synonym_group def get_synonym_conflicts(tag: str, other_tags: list[str]) -> list[str]: if not other_tags: return [] tag_group = _find_synonym_group(tag) if tag_group is None: return [] other_lower = {o.lower().strip() for o in other_tags} return [o for o in other_tags if o.lower().strip() in tag_group and o.lower().strip() != tag.lower().strip()] def has_synonym_conflict(tag: str, existing: list[str]) -> bool: group = _find_synonym_group(tag) if group is None: return False tag_lower = tag.lower().strip() existing_lower = {e.lower().strip() for e in existing} for member in group: if member != tag_lower and member in existing_lower: return True return False def filter_synonym_duplicates(tags: list[str]) -> list[str]: if not tags: return [] result = [] for t in tags: if not has_synonym_conflict(t, result): result.append(t) return result def filter_user_against_category_tags( user_tags: list[str], category_tags: list[str], ) -> list[str]: """Preserve the user's explicit tags. Previously this stripped any user tag that shared a synonym group with the selected category pools, which silently erased the user's stated intent (e.g. "blue hair" vanished because "blue hair" also lives in the hair pool). Generation now keeps the user's tags and only drops internal synonym duplicates, relying on the engine's protected-set / head-noun guard to avoid re-adding redundant tags. """ if not user_tags: return [] return filter_synonym_duplicates(user_tags) def apply_synonym_filter( parsed: ParsedPrompt, selected_categories: list[str], warehouse: TagWarehouse, model: str = "anima", rating: str = "pg", ) -> ParsedPrompt: """Drop internal synonym duplicates from the user's own tags. Category pool membership is intentionally NOT consulted here: the previous behavior of stripping user tags that collide with a pool erased explicit user intent. `warehouse`/`rating` stay in the signature for API compat. """ if parsed is None: return parsed result = deepcopy(parsed) if result.general_tags is None: result.general_tags = [] result.general_tags = filter_synonym_duplicates(result.general_tags) return result def reload_groups(): from src.synonym_data import reload_synonym_groups reload_synonym_groups() _load_groups()