From 72a9094b42fd39f57708e1134afd214f5614ed9c Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Thu, 4 Sep 2025 12:41:40 -0400 Subject: [PATCH] better model version detect and experimental pydantic v2 Signed-off-by: Vladimir Mandic --- TODO.md | 7 ++++--- extensions-builtin/sdnext-modernui | 2 +- javascript/extraNetworks.js | 5 ++--- modules/api/endpoints.py | 3 ++- modules/api/models.py | 11 +++++------ modules/face/__init__.py | 2 +- modules/lora/network.py | 3 +++ modules/options_handler.py | 6 +++++- modules/progress.py | 9 +++++---- modules/sd_checkpoint.py | 2 +- modules/ui_extra_networks.py | 15 +++++++++------ modules/ui_extra_networks_checkpoints.py | 24 ++++++++++++++++++++---- 12 files changed, 58 insertions(+), 31 deletions(-) diff --git a/TODO.md b/TODO.md index dca55b534..0c4034cb4 100644 --- a/TODO.md +++ b/TODO.md @@ -20,9 +20,10 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma ### Blocked items -- Upgrade: unblock `pydantic` and `albumentations` - - see - - blocked by `insightface` +- Upgrade: `pydantic` + - see +- Upgrade: `albumentations` + - blocked by `insightface` ### Under Consideration diff --git a/extensions-builtin/sdnext-modernui b/extensions-builtin/sdnext-modernui index 3e06d7f5d..f2c4bdae1 160000 --- a/extensions-builtin/sdnext-modernui +++ b/extensions-builtin/sdnext-modernui @@ -1 +1 @@ -Subproject commit 3e06d7f5dddce471ba8f3e82542f44af34d1d527 +Subproject commit f2c4bdae1e2b8e900db335aee8bde967eebed726 diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 5026ef6e2..e227203b3 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -293,9 +293,8 @@ function extraNetworksSearchButton(event) { function extraNetworksFilterVersion(event) { // log('extraNetworksFilterVersion', event); const version = event.target.textContent.trim(); - const activeTab = gradioApp().querySelector('.extra-networks-tab:not([style*="display: none"])'); - if (!activeTab) return; - const cardContainer = activeTab.querySelector('.extra-network-cards'); + const activeTab = getENActiveTab(); + const cardContainer = gradioApp().querySelector(`#${activeTab}_model_cards`); if (!cardContainer) return; if (cardContainer.dataset.activeVersion === version) { cardContainer.dataset.activeVersion = ''; diff --git a/modules/api/endpoints.py b/modules/api/endpoints.py index 44120e7b5..c00648ade 100644 --- a/modules/api/endpoints.py +++ b/modules/api/endpoints.py @@ -20,7 +20,8 @@ def get_sd_models(): from modules import sd_checkpoint checkpoints = [] for v in sd_checkpoint.checkpoints_list.values(): - checkpoints.append({"title": v.title, "model_name": v.name, "filename": v.filename, "type": v.type, "hash": v.shorthash, "sha256": v.sha256}) + model = models.ItemModel(title=v.title, model_name=v.name, filename=v.filename, type=v.type, hash=v.shorthash, sha256=v.sha256, config=None) + checkpoints.append(model) return checkpoints def get_controlnets(model_type: Optional[str] = None): diff --git a/modules/api/models.py b/modules/api/models.py index a97ddff0c..c37e32e94 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -188,7 +188,7 @@ class ItemExtension(BaseModel): branch: str = Field(default="uknnown", title="Branch", description="Extension Repository Branch") commit_hash: str = Field(title="Commit Hash", description="Extension Repository Commit Hash") version: str = Field(title="Version", description="Extension Version") - commit_date: str = Field(title="Commit Date", description="Extension Repository Commit Date") + commit_date: Union[str, int] = Field(title="Commit Date", description="Extension Repository Commit Date") enabled: bool = Field(title="Enabled", description="Flag specifying whether this extension is enabled") ### request/response classes @@ -311,13 +311,13 @@ class ReqPostLog(BaseModel): error: Optional[str] = Field(default=None, title="Error message", description="The error message to log") class ReqHistory(BaseModel): - id: str = Field(default=None, title="Task ID", description="Task ID") + id: Union[int, str, None] = Field(default=None, title="Task ID", description="Task ID") class ReqProgress(BaseModel): skip_current_image: bool = Field(default=False, title="Skip current image", description="Skip current image serialization") class ResProgress(BaseModel): - id: str = Field(title="TaskID", description="Task ID") + id: Union[int, str, None] = Field(title="TaskID", description="Task ID") progress: float = Field(title="Progress", description="The progress with a range of 0 to 1") eta_relative: float = Field(title="ETA in secs") state: dict = Field(title="State", description="The current state snapshot") @@ -325,7 +325,7 @@ class ResProgress(BaseModel): textinfo: Optional[str] = Field(default=None, title="Info text", description="Info text used by WebUI.") class ResHistory(BaseModel): - id: str = Field(title="ID", description="Task ID") + id: Union[int, str, None] = Field(title="ID", description="Task ID") job: str = Field(title="Job", description="Job name") op: str = Field(title="Operation", description="Operation name") start: Union[float, None] = Field(title="Start", description="Start time") @@ -337,7 +337,7 @@ class ResStatus(BaseModel): task: str = Field(title="Task", description="Current job") timestamp: Optional[str] = Field(title="Timestamp", description="Timestamp of the current job") current: str = Field(title="Task", description="Current job") - id: str = Field(title="ID", description="ID of the current task") + id: Union[int, str, None] = Field(title="ID", description="ID of the current task") job: int = Field(title="Job", description="Current job") jobs: int = Field(title="Jobs", description="Total jobs") total: int = Field(title="Total Jobs", description="Total jobs") @@ -349,7 +349,6 @@ class ResStatus(BaseModel): eta: Optional[float] = Field(default=None, title="ETA in secs") progress: Optional[float] = Field(default=None, title="Progress", description="The progress with a range of 0 to 1") - class ReqInterrogate(BaseModel): image: str = Field(default="", title="Image", description="Image to work on, must be a Base64 string containing the image's data.") clip_model: str = Field(default="", title="CLiP Model", description="The interrogate model used.") diff --git a/modules/face/__init__.py b/modules/face/__init__.py index 41fdddcdc..8161f7b31 100644 --- a/modules/face/__init__.py +++ b/modules/face/__init__.py @@ -165,7 +165,7 @@ class Script(scripts_manager.Script): processed.info = processed.infotext(p, 0) processed.infotexts = [processed.info] - if shared.opts.samples_save and not p.do_not_save_samples: + if shared.opts.samples_save and not p.do_not_save_samples and processed.images is not None: for i, image in enumerate(processed.images): info = processing.create_infotext(p, index=i) images.save_image(image, path=p.outpath_samples, seed=p.all_seeds[i], prompt=p.all_prompts[i], info=info, p=p) diff --git a/modules/lora/network.py b/modules/lora/network.py index b1092a08d..d889952f6 100644 --- a/modules/lora/network.py +++ b/modules/lora/network.py @@ -45,6 +45,9 @@ class NetworkOnDisk: self.set_hash(sha256) self.sd_version = self.detect_version() + def __str__(self): + return f"NetworkOnDisk(name={self.name} filename={self.filename}" + def detect_version(self): base = str(self.metadata.get('ss_base_model_version', "")).lower() arch = str(self.metadata.get('modelspec.architecture', "")).lower() diff --git a/modules/options_handler.py b/modules/options_handler.py index 5dd7f5435..8a9b2fd37 100644 --- a/modules/options_handler.py +++ b/modules/options_handler.py @@ -64,7 +64,11 @@ class Options(): """sets an option and calls its onchange callback, returning True if the option changed and False otherwise""" oldval = self.data.get(key, None) if oldval is None: - oldval = self.data_labels[key].default + if key in self.data_labels: + oldval = self.data_labels[key].default + else: + log.warning(f'Settings: key={key} value={value} unknown') + return False if oldval == value: return False try: diff --git a/modules/progress.py b/modules/progress.py index 83758cc7f..3203995e5 100644 --- a/modules/progress.py +++ b/modules/progress.py @@ -2,6 +2,7 @@ import base64 import os import io import time +from typing import Union from pydantic import BaseModel, Field # pylint: disable=no-name-in-module import modules.shared as shared @@ -47,7 +48,7 @@ class ProgressRequest(BaseModel): class InternalProgressResponse(BaseModel): job: str = Field(default=None, title="Job name", description="Internal job name") - textinfo: str = Field(default=None, title="Info text", description="Info text used by WebUI.") + textinfo: Union[str|None] = Field(default=None, title="Info text", description="Info text used by WebUI.") # status fields active: bool = Field(title="Whether the task is being worked on right now") queued: bool = Field(title="Whether the task is in queue") @@ -61,10 +62,10 @@ class InternalProgressResponse(BaseModel): batch_count: int = Field(default=None, title="Total batches", description="Total number of batches") # calculated fields progress: float = Field(default=None, title="Progress", description="The progress with a range of 0 to 1") - eta: float = Field(default=None, title="ETA in secs") + eta: Union[float|None] = Field(default=None, title="ETA in secs") # image fields - live_preview: str = Field(default=None, title="Live preview image", description="Current live preview; a data: uri") - id_live_preview: int = Field(default=None, title="Live preview image ID", description="Send this together with next request to prevent receiving same image") + live_preview: Union[str|None] = Field(default=None, title="Live preview image", description="Current live preview; a data: uri") + id_live_preview: Union[int|None] = Field(default=None, title="Live preview image ID", description="Send this together with next request to prevent receiving same image") def api_progress(req: ProgressRequest): diff --git a/modules/sd_checkpoint.py b/modules/sd_checkpoint.py index 78bfd0919..4e0f5c186 100644 --- a/modules/sd_checkpoint.py +++ b/modules/sd_checkpoint.py @@ -105,7 +105,7 @@ class CheckpointInfo: return self.shorthash def __str__(self): - return f'checkpoint: type={self.type} title="{self.title}" path="{self.path}"' + return f"CheckpointInfo(name={self.name} filename={self.filename} hash={self.shorthash} type={self.type}" def setup_model(): diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 54d9183c9..fcfa63735 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -480,14 +480,17 @@ class ExtraNetworksPage: if shared.cmd_opts.no_metadata: return data if path is not None: + t0 = time.time() fn = os.path.splitext(path)[0] + '.json' - if os.path.exists(fn): - t0 = time.time() + if not data and os.path.exists(fn): data = shared.readfile(fn, silent=True) - if type(data) is list: - data = data[0] - t1 = time.time() - self.info_time += t1-t0 + fn = os.path.join(path, 'model_index.json') + if not data and os.path.exists(fn): + data = shared.readfile(fn, silent=True) + if type(data) is list: + data = data[0] + t1 = time.time() + self.info_time += t1-t0 return data diff --git a/modules/ui_extra_networks_checkpoints.py b/modules/ui_extra_networks_checkpoints.py index 04a327de2..a8c35c2c4 100644 --- a/modules/ui_extra_networks_checkpoints.py +++ b/modules/ui_extra_networks_checkpoints.py @@ -6,6 +6,16 @@ from modules import shared, ui_extra_networks, sd_models, modelstats reference_dir = os.path.join('models', 'Reference') +version_map = { + "QwenEdit": "Qwen", + "Flux.1 D": "Flux", + "Flux.1 S": "Flux", + "FluxKontext": "Flux", + "SDXL 1.0": "SD XL", + "SDXL Hyper": "SD XL", + "StableDiffusion3": "SD 3", + "StableDiffusionXL": "SD XL", +} class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage): def __init__(self): @@ -62,10 +72,16 @@ class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage): "mtime": mtime, "size": size, } - record["info"] = self.find_info(checkpoint.filename) - record["description"] = self.find_description(checkpoint.filename, record["info"]) - version = self.find_version(checkpoint, record["info"]) - record["version"] = version.get("baseModel", "") if record["info"] else "" + record['info'] = self.find_info(checkpoint.filename) + record['description'] = self.find_description(checkpoint.filename, record['info']) + version = self.find_version(checkpoint, record['info']) + if 'baseModel' in version: + record['version'] = version.get("baseModel", "") + elif '_class_name' in record['info']: + record['version'] = record['info'].get('_class_name', '').replace('Pipeline', '').replace('Image', '') + else: + record['version'] = '' + record['version'] = version_map.get(record['version'], record['version']) except Exception as e: shared.log.debug(f'Networks error: type=model file="{name}" {e}')