mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-23 12:04:09 -03:00
fix(organize): stop keyword-dump tags from becoming folder names (#1119)
CivitAI tags are normally short single-concept labels, but some uploaders pack their entire keyword list into one tag. The model in #1119 carries "lora, character, rosie, irish, ... face" as a single 181-character tag. Priority resolution matches aliases by exact equality, so that tag matched nothing and resolve_priority_tag_for_model fell back to tags[0] -- the blob. With the default "{base_model}/{first_tag}" template the model was filed under "Krea 2/<181-character blob>/", and the full path plus the ".civitai.info" sidecar and the preview images next to it ran into the Windows MAX_PATH limit. Tags also bypassed sanitization on the way into a path: both calculate_relative_path_for_model and DownloadManager._calculate_relative_path sanitized model_name and version_name but interpolated {first_tag} verbatim, so a tag containing "/" or ":" silently produced nested or illegal folders. Two changes: - The fallback skips tags that cannot serve as a folder name. is_usable_path_tag rejects comma-separated keyword dumps and tags longer than MAX_PATH_TAG_LENGTH; the resolver returns "" when nothing usable is left, which callers already render as "no tags". Whole-tag priority matching is untouched, so existing priority configurations behave the same. - sanitize_folder_name gains an optional max_length, and every tag-derived segment now goes through it. Tags are capped at MAX_PATH_TAG_LENGTH, model and version names at MAX_FOLDER_NAME_LENGTH, and rendered filename stems at MAX_FILENAME_STEM_LENGTH. For the reported model the folder becomes "Krea 2/base model" instead of the blob, and the full path drops from 235 to 64 characters. Existing libraries are not migrated up front: a path is only recomputed on download, on an auto-organize run or when a filename template is applied, and values already inside the caps are left byte-identical. Models previously filed under a keyword-dump folder move on the next auto-organize run.
This commit is contained in:
@@ -2,6 +2,11 @@ import pytest
|
||||
|
||||
from py.services.settings_manager import SettingsManager, get_settings_manager
|
||||
from py.services.service_registry import ServiceRegistry
|
||||
from py.utils.constants import (
|
||||
MAX_FILENAME_STEM_LENGTH,
|
||||
MAX_FOLDER_NAME_LENGTH,
|
||||
MAX_PATH_TAG_LENGTH,
|
||||
)
|
||||
from py.utils.utils import (
|
||||
calculate_filename_for_model,
|
||||
calculate_recipe_fingerprint,
|
||||
@@ -12,6 +17,15 @@ from py.utils.utils import (
|
||||
)
|
||||
|
||||
|
||||
# Real CivitAI data for the model reported in issue #1119: the uploader dumped
|
||||
# a whole keyword list into a single tag.
|
||||
KEYWORD_DUMP_TAG = (
|
||||
"lora, character, rosie, irish, redhead, auburn, freckles, green eyes, "
|
||||
"curly hair, woman, female, photorealistic, realistic, krea2, dark beast, "
|
||||
"kreativity, nsfw, nude, portrait, face"
|
||||
)
|
||||
|
||||
|
||||
class _FakeCache:
|
||||
def __init__(self, items):
|
||||
self.raw_data = list(items)
|
||||
@@ -147,6 +161,66 @@ def test_calculate_relative_path_sanitizes_double_slashes(isolated_settings):
|
||||
assert relative_path == "no tags/Author"
|
||||
|
||||
|
||||
def test_calculate_relative_path_ignores_keyword_dump_tag(isolated_settings):
|
||||
"""A tag holding a whole keyword list must not become a folder name (#1119)."""
|
||||
model_data = {"base_model": "Krea 2", "tags": [KEYWORD_DUMP_TAG]}
|
||||
|
||||
relative_path = calculate_relative_path_for_model(model_data, "lora")
|
||||
|
||||
assert relative_path == "Krea 2/no tags"
|
||||
|
||||
|
||||
def test_calculate_relative_path_uses_next_usable_tag(isolated_settings):
|
||||
"""Unusable tags are skipped instead of hijacking the folder name (#1119)."""
|
||||
model_data = {"base_model": "Krea 2", "tags": [KEYWORD_DUMP_TAG, "portrait"]}
|
||||
|
||||
assert calculate_relative_path_for_model(model_data, "lora") == "Krea 2/portrait"
|
||||
|
||||
|
||||
def test_calculate_relative_path_sanitizes_tag_segment(isolated_settings):
|
||||
"""A tag with path separators must not create nested folders."""
|
||||
model_data = {"base_model": "SDXL", "tags": ["a/b:c"]}
|
||||
|
||||
assert calculate_relative_path_for_model(model_data, "lora") == "SDXL/a_b_c"
|
||||
|
||||
|
||||
def test_calculate_relative_path_caps_tag_segment(isolated_settings):
|
||||
"""A long configured priority tag is truncated to the tag length budget."""
|
||||
long_tag = "y" * 80
|
||||
isolated_settings["priority_tags"] = {"lora": long_tag}
|
||||
|
||||
model_data = {"base_model": "SDXL", "tags": [long_tag]}
|
||||
|
||||
relative_path = calculate_relative_path_for_model(model_data, "lora")
|
||||
|
||||
assert relative_path == "SDXL/" + "y" * MAX_PATH_TAG_LENGTH
|
||||
|
||||
|
||||
def test_calculate_relative_path_keeps_tag_within_budget(isolated_settings):
|
||||
model_data = {"base_model": "SDXL", "tags": ["t" * 40]}
|
||||
|
||||
relative_path = calculate_relative_path_for_model(model_data, "lora")
|
||||
|
||||
assert relative_path == "SDXL/" + "t" * 40
|
||||
|
||||
|
||||
def test_calculate_relative_path_caps_model_and_version_names(isolated_settings):
|
||||
isolated_settings["download_path_templates"]["lora"] = "{model_name}/{version_name}"
|
||||
|
||||
model_data = {
|
||||
"model_name": "m" * 300,
|
||||
"base_model": "SDXL",
|
||||
"tags": [],
|
||||
"civitai": {"id": 1, "name": "v" * 300, "creator": {"username": "Creator"}},
|
||||
}
|
||||
|
||||
relative_path = calculate_relative_path_for_model(model_data, "lora")
|
||||
|
||||
assert relative_path == (
|
||||
"m" * MAX_FOLDER_NAME_LENGTH + "/" + "v" * MAX_FOLDER_NAME_LENGTH
|
||||
)
|
||||
|
||||
|
||||
def test_calculate_recipe_fingerprint_filters_and_sorts():
|
||||
loras = [
|
||||
{"hash": "ABC", "strength": 0.1234},
|
||||
@@ -304,6 +378,32 @@ def test_calculate_filename_original_name_falls_back_to_file_name(isolated_setti
|
||||
assert calculate_filename_for_model(model_data, "lora") == "legacy-name-0123456789"
|
||||
|
||||
|
||||
def test_calculate_filename_drops_keyword_dump_tag(isolated_settings):
|
||||
"""The keyword-dump tag collapses instead of filling the filename (#1119)."""
|
||||
_set_filename_templates(isolated_settings, "{base_model}-{first_tag}")
|
||||
|
||||
model_data = {
|
||||
"base_model": "Krea 2",
|
||||
"tags": [KEYWORD_DUMP_TAG],
|
||||
"file_path": "/models/V1.safetensors",
|
||||
}
|
||||
|
||||
assert calculate_filename_for_model(model_data, "lora") == "Krea 2"
|
||||
|
||||
|
||||
def test_calculate_filename_caps_rendered_stem(isolated_settings):
|
||||
_set_filename_templates(isolated_settings, "{model_name}")
|
||||
|
||||
model_data = {
|
||||
"model_name": "m" * 400,
|
||||
"file_path": "/models/V1.safetensors",
|
||||
}
|
||||
|
||||
result = calculate_filename_for_model(model_data, "lora")
|
||||
|
||||
assert len(result) == MAX_FILENAME_STEM_LENGTH
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"original, expected",
|
||||
[
|
||||
@@ -318,6 +418,27 @@ def test_sanitize_folder_name(original, expected):
|
||||
assert sanitize_folder_name(original) == expected
|
||||
|
||||
|
||||
def test_sanitize_folder_name_without_max_length_is_unbounded():
|
||||
assert sanitize_folder_name("x" * 300) == "x" * 300
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"original, max_length, expected",
|
||||
[
|
||||
("abcdefghij", 4, "abcd"),
|
||||
# Re-trim separators and spaces exposed by the cut.
|
||||
("abc...defg", 4, "abc"),
|
||||
("abcdefg hij", 8, "abcdefg"),
|
||||
# Shorter than the cap is returned untouched.
|
||||
("short", 10, "short"),
|
||||
# A cut that leaves only separators falls back to "unnamed".
|
||||
("...abcdef", 3, "unnamed"),
|
||||
],
|
||||
)
|
||||
def test_sanitize_folder_name_truncates_to_max_length(original, max_length, expected):
|
||||
assert sanitize_folder_name(original, max_length=max_length) == expected
|
||||
|
||||
|
||||
def test_get_lora_info_absolute_bare_name(mock_lora_scanner):
|
||||
mock_lora_scanner([
|
||||
{"file_name": "mylora", "folder": "SDXL", "file_path": "/models/Lora/SDXL/mylora.safetensors", "civitai": {"trainedWords": ["trigger1"]}},
|
||||
|
||||
Reference in New Issue
Block a user