mirror of
https://github.com/vladmandic/automatic
synced 2026-09-18 08:44:33 +02:00
Merge branch 'dev' of https://github.com/vladmandic/sdnext into dev
This commit is contained in:
@@ -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",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
@@ -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:
|
||||
|
||||
+41
-29
@@ -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("<wbr />_", text)
|
||||
return re_camelCase.sub(r"<wbr />\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):
|
||||
</colgroup>
|
||||
<thead style="font-size: 110%; border-style: solid; border-bottom: 1px var(--button-primary-border-color) solid">
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<th>Enabled</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th>Extension</th>
|
||||
<th>Description</th>
|
||||
<th>Type</th>
|
||||
@@ -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"""<div class="type">{"SYSTEM" if ext['is_builtin'] else 'USER'}</div>"""
|
||||
version_code = f"""<div class="version" style="background: {"--input-border-color-focus" if update_available else "inherit"}">{ext['version']}</div>"""
|
||||
enabled_code = f"""<input class="gr-check-radio gr-checkbox" name="enable_{html.escape(ext.get("name", "unknown"))}" type="checkbox" {'checked="checked"' if ext.get("enabled", False) else ''}>"""
|
||||
enabled_code = f"""<input class="gr-check-radio gr-checkbox" style="display:block;margin:auto;width:fit-content;" name="enable_{html.escape(ext.get("name", "unknown"))}" type="checkbox" {'checked="checked"' if ext.get("enabled", False) else ''}>"""
|
||||
masked_path = html.escape(ext.get("path", "").replace('\\', '/'))
|
||||
if not ext['is_builtin']:
|
||||
install_code = f"""<button onclick="uninstall_extension(this, '{masked_path}')" class="lg secondary gradio-button custom-button extension-button">uninstall</button>"""
|
||||
@@ -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"<div style='cursor:help;width:1em;' title='Local'>{ui_symbols.svg_bullet.color('#00C0FD')}</div>"
|
||||
status = f"<div style='cursor:help;width:1rem;margin:auto;' title='Local'>{ui_symbols.svg_bullet.style('#00C0FD')}</div>"
|
||||
elif ext['status'] > 0:
|
||||
if ext['status'] == 1:
|
||||
status = f"<div style='cursor:help;width:1em;' title='Verified'>{ui_symbols.svg_bullet.color('#00FD9C')}</div>"
|
||||
status = f"<div style='cursor:help;width:1rem;margin:auto;' title='Verified'>{ui_symbols.svg_bullet.style('#00FD9C')}</div>"
|
||||
elif ext['status'] == 2:
|
||||
status = f"<div style='cursor:help;width:1em;' title='Supported only with backend: Original'>{ui_symbols.svg_bullet.color('#FFC300')}</div>"
|
||||
status = f"<div style='cursor:help;width:1rem;margin:auto;' title='Supported only with backend: Original'>{ui_symbols.svg_bullet.style('#FFC300')}</div>"
|
||||
elif ext['status'] == 3:
|
||||
status = f"<div style='cursor:help;width:1em;' title='Supported only with backend: Diffusers'>{ui_symbols.svg_bullet.color('#FFC300')}</div>"
|
||||
status = f"<div style='cursor:help;width:1rem;margin:auto;' title='Supported only with backend: Diffusers'>{ui_symbols.svg_bullet.style('#FFC300')}</div>"
|
||||
elif ext['status'] == 4:
|
||||
status = f"<div style='cursor:help;width:1em;' title=\"{html.escape(ext.get('note', 'custom value'))}\">{ui_symbols.svg_bullet.color('#4E22FF')}</div>"
|
||||
status = f"<div style='cursor:help;width:1rem;margin:auto;' title=\"{html.escape(ext.get('note', 'custom value'))}\">{ui_symbols.svg_bullet.style('#4E22FF')}</div>"
|
||||
elif ext['status'] == 5:
|
||||
status = f"<div style='cursor:help;width:1em;' title='Not supported'>{ui_symbols.svg_bullet.color('#CE0000')}</div>"
|
||||
status = f"<div style='cursor:help;width:1rem;margin:auto;' title='Not supported'>{ui_symbols.svg_bullet.style('#CE0000')}</div>"
|
||||
elif ext['status'] == 6:
|
||||
status = f"<div style='cursor:help;width:1em;' title='Just discovered'>{ui_symbols.svg_bullet.color('#AEAEAE')}</div>"
|
||||
status = f"<div style='cursor:help;width:1rem;margin:auto;' title='Just discovered'>{ui_symbols.svg_bullet.style('#AEAEAE')}</div>"
|
||||
else:
|
||||
status = f"<div style='cursor:help;width:1em;' title='Unknown status'>{ui_symbols.svg_bullet.color('#008EBC')}</div>"
|
||||
status = f"<div style='cursor:help;width:1rem;margin:auto;' title='Unknown status'>{ui_symbols.svg_bullet.style('#008EBC')}</div>"
|
||||
else:
|
||||
if updated < datetime.now(timezone.utc) - timedelta(6*30): # TZ-aware
|
||||
status = f"<div style='cursor:help;width:1em;' title='Unmaintained'>{ui_symbols.svg_bullet.color('#C000CF')}</div>"
|
||||
status = f"<div style='cursor:help;width:1rem;margin:auto;' title='Unmaintained'>{ui_symbols.svg_bullet.style('#C000CF')}</div>"
|
||||
else:
|
||||
status = f"<div style='cursor:help;width:1em;' title='No info'>{ui_symbols.svg_bullet.color('#7C7C7C')}</div>"
|
||||
status = f"<div style='cursor:help;width:1rem;margin:auto;' title='No info'>{ui_symbols.svg_bullet.style('#7C7C7C')}</div>"
|
||||
|
||||
code += f"""
|
||||
<tr style="display: {visible}">
|
||||
<td>{status}</td>
|
||||
<td{' class="extension_status"' if ext['installed'] else ''}>{enabled_code}</td>
|
||||
<td><a href="{html.escape(ext.get('url', ''))}" title={html.escape(ext.get('url', ''))} target="_blank" class="name">{html.escape(ext.get("name", "unknown"))}</a><br>{tags_text}</td>
|
||||
<td>{status}</td>
|
||||
<td><a href="{html.escape(ext.get('url', ''))}" title={html.escape(ext.get('url', ''))} target="_blank" class="name">{make_wrappable_html(ext.get("name", "unknown"))}</a><br>{tags_text}</td>
|
||||
<td>{html.escape(ext.get("description", ""))}
|
||||
<p class="info"><span class="date">Created {html.escape(dt('created'))} | Added {html.escape(dt('added'))} | Pushed {html.escape(dt('pushed'))} | Updated {html.escape(dt('updated'))}</span></p>
|
||||
<p class="info"><span class="date">{author} | Stars {html.escape(str(ext.get('stars', 0)))} | Size {html.escape(str(ext.get('size', 0)))} | Commits {html.escape(str(ext.get('commits', 0)))} | Issues {html.escape(str(ext.get('issues', 0)))} | Trending {html.escape(str(ext['sort_trending']))}</span></p>
|
||||
<p class="info"><span class="date">Created: {html.escape(dt('created'))} | Added: {html.escape(dt('added'))} | Pushed: {html.escape(dt('pushed'))} | Updated: {html.escape(dt('updated'))}</span></p>
|
||||
<p class="info"><span class="date">{author} | Stars: {html.escape(str(ext.get('stars', 0)))} | Size: {html.escape(str(ext.get('size', 0)))} | Commits: {html.escape(str(ext.get('commits', 0)))} | Issues: {html.escape(str(ext.get('issues', 0)))} | Trending: {html.escape(str(ext['sort_trending']))}</span></p>
|
||||
</td>
|
||||
<td>{type_code}</td>
|
||||
<td>{version_code}</td>
|
||||
|
||||
+38
-9
@@ -1,3 +1,10 @@
|
||||
import re
|
||||
from functools import lru_cache
|
||||
from typing import final
|
||||
|
||||
|
||||
# Basic symbols
|
||||
|
||||
refresh = '⟲'
|
||||
close = '✕'
|
||||
load = '⇧'
|
||||
@@ -41,23 +48,45 @@ sort_time_dsc = '\uf0dd'
|
||||
style_apply = '↶'
|
||||
style_save = '↷'
|
||||
|
||||
# Configurable symbols
|
||||
|
||||
@final
|
||||
class SVGSymbol:
|
||||
__created = []
|
||||
__re_display = re.compile(r"(?<=display:)\s*([\w\-]+)(?=;)")
|
||||
|
||||
@classmethod
|
||||
@lru_cache # Class method due to B019, but also mostly so the `style` method shows params in IDE
|
||||
def __stylize(cls, svg: str, color: str | None = None, display: str | None = None):
|
||||
if color:
|
||||
svg = re.sub("currentColor", color, svg)
|
||||
if display:
|
||||
svg = cls.__re_display.sub(display, svg, count=1)
|
||||
return svg
|
||||
|
||||
def __init__(self, svg: str):
|
||||
svg = re.sub(r"\s{2,}", " ", svg.replace("\n", "")).replace("> <", "><").strip()
|
||||
if svg in self.__created:
|
||||
raise RuntimeError("SVGSymbol class was created with an existing value. There should only be one instance per symbol.", svg)
|
||||
else:
|
||||
self.__created.append(svg)
|
||||
self.svg = svg
|
||||
self.before = ""
|
||||
self.after = ""
|
||||
self.supports_color = False
|
||||
self.supports_display = False
|
||||
if "currentColor" in self.svg:
|
||||
self.supports_color = True
|
||||
self.before, self.after = self.svg.split("currentColor", maxsplit=1)
|
||||
if self.__re_display.search(self.svg):
|
||||
self.supports_display = True
|
||||
|
||||
def color(self, color: str):
|
||||
if self.supports_color:
|
||||
return self.before + color + self.after
|
||||
else:
|
||||
return self.svg
|
||||
def style(self, color: str | None = None, display: str | None = None) -> str:
|
||||
style_args = {
|
||||
"color": color if color and self.supports_color else None,
|
||||
"display": display if display and self.supports_display else None
|
||||
}
|
||||
return self.__stylize(self.svg, **style_args)
|
||||
|
||||
def __str__(self):
|
||||
return self.svg
|
||||
|
||||
svg_bullet = SVGSymbol("<svg style='stroke:currentColor;fill:none;stroke-width:2;' viewBox='0 0 16 16'><circle cx='8' cy='8' r='7'/></svg>")
|
||||
|
||||
svg_bullet = SVGSymbol("<svg style='stroke:currentColor;fill:none;stroke-width:2;display:block;' viewBox='0 0 16 16'><circle cx='8' cy='8' r='7'/></svg>")
|
||||
|
||||
Reference in New Issue
Block a user