mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-20 18:51:26 -03:00
feat(settings): editable model library paths for standalone mode
Standalone users previously had to hand-edit settings.json to configure primary folder_paths. Add a standalone-only Model Paths section to the settings modal: - Backend exposes standalone_mode, folder_paths (with template placeholder values filtered out) and a data-driven folder_path_schema derived from OTHER_MODEL_FOLDER_SUBTYPES via GET /api/lm/settings - The new section renders multi-path editors per model type from the schema, with inline enable_other_models / sub-type controls so other model types are configured without leaving the tab - Persistent restart-required cues after a save: nav dot, inline notice and a global banner (unique id per change so dismissals don't mute future reminders) - The missing-model-paths startup banner and the Other Models no-paths empty state now deep-link into the new section instead of pointing at settings.json
This commit is contained in:
@@ -54,6 +54,7 @@ from ...utils.constants import (
|
||||
SUPPORTED_MEDIA_EXTENSIONS,
|
||||
VALID_LORA_TYPES,
|
||||
VALID_OTHER_CIVITAI_TYPES,
|
||||
folder_path_schema,
|
||||
)
|
||||
from .model_source_handlers import ModelSourceHandler
|
||||
from .agent_handlers import AgentHandler
|
||||
@@ -1580,6 +1581,30 @@ class SettingsHandler:
|
||||
availability_error,
|
||||
)
|
||||
response_data["other_models_paths_available"] = None
|
||||
standalone_mode = os.environ.get("LORA_MANAGER_STANDALONE", "0") == "1"
|
||||
response_data["standalone_mode"] = standalone_mode
|
||||
if standalone_mode:
|
||||
# Standalone reads its model roots exclusively from
|
||||
# settings.json, so the Model Paths settings UI needs the
|
||||
# current values plus the editable-key schema. In plugin mode
|
||||
# the paths come from the ComfyUI host and stay hidden.
|
||||
folder_paths = self._settings.get("folder_paths") or {}
|
||||
# A fresh install is seeded from settings.json.example, whose
|
||||
# folder_paths are documentation placeholders — hide them so
|
||||
# the UI starts with empty editors instead of fake paths.
|
||||
get_placeholders = getattr(
|
||||
self._settings, "get_template_folder_path_placeholders", None
|
||||
)
|
||||
placeholders = get_placeholders() if get_placeholders else set()
|
||||
if placeholders:
|
||||
folder_paths = {
|
||||
key: [p for p in paths if p not in placeholders]
|
||||
if isinstance(paths, list)
|
||||
else paths
|
||||
for key, paths in folder_paths.items()
|
||||
}
|
||||
response_data["folder_paths"] = folder_paths
|
||||
response_data["folder_path_schema"] = folder_path_schema()
|
||||
settings_file = getattr(self._settings, "settings_file", None)
|
||||
if settings_file:
|
||||
response_data["settings_file"] = settings_file
|
||||
|
||||
@@ -89,8 +89,8 @@ class OtherRoutes(BaseModelRoutes):
|
||||
"standalone_mode": standalone_mode,
|
||||
}
|
||||
if standalone_mode:
|
||||
# The settings UI cannot edit primary folder_paths, so the empty
|
||||
# state must point at the actual file the user has to edit.
|
||||
# The empty state points at the Model Paths settings section and
|
||||
# shows the settings.json path as a fallback reference.
|
||||
context["settings_file"] = getattr(self._settings, "settings_file", "") or ""
|
||||
return context
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ from typing import (
|
||||
Mapping,
|
||||
Optional,
|
||||
Sequence,
|
||||
Set,
|
||||
Tuple,
|
||||
)
|
||||
|
||||
@@ -308,6 +309,29 @@ class SettingsManager:
|
||||
|
||||
return payload == template
|
||||
|
||||
def get_template_folder_path_placeholders(self) -> Set[str]:
|
||||
"""Placeholder folder_paths values shipped in settings.json.example.
|
||||
|
||||
A fresh standalone install is seeded from the template, so its
|
||||
documentation-only placeholder paths end up in the live settings
|
||||
file. The Model Paths settings UI hides them; the first real save
|
||||
overwrites them via ``set("folder_paths")``.
|
||||
"""
|
||||
|
||||
template = self._read_template_payload()
|
||||
if not template:
|
||||
return set()
|
||||
|
||||
folder_paths = template.get("folder_paths")
|
||||
if not isinstance(folder_paths, Mapping):
|
||||
return set()
|
||||
|
||||
placeholders: Set[str] = set()
|
||||
for value in folder_paths.values():
|
||||
paths = value if isinstance(value, list) else [value]
|
||||
placeholders.update(p for p in paths if isinstance(p, str) and p)
|
||||
return placeholders
|
||||
|
||||
def _merge_template_with_defaults(
|
||||
self, defaults: Dict[str, Any], template: Mapping[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
@@ -1219,19 +1243,27 @@ class SettingsManager:
|
||||
if self._bootstrap_reason == "missing":
|
||||
message = (
|
||||
"LoRA Manager created a default settings.json because no configuration was found. "
|
||||
"Edit settings.json to add your model directories so library scanning can run."
|
||||
"Open Settings → Model Paths to add your model directories so library scanning can run."
|
||||
)
|
||||
else:
|
||||
message = (
|
||||
"LoRA Manager could not locate any configured model directories. "
|
||||
"Edit settings.json to add your model folders so library scanning can run."
|
||||
"Open Settings → Model Paths to add your model folders so library scanning can run."
|
||||
)
|
||||
self._add_startup_message(
|
||||
code="missing-model-paths",
|
||||
title="Model folders need setup",
|
||||
message=message,
|
||||
severity="warning",
|
||||
actions=self._default_settings_actions(),
|
||||
actions=[
|
||||
{
|
||||
"action": "open-model-paths-settings",
|
||||
"label": "Configure model folders",
|
||||
"type": "primary",
|
||||
"icon": "fas fa-cog",
|
||||
},
|
||||
*self._default_settings_actions(),
|
||||
],
|
||||
dismissible=False,
|
||||
)
|
||||
|
||||
|
||||
@@ -127,6 +127,29 @@ def other_sub_type_folder_keys() -> Dict[str, List[str]]:
|
||||
# Precomputed inverse of OTHER_MODEL_FOLDER_SUBTYPES, keeping the table order.
|
||||
OTHER_SUB_TYPE_FOLDER_KEYS: Dict[str, List[str]] = other_sub_type_folder_keys()
|
||||
|
||||
# Core folder_paths keys every LoRA Manager installation understands.
|
||||
CORE_FOLDER_PATH_KEYS: List[str] = ["loras", "checkpoints", "unet", "embeddings"]
|
||||
|
||||
|
||||
def folder_path_schema() -> List[Dict[str, Any]]:
|
||||
"""Ordered schema describing the editable folder_paths keys.
|
||||
|
||||
Drives the standalone-only Model Paths settings UI: the frontend renders
|
||||
one multi-path editor per entry and resolves labels via the
|
||||
``settings.modelPaths.folderKeys.<key>`` i18n keys, so adding a new model
|
||||
category is a constants + locale change only. ``sub_type`` lets the UI
|
||||
hide editors for other-model categories the user has not enabled.
|
||||
"""
|
||||
schema: List[Dict[str, Any]] = [
|
||||
{"key": key, "category": "core", "sub_type": None}
|
||||
for key in CORE_FOLDER_PATH_KEYS
|
||||
]
|
||||
schema.extend(
|
||||
{"key": folder_key, "category": "other", "sub_type": sub_type}
|
||||
for folder_key, sub_type in OTHER_MODEL_FOLDER_SUBTYPES.items()
|
||||
)
|
||||
return schema
|
||||
|
||||
|
||||
def normalize_other_sub_types(value: Any) -> List[str]:
|
||||
"""Normalize a stored/requested enabled-sub_type list.
|
||||
|
||||
Reference in New Issue
Block a user