mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-21 11:11:26 -03:00
feat(backend): gate Other Models behind opt-in management toggles
Other Models management is now opt-in: enable_other_models (default false) plus the enabled_other_sub_types allow-list replace the unreleased additive enabled_other_folders key. - config._get_enabled_other_folder_keys() is the single scan gate; a new refresh_other_roots() rebuilds roots and preview roots on toggle. - ModelScanner gains a _should_keep_cached_entry() hydration hook and on_library_changed(reconcile=...) so switching a sub_type off drops its entries (and hash/autov3 rows) at load time and switching it on rescans. - OtherScanner filters location-derived entries accordingly. - Other routes reject every other type while off (or a disabled sub_type) and expose an "other_disabled" page flag; download routing returns a disabled marker instead of guessing; the download manager refuses other-type downloads and default-path routing for switched-off sub_types. - Doctor / init-status / refresh-all skip the other scanner while off; the scanner stays registered so staged pending-deletes still merge. - Tests updated with explicit opt-in fixtures plus new gating coverage.
This commit is contained in:
@@ -56,11 +56,38 @@ class DownloadRoutingHandler:
|
||||
)
|
||||
|
||||
if model_type.lower() in VALID_OTHER_CIVITAI_TYPES:
|
||||
from ...services.settings_manager import get_settings_manager
|
||||
|
||||
settings = get_settings_manager()
|
||||
if not settings.is_other_models_enabled():
|
||||
# Opt-in feature is off: never auto-route, the UI falls back to
|
||||
# manual folder selection and the download manager rejects it.
|
||||
return web.json_response(
|
||||
{
|
||||
"success": True,
|
||||
"root_kind": "other",
|
||||
"sub_type": None,
|
||||
"disabled": True,
|
||||
"reason": "other_models_disabled",
|
||||
}
|
||||
)
|
||||
|
||||
sub_type = resolve_other_download_sub_type(
|
||||
model_type,
|
||||
file_types=(str(t) for t in file_types),
|
||||
selected_file_type=selected_file_type,
|
||||
)
|
||||
if sub_type and not settings.is_other_sub_type_enabled(sub_type):
|
||||
return web.json_response(
|
||||
{
|
||||
"success": True,
|
||||
"root_kind": "other",
|
||||
"sub_type": None,
|
||||
"disabled": True,
|
||||
"reason": "other_sub_type_disabled",
|
||||
"requested_sub_type": sub_type,
|
||||
}
|
||||
)
|
||||
return web.json_response(
|
||||
{
|
||||
"success": True,
|
||||
|
||||
@@ -663,6 +663,17 @@ class HealthCheckHandler:
|
||||
"recipe": ServiceRegistry.get_recipe_scanner,
|
||||
}
|
||||
|
||||
def _active_scanner_getters(
|
||||
self,
|
||||
) -> Mapping[str, Callable[[], Awaitable[Any]]]:
|
||||
"""Drop the opt-in other scanner while Other Models is disabled."""
|
||||
getters = self._scanner_getters
|
||||
if "other" not in getters:
|
||||
return getters
|
||||
if get_settings_manager().is_other_models_enabled():
|
||||
return getters
|
||||
return {name: getter for name, getter in getters.items() if name != "other"}
|
||||
|
||||
async def health_check(self, request: web.Request) -> web.Response:
|
||||
return web.json_response({"status": "ok"})
|
||||
|
||||
@@ -674,7 +685,7 @@ class HealthCheckHandler:
|
||||
page accepts the update and only reloads once all scanners are done.
|
||||
"""
|
||||
pending: list[str] = []
|
||||
for name, getter in self._scanner_getters.items():
|
||||
for name, getter in self._active_scanner_getters().items():
|
||||
try:
|
||||
scanner = await getter()
|
||||
except Exception:
|
||||
@@ -764,6 +775,14 @@ class DoctorHandler:
|
||||
)
|
||||
self._app_version_getter = app_version_getter
|
||||
|
||||
def _active_scanner_factories(
|
||||
self,
|
||||
) -> Sequence[tuple[str, str, Callable[[], Awaitable[Any]]]]:
|
||||
"""Drop the opt-in other scanner while Other Models is disabled."""
|
||||
if self._settings.is_other_models_enabled():
|
||||
return self._scanner_factories
|
||||
return tuple(entry for entry in self._scanner_factories if entry[0] != "other")
|
||||
|
||||
async def get_doctor_diagnostics(self, request: web.Request) -> web.Response:
|
||||
try:
|
||||
client_version = (request.query.get("clientVersion") or "").strip()
|
||||
@@ -811,7 +830,7 @@ class DoctorHandler:
|
||||
repaired: list[dict[str, Any]] = []
|
||||
failures: list[dict[str, str]] = []
|
||||
|
||||
for model_type, label, factory in self._scanner_factories:
|
||||
for model_type, label, factory in self._active_scanner_factories():
|
||||
try:
|
||||
scanner = await factory()
|
||||
await scanner.get_cached_data(force_refresh=True, rebuild_cache=True)
|
||||
@@ -843,7 +862,7 @@ class DoctorHandler:
|
||||
renamed: list[dict[str, Any]] = []
|
||||
|
||||
try:
|
||||
for model_type, label, factory in self._scanner_factories:
|
||||
for model_type, label, factory in self._active_scanner_factories():
|
||||
try:
|
||||
scanner = await factory()
|
||||
hash_index = getattr(scanner, "_hash_index", None)
|
||||
@@ -1075,7 +1094,7 @@ class DoctorHandler:
|
||||
overall_status = "ok"
|
||||
summary = "All model caches look healthy."
|
||||
|
||||
for model_type, label, factory in self._scanner_factories:
|
||||
for model_type, label, factory in self._active_scanner_factories():
|
||||
try:
|
||||
scanner = await factory()
|
||||
persisted = None
|
||||
@@ -1160,7 +1179,7 @@ class DoctorHandler:
|
||||
total_conflict_groups = 0
|
||||
total_conflict_files = 0
|
||||
|
||||
for model_type, label, factory in self._scanner_factories:
|
||||
for model_type, label, factory in self._active_scanner_factories():
|
||||
# Duplicate filename detection targets LoRAs which use basename-only
|
||||
# syntax (<lora:name:strength>). Checkpoints/embeddings reference
|
||||
# models via relative paths with extensions, so conflicts there would
|
||||
@@ -2797,6 +2816,8 @@ class ModelLibraryHandler:
|
||||
|
||||
# Acquire the other scanner lazily so adapters without it only
|
||||
# fail when the payload actually contains other-type models.
|
||||
# While the opt-in feature is off the scanner still exists (its
|
||||
# cache is empty), so other types simply report inLibrary=False.
|
||||
needs_other_scanner = any(
|
||||
isinstance(model, dict)
|
||||
and str(model.get("type", "")).lower() in other_type_aliases
|
||||
|
||||
@@ -90,6 +90,7 @@ class ModelPageView:
|
||||
settings_service: SettingsManager,
|
||||
server_i18n,
|
||||
logger: logging.Logger,
|
||||
page_context_provider: Callable[[web.Request], Dict[str, Any]] | None = None,
|
||||
) -> None:
|
||||
self._template_env = template_env
|
||||
self._template_name = template_name
|
||||
@@ -97,6 +98,7 @@ class ModelPageView:
|
||||
self._settings = settings_service
|
||||
self._server_i18n = server_i18n
|
||||
self._logger = logger
|
||||
self._page_context_provider = page_context_provider
|
||||
|
||||
def _load_supporters(self) -> dict[str, Any]:
|
||||
"""Load supporters data from JSON file."""
|
||||
@@ -210,6 +212,16 @@ class ModelPageView:
|
||||
self._logger.error("Error loading cache data: %s", cache_error)
|
||||
template_context["is_initializing"] = True
|
||||
|
||||
if self._page_context_provider is not None:
|
||||
try:
|
||||
extra_context = self._page_context_provider(request)
|
||||
if isinstance(extra_context, dict):
|
||||
template_context.update(extra_context)
|
||||
except Exception as context_error: # pragma: no cover - logging path
|
||||
self._logger.error(
|
||||
"Error building page context: %s", context_error
|
||||
)
|
||||
|
||||
rendered = self._template_env.get_template(self._template_name).render(
|
||||
**template_context
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user