diff --git a/docs/architecture/multi_library_design.md b/docs/architecture/multi_library_design.md index 3e222f0e..8022df5a 100644 --- a/docs/architecture/multi_library_design.md +++ b/docs/architecture/multi_library_design.md @@ -1,5 +1,10 @@ # Multi-Library Management for Standalone Mode +> **Deprecated:** Multi-library support is deprecated and will be removed in an +> upcoming release. Multiple folder roots within a single library +> (`folder_paths` / `extra_folder_paths`) remain fully supported. This document +> is kept for historical reference only. + ## Requirements Summary - **Independent libraries**: In standalone mode, users can maintain multiple libraries, where each library represents a distinct set of model folders (LoRAs, checkpoints, embeddings, etc.). Only one library is active at any given time, but users need a fast way to switch between them. - **Library-specific settings**: The fields that vary per library are `folder_paths`, `default_lora_root`, `default_checkpoint_root`, and `default_embedding_root` inside `settings.json`. diff --git a/docs/library-switching.md b/docs/library-switching.md index b75db4f7..824ca946 100644 --- a/docs/library-switching.md +++ b/docs/library-switching.md @@ -1,5 +1,10 @@ # Library Switching and Preview Routes +> **Deprecated:** Multi-library support (and therefore library switching) is +> deprecated and will be removed in an upcoming release. The dynamic preview +> route described here will remain — it also serves single-library +> installations. + Library switching no longer requires restarting the backend. The preview thumbnails shown in the UI are now served through a dynamic endpoint that resolves files against the folders registered for the active library at request diff --git a/locales/en.json b/locales/en.json index 040a5f89..0db5e294 100644 --- a/locales/en.json +++ b/locales/en.json @@ -555,8 +555,8 @@ "cardBlurAmountHelp": "Adjust the blur intensity of the header and footer overlays on model and recipe cards (0 = no blur, 20 = maximum blur)." }, "folderSettings": { - "activeLibrary": "Active Library", - "activeLibraryHelp": "Switch between configured libraries to update default folders. Changing the selection reloads the page.", + "activeLibrary": "Active Library (Deprecated)", + "activeLibraryHelp": "Switch between configured libraries to update default folders. Changing the selection reloads the page. Deprecated: multi-library support will be removed in an upcoming release. Multiple folder roots within a single library remain fully supported.", "loadingLibraries": "Loading libraries...", "noLibraries": "No libraries configured", "defaultLoraRoot": "LoRA Root", diff --git a/py/services/settings_manager.py b/py/services/settings_manager.py index e06910af..21b71a12 100644 --- a/py/services/settings_manager.py +++ b/py/services/settings_manager.py @@ -583,6 +583,39 @@ class SettingsManager: if changed and initial_bootstrap: self._needs_initial_save = True + self._warn_if_multi_library() + + def _warn_if_multi_library(self) -> None: + """Surface a deprecation notice when more than one library is configured.""" + libraries = self.settings.get("libraries", {}) + if not isinstance(libraries, Mapping) or len(libraries) <= 1: + return + + names = ", ".join(sorted(str(name) for name in libraries)) + logger.warning( + "Multi-library support is deprecated and will be removed in an " + "upcoming release. Detected %d libraries in settings.json (%s). " + "Multiple folder roots within a single library remain fully " + "supported; please consolidate your libraries into one.", + len(libraries), + names, + ) + self._add_startup_message( + code="multi-library-deprecated", + title="Multi-library support is deprecated", + message=( + f"This installation has {len(libraries)} libraries configured in " + f"settings.json ({names}). Multi-library support is deprecated and " + "will be removed in an upcoming release; at that point all " + "libraries' folder paths will be merged into a single library. " + "Multiple folder roots within a single library remain fully " + "supported, so consider consolidating into one library now." + ), + severity="warning", + actions=self._default_settings_actions(), + dismissible=True, + ) + def _sync_active_library_to_root(self, *, save: bool = False) -> None: """Update top-level folder path settings to mirror the active library.""" libraries = self.settings.get("libraries", {}) @@ -2382,34 +2415,6 @@ class SettingsManager: if was_active: self._notify_library_change(self.settings["active_library"]) - def update_active_library_paths( - self, - folder_paths: Mapping[str, Iterable[str]], - *, - extra_folder_paths: Optional[Mapping[str, Iterable[str]]] = None, - default_lora_root: Optional[str] = None, - default_checkpoint_root: Optional[str] = None, - default_unet_root: Optional[str] = None, - default_embedding_root: Optional[str] = None, - default_other_roots: Optional[Mapping[str, str]] = None, - recipes_path: Optional[str] = None, - ) -> None: - """Update folder paths for the active library.""" - - active_name = self.get_active_library_name() - self.upsert_library( - active_name, - folder_paths=folder_paths, - extra_folder_paths=extra_folder_paths, - default_lora_root=default_lora_root, - default_checkpoint_root=default_checkpoint_root, - default_unet_root=default_unet_root, - default_embedding_root=default_embedding_root, - default_other_roots=default_other_roots, - recipes_path=recipes_path, - activate=True, - ) - def _notify_library_change(self, library_name: str) -> None: """Notify dependent services that the active library changed.""" libraries = self.settings.get("libraries", {}) diff --git a/tests/services/test_settings_manager.py b/tests/services/test_settings_manager.py index 4bdbd44d..afa16004 100644 --- a/tests/services/test_settings_manager.py +++ b/tests/services/test_settings_manager.py @@ -1,6 +1,7 @@ import asyncio import copy import json +import logging import os from concurrent.futures import Future @@ -133,6 +134,58 @@ def test_existing_folder_paths_seed_default_library(tmp_path, monkeypatch): assert manager.get_startup_messages() == [] +def test_multi_library_configuration_triggers_deprecation_warning( + tmp_path, monkeypatch, caplog +): + initial = { + "libraries": { + "default": {"folder_paths": {"loras": ["/loras-a"]}}, + "archive": {"folder_paths": {"loras": ["/loras-b"]}}, + }, + "active_library": "default", + } + + with caplog.at_level(logging.WARNING, logger="py.services.settings_manager"): + manager = _create_manager_with_settings(tmp_path, monkeypatch, initial) + + assert any( + "Multi-library support is deprecated" in record.getMessage() + for record in caplog.records + ) + deprecation = [ + message + for message in manager.get_startup_messages() + if message["code"] == "multi-library-deprecated" + ] + assert len(deprecation) == 1 + assert deprecation[0]["severity"] == "warning" + assert deprecation[0]["dismissible"] is True + assert "archive" in deprecation[0]["message"] + + +def test_single_library_configuration_has_no_deprecation_warning( + tmp_path, monkeypatch, caplog +): + initial = { + "libraries": { + "default": {"folder_paths": {"loras": ["/loras-a"]}}, + }, + "active_library": "default", + } + + with caplog.at_level(logging.WARNING, logger="py.services.settings_manager"): + manager = _create_manager_with_settings(tmp_path, monkeypatch, initial) + + assert not any( + "Multi-library support is deprecated" in record.getMessage() + for record in caplog.records + ) + assert not any( + message["code"] == "multi-library-deprecated" + for message in 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")