fix(sidebar): keep the recipes storage dir out of folder management

The default recipes dir (<first lora root>/recipes) is auto-created by the
recipe scanner and recorded by the model-root walks as an empty folder, so
it showed up in the folder sidebar — and since recipe files are not model
weight files, the folder delete endpoint's model_count check let a
right-click delete wipe the whole recipe library via shutil.rmtree.

- scan walks (full scan, reconcile, all-folders backfill) no longer descend
  into the effective recipes dir, and get_all_folders() filters it out of
  snapshots persisted before the exclusion existed
- delete_folder/rename_folder refuse the recipes dir and any ancestor of
  it with code "protected"
- the delete modal renders a protected state (confirm hidden, per-row
  checkbox disabled) instead of a deletable 'no models' prediction
- RecipeScanner.recipes_dir now shares the side-effect-free resolver in
  py/utils/recipes_paths.py
This commit is contained in:
Will Miao
2026-10-07 19:38:28 +08:00
parent f4d0228d6b
commit 1805519f4f
9 changed files with 397 additions and 26 deletions
+3 -15
View File
@@ -2676,23 +2676,11 @@ class RecipeScanner:
@property
def recipes_dir(self) -> str:
"""Get path to recipes directory"""
from .settings_manager import get_settings_manager
from ..utils.recipes_paths import get_effective_recipes_dir
custom_recipes_dir = get_settings_manager().get("recipes_path", "")
if isinstance(custom_recipes_dir, str) and custom_recipes_dir.strip():
recipes_dir = os.path.abspath(
os.path.normpath(os.path.expanduser(custom_recipes_dir.strip()))
)
recipes_dir = get_effective_recipes_dir()
if recipes_dir:
os.makedirs(recipes_dir, exist_ok=True)
return recipes_dir
if not config.loras_roots:
return ""
# config.loras_roots already sorted case-insensitively, use the first one
recipes_dir = os.path.join(config.loras_roots[0], "recipes")
os.makedirs(recipes_dir, exist_ok=True)
return recipes_dir
async def get_cached_data(self, force_refresh: bool = False) -> RecipeCache: