diff --git a/html/reference.json b/html/reference.json
index a7c42a338..4fd74b92c 100644
--- a/html/reference.json
+++ b/html/reference.json
@@ -181,6 +181,15 @@
"extras": "",
"size": 56.1,
"date": "2025 August"
+ },
+ "Qwen-Image-2512": {
+ "path": "Qwen/Qwen-Image-2512",
+ "preview": "Qwen--Qwen-Image-2512.jpg",
+ "desc": "Qwen-Image-2512 is an Qwen Image successor, that significantly reduces the AI-generated look, got finer natural detailils and improved text rendering.",
+ "skip": true,
+ "extras": "",
+ "size": 53.7,
+ "date": "2025 December"
},
"Qwen-Image-Edit": {
"path": "Qwen/Qwen-Image-Edit",
diff --git a/models/Reference/Qwen--Qwen-Image-2512.jpg b/models/Reference/Qwen--Qwen-Image-2512.jpg
new file mode 100644
index 000000000..dc1839828
Binary files /dev/null and b/models/Reference/Qwen--Qwen-Image-2512.jpg differ
diff --git a/modules/extensions.py b/modules/extensions.py
index b1b5d4e32..b29a6cbb0 100644
--- a/modules/extensions.py
+++ b/modules/extensions.py
@@ -25,10 +25,12 @@ def parse_isotime(time_string: str) -> datetime:
raise ValueError(f"Unexpected time string format: '{time_string}'")
-def format_dt(d: datetime) -> str:
+def format_dt(d: datetime, seconds = False) -> str:
if d.tzinfo is None:
return d.strftime('%Y-%m-%d %H:%M')
- return d.astimezone(timezone.utc).strftime('%Y-%m-%d %H:%M:%S') # Ensure UTC time is shown (just in case)
+ if seconds:
+ return d.astimezone(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')
+ return d.astimezone(timezone.utc).strftime('%Y-%m-%d %H:%M')
def ts2utc(timestamp: int) -> datetime:
diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py
index a6c5ca3ec..4a800ec73 100644
--- a/modules/ui_extensions.py
+++ b/modules/ui_extensions.py
@@ -3,6 +3,7 @@ import json
import shutil
import errno
import html
+import re
from datetime import datetime, timezone, timedelta
import gradio as gr
from modules import extensions, shared, paths, errors, ui_symbols, call_queue
@@ -29,6 +30,10 @@ sort_ordering = {
}
+re_snake_case = re.compile(r'_(?=[a-zA-z0-9])')
+re_camelCase = re.compile(r'(?<=[a-z])([A-Z])')
+
+
def get_installed(ext):
installed = [e for e in extensions.extensions if (e.remote or '').startswith(ext['url'].replace('.git', ''))]
return installed[0] if len(installed) > 0 else None
@@ -108,10 +113,10 @@ def check_updates(_id_task, disable_list, search_text, sort_column):
ext.git_fetch()
ext.read_info()
commit_date = ext.commit_date or 1577836800
- shared.log.info(f'Extensions updated: {ext.name} {ext.commit_hash[:8]} {extensions.format_dt(extensions.ts2utc(commit_date))}')
+ shared.log.info(f'Extensions updated: {ext.name} {ext.commit_hash[:8]} {extensions.format_dt(extensions.ts2utc(commit_date), seconds=True)}')
else:
commit_date = ext.commit_date or 1577836800
- shared.log.debug(f'Extensions no update available: {ext.name} {ext.commit_hash[:8]} {extensions.format_dt(extensions.ts2utc(commit_date))}')
+ shared.log.debug(f'Extensions no update available: {ext.name} {ext.commit_hash[:8]} {extensions.format_dt(extensions.ts2utc(commit_date), seconds=True)}')
except FileNotFoundError as e:
if 'FETCH_HEAD' not in str(e):
raise
@@ -121,26 +126,27 @@ def check_updates(_id_task, disable_list, search_text, sort_column):
return create_html(search_text, sort_column), "Extension update complete | Restart required"
-def normalize_git_url(url) -> str:
- return '' if url is None else url.removesuffix('.git')
+def normalize_git_url(url: str | None) -> str:
+ return '' if url is None else url.strip().removesuffix('.git')
def install_extension_from_url(dirname, url, branch_name, search_text, sort_column):
if shared.cmd_opts.disable_extension_access:
shared.log.error('Extension: apply changes disallowed because public access is enabled and insecure is not specified')
return ['', '']
- if url is None or len(url) == 0:
+ url = normalize_git_url(url)
+ if not url:
shared.log.error('Extension: url is not specified')
return ['', '']
- if dirname is None or dirname == "":
- dirname = normalize_git_url(url.split('/')[-1])
+ if not dirname:
+ dirname = url.split('/')[-1]
target_dir = os.path.join(extensions.extensions_dir, dirname)
shared.log.info(f'Installing extension: {url} into {target_dir}')
if os.path.exists(target_dir):
shared.log.error(f'Extension: path="{target_dir}" directory already exists')
return ['', '']
- url = normalize_git_url(url)
- assert len([x for x in extensions.extensions if normalize_git_url(x.remote) == url]) == 0, 'Extension with this URL is already installed'
+ if any(normalize_git_url(x.remote) == url for x in extensions.extensions):
+ return ['', "Extension with this URL is already installed"]
tmpdir = os.path.join(paths.data_path, "tmp", dirname)
try:
import git
@@ -229,10 +235,10 @@ def update_extension(extension_path, search_text, sort_column):
ext.git_fetch()
ext.read_info()
commit_date = ext.commit_date or 1577836800
- shared.log.info(f'Extensions updated: {ext.name} {ext.commit_hash[:8]} {extensions.format_dt(extensions.ts2utc(commit_date))}')
+ shared.log.info(f'Extensions updated: {ext.name} {ext.commit_hash[:8]} {extensions.format_dt(extensions.ts2utc(commit_date), seconds=True)}')
else:
commit_date = ext.commit_date or 1577836800
- shared.log.info(f'Extensions no update available: {ext.name} {ext.commit_hash[:8]} {extensions.format_dt(extensions.ts2utc(commit_date))}')
+ shared.log.info(f'Extensions no update available: {ext.name} {ext.commit_hash[:8]} {extensions.format_dt(extensions.ts2utc(commit_date), seconds=True)}')
except FileNotFoundError as e:
if 'FETCH_HEAD' not in str(e):
raise
@@ -268,6 +274,12 @@ def search_extensions(search_text, sort_column):
return code, f'Search | {search_text} | {sort_column}'
+def make_wrappable_html(text: str) -> str:
+ text = html.escape(text)
+ text = re_snake_case.sub("_", text)
+ return re_camelCase.sub(r"\1", text)
+
+
def create_html(search_text, sort_column):
# shared.log.debug(f'Extensions manager: refresh list search="{search_text}" sort="{sort_column}"')
code = """
@@ -284,8 +296,8 @@ def create_html(search_text, sort_column):
-
Status
-
Enabled
+
+
Extension
Description
Type
@@ -331,7 +343,7 @@ def create_html(search_text, sort_column):
local_ver_date = extensions.ts2utc(ext['commit_date']) # TZ-aware
update_available = (installed is not None) and (not ext['is_builtin']) and (ext['remote'] is not None) and (updated > local_ver_date) # TZ-aware
if update_available:
- debug(f'Extension update available: name={ext["name"]} updated={extensions.format_dt(updated)} commit={extensions.format_dt(local_ver_date)}') # TZ-aware
+ debug(f'Extension update available: name={ext["name"]} updated={extensions.format_dt(updated, seconds=True)} commit={extensions.format_dt(local_ver_date, seconds=True)}') # TZ-aware
ext['sort_user'] = f"{'0' if ext['is_builtin'] else '1'}{'1' if ext['installed'] else '0'}{ext.get('name', '')}"
ext['sort_enabled'] = f"{'0' if ext['enabled'] else '1'}{'1' if ext['is_builtin'] else '0'}{'1' if ext['installed'] else '0'}{ext.get('updated', '2000-01-01T00:00Z')}"
ext['sort_update'] = f"{'1' if update_available else '0'}{'1' if ext['installed'] else '0'}{ext.get('updated', '2000-01-01T00:00Z')}"
@@ -368,7 +380,7 @@ def create_html(search_text, sort_column):
stats['enabled'] += 1
type_code = f"""
{"SYSTEM" if ext['is_builtin'] else 'USER'}
"""
version_code = f"""
{ext['version']}
"""
- enabled_code = f""""""
+ enabled_code = f""""""
masked_path = html.escape(ext.get("path", "").replace('\\', '/'))
if not ext['is_builtin']:
install_code = f""""""
@@ -380,36 +392,36 @@ def create_html(search_text, sort_column):
if ext.get('status', None) is None or type(ext['status']) == str: # old format
ext['status'] = 0
if ext['url'] is None or ext['url'] == '':
- status = f"
{ui_symbols.svg_bullet.color('#00C0FD')}
"
+ status = f"
{ui_symbols.svg_bullet.style('#00C0FD')}
"
elif ext['status'] > 0:
if ext['status'] == 1:
- status = f"
{ui_symbols.svg_bullet.color('#00FD9C')}
"
+ status = f"
{ui_symbols.svg_bullet.style('#00FD9C')}
"
elif ext['status'] == 2:
- status = f"
{ui_symbols.svg_bullet.color('#FFC300')}
"
+ status = f"
{ui_symbols.svg_bullet.style('#FFC300')}
"
elif ext['status'] == 3:
- status = f"
{ui_symbols.svg_bullet.color('#FFC300')}
"
+ status = f"
{ui_symbols.svg_bullet.style('#FFC300')}
"
elif ext['status'] == 4:
- status = f"
{ui_symbols.svg_bullet.color('#4E22FF')}
"
+ status = f"
{ui_symbols.svg_bullet.style('#4E22FF')}
"
elif ext['status'] == 5:
- status = f"
{ui_symbols.svg_bullet.color('#CE0000')}
"
+ status = f"
{ui_symbols.svg_bullet.style('#CE0000')}
"
elif ext['status'] == 6:
- status = f"
{ui_symbols.svg_bullet.color('#AEAEAE')}
"
+ status = f"
{ui_symbols.svg_bullet.style('#AEAEAE')}
"
else:
- status = f"
{ui_symbols.svg_bullet.color('#008EBC')}
"
+ status = f"
{ui_symbols.svg_bullet.style('#008EBC')}
"
else:
if updated < datetime.now(timezone.utc) - timedelta(6*30): # TZ-aware
- status = f"