Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,8 @@ def download_models(default_model, previous_default_models, checkpoint_downloads
load_file_from_url(url=url, model_dir=config.path_embeddings, file_name=file_name)

for file_name, url in lora_downloads.items():
model_dir = os.path.dirname(get_file_from_folder_list(file_name, config.paths_loras))
common_paths = [os.path.join(path, 'common') for path in config.paths_loras]
model_dir = os.path.dirname(get_file_from_folder_list(file_name, common_paths))
load_file_from_url(url=url, model_dir=model_dir, file_name=file_name)
Comment on lines +147 to 149
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

❓ Verification inconclusive

Hard-coding the "common" sub-folder scatters path logic and risks duplication

The same "common" suffix is now appended here, in modules/model_structure.create_model_structure(), and (implicitly) in several preset JSON files. When the convention changes again (e.g. per-user folders, a different tier like "public"), every call-site must be audited.

-        common_paths = [os.path.join(path, 'common') for path in config.paths_loras]
+        # keep the “where are my LoRAs?” rule in one place
+        common_paths = config.get_common_lora_paths()

Consider adding a helper such as config.get_common_lora_paths() (or re-using one if it already exists) so the rule lives in exactly one module.


Extract “common” path logic into a single helper

To avoid scattering the "common" suffix in multiple places, centralize it in one method on config.

• In launch.py (lines 147–149) and in modules/model_structure.py you’re appending "common" to each entry in config.paths_loras.
• Preset JSON files also hard‐code the "common" folder.

Proposed changes:

  1. Add to your config class (e.g. in config.py):
    def get_common_lora_paths(self) -> list[str]:
        """Return each LORA root path with the 'common' subfolder appended."""
        return [os.path.join(path, "common") for path in self.paths_loras]
  1. Replace both call‐sites:
-   common_paths = [os.path.join(path, 'common') for path in config.paths_loras]
+   common_paths = config.get_common_lora_paths()

This keeps the convention in one place and prevents divergence when folder tiers change.

🤖 Prompt for AI Agents
In launch.py around lines 147 to 149, the "common" subfolder is hard-coded when
constructing paths, which duplicates logic also found in
modules/model_structure.py and preset JSON files. To fix this, add a helper
method get_common_lora_paths() to the config class that returns paths_loras
entries with "common" appended, then replace the list comprehension in launch.py
and the similar code in model_structure.py with calls to this new helper. This
centralizes the "common" path logic and prevents inconsistencies.


for file_name, url in vae_downloads.items():
Expand Down
2 changes: 2 additions & 0 deletions ldm_patched/modules/args_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ def __call__(self, parser, namespace, values, option_string=None):
parser.add_argument("--web-upload-size", type=float, default=100)
parser.add_argument("--hf-mirror", type=str, default=None)

parser.add_argument("--path-loras", type=str, default=None)

parser.add_argument("--external-working-path", type=str, default=None, metavar="PATH", nargs='+', action='append')
parser.add_argument("--output-path", type=str, default=None)
parser.add_argument("--temp-path", type=str, default=None)
Expand Down
33 changes: 33 additions & 0 deletions ldm_patched/modules/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import numpy as np
from PIL import Image

import modules

def load_torch_file(ckpt, safe_load=False, device=None):
if device is None:
device = torch.device("cpu")
Expand Down Expand Up @@ -459,3 +461,34 @@ def update_absolute(self, value, total=None, preview=None):

def update(self, value):
self.update_absolute(self.current + value)

def move_lora(filepath: str, username: str) -> bool:
"""Takes a valid filepath as an input (supposed to be a LoRA file) and a username
and moves the file to the user's LoRA directory.

Returns True if the copy succeeded, False otherwise.
"""
import shutil
import os
from pathlib import Path

filepath = Path(filepath)
print(f"FILEPATH: {filepath} and USERNAME: {username} and PATHS_LORAS: {modules.config.paths_loras}")
destination_dir = Path(modules.config.paths_loras[0]) / username

print(f"User {username} requested to move {filepath}")

if filepath.is_file():
try:
if not destination_dir.exists():
# Prevent parent creation to be safe with folder creation
destination_dir.mkdir(parents=False, exist_ok=True)

shutil.copy(filepath, destination_dir.as_posix())
os.remove(filepath) # Deleting temp file
return True

except Exception as e:
print("Error while copying uploaded LoRA:", e)

return False
Comment on lines +465 to +494
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Harden move_lora – path-sanitisation, logging, and atomic move

  1. Security: username is concatenated straight into the path.
    move_lora("../../etc", "..") would happily escape the intended root.
    Strip separators and forbid .. components.

  2. Debug prints leak absolute paths & usernames; switch to logging.debug or drop them in production code.

  3. destination_dir.mkdir(parents=False) explodes if the parent folder is absent.
    Use parents=True or explicitly check.

  4. Instead of shutil.copy + os.remove, call shutil.move for atomic semantics and to preserve metadata.

-    destination_dir = Path(modules.config.paths_loras[0]) / username
+    safe_user = Path(username).name          # remove path traversal
+    destination_dir = Path(modules.config.paths_loras[0]) / safe_user

-    print(f"FILEPATH: {filepath} and USERNAME: {username} and PATHS_LORAS: {modules.config.paths_loras}")
-    ...
-            if not destination_dir.exists():
-                # Prevent parent creation to be safe with folder creation
-                destination_dir.mkdir(parents=False, exist_ok=True)  
+    # Avoid verbose printing in production; use logging if required
+    if not destination_dir.exists():
+        destination_dir.mkdir(parents=True, exist_ok=True)

-            shutil.copy(filepath, destination_dir.as_posix())
-            os.remove(filepath)  # Deleting temp file
+            shutil.move(filepath, destination_dir / filepath.name)

These tweaks tighten security and robustness.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def move_lora(filepath: str, username: str) -> bool:
"""Takes a valid filepath as an input (supposed to be a LoRA file) and a username
and moves the file to the user's LoRA directory.
Returns True if the copy succeeded, False otherwise.
"""
import shutil
import os
from pathlib import Path
filepath = Path(filepath)
print(f"FILEPATH: {filepath} and USERNAME: {username} and PATHS_LORAS: {modules.config.paths_loras}")
destination_dir = Path(modules.config.paths_loras[0]) / username
print(f"User {username} requested to move {filepath}")
if filepath.is_file():
try:
if not destination_dir.exists():
# Prevent parent creation to be safe with folder creation
destination_dir.mkdir(parents=False, exist_ok=True)
shutil.copy(filepath, destination_dir.as_posix())
os.remove(filepath) # Deleting temp file
return True
except Exception as e:
print("Error while copying uploaded LoRA:", e)
return False
def move_lora(filepath: str, username: str) -> bool:
"""Takes a valid filepath as an input (supposed to be a LoRA file) and a username
and moves the file to the user's LoRA directory.
Returns True if the copy succeeded, False otherwise.
"""
import shutil
import os
from pathlib import Path
filepath = Path(filepath)
safe_user = Path(username).name # remove path traversal
destination_dir = Path(modules.config.paths_loras[0]) / safe_user
print(f"User {username} requested to move {filepath}")
if filepath.is_file():
try:
# Avoid verbose printing in production; use logging if required
if not destination_dir.exists():
destination_dir.mkdir(parents=True, exist_ok=True)
shutil.move(filepath, destination_dir / filepath.name)
return True
except Exception as e:
print("Error while copying uploaded LoRA:", e)
return False
🤖 Prompt for AI Agents
In ldm_patched/modules/utils.py around lines 465 to 494, improve move_lora by
sanitizing the username to remove path separators and forbid '..' components to
prevent directory traversal. Replace print statements with logging.debug for
better logging practice. Change destination_dir.mkdir to use parents=True to
ensure parent directories are created without error. Finally, replace
shutil.copy followed by os.remove with shutil.move to perform an atomic move
operation that preserves metadata.

2 changes: 0 additions & 2 deletions modules/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ def load_auth_data(filename=None):


def check_auth(user, password):
print(f'user:{user},password:{password}\nauth_dict:{auth_dict}')
print(f'auth_dict[user]:{auth_dict[user]}\n cp:{hashlib.sha256(bytes(password, encoding="utf-8")).hexdigest()}')
if user not in auth_dict:
return False
else:
Expand Down
39 changes: 30 additions & 9 deletions modules/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,26 @@ def get_path_output() -> str:
print('Loading support files...')
return path_output

def get_loras_path() -> str:
global config_dict
path_loras = [f'{path_models_root}/loras/', '../models/loras/']
if args_manager.args.path_loras:
if isinstance(args_manager.args.path_loras, list):
path_loras = args_manager.args.path_loras
else:
path_loras = [args_manager.args.path_loras, args_manager.args.path_loras]
config_dict['path_loras'] = args_manager.args.path_loras
elif 'path_loras' in config_dict and config_dict['path_loras']:
if isinstance(config_dict['path_loras'], list):
path_loras = config_dict['path_loras']
else:
path_loras = [config_dict['path_loras'], config_dict['path_loras']]

path_loras = get_dir_or_set_default('path_loras', path_loras, True)

print(f'Loras will be stored in path: {path_loras}')
return path_loras
Comment on lines +166 to +184
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Fix type-hint, ensure directory creation & align with new “common” sub-folder

Several issues in get_loras_path() can bite later:

  1. The function returns a list[str], not a single str.
  2. Default paths should already include the common/ sub-directory introduced elsewhere – right now they point one level too high.
  3. get_dir_or_set_default() is only invoked in the elif branch, so when the CLI flag is used or when defaults are taken, the folders are never validated / created.
  4. Because of (3) you may hit “file not found” errors when users try first-time uploads.

A compact patch:

-def get_loras_path() -> str:
+def get_loras_path() -> list[str]:
@@
-    path_loras = [f'{path_models_root}/loras/', '../models/loras/']
+    # Default to the new 'common' namespace
+    path_loras = [f'{path_models_root}/loras/common/', '../models/loras/common/']
@@
-        config_dict['path_loras'] = args_manager.args.path_loras
+        config_dict['path_loras'] = args_manager.args.path_loras
@@
-    elif 'path_loras' in config_dict and config_dict['path_loras']:
+    elif 'path_loras' in config_dict and config_dict['path_loras']:
@@
-    
-        path_loras = get_dir_or_set_default('path_loras', path_loras, True)
-    
-    print(f'Loras will be stored in path: {path_loras}')
-    return path_loras
+    # Always validate / create directories
+    path_loras = get_dir_or_set_default('path_loras', path_loras, True, make_directory=True)
+    print(f'Loras will be stored in path: {path_loras}')
+    return path_loras
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def get_loras_path() -> str:
global config_dict
path_loras = [f'{path_models_root}/loras/', '../models/loras/']
if args_manager.args.path_loras:
if isinstance(args_manager.args.path_loras, list):
path_loras = args_manager.args.path_loras
else:
path_loras = [args_manager.args.path_loras, args_manager.args.path_loras]
config_dict['path_loras'] = args_manager.args.path_loras
elif 'path_loras' in config_dict and config_dict['path_loras']:
if isinstance(config_dict['path_loras'], list):
path_loras = config_dict['path_loras']
else:
path_loras = [config_dict['path_loras'], config_dict['path_loras']]
path_loras = get_dir_or_set_default('path_loras', path_loras, True)
print(f'Loras will be stored in path: {path_loras}')
return path_loras
def get_loras_path() -> list[str]:
global config_dict
# Default to the new 'common' namespace
path_loras = [f'{path_models_root}/loras/common/', '../models/loras/common/']
if args_manager.args.path_loras:
if isinstance(args_manager.args.path_loras, list):
path_loras = args_manager.args.path_loras
else:
path_loras = [args_manager.args.path_loras, args_manager.args.path_loras]
config_dict['path_loras'] = args_manager.args.path_loras
elif 'path_loras' in config_dict and config_dict['path_loras']:
if isinstance(config_dict['path_loras'], list):
path_loras = config_dict['path_loras']
else:
path_loras = [config_dict['path_loras'], config_dict['path_loras']]
# Always validate / create directories
path_loras = get_dir_or_set_default('path_loras', path_loras, True, make_directory=True)
print(f'Loras will be stored in path: {path_loras}')
return path_loras
🤖 Prompt for AI Agents
In modules/config.py around lines 166 to 184, the get_loras_path function has an
incorrect return type hint (should be list of strings, not a single string),
default paths missing the "common/" sub-folder, and directory creation is only
triggered in the elif branch. Fix by changing the return type hint to list[str],
update default paths to include the "common/" sub-folder, and ensure
get_dir_or_set_default is called regardless of whether the path comes from CLI
args, config, or defaults to validate and create directories as needed.


def get_path_models_root() -> str:
global config_dict
models_root = 'models'
Expand Down Expand Up @@ -223,7 +243,8 @@ def get_dir_or_set_default(key, default_value, as_array=False, make_directory=Fa

path_models_root = get_path_models_root()
paths_checkpoints = get_dir_or_set_default('path_checkpoints', [f'{path_models_root}/checkpoints/', '../models/checkpoints/'], True)
paths_loras = get_dir_or_set_default('path_loras', [f'{path_models_root}/loras/', '../models/loras/'], True)
paths_loras = get_loras_path()
paths_performance_loras = [f'{path_models_root}/loras/performance/', '../models/loras/performance/']
path_embeddings = get_dir_or_set_default('path_embeddings', f'{path_models_root}/embeddings/')
Comment on lines +247 to 248
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

paths_performance_loras directories are never created

paths_performance_loras is assigned a raw list; unlike every other path list, it bypasses get_dir_or_set_default, so the folder may not exist. Recommend:

-paths_performance_loras = [f'{path_models_root}/loras/performance/', '../models/loras/performance/']
+paths_performance_loras = get_dir_or_set_default(
+    'path_performance_loras',
+    [f'{path_models_root}/loras/performance/', '../models/loras/performance/'],
+    as_array=True,
+    make_directory=True
+)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
paths_performance_loras = [f'{path_models_root}/loras/performance/', '../models/loras/performance/']
path_embeddings = get_dir_or_set_default('path_embeddings', f'{path_models_root}/embeddings/')
paths_performance_loras = get_dir_or_set_default(
'path_performance_loras',
[f'{path_models_root}/loras/performance/', '../models/loras/performance/'],
as_array=True,
make_directory=True
)
path_embeddings = get_dir_or_set_default('path_embeddings', f'{path_models_root}/embeddings/')
🤖 Prompt for AI Agents
In modules/config.py around lines 247 to 248, the variable
paths_performance_loras is assigned a list of directory paths directly without
ensuring these directories exist. To fix this, replace the raw list assignment
with calls to get_dir_or_set_default for each path to guarantee the directories
are created or set to defaults, consistent with other path variables.

path_vae_approx = get_dir_or_set_default('path_vae_approx', f'{path_models_root}/vae_approx/')
path_vae = get_dir_or_set_default('path_vae', f'{path_models_root}/vae/')
Expand Down Expand Up @@ -1002,11 +1023,11 @@ def get_base_model_list(engine='Fooocus', task_method=None):
base_model_list = [f for f in base_model_list if ("hyp8" in f or "hyp16" in f or "flux" in f) and f.endswith("gguf")]
return base_model_list

def update_files(engine='Fooocus', task_method=None):
def update_files(engine='Fooocus', task_method=None, user: str = None):
global modelsinfo, model_filenames, lora_filenames, vae_filenames, wildcard_filenames, available_presets
modelsinfo.refresh_from_path()
model_filenames = get_base_model_list(engine, task_method)
lora_filenames = modelsinfo.get_model_names('loras')
lora_filenames = modelsinfo.get_model_names('loras', user=user)
vae_filenames = modelsinfo.get_model_names('vae')
wildcard_filenames = get_files_from_folder(path_wildcards, ['.txt'])
available_presets = get_presets()
Expand Down Expand Up @@ -1054,28 +1075,28 @@ def downloading_inpaint_models(v):
def downloading_sdxl_lcm_lora():
load_file_from_url(
url='https://huggingface.co/lllyasviel/misc/resolve/main/sdxl_lcm_lora.safetensors',
model_dir=paths_loras[0],
model_dir=paths_performance_loras[0],
file_name=modules.flags.PerformanceLoRA.EXTREME_SPEED.value
)
return modules.flags.PerformanceLoRA.EXTREME_SPEED.value
return f"{modules.flags.PerformanceLoRA.EXTREME_SPEED.value}"


def downloading_sdxl_lightning_lora():
load_file_from_url(
url='https://huggingface.co/mashb1t/misc/resolve/main/sdxl_lightning_4step_lora.safetensors',
model_dir=paths_loras[0],
model_dir=paths_performance_loras[0],
file_name=modules.flags.PerformanceLoRA.LIGHTNING.value
)
return modules.flags.PerformanceLoRA.LIGHTNING.value
return f"{modules.flags.PerformanceLoRA.LIGHTNING.value}"


def downloading_sdxl_hyper_sd_lora():
load_file_from_url(
url='https://huggingface.co/mashb1t/misc/resolve/main/sdxl_hyper_sd_4step_lora.safetensors',
model_dir=paths_loras[0],
model_dir=paths_performance_loras[0],
file_name=modules.flags.PerformanceLoRA.HYPER_SD.value
)
return modules.flags.PerformanceLoRA.HYPER_SD.value
return f"{modules.flags.PerformanceLoRA.HYPER_SD.value}"

Comment on lines 1075 to 1100
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Download helpers now return just the file-name, breaking previous callers

downloading_sdxl_*_lora() used to return the full path; after the change it returns only the bare filename:

return f"{modules.flags.PerformanceLoRA.EXTREME_SPEED.value}"

Down-stream code expecting os.path.join(dir, file) will now fail to locate the file.

Either restore the old behaviour:

-    return f"{modules.flags.PerformanceLoRA.EXTREME_SPEED.value}"
+    return os.path.join(paths_performance_loras[0], modules.flags.PerformanceLoRA.EXTREME_SPEED.value)

and similarly for the other two helpers, or audit every call-site.

Also ensure the parent directory exists (issue noted in a previous comment).


🏁 Script executed:

#!/bin/bash
set -e

echo "🔍 Searching for call‐sites of downloading_sdxl_*_lora()..."
rg -n "downloading_sdxl_(lcm|lightning|hyper)_lora" .

echo
echo "🔍 Locating load_file_from_url definition to inspect its return value..."
rg -n "def load_file_from_url" -A10 .

Length of output: 4600


Restore full file path in download helpers

The downloading_sdxl_*_lora() functions currently discard the path returned by load_file_from_url and only return the bare filename, but downstream code (e.g. in modules/async_worker.py) expects the full file path. Update each helper to return the result of load_file_from_url and ensure its target directory exists:

• File: modules/config.py (around lines 1075–1100)

def downloading_sdxl_lcm_lora():
-   load_file_from_url(
+   return load_file_from_url(
       url='https://huggingface.co/lllyasviel/misc/resolve/main/sdxl_lcm_lora.safetensors',
       model_dir=paths_performance_loras[0],
       file_name=modules.flags.PerformanceLoRA.EXTREME_SPEED.value
   )
-   return f"{modules.flags.PerformanceLoRA.EXTREME_SPEED.value}"

Apply the same pattern to downloading_sdxl_lightning_lora() and downloading_sdxl_hyper_sd_lora().
Also, before calling load_file_from_url, ensure paths_performance_loras[0] exists (e.g., os.makedirs(..., exist_ok=True)).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def downloading_sdxl_lcm_lora():
load_file_from_url(
url='https://huggingface.co/lllyasviel/misc/resolve/main/sdxl_lcm_lora.safetensors',
model_dir=paths_loras[0],
model_dir=paths_performance_loras[0],
file_name=modules.flags.PerformanceLoRA.EXTREME_SPEED.value
)
return modules.flags.PerformanceLoRA.EXTREME_SPEED.value
return f"{modules.flags.PerformanceLoRA.EXTREME_SPEED.value}"
def downloading_sdxl_lightning_lora():
load_file_from_url(
url='https://huggingface.co/mashb1t/misc/resolve/main/sdxl_lightning_4step_lora.safetensors',
model_dir=paths_loras[0],
model_dir=paths_performance_loras[0],
file_name=modules.flags.PerformanceLoRA.LIGHTNING.value
)
return modules.flags.PerformanceLoRA.LIGHTNING.value
return f"{modules.flags.PerformanceLoRA.LIGHTNING.value}"
def downloading_sdxl_hyper_sd_lora():
load_file_from_url(
url='https://huggingface.co/mashb1t/misc/resolve/main/sdxl_hyper_sd_4step_lora.safetensors',
model_dir=paths_loras[0],
model_dir=paths_performance_loras[0],
file_name=modules.flags.PerformanceLoRA.HYPER_SD.value
)
return modules.flags.PerformanceLoRA.HYPER_SD.value
return f"{modules.flags.PerformanceLoRA.HYPER_SD.value}"
def downloading_sdxl_lcm_lora():
return load_file_from_url(
url='https://huggingface.co/lllyasviel/misc/resolve/main/sdxl_lcm_lora.safetensors',
model_dir=paths_performance_loras[0],
file_name=modules.flags.PerformanceLoRA.EXTREME_SPEED.value
)
def downloading_sdxl_lightning_lora():
return load_file_from_url(
url='https://huggingface.co/mashb1t/misc/resolve/main/sdxl_lightning_4step_lora.safetensors',
model_dir=paths_performance_loras[0],
file_name=modules.flags.PerformanceLoRA.LIGHTNING.value
)
def downloading_sdxl_hyper_sd_lora():
return load_file_from_url(
url='https://huggingface.co/mashb1t/misc/resolve/main/sdxl_hyper_sd_4step_lora.safetensors',
model_dir=paths_performance_loras[0],
file_name=modules.flags.PerformanceLoRA.HYPER_SD.value
)
🤖 Prompt for AI Agents
In modules/config.py around lines 1075 to 1100, the downloading_sdxl_*_lora
functions currently return only the filename string instead of the full file
path, which breaks downstream code expecting the full path. To fix this, modify
each function to first ensure the directory paths_performance_loras[0] exists by
creating it if necessary, then call load_file_from_url and return its full path
result instead of just the filename. Apply this fix consistently to
downloading_sdxl_lcm_lora, downloading_sdxl_lightning_lora, and
downloading_sdxl_hyper_sd_lora.


def downloading_controlnet_canny():
Expand Down
12 changes: 10 additions & 2 deletions modules/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,18 @@ def refresh_loras(self, loras):
if os.path.exists(filename):
lora_filename = filename
else:
lora_filename = get_file_from_folder_list(filename, modules.config.paths_loras)
loras_paths = []
if isinstance(modules.config.paths_loras, list):
loras_paths.extend(modules.config.paths_loras)
else:
loras_paths.append(modules.config.paths_loras)
if isinstance(modules.config.paths_performance_loras, list):
loras_paths.extend(modules.config.paths_performance_loras)
else:
loras_paths.append(modules.config.paths_performance_loras)
lora_filename = get_file_from_folder_list(filename, loras_paths)

Comment on lines +84 to 94
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard against None returns and remove duplicate search paths

  1. get_file_from_folder_list can return None; calling os.path.exists(None) raises TypeError.
  2. paths_loras & paths_performance_loras may contain None or overlap, yielding redundant work.
-                lora_filename = get_file_from_folder_list(filename, loras_paths)
-
-            if not os.path.exists(lora_filename):
+                lora_filename = get_file_from_folder_list(filename, loras_paths)
+
+            # Abort early if lookup failed
+            if not lora_filename or not os.path.exists(lora_filename):
                 continue

Optionally build loras_paths via a set() to deduplicate.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In modules/core.py around lines 84 to 94, the code does not guard against None
values in paths_loras and paths_performance_loras, which can cause
get_file_from_folder_list to return None and lead to errors when checking file
existence. Also, the current approach may add duplicate or None paths, causing
redundant processing. Fix this by filtering out None values before adding paths,
use a set to collect unique paths from both config lists, then convert back to a
list before passing to get_file_from_folder_list.

if not os.path.exists(lora_filename):
print(f'Lora file not found: {lora_filename}')
continue

loras_to_load.append((lora_filename, weight))
Expand Down
10 changes: 5 additions & 5 deletions modules/model_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ def create_model_structure():
os.makedirs(config.paths_checkpoints[0] + '\SD3x', exist_ok=True)

# ensure that the special LoRA directories exist
os.makedirs(config.paths_loras[0] + '\Alternative', exist_ok=True)
os.makedirs(config.paths_loras[0] + '\Flux', exist_ok=True)
os.makedirs(config.paths_loras[0] + '\Pony', exist_ok=True)
os.makedirs(config.paths_loras[0] + '\SD1.5', exist_ok=True)
os.makedirs(config.paths_loras[0] + '\SD3x', exist_ok=True)
os.makedirs(config.paths_loras[0] + '\common\Alternative', exist_ok=True)
os.makedirs(config.paths_loras[0] + '\common\Flux', exist_ok=True)
os.makedirs(config.paths_loras[0] + '\common\Pony', exist_ok=True)
os.makedirs(config.paths_loras[0] + '\common\SD1.5', exist_ok=True)
os.makedirs(config.paths_loras[0] + '\common\SD3x', exist_ok=True)
Comment on lines +14 to +18
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Windows-only path separators will break on Linux / macOS

os.makedirs(config.paths_loras[0] + '\common\Alternative', …) embeds back-slashes.
On POSIX these are literal characters, so the code creates a directory literally named
“…/loras\common\Alternative”, and later look-ups using os.path.join(base, "common", "Alternative")
will not find it.

-os.makedirs(config.paths_loras[0] + '\common\Alternative', exist_ok=True)        
-os.makedirs(config.paths_loras[0] + '\common\Flux', exist_ok=True)
-
+base = config.paths_loras[0]
+for sub in ("Alternative", "Flux", "Pony", "SD1.5", "SD3x"):
+    os.makedirs(os.path.join(base, "common", sub), exist_ok=True)

Please replace the string concatenation with os.path.join (or pathlib) for full cross-platform support.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
os.makedirs(config.paths_loras[0] + '\common\Alternative', exist_ok=True)
os.makedirs(config.paths_loras[0] + '\common\Flux', exist_ok=True)
os.makedirs(config.paths_loras[0] + '\common\Pony', exist_ok=True)
os.makedirs(config.paths_loras[0] + '\common\SD1.5', exist_ok=True)
os.makedirs(config.paths_loras[0] + '\common\SD3x', exist_ok=True)
base = config.paths_loras[0]
for sub in ("Alternative", "Flux", "Pony", "SD1.5", "SD3x"):
os.makedirs(os.path.join(base, "common", sub), exist_ok=True)
🤖 Prompt for AI Agents
In modules/model_structure.py around lines 14 to 18, the directory paths are
constructed using string concatenation with backslashes, which causes issues on
Linux and macOS. Replace these concatenations with os.path.join to build the
paths in a cross-platform way, ensuring the directories are created correctly
regardless of the operating system.


return
83 changes: 30 additions & 53 deletions presets/4GB_Default.json
Original file line number Diff line number Diff line change
@@ -1,55 +1,32 @@
{
"preset category": "LowVRAM",
"Download manually from": "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_offset_example-lora_1.0.safetensors",
"default_model": "LowVRAM\\sdxl4GB2GBImprovedFP8_fp8FullCheckpoint.safetensors",
"default_refiner": "None",
"default_refiner_switch": 0.75,
"default_loras": [
[
true,
"sd_xl_offset_example-lora_1.0.safetensors",
0.5
],
[
true,
"None",
1.0
],
[
true,
"None",
1.0
],
[
true,
"None",
1.0
],
[
true,
"None",
1.0
]
],
"default_cfg_scale": 7.0,
"default_sample_sharpness": 2.0,
"default_sampler": "dpmpp_2m_sde_gpu",
"default_scheduler": "karras",
"default_performance": "Speed",
"default_prompt": "",
"default_prompt_negative": "",
"default_styles": [
"Fooocus V2",
"Fooocus Enhance"
],
"default_aspect_ratio": "1024*1024",
"default_overwrite_step": -1,
"checkpoint_downloads": {
"LowVRAM\\sdxl4GB2GBImprovedFP8_fp8FullCheckpoint.safetensors": "https://civitai.com/api/download/models/971447?type=Model&format=SafeTensor&size=full&fp=fp8&token=b9c06333099ba600ef8e993a5e97f31a"
},
"embeddings_downloads": {},
"lora_downloads": {
"sd_xl_offset_example-lora_1.0.safetensors": "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_offset_example-lora_1.0.safetensors"
},
"previous_default_models": []
"preset category": "LowVRAM",
"Download manually from": "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_offset_example-lora_1.0.safetensors",
"default_model": "LowVRAM\\sdxl4GB2GBImprovedFP8_fp8FullCheckpoint.safetensors",
"default_refiner": "None",
"default_refiner_switch": 0.75,
"default_loras": [
[true, "common\\sd_xl_offset_example-lora_1.0.safetensors", 0.5],
[true, "None", 1.0],
[true, "None", 1.0],
[true, "None", 1.0],
[true, "None", 1.0]
],
"default_cfg_scale": 7.0,
"default_sample_sharpness": 2.0,
"default_sampler": "dpmpp_2m_sde_gpu",
"default_scheduler": "karras",
"default_performance": "Speed",
"default_prompt": "",
"default_prompt_negative": "",
"default_styles": ["Fooocus V2", "Fooocus Enhance"],
"default_aspect_ratio": "1024*1024",
"default_overwrite_step": -1,
"checkpoint_downloads": {
"LowVRAM\\sdxl4GB2GBImprovedFP8_fp8FullCheckpoint.safetensors": "https://civitai.com/api/download/models/971447?type=Model&format=SafeTensor&size=full&fp=fp8&token=b9c06333099ba600ef8e993a5e97f31a"
},
"embeddings_downloads": {},
"lora_downloads": {
"sd_xl_offset_example-lora_1.0.safetensors": "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_offset_example-lora_1.0.safetensors"
},
"previous_default_models": []
}
80 changes: 29 additions & 51 deletions presets/4GB_Pony.json
Original file line number Diff line number Diff line change
@@ -1,53 +1,31 @@
{
"preset category": "LowVRAM",
"Download manually from": "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_offset_example-lora_1.0.safetensors",
"default_model": "LowVRAM\\4gbPONY4STEP2GB_fp8FullCheckpoint.safetensors",
"default_refiner": "None",
"default_refiner_switch": 0.6,
"default_loras": [
[
true,
"None",
1.0
],
[
true,
"None",
1.0
],
[
true,
"None",
1.0
],
[
true,
"None",
1.0
],
[
true,
"None",
1.0
]
],
"default_cfg_scale": 7.0,
"default_sample_sharpness": 2.0,
"default_sampler": "dpmpp_2m_sde_gpu",
"default_scheduler": "karras",
"default_performance": "Speed",
"default_prompt": "",
"default_prompt_negative": "",
"default_styles": [
"Pony Real"
],
"default_aspect_ratio": "1024*1024",
"default_overwrite_step": -1,
"default_inpaint_engine_version": "None",
"checkpoint_downloads": {
"4gbPONY4STEP2GB_fp8FullCheckpoint.safetensors": "https://civitai.com/api/download/models/968654?type=Model&format=SafeTensor&size=full&fp=fp8&token=b9c06333099ba600ef8e993a5e97f31a"
},
"embeddings_downloads": {},
"lora_downloads": {},
"vae_downloads": {}
"preset category": "LowVRAM",
"Download manually from": "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_offset_example-lora_1.0.safetensors",
"default_model": "LowVRAM\\4gbPONY4STEP2GB_fp8FullCheckpoint.safetensors",
"default_refiner": "None",
"default_refiner_switch": 0.6,
"default_loras": [
[true, "None", 1.0],
[true, "None", 1.0],
[true, "None", 1.0],
[true, "None", 1.0],
[true, "None", 1.0]
],
"default_cfg_scale": 7.0,
"default_sample_sharpness": 2.0,
"default_sampler": "dpmpp_2m_sde_gpu",
"default_scheduler": "karras",
"default_performance": "Speed",
"default_prompt": "",
"default_prompt_negative": "",
"default_styles": ["Pony Real"],
"default_aspect_ratio": "1024*1024",
"default_overwrite_step": -1,
"default_inpaint_engine_version": "None",
"checkpoint_downloads": {
"4gbPONY4STEP2GB_fp8FullCheckpoint.safetensors": "https://civitai.com/api/download/models/968654?type=Model&format=SafeTensor&size=full&fp=fp8&token=b9c06333099ba600ef8e993a5e97f31a"
},
"embeddings_downloads": {},
"lora_downloads": {},
"vae_downloads": {}
}
Loading