feat(lora): support relative paths in <lora:folder/name:strength> syntax (#917)

Autocomplete, copy/send-to-workflow, and recipe syntax now emit
<lora:folder/name:strength> instead of <lora:name:strength>, using
relative paths to disambiguate identically-named loras in different
subfolders without requiring file renames.

Backend: 3-tier hybrid resolution (path → bare → basename fallback)
across get_lora_info, get_lora_info_absolute, get_model_preview_url,
get_model_civitai_url, get_model_info_by_name, get_lora_metadata_by_filename,
and get_hash_by_filename. Also fix get_random_loras and get_cycler_list
to return path-prefixed names for randomizer/cycler consistency.

Frontend: autocomplete, copyLoraSyntax, handleSendToWorkflow emit
folder-prefixed syntax. extract_lora_name preserves relative paths.

Saved image metadata (<lora:...> in EXIF) intentionally keeps basename-only
for compatibility with A1111/Forge ecosystem.
This commit is contained in:
Will Miao
2026-05-20 19:39:12 +08:00
parent 33e5f3d85d
commit 9ce56dd40c
13 changed files with 404 additions and 48 deletions

View File

@@ -1597,12 +1597,39 @@ class ModelScanner:
"""Get model information by name"""
try:
cache = await self.get_cached_data()
name_normalized = name.replace("\\", "/")
name_no_ext = name_normalized
for ext in (".safetensors", ".ckpt", ".pt", ".bin"):
if name_no_ext.lower().endswith(ext):
name_no_ext = name_no_ext[: -len(ext)]
break
has_path = "/" in name_no_ext
basename = os.path.basename(name_no_ext) if has_path else name_no_ext
best_fallback = None
for model in cache.raw_data:
if model.get("file_name") == name:
file_name = model.get("file_name", "")
folder = model.get("folder", "")
file_name_no_ext = file_name
for ext in (".safetensors", ".ckpt", ".pt", ".bin"):
if file_name_no_ext.lower().endswith(ext):
file_name_no_ext = file_name_no_ext[: -len(ext)]
break
path_name = f"{folder}/{file_name_no_ext}".replace("\\", "/") if folder else file_name_no_ext
if name_no_ext == file_name_no_ext or name_no_ext == path_name:
return model
return None
if has_path and file_name_no_ext == basename:
if folder and name_no_ext.startswith(folder.replace("\\", "/") + "/"):
best_fallback = model
elif best_fallback is None:
best_fallback = model
return best_fallback
except Exception as e:
logger.error(f"Error getting model info by name: {e}", exc_info=True)
return None