dikdimon commited on
Commit
d2e1244
·
verified ·
1 Parent(s): 0efab99

Upload ui_gradio_extensions.py

Browse files
Files changed (1) hide show
  1. ui_gradio_extensions.py +322 -0
ui_gradio_extensions.py ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import html
2
+ import json
3
+ import os
4
+ import gradio as gr
5
+
6
+ from modules import localization, shared, scripts, util
7
+ from modules.paths import script_path, data_path
8
+
9
+
10
+ # JS load policy:
11
+ # core : always sync (A1111 core, needed for set_theme/onUiLoaded/gradioApp)
12
+ # fix : always sync (safety/diagnostics layer should initialize early)
13
+ # builtin extension: sync by default for compatibility
14
+ # user extension : sync by default for compatibility; mixed/defer are opt-in
15
+ #
16
+ # Environment overrides:
17
+ # A1111_EXTENSION_JS_MODE = sync|mixed|safe|compat|defer
18
+ # A1111_EXTENSION_JS_SYNC_NAMES = comma-separated substrings / basenames
19
+ # A1111_EXTENSION_JS_DEFER_NAMES = comma-separated substrings / basenames
20
+ #
21
+ # Notes:
22
+ # - default is `sync` because preserving stock-like execution order is safer.
23
+ # - this file preserves the original order from scripts.list_scripts() and only
24
+ # changes per-script loading attributes when explicitly requested.
25
+ # - unlike v4.0, loader diagnostics are seeded at injection time from a manifest,
26
+ # so "pending" reflects what was actually injected into the page, not only what
27
+ # later fired onload/onerror.
28
+
29
+ DEFAULT_EXTENSION_JS_MODE = "sync"
30
+ _VALID_EXTENSION_JS_MODES = {"mixed", "sync", "defer"}
31
+ _FIX_MARKERS = (
32
+ "0000-a1111-fix",
33
+ "!0000-a1111-fix",
34
+ "fix.js",
35
+ )
36
+
37
+
38
+ def webpath(fn):
39
+ return f'file={util.truncate_path(fn)}?{os.path.getmtime(fn)}'
40
+
41
+
42
+ def _env_csv(name):
43
+ raw = os.environ.get(name, "")
44
+ return [item.strip().lower() for item in raw.split(",") if item.strip()]
45
+
46
+
47
+ def _normalized_lower_path(path):
48
+ return os.path.normpath(path).lower().replace("\\", "/")
49
+
50
+
51
+ def _script_kind(script):
52
+ path = os.path.normpath(script.path)
53
+ basedir = os.path.normpath(getattr(script, "basedir", "") or "")
54
+ lower_path = _normalized_lower_path(path)
55
+ lower_basedir = _normalized_lower_path(basedir)
56
+
57
+ if basedir == os.path.normpath(script_path):
58
+ return "core"
59
+
60
+ if any(marker in lower_path or marker in lower_basedir for marker in _FIX_MARKERS):
61
+ return "fix_extension"
62
+
63
+ if "/extensions-builtin/" in lower_path or "/extensions-builtin/" in lower_basedir:
64
+ return "builtin_extension"
65
+
66
+ return "user_extension"
67
+
68
+
69
+ def _matches_override(script, patterns):
70
+ if not patterns:
71
+ return False
72
+
73
+ haystacks = {
74
+ os.path.basename(script.path).lower(),
75
+ _normalized_lower_path(script.path),
76
+ _normalized_lower_path(getattr(script, "basedir", "") or ""),
77
+ }
78
+
79
+ return any(any(pattern in hay for hay in haystacks) for pattern in patterns)
80
+
81
+
82
+ def _get_extension_mode():
83
+ mode = os.environ.get("A1111_EXTENSION_JS_MODE", DEFAULT_EXTENSION_JS_MODE).strip().lower()
84
+ aliases = {
85
+ "safe": "sync",
86
+ "compat": "sync",
87
+ }
88
+ mode = aliases.get(mode, mode or DEFAULT_EXTENSION_JS_MODE)
89
+ if mode not in _VALID_EXTENSION_JS_MODES:
90
+ return DEFAULT_EXTENSION_JS_MODE
91
+ return mode
92
+
93
+
94
+ def _script_load_mode(script):
95
+ kind = _script_kind(script)
96
+ mode = _get_extension_mode()
97
+
98
+ if kind in {"core", "fix_extension"}:
99
+ return "sync", kind
100
+
101
+ sync_overrides = _env_csv("A1111_EXTENSION_JS_SYNC_NAMES")
102
+ defer_overrides = _env_csv("A1111_EXTENSION_JS_DEFER_NAMES")
103
+
104
+ if _matches_override(script, sync_overrides):
105
+ return "sync", kind
106
+
107
+ if _matches_override(script, defer_overrides):
108
+ return "defer", kind
109
+
110
+ if mode == "sync":
111
+ return "sync", kind
112
+
113
+ if mode == "defer":
114
+ return "defer", kind
115
+
116
+ # mixed (opt-in): builtin extensions stay sync, user extensions defer
117
+ if kind == "builtin_extension":
118
+ return "sync", kind
119
+
120
+ return "defer", kind
121
+
122
+
123
+ def _build_script_entries():
124
+ entries = []
125
+
126
+ script_js = os.path.join(script_path, "script.js")
127
+ entries.append({
128
+ "src": webpath(script_js),
129
+ "role": "core",
130
+ "mode": "sync",
131
+ "path": script_js,
132
+ "basedir": script_path,
133
+ "module": False,
134
+ })
135
+
136
+ seen_script_paths = {os.path.normpath(script_js)}
137
+ for script in scripts.list_scripts("javascript", ".js"):
138
+ norm_path = os.path.normpath(script.path)
139
+ if norm_path in seen_script_paths:
140
+ continue
141
+ seen_script_paths.add(norm_path)
142
+
143
+ load_mode, role = _script_load_mode(script)
144
+ entries.append({
145
+ "src": webpath(script.path),
146
+ "role": role,
147
+ "mode": load_mode,
148
+ "path": script.path,
149
+ "basedir": getattr(script, "basedir", "") or "",
150
+ "module": False,
151
+ })
152
+
153
+ seen_module_paths = set()
154
+ for script in scripts.list_scripts("javascript", ".mjs"):
155
+ norm_path = os.path.normpath(script.path)
156
+ if norm_path in seen_module_paths:
157
+ continue
158
+ seen_module_paths.add(norm_path)
159
+
160
+ role = _script_kind(script)
161
+ entries.append({
162
+ "src": webpath(script.path),
163
+ "role": role,
164
+ "mode": "module",
165
+ "path": script.path,
166
+ "basedir": getattr(script, "basedir", "") or "",
167
+ "module": True,
168
+ })
169
+
170
+ return entries
171
+
172
+
173
+ def _script_registry_bootstrap_html(entries):
174
+ manifest = {
175
+ "mode": _get_extension_mode(),
176
+ "scripts": {
177
+ entry["src"]: {
178
+ "role": entry["role"],
179
+ "mode": entry["mode"],
180
+ "path": entry["path"],
181
+ "basedir": entry["basedir"],
182
+ "injected": True,
183
+ }
184
+ for entry in entries
185
+ },
186
+ "loaded": {},
187
+ "errors": {},
188
+ "injected_order": [entry["src"] for entry in entries],
189
+ }
190
+
191
+ manifest_json = json.dumps(manifest, ensure_ascii=False)
192
+
193
+ return (
194
+ '<script type="text/javascript">\n'
195
+ '(function(){\n'
196
+ f' var manifest = {manifest_json};\n'
197
+ ' var reg = window.__a1111ScriptRegistry = window.__a1111ScriptRegistry || manifest;\n'
198
+ ' reg.mode = manifest.mode;\n'
199
+ ' reg.scripts = reg.scripts || {};\n'
200
+ ' reg.loaded = reg.loaded || {};\n'
201
+ ' reg.errors = reg.errors || {};\n'
202
+ ' reg.injected_order = reg.injected_order || [];\n'
203
+ ' for (var src in manifest.scripts) {\n'
204
+ ' if (!Object.prototype.hasOwnProperty.call(manifest.scripts, src)) continue;\n'
205
+ ' reg.scripts[src] = reg.scripts[src] || manifest.scripts[src];\n'
206
+ ' }\n'
207
+ ' for (var i = 0; i < manifest.injected_order.length; i++) {\n'
208
+ ' var injectedSrc = manifest.injected_order[i];\n'
209
+ ' if (!reg.injected_order.includes(injectedSrc)) {\n'
210
+ ' reg.injected_order.push(injectedSrc);\n'
211
+ ' }\n'
212
+ ' }\n'
213
+ ' window.__a1111RegisterScript = window.__a1111RegisterScript || function(el){\n'
214
+ ' if (!el) return;\n'
215
+ ' var src = el.getAttribute("src") || "";\n'
216
+ ' reg.scripts[src] = reg.scripts[src] || {\n'
217
+ ' role: el.getAttribute("data-a1111-role") || "unknown",\n'
218
+ ' mode: el.getAttribute("data-a1111-mode") || "unknown",\n'
219
+ ' path: el.getAttribute("data-a1111-path") || src,\n'
220
+ ' basedir: el.getAttribute("data-a1111-basedir") || "",\n'
221
+ ' injected: true\n'
222
+ ' };\n'
223
+ ' if (!reg.injected_order.includes(src)) {\n'
224
+ ' reg.injected_order.push(src);\n'
225
+ ' }\n'
226
+ ' };\n'
227
+ ' window.__a1111MarkScriptLoaded = window.__a1111MarkScriptLoaded || function(el){\n'
228
+ ' if (!el) return;\n'
229
+ ' window.__a1111RegisterScript(el);\n'
230
+ ' var src = el.getAttribute("src") || "";\n'
231
+ ' reg.loaded[src] = true;\n'
232
+ ' };\n'
233
+ ' window.__a1111MarkScriptError = window.__a1111MarkScriptError || function(el){\n'
234
+ ' if (!el) return;\n'
235
+ ' window.__a1111RegisterScript(el);\n'
236
+ ' var src = el.getAttribute("src") || "";\n'
237
+ ' reg.errors[src] = true;\n'
238
+ ' console.error("[a1111-loader] script load failed:", el.getAttribute("data-a1111-path") || src);\n'
239
+ ' };\n'
240
+ '})();\n'
241
+ '</script>\n'
242
+ )
243
+
244
+
245
+ def _script_tag(src, *, role, mode, path, basedir, module=False):
246
+ attrs = [
247
+ 'type="module"' if module else 'type="text/javascript"',
248
+ f'src="{html.escape(src, quote=True)}"',
249
+ f'data-a1111-role="{html.escape(role, quote=True)}"',
250
+ f'data-a1111-mode="{html.escape(mode, quote=True)}"',
251
+ f'data-a1111-path="{html.escape(path, quote=True)}"',
252
+ f'data-a1111-basedir="{html.escape(basedir, quote=True)}"',
253
+ 'onload="window.__a1111MarkScriptLoaded&&window.__a1111MarkScriptLoaded(this)"',
254
+ 'onerror="window.__a1111MarkScriptError&&window.__a1111MarkScriptError(this)"',
255
+ ]
256
+ if not module and mode == "defer":
257
+ attrs.append("defer")
258
+ return f'<script {" ".join(attrs)}></script>\n'
259
+
260
+
261
+ def javascript_html():
262
+ # Localize first so extensions that read localization see it immediately.
263
+ head = f'<script type="text/javascript">{localization.localization_js(shared.opts.localization)}</script>\n'
264
+
265
+ entries = _build_script_entries()
266
+
267
+ # Bootstrap loader diagnostics before any external scripts.
268
+ head += _script_registry_bootstrap_html(entries)
269
+
270
+ for entry in entries:
271
+ head += _script_tag(
272
+ entry["src"],
273
+ role=entry["role"],
274
+ mode=entry["mode"],
275
+ path=entry["path"],
276
+ basedir=entry["basedir"],
277
+ module=entry["module"],
278
+ )
279
+
280
+ if shared.cmd_opts.theme:
281
+ head += f'<script type="text/javascript">set_theme("{shared.cmd_opts.theme}");</script>\n'
282
+
283
+ return head
284
+
285
+
286
+ def css_html():
287
+ head = ""
288
+
289
+ def stylesheet(fn):
290
+ return f'<link rel="stylesheet" property="stylesheet" href="{webpath(fn)}">'
291
+
292
+ for cssfile in scripts.list_files_with_name("style.css"):
293
+ head += stylesheet(cssfile)
294
+
295
+ user_css = os.path.join(data_path, "user.css")
296
+ if os.path.exists(user_css):
297
+ head += stylesheet(user_css)
298
+
299
+ from modules.shared_gradio_themes import resolve_var
300
+ light = resolve_var('background_fill_primary')
301
+ dark = resolve_var('background_fill_primary_dark')
302
+ head += f'<style>html {{ background-color: {light}; }} @media (prefers-color-scheme: dark) {{ html {{background-color: {dark}; }} }}</style>'
303
+
304
+ return head
305
+
306
+
307
+ def reload_javascript():
308
+ js = javascript_html()
309
+ css = css_html()
310
+
311
+ def template_response(*args, **kwargs):
312
+ res = shared.GradioTemplateResponseOriginal(*args, **kwargs)
313
+ res.body = res.body.replace(b'</head>', f'{js}<meta name="referrer" content="no-referrer"/></head>'.encode("utf8"))
314
+ res.body = res.body.replace(b'</body>', f'{css}</body>'.encode("utf8"))
315
+ res.init_headers()
316
+ return res
317
+
318
+ gr.routes.templates.TemplateResponse = template_response
319
+
320
+
321
+ if not hasattr(shared, 'GradioTemplateResponseOriginal'):
322
+ shared.GradioTemplateResponseOriginal = gr.routes.templates.TemplateResponse