mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-24 22:52:12 -03:00
chore(priority-tags): add newline terminator
This commit is contained in:
@@ -162,6 +162,7 @@ class SettingsHandler:
|
||||
"include_trigger_words",
|
||||
"show_only_sfw",
|
||||
"compact_mode",
|
||||
"priority_tags",
|
||||
)
|
||||
|
||||
_PROXY_KEYS = {"proxy_enabled", "proxy_host", "proxy_port", "proxy_username", "proxy_password", "proxy_type"}
|
||||
@@ -207,6 +208,14 @@ class SettingsHandler:
|
||||
logger.error("Error getting settings: %s", exc, exc_info=True)
|
||||
return web.json_response({"success": False, "error": str(exc)}, status=500)
|
||||
|
||||
async def get_priority_tags(self, request: web.Request) -> web.Response:
|
||||
try:
|
||||
suggestions = self._settings.get_priority_tag_suggestions()
|
||||
return web.json_response({"success": True, "tags": suggestions})
|
||||
except Exception as exc: # pragma: no cover - defensive logging
|
||||
logger.error("Error getting priority tags: %s", exc, exc_info=True)
|
||||
return web.json_response({"success": False, "error": str(exc)}, status=500)
|
||||
|
||||
async def activate_library(self, request: web.Request) -> web.Response:
|
||||
"""Activate the selected library."""
|
||||
|
||||
@@ -942,6 +951,7 @@ class MiscHandlerSet:
|
||||
"health_check": self.health.health_check,
|
||||
"get_settings": self.settings.get_settings,
|
||||
"update_settings": self.settings.update_settings,
|
||||
"get_priority_tags": self.settings.get_priority_tags,
|
||||
"get_settings_libraries": self.settings.get_libraries,
|
||||
"activate_library": self.settings.activate_library,
|
||||
"update_usage_stats": self.usage_stats.update_usage_stats,
|
||||
|
||||
@@ -22,6 +22,7 @@ class RouteDefinition:
|
||||
MISC_ROUTE_DEFINITIONS: tuple[RouteDefinition, ...] = (
|
||||
RouteDefinition("GET", "/api/lm/settings", "get_settings"),
|
||||
RouteDefinition("POST", "/api/lm/settings", "update_settings"),
|
||||
RouteDefinition("GET", "/api/lm/priority-tags", "get_priority_tags"),
|
||||
RouteDefinition("GET", "/api/lm/settings/libraries", "get_settings_libraries"),
|
||||
RouteDefinition("POST", "/api/lm/settings/libraries/activate", "activate_library"),
|
||||
RouteDefinition("GET", "/api/lm/health-check", "health_check"),
|
||||
|
||||
@@ -6,7 +6,7 @@ import uuid
|
||||
from typing import Dict, List
|
||||
from urllib.parse import urlparse
|
||||
from ..utils.models import LoraMetadata, CheckpointMetadata, EmbeddingMetadata
|
||||
from ..utils.constants import CARD_PREVIEW_WIDTH, VALID_LORA_TYPES, CIVITAI_MODEL_TAGS
|
||||
from ..utils.constants import CARD_PREVIEW_WIDTH, VALID_LORA_TYPES
|
||||
from ..utils.civitai_utils import rewrite_preview_url
|
||||
from ..utils.exif_utils import ExifUtils
|
||||
from ..utils.metadata_manager import MetadataManager
|
||||
@@ -386,18 +386,9 @@ class DownloadManager:
|
||||
|
||||
# Get model tags
|
||||
model_tags = version_info.get('model', {}).get('tags', [])
|
||||
|
||||
# Find the first Civitai model tag that exists in model_tags
|
||||
first_tag = ''
|
||||
for civitai_tag in CIVITAI_MODEL_TAGS:
|
||||
if civitai_tag in model_tags:
|
||||
first_tag = civitai_tag
|
||||
break
|
||||
|
||||
# If no Civitai model tag found, fallback to first tag
|
||||
if not first_tag and model_tags:
|
||||
first_tag = model_tags[0]
|
||||
|
||||
|
||||
first_tag = settings_manager.resolve_priority_tag_for_model(model_tags, model_type)
|
||||
|
||||
# Format the template with available data
|
||||
formatted_path = path_template
|
||||
formatted_path = formatted_path.replace('{base_model}', mapped_base_model)
|
||||
|
||||
@@ -4,9 +4,16 @@ import os
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from threading import Lock
|
||||
from typing import Any, Dict, Iterable, List, Mapping, Optional
|
||||
from typing import Any, Dict, Iterable, List, Mapping, Optional, Sequence
|
||||
|
||||
from ..utils.constants import DEFAULT_PRIORITY_TAG_CONFIG
|
||||
from ..utils.settings_paths import ensure_settings_file
|
||||
from ..utils.tag_priorities import (
|
||||
PriorityTagEntry,
|
||||
collect_canonical_tags,
|
||||
parse_priority_tag_string,
|
||||
resolve_priority_tag,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -36,6 +43,7 @@ DEFAULT_SETTINGS: Dict[str, Any] = {
|
||||
"card_info_display": "always",
|
||||
"include_trigger_words": False,
|
||||
"compact_mode": False,
|
||||
"priority_tags": DEFAULT_PRIORITY_TAG_CONFIG.copy(),
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +71,12 @@ class SettingsManager:
|
||||
def _ensure_default_settings(self) -> None:
|
||||
"""Ensure all default settings keys exist"""
|
||||
updated = False
|
||||
normalized_priority = self._normalize_priority_tag_config(
|
||||
self.settings.get("priority_tags")
|
||||
)
|
||||
if normalized_priority != self.settings.get("priority_tags"):
|
||||
self.settings["priority_tags"] = normalized_priority
|
||||
updated = True
|
||||
for key, value in self._get_default_settings().items():
|
||||
if key not in self.settings:
|
||||
if isinstance(value, dict):
|
||||
@@ -385,8 +399,56 @@ class SettingsManager:
|
||||
# Ensure nested dicts are independent copies
|
||||
defaults['base_model_path_mappings'] = {}
|
||||
defaults['download_path_templates'] = {}
|
||||
defaults['priority_tags'] = DEFAULT_PRIORITY_TAG_CONFIG.copy()
|
||||
return defaults
|
||||
|
||||
def _normalize_priority_tag_config(self, value: Any) -> Dict[str, str]:
|
||||
normalized: Dict[str, str] = {}
|
||||
if isinstance(value, Mapping):
|
||||
for key, raw in value.items():
|
||||
if not isinstance(key, str) or not isinstance(raw, str):
|
||||
continue
|
||||
normalized[key] = raw.strip()
|
||||
|
||||
for model_type, default_value in DEFAULT_PRIORITY_TAG_CONFIG.items():
|
||||
normalized.setdefault(model_type, default_value)
|
||||
|
||||
return normalized
|
||||
|
||||
def get_priority_tag_config(self) -> Dict[str, str]:
|
||||
stored_value = self.settings.get("priority_tags")
|
||||
normalized = self._normalize_priority_tag_config(stored_value)
|
||||
if normalized != stored_value:
|
||||
self.settings["priority_tags"] = normalized
|
||||
self._save_settings()
|
||||
return normalized.copy()
|
||||
|
||||
def get_priority_tag_entries(self, model_type: str) -> List[PriorityTagEntry]:
|
||||
config = self.get_priority_tag_config()
|
||||
raw_config = config.get(model_type, "")
|
||||
return parse_priority_tag_string(raw_config)
|
||||
|
||||
def resolve_priority_tag_for_model(
|
||||
self, tags: Sequence[str] | Iterable[str], model_type: str
|
||||
) -> str:
|
||||
entries = self.get_priority_tag_entries(model_type)
|
||||
resolved = resolve_priority_tag(tags, entries)
|
||||
if resolved:
|
||||
return resolved
|
||||
|
||||
for tag in tags:
|
||||
if isinstance(tag, str) and tag:
|
||||
return tag
|
||||
return ""
|
||||
|
||||
def get_priority_tag_suggestions(self) -> Dict[str, List[str]]:
|
||||
suggestions: Dict[str, List[str]] = {}
|
||||
config = self.get_priority_tag_config()
|
||||
for model_type, raw_value in config.items():
|
||||
entries = parse_priority_tag_string(raw_value)
|
||||
suggestions[model_type] = collect_canonical_tags(entries)
|
||||
return suggestions
|
||||
|
||||
def get(self, key: str, default: Any = None) -> Any:
|
||||
"""Get setting value"""
|
||||
return self.settings.get(key, default)
|
||||
|
||||
@@ -64,4 +64,11 @@ CIVITAI_MODEL_TAGS = [
|
||||
'realistic', 'anime', 'toon', 'furry', 'style',
|
||||
'poses', 'background', 'tool', 'vehicle', 'buildings',
|
||||
'objects', 'assets', 'animal', 'action'
|
||||
]
|
||||
]
|
||||
|
||||
# Default priority tag configuration strings for each model type
|
||||
DEFAULT_PRIORITY_TAG_CONFIG = {
|
||||
'lora': ', '.join(CIVITAI_MODEL_TAGS),
|
||||
'checkpoint': ', '.join(CIVITAI_MODEL_TAGS),
|
||||
'embedding': ', '.join(CIVITAI_MODEL_TAGS),
|
||||
}
|
||||
|
||||
104
py/utils/tag_priorities.py
Normal file
104
py/utils/tag_priorities.py
Normal file
@@ -0,0 +1,104 @@
|
||||
"""Helpers for parsing and resolving priority tag configurations."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Iterable, List, Optional, Sequence, Set
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PriorityTagEntry:
|
||||
"""A parsed priority tag configuration entry."""
|
||||
|
||||
canonical: str
|
||||
aliases: Set[str]
|
||||
|
||||
@property
|
||||
def normalized_aliases(self) -> Set[str]:
|
||||
return {alias.lower() for alias in self.aliases}
|
||||
|
||||
|
||||
def _normalize_alias(alias: str) -> str:
|
||||
return alias.strip()
|
||||
|
||||
|
||||
def parse_priority_tag_string(config: str | None) -> List[PriorityTagEntry]:
|
||||
"""Parse the user-facing priority tag string into structured entries."""
|
||||
|
||||
if not config:
|
||||
return []
|
||||
|
||||
entries: List[PriorityTagEntry] = []
|
||||
seen_canonicals: Set[str] = set()
|
||||
|
||||
for raw_entry in _split_priority_entries(config):
|
||||
canonical, aliases = _parse_priority_entry(raw_entry)
|
||||
if not canonical:
|
||||
continue
|
||||
|
||||
normalized_canonical = canonical.lower()
|
||||
if normalized_canonical in seen_canonicals:
|
||||
# Skip duplicate canonicals while preserving first occurrence priority
|
||||
continue
|
||||
seen_canonicals.add(normalized_canonical)
|
||||
|
||||
alias_set = {canonical, *aliases}
|
||||
cleaned_aliases = {_normalize_alias(alias) for alias in alias_set if _normalize_alias(alias)}
|
||||
if not cleaned_aliases:
|
||||
continue
|
||||
|
||||
entries.append(PriorityTagEntry(canonical=canonical, aliases=cleaned_aliases))
|
||||
|
||||
return entries
|
||||
|
||||
|
||||
def _split_priority_entries(config: str) -> List[str]:
|
||||
# Split on commas while respecting that users may add new lines for readability
|
||||
parts = []
|
||||
for chunk in config.split('\n'):
|
||||
parts.extend(chunk.split(','))
|
||||
return [part.strip() for part in parts if part.strip()]
|
||||
|
||||
|
||||
def _parse_priority_entry(entry: str) -> tuple[str, Set[str]]:
|
||||
if '(' in entry and entry.endswith(')'):
|
||||
canonical, raw_aliases = entry.split('(', 1)
|
||||
canonical = canonical.strip()
|
||||
alias_section = raw_aliases[:-1] # drop trailing ')'
|
||||
aliases = {alias.strip() for alias in alias_section.split('|') if alias.strip()}
|
||||
return canonical, aliases
|
||||
|
||||
if '(' in entry and not entry.endswith(')'):
|
||||
# Malformed entry; treat as literal canonical to avoid surprises
|
||||
entry = entry.replace('(', '').replace(')', '')
|
||||
|
||||
canonical = entry.strip()
|
||||
return canonical, set()
|
||||
|
||||
|
||||
def resolve_priority_tag(
|
||||
tags: Sequence[str] | Iterable[str],
|
||||
entries: Sequence[PriorityTagEntry],
|
||||
) -> Optional[str]:
|
||||
"""Resolve the first matching canonical priority tag for the provided tags."""
|
||||
|
||||
tag_lookup: Dict[str, str] = {}
|
||||
for tag in tags:
|
||||
if not isinstance(tag, str):
|
||||
continue
|
||||
normalized = tag.lower()
|
||||
if normalized not in tag_lookup:
|
||||
tag_lookup[normalized] = tag
|
||||
|
||||
for entry in entries:
|
||||
for alias in entry.normalized_aliases:
|
||||
if alias in tag_lookup:
|
||||
return entry.canonical
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def collect_canonical_tags(entries: Iterable[PriorityTagEntry]) -> List[str]:
|
||||
"""Return the ordered list of canonical tags from the parsed entries."""
|
||||
|
||||
return [entry.canonical for entry in entries]
|
||||
@@ -4,7 +4,6 @@ from typing import Dict
|
||||
from ..services.service_registry import ServiceRegistry
|
||||
from ..config import config
|
||||
from ..services.settings_manager import get_settings_manager
|
||||
from .constants import CIVITAI_MODEL_TAGS
|
||||
import asyncio
|
||||
|
||||
def get_lora_info(lora_name):
|
||||
@@ -170,16 +169,7 @@ def calculate_relative_path_for_model(model_data: Dict, model_type: str = 'lora'
|
||||
base_model_mappings = settings_manager.get('base_model_path_mappings', {})
|
||||
mapped_base_model = base_model_mappings.get(base_model, base_model)
|
||||
|
||||
# Find the first Civitai model tag that exists in model_tags
|
||||
first_tag = ''
|
||||
for civitai_tag in CIVITAI_MODEL_TAGS:
|
||||
if civitai_tag in model_tags:
|
||||
first_tag = civitai_tag
|
||||
break
|
||||
|
||||
# If no Civitai model tag found, fallback to first tag
|
||||
if not first_tag and model_tags:
|
||||
first_tag = model_tags[0]
|
||||
first_tag = settings_manager.resolve_priority_tag_for_model(model_tags, model_type)
|
||||
|
||||
if not first_tag:
|
||||
first_tag = 'no tags' # Default if no tags available
|
||||
|
||||
Reference in New Issue
Block a user