complete refactor javascript to typescript and reorg frontend files and folders

Signed-off-by: Vladimir Mandic <mandic00@live.com>
This commit is contained in:
Vladimir Mandic
2026-05-20 11:40:17 +02:00
parent 4341193b35
commit 4b2f38ab7f
174 changed files with 22603 additions and 3989 deletions
+3 -5
View File
@@ -71,12 +71,10 @@ def create_docs(app: FastAPI):
res = get_swagger_ui_html(
title=f'{app.title}: Swagger UI',
openapi_url=app.openapi_url,
swagger_favicon_url='/file=html/favicon.svg',
swagger_css_url='/file=html/swagger.css',
swagger_favicon_url='/file=ui/assets/favicon.svg',
swagger_css_url='/file=ui/css/swagger.css',
swagger_ui_parameters=swagger_ui_parameters,
# swagger_extra_css_url='file=html/swagger.css',
)
# res = inject_css(html.content, 'html/swagger.css')
return res
@@ -86,6 +84,6 @@ def create_redocs(app: FastAPI):
res = get_redoc_html(
title=f'{app.title}: ReDoc',
openapi_url=app.openapi_url,
redoc_favicon_url='/file=html/favicon.svg',
redoc_favicon_url='/file=ui/assets/favicon.svg',
)
return res
+1 -1
View File
@@ -73,7 +73,7 @@ def setup_middleware(app: FastAPI, cmd_opts):
}
if err['code'] == 401 and 'file=' in req.url.path: # dont spam with unauth
return JSONResponse(status_code=err['code'], content=jsonable_encoder(err))
if err['code'] == 404 and 'file=html/' in req.url.path: # dont spam with locales
if err['code'] == 404 and 'file=ui/' in req.url.path: # dont spam with locales
return JSONResponse(status_code=err['code'], content=jsonable_encoder(err))
if err["code"] == 429: # dont spam with rate limit errors
return JSONResponse(status_code=err["code"], content=jsonable_encoder(err))
+3 -1
View File
@@ -1,8 +1,10 @@
# VQA Detection Utilities
# Parsing, formatting, and drawing functions for detection results (points, bboxes, gaze)
import os
from PIL import Image, ImageDraw, ImageFont
from modules import shared
from modules.paths import script_path
def parse_points(result) -> list:
@@ -311,7 +313,7 @@ def draw_bounding_boxes(image: Image.Image, detections: list, points: list | Non
# Try to load a font, fall back to default if unavailable
try:
font_size = max(12, int(min(width, height) * 0.02))
font_path = shared.opts.font or "javascript/notosans-nerdfont-regular.ttf"
font_path = shared.opts.font or os.path.join(script_path, "ui", "fonts", "notosans-nerdfont-regular.ttf")
font = ImageFont.truetype(font_path, size=font_size)
except Exception:
font = ImageFont.load_default()
+1 -1
View File
@@ -96,7 +96,7 @@ def create_model_cards(all_models: list[CivitModel]) -> str:
if image.url and not image.url.lower().endswith('.mp4'):
previews.append(image.url)
if not previews:
previews = ['/sdapi/v1/network/thumb?filename=html/missing.png']
previews = ['/sdapi/v1/network/thumb?filename=ui/assets/missing.png']
all_cards += card.format(id=model.id, name=model.name, type=model.type, preview=previews[0])
html = details + cards.format(cards=all_cards)
return html
+4 -2
View File
@@ -1,3 +1,4 @@
import os
import math
from typing import NamedTuple
@@ -6,6 +7,7 @@ from PIL import Image, ImageDraw, ImageFont
from modules import script_callbacks, shared
from modules.logger import log
from modules.paths import script_path
class Grid(NamedTuple):
@@ -143,9 +145,9 @@ class GridAnnotation:
def get_font(fontsize: float):
try:
return ImageFont.truetype(shared.opts.font or "javascript/notosans-nerdfont-regular.ttf", fontsize)
return ImageFont.truetype(shared.opts.font or os.path.join(script_path, "ui", "fonts", "notosans-nerdfont-regular.ttf"), fontsize)
except Exception:
return ImageFont.truetype("javascript/notosans-nerdfont-regular.ttf", fontsize)
return ImageFont.truetype(os.path.join(script_path, "ui", "fonts", "notosans-nerdfont-regular.ttf"), fontsize)
def draw_grid_annotations(im: Image.Image, width: int, height: int, x_texts: list[list[GridAnnotation]], y_texts: list[list[GridAnnotation]], margin=0, title: list[GridAnnotation] | None = None):
-7
View File
@@ -1,9 +1,2 @@
def hijack_transformers():
# transformers>=4.56 flattened CLIPTextModel internals; diffusers single-file loader still expects `text_model`.
return
try:
import transformers
if hasattr(transformers, 'CLIPTextModel') and not hasattr(transformers.CLIPTextModel, 'text_model'):
transformers.CLIPTextModel.text_model = property(lambda self: self)
except Exception:
pass
+3 -3
View File
@@ -4,7 +4,7 @@ import logging
import torch
from modules import shared, errors, devices, sd_models, sd_models_utils
from modules.logger import log
from installer import setup_logging, install
from installer import setup_logging
debug = os.environ.get('SD_COMPILE_DEBUG', None) is not None
debug_log = log.trace if debug else lambda *args, **kwargs: None
@@ -87,8 +87,8 @@ def optimize_openvino(sd_model, clear_cache=True):
def compile_pruna(sd_model):
# TODO
# install('pruna') # TODO pruna: enable when it supports transformers==5.5
# TODO pruna: enable when it supports transformers==5.5
# install('pruna')
"""
from pruna import smash, SmashConfig
smash_config = SmashConfig(["deepcache", "stable_fast"])
+1 -1
View File
@@ -348,7 +348,7 @@ class StyleDatabase:
for fn in style_files:
future_items[executor.submit(self.load_style, fn, None)] = fn
if self.built_in:
fn = os.path.join('html', 'art-styles.json')
fn = os.path.join('data', 'art-styles.json')
future_items[executor.submit(self.load_style, fn, 'Reference')] = fn
for future in concurrent.futures.as_completed(future_items):
future.result()
+4 -1
View File
@@ -19,7 +19,10 @@ gradio_theme = gr.themes.Base()
def list_builtin_themes():
files = [os.path.splitext(f)[0] for f in os.listdir('javascript') if f.endswith('.css') and f not in ['base.css', 'sdnext.css', 'style.css']]
from modules.paths import script_path
folder = os.path.join(script_path, "ui", "css")
exclude = ['base.css', 'sdnext.css', 'style.css', 'timesheet.css', 'swagger.css']
files = [os.path.splitext(f)[0] for f in os.listdir(folder) if f.endswith('.css') and f not in exclude]
return files
+1 -1
View File
@@ -564,7 +564,7 @@ def create_settings(cmd_opts):
"live_preview_downscale": OptionInfo(True, "Downscale high resolution live previews"),
"notification_audio_enable": OptionInfo(False, "Play a notification upon completion"),
"notification_audio_path": OptionInfo("html/notification.mp3","Path to notification sound", component_args=hide_dirs, folder=True),
"notification_audio_path": OptionInfo("ui/assets/notification.mp3","Path to notification sound", component_args=hide_dirs, folder=True),
}))
# --- Postprocessing ---
+10 -10
View File
@@ -61,8 +61,8 @@ def init_api():
if filename is None or len(filename) == 0:
return JSONResponse({ "error": "no filename" }, status_code=400)
if not os.path.exists(filename) or not os.path.isfile(filename) or os.path.getsize(filename) == 0:
return FileResponse('html/missing.png', headers={"Accept-Ranges": "bytes"})
if filename.startswith('html/') or filename.startswith('models/'):
return FileResponse('ui/assets/missing.png', headers={"Accept-Ranges": "bytes"})
if filename.startswith('html/') or filename.startswith('models/') or filename.startswith('data/') or filename.startswith('ui/'):
return FileResponse(filename, headers={"Accept-Ranges": "bytes"})
if not any(Path(folder).absolute() in Path(filename).absolute().parents for folder in allowed_dirs):
return JSONResponse({ "error": f"file {filename}: must be in one of allowed directories" }, status_code=403)
@@ -414,7 +414,7 @@ class ExtraNetworksPage:
"filename": html.escape(item.get('filename', ''), quote=True),
"short": os.path.splitext(os.path.basename(item.get('filename', '')))[0],
"tags": '|'.join([item.get('tags')] if isinstance(item.get('tags', {}), str) else list(item.get('tags', {}).keys())),
"preview": html.escape(item.get('preview', None) or self.link_preview('html/missing.png')),
"preview": html.escape(item.get('preview', None) or self.link_preview('ui/assets/missing.png')),
"width": 'var(--card-size)',
"height": 'var(--card-size)' if shared.opts.extra_networks_card_square else 'auto',
"fit": shared.opts.extra_networks_card_fit,
@@ -440,7 +440,7 @@ class ExtraNetworksPage:
def find_preview_file(self, path: str | None):
if path is None:
return 'html/missing.png'
return 'ui/assets/missing.png'
if os.path.join('models', 'Reference') in path:
return path
exts = ["jpg", "jpeg", "png", "webp", "tiff", "jp2", "jxl"]
@@ -457,7 +457,7 @@ class ExtraNetworksPage:
if '.thumb.' not in file:
self.missing_thumbs.append(file)
return file
return 'html/missing.png'
return 'ui/assets/missing.png'
def find_preview(self, filename: str):
t0 = time.time()
@@ -508,7 +508,7 @@ class ExtraNetworksPage:
item['preview'] = self.link_preview(found)
debug(f'EN mapped-preview: {item["name"]}={found}')
if item.get('preview', None) is None:
item['preview'] = self.link_preview('html/missing.png')
item['preview'] = self.link_preview('ui/assets/missing.png')
debug(f'EN missing-preview: {item["name"]}')
self.preview_time += time.time() - t0
@@ -787,19 +787,19 @@ def create_ui(container, button_parent: gr.Button, tabname: str, skip_indexing =
def fn_save_img(image):
if ui.last_item is None or ui.last_item.local_preview is None:
return 'html/missing.png'
return 'ui/assets/missing.png'
images = []
if ui.gallery is not None:
images = list(ui.gallery.temp_files) # gallery cannot be used as input component so looking at most recently registered temp files
if len(images) < 1:
log.warning(f'Network no image: item="{ui.last_item.name}"')
return 'html/missing.png'
return 'ui/assets/missing.png'
try:
images.sort(key=lambda f: os.path.getmtime(f), reverse=True)
image = Image.open(images[0])
except Exception as e:
log.error(f'Network error opening image: item="{ui.last_item.name}" {e}')
return 'html/missing.png'
return 'ui/assets/missing.png'
fn_delete_img(image)
if image.width > 512 or image.height > 512:
image = image.convert('RGB')
@@ -818,7 +818,7 @@ def create_ui(container, button_parent: gr.Button, tabname: str, skip_indexing =
if os.path.exists(file):
os.remove(file)
log.debug(f'Network delete image: item="{ui.last_item.name}" filename="{file}"')
return 'html/missing.png'
return 'ui/assets/missing.png'
def fn_save_desc(desc):
if hasattr(ui.last_item, 'type') and ui.last_item.type == 'Style':
+1 -1
View File
@@ -114,7 +114,7 @@ class ExtraNetworkStyles(extra_networks.ExtraNetwork):
super().__init__('style')
self.indexes = {}
def activate(self, p, params_list):
def activate(self, p, params_list, *args, **kwargs):
for param in params_list:
if len(param.items) > 0:
style = None
+14 -10
View File
@@ -19,16 +19,17 @@ def webpath(fn):
def html_head():
head = ''
main = ['script.js']
main = ['sdnext.mjs']
skip = ['login.js']
for js in main:
script_js = os.path.join(script_path, "javascript", js)
# script_js = os.path.join(script_path, 'javascript', js)
script_js = os.path.join(script_path, "ui", "dist", js)
if '.esm' in js or '.mjs' in js:
head += f'<script type="module" src="{webpath(script_js)}"></script>\n'
else:
head += f'<script type="text/javascript" src="{webpath(script_js)}"></script>\n'
added = []
for script in scripts_manager.list_scripts("javascript", ".js"):
for script in scripts_manager.list_scripts('javascript', ".js"):
if script.filename in main or script.filename in skip:
continue
if '.esm' in script.filename or '.mjs' in script.filename:
@@ -36,7 +37,7 @@ def html_head():
else:
head += f'<script type="text/javascript" src="{webpath(script.path)}"></script>\n'
added.append(script.path)
for script in scripts_manager.list_scripts("javascript", ".mjs"):
for script in scripts_manager.list_scripts('javascript', ".mjs"):
head += f'<script type="module" src="{webpath(script.path)}"></script>\n'
added.append(script.path)
added = [a.replace(script_path, '').replace('\\', '/') for a in added]
@@ -54,7 +55,8 @@ def html_body():
def html_login():
fn = os.path.join(script_path, "javascript", "login.js")
# fn = os.path.join(script_path, 'javascript', 'login.js')
fn = os.path.join(script_path, "ui", "js", "login.js")
with open(fn, encoding='utf8') as f:
inline = f.read()
js = f'<script type="text/javascript">{inline}</script>\n'
@@ -67,7 +69,8 @@ def html_css(css: list[str]):
head = ''
for cssfile in css:
f = os.path.join(script_path, 'javascript', cssfile)
# f = os.path.join(script_path, 'javascript', cssfile)
f = os.path.join(script_path, 'ui', 'css', cssfile)
if os.path.isfile(f):
head += stylesheet(f)
for cssfile in scripts_manager.list_files_with_name("style.css"):
@@ -77,7 +80,8 @@ def html_css(css: list[str]):
usercss = os.path.join(data_path, "user.css") if os.path.exists(os.path.join(data_path, "user.css")) else None
if shared.opts.theme_type == 'Standard':
themecss = os.path.join(script_path, "javascript", f"{shared.opts.gradio_theme}.css")
# themecss = os.path.join(script_path, 'javascript', f"{shared.opts.gradio_theme}.css")
themecss = os.path.join(script_path, 'ui', 'css', f"{shared.opts.gradio_theme}.css")
if os.path.exists(themecss):
head += stylesheet(themecss)
log.debug(f'UI theme: css="{themecss}" base="{css}" user="{usercss}"')
@@ -98,7 +102,7 @@ def html_css(css: list[str]):
def reload_javascript():
title = '<title>SD.Next</title>'
manifest = f'<link rel="manifest" href="{webpath(os.path.join(script_path, "html", "manifest.json"))}">'
manifest = f'<link rel="manifest" href="{webpath(os.path.join(script_path, "ui", "manifest", "manifest.json"))}">'
login = html_login()
js = html_head()
@@ -121,8 +125,8 @@ def reload_javascript():
for line in lines:
if 'meta name="twitter:' in line:
res.body = res.body.replace(line.encode("utf8"), b'')
if 'iframeResizer.contentWindow.min.js' in line:
res.body = res.body.replace(line.encode("utf8"), b'src="file=javascript/iframeResizer.min.js"')
if 'iframeResizer.contentWindow' in line:
res.body = res.body.replace(line.encode("utf8"), b'src="file=ui/js/iframeResizer.js"')
res.init_headers()
return res