feat(settings): improve library bootstrap logic and path handling

- Normalize folder paths before library bootstrap to ensure consistent structure
- Add _has_configured_paths helper to detect valid folder configurations
- Enhance bootstrap logic to handle edge cases with single libraries and empty paths
- Update library payload construction to use normalized paths
- Add example settings file changes to demonstrate new path structure

The changes ensure more robust library initialization when folder paths are configured at the top level but not properly propagated to individual libraries.
This commit is contained in:
Will Miao
2025-10-26 23:40:07 +08:00
parent 2d4bc47746
commit e0332571da
3 changed files with 111 additions and 8 deletions

View File

@@ -217,10 +217,32 @@ class SettingsManager:
active_name = self.settings.get("active_library")
initial_bootstrap = self._bootstrap_reason == "missing"
if not isinstance(libraries, dict) or not libraries:
raw_top_level_paths = self.settings.get("folder_paths", {})
normalized_top_level_paths: Dict[str, List[str]] = {}
if isinstance(raw_top_level_paths, Mapping):
normalized_top_level_paths = self._normalize_folder_paths(raw_top_level_paths)
if normalized_top_level_paths != raw_top_level_paths:
self.settings["folder_paths"] = copy.deepcopy(normalized_top_level_paths)
top_level_has_paths = self._has_configured_paths(normalized_top_level_paths)
needs_library_bootstrap = not isinstance(libraries, dict) or not libraries
if (
not needs_library_bootstrap
and top_level_has_paths
and len(libraries) == 1
):
only_library_payload = next(iter(libraries.values()))
if isinstance(only_library_payload, Mapping):
folder_payload = only_library_payload.get("folder_paths")
if not self._has_configured_paths(folder_payload):
needs_library_bootstrap = True
if needs_library_bootstrap:
library_name = active_name or "default"
library_payload = self._build_library_payload(
folder_paths=self.settings.get("folder_paths", {}),
folder_paths=normalized_top_level_paths,
default_lora_root=self.settings.get("default_lora_root", ""),
default_checkpoint_root=self.settings.get("default_checkpoint_root", ""),
default_embedding_root=self.settings.get("default_embedding_root", ""),
@@ -233,14 +255,36 @@ class SettingsManager:
self._save_settings()
return
seed_library_name: Optional[str] = None
if top_level_has_paths and isinstance(libraries, dict):
target_name: Optional[str] = None
if active_name and active_name in libraries:
target_name = active_name
elif len(libraries) == 1:
target_name = next(iter(libraries.keys()))
if target_name:
candidate_payload = libraries.get(target_name)
if isinstance(candidate_payload, Mapping) and not self._has_configured_paths(candidate_payload.get("folder_paths")):
seed_library_name = target_name
sanitized_libraries: Dict[str, Dict[str, Any]] = {}
changed = False
for name, data in libraries.items():
if not isinstance(data, dict):
data = {}
changed = True
candidate_folder_paths = data.get("folder_paths")
if (
seed_library_name == name
and not self._has_configured_paths(candidate_folder_paths)
and top_level_has_paths
):
candidate_folder_paths = normalized_top_level_paths
payload = self._build_library_payload(
folder_paths=data.get("folder_paths"),
folder_paths=candidate_folder_paths,
default_lora_root=data.get("default_lora_root"),
default_checkpoint_root=data.get("default_checkpoint_root"),
default_embedding_root=data.get("default_embedding_root"),
@@ -352,6 +396,25 @@ class SettingsManager:
normalized[key] = cleaned
return normalized
def _has_configured_paths(self, folder_paths: Any) -> bool:
if not isinstance(folder_paths, Mapping):
return False
for values in folder_paths.values():
if isinstance(values, str):
candidate_values = [values]
else:
try:
candidate_values = list(values) # type: ignore[arg-type]
except TypeError:
continue
for path in candidate_values:
if isinstance(path, str) and path.strip():
return True
return False
def _validate_folder_paths(
self,
library_name: str,

View File

@@ -1,16 +1,17 @@
{
"_note": "LoRA Manager builds the detailed library registry automatically at runtime.",
"language": "en",
"civitai_api_key": "your_civitai_api_key_here",
"folder_paths": {
"loras": [
"C:/path/to/your/loras_folder"
"C:/path/to/your/loras_folder",
"C:/path/to/another/loras_folder"
],
"checkpoints": [
"C:/path/to/your/checkpoints_folder"
"C:/path/to/your/checkpoints_folder",
"C:/path/to/another/checkpoints_folder"
],
"embeddings": [
"C:/path/to/your/embeddings_folder"
"C:/path/to/your/embeddings_folder",
"C:/path/to/another/embeddings_folder"
]
}
}

View File

@@ -53,6 +53,45 @@ def test_initial_save_persists_minimal_template(tmp_path, monkeypatch):
assert manager.get_libraries()["default"]["folder_paths"]["loras"] == ["/loras"]
def test_existing_folder_paths_seed_default_library(tmp_path, monkeypatch):
monkeypatch.setenv("LORA_MANAGER_STANDALONE", "1")
lora_dir = tmp_path / "loras"
checkpoint_dir = tmp_path / "checkpoints"
unet_dir = tmp_path / "unet"
diffusion_dir = tmp_path / "diffusion_models"
embedding_dir = tmp_path / "embeddings"
for directory in (lora_dir, checkpoint_dir, unet_dir, diffusion_dir, embedding_dir):
directory.mkdir()
initial = {
"folder_paths": {
"loras": [str(lora_dir)],
"checkpoints": [str(checkpoint_dir)],
"unet": [str(diffusion_dir), str(unet_dir)],
"embeddings": [str(embedding_dir)],
}
}
manager = _create_manager_with_settings(tmp_path, monkeypatch, initial)
stored_paths = manager.get("folder_paths")
assert stored_paths["loras"] == [str(lora_dir)]
assert stored_paths["checkpoints"] == [str(checkpoint_dir)]
assert stored_paths["unet"] == [str(diffusion_dir), str(unet_dir)]
assert stored_paths["embeddings"] == [str(embedding_dir)]
libraries = manager.get_libraries()
assert "default" in libraries
assert libraries["default"]["folder_paths"]["loras"] == [str(lora_dir)]
assert libraries["default"]["folder_paths"]["checkpoints"] == [str(checkpoint_dir)]
assert libraries["default"]["folder_paths"]["unet"] == [str(diffusion_dir), str(unet_dir)]
assert libraries["default"]["folder_paths"]["embeddings"] == [str(embedding_dir)]
assert manager.get_startup_messages() == []
def test_environment_variable_overrides_settings(tmp_path, monkeypatch):
monkeypatch.setattr(SettingsManager, "_save_settings", lambda self: None)
monkeypatch.setenv("CIVITAI_API_KEY", "secret")