diff --git a/py/services/settings_manager.py b/py/services/settings_manager.py index 467959fb..2ececf6e 100644 --- a/py/services/settings_manager.py +++ b/py/services/settings_manager.py @@ -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, diff --git a/settings.json.example b/settings.json.example index b5ea2e0a..673aa76d 100644 --- a/settings.json.example +++ b/settings.json.example @@ -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" ] } } diff --git a/tests/services/test_settings_manager.py b/tests/services/test_settings_manager.py index 71447baf..1643f24f 100644 --- a/tests/services/test_settings_manager.py +++ b/tests/services/test_settings_manager.py @@ -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")