Spaces:
Running on Zero
Running on Zero
feat(ui): soft dark restraint redesign — native gr.Radio + accordion + lora toggle
Browse filesDrops the custom model-card grid, the popover tooltip pattern, the JS shim,
and the decorative CSS. Uses Gradio-native shapes throughout (gr.Radio for
Base/Turbo, gr.Checkbox to toggle LoRA visibility, gr.Accordion for advanced
width/height/seed, info= subtitle text in place of the (i) icon).
Adds the ControlNet preprocessor preview slot (Canny / Depth / Pose render
live as the user changes the dropdown).
Single output meta under the image (the prior mockup duplicated it inside
Advanced).
- app.py +69 -34
- tests/test_app.py +16 -0
- tests/test_theme.py +26 -45
- tests/test_ui.py +51 -43
- theme.py +96 -184
- ui.py +229 -124
app.py
CHANGED
|
@@ -24,6 +24,7 @@ import gradio as gr
|
|
| 24 |
import backend
|
| 25 |
import lora as lora_mod
|
| 26 |
import models
|
|
|
|
| 27 |
import theme
|
| 28 |
import ui
|
| 29 |
|
|
@@ -93,12 +94,26 @@ def _coerce_lora(lora_path: str | None) -> Path | None:
|
|
| 93 |
|
| 94 |
|
| 95 |
def _on_model_change(model_name: str) -> tuple[int, float]:
|
| 96 |
-
"""When the user
|
| 97 |
if model_name == "Base":
|
| 98 |
return 25, 4.0
|
| 99 |
return 8, 1.0 # Turbo
|
| 100 |
|
| 101 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
def _esrgan_path() -> str:
|
| 103 |
"""Locate the preloaded RealESRGAN_x4plus.pth."""
|
| 104 |
from huggingface_hub import hf_hub_download
|
|
@@ -115,10 +130,13 @@ def on_t2i_generate(
|
|
| 115 |
width,
|
| 116 |
height,
|
| 117 |
seed,
|
|
|
|
| 118 |
lora_path,
|
| 119 |
lora_strength,
|
| 120 |
progress=gr.Progress(track_tqdm=True), # noqa: B008
|
| 121 |
):
|
|
|
|
|
|
|
| 122 |
try:
|
| 123 |
lora_p = _coerce_lora(lora_path)
|
| 124 |
except lora_mod.LoRAValidationError as e:
|
|
@@ -147,10 +165,13 @@ def on_controlnet_generate(
|
|
| 147 |
controlnet_scale,
|
| 148 |
steps,
|
| 149 |
seed,
|
|
|
|
| 150 |
lora_path,
|
| 151 |
lora_strength,
|
| 152 |
progress=gr.Progress(track_tqdm=True), # noqa: B008
|
| 153 |
):
|
|
|
|
|
|
|
| 154 |
try:
|
| 155 |
lora_p = _coerce_lora(lora_path)
|
| 156 |
except lora_mod.LoRAValidationError as e:
|
|
@@ -176,10 +197,13 @@ def on_upscale_generate(
|
|
| 176 |
refine_steps,
|
| 177 |
refine_denoise,
|
| 178 |
seed,
|
|
|
|
| 179 |
lora_path,
|
| 180 |
lora_strength,
|
| 181 |
progress=gr.Progress(track_tqdm=True), # noqa: B008
|
| 182 |
):
|
|
|
|
|
|
|
| 183 |
try:
|
| 184 |
lora_p = _coerce_lora(lora_path)
|
| 185 |
except lora_mod.LoRAValidationError as e:
|
|
@@ -203,42 +227,16 @@ def on_upscale_generate(
|
|
| 203 |
|
| 204 |
HEADER_HTML = """
|
| 205 |
<div style="display:flex;justify-content:space-between;align-items:baseline;padding:8px 0 4px 0;">
|
| 206 |
-
<div style="font-
|
| 207 |
-
z<span
|
| 208 |
</div>
|
| 209 |
-
<div class="zis-status">ready</div>
|
| 210 |
</div>
|
| 211 |
""".strip()
|
| 212 |
|
| 213 |
|
| 214 |
-
_HEAD_JS = """
|
| 215 |
-
<script>
|
| 216 |
-
window.zis = {
|
| 217 |
-
setModel: function(name) {
|
| 218 |
-
document.querySelectorAll('.zis-model').forEach(el => {
|
| 219 |
-
el.classList.toggle('on', el.dataset.value === name);
|
| 220 |
-
});
|
| 221 |
-
const hidden = document.querySelector('#zis-model-state textarea, #zis-model-state input');
|
| 222 |
-
if (hidden) {
|
| 223 |
-
hidden.value = name;
|
| 224 |
-
hidden.dispatchEvent(new Event('input', { bubbles: true }));
|
| 225 |
-
}
|
| 226 |
-
}
|
| 227 |
-
};
|
| 228 |
-
// Tap-to-pin tooltips on mobile
|
| 229 |
-
document.addEventListener('touchstart', function(e) {
|
| 230 |
-
const tip = e.target.closest('.zis-info');
|
| 231 |
-
document.querySelectorAll('.zis-info.shown').forEach(el => {
|
| 232 |
-
if (el !== tip) el.classList.remove('shown');
|
| 233 |
-
});
|
| 234 |
-
if (tip) tip.classList.toggle('shown');
|
| 235 |
-
}, { passive: true });
|
| 236 |
-
</script>
|
| 237 |
-
""".strip()
|
| 238 |
-
|
| 239 |
-
|
| 240 |
def build_app() -> gr.Blocks:
|
| 241 |
-
with gr.Blocks(theme=theme.build_theme(), css=theme.CSS,
|
| 242 |
gr.HTML(HEADER_HTML)
|
| 243 |
|
| 244 |
with gr.Tabs():
|
|
@@ -249,22 +247,35 @@ def build_app() -> gr.Blocks:
|
|
| 249 |
inputs=[
|
| 250 |
t["prompt"],
|
| 251 |
t["negative_prompt"],
|
| 252 |
-
t["
|
| 253 |
t["steps"],
|
| 254 |
t["cfg"],
|
| 255 |
t["width"],
|
| 256 |
t["height"],
|
| 257 |
t["seed"],
|
|
|
|
| 258 |
t["lora_path"],
|
| 259 |
t["lora_strength"],
|
| 260 |
],
|
| 261 |
outputs=[t["output_image"], t["output_meta"]],
|
| 262 |
)
|
| 263 |
-
|
|
|
|
| 264 |
fn=_on_model_change,
|
| 265 |
-
inputs=[t["
|
| 266 |
outputs=[t["steps"], t["cfg"]],
|
| 267 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
|
| 269 |
with gr.Tab("ControlNet"):
|
| 270 |
c = ui.build_controlnet_tab()
|
|
@@ -277,11 +288,29 @@ def build_app() -> gr.Blocks:
|
|
| 277 |
c["controlnet_scale"],
|
| 278 |
c["steps"],
|
| 279 |
c["seed"],
|
|
|
|
| 280 |
c["lora_path"],
|
| 281 |
c["lora_strength"],
|
| 282 |
],
|
| 283 |
outputs=[c["output_image"], c["output_meta"]],
|
| 284 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
|
| 286 |
with gr.Tab("Upscale"):
|
| 287 |
u = ui.build_upscale_tab()
|
|
@@ -293,11 +322,17 @@ def build_app() -> gr.Blocks:
|
|
| 293 |
u["refine_steps"],
|
| 294 |
u["refine_denoise"],
|
| 295 |
u["seed"],
|
|
|
|
| 296 |
u["lora_path"],
|
| 297 |
u["lora_strength"],
|
| 298 |
],
|
| 299 |
outputs=[u["output_image"], u["output_meta"]],
|
| 300 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
return demo
|
| 302 |
|
| 303 |
|
|
|
|
| 24 |
import backend
|
| 25 |
import lora as lora_mod
|
| 26 |
import models
|
| 27 |
+
import preprocessors
|
| 28 |
import theme
|
| 29 |
import ui
|
| 30 |
|
|
|
|
| 94 |
|
| 95 |
|
| 96 |
def _on_model_change(model_name: str) -> tuple[int, float]:
|
| 97 |
+
"""When the user picks Base / Turbo in the radio, update steps + cfg defaults."""
|
| 98 |
if model_name == "Base":
|
| 99 |
return 25, 4.0
|
| 100 |
return 8, 1.0 # Turbo
|
| 101 |
|
| 102 |
|
| 103 |
+
def _preview_cn(image, mode):
|
| 104 |
+
"""Render the live preprocessor preview next to the input on the ControlNet tab.
|
| 105 |
+
|
| 106 |
+
Wrapped in ``try/except`` so that a missing optional dep (controlnet_aux for
|
| 107 |
+
Depth / Pose) never breaks the form — it just falls back to the raw input.
|
| 108 |
+
"""
|
| 109 |
+
if image is None:
|
| 110 |
+
return None
|
| 111 |
+
try:
|
| 112 |
+
return preprocessors.run(mode, image)
|
| 113 |
+
except Exception:
|
| 114 |
+
return image
|
| 115 |
+
|
| 116 |
+
|
| 117 |
def _esrgan_path() -> str:
|
| 118 |
"""Locate the preloaded RealESRGAN_x4plus.pth."""
|
| 119 |
from huggingface_hub import hf_hub_download
|
|
|
|
| 130 |
width,
|
| 131 |
height,
|
| 132 |
seed,
|
| 133 |
+
lora_enabled,
|
| 134 |
lora_path,
|
| 135 |
lora_strength,
|
| 136 |
progress=gr.Progress(track_tqdm=True), # noqa: B008
|
| 137 |
):
|
| 138 |
+
if not lora_enabled:
|
| 139 |
+
lora_path = None
|
| 140 |
try:
|
| 141 |
lora_p = _coerce_lora(lora_path)
|
| 142 |
except lora_mod.LoRAValidationError as e:
|
|
|
|
| 165 |
controlnet_scale,
|
| 166 |
steps,
|
| 167 |
seed,
|
| 168 |
+
lora_enabled,
|
| 169 |
lora_path,
|
| 170 |
lora_strength,
|
| 171 |
progress=gr.Progress(track_tqdm=True), # noqa: B008
|
| 172 |
):
|
| 173 |
+
if not lora_enabled:
|
| 174 |
+
lora_path = None
|
| 175 |
try:
|
| 176 |
lora_p = _coerce_lora(lora_path)
|
| 177 |
except lora_mod.LoRAValidationError as e:
|
|
|
|
| 197 |
refine_steps,
|
| 198 |
refine_denoise,
|
| 199 |
seed,
|
| 200 |
+
lora_enabled,
|
| 201 |
lora_path,
|
| 202 |
lora_strength,
|
| 203 |
progress=gr.Progress(track_tqdm=True), # noqa: B008
|
| 204 |
):
|
| 205 |
+
if not lora_enabled:
|
| 206 |
+
lora_path = None
|
| 207 |
try:
|
| 208 |
lora_p = _coerce_lora(lora_path)
|
| 209 |
except lora_mod.LoRAValidationError as e:
|
|
|
|
| 227 |
|
| 228 |
HEADER_HTML = """
|
| 229 |
<div style="display:flex;justify-content:space-between;align-items:baseline;padding:8px 0 4px 0;">
|
| 230 |
+
<div style="font-size:16px;font-weight:600;letter-spacing:-0.01em;">
|
| 231 |
+
z-image-studio<span class="zis-brand-period">.</span>
|
| 232 |
</div>
|
| 233 |
+
<div class="zis-status-dot" style="font-size:11px;color:#988B7C;letter-spacing:0.02em;">ready</div>
|
| 234 |
</div>
|
| 235 |
""".strip()
|
| 236 |
|
| 237 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
def build_app() -> gr.Blocks:
|
| 239 |
+
with gr.Blocks(theme=theme.build_theme(), css=theme.CSS, title="z-image-studio") as demo:
|
| 240 |
gr.HTML(HEADER_HTML)
|
| 241 |
|
| 242 |
with gr.Tabs():
|
|
|
|
| 247 |
inputs=[
|
| 248 |
t["prompt"],
|
| 249 |
t["negative_prompt"],
|
| 250 |
+
t["model"],
|
| 251 |
t["steps"],
|
| 252 |
t["cfg"],
|
| 253 |
t["width"],
|
| 254 |
t["height"],
|
| 255 |
t["seed"],
|
| 256 |
+
t["lora_enabled"],
|
| 257 |
t["lora_path"],
|
| 258 |
t["lora_strength"],
|
| 259 |
],
|
| 260 |
outputs=[t["output_image"], t["output_meta"]],
|
| 261 |
)
|
| 262 |
+
# Radio change → update step / cfg defaults + reveal Base-only fields.
|
| 263 |
+
t["model"].change(
|
| 264 |
fn=_on_model_change,
|
| 265 |
+
inputs=[t["model"]],
|
| 266 |
outputs=[t["steps"], t["cfg"]],
|
| 267 |
)
|
| 268 |
+
t["model"].change(
|
| 269 |
+
fn=lambda m: gr.Group(visible=(m == "Base")),
|
| 270 |
+
inputs=[t["model"]],
|
| 271 |
+
outputs=[t["base_group"]],
|
| 272 |
+
)
|
| 273 |
+
# LoRA checkbox → reveal file + strength.
|
| 274 |
+
t["lora_enabled"].change(
|
| 275 |
+
fn=lambda v: gr.Group(visible=v),
|
| 276 |
+
inputs=[t["lora_enabled"]],
|
| 277 |
+
outputs=[t["lora_group"]],
|
| 278 |
+
)
|
| 279 |
|
| 280 |
with gr.Tab("ControlNet"):
|
| 281 |
c = ui.build_controlnet_tab()
|
|
|
|
| 288 |
c["controlnet_scale"],
|
| 289 |
c["steps"],
|
| 290 |
c["seed"],
|
| 291 |
+
c["lora_enabled"],
|
| 292 |
c["lora_path"],
|
| 293 |
c["lora_strength"],
|
| 294 |
],
|
| 295 |
outputs=[c["output_image"], c["output_meta"]],
|
| 296 |
)
|
| 297 |
+
# Live preprocessor preview — fires on input change or mode change.
|
| 298 |
+
c["input_image"].change(
|
| 299 |
+
fn=_preview_cn,
|
| 300 |
+
inputs=[c["input_image"], c["preprocessor"]],
|
| 301 |
+
outputs=[c["preview_image"]],
|
| 302 |
+
)
|
| 303 |
+
c["preprocessor"].change(
|
| 304 |
+
fn=_preview_cn,
|
| 305 |
+
inputs=[c["input_image"], c["preprocessor"]],
|
| 306 |
+
outputs=[c["preview_image"]],
|
| 307 |
+
)
|
| 308 |
+
# LoRA checkbox → reveal file + strength.
|
| 309 |
+
c["lora_enabled"].change(
|
| 310 |
+
fn=lambda v: gr.Group(visible=v),
|
| 311 |
+
inputs=[c["lora_enabled"]],
|
| 312 |
+
outputs=[c["lora_group"]],
|
| 313 |
+
)
|
| 314 |
|
| 315 |
with gr.Tab("Upscale"):
|
| 316 |
u = ui.build_upscale_tab()
|
|
|
|
| 322 |
u["refine_steps"],
|
| 323 |
u["refine_denoise"],
|
| 324 |
u["seed"],
|
| 325 |
+
u["lora_enabled"],
|
| 326 |
u["lora_path"],
|
| 327 |
u["lora_strength"],
|
| 328 |
],
|
| 329 |
outputs=[u["output_image"], u["output_meta"]],
|
| 330 |
)
|
| 331 |
+
u["lora_enabled"].change(
|
| 332 |
+
fn=lambda v: gr.Group(visible=v),
|
| 333 |
+
inputs=[u["lora_enabled"]],
|
| 334 |
+
outputs=[u["lora_group"]],
|
| 335 |
+
)
|
| 336 |
return demo
|
| 337 |
|
| 338 |
|
tests/test_app.py
CHANGED
|
@@ -11,3 +11,19 @@ def test_on_model_change_returns_turbo_defaults():
|
|
| 11 |
|
| 12 |
def test_on_model_change_unknown_falls_back_to_turbo():
|
| 13 |
assert app._on_model_change("Edit") == (8, 1.0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
def test_on_model_change_unknown_falls_back_to_turbo():
|
| 13 |
assert app._on_model_change("Edit") == (8, 1.0)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def test_preview_cn_returns_none_when_no_image():
|
| 17 |
+
assert app._preview_cn(None, "Canny") is None
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def test_preview_cn_falls_back_to_input_on_error():
|
| 21 |
+
"""If the preprocessor raises (e.g. missing optional dep), pass-through."""
|
| 22 |
+
|
| 23 |
+
from PIL import Image
|
| 24 |
+
|
| 25 |
+
img = Image.new("RGB", (16, 16), "white")
|
| 26 |
+
# "BogusMode" raises ValueError inside preprocessors.run — _preview_cn should
|
| 27 |
+
# swallow it and return the raw input.
|
| 28 |
+
out = app._preview_cn(img, "BogusMode")
|
| 29 |
+
assert out is img
|
tests/test_theme.py
CHANGED
|
@@ -1,68 +1,49 @@
|
|
|
|
|
|
|
|
| 1 |
import theme
|
| 2 |
|
| 3 |
|
| 4 |
-
def
|
| 5 |
-
pal = theme.
|
| 6 |
-
assert pal["body_bg"] == "#
|
| 7 |
-
assert pal["text"] == "#
|
| 8 |
-
assert pal["text_dim"] == "#
|
| 9 |
-
assert pal["border"] == "#
|
| 10 |
assert pal["accent"] == "#FFB02E"
|
| 11 |
assert pal["accent_text"] == "#1A1208"
|
| 12 |
-
assert pal["radius"] == "
|
| 13 |
|
| 14 |
|
| 15 |
def test_build_theme_returns_gradio_base():
|
| 16 |
-
import gradio as gr
|
| 17 |
-
|
| 18 |
th = theme.build_theme()
|
| 19 |
assert isinstance(th, gr.themes.Base)
|
| 20 |
|
| 21 |
|
| 22 |
-
def
|
| 23 |
-
|
| 24 |
-
# warm vignette + amber button glow are the two decorations the spec calls out
|
| 25 |
-
assert "radial-gradient" in css
|
| 26 |
-
assert "rgba(255,176,46" in css.lower() or "255, 176, 46" in css.lower()
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
def test_fonts_geist_and_geist_mono():
|
| 30 |
th = theme.build_theme()
|
| 31 |
-
|
| 32 |
-
# (a) Iterable lists: .font_list / .font_mono_list expose the original entries.
|
| 33 |
-
fonts = [str(f) for f in th.font_list]
|
| 34 |
-
assert any("Geist" in f for f in fonts)
|
| 35 |
-
monos = [str(f) for f in th.font_mono_list]
|
| 36 |
-
assert any("Geist Mono" in f for f in monos)
|
| 37 |
-
|
| 38 |
-
# (b) CSS variables: _get_theme_css() must emit --font and --font-mono that
|
| 39 |
-
# reference Geist / Geist Mono so the browser actually loads the fonts.
|
| 40 |
-
# This assertion is what catches the original bug where property setters
|
| 41 |
-
# redirected self.font → font_str, causing --font-str to be emitted instead.
|
| 42 |
css = th._get_theme_css()
|
| 43 |
-
assert "
|
| 44 |
-
assert "
|
| 45 |
-
assert "Geist" in css, "Geist font name missing from generated theme CSS"
|
| 46 |
-
assert "Geist Mono" in css, "Geist Mono font name missing from generated theme CSS"
|
| 47 |
|
| 48 |
|
| 49 |
-
def
|
| 50 |
css = theme.CSS
|
| 51 |
-
assert ".zis-
|
| 52 |
-
assert "
|
| 53 |
-
assert "::after" in css
|
| 54 |
|
| 55 |
|
| 56 |
-
def
|
| 57 |
css = theme.CSS
|
| 58 |
-
assert ".zis-
|
| 59 |
-
assert "
|
| 60 |
-
assert ".zis-model.on" in css
|
| 61 |
-
assert ".zis-model.soon" in css
|
| 62 |
|
| 63 |
|
| 64 |
-
def
|
| 65 |
css = theme.CSS
|
| 66 |
-
|
| 67 |
-
assert "
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
|
| 3 |
import theme
|
| 4 |
|
| 5 |
|
| 6 |
+
def test_palette_tokens_match_soft_dark_restraint_spec():
|
| 7 |
+
pal = theme.PALETTE
|
| 8 |
+
assert pal["body_bg"] == "#1A1614"
|
| 9 |
+
assert pal["text"] == "#F0E8DD"
|
| 10 |
+
assert pal["text_dim"] == "#988B7C"
|
| 11 |
+
assert pal["border"] == "#2A241E"
|
| 12 |
assert pal["accent"] == "#FFB02E"
|
| 13 |
assert pal["accent_text"] == "#1A1208"
|
| 14 |
+
assert pal["radius"] == "6px"
|
| 15 |
|
| 16 |
|
| 17 |
def test_build_theme_returns_gradio_base():
|
|
|
|
|
|
|
| 18 |
th = theme.build_theme()
|
| 19 |
assert isinstance(th, gr.themes.Base)
|
| 20 |
|
| 21 |
|
| 22 |
+
def test_theme_drops_mono_font():
|
| 23 |
+
"""Redesign uses Inter only — no Geist / Geist Mono / mono custom font."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
th = theme.build_theme()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
css = th._get_theme_css()
|
| 26 |
+
assert "Geist" not in css
|
| 27 |
+
assert "Geist Mono" not in css
|
|
|
|
|
|
|
| 28 |
|
| 29 |
|
| 30 |
+
def test_css_includes_soon_row_styling():
|
| 31 |
css = theme.CSS
|
| 32 |
+
assert ".zis-soon-row" in css
|
| 33 |
+
assert ".zis-soon-row a" in css
|
|
|
|
| 34 |
|
| 35 |
|
| 36 |
+
def test_css_includes_compact_lora_file_widget():
|
| 37 |
css = theme.CSS
|
| 38 |
+
assert ".zis-lora-file" in css
|
| 39 |
+
assert "min-height: 56px" in css
|
|
|
|
|
|
|
| 40 |
|
| 41 |
|
| 42 |
+
def test_css_does_not_reference_deleted_selectors():
|
| 43 |
css = theme.CSS
|
| 44 |
+
# Old (i) tooltip pattern is gone — info= flows through gr.* directly.
|
| 45 |
+
assert ".zis-info" not in css
|
| 46 |
+
# Old custom card grid is gone — gr.Radio replaces it.
|
| 47 |
+
assert ".zis-models" not in css
|
| 48 |
+
assert ".zis-model.on" not in css
|
| 49 |
+
assert ".zis-model.soon" not in css
|
tests/test_ui.py
CHANGED
|
@@ -4,48 +4,6 @@ import pytest
|
|
| 4 |
import ui
|
| 5 |
|
| 6 |
|
| 7 |
-
def test_labeled_label_returns_html_string():
|
| 8 |
-
out = ui.labeled_label("Steps", "Denoising steps.")
|
| 9 |
-
assert isinstance(out, str)
|
| 10 |
-
assert "<label" in out and "</label>" in out
|
| 11 |
-
assert ">Steps<" in out
|
| 12 |
-
assert 'data-info="Denoising steps."' in out
|
| 13 |
-
assert ">i<" in out # the icon glyph
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
def test_labeled_label_escapes_html_chars():
|
| 17 |
-
out = ui.labeled_label("Steps <x>", 'A "quoted" hint')
|
| 18 |
-
assert "<x>" not in out
|
| 19 |
-
assert "<x>" in out
|
| 20 |
-
assert ""quoted"" in out
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
def test_model_selector_html_marks_current_as_on():
|
| 24 |
-
out = ui.model_selector_html(current="Turbo")
|
| 25 |
-
assert 'class="zis-model on" data-value="Turbo"' in out
|
| 26 |
-
assert 'class="zis-model" data-value="Base"' in out
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
def test_model_selector_html_includes_both_soon_cards_with_github_link():
|
| 30 |
-
out = ui.model_selector_html(current="Turbo")
|
| 31 |
-
assert out.count("github.com/Tongyi-MAI/Z-Image#-model-zoo") == 2
|
| 32 |
-
assert "Edit" in out
|
| 33 |
-
assert "Omni Base" in out
|
| 34 |
-
assert "soon-tag" in out
|
| 35 |
-
assert 'target="_blank"' in out
|
| 36 |
-
assert 'rel="noopener noreferrer"' in out
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
def test_model_selector_html_defaults_to_turbo():
|
| 40 |
-
out = ui.model_selector_html()
|
| 41 |
-
assert 'class="zis-model on" data-value="Turbo"' in out
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
def test_model_selector_html_escapes_current_value():
|
| 45 |
-
out = ui.model_selector_html(current="<script>alert(1)</script>")
|
| 46 |
-
assert "<script>" not in out
|
| 47 |
-
|
| 48 |
-
|
| 49 |
@pytest.fixture(autouse=True)
|
| 50 |
def _blocks_ctx():
|
| 51 |
"""Each builder must be called inside a gr.Blocks() context."""
|
|
@@ -53,12 +11,29 @@ def _blocks_ctx():
|
|
| 53 |
yield
|
| 54 |
|
| 55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
def test_build_t2i_tab_returns_components():
|
| 57 |
components = ui.build_t2i_tab()
|
| 58 |
expected = {
|
| 59 |
"prompt",
|
| 60 |
"negative_prompt",
|
| 61 |
-
"
|
|
|
|
|
|
|
|
|
|
| 62 |
"steps",
|
| 63 |
"cfg",
|
| 64 |
"width",
|
|
@@ -73,15 +48,40 @@ def test_build_t2i_tab_returns_components():
|
|
| 73 |
assert expected.issubset(components.keys())
|
| 74 |
|
| 75 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
def test_build_controlnet_tab_returns_components():
|
| 77 |
components = ui.build_controlnet_tab()
|
| 78 |
expected = {
|
| 79 |
"prompt",
|
| 80 |
"input_image",
|
|
|
|
| 81 |
"preprocessor",
|
| 82 |
"controlnet_scale",
|
| 83 |
"steps",
|
| 84 |
"seed",
|
|
|
|
|
|
|
| 85 |
"lora_path",
|
| 86 |
"lora_strength",
|
| 87 |
"generate_btn",
|
|
@@ -91,6 +91,12 @@ def test_build_controlnet_tab_returns_components():
|
|
| 91 |
assert expected.issubset(components.keys())
|
| 92 |
|
| 93 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
def test_build_upscale_tab_returns_components():
|
| 95 |
components = ui.build_upscale_tab()
|
| 96 |
expected = {
|
|
@@ -99,6 +105,8 @@ def test_build_upscale_tab_returns_components():
|
|
| 99 |
"refine_steps",
|
| 100 |
"refine_denoise",
|
| 101 |
"seed",
|
|
|
|
|
|
|
| 102 |
"lora_path",
|
| 103 |
"lora_strength",
|
| 104 |
"generate_btn",
|
|
|
|
| 4 |
import ui
|
| 5 |
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
@pytest.fixture(autouse=True)
|
| 8 |
def _blocks_ctx():
|
| 9 |
"""Each builder must be called inside a gr.Blocks() context."""
|
|
|
|
| 11 |
yield
|
| 12 |
|
| 13 |
|
| 14 |
+
def test_model_soon_row_links_to_zoo_twice():
|
| 15 |
+
html = ui._model_soon_row_html()
|
| 16 |
+
assert html.count(ui.MODEL_ZOO_URL) == 2
|
| 17 |
+
assert "Edit" in html and "Omni Base" in html
|
| 18 |
+
assert "(coming soon)" in html
|
| 19 |
+
assert 'target="_blank"' in html
|
| 20 |
+
assert 'rel="noopener noreferrer"' in html
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def test_model_soon_row_uses_dim_link_class():
|
| 24 |
+
html = ui._model_soon_row_html()
|
| 25 |
+
assert 'class="zis-soon-row"' in html
|
| 26 |
+
|
| 27 |
+
|
| 28 |
def test_build_t2i_tab_returns_components():
|
| 29 |
components = ui.build_t2i_tab()
|
| 30 |
expected = {
|
| 31 |
"prompt",
|
| 32 |
"negative_prompt",
|
| 33 |
+
"model",
|
| 34 |
+
"base_group",
|
| 35 |
+
"lora_enabled",
|
| 36 |
+
"lora_group",
|
| 37 |
"steps",
|
| 38 |
"cfg",
|
| 39 |
"width",
|
|
|
|
| 48 |
assert expected.issubset(components.keys())
|
| 49 |
|
| 50 |
|
| 51 |
+
def test_build_t2i_tab_model_is_native_radio():
|
| 52 |
+
components = ui.build_t2i_tab()
|
| 53 |
+
assert isinstance(components["model"], gr.Radio)
|
| 54 |
+
assert components["model"].value == "Turbo"
|
| 55 |
+
# Confirm the radio carries both choices. Gradio stores them as (label, value)
|
| 56 |
+
# tuples on the .choices attribute.
|
| 57 |
+
values = [c[1] for c in components["model"].choices]
|
| 58 |
+
assert values == ["Base", "Turbo"]
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def test_build_t2i_tab_lora_group_starts_hidden():
|
| 62 |
+
components = ui.build_t2i_tab()
|
| 63 |
+
assert components["lora_group"].visible is False
|
| 64 |
+
assert components["lora_enabled"].value is False
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def test_build_t2i_tab_base_group_starts_hidden():
|
| 68 |
+
"""Turbo is the default model, so the Base-only fields are hidden up front."""
|
| 69 |
+
components = ui.build_t2i_tab()
|
| 70 |
+
assert components["base_group"].visible is False
|
| 71 |
+
|
| 72 |
+
|
| 73 |
def test_build_controlnet_tab_returns_components():
|
| 74 |
components = ui.build_controlnet_tab()
|
| 75 |
expected = {
|
| 76 |
"prompt",
|
| 77 |
"input_image",
|
| 78 |
+
"preview_image",
|
| 79 |
"preprocessor",
|
| 80 |
"controlnet_scale",
|
| 81 |
"steps",
|
| 82 |
"seed",
|
| 83 |
+
"lora_enabled",
|
| 84 |
+
"lora_group",
|
| 85 |
"lora_path",
|
| 86 |
"lora_strength",
|
| 87 |
"generate_btn",
|
|
|
|
| 91 |
assert expected.issubset(components.keys())
|
| 92 |
|
| 93 |
|
| 94 |
+
def test_build_controlnet_tab_preview_is_non_interactive_image():
|
| 95 |
+
components = ui.build_controlnet_tab()
|
| 96 |
+
assert isinstance(components["preview_image"], gr.Image)
|
| 97 |
+
assert components["preview_image"].interactive is False
|
| 98 |
+
|
| 99 |
+
|
| 100 |
def test_build_upscale_tab_returns_components():
|
| 101 |
components = ui.build_upscale_tab()
|
| 102 |
expected = {
|
|
|
|
| 105 |
"refine_steps",
|
| 106 |
"refine_denoise",
|
| 107 |
"seed",
|
| 108 |
+
"lora_enabled",
|
| 109 |
+
"lora_group",
|
| 110 |
"lora_path",
|
| 111 |
"lora_strength",
|
| 112 |
"generate_btn",
|
theme.py
CHANGED
|
@@ -1,54 +1,45 @@
|
|
| 1 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
import gradio as gr
|
| 6 |
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
"
|
| 12 |
-
"
|
| 13 |
-
"
|
| 14 |
-
"
|
|
|
|
|
|
|
|
|
|
| 15 |
"accent": "#FFB02E",
|
| 16 |
"accent_text": "#1A1208",
|
| 17 |
-
"radius": "
|
| 18 |
-
"radius_sm": "6px",
|
| 19 |
}
|
| 20 |
|
| 21 |
|
| 22 |
-
|
| 23 |
-
"""
|
| 24 |
|
| 25 |
-
Gradio
|
| 26 |
-
|
| 27 |
-
iterate. The public self.font / self.font_mono attributes are left alone so
|
| 28 |
-
that _get_theme_css() emits the correct --font / --font-mono CSS variables.
|
| 29 |
"""
|
| 30 |
-
|
| 31 |
-
@property
|
| 32 |
-
def font_list(self) -> list:
|
| 33 |
-
"""Return the original font list (GoogleFont + str entries)."""
|
| 34 |
-
return self._font
|
| 35 |
-
|
| 36 |
-
@property
|
| 37 |
-
def font_mono_list(self) -> list:
|
| 38 |
-
"""Return the original monospace font list (GoogleFont + str entries)."""
|
| 39 |
-
return self._font_mono
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
def build_theme() -> gr.themes.Base:
|
| 43 |
-
"""Return a Gradio theme matching the Onyx Amber palette."""
|
| 44 |
-
return _OnyxAmberBase(
|
| 45 |
primary_hue=gr.themes.Color(
|
| 46 |
c50="#FFF8E6",
|
| 47 |
c100="#FFEFC2",
|
| 48 |
c200="#FFE08A",
|
| 49 |
c300="#FFD161",
|
| 50 |
c400="#FFC042",
|
| 51 |
-
c500=
|
| 52 |
c600="#E69926",
|
| 53 |
c700="#B37A1F",
|
| 54 |
c800="#805717",
|
|
@@ -56,164 +47,85 @@ def build_theme() -> gr.themes.Base:
|
|
| 56 |
c950="#1A1208",
|
| 57 |
),
|
| 58 |
neutral_hue=gr.themes.Color(
|
| 59 |
-
c50="#
|
| 60 |
-
c100="#
|
| 61 |
-
c200="#
|
| 62 |
-
c300="#
|
| 63 |
-
c400="#
|
| 64 |
-
c500="#
|
| 65 |
-
c600="#
|
| 66 |
-
c700="#
|
| 67 |
-
c800="#
|
| 68 |
-
c900="#
|
| 69 |
-
c950="#
|
| 70 |
),
|
| 71 |
-
|
| 72 |
-
font_mono=[gr.themes.GoogleFont("Geist Mono"), "ui-monospace", "monospace"],
|
| 73 |
-
radius_size=gr.themes.sizes.radius_md,
|
| 74 |
).set(
|
| 75 |
-
body_background_fill=
|
| 76 |
-
body_text_color=
|
| 77 |
-
body_text_color_subdued=
|
| 78 |
-
background_fill_primary=
|
| 79 |
-
background_fill_secondary=
|
| 80 |
-
block_background_fill=
|
| 81 |
-
block_border_color=
|
| 82 |
block_border_width="1px",
|
| 83 |
-
block_radius=
|
| 84 |
-
input_background_fill=
|
| 85 |
-
input_border_color=
|
| 86 |
-
button_primary_background_fill=
|
| 87 |
-
button_primary_background_fill_hover=
|
| 88 |
-
button_primary_text_color=
|
| 89 |
-
button_primary_border_color=
|
| 90 |
-
slider_color=
|
| 91 |
-
color_accent=
|
| 92 |
color_accent_soft="rgba(255,176,46,0.12)",
|
| 93 |
)
|
| 94 |
|
| 95 |
|
| 96 |
CSS: str = """
|
| 97 |
-
/*
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
color: #
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
}
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
}
|
| 128 |
-
.zis-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
transform: translateX(-50%) translateY(-4px);
|
| 142 |
-
background: #1C170F; color: #FAF1E3;
|
| 143 |
-
border: 1px solid #2A2218; border-radius: 6px;
|
| 144 |
-
padding: 6px 10px;
|
| 145 |
-
font: 400 11px 'Geist', system-ui, sans-serif; line-height: 1.4;
|
| 146 |
-
width: 200px; white-space: normal;
|
| 147 |
-
opacity: 0; pointer-events: none;
|
| 148 |
-
transition: opacity 0.12s; z-index: 50;
|
| 149 |
-
box-shadow: 0 4px 16px rgba(0,0,0,0.4);
|
| 150 |
-
}
|
| 151 |
-
.zis-info:hover::after, .zis-info.shown::after { opacity: 1; }
|
| 152 |
-
|
| 153 |
-
/* Hidden state carrier — in DOM for JS targeting, invisible to users */
|
| 154 |
-
.zis-hidden { display: none !important; }
|
| 155 |
-
|
| 156 |
-
/* ===== Custom model selector — 2-col phone / 4-col tablet+ (spec § 4.7) ===== */
|
| 157 |
-
|
| 158 |
-
.zis-models {
|
| 159 |
-
display: grid;
|
| 160 |
-
grid-template-columns: 1fr 1fr;
|
| 161 |
-
gap: 8px;
|
| 162 |
-
margin-bottom: 10px;
|
| 163 |
-
}
|
| 164 |
-
@media (min-width: 768px) {
|
| 165 |
-
.zis-models { grid-template-columns: repeat(4, 1fr); }
|
| 166 |
-
}
|
| 167 |
-
.zis-model {
|
| 168 |
-
display: flex; align-items: center; gap: 8px;
|
| 169 |
-
padding: 10px 12px;
|
| 170 |
-
border: 1px solid #2A2218; border-radius: 8px;
|
| 171 |
-
background: transparent; cursor: pointer;
|
| 172 |
-
color: #FAF1E3;
|
| 173 |
-
font: 500 12px 'Geist', system-ui, sans-serif;
|
| 174 |
-
text-decoration: none;
|
| 175 |
-
transition: opacity 0.15s, border-color 0.15s, background 0.15s;
|
| 176 |
-
}
|
| 177 |
-
.zis-model .dot {
|
| 178 |
-
width: 10px; height: 10px; border-radius: 50%;
|
| 179 |
-
border: 1px solid #2A2218; flex-shrink: 0;
|
| 180 |
-
}
|
| 181 |
-
.zis-model .name { flex: 1; text-align: left; }
|
| 182 |
-
.zis-model.on {
|
| 183 |
-
background: #FFB02E; color: #1A1208; border-color: #FFB02E;
|
| 184 |
-
}
|
| 185 |
-
.zis-model.on .dot { background: #1A1208; border-color: #1A1208; }
|
| 186 |
-
.zis-model.soon {
|
| 187 |
-
opacity: 0.55;
|
| 188 |
-
background: rgba(255,176,46,0.04);
|
| 189 |
-
border-style: dashed;
|
| 190 |
-
position: relative;
|
| 191 |
-
}
|
| 192 |
-
.zis-model.soon .name { color: #A89478; }
|
| 193 |
-
.zis-model.soon .name .ext {
|
| 194 |
-
font-size: 10px; color: #FFB02E;
|
| 195 |
-
margin-left: 4px; vertical-align: super;
|
| 196 |
-
}
|
| 197 |
-
.zis-model.soon .soon-tag {
|
| 198 |
-
font-family: 'Geist Mono', ui-monospace, monospace;
|
| 199 |
-
font-size: 8.5px; letter-spacing: 0.12em; text-transform: uppercase;
|
| 200 |
-
background: rgba(255,176,46,0.18); color: #FFB02E;
|
| 201 |
-
padding: 2px 6px; border-radius: 100px;
|
| 202 |
-
flex-shrink: 0;
|
| 203 |
-
}
|
| 204 |
-
.zis-model.soon:hover { opacity: 0.78; border-color: #FFB02E; }
|
| 205 |
-
.zis-model.soon::after {
|
| 206 |
-
content: "Coming soon — opens GitHub";
|
| 207 |
-
position: absolute; bottom: 100%; left: 50%;
|
| 208 |
-
transform: translateX(-50%) translateY(-4px);
|
| 209 |
-
background: #1C170F; color: #FAF1E3;
|
| 210 |
-
border: 1px solid #2A2218; border-radius: 6px;
|
| 211 |
-
padding: 6px 10px;
|
| 212 |
-
font: 400 11px 'Geist', system-ui, sans-serif;
|
| 213 |
-
white-space: nowrap;
|
| 214 |
-
opacity: 0; pointer-events: none;
|
| 215 |
-
transition: opacity 0.12s; z-index: 50;
|
| 216 |
-
box-shadow: 0 4px 16px rgba(0,0,0,0.4);
|
| 217 |
}
|
| 218 |
-
.zis-model.soon:hover::after { opacity: 1; }
|
| 219 |
""".strip()
|
|
|
|
| 1 |
+
"""Soft Dark Restraint theme — warm-toned dark surface with a single amber accent.
|
| 2 |
+
|
| 3 |
+
Palette tokens, ``gr.themes.Base`` configuration, and a small CSS string that
|
| 4 |
+
applies the touches Gradio's theme tokens can't express alone (link row under
|
| 5 |
+
the model radio + compact LoRA file widget).
|
| 6 |
+
"""
|
| 7 |
|
| 8 |
from __future__ import annotations
|
| 9 |
|
| 10 |
import gradio as gr
|
| 11 |
|
| 12 |
+
# Single source of truth for the palette. The mockup at
|
| 13 |
+
# ``.superpowers/brainstorm/47889-1778679653/content/simple-v1.html`` is the
|
| 14 |
+
# locked spec for these values (Variant A — "Soft Dark Restraint").
|
| 15 |
+
PALETTE: dict[str, str] = {
|
| 16 |
+
"body_bg": "#1A1614",
|
| 17 |
+
"panel_bg": "#1F1B17",
|
| 18 |
+
"input_bg": "#14110F",
|
| 19 |
+
"text": "#F0E8DD",
|
| 20 |
+
"text_dim": "#988B7C",
|
| 21 |
+
"border": "#2A241E",
|
| 22 |
+
"border_strong": "#3A3128",
|
| 23 |
"accent": "#FFB02E",
|
| 24 |
"accent_text": "#1A1208",
|
| 25 |
+
"radius": "6px",
|
|
|
|
| 26 |
}
|
| 27 |
|
| 28 |
|
| 29 |
+
def build_theme() -> gr.themes.Base:
|
| 30 |
+
"""Return a Gradio theme matching the Soft Dark Restraint palette.
|
| 31 |
|
| 32 |
+
Uses Gradio's default font chain (Inter + system-ui fallbacks). No mono
|
| 33 |
+
font — the redesign drops monospaced UI text entirely.
|
|
|
|
|
|
|
| 34 |
"""
|
| 35 |
+
return gr.themes.Base(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
primary_hue=gr.themes.Color(
|
| 37 |
c50="#FFF8E6",
|
| 38 |
c100="#FFEFC2",
|
| 39 |
c200="#FFE08A",
|
| 40 |
c300="#FFD161",
|
| 41 |
c400="#FFC042",
|
| 42 |
+
c500=PALETTE["accent"],
|
| 43 |
c600="#E69926",
|
| 44 |
c700="#B37A1F",
|
| 45 |
c800="#805717",
|
|
|
|
| 47 |
c950="#1A1208",
|
| 48 |
),
|
| 49 |
neutral_hue=gr.themes.Color(
|
| 50 |
+
c50="#F0E8DD",
|
| 51 |
+
c100="#E0D5C3",
|
| 52 |
+
c200="#C8B89E",
|
| 53 |
+
c300="#988B7C",
|
| 54 |
+
c400="#7A6E60",
|
| 55 |
+
c500="#5C5246",
|
| 56 |
+
c600="#3A3128",
|
| 57 |
+
c700="#2A241E",
|
| 58 |
+
c800="#1F1B17",
|
| 59 |
+
c900="#1A1614",
|
| 60 |
+
c950="#14110F",
|
| 61 |
),
|
| 62 |
+
radius_size=gr.themes.sizes.radius_sm,
|
|
|
|
|
|
|
| 63 |
).set(
|
| 64 |
+
body_background_fill=PALETTE["body_bg"],
|
| 65 |
+
body_text_color=PALETTE["text"],
|
| 66 |
+
body_text_color_subdued=PALETTE["text_dim"],
|
| 67 |
+
background_fill_primary=PALETTE["panel_bg"],
|
| 68 |
+
background_fill_secondary=PALETTE["body_bg"],
|
| 69 |
+
block_background_fill=PALETTE["panel_bg"],
|
| 70 |
+
block_border_color=PALETTE["border"],
|
| 71 |
block_border_width="1px",
|
| 72 |
+
block_radius=PALETTE["radius"],
|
| 73 |
+
input_background_fill=PALETTE["input_bg"],
|
| 74 |
+
input_border_color=PALETTE["border"],
|
| 75 |
+
button_primary_background_fill=PALETTE["accent"],
|
| 76 |
+
button_primary_background_fill_hover=PALETTE["accent"],
|
| 77 |
+
button_primary_text_color=PALETTE["accent_text"],
|
| 78 |
+
button_primary_border_color=PALETTE["accent"],
|
| 79 |
+
slider_color=PALETTE["accent"],
|
| 80 |
+
color_accent=PALETTE["accent"],
|
| 81 |
color_accent_soft="rgba(255,176,46,0.12)",
|
| 82 |
)
|
| 83 |
|
| 84 |
|
| 85 |
CSS: str = """
|
| 86 |
+
/* Soft Dark Restraint — calm, single-accent decorations Gradio tokens can't express. */
|
| 87 |
+
|
| 88 |
+
/* Small dim link row under the model radio (Edit / Omni Base · coming soon). */
|
| 89 |
+
.zis-soon-row {
|
| 90 |
+
margin-top: 6px;
|
| 91 |
+
font-size: 12px;
|
| 92 |
+
color: #988B7C;
|
| 93 |
+
line-height: 1.45;
|
| 94 |
+
}
|
| 95 |
+
.zis-soon-row a {
|
| 96 |
+
color: #988B7C;
|
| 97 |
+
text-decoration: underline;
|
| 98 |
+
text-decoration-color: #3A3128;
|
| 99 |
+
text-underline-offset: 3px;
|
| 100 |
+
}
|
| 101 |
+
.zis-soon-row a:hover {
|
| 102 |
+
color: #F0E8DD;
|
| 103 |
+
text-decoration-color: #988B7C;
|
| 104 |
+
}
|
| 105 |
+
.zis-soon-row .sep {
|
| 106 |
+
margin: 0 6px;
|
| 107 |
+
color: #3A3128;
|
| 108 |
+
}
|
| 109 |
+
.zis-soon-row .dim {
|
| 110 |
+
margin-left: 6px;
|
| 111 |
+
color: #635A4E;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
/* Compact LoRA file widget — tighten Gradio's default 400px drop zone. */
|
| 115 |
+
.zis-lora-file .upload-container { min-height: 56px !important; padding: 8px 12px !important; }
|
| 116 |
+
.zis-lora-file .icon-wrap, .zis-lora-file svg { display: none !important; }
|
| 117 |
+
.zis-lora-file .wrap > * { text-align: left; }
|
| 118 |
+
|
| 119 |
+
/* Brand period uses the accent — the only place the accent appears in chrome. */
|
| 120 |
+
.zis-brand-period { color: #FFB02E; }
|
| 121 |
+
|
| 122 |
+
/* Live-status dot — accent, matches the single-accent rule. */
|
| 123 |
+
.zis-status-dot::before {
|
| 124 |
+
content: "";
|
| 125 |
+
display: inline-block;
|
| 126 |
+
width: 6px; height: 6px; border-radius: 50%;
|
| 127 |
+
background: #FFB02E;
|
| 128 |
+
margin-right: 6px;
|
| 129 |
+
vertical-align: middle;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
}
|
|
|
|
| 131 |
""".strip()
|
ui.py
CHANGED
|
@@ -1,109 +1,118 @@
|
|
| 1 |
-
"""Gradio UI builders
|
| 2 |
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
-
from
|
| 6 |
|
| 7 |
import gradio as gr
|
| 8 |
|
| 9 |
import preprocessors
|
| 10 |
from tooltips import TOOLTIPS
|
| 11 |
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
|
| 15 |
-
def
|
| 16 |
-
"""Return
|
| 17 |
|
| 18 |
-
|
| 19 |
-
that itself has ``show_label=False``. The CSS for ``.zis-row-label`` and
|
| 20 |
-
``.zis-info`` is defined in :mod:`theme`.
|
| 21 |
"""
|
| 22 |
return (
|
| 23 |
-
|
| 24 |
-
f'<
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
| 26 |
)
|
| 27 |
|
| 28 |
|
| 29 |
-
def
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
-
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
for name in ("Edit", "Omni Base"):
|
| 51 |
-
cards.append(
|
| 52 |
-
f'<a class="zis-model soon" '
|
| 53 |
-
f'href="{GITHUB_MODEL_ZOO_URL}" '
|
| 54 |
-
f'target="_blank" rel="noopener noreferrer">'
|
| 55 |
-
f'<span class="dot"></span>'
|
| 56 |
-
f'<span class="name">{name}<span class="ext">↗</span></span>'
|
| 57 |
-
f'<span class="soon-tag">soon</span>'
|
| 58 |
-
f"</a>"
|
| 59 |
-
)
|
| 60 |
-
_ = current_safe # current is matched in cls above; this line keeps escape() exercised
|
| 61 |
-
return f'<div class="zis-models">{"".join(cards)}</div>'
|
| 62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
-
def build_t2i_tab() -> dict[str, gr.components.Component]:
|
| 65 |
-
with gr.Row():
|
| 66 |
-
with gr.Column(scale=4):
|
| 67 |
-
gr.HTML(labeled_label("Prompt", TOOLTIPS["prompt"]))
|
| 68 |
-
prompt = gr.Textbox(lines=4, show_label=False, placeholder="A latina model peeking through pine branches…")
|
| 69 |
-
gr.HTML(labeled_label("Negative prompt (Base only)", TOOLTIPS["negative_prompt"]))
|
| 70 |
-
negative_prompt = gr.Textbox(lines=2, show_label=False, placeholder="blurry, lowres, distorted")
|
| 71 |
-
gr.HTML(labeled_label("Model", TOOLTIPS["model"]))
|
| 72 |
-
model_state = gr.Textbox(value="Turbo", elem_id="zis-model-state", elem_classes=["zis-hidden"])
|
| 73 |
-
gr.HTML(model_selector_html(current="Turbo"))
|
| 74 |
-
with gr.Row():
|
| 75 |
-
with gr.Column():
|
| 76 |
-
gr.HTML(labeled_label("LoRA (optional)", TOOLTIPS["lora"]))
|
| 77 |
-
lora_path = gr.File(file_types=[".safetensors"], type="filepath", show_label=False)
|
| 78 |
-
with gr.Column():
|
| 79 |
-
gr.HTML(labeled_label("LoRA strength", TOOLTIPS["lora_strength"]))
|
| 80 |
-
lora_strength = gr.Slider(0.0, 1.5, value=0.8, step=0.05, show_label=False)
|
| 81 |
-
with gr.Row():
|
| 82 |
-
with gr.Column():
|
| 83 |
-
gr.HTML(labeled_label("Steps", TOOLTIPS["steps"]))
|
| 84 |
-
steps = gr.Slider(1, 50, value=8, step=1, show_label=False)
|
| 85 |
-
with gr.Column():
|
| 86 |
-
gr.HTML(labeled_label("CFG (Base only)", TOOLTIPS["cfg"]))
|
| 87 |
-
cfg = gr.Slider(0.5, 12.0, value=1.0, step=0.1, show_label=False)
|
| 88 |
-
with gr.Row():
|
| 89 |
-
with gr.Column():
|
| 90 |
-
gr.HTML(labeled_label("Width", TOOLTIPS["width"]))
|
| 91 |
-
width = gr.Slider(384, 1536, value=1024, step=64, show_label=False)
|
| 92 |
-
with gr.Column():
|
| 93 |
-
gr.HTML(labeled_label("Height", TOOLTIPS["height"]))
|
| 94 |
-
height = gr.Slider(384, 1536, value=1024, step=64, show_label=False)
|
| 95 |
-
with gr.Column():
|
| 96 |
-
gr.HTML(labeled_label("Seed (0 = random)", TOOLTIPS["seed"]))
|
| 97 |
-
seed = gr.Number(value=0, precision=0, show_label=False)
|
| 98 |
generate_btn = gr.Button("Generate", variant="primary")
|
|
|
|
| 99 |
with gr.Column(scale=5):
|
| 100 |
-
gr.
|
| 101 |
-
output_image = gr.Image(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
output_meta = gr.JSON(label="Meta", value={})
|
|
|
|
| 103 |
return dict(
|
| 104 |
prompt=prompt,
|
| 105 |
negative_prompt=negative_prompt,
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
steps=steps,
|
| 108 |
cfg=cfg,
|
| 109 |
width=width,
|
|
@@ -120,43 +129,95 @@ def build_t2i_tab() -> dict[str, gr.components.Component]:
|
|
| 120 |
def build_controlnet_tab() -> dict[str, gr.components.Component]:
|
| 121 |
with gr.Row():
|
| 122 |
with gr.Column(scale=4):
|
| 123 |
-
gr.
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
gr.HTML(labeled_label("Preprocessor", TOOLTIPS["controlnet_preprocessor"]))
|
| 130 |
-
preprocessor = gr.Dropdown(list(preprocessors.MODES), value="Canny", show_label=False)
|
| 131 |
-
with gr.Column():
|
| 132 |
-
gr.HTML(labeled_label("ControlNet scale", TOOLTIPS["controlnet_scale"]))
|
| 133 |
-
controlnet_scale = gr.Slider(0.0, 2.0, value=1.0, step=0.05, show_label=False)
|
| 134 |
with gr.Row():
|
| 135 |
with gr.Column():
|
| 136 |
-
gr.
|
| 137 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
with gr.Column():
|
| 139 |
-
gr.
|
| 140 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
with gr.Row():
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
generate_btn = gr.Button("Generate", variant="primary")
|
|
|
|
| 149 |
with gr.Column(scale=5):
|
| 150 |
-
gr.
|
| 151 |
-
output_image = gr.Image(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
output_meta = gr.JSON(label="Meta", value={})
|
|
|
|
| 153 |
return dict(
|
| 154 |
prompt=prompt,
|
| 155 |
input_image=input_image,
|
|
|
|
| 156 |
preprocessor=preprocessor,
|
| 157 |
controlnet_scale=controlnet_scale,
|
| 158 |
steps=steps,
|
| 159 |
seed=seed,
|
|
|
|
|
|
|
| 160 |
lora_path=lora_path,
|
| 161 |
lora_strength=lora_strength,
|
| 162 |
generate_btn=generate_btn,
|
|
@@ -168,37 +229,81 @@ def build_controlnet_tab() -> dict[str, gr.components.Component]:
|
|
| 168 |
def build_upscale_tab() -> dict[str, gr.components.Component]:
|
| 169 |
with gr.Row():
|
| 170 |
with gr.Column(scale=4):
|
| 171 |
-
gr.
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
with gr.Row():
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
generate_btn = gr.Button("Generate", variant="primary")
|
|
|
|
| 192 |
with gr.Column(scale=5):
|
| 193 |
-
gr.
|
| 194 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
output_meta = gr.JSON(label="Meta", value={})
|
|
|
|
| 196 |
return dict(
|
| 197 |
prompt=prompt,
|
| 198 |
input_image=input_image,
|
| 199 |
refine_steps=refine_steps,
|
| 200 |
refine_denoise=refine_denoise,
|
| 201 |
seed=seed,
|
|
|
|
|
|
|
| 202 |
lora_path=lora_path,
|
| 203 |
lora_strength=lora_strength,
|
| 204 |
generate_btn=generate_btn,
|
|
|
|
| 1 |
+
"""Gradio UI builders for z-image-studio (Soft Dark Restraint redesign).
|
| 2 |
|
| 3 |
+
Each ``build_*_tab()`` returns a dict of components so ``app.py:build_app``
|
| 4 |
+
can wire ``.click()`` / ``.change()`` handlers without reaching into local
|
| 5 |
+
scopes. All param help text flows through Gradio's native ``info=`` parameter
|
| 6 |
+
(dim subtitle under each label) — no custom popover or (i) icon helper.
|
| 7 |
+
"""
|
| 8 |
|
| 9 |
+
from __future__ import annotations
|
| 10 |
|
| 11 |
import gradio as gr
|
| 12 |
|
| 13 |
import preprocessors
|
| 14 |
from tooltips import TOOLTIPS
|
| 15 |
|
| 16 |
+
# Link targets for the small dim row under the model radio. The Z-Image
|
| 17 |
+
# README's Model Zoo section is the canonical "where to find more models"
|
| 18 |
+
# anchor; Omni Base lives in the same README. When more models ship, swap
|
| 19 |
+
# these constants and nothing else needs to change.
|
| 20 |
+
MODEL_ZOO_URL = "https://github.com/Tongyi-MAI/Z-Image#-model-zoo"
|
| 21 |
|
| 22 |
|
| 23 |
+
def _model_soon_row_html() -> str:
|
| 24 |
+
"""Return the small dim link row that lives directly under the model radio.
|
| 25 |
|
| 26 |
+
Two anchor links + a "(coming soon)" qualifier. Static — no state, no JS.
|
|
|
|
|
|
|
| 27 |
"""
|
| 28 |
return (
|
| 29 |
+
'<div class="zis-soon-row">'
|
| 30 |
+
f'<a href="{MODEL_ZOO_URL}" target="_blank" rel="noopener noreferrer">Edit ↗</a>'
|
| 31 |
+
'<span class="sep">·</span>'
|
| 32 |
+
f'<a href="{MODEL_ZOO_URL}" target="_blank" rel="noopener noreferrer">Omni Base ↗</a>'
|
| 33 |
+
'<span class="dim">(coming soon)</span>'
|
| 34 |
+
"</div>"
|
| 35 |
)
|
| 36 |
|
| 37 |
|
| 38 |
+
def build_t2i_tab() -> dict[str, gr.components.Component]:
|
| 39 |
+
with gr.Row():
|
| 40 |
+
with gr.Column(scale=4):
|
| 41 |
+
prompt = gr.Textbox(
|
| 42 |
+
label="Prompt",
|
| 43 |
+
info=TOOLTIPS["prompt"],
|
| 44 |
+
lines=4,
|
| 45 |
+
placeholder="A latina model peeking through pine branches…",
|
| 46 |
+
)
|
| 47 |
|
| 48 |
+
model = gr.Radio(
|
| 49 |
+
["Base", "Turbo"],
|
| 50 |
+
value="Turbo",
|
| 51 |
+
label="Model",
|
| 52 |
+
info=TOOLTIPS["model"],
|
| 53 |
+
)
|
| 54 |
+
model_soon_row = gr.HTML(_model_soon_row_html())
|
| 55 |
|
| 56 |
+
with gr.Group(visible=False) as base_group:
|
| 57 |
+
negative_prompt = gr.Textbox(
|
| 58 |
+
label="Negative prompt",
|
| 59 |
+
info=TOOLTIPS["negative_prompt"],
|
| 60 |
+
lines=2,
|
| 61 |
+
placeholder="blurry, lowres, distorted",
|
| 62 |
+
)
|
| 63 |
+
cfg = gr.Slider(
|
| 64 |
+
0.5,
|
| 65 |
+
12.0,
|
| 66 |
+
value=1.0,
|
| 67 |
+
step=0.1,
|
| 68 |
+
label="CFG",
|
| 69 |
+
info=TOOLTIPS["cfg"],
|
| 70 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
+
lora_enabled = gr.Checkbox(label="Use a LoRA", value=False)
|
| 73 |
+
with gr.Group(visible=False) as lora_group:
|
| 74 |
+
lora_path = gr.File(
|
| 75 |
+
label="LoRA file",
|
| 76 |
+
file_types=[".safetensors"],
|
| 77 |
+
type="filepath",
|
| 78 |
+
elem_classes=["zis-lora-file"],
|
| 79 |
+
)
|
| 80 |
+
lora_strength = gr.Slider(
|
| 81 |
+
0.0,
|
| 82 |
+
1.5,
|
| 83 |
+
value=0.8,
|
| 84 |
+
step=0.05,
|
| 85 |
+
label="LoRA strength",
|
| 86 |
+
info=TOOLTIPS["lora_strength"],
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
steps = gr.Slider(1, 50, value=8, step=1, label="Steps", info=TOOLTIPS["steps"])
|
| 90 |
+
|
| 91 |
+
with gr.Accordion("Advanced", open=False):
|
| 92 |
+
width = gr.Slider(384, 1536, value=1024, step=64, label="Width", info=TOOLTIPS["width"])
|
| 93 |
+
height = gr.Slider(384, 1536, value=1024, step=64, label="Height", info=TOOLTIPS["height"])
|
| 94 |
+
seed = gr.Number(value=0, precision=0, label="Seed", info=TOOLTIPS["seed"])
|
| 95 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
generate_btn = gr.Button("Generate", variant="primary")
|
| 97 |
+
|
| 98 |
with gr.Column(scale=5):
|
| 99 |
+
gr.Markdown(f"**Output** \n<span style='color:#988B7C;font-size:12px;'>{TOOLTIPS['output']}</span>")
|
| 100 |
+
output_image = gr.Image(
|
| 101 |
+
show_label=False,
|
| 102 |
+
type="pil",
|
| 103 |
+
height=512,
|
| 104 |
+
show_download_button=True,
|
| 105 |
+
)
|
| 106 |
output_meta = gr.JSON(label="Meta", value={})
|
| 107 |
+
|
| 108 |
return dict(
|
| 109 |
prompt=prompt,
|
| 110 |
negative_prompt=negative_prompt,
|
| 111 |
+
model=model,
|
| 112 |
+
model_soon_row=model_soon_row,
|
| 113 |
+
base_group=base_group,
|
| 114 |
+
lora_enabled=lora_enabled,
|
| 115 |
+
lora_group=lora_group,
|
| 116 |
steps=steps,
|
| 117 |
cfg=cfg,
|
| 118 |
width=width,
|
|
|
|
| 129 |
def build_controlnet_tab() -> dict[str, gr.components.Component]:
|
| 130 |
with gr.Row():
|
| 131 |
with gr.Column(scale=4):
|
| 132 |
+
prompt = gr.Textbox(
|
| 133 |
+
label="Prompt",
|
| 134 |
+
info=TOOLTIPS["prompt"],
|
| 135 |
+
lines=3,
|
| 136 |
+
)
|
| 137 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
with gr.Row():
|
| 139 |
with gr.Column():
|
| 140 |
+
gr.Markdown(
|
| 141 |
+
f"**Control image** \n<span style='color:#988B7C;font-size:12px;'>"
|
| 142 |
+
f"{TOOLTIPS['controlnet_image']}</span>"
|
| 143 |
+
)
|
| 144 |
+
input_image = gr.Image(
|
| 145 |
+
show_label=False,
|
| 146 |
+
type="pil",
|
| 147 |
+
height=240,
|
| 148 |
+
)
|
| 149 |
with gr.Column():
|
| 150 |
+
gr.Markdown(
|
| 151 |
+
"**Preprocessor preview** \n<span style='color:#988B7C;font-size:12px;'>"
|
| 152 |
+
"Live edge map / depth map / pose. Updates as you change the preprocessor.</span>"
|
| 153 |
+
)
|
| 154 |
+
preview_image = gr.Image(
|
| 155 |
+
show_label=False,
|
| 156 |
+
type="pil",
|
| 157 |
+
height=240,
|
| 158 |
+
interactive=False,
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
with gr.Row():
|
| 162 |
+
preprocessor = gr.Dropdown(
|
| 163 |
+
list(preprocessors.MODES),
|
| 164 |
+
value="Canny",
|
| 165 |
+
label="Preprocessor",
|
| 166 |
+
info=TOOLTIPS["controlnet_preprocessor"],
|
| 167 |
+
)
|
| 168 |
+
controlnet_scale = gr.Slider(
|
| 169 |
+
0.0,
|
| 170 |
+
2.0,
|
| 171 |
+
value=1.0,
|
| 172 |
+
step=0.05,
|
| 173 |
+
label="ControlNet scale",
|
| 174 |
+
info=TOOLTIPS["controlnet_scale"],
|
| 175 |
+
)
|
| 176 |
+
|
| 177 |
+
lora_enabled = gr.Checkbox(label="Use a LoRA", value=False)
|
| 178 |
+
with gr.Group(visible=False) as lora_group:
|
| 179 |
+
lora_path = gr.File(
|
| 180 |
+
label="LoRA file",
|
| 181 |
+
file_types=[".safetensors"],
|
| 182 |
+
type="filepath",
|
| 183 |
+
elem_classes=["zis-lora-file"],
|
| 184 |
+
)
|
| 185 |
+
lora_strength = gr.Slider(
|
| 186 |
+
0.0,
|
| 187 |
+
1.5,
|
| 188 |
+
value=0.8,
|
| 189 |
+
step=0.05,
|
| 190 |
+
label="LoRA strength",
|
| 191 |
+
info=TOOLTIPS["lora_strength"],
|
| 192 |
+
)
|
| 193 |
+
|
| 194 |
+
steps = gr.Slider(1, 30, value=9, step=1, label="Steps", info=TOOLTIPS["steps"])
|
| 195 |
+
|
| 196 |
+
with gr.Accordion("Advanced", open=False):
|
| 197 |
+
seed = gr.Number(value=0, precision=0, label="Seed", info=TOOLTIPS["seed"])
|
| 198 |
+
|
| 199 |
generate_btn = gr.Button("Generate", variant="primary")
|
| 200 |
+
|
| 201 |
with gr.Column(scale=5):
|
| 202 |
+
gr.Markdown(f"**Output** \n<span style='color:#988B7C;font-size:12px;'>{TOOLTIPS['output']}</span>")
|
| 203 |
+
output_image = gr.Image(
|
| 204 |
+
show_label=False,
|
| 205 |
+
type="pil",
|
| 206 |
+
height=512,
|
| 207 |
+
show_download_button=True,
|
| 208 |
+
)
|
| 209 |
output_meta = gr.JSON(label="Meta", value={})
|
| 210 |
+
|
| 211 |
return dict(
|
| 212 |
prompt=prompt,
|
| 213 |
input_image=input_image,
|
| 214 |
+
preview_image=preview_image,
|
| 215 |
preprocessor=preprocessor,
|
| 216 |
controlnet_scale=controlnet_scale,
|
| 217 |
steps=steps,
|
| 218 |
seed=seed,
|
| 219 |
+
lora_enabled=lora_enabled,
|
| 220 |
+
lora_group=lora_group,
|
| 221 |
lora_path=lora_path,
|
| 222 |
lora_strength=lora_strength,
|
| 223 |
generate_btn=generate_btn,
|
|
|
|
| 229 |
def build_upscale_tab() -> dict[str, gr.components.Component]:
|
| 230 |
with gr.Row():
|
| 231 |
with gr.Column(scale=4):
|
| 232 |
+
prompt = gr.Textbox(
|
| 233 |
+
label="Refinement prompt",
|
| 234 |
+
info=TOOLTIPS["prompt"],
|
| 235 |
+
value="masterpiece, 8k",
|
| 236 |
+
lines=2,
|
| 237 |
+
)
|
| 238 |
+
gr.Markdown(
|
| 239 |
+
f"**Input image** \n<span style='color:#988B7C;font-size:12px;'>{TOOLTIPS['upscale_image']}</span>"
|
| 240 |
+
)
|
| 241 |
+
input_image = gr.Image(
|
| 242 |
+
show_label=False,
|
| 243 |
+
type="pil",
|
| 244 |
+
height=240,
|
| 245 |
+
)
|
| 246 |
+
|
| 247 |
with gr.Row():
|
| 248 |
+
refine_steps = gr.Slider(
|
| 249 |
+
1,
|
| 250 |
+
20,
|
| 251 |
+
value=5,
|
| 252 |
+
step=1,
|
| 253 |
+
label="Refine steps",
|
| 254 |
+
info=TOOLTIPS["refine_steps"],
|
| 255 |
+
)
|
| 256 |
+
refine_denoise = gr.Slider(
|
| 257 |
+
0.0,
|
| 258 |
+
1.0,
|
| 259 |
+
value=0.33,
|
| 260 |
+
step=0.01,
|
| 261 |
+
label="Refine denoise",
|
| 262 |
+
info=TOOLTIPS["refine_denoise"],
|
| 263 |
+
)
|
| 264 |
+
|
| 265 |
+
lora_enabled = gr.Checkbox(label="Use a LoRA", value=False)
|
| 266 |
+
with gr.Group(visible=False) as lora_group:
|
| 267 |
+
lora_path = gr.File(
|
| 268 |
+
label="LoRA file",
|
| 269 |
+
file_types=[".safetensors"],
|
| 270 |
+
type="filepath",
|
| 271 |
+
elem_classes=["zis-lora-file"],
|
| 272 |
+
)
|
| 273 |
+
lora_strength = gr.Slider(
|
| 274 |
+
0.0,
|
| 275 |
+
1.5,
|
| 276 |
+
value=0.8,
|
| 277 |
+
step=0.05,
|
| 278 |
+
label="LoRA strength",
|
| 279 |
+
info=TOOLTIPS["lora_strength"],
|
| 280 |
+
)
|
| 281 |
+
|
| 282 |
+
with gr.Accordion("Advanced", open=False):
|
| 283 |
+
seed = gr.Number(value=0, precision=0, label="Seed", info=TOOLTIPS["seed"])
|
| 284 |
+
|
| 285 |
generate_btn = gr.Button("Generate", variant="primary")
|
| 286 |
+
|
| 287 |
with gr.Column(scale=5):
|
| 288 |
+
gr.Markdown(
|
| 289 |
+
f"**Output (2x upscaled)** \n<span style='color:#988B7C;font-size:12px;'>{TOOLTIPS['output']}</span>"
|
| 290 |
+
)
|
| 291 |
+
output_image = gr.Image(
|
| 292 |
+
show_label=False,
|
| 293 |
+
type="pil",
|
| 294 |
+
height=512,
|
| 295 |
+
show_download_button=True,
|
| 296 |
+
)
|
| 297 |
output_meta = gr.JSON(label="Meta", value={})
|
| 298 |
+
|
| 299 |
return dict(
|
| 300 |
prompt=prompt,
|
| 301 |
input_image=input_image,
|
| 302 |
refine_steps=refine_steps,
|
| 303 |
refine_denoise=refine_denoise,
|
| 304 |
seed=seed,
|
| 305 |
+
lora_enabled=lora_enabled,
|
| 306 |
+
lora_group=lora_group,
|
| 307 |
lora_path=lora_path,
|
| 308 |
lora_strength=lora_strength,
|
| 309 |
generate_btn=generate_btn,
|