Upload folder using huggingface_hub
Browse files- README.md +108 -116
- app.py +99 -108
- space.py +104 -112
- src/README.md +108 -116
- src/backend/gradio_propertysheet/propertysheet.py +72 -30
- src/backend/gradio_propertysheet/templates/component/index.js +0 -0
- src/backend/gradio_propertysheet/templates/component/style.css +1 -1
- src/demo/app.py +99 -108
- src/demo/space.py +104 -112
- src/frontend/Index.svelte +105 -111
- src/pyproject.toml +2 -2
README.md
CHANGED
|
@@ -10,7 +10,7 @@ app_file: space.py
|
|
| 10 |
---
|
| 11 |
|
| 12 |
# `gradio_propertysheet`
|
| 13 |
-
<img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.
|
| 14 |
|
| 15 |
The **PropertySheet** component for Gradio allows you to automatically generate a complete and interactive settings panel from a standard Python `dataclass`. It's designed to bring the power of IDE-like property editors directly into your Gradio applications.
|
| 16 |
|
|
@@ -50,115 +50,55 @@ from dataclasses import dataclass, field, asdict
|
|
| 50 |
from typing import Literal
|
| 51 |
from gradio_propertysheet import PropertySheet
|
| 52 |
|
| 53 |
-
# ---
|
|
|
|
| 54 |
@dataclass
|
| 55 |
class ModelSettings:
|
| 56 |
"""Settings for loading models, VAEs, etc."""
|
| 57 |
-
model_type: Literal["SD 1.5", "SDXL", "Pony", "Custom"] = field(
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
)
|
| 61 |
-
custom_model_path: str = field(
|
| 62 |
-
default="/path/to/default.safetensors",
|
| 63 |
-
metadata={"label": "Custom Model Path", "interactive_if": {"field": "model_type", "value": "Custom"}}
|
| 64 |
-
)
|
| 65 |
-
vae_path: str = field(
|
| 66 |
-
default="",
|
| 67 |
-
metadata={"label": "VAE Path (optional)"}
|
| 68 |
-
)
|
| 69 |
|
| 70 |
@dataclass
|
| 71 |
class SamplingSettings:
|
| 72 |
"""Settings for the image sampling process."""
|
| 73 |
-
sampler_name: Literal["Euler", "Euler a", "DPM++ 2M Karras", "UniPC"] = field(
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
)
|
| 77 |
-
steps: int = field(
|
| 78 |
-
default=25,
|
| 79 |
-
metadata={"component": "slider", "minimum": 1, "maximum": 150, "step": 1, "label": "Sampling Steps", "help": "More steps can improve quality."}
|
| 80 |
-
)
|
| 81 |
-
cfg_scale: float = field(
|
| 82 |
-
default=7.0,
|
| 83 |
-
metadata={"component": "slider", "minimum": 1.0, "maximum": 30.0, "step": 0.5, "label": "CFG Scale", "help": "How strongly the prompt is adhered to."}
|
| 84 |
-
)
|
| 85 |
|
| 86 |
@dataclass
|
| 87 |
class ImageSettings:
|
| 88 |
"""Settings for image dimensions."""
|
| 89 |
-
width: int = field(
|
| 90 |
-
|
| 91 |
-
metadata={"component": "slider", "minimum": 512, "maximum": 2048, "step": 64, "label": "Image Width"}
|
| 92 |
-
)
|
| 93 |
-
height: int = field(
|
| 94 |
-
default=1024,
|
| 95 |
-
metadata={"component": "slider", "minimum": 512, "maximum": 2048, "step": 64, "label": "Image Height"}
|
| 96 |
-
)
|
| 97 |
|
| 98 |
@dataclass
|
| 99 |
class PostprocessingSettings:
|
| 100 |
"""Settings for image post-processing effects."""
|
| 101 |
-
restore_faces: bool = field(
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
)
|
| 105 |
-
enable_hr: bool = field(
|
| 106 |
-
default=False,
|
| 107 |
-
metadata={"label": "Hires. fix", "help": "Enable a second pass at a higher resolution."}
|
| 108 |
-
)
|
| 109 |
-
denoising_strength: float = field(
|
| 110 |
-
default=0.45,
|
| 111 |
-
metadata={"component": "slider", "minimum": 0.0, "maximum": 1.0, "step": 0.01, "label": "Denoising Strength", "interactive_if": {"field": "enable_hr", "value": True}}
|
| 112 |
-
)
|
| 113 |
|
| 114 |
@dataclass
|
| 115 |
class AdvancedSettings:
|
| 116 |
"""Advanced and rarely changed settings."""
|
| 117 |
-
clip_skip: int = field(
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
)
|
| 121 |
-
noise_schedule: Literal["Default", "Karras", "Exponential"] = field(
|
| 122 |
-
default="Karras",
|
| 123 |
-
metadata={"component": "dropdown", "label": "Noise Schedule"}
|
| 124 |
-
)
|
| 125 |
-
do_not_scale_cond_uncond: bool = field(
|
| 126 |
-
default=False,
|
| 127 |
-
metadata={"label": "Do not scale cond/uncond"}
|
| 128 |
-
)
|
| 129 |
-
s_churn: int = field(
|
| 130 |
-
default=1,
|
| 131 |
-
metadata={"component": "number_integer", "minimum": 1, "maximum": 12, "label": "S_churn", "help": "S_churn value for generation."}
|
| 132 |
-
)
|
| 133 |
|
| 134 |
@dataclass
|
| 135 |
class ScriptSettings:
|
| 136 |
"""Settings for automation scripts like X/Y/Z plots."""
|
| 137 |
-
script_name: Literal["None", "Prompt matrix", "X/Y/Z plot"] = field(
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
)
|
| 141 |
-
x_values: str = field(
|
| 142 |
-
default="-1, 10, 20",
|
| 143 |
-
metadata={"label": "X axis values", "interactive_if": {"field": "script_name", "value": "X/Y/Z plot"}}
|
| 144 |
-
)
|
| 145 |
-
y_values: str = field(
|
| 146 |
-
default="",
|
| 147 |
-
metadata={"label": "Y axis values", "interactive_if": {"field": "script_name", "value": "X/Y/Z plot"}}
|
| 148 |
-
)
|
| 149 |
|
| 150 |
@dataclass
|
| 151 |
class RenderConfig:
|
| 152 |
"""Main configuration object for rendering, grouping all settings."""
|
| 153 |
-
seed: int = field(
|
| 154 |
-
|
| 155 |
-
metadata={"component": "number_integer", "label": "Seed (-1 for random)", "help": "The random seed for generation."}
|
| 156 |
-
)
|
| 157 |
-
batch_size: int = field(
|
| 158 |
-
default=1,
|
| 159 |
-
metadata={"component": "slider", "minimum": 1, "maximum": 8, "step": 1, "label": "Batch Size"}
|
| 160 |
-
)
|
| 161 |
-
# Nested groups
|
| 162 |
model: ModelSettings = field(default_factory=ModelSettings)
|
| 163 |
sampling: SamplingSettings = field(default_factory=SamplingSettings)
|
| 164 |
image: ImageSettings = field(default_factory=ImageSettings)
|
|
@@ -166,7 +106,6 @@ class RenderConfig:
|
|
| 166 |
scripts: ScriptSettings = field(default_factory=ScriptSettings)
|
| 167 |
advanced: AdvancedSettings = field(default_factory=AdvancedSettings)
|
| 168 |
|
| 169 |
-
|
| 170 |
@dataclass
|
| 171 |
class Lighting:
|
| 172 |
"""Lighting settings for the environment."""
|
|
@@ -180,8 +119,8 @@ class EnvironmentConfig:
|
|
| 180 |
background: Literal["Sky", "Color", "Image"] = field(default="Sky", metadata={"component": "dropdown"})
|
| 181 |
lighting: Lighting = field(default_factory=Lighting)
|
| 182 |
|
| 183 |
-
|
| 184 |
# --- Initial Instances ---
|
|
|
|
| 185 |
initial_render_config = RenderConfig()
|
| 186 |
initial_env_config = EnvironmentConfig()
|
| 187 |
|
|
@@ -191,66 +130,118 @@ with gr.Blocks(title="PropertySheet Demo") as demo:
|
|
| 191 |
gr.Markdown("An example of a realistic application layout using the `PropertySheet` component as a sidebar for settings.")
|
| 192 |
gr.Markdown("<span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_propertysheet'>Component GitHub Code</a></span>")
|
| 193 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
with gr.Row():
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
#gr.Image(label="Main Viewport", height=500, value=None)
|
| 198 |
-
gr.Textbox(label="AI Prompt", lines=3, placeholder="Enter your prompt here...")
|
| 199 |
-
gr.Button("Generate", variant="primary")
|
| 200 |
with gr.Row():
|
| 201 |
output_render_json = gr.JSON(label="Live Render State")
|
| 202 |
output_env_json = gr.JSON(label="Live Environment State")
|
| 203 |
|
| 204 |
-
# Sidebar with Property Sheets on the right
|
| 205 |
with gr.Column(scale=1):
|
| 206 |
render_sheet = PropertySheet(
|
| 207 |
-
value=initial_render_config,
|
| 208 |
label="Render Settings",
|
| 209 |
width=400,
|
| 210 |
-
height=550
|
|
|
|
| 211 |
)
|
| 212 |
environment_sheet = PropertySheet(
|
| 213 |
value=initial_env_config,
|
| 214 |
label="Environment Settings",
|
| 215 |
width=400,
|
| 216 |
-
open=False
|
|
|
|
| 217 |
)
|
| 218 |
|
| 219 |
# --- Event Handlers ---
|
| 220 |
-
def
|
| 221 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
if updated_config is None:
|
| 223 |
-
return
|
| 224 |
|
| 225 |
-
# Example of business logic
|
| 226 |
if updated_config.model.model_type != "Custom":
|
| 227 |
updated_config.model.custom_model_path = "/path/to/default.safetensors"
|
| 228 |
-
|
| 229 |
-
return updated_config, asdict(updated_config)
|
| 230 |
|
| 231 |
-
def handle_env_change(updated_config: EnvironmentConfig | None):
|
| 232 |
-
"""
|
| 233 |
if updated_config is None:
|
| 234 |
-
return
|
| 235 |
-
return updated_config, asdict(updated_config)
|
| 236 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
render_sheet.change(
|
| 238 |
fn=handle_render_change,
|
| 239 |
-
inputs=[render_sheet],
|
| 240 |
-
outputs=[render_sheet, output_render_json]
|
| 241 |
)
|
|
|
|
|
|
|
| 242 |
environment_sheet.change(
|
| 243 |
fn=handle_env_change,
|
| 244 |
-
inputs=[environment_sheet],
|
| 245 |
-
outputs=[environment_sheet, output_env_json]
|
| 246 |
)
|
| 247 |
|
| 248 |
-
# Load initial
|
| 249 |
demo.load(
|
| 250 |
fn=lambda: (asdict(initial_render_config), asdict(initial_env_config)),
|
| 251 |
outputs=[output_render_json, output_env_json]
|
| 252 |
)
|
| 253 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
if __name__ == "__main__":
|
| 255 |
demo.launch()
|
| 256 |
```
|
|
@@ -418,10 +409,10 @@ list[str] | str | None
|
|
| 418 |
|
| 419 |
| name | description |
|
| 420 |
|:-----|:------------|
|
| 421 |
-
| `change` |
|
| 422 |
-
| `input` |
|
| 423 |
-
| `expand` |
|
| 424 |
-
| `collapse` |
|
| 425 |
|
| 426 |
|
| 427 |
|
|
@@ -434,7 +425,8 @@ The impact on the users predict function varies depending on whether the compone
|
|
| 434 |
|
| 435 |
The code snippet below is accurate in cases where the component is used as both an input and an output.
|
| 436 |
|
| 437 |
-
|
|
|
|
| 438 |
|
| 439 |
```python
|
| 440 |
def predict(
|
|
@@ -442,4 +434,4 @@ The code snippet below is accurate in cases where the component is used as both
|
|
| 442 |
) -> Any:
|
| 443 |
return value
|
| 444 |
```
|
| 445 |
-
|
|
|
|
| 10 |
---
|
| 11 |
|
| 12 |
# `gradio_propertysheet`
|
| 13 |
+
<img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.2%20-%20blue"> <a href="https://huggingface.co/spaces/elismasilva/gradio_propertysheet"><img src="https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Demo-blue"></a><p><span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_propertysheet'>Component GitHub Code</a></span></p>
|
| 14 |
|
| 15 |
The **PropertySheet** component for Gradio allows you to automatically generate a complete and interactive settings panel from a standard Python `dataclass`. It's designed to bring the power of IDE-like property editors directly into your Gradio applications.
|
| 16 |
|
|
|
|
| 50 |
from typing import Literal
|
| 51 |
from gradio_propertysheet import PropertySheet
|
| 52 |
|
| 53 |
+
# --- Configuration Data Models ---
|
| 54 |
+
# These dataclasses define the structure for all settings panels.
|
| 55 |
@dataclass
|
| 56 |
class ModelSettings:
|
| 57 |
"""Settings for loading models, VAEs, etc."""
|
| 58 |
+
model_type: Literal["SD 1.5", "SDXL", "Pony", "Custom"] = field(default="SDXL", metadata={"component": "dropdown", "label": "Base Model"})
|
| 59 |
+
custom_model_path: str = field(default="/path/to/default.safetensors", metadata={"label": "Custom Model Path", "interactive_if": {"field": "model_type", "value": "Custom"}})
|
| 60 |
+
vae_path: str = field(default="", metadata={"label": "VAE Path (optional)"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
@dataclass
|
| 63 |
class SamplingSettings:
|
| 64 |
"""Settings for the image sampling process."""
|
| 65 |
+
sampler_name: Literal["Euler", "Euler a", "DPM++ 2M Karras", "UniPC"] = field(default="DPM++ 2M Karras", metadata={"component": "dropdown", "label": "Sampler", "help": "The algorithm for the diffusion process."})
|
| 66 |
+
steps: int = field(default=25, metadata={"component": "slider", "minimum": 1, "maximum": 150, "step": 1, "label": "Sampling Steps", "help": "More steps can improve quality."})
|
| 67 |
+
cfg_scale: float = field(default=7.0, metadata={"component": "slider", "minimum": 1.0, "maximum": 30.0, "step": 0.5, "label": "CFG Scale", "help": "How strongly the prompt is adhered to."})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
@dataclass
|
| 70 |
class ImageSettings:
|
| 71 |
"""Settings for image dimensions."""
|
| 72 |
+
width: int = field(default=1024, metadata={"component": "slider", "minimum": 512, "maximum": 2048, "step": 64, "label": "Image Width"})
|
| 73 |
+
height: int = field(default=1024, metadata={"component": "slider", "minimum": 512, "maximum": 2048, "step": 64, "label": "Image Height"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
@dataclass
|
| 76 |
class PostprocessingSettings:
|
| 77 |
"""Settings for image post-processing effects."""
|
| 78 |
+
restore_faces: bool = field(default=True, metadata={"label": "Restore Faces", "help": "Use a secondary model to fix distorted faces."})
|
| 79 |
+
enable_hr: bool = field(default=False, metadata={"label": "Hires. fix", "help": "Enable a second pass at a higher resolution."})
|
| 80 |
+
denoising_strength: float = field(default=0.45, metadata={"component": "slider", "minimum": 0.0, "maximum": 1.0, "step": 0.01, "label": "Denoising Strength", "interactive_if": {"field": "enable_hr", "value": True}})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
@dataclass
|
| 83 |
class AdvancedSettings:
|
| 84 |
"""Advanced and rarely changed settings."""
|
| 85 |
+
clip_skip: int = field(default=2, metadata={"component": "slider", "minimum": 1, "maximum": 12, "step": 1, "label": "CLIP Skip", "help": "Skip final layers of the text encoder."})
|
| 86 |
+
noise_schedule: Literal["Default", "Karras", "Exponential"] = field(default="Karras", metadata={"component": "dropdown", "label": "Noise Schedule"})
|
| 87 |
+
do_not_scale_cond_uncond: bool = field(default=False, metadata={"label": "Do not scale cond/uncond"})
|
| 88 |
+
s_churn: int = field(default=1, metadata={"component": "number_integer", "minimum": 1, "maximum": 12, "label": "S_churn", "help": "S_churn value for generation."})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
@dataclass
|
| 91 |
class ScriptSettings:
|
| 92 |
"""Settings for automation scripts like X/Y/Z plots."""
|
| 93 |
+
script_name: Literal["None", "Prompt matrix", "X/Y/Z plot"] = field(default="None", metadata={"component": "dropdown", "label": "Script"})
|
| 94 |
+
x_values: str = field(default="-1, 10, 20", metadata={"label": "X axis values", "interactive_if": {"field": "script_name", "value": "X/Y/Z plot"}})
|
| 95 |
+
y_values: str = field(default="", metadata={"label": "Y axis values", "interactive_if": {"field": "script_name", "value": "X/Y/Z plot"}})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
@dataclass
|
| 98 |
class RenderConfig:
|
| 99 |
"""Main configuration object for rendering, grouping all settings."""
|
| 100 |
+
seed: int = field(default=-1, metadata={"component": "number_integer", "label": "Seed (-1 for random)", "help": "The random seed for generation."})
|
| 101 |
+
batch_size: int = field(default=1, metadata={"component": "slider", "minimum": 1, "maximum": 8, "step": 1, "label": "Batch Size"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
model: ModelSettings = field(default_factory=ModelSettings)
|
| 103 |
sampling: SamplingSettings = field(default_factory=SamplingSettings)
|
| 104 |
image: ImageSettings = field(default_factory=ImageSettings)
|
|
|
|
| 106 |
scripts: ScriptSettings = field(default_factory=ScriptSettings)
|
| 107 |
advanced: AdvancedSettings = field(default_factory=AdvancedSettings)
|
| 108 |
|
|
|
|
| 109 |
@dataclass
|
| 110 |
class Lighting:
|
| 111 |
"""Lighting settings for the environment."""
|
|
|
|
| 119 |
background: Literal["Sky", "Color", "Image"] = field(default="Sky", metadata={"component": "dropdown"})
|
| 120 |
lighting: Lighting = field(default_factory=Lighting)
|
| 121 |
|
|
|
|
| 122 |
# --- Initial Instances ---
|
| 123 |
+
# Create default instances of the configuration objects.
|
| 124 |
initial_render_config = RenderConfig()
|
| 125 |
initial_env_config = EnvironmentConfig()
|
| 126 |
|
|
|
|
| 130 |
gr.Markdown("An example of a realistic application layout using the `PropertySheet` component as a sidebar for settings.")
|
| 131 |
gr.Markdown("<span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_propertysheet'>Component GitHub Code</a></span>")
|
| 132 |
|
| 133 |
+
# --- Persistent State Management ---
|
| 134 |
+
# Use gr.State to hold the application's data. This is the "single source of truth".
|
| 135 |
+
render_state = gr.State(value=initial_render_config)
|
| 136 |
+
env_state = gr.State(value=initial_env_config)
|
| 137 |
+
|
| 138 |
with gr.Row():
|
| 139 |
+
with gr.Column(scale=3):
|
| 140 |
+
generate = gr.Button("Show Settings", variant="primary")
|
|
|
|
|
|
|
|
|
|
| 141 |
with gr.Row():
|
| 142 |
output_render_json = gr.JSON(label="Live Render State")
|
| 143 |
output_env_json = gr.JSON(label="Live Environment State")
|
| 144 |
|
|
|
|
| 145 |
with gr.Column(scale=1):
|
| 146 |
render_sheet = PropertySheet(
|
| 147 |
+
value=initial_render_config,
|
| 148 |
label="Render Settings",
|
| 149 |
width=400,
|
| 150 |
+
height=550,
|
| 151 |
+
visible=False
|
| 152 |
)
|
| 153 |
environment_sheet = PropertySheet(
|
| 154 |
value=initial_env_config,
|
| 155 |
label="Environment Settings",
|
| 156 |
width=400,
|
| 157 |
+
open=False,
|
| 158 |
+
visible=False
|
| 159 |
)
|
| 160 |
|
| 161 |
# --- Event Handlers ---
|
| 162 |
+
def change_visibility(render_config, env_config):
|
| 163 |
+
"""
|
| 164 |
+
Handles the visibility toggle for the property sheets.
|
| 165 |
+
NOTE: This approach of modifying the component object's attribute directly
|
| 166 |
+
is not reliable for tracking state changes in Gradio. A gr.State object is
|
| 167 |
+
the recommended way to manage UI state like visibility.
|
| 168 |
+
"""
|
| 169 |
+
if render_sheet.visible != environment_sheet.visible:
|
| 170 |
+
render_sheet.visible = False
|
| 171 |
+
environment_sheet.visible = False
|
| 172 |
+
|
| 173 |
+
if render_sheet.visible == False and environment_sheet.visible == False:
|
| 174 |
+
render_sheet.visible = True
|
| 175 |
+
environment_sheet.visible = True
|
| 176 |
+
return (
|
| 177 |
+
gr.update(visible=True, value=render_config),
|
| 178 |
+
gr.update(visible=True, value=env_config),
|
| 179 |
+
gr.update(value="Hide Settings")
|
| 180 |
+
)
|
| 181 |
+
else:
|
| 182 |
+
render_sheet.visible = False
|
| 183 |
+
environment_sheet.visible = False
|
| 184 |
+
return (
|
| 185 |
+
gr.update(visible=False, value=render_config),
|
| 186 |
+
gr.update(visible=False, value=env_config),
|
| 187 |
+
gr.update(value="Show Settings")
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
def handle_render_change(updated_config: RenderConfig | None, current_state: RenderConfig):
|
| 191 |
+
"""Processes updates from the render PropertySheet and syncs the state."""
|
| 192 |
if updated_config is None:
|
| 193 |
+
return current_state, asdict(current_state), current_state
|
| 194 |
|
| 195 |
+
# Example of applying business logic
|
| 196 |
if updated_config.model.model_type != "Custom":
|
| 197 |
updated_config.model.custom_model_path = "/path/to/default.safetensors"
|
| 198 |
+
|
| 199 |
+
return updated_config, asdict(updated_config), updated_config
|
| 200 |
|
| 201 |
+
def handle_env_change(updated_config: EnvironmentConfig | None, current_state: EnvironmentConfig):
|
| 202 |
+
"""Processes updates from the environment PropertySheet and syncs the state."""
|
| 203 |
if updated_config is None:
|
| 204 |
+
return current_state, asdict(current_state), current_state
|
| 205 |
+
return updated_config, asdict(updated_config), updated_config
|
| 206 |
+
|
| 207 |
+
# --- Event Listeners ---
|
| 208 |
+
# Toggle the property sheets' visibility on button click.
|
| 209 |
+
generate.click(
|
| 210 |
+
fn=change_visibility,
|
| 211 |
+
inputs=[render_state, env_state],
|
| 212 |
+
outputs=[render_sheet, environment_sheet, generate]
|
| 213 |
+
)
|
| 214 |
+
|
| 215 |
+
# Syncs changes from the UI back to the state and JSON display.
|
| 216 |
render_sheet.change(
|
| 217 |
fn=handle_render_change,
|
| 218 |
+
inputs=[render_sheet, render_state],
|
| 219 |
+
outputs=[render_sheet, output_render_json, render_state]
|
| 220 |
)
|
| 221 |
+
|
| 222 |
+
# Syncs changes from the UI back to the state and JSON display.
|
| 223 |
environment_sheet.change(
|
| 224 |
fn=handle_env_change,
|
| 225 |
+
inputs=[environment_sheet, env_state],
|
| 226 |
+
outputs=[environment_sheet, output_env_json, env_state]
|
| 227 |
)
|
| 228 |
|
| 229 |
+
# Load initial data into JSON displays on app start.
|
| 230 |
demo.load(
|
| 231 |
fn=lambda: (asdict(initial_render_config), asdict(initial_env_config)),
|
| 232 |
outputs=[output_render_json, output_env_json]
|
| 233 |
)
|
| 234 |
|
| 235 |
+
# Ensure components are populated with state values on load/reload.
|
| 236 |
+
demo.load(
|
| 237 |
+
fn=lambda render_config, env_config: (
|
| 238 |
+
gr.update(value=render_config),
|
| 239 |
+
gr.update(value=env_config)
|
| 240 |
+
),
|
| 241 |
+
inputs=[render_state, env_state],
|
| 242 |
+
outputs=[render_sheet, environment_sheet]
|
| 243 |
+
)
|
| 244 |
+
|
| 245 |
if __name__ == "__main__":
|
| 246 |
demo.launch()
|
| 247 |
```
|
|
|
|
| 409 |
|
| 410 |
| name | description |
|
| 411 |
|:-----|:------------|
|
| 412 |
+
| `change` | |
|
| 413 |
+
| `input` | |
|
| 414 |
+
| `expand` | |
|
| 415 |
+
| `collapse` | |
|
| 416 |
|
| 417 |
|
| 418 |
|
|
|
|
| 425 |
|
| 426 |
The code snippet below is accurate in cases where the component is used as both an input and an output.
|
| 427 |
|
| 428 |
+
- **As output:** Is passed, a new, updated instance of the dataclass.
|
| 429 |
+
- **As input:** Should return, the dataclass instance to process.
|
| 430 |
|
| 431 |
```python
|
| 432 |
def predict(
|
|
|
|
| 434 |
) -> Any:
|
| 435 |
return value
|
| 436 |
```
|
| 437 |
+
|
app.py
CHANGED
|
@@ -3,115 +3,55 @@ from dataclasses import dataclass, field, asdict
|
|
| 3 |
from typing import Literal
|
| 4 |
from gradio_propertysheet import PropertySheet
|
| 5 |
|
| 6 |
-
# ---
|
|
|
|
| 7 |
@dataclass
|
| 8 |
class ModelSettings:
|
| 9 |
"""Settings for loading models, VAEs, etc."""
|
| 10 |
-
model_type: Literal["SD 1.5", "SDXL", "Pony", "Custom"] = field(
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
)
|
| 14 |
-
custom_model_path: str = field(
|
| 15 |
-
default="/path/to/default.safetensors",
|
| 16 |
-
metadata={"label": "Custom Model Path", "interactive_if": {"field": "model_type", "value": "Custom"}}
|
| 17 |
-
)
|
| 18 |
-
vae_path: str = field(
|
| 19 |
-
default="",
|
| 20 |
-
metadata={"label": "VAE Path (optional)"}
|
| 21 |
-
)
|
| 22 |
|
| 23 |
@dataclass
|
| 24 |
class SamplingSettings:
|
| 25 |
"""Settings for the image sampling process."""
|
| 26 |
-
sampler_name: Literal["Euler", "Euler a", "DPM++ 2M Karras", "UniPC"] = field(
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
)
|
| 30 |
-
steps: int = field(
|
| 31 |
-
default=25,
|
| 32 |
-
metadata={"component": "slider", "minimum": 1, "maximum": 150, "step": 1, "label": "Sampling Steps", "help": "More steps can improve quality."}
|
| 33 |
-
)
|
| 34 |
-
cfg_scale: float = field(
|
| 35 |
-
default=7.0,
|
| 36 |
-
metadata={"component": "slider", "minimum": 1.0, "maximum": 30.0, "step": 0.5, "label": "CFG Scale", "help": "How strongly the prompt is adhered to."}
|
| 37 |
-
)
|
| 38 |
|
| 39 |
@dataclass
|
| 40 |
class ImageSettings:
|
| 41 |
"""Settings for image dimensions."""
|
| 42 |
-
width: int = field(
|
| 43 |
-
|
| 44 |
-
metadata={"component": "slider", "minimum": 512, "maximum": 2048, "step": 64, "label": "Image Width"}
|
| 45 |
-
)
|
| 46 |
-
height: int = field(
|
| 47 |
-
default=1024,
|
| 48 |
-
metadata={"component": "slider", "minimum": 512, "maximum": 2048, "step": 64, "label": "Image Height"}
|
| 49 |
-
)
|
| 50 |
|
| 51 |
@dataclass
|
| 52 |
class PostprocessingSettings:
|
| 53 |
"""Settings for image post-processing effects."""
|
| 54 |
-
restore_faces: bool = field(
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
)
|
| 58 |
-
enable_hr: bool = field(
|
| 59 |
-
default=False,
|
| 60 |
-
metadata={"label": "Hires. fix", "help": "Enable a second pass at a higher resolution."}
|
| 61 |
-
)
|
| 62 |
-
denoising_strength: float = field(
|
| 63 |
-
default=0.45,
|
| 64 |
-
metadata={"component": "slider", "minimum": 0.0, "maximum": 1.0, "step": 0.01, "label": "Denoising Strength", "interactive_if": {"field": "enable_hr", "value": True}}
|
| 65 |
-
)
|
| 66 |
|
| 67 |
@dataclass
|
| 68 |
class AdvancedSettings:
|
| 69 |
"""Advanced and rarely changed settings."""
|
| 70 |
-
clip_skip: int = field(
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
)
|
| 74 |
-
noise_schedule: Literal["Default", "Karras", "Exponential"] = field(
|
| 75 |
-
default="Karras",
|
| 76 |
-
metadata={"component": "dropdown", "label": "Noise Schedule"}
|
| 77 |
-
)
|
| 78 |
-
do_not_scale_cond_uncond: bool = field(
|
| 79 |
-
default=False,
|
| 80 |
-
metadata={"label": "Do not scale cond/uncond"}
|
| 81 |
-
)
|
| 82 |
-
s_churn: int = field(
|
| 83 |
-
default=1,
|
| 84 |
-
metadata={"component": "number_integer", "minimum": 1, "maximum": 12, "label": "S_churn", "help": "S_churn value for generation."}
|
| 85 |
-
)
|
| 86 |
|
| 87 |
@dataclass
|
| 88 |
class ScriptSettings:
|
| 89 |
"""Settings for automation scripts like X/Y/Z plots."""
|
| 90 |
-
script_name: Literal["None", "Prompt matrix", "X/Y/Z plot"] = field(
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
)
|
| 94 |
-
x_values: str = field(
|
| 95 |
-
default="-1, 10, 20",
|
| 96 |
-
metadata={"label": "X axis values", "interactive_if": {"field": "script_name", "value": "X/Y/Z plot"}}
|
| 97 |
-
)
|
| 98 |
-
y_values: str = field(
|
| 99 |
-
default="",
|
| 100 |
-
metadata={"label": "Y axis values", "interactive_if": {"field": "script_name", "value": "X/Y/Z plot"}}
|
| 101 |
-
)
|
| 102 |
|
| 103 |
@dataclass
|
| 104 |
class RenderConfig:
|
| 105 |
"""Main configuration object for rendering, grouping all settings."""
|
| 106 |
-
seed: int = field(
|
| 107 |
-
|
| 108 |
-
metadata={"component": "number_integer", "label": "Seed (-1 for random)", "help": "The random seed for generation."}
|
| 109 |
-
)
|
| 110 |
-
batch_size: int = field(
|
| 111 |
-
default=1,
|
| 112 |
-
metadata={"component": "slider", "minimum": 1, "maximum": 8, "step": 1, "label": "Batch Size"}
|
| 113 |
-
)
|
| 114 |
-
# Nested groups
|
| 115 |
model: ModelSettings = field(default_factory=ModelSettings)
|
| 116 |
sampling: SamplingSettings = field(default_factory=SamplingSettings)
|
| 117 |
image: ImageSettings = field(default_factory=ImageSettings)
|
|
@@ -119,7 +59,6 @@ class RenderConfig:
|
|
| 119 |
scripts: ScriptSettings = field(default_factory=ScriptSettings)
|
| 120 |
advanced: AdvancedSettings = field(default_factory=AdvancedSettings)
|
| 121 |
|
| 122 |
-
|
| 123 |
@dataclass
|
| 124 |
class Lighting:
|
| 125 |
"""Lighting settings for the environment."""
|
|
@@ -133,8 +72,8 @@ class EnvironmentConfig:
|
|
| 133 |
background: Literal["Sky", "Color", "Image"] = field(default="Sky", metadata={"component": "dropdown"})
|
| 134 |
lighting: Lighting = field(default_factory=Lighting)
|
| 135 |
|
| 136 |
-
|
| 137 |
# --- Initial Instances ---
|
|
|
|
| 138 |
initial_render_config = RenderConfig()
|
| 139 |
initial_env_config = EnvironmentConfig()
|
| 140 |
|
|
@@ -144,65 +83,117 @@ with gr.Blocks(title="PropertySheet Demo") as demo:
|
|
| 144 |
gr.Markdown("An example of a realistic application layout using the `PropertySheet` component as a sidebar for settings.")
|
| 145 |
gr.Markdown("<span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_propertysheet'>Component GitHub Code</a></span>")
|
| 146 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
with gr.Row():
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
#gr.Image(label="Main Viewport", height=500, value=None)
|
| 151 |
-
gr.Textbox(label="AI Prompt", lines=3, placeholder="Enter your prompt here...")
|
| 152 |
-
gr.Button("Generate", variant="primary")
|
| 153 |
with gr.Row():
|
| 154 |
output_render_json = gr.JSON(label="Live Render State")
|
| 155 |
output_env_json = gr.JSON(label="Live Environment State")
|
| 156 |
|
| 157 |
-
# Sidebar with Property Sheets on the right
|
| 158 |
with gr.Column(scale=1):
|
| 159 |
render_sheet = PropertySheet(
|
| 160 |
-
value=initial_render_config,
|
| 161 |
label="Render Settings",
|
| 162 |
width=400,
|
| 163 |
-
height=550
|
|
|
|
| 164 |
)
|
| 165 |
environment_sheet = PropertySheet(
|
| 166 |
value=initial_env_config,
|
| 167 |
label="Environment Settings",
|
| 168 |
width=400,
|
| 169 |
-
open=False
|
|
|
|
| 170 |
)
|
| 171 |
|
| 172 |
# --- Event Handlers ---
|
| 173 |
-
def
|
| 174 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
if updated_config is None:
|
| 176 |
-
return
|
| 177 |
|
| 178 |
-
# Example of business logic
|
| 179 |
if updated_config.model.model_type != "Custom":
|
| 180 |
updated_config.model.custom_model_path = "/path/to/default.safetensors"
|
| 181 |
-
|
| 182 |
-
return updated_config, asdict(updated_config)
|
| 183 |
|
| 184 |
-
def handle_env_change(updated_config: EnvironmentConfig | None):
|
| 185 |
-
"""
|
| 186 |
if updated_config is None:
|
| 187 |
-
return
|
| 188 |
-
return updated_config, asdict(updated_config)
|
| 189 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
render_sheet.change(
|
| 191 |
fn=handle_render_change,
|
| 192 |
-
inputs=[render_sheet],
|
| 193 |
-
outputs=[render_sheet, output_render_json]
|
| 194 |
)
|
|
|
|
|
|
|
| 195 |
environment_sheet.change(
|
| 196 |
fn=handle_env_change,
|
| 197 |
-
inputs=[environment_sheet],
|
| 198 |
-
outputs=[environment_sheet, output_env_json]
|
| 199 |
)
|
| 200 |
|
| 201 |
-
# Load initial
|
| 202 |
demo.load(
|
| 203 |
fn=lambda: (asdict(initial_render_config), asdict(initial_env_config)),
|
| 204 |
outputs=[output_render_json, output_env_json]
|
| 205 |
)
|
| 206 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
if __name__ == "__main__":
|
| 208 |
demo.launch()
|
|
|
|
| 3 |
from typing import Literal
|
| 4 |
from gradio_propertysheet import PropertySheet
|
| 5 |
|
| 6 |
+
# --- Configuration Data Models ---
|
| 7 |
+
# These dataclasses define the structure for all settings panels.
|
| 8 |
@dataclass
|
| 9 |
class ModelSettings:
|
| 10 |
"""Settings for loading models, VAEs, etc."""
|
| 11 |
+
model_type: Literal["SD 1.5", "SDXL", "Pony", "Custom"] = field(default="SDXL", metadata={"component": "dropdown", "label": "Base Model"})
|
| 12 |
+
custom_model_path: str = field(default="/path/to/default.safetensors", metadata={"label": "Custom Model Path", "interactive_if": {"field": "model_type", "value": "Custom"}})
|
| 13 |
+
vae_path: str = field(default="", metadata={"label": "VAE Path (optional)"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
@dataclass
|
| 16 |
class SamplingSettings:
|
| 17 |
"""Settings for the image sampling process."""
|
| 18 |
+
sampler_name: Literal["Euler", "Euler a", "DPM++ 2M Karras", "UniPC"] = field(default="DPM++ 2M Karras", metadata={"component": "dropdown", "label": "Sampler", "help": "The algorithm for the diffusion process."})
|
| 19 |
+
steps: int = field(default=25, metadata={"component": "slider", "minimum": 1, "maximum": 150, "step": 1, "label": "Sampling Steps", "help": "More steps can improve quality."})
|
| 20 |
+
cfg_scale: float = field(default=7.0, metadata={"component": "slider", "minimum": 1.0, "maximum": 30.0, "step": 0.5, "label": "CFG Scale", "help": "How strongly the prompt is adhered to."})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
@dataclass
|
| 23 |
class ImageSettings:
|
| 24 |
"""Settings for image dimensions."""
|
| 25 |
+
width: int = field(default=1024, metadata={"component": "slider", "minimum": 512, "maximum": 2048, "step": 64, "label": "Image Width"})
|
| 26 |
+
height: int = field(default=1024, metadata={"component": "slider", "minimum": 512, "maximum": 2048, "step": 64, "label": "Image Height"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
@dataclass
|
| 29 |
class PostprocessingSettings:
|
| 30 |
"""Settings for image post-processing effects."""
|
| 31 |
+
restore_faces: bool = field(default=True, metadata={"label": "Restore Faces", "help": "Use a secondary model to fix distorted faces."})
|
| 32 |
+
enable_hr: bool = field(default=False, metadata={"label": "Hires. fix", "help": "Enable a second pass at a higher resolution."})
|
| 33 |
+
denoising_strength: float = field(default=0.45, metadata={"component": "slider", "minimum": 0.0, "maximum": 1.0, "step": 0.01, "label": "Denoising Strength", "interactive_if": {"field": "enable_hr", "value": True}})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
@dataclass
|
| 36 |
class AdvancedSettings:
|
| 37 |
"""Advanced and rarely changed settings."""
|
| 38 |
+
clip_skip: int = field(default=2, metadata={"component": "slider", "minimum": 1, "maximum": 12, "step": 1, "label": "CLIP Skip", "help": "Skip final layers of the text encoder."})
|
| 39 |
+
noise_schedule: Literal["Default", "Karras", "Exponential"] = field(default="Karras", metadata={"component": "dropdown", "label": "Noise Schedule"})
|
| 40 |
+
do_not_scale_cond_uncond: bool = field(default=False, metadata={"label": "Do not scale cond/uncond"})
|
| 41 |
+
s_churn: int = field(default=1, metadata={"component": "number_integer", "minimum": 1, "maximum": 12, "label": "S_churn", "help": "S_churn value for generation."})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
@dataclass
|
| 44 |
class ScriptSettings:
|
| 45 |
"""Settings for automation scripts like X/Y/Z plots."""
|
| 46 |
+
script_name: Literal["None", "Prompt matrix", "X/Y/Z plot"] = field(default="None", metadata={"component": "dropdown", "label": "Script"})
|
| 47 |
+
x_values: str = field(default="-1, 10, 20", metadata={"label": "X axis values", "interactive_if": {"field": "script_name", "value": "X/Y/Z plot"}})
|
| 48 |
+
y_values: str = field(default="", metadata={"label": "Y axis values", "interactive_if": {"field": "script_name", "value": "X/Y/Z plot"}})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
@dataclass
|
| 51 |
class RenderConfig:
|
| 52 |
"""Main configuration object for rendering, grouping all settings."""
|
| 53 |
+
seed: int = field(default=-1, metadata={"component": "number_integer", "label": "Seed (-1 for random)", "help": "The random seed for generation."})
|
| 54 |
+
batch_size: int = field(default=1, metadata={"component": "slider", "minimum": 1, "maximum": 8, "step": 1, "label": "Batch Size"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
model: ModelSettings = field(default_factory=ModelSettings)
|
| 56 |
sampling: SamplingSettings = field(default_factory=SamplingSettings)
|
| 57 |
image: ImageSettings = field(default_factory=ImageSettings)
|
|
|
|
| 59 |
scripts: ScriptSettings = field(default_factory=ScriptSettings)
|
| 60 |
advanced: AdvancedSettings = field(default_factory=AdvancedSettings)
|
| 61 |
|
|
|
|
| 62 |
@dataclass
|
| 63 |
class Lighting:
|
| 64 |
"""Lighting settings for the environment."""
|
|
|
|
| 72 |
background: Literal["Sky", "Color", "Image"] = field(default="Sky", metadata={"component": "dropdown"})
|
| 73 |
lighting: Lighting = field(default_factory=Lighting)
|
| 74 |
|
|
|
|
| 75 |
# --- Initial Instances ---
|
| 76 |
+
# Create default instances of the configuration objects.
|
| 77 |
initial_render_config = RenderConfig()
|
| 78 |
initial_env_config = EnvironmentConfig()
|
| 79 |
|
|
|
|
| 83 |
gr.Markdown("An example of a realistic application layout using the `PropertySheet` component as a sidebar for settings.")
|
| 84 |
gr.Markdown("<span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_propertysheet'>Component GitHub Code</a></span>")
|
| 85 |
|
| 86 |
+
# --- Persistent State Management ---
|
| 87 |
+
# Use gr.State to hold the application's data. This is the "single source of truth".
|
| 88 |
+
render_state = gr.State(value=initial_render_config)
|
| 89 |
+
env_state = gr.State(value=initial_env_config)
|
| 90 |
+
|
| 91 |
with gr.Row():
|
| 92 |
+
with gr.Column(scale=3):
|
| 93 |
+
generate = gr.Button("Show Settings", variant="primary")
|
|
|
|
|
|
|
|
|
|
| 94 |
with gr.Row():
|
| 95 |
output_render_json = gr.JSON(label="Live Render State")
|
| 96 |
output_env_json = gr.JSON(label="Live Environment State")
|
| 97 |
|
|
|
|
| 98 |
with gr.Column(scale=1):
|
| 99 |
render_sheet = PropertySheet(
|
| 100 |
+
value=initial_render_config,
|
| 101 |
label="Render Settings",
|
| 102 |
width=400,
|
| 103 |
+
height=550,
|
| 104 |
+
visible=False
|
| 105 |
)
|
| 106 |
environment_sheet = PropertySheet(
|
| 107 |
value=initial_env_config,
|
| 108 |
label="Environment Settings",
|
| 109 |
width=400,
|
| 110 |
+
open=False,
|
| 111 |
+
visible=False
|
| 112 |
)
|
| 113 |
|
| 114 |
# --- Event Handlers ---
|
| 115 |
+
def change_visibility(render_config, env_config):
|
| 116 |
+
"""
|
| 117 |
+
Handles the visibility toggle for the property sheets.
|
| 118 |
+
NOTE: This approach of modifying the component object's attribute directly
|
| 119 |
+
is not reliable for tracking state changes in Gradio. A gr.State object is
|
| 120 |
+
the recommended way to manage UI state like visibility.
|
| 121 |
+
"""
|
| 122 |
+
if render_sheet.visible != environment_sheet.visible:
|
| 123 |
+
render_sheet.visible = False
|
| 124 |
+
environment_sheet.visible = False
|
| 125 |
+
|
| 126 |
+
if render_sheet.visible == False and environment_sheet.visible == False:
|
| 127 |
+
render_sheet.visible = True
|
| 128 |
+
environment_sheet.visible = True
|
| 129 |
+
return (
|
| 130 |
+
gr.update(visible=True, value=render_config),
|
| 131 |
+
gr.update(visible=True, value=env_config),
|
| 132 |
+
gr.update(value="Hide Settings")
|
| 133 |
+
)
|
| 134 |
+
else:
|
| 135 |
+
render_sheet.visible = False
|
| 136 |
+
environment_sheet.visible = False
|
| 137 |
+
return (
|
| 138 |
+
gr.update(visible=False, value=render_config),
|
| 139 |
+
gr.update(visible=False, value=env_config),
|
| 140 |
+
gr.update(value="Show Settings")
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
def handle_render_change(updated_config: RenderConfig | None, current_state: RenderConfig):
|
| 144 |
+
"""Processes updates from the render PropertySheet and syncs the state."""
|
| 145 |
if updated_config is None:
|
| 146 |
+
return current_state, asdict(current_state), current_state
|
| 147 |
|
| 148 |
+
# Example of applying business logic
|
| 149 |
if updated_config.model.model_type != "Custom":
|
| 150 |
updated_config.model.custom_model_path = "/path/to/default.safetensors"
|
| 151 |
+
|
| 152 |
+
return updated_config, asdict(updated_config), updated_config
|
| 153 |
|
| 154 |
+
def handle_env_change(updated_config: EnvironmentConfig | None, current_state: EnvironmentConfig):
|
| 155 |
+
"""Processes updates from the environment PropertySheet and syncs the state."""
|
| 156 |
if updated_config is None:
|
| 157 |
+
return current_state, asdict(current_state), current_state
|
| 158 |
+
return updated_config, asdict(updated_config), updated_config
|
| 159 |
|
| 160 |
+
# --- Event Listeners ---
|
| 161 |
+
# Toggle the property sheets' visibility on button click.
|
| 162 |
+
generate.click(
|
| 163 |
+
fn=change_visibility,
|
| 164 |
+
inputs=[render_state, env_state],
|
| 165 |
+
outputs=[render_sheet, environment_sheet, generate]
|
| 166 |
+
)
|
| 167 |
+
|
| 168 |
+
# Syncs changes from the UI back to the state and JSON display.
|
| 169 |
render_sheet.change(
|
| 170 |
fn=handle_render_change,
|
| 171 |
+
inputs=[render_sheet, render_state],
|
| 172 |
+
outputs=[render_sheet, output_render_json, render_state]
|
| 173 |
)
|
| 174 |
+
|
| 175 |
+
# Syncs changes from the UI back to the state and JSON display.
|
| 176 |
environment_sheet.change(
|
| 177 |
fn=handle_env_change,
|
| 178 |
+
inputs=[environment_sheet, env_state],
|
| 179 |
+
outputs=[environment_sheet, output_env_json, env_state]
|
| 180 |
)
|
| 181 |
|
| 182 |
+
# Load initial data into JSON displays on app start.
|
| 183 |
demo.load(
|
| 184 |
fn=lambda: (asdict(initial_render_config), asdict(initial_env_config)),
|
| 185 |
outputs=[output_render_json, output_env_json]
|
| 186 |
)
|
| 187 |
|
| 188 |
+
# Ensure components are populated with state values on load/reload.
|
| 189 |
+
demo.load(
|
| 190 |
+
fn=lambda render_config, env_config: (
|
| 191 |
+
gr.update(value=render_config),
|
| 192 |
+
gr.update(value=env_config)
|
| 193 |
+
),
|
| 194 |
+
inputs=[render_state, env_state],
|
| 195 |
+
outputs=[render_sheet, environment_sheet]
|
| 196 |
+
)
|
| 197 |
+
|
| 198 |
if __name__ == "__main__":
|
| 199 |
demo.launch()
|
space.py
CHANGED
|
@@ -3,7 +3,7 @@ import gradio as gr
|
|
| 3 |
from app import demo as app
|
| 4 |
import os
|
| 5 |
|
| 6 |
-
_docs = {'PropertySheet': {'description': 'A Gradio component that renders a dynamic UI from a Python dataclass instance.', 'members': {'__init__': {'value': {'type': 'typing.Optional[typing.Any][Any, None]', 'default': 'None', 'description': 'The initial dataclass instance to render.'}, 'label': {'type': 'str | None', 'default': 'None', 'description': 'The main label for the component, displayed in the accordion header.'}, 'visible': {'type': 'bool', 'default': 'True', 'description': 'If False, the component will be hidden.'}, 'open': {'type': 'bool', 'default': 'True', 'description': 'If False, the accordion will be collapsed by default.'}, 'elem_id': {'type': 'str | None', 'default': 'None', 'description': 'An optional string that is assigned as the id of this component in the DOM.'}, 'scale': {'type': 'int | None', 'default': 'None', 'description': 'The relative size of the component in its container.'}, 'width': {'type': 'int | str | None', 'default': 'None', 'description': 'The width of the component in pixels.'}, 'height': {'type': 'int | str | None', 'default': 'None', 'description': "The maximum height of the component's content area in pixels before scrolling."}, 'min_width': {'type': 'int | None', 'default': 'None', 'description': 'The minimum width of the component in pixels.'}, 'container': {'type': 'bool', 'default': 'True', 'description': 'If True, wraps the component in a container with a background.'}, 'elem_classes': {'type': 'list[str] | str | None', 'default': 'None', 'description': 'An optional list of strings that are assigned as the classes of this component in the DOM.'}}, 'postprocess': {'value': {'type': 'Any', 'description':
|
| 7 |
|
| 8 |
abs_path = os.path.join(os.path.dirname(__file__), "css.css")
|
| 9 |
|
|
@@ -21,7 +21,7 @@ with gr.Blocks(
|
|
| 21 |
# `gradio_propertysheet`
|
| 22 |
|
| 23 |
<div style="display: flex; gap: 7px;">
|
| 24 |
-
<img alt="
|
| 25 |
</div>
|
| 26 |
|
| 27 |
Property sheet
|
|
@@ -43,115 +43,55 @@ from dataclasses import dataclass, field, asdict
|
|
| 43 |
from typing import Literal
|
| 44 |
from gradio_propertysheet import PropertySheet
|
| 45 |
|
| 46 |
-
# ---
|
|
|
|
| 47 |
@dataclass
|
| 48 |
class ModelSettings:
|
| 49 |
\"\"\"Settings for loading models, VAEs, etc.\"\"\"
|
| 50 |
-
model_type: Literal["SD 1.5", "SDXL", "Pony", "Custom"] = field(
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
)
|
| 54 |
-
custom_model_path: str = field(
|
| 55 |
-
default="/path/to/default.safetensors",
|
| 56 |
-
metadata={"label": "Custom Model Path", "interactive_if": {"field": "model_type", "value": "Custom"}}
|
| 57 |
-
)
|
| 58 |
-
vae_path: str = field(
|
| 59 |
-
default="",
|
| 60 |
-
metadata={"label": "VAE Path (optional)"}
|
| 61 |
-
)
|
| 62 |
|
| 63 |
@dataclass
|
| 64 |
class SamplingSettings:
|
| 65 |
\"\"\"Settings for the image sampling process.\"\"\"
|
| 66 |
-
sampler_name: Literal["Euler", "Euler a", "DPM++ 2M Karras", "UniPC"] = field(
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
)
|
| 70 |
-
steps: int = field(
|
| 71 |
-
default=25,
|
| 72 |
-
metadata={"component": "slider", "minimum": 1, "maximum": 150, "step": 1, "label": "Sampling Steps", "help": "More steps can improve quality."}
|
| 73 |
-
)
|
| 74 |
-
cfg_scale: float = field(
|
| 75 |
-
default=7.0,
|
| 76 |
-
metadata={"component": "slider", "minimum": 1.0, "maximum": 30.0, "step": 0.5, "label": "CFG Scale", "help": "How strongly the prompt is adhered to."}
|
| 77 |
-
)
|
| 78 |
|
| 79 |
@dataclass
|
| 80 |
class ImageSettings:
|
| 81 |
\"\"\"Settings for image dimensions.\"\"\"
|
| 82 |
-
width: int = field(
|
| 83 |
-
|
| 84 |
-
metadata={"component": "slider", "minimum": 512, "maximum": 2048, "step": 64, "label": "Image Width"}
|
| 85 |
-
)
|
| 86 |
-
height: int = field(
|
| 87 |
-
default=1024,
|
| 88 |
-
metadata={"component": "slider", "minimum": 512, "maximum": 2048, "step": 64, "label": "Image Height"}
|
| 89 |
-
)
|
| 90 |
|
| 91 |
@dataclass
|
| 92 |
class PostprocessingSettings:
|
| 93 |
\"\"\"Settings for image post-processing effects.\"\"\"
|
| 94 |
-
restore_faces: bool = field(
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
)
|
| 98 |
-
enable_hr: bool = field(
|
| 99 |
-
default=False,
|
| 100 |
-
metadata={"label": "Hires. fix", "help": "Enable a second pass at a higher resolution."}
|
| 101 |
-
)
|
| 102 |
-
denoising_strength: float = field(
|
| 103 |
-
default=0.45,
|
| 104 |
-
metadata={"component": "slider", "minimum": 0.0, "maximum": 1.0, "step": 0.01, "label": "Denoising Strength", "interactive_if": {"field": "enable_hr", "value": True}}
|
| 105 |
-
)
|
| 106 |
|
| 107 |
@dataclass
|
| 108 |
class AdvancedSettings:
|
| 109 |
\"\"\"Advanced and rarely changed settings.\"\"\"
|
| 110 |
-
clip_skip: int = field(
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
)
|
| 114 |
-
noise_schedule: Literal["Default", "Karras", "Exponential"] = field(
|
| 115 |
-
default="Karras",
|
| 116 |
-
metadata={"component": "dropdown", "label": "Noise Schedule"}
|
| 117 |
-
)
|
| 118 |
-
do_not_scale_cond_uncond: bool = field(
|
| 119 |
-
default=False,
|
| 120 |
-
metadata={"label": "Do not scale cond/uncond"}
|
| 121 |
-
)
|
| 122 |
-
s_churn: int = field(
|
| 123 |
-
default=1,
|
| 124 |
-
metadata={"component": "number_integer", "minimum": 1, "maximum": 12, "label": "S_churn", "help": "S_churn value for generation."}
|
| 125 |
-
)
|
| 126 |
|
| 127 |
@dataclass
|
| 128 |
class ScriptSettings:
|
| 129 |
\"\"\"Settings for automation scripts like X/Y/Z plots.\"\"\"
|
| 130 |
-
script_name: Literal["None", "Prompt matrix", "X/Y/Z plot"] = field(
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
)
|
| 134 |
-
x_values: str = field(
|
| 135 |
-
default="-1, 10, 20",
|
| 136 |
-
metadata={"label": "X axis values", "interactive_if": {"field": "script_name", "value": "X/Y/Z plot"}}
|
| 137 |
-
)
|
| 138 |
-
y_values: str = field(
|
| 139 |
-
default="",
|
| 140 |
-
metadata={"label": "Y axis values", "interactive_if": {"field": "script_name", "value": "X/Y/Z plot"}}
|
| 141 |
-
)
|
| 142 |
|
| 143 |
@dataclass
|
| 144 |
class RenderConfig:
|
| 145 |
\"\"\"Main configuration object for rendering, grouping all settings.\"\"\"
|
| 146 |
-
seed: int = field(
|
| 147 |
-
|
| 148 |
-
metadata={"component": "number_integer", "label": "Seed (-1 for random)", "help": "The random seed for generation."}
|
| 149 |
-
)
|
| 150 |
-
batch_size: int = field(
|
| 151 |
-
default=1,
|
| 152 |
-
metadata={"component": "slider", "minimum": 1, "maximum": 8, "step": 1, "label": "Batch Size"}
|
| 153 |
-
)
|
| 154 |
-
# Nested groups
|
| 155 |
model: ModelSettings = field(default_factory=ModelSettings)
|
| 156 |
sampling: SamplingSettings = field(default_factory=SamplingSettings)
|
| 157 |
image: ImageSettings = field(default_factory=ImageSettings)
|
|
@@ -159,7 +99,6 @@ class RenderConfig:
|
|
| 159 |
scripts: ScriptSettings = field(default_factory=ScriptSettings)
|
| 160 |
advanced: AdvancedSettings = field(default_factory=AdvancedSettings)
|
| 161 |
|
| 162 |
-
|
| 163 |
@dataclass
|
| 164 |
class Lighting:
|
| 165 |
\"\"\"Lighting settings for the environment.\"\"\"
|
|
@@ -173,8 +112,8 @@ class EnvironmentConfig:
|
|
| 173 |
background: Literal["Sky", "Color", "Image"] = field(default="Sky", metadata={"component": "dropdown"})
|
| 174 |
lighting: Lighting = field(default_factory=Lighting)
|
| 175 |
|
| 176 |
-
|
| 177 |
# --- Initial Instances ---
|
|
|
|
| 178 |
initial_render_config = RenderConfig()
|
| 179 |
initial_env_config = EnvironmentConfig()
|
| 180 |
|
|
@@ -184,66 +123,118 @@ with gr.Blocks(title="PropertySheet Demo") as demo:
|
|
| 184 |
gr.Markdown("An example of a realistic application layout using the `PropertySheet` component as a sidebar for settings.")
|
| 185 |
gr.Markdown("<span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_propertysheet'>Component GitHub Code</a></span>")
|
| 186 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
with gr.Row():
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
#gr.Image(label="Main Viewport", height=500, value=None)
|
| 191 |
-
gr.Textbox(label="AI Prompt", lines=3, placeholder="Enter your prompt here...")
|
| 192 |
-
gr.Button("Generate", variant="primary")
|
| 193 |
with gr.Row():
|
| 194 |
output_render_json = gr.JSON(label="Live Render State")
|
| 195 |
output_env_json = gr.JSON(label="Live Environment State")
|
| 196 |
|
| 197 |
-
# Sidebar with Property Sheets on the right
|
| 198 |
with gr.Column(scale=1):
|
| 199 |
render_sheet = PropertySheet(
|
| 200 |
-
value=initial_render_config,
|
| 201 |
label="Render Settings",
|
| 202 |
width=400,
|
| 203 |
-
height=550
|
|
|
|
| 204 |
)
|
| 205 |
environment_sheet = PropertySheet(
|
| 206 |
value=initial_env_config,
|
| 207 |
label="Environment Settings",
|
| 208 |
width=400,
|
| 209 |
-
open=False
|
|
|
|
| 210 |
)
|
| 211 |
|
| 212 |
# --- Event Handlers ---
|
| 213 |
-
def
|
| 214 |
-
\"\"\"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
if updated_config is None:
|
| 216 |
-
return
|
| 217 |
|
| 218 |
-
# Example of business logic
|
| 219 |
if updated_config.model.model_type != "Custom":
|
| 220 |
updated_config.model.custom_model_path = "/path/to/default.safetensors"
|
| 221 |
-
|
| 222 |
-
return updated_config, asdict(updated_config)
|
| 223 |
|
| 224 |
-
def handle_env_change(updated_config: EnvironmentConfig | None):
|
| 225 |
-
\"\"\"
|
| 226 |
if updated_config is None:
|
| 227 |
-
return
|
| 228 |
-
return updated_config, asdict(updated_config)
|
| 229 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
render_sheet.change(
|
| 231 |
fn=handle_render_change,
|
| 232 |
-
inputs=[render_sheet],
|
| 233 |
-
outputs=[render_sheet, output_render_json]
|
| 234 |
)
|
|
|
|
|
|
|
| 235 |
environment_sheet.change(
|
| 236 |
fn=handle_env_change,
|
| 237 |
-
inputs=[environment_sheet],
|
| 238 |
-
outputs=[environment_sheet, output_env_json]
|
| 239 |
)
|
| 240 |
|
| 241 |
-
# Load initial
|
| 242 |
demo.load(
|
| 243 |
fn=lambda: (asdict(initial_render_config), asdict(initial_env_config)),
|
| 244 |
outputs=[output_render_json, output_env_json]
|
| 245 |
)
|
| 246 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
if __name__ == "__main__":
|
| 248 |
demo.launch()
|
| 249 |
```
|
|
@@ -276,7 +267,8 @@ The impact on the users predict function varies depending on whether the compone
|
|
| 276 |
|
| 277 |
The code snippet below is accurate in cases where the component is used as both an input and an output.
|
| 278 |
|
| 279 |
-
|
|
|
|
| 280 |
|
| 281 |
```python
|
| 282 |
def predict(
|
|
|
|
| 3 |
from app import demo as app
|
| 4 |
import os
|
| 5 |
|
| 6 |
+
_docs = {'PropertySheet': {'description': 'A Gradio component that renders a dynamic UI from a Python dataclass instance.\nIt allows for nested settings and automatically infers input types.', 'members': {'__init__': {'value': {'type': 'typing.Optional[typing.Any][Any, None]', 'default': 'None', 'description': 'The initial dataclass instance to render.'}, 'label': {'type': 'str | None', 'default': 'None', 'description': 'The main label for the component, displayed in the accordion header.'}, 'visible': {'type': 'bool', 'default': 'True', 'description': 'If False, the component will be hidden.'}, 'open': {'type': 'bool', 'default': 'True', 'description': 'If False, the accordion will be collapsed by default.'}, 'elem_id': {'type': 'str | None', 'default': 'None', 'description': 'An optional string that is assigned as the id of this component in the DOM.'}, 'scale': {'type': 'int | None', 'default': 'None', 'description': 'The relative size of the component in its container.'}, 'width': {'type': 'int | str | None', 'default': 'None', 'description': 'The width of the component in pixels.'}, 'height': {'type': 'int | str | None', 'default': 'None', 'description': "The maximum height of the component's content area in pixels before scrolling."}, 'min_width': {'type': 'int | None', 'default': 'None', 'description': 'The minimum width of the component in pixels.'}, 'container': {'type': 'bool', 'default': 'True', 'description': 'If True, wraps the component in a container with a background.'}, 'elem_classes': {'type': 'list[str] | str | None', 'default': 'None', 'description': 'An optional list of strings that are assigned as the classes of this component in the DOM.'}}, 'postprocess': {'value': {'type': 'Any', 'description': 'The dataclass instance to process.'}}, 'preprocess': {'return': {'type': 'Any', 'description': 'A new, updated instance of the dataclass.'}, 'value': None}}, 'events': {'change': {'type': None, 'default': None, 'description': ''}, 'input': {'type': None, 'default': None, 'description': ''}, 'expand': {'type': None, 'default': None, 'description': ''}, 'collapse': {'type': None, 'default': None, 'description': ''}}}, '__meta__': {'additional_interfaces': {}, 'user_fn_refs': {'PropertySheet': []}}}
|
| 7 |
|
| 8 |
abs_path = os.path.join(os.path.dirname(__file__), "css.css")
|
| 9 |
|
|
|
|
| 21 |
# `gradio_propertysheet`
|
| 22 |
|
| 23 |
<div style="display: flex; gap: 7px;">
|
| 24 |
+
<a href="https://pypi.org/project/gradio_propertysheet/" target="_blank"><img alt="PyPI - Version" src="https://img.shields.io/pypi/v/gradio_propertysheet"></a>
|
| 25 |
</div>
|
| 26 |
|
| 27 |
Property sheet
|
|
|
|
| 43 |
from typing import Literal
|
| 44 |
from gradio_propertysheet import PropertySheet
|
| 45 |
|
| 46 |
+
# --- Configuration Data Models ---
|
| 47 |
+
# These dataclasses define the structure for all settings panels.
|
| 48 |
@dataclass
|
| 49 |
class ModelSettings:
|
| 50 |
\"\"\"Settings for loading models, VAEs, etc.\"\"\"
|
| 51 |
+
model_type: Literal["SD 1.5", "SDXL", "Pony", "Custom"] = field(default="SDXL", metadata={"component": "dropdown", "label": "Base Model"})
|
| 52 |
+
custom_model_path: str = field(default="/path/to/default.safetensors", metadata={"label": "Custom Model Path", "interactive_if": {"field": "model_type", "value": "Custom"}})
|
| 53 |
+
vae_path: str = field(default="", metadata={"label": "VAE Path (optional)"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
@dataclass
|
| 56 |
class SamplingSettings:
|
| 57 |
\"\"\"Settings for the image sampling process.\"\"\"
|
| 58 |
+
sampler_name: Literal["Euler", "Euler a", "DPM++ 2M Karras", "UniPC"] = field(default="DPM++ 2M Karras", metadata={"component": "dropdown", "label": "Sampler", "help": "The algorithm for the diffusion process."})
|
| 59 |
+
steps: int = field(default=25, metadata={"component": "slider", "minimum": 1, "maximum": 150, "step": 1, "label": "Sampling Steps", "help": "More steps can improve quality."})
|
| 60 |
+
cfg_scale: float = field(default=7.0, metadata={"component": "slider", "minimum": 1.0, "maximum": 30.0, "step": 0.5, "label": "CFG Scale", "help": "How strongly the prompt is adhered to."})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
@dataclass
|
| 63 |
class ImageSettings:
|
| 64 |
\"\"\"Settings for image dimensions.\"\"\"
|
| 65 |
+
width: int = field(default=1024, metadata={"component": "slider", "minimum": 512, "maximum": 2048, "step": 64, "label": "Image Width"})
|
| 66 |
+
height: int = field(default=1024, metadata={"component": "slider", "minimum": 512, "maximum": 2048, "step": 64, "label": "Image Height"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
@dataclass
|
| 69 |
class PostprocessingSettings:
|
| 70 |
\"\"\"Settings for image post-processing effects.\"\"\"
|
| 71 |
+
restore_faces: bool = field(default=True, metadata={"label": "Restore Faces", "help": "Use a secondary model to fix distorted faces."})
|
| 72 |
+
enable_hr: bool = field(default=False, metadata={"label": "Hires. fix", "help": "Enable a second pass at a higher resolution."})
|
| 73 |
+
denoising_strength: float = field(default=0.45, metadata={"component": "slider", "minimum": 0.0, "maximum": 1.0, "step": 0.01, "label": "Denoising Strength", "interactive_if": {"field": "enable_hr", "value": True}})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
@dataclass
|
| 76 |
class AdvancedSettings:
|
| 77 |
\"\"\"Advanced and rarely changed settings.\"\"\"
|
| 78 |
+
clip_skip: int = field(default=2, metadata={"component": "slider", "minimum": 1, "maximum": 12, "step": 1, "label": "CLIP Skip", "help": "Skip final layers of the text encoder."})
|
| 79 |
+
noise_schedule: Literal["Default", "Karras", "Exponential"] = field(default="Karras", metadata={"component": "dropdown", "label": "Noise Schedule"})
|
| 80 |
+
do_not_scale_cond_uncond: bool = field(default=False, metadata={"label": "Do not scale cond/uncond"})
|
| 81 |
+
s_churn: int = field(default=1, metadata={"component": "number_integer", "minimum": 1, "maximum": 12, "label": "S_churn", "help": "S_churn value for generation."})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
|
| 83 |
@dataclass
|
| 84 |
class ScriptSettings:
|
| 85 |
\"\"\"Settings for automation scripts like X/Y/Z plots.\"\"\"
|
| 86 |
+
script_name: Literal["None", "Prompt matrix", "X/Y/Z plot"] = field(default="None", metadata={"component": "dropdown", "label": "Script"})
|
| 87 |
+
x_values: str = field(default="-1, 10, 20", metadata={"label": "X axis values", "interactive_if": {"field": "script_name", "value": "X/Y/Z plot"}})
|
| 88 |
+
y_values: str = field(default="", metadata={"label": "Y axis values", "interactive_if": {"field": "script_name", "value": "X/Y/Z plot"}})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
@dataclass
|
| 91 |
class RenderConfig:
|
| 92 |
\"\"\"Main configuration object for rendering, grouping all settings.\"\"\"
|
| 93 |
+
seed: int = field(default=-1, metadata={"component": "number_integer", "label": "Seed (-1 for random)", "help": "The random seed for generation."})
|
| 94 |
+
batch_size: int = field(default=1, metadata={"component": "slider", "minimum": 1, "maximum": 8, "step": 1, "label": "Batch Size"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
model: ModelSettings = field(default_factory=ModelSettings)
|
| 96 |
sampling: SamplingSettings = field(default_factory=SamplingSettings)
|
| 97 |
image: ImageSettings = field(default_factory=ImageSettings)
|
|
|
|
| 99 |
scripts: ScriptSettings = field(default_factory=ScriptSettings)
|
| 100 |
advanced: AdvancedSettings = field(default_factory=AdvancedSettings)
|
| 101 |
|
|
|
|
| 102 |
@dataclass
|
| 103 |
class Lighting:
|
| 104 |
\"\"\"Lighting settings for the environment.\"\"\"
|
|
|
|
| 112 |
background: Literal["Sky", "Color", "Image"] = field(default="Sky", metadata={"component": "dropdown"})
|
| 113 |
lighting: Lighting = field(default_factory=Lighting)
|
| 114 |
|
|
|
|
| 115 |
# --- Initial Instances ---
|
| 116 |
+
# Create default instances of the configuration objects.
|
| 117 |
initial_render_config = RenderConfig()
|
| 118 |
initial_env_config = EnvironmentConfig()
|
| 119 |
|
|
|
|
| 123 |
gr.Markdown("An example of a realistic application layout using the `PropertySheet` component as a sidebar for settings.")
|
| 124 |
gr.Markdown("<span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_propertysheet'>Component GitHub Code</a></span>")
|
| 125 |
|
| 126 |
+
# --- Persistent State Management ---
|
| 127 |
+
# Use gr.State to hold the application's data. This is the "single source of truth".
|
| 128 |
+
render_state = gr.State(value=initial_render_config)
|
| 129 |
+
env_state = gr.State(value=initial_env_config)
|
| 130 |
+
|
| 131 |
with gr.Row():
|
| 132 |
+
with gr.Column(scale=3):
|
| 133 |
+
generate = gr.Button("Show Settings", variant="primary")
|
|
|
|
|
|
|
|
|
|
| 134 |
with gr.Row():
|
| 135 |
output_render_json = gr.JSON(label="Live Render State")
|
| 136 |
output_env_json = gr.JSON(label="Live Environment State")
|
| 137 |
|
|
|
|
| 138 |
with gr.Column(scale=1):
|
| 139 |
render_sheet = PropertySheet(
|
| 140 |
+
value=initial_render_config,
|
| 141 |
label="Render Settings",
|
| 142 |
width=400,
|
| 143 |
+
height=550,
|
| 144 |
+
visible=False
|
| 145 |
)
|
| 146 |
environment_sheet = PropertySheet(
|
| 147 |
value=initial_env_config,
|
| 148 |
label="Environment Settings",
|
| 149 |
width=400,
|
| 150 |
+
open=False,
|
| 151 |
+
visible=False
|
| 152 |
)
|
| 153 |
|
| 154 |
# --- Event Handlers ---
|
| 155 |
+
def change_visibility(render_config, env_config):
|
| 156 |
+
\"\"\"
|
| 157 |
+
Handles the visibility toggle for the property sheets.
|
| 158 |
+
NOTE: This approach of modifying the component object's attribute directly
|
| 159 |
+
is not reliable for tracking state changes in Gradio. A gr.State object is
|
| 160 |
+
the recommended way to manage UI state like visibility.
|
| 161 |
+
\"\"\"
|
| 162 |
+
if render_sheet.visible != environment_sheet.visible:
|
| 163 |
+
render_sheet.visible = False
|
| 164 |
+
environment_sheet.visible = False
|
| 165 |
+
|
| 166 |
+
if render_sheet.visible == False and environment_sheet.visible == False:
|
| 167 |
+
render_sheet.visible = True
|
| 168 |
+
environment_sheet.visible = True
|
| 169 |
+
return (
|
| 170 |
+
gr.update(visible=True, value=render_config),
|
| 171 |
+
gr.update(visible=True, value=env_config),
|
| 172 |
+
gr.update(value="Hide Settings")
|
| 173 |
+
)
|
| 174 |
+
else:
|
| 175 |
+
render_sheet.visible = False
|
| 176 |
+
environment_sheet.visible = False
|
| 177 |
+
return (
|
| 178 |
+
gr.update(visible=False, value=render_config),
|
| 179 |
+
gr.update(visible=False, value=env_config),
|
| 180 |
+
gr.update(value="Show Settings")
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
def handle_render_change(updated_config: RenderConfig | None, current_state: RenderConfig):
|
| 184 |
+
\"\"\"Processes updates from the render PropertySheet and syncs the state.\"\"\"
|
| 185 |
if updated_config is None:
|
| 186 |
+
return current_state, asdict(current_state), current_state
|
| 187 |
|
| 188 |
+
# Example of applying business logic
|
| 189 |
if updated_config.model.model_type != "Custom":
|
| 190 |
updated_config.model.custom_model_path = "/path/to/default.safetensors"
|
| 191 |
+
|
| 192 |
+
return updated_config, asdict(updated_config), updated_config
|
| 193 |
|
| 194 |
+
def handle_env_change(updated_config: EnvironmentConfig | None, current_state: EnvironmentConfig):
|
| 195 |
+
\"\"\"Processes updates from the environment PropertySheet and syncs the state.\"\"\"
|
| 196 |
if updated_config is None:
|
| 197 |
+
return current_state, asdict(current_state), current_state
|
| 198 |
+
return updated_config, asdict(updated_config), updated_config
|
| 199 |
+
|
| 200 |
+
# --- Event Listeners ---
|
| 201 |
+
# Toggle the property sheets' visibility on button click.
|
| 202 |
+
generate.click(
|
| 203 |
+
fn=change_visibility,
|
| 204 |
+
inputs=[render_state, env_state],
|
| 205 |
+
outputs=[render_sheet, environment_sheet, generate]
|
| 206 |
+
)
|
| 207 |
+
|
| 208 |
+
# Syncs changes from the UI back to the state and JSON display.
|
| 209 |
render_sheet.change(
|
| 210 |
fn=handle_render_change,
|
| 211 |
+
inputs=[render_sheet, render_state],
|
| 212 |
+
outputs=[render_sheet, output_render_json, render_state]
|
| 213 |
)
|
| 214 |
+
|
| 215 |
+
# Syncs changes from the UI back to the state and JSON display.
|
| 216 |
environment_sheet.change(
|
| 217 |
fn=handle_env_change,
|
| 218 |
+
inputs=[environment_sheet, env_state],
|
| 219 |
+
outputs=[environment_sheet, output_env_json, env_state]
|
| 220 |
)
|
| 221 |
|
| 222 |
+
# Load initial data into JSON displays on app start.
|
| 223 |
demo.load(
|
| 224 |
fn=lambda: (asdict(initial_render_config), asdict(initial_env_config)),
|
| 225 |
outputs=[output_render_json, output_env_json]
|
| 226 |
)
|
| 227 |
|
| 228 |
+
# Ensure components are populated with state values on load/reload.
|
| 229 |
+
demo.load(
|
| 230 |
+
fn=lambda render_config, env_config: (
|
| 231 |
+
gr.update(value=render_config),
|
| 232 |
+
gr.update(value=env_config)
|
| 233 |
+
),
|
| 234 |
+
inputs=[render_state, env_state],
|
| 235 |
+
outputs=[render_sheet, environment_sheet]
|
| 236 |
+
)
|
| 237 |
+
|
| 238 |
if __name__ == "__main__":
|
| 239 |
demo.launch()
|
| 240 |
```
|
|
|
|
| 267 |
|
| 268 |
The code snippet below is accurate in cases where the component is used as both an input and an output.
|
| 269 |
|
| 270 |
+
- **As input:** Is passed, a new, updated instance of the dataclass.
|
| 271 |
+
- **As output:** Should return, the dataclass instance to process.
|
| 272 |
|
| 273 |
```python
|
| 274 |
def predict(
|
src/README.md
CHANGED
|
@@ -10,7 +10,7 @@ app_file: space.py
|
|
| 10 |
---
|
| 11 |
|
| 12 |
# `gradio_propertysheet`
|
| 13 |
-
<img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.
|
| 14 |
|
| 15 |
The **PropertySheet** component for Gradio allows you to automatically generate a complete and interactive settings panel from a standard Python `dataclass`. It's designed to bring the power of IDE-like property editors directly into your Gradio applications.
|
| 16 |
|
|
@@ -50,115 +50,55 @@ from dataclasses import dataclass, field, asdict
|
|
| 50 |
from typing import Literal
|
| 51 |
from gradio_propertysheet import PropertySheet
|
| 52 |
|
| 53 |
-
# ---
|
|
|
|
| 54 |
@dataclass
|
| 55 |
class ModelSettings:
|
| 56 |
"""Settings for loading models, VAEs, etc."""
|
| 57 |
-
model_type: Literal["SD 1.5", "SDXL", "Pony", "Custom"] = field(
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
)
|
| 61 |
-
custom_model_path: str = field(
|
| 62 |
-
default="/path/to/default.safetensors",
|
| 63 |
-
metadata={"label": "Custom Model Path", "interactive_if": {"field": "model_type", "value": "Custom"}}
|
| 64 |
-
)
|
| 65 |
-
vae_path: str = field(
|
| 66 |
-
default="",
|
| 67 |
-
metadata={"label": "VAE Path (optional)"}
|
| 68 |
-
)
|
| 69 |
|
| 70 |
@dataclass
|
| 71 |
class SamplingSettings:
|
| 72 |
"""Settings for the image sampling process."""
|
| 73 |
-
sampler_name: Literal["Euler", "Euler a", "DPM++ 2M Karras", "UniPC"] = field(
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
)
|
| 77 |
-
steps: int = field(
|
| 78 |
-
default=25,
|
| 79 |
-
metadata={"component": "slider", "minimum": 1, "maximum": 150, "step": 1, "label": "Sampling Steps", "help": "More steps can improve quality."}
|
| 80 |
-
)
|
| 81 |
-
cfg_scale: float = field(
|
| 82 |
-
default=7.0,
|
| 83 |
-
metadata={"component": "slider", "minimum": 1.0, "maximum": 30.0, "step": 0.5, "label": "CFG Scale", "help": "How strongly the prompt is adhered to."}
|
| 84 |
-
)
|
| 85 |
|
| 86 |
@dataclass
|
| 87 |
class ImageSettings:
|
| 88 |
"""Settings for image dimensions."""
|
| 89 |
-
width: int = field(
|
| 90 |
-
|
| 91 |
-
metadata={"component": "slider", "minimum": 512, "maximum": 2048, "step": 64, "label": "Image Width"}
|
| 92 |
-
)
|
| 93 |
-
height: int = field(
|
| 94 |
-
default=1024,
|
| 95 |
-
metadata={"component": "slider", "minimum": 512, "maximum": 2048, "step": 64, "label": "Image Height"}
|
| 96 |
-
)
|
| 97 |
|
| 98 |
@dataclass
|
| 99 |
class PostprocessingSettings:
|
| 100 |
"""Settings for image post-processing effects."""
|
| 101 |
-
restore_faces: bool = field(
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
)
|
| 105 |
-
enable_hr: bool = field(
|
| 106 |
-
default=False,
|
| 107 |
-
metadata={"label": "Hires. fix", "help": "Enable a second pass at a higher resolution."}
|
| 108 |
-
)
|
| 109 |
-
denoising_strength: float = field(
|
| 110 |
-
default=0.45,
|
| 111 |
-
metadata={"component": "slider", "minimum": 0.0, "maximum": 1.0, "step": 0.01, "label": "Denoising Strength", "interactive_if": {"field": "enable_hr", "value": True}}
|
| 112 |
-
)
|
| 113 |
|
| 114 |
@dataclass
|
| 115 |
class AdvancedSettings:
|
| 116 |
"""Advanced and rarely changed settings."""
|
| 117 |
-
clip_skip: int = field(
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
)
|
| 121 |
-
noise_schedule: Literal["Default", "Karras", "Exponential"] = field(
|
| 122 |
-
default="Karras",
|
| 123 |
-
metadata={"component": "dropdown", "label": "Noise Schedule"}
|
| 124 |
-
)
|
| 125 |
-
do_not_scale_cond_uncond: bool = field(
|
| 126 |
-
default=False,
|
| 127 |
-
metadata={"label": "Do not scale cond/uncond"}
|
| 128 |
-
)
|
| 129 |
-
s_churn: int = field(
|
| 130 |
-
default=1,
|
| 131 |
-
metadata={"component": "number_integer", "minimum": 1, "maximum": 12, "label": "S_churn", "help": "S_churn value for generation."}
|
| 132 |
-
)
|
| 133 |
|
| 134 |
@dataclass
|
| 135 |
class ScriptSettings:
|
| 136 |
"""Settings for automation scripts like X/Y/Z plots."""
|
| 137 |
-
script_name: Literal["None", "Prompt matrix", "X/Y/Z plot"] = field(
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
)
|
| 141 |
-
x_values: str = field(
|
| 142 |
-
default="-1, 10, 20",
|
| 143 |
-
metadata={"label": "X axis values", "interactive_if": {"field": "script_name", "value": "X/Y/Z plot"}}
|
| 144 |
-
)
|
| 145 |
-
y_values: str = field(
|
| 146 |
-
default="",
|
| 147 |
-
metadata={"label": "Y axis values", "interactive_if": {"field": "script_name", "value": "X/Y/Z plot"}}
|
| 148 |
-
)
|
| 149 |
|
| 150 |
@dataclass
|
| 151 |
class RenderConfig:
|
| 152 |
"""Main configuration object for rendering, grouping all settings."""
|
| 153 |
-
seed: int = field(
|
| 154 |
-
|
| 155 |
-
metadata={"component": "number_integer", "label": "Seed (-1 for random)", "help": "The random seed for generation."}
|
| 156 |
-
)
|
| 157 |
-
batch_size: int = field(
|
| 158 |
-
default=1,
|
| 159 |
-
metadata={"component": "slider", "minimum": 1, "maximum": 8, "step": 1, "label": "Batch Size"}
|
| 160 |
-
)
|
| 161 |
-
# Nested groups
|
| 162 |
model: ModelSettings = field(default_factory=ModelSettings)
|
| 163 |
sampling: SamplingSettings = field(default_factory=SamplingSettings)
|
| 164 |
image: ImageSettings = field(default_factory=ImageSettings)
|
|
@@ -166,7 +106,6 @@ class RenderConfig:
|
|
| 166 |
scripts: ScriptSettings = field(default_factory=ScriptSettings)
|
| 167 |
advanced: AdvancedSettings = field(default_factory=AdvancedSettings)
|
| 168 |
|
| 169 |
-
|
| 170 |
@dataclass
|
| 171 |
class Lighting:
|
| 172 |
"""Lighting settings for the environment."""
|
|
@@ -180,8 +119,8 @@ class EnvironmentConfig:
|
|
| 180 |
background: Literal["Sky", "Color", "Image"] = field(default="Sky", metadata={"component": "dropdown"})
|
| 181 |
lighting: Lighting = field(default_factory=Lighting)
|
| 182 |
|
| 183 |
-
|
| 184 |
# --- Initial Instances ---
|
|
|
|
| 185 |
initial_render_config = RenderConfig()
|
| 186 |
initial_env_config = EnvironmentConfig()
|
| 187 |
|
|
@@ -191,66 +130,118 @@ with gr.Blocks(title="PropertySheet Demo") as demo:
|
|
| 191 |
gr.Markdown("An example of a realistic application layout using the `PropertySheet` component as a sidebar for settings.")
|
| 192 |
gr.Markdown("<span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_propertysheet'>Component GitHub Code</a></span>")
|
| 193 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
with gr.Row():
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
#gr.Image(label="Main Viewport", height=500, value=None)
|
| 198 |
-
gr.Textbox(label="AI Prompt", lines=3, placeholder="Enter your prompt here...")
|
| 199 |
-
gr.Button("Generate", variant="primary")
|
| 200 |
with gr.Row():
|
| 201 |
output_render_json = gr.JSON(label="Live Render State")
|
| 202 |
output_env_json = gr.JSON(label="Live Environment State")
|
| 203 |
|
| 204 |
-
# Sidebar with Property Sheets on the right
|
| 205 |
with gr.Column(scale=1):
|
| 206 |
render_sheet = PropertySheet(
|
| 207 |
-
value=initial_render_config,
|
| 208 |
label="Render Settings",
|
| 209 |
width=400,
|
| 210 |
-
height=550
|
|
|
|
| 211 |
)
|
| 212 |
environment_sheet = PropertySheet(
|
| 213 |
value=initial_env_config,
|
| 214 |
label="Environment Settings",
|
| 215 |
width=400,
|
| 216 |
-
open=False
|
|
|
|
| 217 |
)
|
| 218 |
|
| 219 |
# --- Event Handlers ---
|
| 220 |
-
def
|
| 221 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
if updated_config is None:
|
| 223 |
-
return
|
| 224 |
|
| 225 |
-
# Example of business logic
|
| 226 |
if updated_config.model.model_type != "Custom":
|
| 227 |
updated_config.model.custom_model_path = "/path/to/default.safetensors"
|
| 228 |
-
|
| 229 |
-
return updated_config, asdict(updated_config)
|
| 230 |
|
| 231 |
-
def handle_env_change(updated_config: EnvironmentConfig | None):
|
| 232 |
-
"""
|
| 233 |
if updated_config is None:
|
| 234 |
-
return
|
| 235 |
-
return updated_config, asdict(updated_config)
|
| 236 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
render_sheet.change(
|
| 238 |
fn=handle_render_change,
|
| 239 |
-
inputs=[render_sheet],
|
| 240 |
-
outputs=[render_sheet, output_render_json]
|
| 241 |
)
|
|
|
|
|
|
|
| 242 |
environment_sheet.change(
|
| 243 |
fn=handle_env_change,
|
| 244 |
-
inputs=[environment_sheet],
|
| 245 |
-
outputs=[environment_sheet, output_env_json]
|
| 246 |
)
|
| 247 |
|
| 248 |
-
# Load initial
|
| 249 |
demo.load(
|
| 250 |
fn=lambda: (asdict(initial_render_config), asdict(initial_env_config)),
|
| 251 |
outputs=[output_render_json, output_env_json]
|
| 252 |
)
|
| 253 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
if __name__ == "__main__":
|
| 255 |
demo.launch()
|
| 256 |
```
|
|
@@ -418,10 +409,10 @@ list[str] | str | None
|
|
| 418 |
|
| 419 |
| name | description |
|
| 420 |
|:-----|:------------|
|
| 421 |
-
| `change` |
|
| 422 |
-
| `input` |
|
| 423 |
-
| `expand` |
|
| 424 |
-
| `collapse` |
|
| 425 |
|
| 426 |
|
| 427 |
|
|
@@ -434,7 +425,8 @@ The impact on the users predict function varies depending on whether the compone
|
|
| 434 |
|
| 435 |
The code snippet below is accurate in cases where the component is used as both an input and an output.
|
| 436 |
|
| 437 |
-
|
|
|
|
| 438 |
|
| 439 |
```python
|
| 440 |
def predict(
|
|
@@ -442,4 +434,4 @@ The code snippet below is accurate in cases where the component is used as both
|
|
| 442 |
) -> Any:
|
| 443 |
return value
|
| 444 |
```
|
| 445 |
-
|
|
|
|
| 10 |
---
|
| 11 |
|
| 12 |
# `gradio_propertysheet`
|
| 13 |
+
<img alt="Static Badge" src="https://img.shields.io/badge/version%20-%200.0.2%20-%20blue"> <a href="https://huggingface.co/spaces/elismasilva/gradio_propertysheet"><img src="https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Demo-blue"></a><p><span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_propertysheet'>Component GitHub Code</a></span></p>
|
| 14 |
|
| 15 |
The **PropertySheet** component for Gradio allows you to automatically generate a complete and interactive settings panel from a standard Python `dataclass`. It's designed to bring the power of IDE-like property editors directly into your Gradio applications.
|
| 16 |
|
|
|
|
| 50 |
from typing import Literal
|
| 51 |
from gradio_propertysheet import PropertySheet
|
| 52 |
|
| 53 |
+
# --- Configuration Data Models ---
|
| 54 |
+
# These dataclasses define the structure for all settings panels.
|
| 55 |
@dataclass
|
| 56 |
class ModelSettings:
|
| 57 |
"""Settings for loading models, VAEs, etc."""
|
| 58 |
+
model_type: Literal["SD 1.5", "SDXL", "Pony", "Custom"] = field(default="SDXL", metadata={"component": "dropdown", "label": "Base Model"})
|
| 59 |
+
custom_model_path: str = field(default="/path/to/default.safetensors", metadata={"label": "Custom Model Path", "interactive_if": {"field": "model_type", "value": "Custom"}})
|
| 60 |
+
vae_path: str = field(default="", metadata={"label": "VAE Path (optional)"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
@dataclass
|
| 63 |
class SamplingSettings:
|
| 64 |
"""Settings for the image sampling process."""
|
| 65 |
+
sampler_name: Literal["Euler", "Euler a", "DPM++ 2M Karras", "UniPC"] = field(default="DPM++ 2M Karras", metadata={"component": "dropdown", "label": "Sampler", "help": "The algorithm for the diffusion process."})
|
| 66 |
+
steps: int = field(default=25, metadata={"component": "slider", "minimum": 1, "maximum": 150, "step": 1, "label": "Sampling Steps", "help": "More steps can improve quality."})
|
| 67 |
+
cfg_scale: float = field(default=7.0, metadata={"component": "slider", "minimum": 1.0, "maximum": 30.0, "step": 0.5, "label": "CFG Scale", "help": "How strongly the prompt is adhered to."})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
@dataclass
|
| 70 |
class ImageSettings:
|
| 71 |
"""Settings for image dimensions."""
|
| 72 |
+
width: int = field(default=1024, metadata={"component": "slider", "minimum": 512, "maximum": 2048, "step": 64, "label": "Image Width"})
|
| 73 |
+
height: int = field(default=1024, metadata={"component": "slider", "minimum": 512, "maximum": 2048, "step": 64, "label": "Image Height"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
@dataclass
|
| 76 |
class PostprocessingSettings:
|
| 77 |
"""Settings for image post-processing effects."""
|
| 78 |
+
restore_faces: bool = field(default=True, metadata={"label": "Restore Faces", "help": "Use a secondary model to fix distorted faces."})
|
| 79 |
+
enable_hr: bool = field(default=False, metadata={"label": "Hires. fix", "help": "Enable a second pass at a higher resolution."})
|
| 80 |
+
denoising_strength: float = field(default=0.45, metadata={"component": "slider", "minimum": 0.0, "maximum": 1.0, "step": 0.01, "label": "Denoising Strength", "interactive_if": {"field": "enable_hr", "value": True}})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
@dataclass
|
| 83 |
class AdvancedSettings:
|
| 84 |
"""Advanced and rarely changed settings."""
|
| 85 |
+
clip_skip: int = field(default=2, metadata={"component": "slider", "minimum": 1, "maximum": 12, "step": 1, "label": "CLIP Skip", "help": "Skip final layers of the text encoder."})
|
| 86 |
+
noise_schedule: Literal["Default", "Karras", "Exponential"] = field(default="Karras", metadata={"component": "dropdown", "label": "Noise Schedule"})
|
| 87 |
+
do_not_scale_cond_uncond: bool = field(default=False, metadata={"label": "Do not scale cond/uncond"})
|
| 88 |
+
s_churn: int = field(default=1, metadata={"component": "number_integer", "minimum": 1, "maximum": 12, "label": "S_churn", "help": "S_churn value for generation."})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
@dataclass
|
| 91 |
class ScriptSettings:
|
| 92 |
"""Settings for automation scripts like X/Y/Z plots."""
|
| 93 |
+
script_name: Literal["None", "Prompt matrix", "X/Y/Z plot"] = field(default="None", metadata={"component": "dropdown", "label": "Script"})
|
| 94 |
+
x_values: str = field(default="-1, 10, 20", metadata={"label": "X axis values", "interactive_if": {"field": "script_name", "value": "X/Y/Z plot"}})
|
| 95 |
+
y_values: str = field(default="", metadata={"label": "Y axis values", "interactive_if": {"field": "script_name", "value": "X/Y/Z plot"}})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
@dataclass
|
| 98 |
class RenderConfig:
|
| 99 |
"""Main configuration object for rendering, grouping all settings."""
|
| 100 |
+
seed: int = field(default=-1, metadata={"component": "number_integer", "label": "Seed (-1 for random)", "help": "The random seed for generation."})
|
| 101 |
+
batch_size: int = field(default=1, metadata={"component": "slider", "minimum": 1, "maximum": 8, "step": 1, "label": "Batch Size"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
model: ModelSettings = field(default_factory=ModelSettings)
|
| 103 |
sampling: SamplingSettings = field(default_factory=SamplingSettings)
|
| 104 |
image: ImageSettings = field(default_factory=ImageSettings)
|
|
|
|
| 106 |
scripts: ScriptSettings = field(default_factory=ScriptSettings)
|
| 107 |
advanced: AdvancedSettings = field(default_factory=AdvancedSettings)
|
| 108 |
|
|
|
|
| 109 |
@dataclass
|
| 110 |
class Lighting:
|
| 111 |
"""Lighting settings for the environment."""
|
|
|
|
| 119 |
background: Literal["Sky", "Color", "Image"] = field(default="Sky", metadata={"component": "dropdown"})
|
| 120 |
lighting: Lighting = field(default_factory=Lighting)
|
| 121 |
|
|
|
|
| 122 |
# --- Initial Instances ---
|
| 123 |
+
# Create default instances of the configuration objects.
|
| 124 |
initial_render_config = RenderConfig()
|
| 125 |
initial_env_config = EnvironmentConfig()
|
| 126 |
|
|
|
|
| 130 |
gr.Markdown("An example of a realistic application layout using the `PropertySheet` component as a sidebar for settings.")
|
| 131 |
gr.Markdown("<span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_propertysheet'>Component GitHub Code</a></span>")
|
| 132 |
|
| 133 |
+
# --- Persistent State Management ---
|
| 134 |
+
# Use gr.State to hold the application's data. This is the "single source of truth".
|
| 135 |
+
render_state = gr.State(value=initial_render_config)
|
| 136 |
+
env_state = gr.State(value=initial_env_config)
|
| 137 |
+
|
| 138 |
with gr.Row():
|
| 139 |
+
with gr.Column(scale=3):
|
| 140 |
+
generate = gr.Button("Show Settings", variant="primary")
|
|
|
|
|
|
|
|
|
|
| 141 |
with gr.Row():
|
| 142 |
output_render_json = gr.JSON(label="Live Render State")
|
| 143 |
output_env_json = gr.JSON(label="Live Environment State")
|
| 144 |
|
|
|
|
| 145 |
with gr.Column(scale=1):
|
| 146 |
render_sheet = PropertySheet(
|
| 147 |
+
value=initial_render_config,
|
| 148 |
label="Render Settings",
|
| 149 |
width=400,
|
| 150 |
+
height=550,
|
| 151 |
+
visible=False
|
| 152 |
)
|
| 153 |
environment_sheet = PropertySheet(
|
| 154 |
value=initial_env_config,
|
| 155 |
label="Environment Settings",
|
| 156 |
width=400,
|
| 157 |
+
open=False,
|
| 158 |
+
visible=False
|
| 159 |
)
|
| 160 |
|
| 161 |
# --- Event Handlers ---
|
| 162 |
+
def change_visibility(render_config, env_config):
|
| 163 |
+
"""
|
| 164 |
+
Handles the visibility toggle for the property sheets.
|
| 165 |
+
NOTE: This approach of modifying the component object's attribute directly
|
| 166 |
+
is not reliable for tracking state changes in Gradio. A gr.State object is
|
| 167 |
+
the recommended way to manage UI state like visibility.
|
| 168 |
+
"""
|
| 169 |
+
if render_sheet.visible != environment_sheet.visible:
|
| 170 |
+
render_sheet.visible = False
|
| 171 |
+
environment_sheet.visible = False
|
| 172 |
+
|
| 173 |
+
if render_sheet.visible == False and environment_sheet.visible == False:
|
| 174 |
+
render_sheet.visible = True
|
| 175 |
+
environment_sheet.visible = True
|
| 176 |
+
return (
|
| 177 |
+
gr.update(visible=True, value=render_config),
|
| 178 |
+
gr.update(visible=True, value=env_config),
|
| 179 |
+
gr.update(value="Hide Settings")
|
| 180 |
+
)
|
| 181 |
+
else:
|
| 182 |
+
render_sheet.visible = False
|
| 183 |
+
environment_sheet.visible = False
|
| 184 |
+
return (
|
| 185 |
+
gr.update(visible=False, value=render_config),
|
| 186 |
+
gr.update(visible=False, value=env_config),
|
| 187 |
+
gr.update(value="Show Settings")
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
def handle_render_change(updated_config: RenderConfig | None, current_state: RenderConfig):
|
| 191 |
+
"""Processes updates from the render PropertySheet and syncs the state."""
|
| 192 |
if updated_config is None:
|
| 193 |
+
return current_state, asdict(current_state), current_state
|
| 194 |
|
| 195 |
+
# Example of applying business logic
|
| 196 |
if updated_config.model.model_type != "Custom":
|
| 197 |
updated_config.model.custom_model_path = "/path/to/default.safetensors"
|
| 198 |
+
|
| 199 |
+
return updated_config, asdict(updated_config), updated_config
|
| 200 |
|
| 201 |
+
def handle_env_change(updated_config: EnvironmentConfig | None, current_state: EnvironmentConfig):
|
| 202 |
+
"""Processes updates from the environment PropertySheet and syncs the state."""
|
| 203 |
if updated_config is None:
|
| 204 |
+
return current_state, asdict(current_state), current_state
|
| 205 |
+
return updated_config, asdict(updated_config), updated_config
|
| 206 |
+
|
| 207 |
+
# --- Event Listeners ---
|
| 208 |
+
# Toggle the property sheets' visibility on button click.
|
| 209 |
+
generate.click(
|
| 210 |
+
fn=change_visibility,
|
| 211 |
+
inputs=[render_state, env_state],
|
| 212 |
+
outputs=[render_sheet, environment_sheet, generate]
|
| 213 |
+
)
|
| 214 |
+
|
| 215 |
+
# Syncs changes from the UI back to the state and JSON display.
|
| 216 |
render_sheet.change(
|
| 217 |
fn=handle_render_change,
|
| 218 |
+
inputs=[render_sheet, render_state],
|
| 219 |
+
outputs=[render_sheet, output_render_json, render_state]
|
| 220 |
)
|
| 221 |
+
|
| 222 |
+
# Syncs changes from the UI back to the state and JSON display.
|
| 223 |
environment_sheet.change(
|
| 224 |
fn=handle_env_change,
|
| 225 |
+
inputs=[environment_sheet, env_state],
|
| 226 |
+
outputs=[environment_sheet, output_env_json, env_state]
|
| 227 |
)
|
| 228 |
|
| 229 |
+
# Load initial data into JSON displays on app start.
|
| 230 |
demo.load(
|
| 231 |
fn=lambda: (asdict(initial_render_config), asdict(initial_env_config)),
|
| 232 |
outputs=[output_render_json, output_env_json]
|
| 233 |
)
|
| 234 |
|
| 235 |
+
# Ensure components are populated with state values on load/reload.
|
| 236 |
+
demo.load(
|
| 237 |
+
fn=lambda render_config, env_config: (
|
| 238 |
+
gr.update(value=render_config),
|
| 239 |
+
gr.update(value=env_config)
|
| 240 |
+
),
|
| 241 |
+
inputs=[render_state, env_state],
|
| 242 |
+
outputs=[render_sheet, environment_sheet]
|
| 243 |
+
)
|
| 244 |
+
|
| 245 |
if __name__ == "__main__":
|
| 246 |
demo.launch()
|
| 247 |
```
|
|
|
|
| 409 |
|
| 410 |
| name | description |
|
| 411 |
|:-----|:------------|
|
| 412 |
+
| `change` | |
|
| 413 |
+
| `input` | |
|
| 414 |
+
| `expand` | |
|
| 415 |
+
| `collapse` | |
|
| 416 |
|
| 417 |
|
| 418 |
|
|
|
|
| 425 |
|
| 426 |
The code snippet below is accurate in cases where the component is used as both an input and an output.
|
| 427 |
|
| 428 |
+
- **As output:** Is passed, a new, updated instance of the dataclass.
|
| 429 |
+
- **As input:** Should return, the dataclass instance to process.
|
| 430 |
|
| 431 |
```python
|
| 432 |
def predict(
|
|
|
|
| 434 |
) -> Any:
|
| 435 |
return value
|
| 436 |
```
|
| 437 |
+
|
src/backend/gradio_propertysheet/propertysheet.py
CHANGED
|
@@ -7,14 +7,17 @@ from gradio.components.base import Component
|
|
| 7 |
def prop_meta(**kwargs) -> dataclasses.Field:
|
| 8 |
"""
|
| 9 |
A helper function to create a dataclass field with Gradio-specific metadata.
|
|
|
|
|
|
|
|
|
|
| 10 |
"""
|
| 11 |
return dataclasses.field(metadata=kwargs)
|
| 12 |
|
| 13 |
class PropertySheet(Component):
|
| 14 |
"""
|
| 15 |
A Gradio component that renders a dynamic UI from a Python dataclass instance.
|
|
|
|
| 16 |
"""
|
| 17 |
-
|
| 18 |
EVENTS = ["change", "input", "expand", "collapse"]
|
| 19 |
|
| 20 |
def __init__(
|
|
@@ -52,8 +55,11 @@ class PropertySheet(Component):
|
|
| 52 |
if value is not None and not dataclasses.is_dataclass(value):
|
| 53 |
raise ValueError("Initial value must be a dataclass instance")
|
| 54 |
|
| 55 |
-
|
| 56 |
-
|
|
|
|
|
|
|
|
|
|
| 57 |
self.width = width
|
| 58 |
self.height = height
|
| 59 |
self.open = open
|
|
@@ -67,6 +73,15 @@ class PropertySheet(Component):
|
|
| 67 |
def _extract_prop_metadata(self, obj: Any, field: dataclasses.Field) -> Dict[str, Any]:
|
| 68 |
"""
|
| 69 |
Inspects a dataclass field and extracts metadata for UI rendering.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
"""
|
| 71 |
metadata = field.metadata.copy()
|
| 72 |
metadata["name"] = field.name
|
|
@@ -94,11 +109,23 @@ class PropertySheet(Component):
|
|
| 94 |
def postprocess(self, value: Any) -> List[Dict[str, Any]]:
|
| 95 |
"""
|
| 96 |
Converts the Python dataclass instance into a JSON schema for the frontend.
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
"""
|
| 99 |
if dataclasses.is_dataclass(value):
|
| 100 |
self._dataclass_value = copy.deepcopy(value)
|
| 101 |
-
|
|
|
|
|
|
|
|
|
|
| 102 |
if value is None or not dataclasses.is_dataclass(value):
|
| 103 |
return []
|
| 104 |
|
|
@@ -126,52 +153,67 @@ class PropertySheet(Component):
|
|
| 126 |
|
| 127 |
def preprocess(self, payload: Any) -> Any:
|
| 128 |
"""
|
| 129 |
-
Processes the payload from the frontend to
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
"""
|
| 131 |
if payload is None:
|
| 132 |
-
return
|
| 133 |
|
| 134 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
|
| 136 |
-
#
|
| 137 |
if isinstance(payload, list):
|
| 138 |
for group in payload:
|
| 139 |
for prop in group.get("properties", []):
|
| 140 |
prop_name = prop["name"]
|
| 141 |
new_value = prop["value"]
|
| 142 |
-
if hasattr(
|
| 143 |
-
|
| 144 |
-
if current_value != new_value:
|
| 145 |
-
setattr(updated_config_obj, prop_name, new_value)
|
| 146 |
else:
|
| 147 |
-
|
|
|
|
| 148 |
if dataclasses.is_dataclass(f.type):
|
| 149 |
-
group_obj = getattr(
|
| 150 |
if hasattr(group_obj, prop_name):
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
setattr(group_obj, prop_name, new_value)
|
| 154 |
-
break
|
| 155 |
-
# This logic handles single-value updates from sliders, textboxes, etc.
|
| 156 |
elif isinstance(payload, dict):
|
| 157 |
-
|
| 158 |
-
if hasattr(
|
| 159 |
-
setattr(
|
| 160 |
else:
|
| 161 |
-
|
|
|
|
| 162 |
if dataclasses.is_dataclass(f.type):
|
| 163 |
-
group_obj = getattr(
|
| 164 |
if hasattr(group_obj, key):
|
| 165 |
setattr(group_obj, key, new_value)
|
| 166 |
break
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
return updated_config_obj
|
| 170 |
|
| 171 |
def api_info(self) -> Dict[str, Any]:
|
| 172 |
-
"""
|
|
|
|
|
|
|
| 173 |
return {"type": "object", "description": "A key-value dictionary of property settings."}
|
| 174 |
|
| 175 |
def example_payload(self) -> Any:
|
| 176 |
-
"""
|
|
|
|
|
|
|
| 177 |
return {"seed": 12345}
|
|
|
|
| 7 |
def prop_meta(**kwargs) -> dataclasses.Field:
|
| 8 |
"""
|
| 9 |
A helper function to create a dataclass field with Gradio-specific metadata.
|
| 10 |
+
|
| 11 |
+
Returns:
|
| 12 |
+
A dataclasses.Field instance with the provided metadata.
|
| 13 |
"""
|
| 14 |
return dataclasses.field(metadata=kwargs)
|
| 15 |
|
| 16 |
class PropertySheet(Component):
|
| 17 |
"""
|
| 18 |
A Gradio component that renders a dynamic UI from a Python dataclass instance.
|
| 19 |
+
It allows for nested settings and automatically infers input types.
|
| 20 |
"""
|
|
|
|
| 21 |
EVENTS = ["change", "input", "expand", "collapse"]
|
| 22 |
|
| 23 |
def __init__(
|
|
|
|
| 55 |
if value is not None and not dataclasses.is_dataclass(value):
|
| 56 |
raise ValueError("Initial value must be a dataclass instance")
|
| 57 |
|
| 58 |
+
# Store the current dataclass instance and its type.
|
| 59 |
+
# These might be None if the component is initialized without a value.
|
| 60 |
+
self._dataclass_value = copy.deepcopy(value) if value is not None else None
|
| 61 |
+
self._dataclass_type = type(value) if dataclasses.is_dataclass(value) else None
|
| 62 |
+
|
| 63 |
self.width = width
|
| 64 |
self.height = height
|
| 65 |
self.open = open
|
|
|
|
| 73 |
def _extract_prop_metadata(self, obj: Any, field: dataclasses.Field) -> Dict[str, Any]:
|
| 74 |
"""
|
| 75 |
Inspects a dataclass field and extracts metadata for UI rendering.
|
| 76 |
+
|
| 77 |
+
This function infers the appropriate frontend component (e.g., slider, checkbox)
|
| 78 |
+
based on the field's type hint if not explicitly specified in the metadata.
|
| 79 |
+
|
| 80 |
+
Args:
|
| 81 |
+
obj: The dataclass instance containing the field.
|
| 82 |
+
field: The dataclasses.Field object to inspect.
|
| 83 |
+
Returns:
|
| 84 |
+
A dictionary of metadata for the frontend to render a property control.
|
| 85 |
"""
|
| 86 |
metadata = field.metadata.copy()
|
| 87 |
metadata["name"] = field.name
|
|
|
|
| 109 |
def postprocess(self, value: Any) -> List[Dict[str, Any]]:
|
| 110 |
"""
|
| 111 |
Converts the Python dataclass instance into a JSON schema for the frontend.
|
| 112 |
+
|
| 113 |
+
Crucially, this method also acts as a "state guardian". When Gradio calls it
|
| 114 |
+
with a valid dataclass (e.g., during a `gr.update` that makes the component visible),
|
| 115 |
+
it synchronizes the component's internal state (`_dataclass_value` and `_dataclass_type`),
|
| 116 |
+
ensuring the object is "rehydrated" and ready for `preprocess`.
|
| 117 |
+
|
| 118 |
+
Args:
|
| 119 |
+
value: The dataclass instance to process.
|
| 120 |
+
Returns:
|
| 121 |
+
A list representing the JSON schema for the frontend UI.
|
| 122 |
"""
|
| 123 |
if dataclasses.is_dataclass(value):
|
| 124 |
self._dataclass_value = copy.deepcopy(value)
|
| 125 |
+
# Restore the dataclass type if it was lost (e.g., on re-initialization).
|
| 126 |
+
if self._dataclass_type is None:
|
| 127 |
+
self._dataclass_type = type(value)
|
| 128 |
+
|
| 129 |
if value is None or not dataclasses.is_dataclass(value):
|
| 130 |
return []
|
| 131 |
|
|
|
|
| 153 |
|
| 154 |
def preprocess(self, payload: Any) -> Any:
|
| 155 |
"""
|
| 156 |
+
Processes the payload from the frontend to create an updated dataclass instance.
|
| 157 |
+
|
| 158 |
+
This method is stateless regarding the instance value. It reconstructs the object
|
| 159 |
+
from scratch using the `_dataclass_type` (which is reliably set by `postprocess`)
|
| 160 |
+
and then applies the changes from the payload.
|
| 161 |
+
|
| 162 |
+
Args:
|
| 163 |
+
payload: The data received from the frontend, typically a list of property groups.
|
| 164 |
+
Returns:
|
| 165 |
+
A new, updated instance of the dataclass.
|
| 166 |
"""
|
| 167 |
if payload is None:
|
| 168 |
+
return None
|
| 169 |
|
| 170 |
+
if self._dataclass_type is None:
|
| 171 |
+
# This can happen if the component is used in a way that prevents postprocess
|
| 172 |
+
# from ever being called with a valid value. Returning None is a safe fallback.
|
| 173 |
+
return None
|
| 174 |
+
|
| 175 |
+
# Create a new, default instance of the stored dataclass type.
|
| 176 |
+
reconstructed_obj = self._dataclass_type()
|
| 177 |
|
| 178 |
+
# Apply the values from the payload to the new instance.
|
| 179 |
if isinstance(payload, list):
|
| 180 |
for group in payload:
|
| 181 |
for prop in group.get("properties", []):
|
| 182 |
prop_name = prop["name"]
|
| 183 |
new_value = prop["value"]
|
| 184 |
+
if hasattr(reconstructed_obj, prop_name):
|
| 185 |
+
setattr(reconstructed_obj, prop_name, new_value)
|
|
|
|
|
|
|
| 186 |
else:
|
| 187 |
+
# Handle nested dataclasses.
|
| 188 |
+
for f in dataclasses.fields(reconstructed_obj):
|
| 189 |
if dataclasses.is_dataclass(f.type):
|
| 190 |
+
group_obj = getattr(reconstructed_obj, f.name)
|
| 191 |
if hasattr(group_obj, prop_name):
|
| 192 |
+
setattr(group_obj, prop_name, new_value)
|
| 193 |
+
break
|
|
|
|
|
|
|
|
|
|
| 194 |
elif isinstance(payload, dict):
|
| 195 |
+
for key, new_value in payload.items():
|
| 196 |
+
if hasattr(reconstructed_obj, key):
|
| 197 |
+
setattr(reconstructed_obj, key, new_value)
|
| 198 |
else:
|
| 199 |
+
# Handle nested dataclasses for dict payloads.
|
| 200 |
+
for f in dataclasses.fields(reconstructed_obj):
|
| 201 |
if dataclasses.is_dataclass(f.type):
|
| 202 |
+
group_obj = getattr(reconstructed_obj, f.name)
|
| 203 |
if hasattr(group_obj, key):
|
| 204 |
setattr(group_obj, key, new_value)
|
| 205 |
break
|
| 206 |
+
|
| 207 |
+
return reconstructed_obj
|
|
|
|
| 208 |
|
| 209 |
def api_info(self) -> Dict[str, Any]:
|
| 210 |
+
"""
|
| 211 |
+
Provides API information for the component for use in API docs.
|
| 212 |
+
"""
|
| 213 |
return {"type": "object", "description": "A key-value dictionary of property settings."}
|
| 214 |
|
| 215 |
def example_payload(self) -> Any:
|
| 216 |
+
"""
|
| 217 |
+
Returns an example payload for the component's API.
|
| 218 |
+
"""
|
| 219 |
return {"seed": 12345}
|
src/backend/gradio_propertysheet/templates/component/index.js
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/backend/gradio_propertysheet/templates/component/style.css
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
.block.svelte-239wnu{position:relative;margin:0;box-shadow:var(--block-shadow);border-width:var(--block-border-width);border-color:var(--block-border-color);border-radius:var(--block-radius);background:var(--block-background-fill);width:100%;line-height:var(--line-sm)}.block.fullscreen.svelte-239wnu{border-radius:0}.auto-margin.svelte-239wnu{margin-left:auto;margin-right:auto}.block.border_focus.svelte-239wnu{border-color:var(--color-accent)}.block.border_contrast.svelte-239wnu{border-color:var(--body-text-color)}.padded.svelte-239wnu{padding:var(--block-padding)}.hidden.svelte-239wnu{display:none}.flex.svelte-239wnu{display:flex;flex-direction:column}.hide-container.svelte-239wnu:not(.fullscreen){margin:0;box-shadow:none;--block-border-width:0;background:transparent;padding:0;overflow:visible}.resize-handle.svelte-239wnu{position:absolute;bottom:0;right:0;width:10px;height:10px;fill:var(--block-border-color);cursor:nwse-resize}.fullscreen.svelte-239wnu{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:1000;overflow:auto}.animating.svelte-239wnu{animation:svelte-239wnu-pop-out .1s ease-out forwards}@keyframes svelte-239wnu-pop-out{0%{position:fixed;top:var(--start-top);left:var(--start-left);width:var(--start-width);height:var(--start-height);z-index:100}to{position:fixed;top:0vh;left:0vw;width:100vw;height:100vh;z-index:1000}}.placeholder.svelte-239wnu{border-radius:var(--block-radius);border-width:var(--block-border-width);border-color:var(--block-border-color);border-style:dashed}Tables */ table,tr,td,th{margin-top:var(--spacing-sm);margin-bottom:var(--spacing-sm);padding:var(--spacing-xl)}.md code,.md pre{background:none;font-family:var(--font-mono);font-size:var(--text-sm);text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:2;tab-size:2;-webkit-hyphens:none;hyphens:none}.md pre[class*=language-]::selection,.md pre[class*=language-] ::selection,.md code[class*=language-]::selection,.md code[class*=language-] ::selection{text-shadow:none;background:#b3d4fc}.md pre{padding:1em;margin:.5em 0;overflow:auto;position:relative;margin-top:var(--spacing-sm);margin-bottom:var(--spacing-sm);box-shadow:none;border:none;border-radius:var(--radius-md);background:var(--code-background-fill);padding:var(--spacing-xxl);font-family:var(--font-mono);text-shadow:none;border-radius:var(--radius-sm);white-space:nowrap;display:block;white-space:pre}.md :not(pre)>code{padding:.1em;border-radius:var(--radius-xs);white-space:normal;background:var(--code-background-fill);border:1px solid var(--panel-border-color);padding:var(--spacing-xxs) var(--spacing-xs)}.md .token.comment,.md .token.prolog,.md .token.doctype,.md .token.cdata{color:#708090}.md .token.punctuation{color:#999}.md .token.namespace{opacity:.7}.md .token.property,.md .token.tag,.md .token.boolean,.md .token.number,.md .token.constant,.md .token.symbol,.md .token.deleted{color:#905}.md .token.selector,.md .token.attr-name,.md .token.string,.md .token.char,.md .token.builtin,.md .token.inserted{color:#690}.md .token.atrule,.md .token.attr-value,.md .token.keyword{color:#07a}.md .token.function,.md .token.class-name{color:#dd4a68}.md .token.regex,.md .token.important,.md .token.variable{color:#e90}.md .token.important,.md .token.bold{font-weight:700}.md .token.italic{font-style:italic}.md .token.entity{cursor:help}.dark .md .token.comment,.dark .md .token.prolog,.dark .md .token.cdata{color:#5c6370}.dark .md .token.doctype,.dark .md .token.punctuation,.dark .md .token.entity{color:#abb2bf}.dark .md .token.attr-name,.dark .md .token.class-name,.dark .md .token.boolean,.dark .md .token.constant,.dark .md .token.number,.dark .md .token.atrule{color:#d19a66}.dark .md .token.keyword{color:#c678dd}.dark .md .token.property,.dark .md .token.tag,.dark .md .token.symbol,.dark .md .token.deleted,.dark .md .token.important{color:#e06c75}.dark .md .token.selector,.dark .md .token.string,.dark .md .token.char,.dark .md .token.builtin,.dark .md .token.inserted,.dark .md .token.regex,.dark .md .token.attr-value,.dark .md .token.attr-value>.token.punctuation{color:#98c379}.dark .md .token.variable,.dark .md .token.operator,.dark .md .token.function{color:#61afef}.dark .md .token.url{color:#56b6c2}span.svelte-1m32c2s div[class*=code_wrap]{position:relative}span.svelte-1m32c2s span.katex{font-size:var(--text-lg);direction:ltr}span.svelte-1m32c2s div[class*=code_wrap]>button{z-index:1;cursor:pointer;border-bottom-left-radius:var(--radius-sm);padding:var(--spacing-md);width:25px;height:25px;position:absolute;right:0}span.svelte-1m32c2s .check{opacity:0;z-index:var(--layer-top);transition:opacity .2s;background:var(--code-background-fill);color:var(--body-text-color);position:absolute;top:var(--size-1-5);left:var(--size-1-5)}span.svelte-1m32c2s p:not(:first-child){margin-top:var(--spacing-xxl)}span.svelte-1m32c2s .md-header-anchor{margin-left:-25px;padding-right:8px;line-height:1;color:var(--body-text-color-subdued);opacity:0}span.svelte-1m32c2s h1:hover .md-header-anchor,span.svelte-1m32c2s h2:hover .md-header-anchor,span.svelte-1m32c2s h3:hover .md-header-anchor,span.svelte-1m32c2s h4:hover .md-header-anchor,span.svelte-1m32c2s h5:hover .md-header-anchor,span.svelte-1m32c2s h6:hover .md-header-anchor{opacity:1}span.md.svelte-1m32c2s .md-header-anchor>svg{color:var(--body-text-color-subdued)}span.svelte-1m32c2s table{word-break:break-word}div.svelte-17qq50w>.md.prose{font-weight:var(--block-info-text-weight);font-size:var(--block-info-text-size);line-height:var(--line-sm)}div.svelte-17qq50w>.md.prose *{color:var(--block-info-text-color)}div.svelte-17qq50w{margin-bottom:var(--spacing-md)}span.has-info.svelte-zgrq3{margin-bottom:var(--spacing-xs)}span.svelte-zgrq3:not(.has-info){margin-bottom:var(--spacing-lg)}span.svelte-zgrq3{display:inline-block;position:relative;z-index:var(--layer-4);border:solid var(--block-title-border-width) var(--block-title-border-color);border-radius:var(--block-title-radius);background:var(--block-title-background-fill);padding:var(--block-title-padding);color:var(--block-title-text-color);font-weight:var(--block-title-text-weight);font-size:var(--block-title-text-size);line-height:var(--line-sm)}span[dir=rtl].svelte-zgrq3{display:block}.hide.svelte-zgrq3{margin:0;height:0}label.svelte-13ao5pu.svelte-13ao5pu{display:inline-flex;align-items:center;z-index:var(--layer-2);box-shadow:var(--block-label-shadow);border:var(--block-label-border-width) solid var(--block-label-border-color);border-top:none;border-left:none;border-radius:var(--block-label-radius);background:var(--block-label-background-fill);padding:var(--block-label-padding);pointer-events:none;color:var(--block-label-text-color);font-weight:var(--block-label-text-weight);font-size:var(--block-label-text-size);line-height:var(--line-sm)}.gr-group label.svelte-13ao5pu.svelte-13ao5pu{border-top-left-radius:0}label.float.svelte-13ao5pu.svelte-13ao5pu{position:absolute;top:var(--block-label-margin);left:var(--block-label-margin)}label.svelte-13ao5pu.svelte-13ao5pu:not(.float){position:static;margin-top:var(--block-label-margin);margin-left:var(--block-label-margin)}.hide.svelte-13ao5pu.svelte-13ao5pu{height:0}span.svelte-13ao5pu.svelte-13ao5pu{opacity:.8;margin-right:var(--size-2);width:calc(var(--block-label-text-size) - 1px);height:calc(var(--block-label-text-size) - 1px)}.hide-label.svelte-13ao5pu.svelte-13ao5pu{box-shadow:none;border-width:0;background:transparent;overflow:visible}label[dir=rtl].svelte-13ao5pu.svelte-13ao5pu{border:var(--block-label-border-width) solid var(--block-label-border-color);border-top:none;border-right:none;border-bottom-left-radius:var(--block-radius);border-bottom-right-radius:var(--block-label-radius);border-top-left-radius:var(--block-label-radius)}label[dir=rtl].svelte-13ao5pu span.svelte-13ao5pu{margin-left:var(--size-2);margin-right:0}button.svelte-qgco6m{display:flex;justify-content:center;align-items:center;gap:1px;z-index:var(--layer-2);border-radius:var(--radius-xs);color:var(--block-label-text-color);border:1px solid transparent;padding:var(--spacing-xxs)}button.svelte-qgco6m:hover{background-color:var(--background-fill-secondary)}button[disabled].svelte-qgco6m{opacity:.5;box-shadow:none}button[disabled].svelte-qgco6m:hover{cursor:not-allowed}.padded.svelte-qgco6m{background:var(--bg-color)}button.svelte-qgco6m:hover,button.highlight.svelte-qgco6m{cursor:pointer;color:var(--color-accent)}.padded.svelte-qgco6m:hover{color:var(--block-label-text-color)}span.svelte-qgco6m{padding:0 1px;font-size:10px}div.svelte-qgco6m{display:flex;align-items:center;justify-content:center;transition:filter .2s ease-in-out}.x-small.svelte-qgco6m{width:10px;height:10px}.small.svelte-qgco6m{width:14px;height:14px}.medium.svelte-qgco6m{width:20px;height:20px}.large.svelte-qgco6m{width:22px;height:22px}.pending.svelte-qgco6m{animation:svelte-qgco6m-flash .5s infinite}@keyframes svelte-qgco6m-flash{0%{opacity:.5}50%{opacity:1}to{opacity:.5}}.transparent.svelte-qgco6m{background:transparent;border:none;box-shadow:none}.empty.svelte-3w3rth{display:flex;justify-content:center;align-items:center;margin-top:calc(0px - var(--size-6));height:var(--size-full)}.icon.svelte-3w3rth{opacity:.5;height:var(--size-5);color:var(--body-text-color)}.small.svelte-3w3rth{min-height:calc(var(--size-32) - 20px)}.large.svelte-3w3rth{min-height:calc(var(--size-64) - 20px)}.unpadded_box.svelte-3w3rth{margin-top:0}.small_parent.svelte-3w3rth{min-height:100%!important}.dropdown-arrow.svelte-145leq6,.dropdown-arrow.svelte-ihhdbf{fill:currentColor}.circle.svelte-ihhdbf{fill:currentColor;opacity:.1}svg.svelte-pb9pol{animation:svelte-pb9pol-spin 1.5s linear infinite}@keyframes svelte-pb9pol-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}h2.svelte-1xg7h5n{font-size:var(--text-xl)!important}p.svelte-1xg7h5n,h2.svelte-1xg7h5n{white-space:pre-line}.wrap.svelte-1xg7h5n{display:flex;flex-direction:column;justify-content:center;align-items:center;min-height:var(--size-60);color:var(--block-label-text-color);line-height:var(--line-md);height:100%;padding-top:var(--size-3);text-align:center;margin:auto var(--spacing-lg)}.or.svelte-1xg7h5n{color:var(--body-text-color-subdued);display:flex}.icon-wrap.svelte-1xg7h5n{width:30px;margin-bottom:var(--spacing-lg)}@media (--screen-md){.wrap.svelte-1xg7h5n{font-size:var(--text-lg)}}.hovered.svelte-1xg7h5n{color:var(--color-accent)}div.svelte-q32hvf{border-top:1px solid transparent;display:flex;max-height:100%;justify-content:center;align-items:center;gap:var(--spacing-sm);height:auto;align-items:flex-end;color:var(--block-label-text-color);flex-shrink:0}.show_border.svelte-q32hvf{border-top:1px solid var(--block-border-color);margin-top:var(--spacing-xxl);box-shadow:var(--shadow-drop)}.source-selection.svelte-15ls1gu{display:flex;align-items:center;justify-content:center;border-top:1px solid var(--border-color-primary);width:100%;margin-left:auto;margin-right:auto;height:var(--size-10)}.icon.svelte-15ls1gu{width:22px;height:22px;margin:var(--spacing-lg) var(--spacing-xs);padding:var(--spacing-xs);color:var(--neutral-400);border-radius:var(--radius-md)}.selected.svelte-15ls1gu{color:var(--color-accent)}.icon.svelte-15ls1gu:hover,.icon.svelte-15ls1gu:focus{color:var(--color-accent)}.icon-button-wrapper.svelte-109se4{display:flex;flex-direction:row;align-items:center;justify-content:center;z-index:var(--layer-3);gap:var(--spacing-sm);box-shadow:var(--shadow-drop);border:1px solid var(--border-color-primary);background:var(--block-background-fill);padding:var(--spacing-xxs)}.icon-button-wrapper.hide-top-corner.svelte-109se4{border-top:none;border-right:none;border-radius:var(--block-label-right-radius)}.icon-button-wrapper.display-top-corner.svelte-109se4{border-radius:var(--radius-sm) 0 0 var(--radius-sm);top:var(--spacing-sm);right:-1px}.icon-button-wrapper.svelte-109se4:not(.top-panel){border:1px solid var(--border-color-primary);border-radius:var(--radius-sm)}.top-panel.svelte-109se4{position:absolute;top:var(--block-label-margin);right:var(--block-label-margin);margin:0}.icon-button-wrapper.svelte-109se4 button{margin:var(--spacing-xxs);border-radius:var(--radius-xs);position:relative}.icon-button-wrapper.svelte-109se4 a.download-link:not(:last-child),.icon-button-wrapper.svelte-109se4 button:not(:last-child){margin-right:var(--spacing-xxs)}.icon-button-wrapper.svelte-109se4 a.download-link:not(:last-child):not(.no-border *):after,.icon-button-wrapper.svelte-109se4 button:not(:last-child):not(.no-border *):after{content:"";position:absolute;right:-4.5px;top:15%;height:70%;width:1px;background-color:var(--border-color-primary)}.icon-button-wrapper.svelte-109se4>*{height:100%}svg.svelte-43sxxs.svelte-43sxxs{width:var(--size-20);height:var(--size-20)}svg.svelte-43sxxs path.svelte-43sxxs{fill:var(--loader-color)}div.svelte-43sxxs.svelte-43sxxs{z-index:var(--layer-2)}.margin.svelte-43sxxs.svelte-43sxxs{margin:var(--size-4)}.wrap.svelte-17v219f.svelte-17v219f{display:flex;flex-direction:column;justify-content:center;align-items:center;z-index:var(--layer-2);transition:opacity .1s ease-in-out;border-radius:var(--block-radius);background:var(--block-background-fill);padding:0 var(--size-6);max-height:var(--size-screen-h);overflow:hidden}.wrap.center.svelte-17v219f.svelte-17v219f{top:0;right:0;left:0}.wrap.default.svelte-17v219f.svelte-17v219f{top:0;right:0;bottom:0;left:0}.hide.svelte-17v219f.svelte-17v219f{opacity:0;pointer-events:none}.generating.svelte-17v219f.svelte-17v219f{animation:svelte-17v219f-pulseStart 1s cubic-bezier(.4,0,.6,1),svelte-17v219f-pulse 2s cubic-bezier(.4,0,.6,1) 1s infinite;border:2px solid var(--color-accent);background:transparent;z-index:var(--layer-1);pointer-events:none}.translucent.svelte-17v219f.svelte-17v219f{background:none}@keyframes svelte-17v219f-pulseStart{0%{opacity:0}to{opacity:1}}@keyframes svelte-17v219f-pulse{0%,to{opacity:1}50%{opacity:.5}}.loading.svelte-17v219f.svelte-17v219f{z-index:var(--layer-2);color:var(--body-text-color)}.eta-bar.svelte-17v219f.svelte-17v219f{position:absolute;top:0;right:0;bottom:0;left:0;transform-origin:left;opacity:.8;z-index:var(--layer-1);transition:10ms;background:var(--background-fill-secondary)}.progress-bar-wrap.svelte-17v219f.svelte-17v219f{border:1px solid var(--border-color-primary);background:var(--background-fill-primary);width:55.5%;height:var(--size-4)}.progress-bar.svelte-17v219f.svelte-17v219f{transform-origin:left;background-color:var(--loader-color);width:var(--size-full);height:var(--size-full)}.progress-level.svelte-17v219f.svelte-17v219f{display:flex;flex-direction:column;align-items:center;gap:1;z-index:var(--layer-2);width:var(--size-full)}.progress-level-inner.svelte-17v219f.svelte-17v219f{margin:var(--size-2) auto;color:var(--body-text-color);font-size:var(--text-sm);font-family:var(--font-mono)}.meta-text.svelte-17v219f.svelte-17v219f{position:absolute;bottom:0;right:0;z-index:var(--layer-2);padding:var(--size-1) var(--size-2);font-size:var(--text-sm);font-family:var(--font-mono)}.meta-text-center.svelte-17v219f.svelte-17v219f{display:flex;position:absolute;top:0;right:0;justify-content:center;align-items:center;transform:translateY(var(--size-6));z-index:var(--layer-2);padding:var(--size-1) var(--size-2);font-size:var(--text-sm);font-family:var(--font-mono);text-align:center}.error.svelte-17v219f.svelte-17v219f{box-shadow:var(--shadow-drop);border:solid 1px var(--error-border-color);border-radius:var(--radius-full);background:var(--error-background-fill);padding-right:var(--size-4);padding-left:var(--size-4);color:var(--error-text-color);font-weight:var(--weight-semibold);font-size:var(--text-lg);line-height:var(--line-lg);font-family:var(--font)}.minimal.svelte-17v219f.svelte-17v219f{pointer-events:none}.minimal.svelte-17v219f .progress-text.svelte-17v219f{background:var(--block-background-fill)}.border.svelte-17v219f.svelte-17v219f{border:1px solid var(--border-color-primary)}.clear-status.svelte-17v219f.svelte-17v219f{position:absolute;display:flex;top:var(--size-2);right:var(--size-2);justify-content:flex-end;gap:var(--spacing-sm);z-index:var(--layer-1)}.toast-body.svelte-1pgj5gs{display:flex;position:relative;right:0;left:0;align-items:center;margin:var(--size-6) var(--size-4);margin:auto;border-radius:var(--container-radius);overflow:hidden;pointer-events:auto}.toast-body.error.svelte-1pgj5gs{border:1px solid var(--color-red-700);background:var(--color-red-50)}.dark .toast-body.error.svelte-1pgj5gs{border:1px solid var(--color-red-500);background-color:var(--color-grey-950)}.toast-body.warning.svelte-1pgj5gs{border:1px solid var(--color-yellow-700);background:var(--color-yellow-50)}.dark .toast-body.warning.svelte-1pgj5gs{border:1px solid var(--color-yellow-500);background-color:var(--color-grey-950)}.toast-body.info.svelte-1pgj5gs{border:1px solid var(--color-grey-700);background:var (--color-grey-50)}.dark .toast-body.info.svelte-1pgj5gs{border:1px solid var(--color-grey-500);background-color:var(--color-grey-950)}.toast-body.success.svelte-1pgj5gs{border:1px solid var(--color-green-700);background:var(--color-green-50)}.dark .toast-body.success.svelte-1pgj5gs{border:1px solid var(--color-green-500);background-color:var(--color-grey-950)}.toast-title.svelte-1pgj5gs{display:flex;align-items:center;font-weight:var(--weight-bold);font-size:var(--text-lg);line-height:var(--line-sm)}.toast-title.error.svelte-1pgj5gs{color:var(--color-red-700)}.dark .toast-title.error.svelte-1pgj5gs{color:var(--color-red-50)}.toast-title.warning.svelte-1pgj5gs{color:var(--color-yellow-700)}.dark .toast-title.warning.svelte-1pgj5gs{color:var(--color-yellow-50)}.toast-title.info.svelte-1pgj5gs{color:var(--color-grey-700)}.dark .toast-title.info.svelte-1pgj5gs{color:var(--color-grey-50)}.toast-title.success.svelte-1pgj5gs{color:var(--color-green-700)}.dark .toast-title.success.svelte-1pgj5gs{color:var(--color-green-50)}.toast-close.svelte-1pgj5gs{margin:0 var(--size-3);border-radius:var(--size-3);padding:0px var(--size-1-5);font-size:var(--size-5);line-height:var(--size-5)}.toast-close.error.svelte-1pgj5gs{color:var(--color-red-700)}.dark .toast-close.error.svelte-1pgj5gs{color:var(--color-red-500)}.toast-close.warning.svelte-1pgj5gs{color:var(--color-yellow-700)}.dark .toast-close.warning.svelte-1pgj5gs{color:var(--color-yellow-500)}.toast-close.info.svelte-1pgj5gs{color:var(--color-grey-700)}.dark .toast-close.info.svelte-1pgj5gs{color:var(--color-grey-500)}.toast-close.success.svelte-1pgj5gs{color:var(--color-green-700)}.dark .toast-close.success.svelte-1pgj5gs{color:var(--color-green-500)}.toast-text.svelte-1pgj5gs{font-size:var(--text-lg);word-wrap:break-word;overflow-wrap:break-word;word-break:break-word}.toast-text.error.svelte-1pgj5gs{color:var(--color-red-700)}.dark .toast-text.error.svelte-1pgj5gs{color:var(--color-red-50)}.toast-text.warning.svelte-1pgj5gs{color:var(--color-yellow-700)}.dark .toast-text.warning.svelte-1pgj5gs{color:var(--color-yellow-50)}.toast-text.info.svelte-1pgj5gs{color:var(--color-grey-700)}.dark .toast-text.info.svelte-1pgj5gs{color:var(--color-grey-50)}.toast-text.success.svelte-1pgj5gs{color:var(--color-green-700)}.dark .toast-text.success.svelte-1pgj5gs{color:var(--color-green-50)}.toast-details.svelte-1pgj5gs{margin:var(--size-3) var(--size-3) var(--size-3) 0;width:100%}.toast-icon.svelte-1pgj5gs{display:flex;position:absolute;position:relative;flex-shrink:0;justify-content:center;align-items:center;margin:var(--size-2);border-radius:var(--radius-full);padding:var(--size-1);padding-left:calc(var(--size-1) - 1px);width:35px;height:35px}.toast-icon.error.svelte-1pgj5gs{color:var(--color-red-700)}.dark .toast-icon.error.svelte-1pgj5gs{color:var(--color-red-500)}.toast-icon.warning.svelte-1pgj5gs{color:var(--color-yellow-700)}.dark .toast-icon.warning.svelte-1pgj5gs{color:var(--color-yellow-500)}.toast-icon.info.svelte-1pgj5gs{color:var(--color-grey-700)}.dark .toast-icon.info.svelte-1pgj5gs{color:var(--color-grey-500)}.toast-icon.success.svelte-1pgj5gs{color:var(--color-green-700)}.dark .toast-icon.success.svelte-1pgj5gs{color:var(--color-green-500)}@keyframes svelte-1pgj5gs-countdown{0%{transform:scaleX(1)}to{transform:scaleX(0)}}.timer.svelte-1pgj5gs{position:absolute;bottom:0;left:0;transform-origin:0 0;animation:svelte-1pgj5gs-countdown 10s linear forwards;width:100%;height:var(--size-1)}.timer.error.svelte-1pgj5gs{background:var(--color-red-700)}.dark .timer.error.svelte-1pgj5gs{background:var(--color-red-500)}.timer.warning.svelte-1pgj5gs{background:var(--color-yellow-700)}.dark .timer.warning.svelte-1pgj5gs{background:var(--color-yellow-500)}.timer.info.svelte-1pgj5gs{background:var(--color-grey-700)}.dark .timer.info.svelte-1pgj5gs{background:var(--color-grey-500)}.timer.success.svelte-1pgj5gs{background:var(--color-green-700)}.dark .timer.success.svelte-1pgj5gs{background:var(--color-green-500)}.hidden.svelte-1pgj5gs{display:none}.toast-text.svelte-1pgj5gs a{text-decoration:underline}.toast-wrap.svelte-gatr8h{display:flex;position:fixed;top:var(--size-4);right:var(--size-4);flex-direction:column;align-items:end;gap:var(--size-2);z-index:var(--layer-top);width:calc(100% - var(--size-8))}@media (--screen-sm){.toast-wrap.svelte-gatr8h{width:calc(var(--size-96) + var(--size-10))}}.streaming-bar.svelte-ga0jj6{position:absolute;bottom:0;left:0;right:0;height:4px;background-color:var(--primary-600);animation:svelte-ga0jj6-countdown linear forwards;z-index:1}@keyframes svelte-ga0jj6-countdown{0%{transform:translate(0)}to{transform:translate(-100%)}}:host{display:flex;flex-direction:column;height:100%}.propertysheet-wrapper{overflow:hidden!important;display:flex;flex-direction:column;flex-grow:1}.accordion-header.svelte-16grkhl.svelte-16grkhl{display:flex;justify-content:space-between;align-items:center;width:100%;cursor:pointer;padding:var(--block-title-padding);background:var(--block-title-background-fill);color:var(--block-title-text-color);flex-shrink:0}.content-wrapper.svelte-16grkhl.svelte-16grkhl{flex-grow:1;min-height:0}.container.svelte-16grkhl.svelte-16grkhl{overflow-y:auto;height:auto;max-height:var(--sheet-max-height, 500px);border-radius:0!important;border:1px solid var(--border-color-primary);border-top:none;border-bottom-left-radius:var(--radius-lg);border-bottom-right-radius:var(--radius-lg);background-color:var(--background-fill-secondary)}.closed.svelte-16grkhl.svelte-16grkhl{display:none}.group-header.svelte-16grkhl.svelte-16grkhl{display:flex;justify-content:space-between;align-items:center;width:100%;padding:var(--spacing-sm) var(--spacing-md);background-color:var(--input-background-fill);color:var(--body-text-color);text-align:left;cursor:pointer;font-size:var(--text-md);font-weight:var(--font-weight-bold);border:1px solid var(--border-color-primary)}.properties-grid.svelte-16grkhl.svelte-16grkhl{display:grid;grid-template-columns:1fr 2fr;gap:0;padding:0}.prop-label.svelte-16grkhl.svelte-16grkhl,.prop-control.svelte-16grkhl.svelte-16grkhl{padding:var(--spacing-sm) var(--spacing-md);display:flex;align-items:center;border-bottom:1px solid var(--background-fill-secondary)}.prop-label.svelte-16grkhl.svelte-16grkhl{background-color:var(--background-fill-primary);color:var(--body-text-color);opacity:.7;font-weight:var(--font-weight-semibold);font-size:var(--text-xs);text-align:right;justify-content:flex-end;word-break:break-word}.prop-control.svelte-16grkhl.svelte-16grkhl{gap:var(--spacing-sm)}.properties-grid.svelte-16grkhl>.svelte-16grkhl:nth-last-child(-n+2){border-bottom:none}.prop-control.svelte-16grkhl input[type=text].svelte-16grkhl,.prop-control.svelte-16grkhl input[type=number].svelte-16grkhl{background-color:var(--input-background-fill);border:var(--input-border-width) solid var(--border-color-primary);box-shadow:var(--input-shadow);color:var(--input-text-color);font-size:var(--input-text-size);border-radius:0;width:100%;padding-top:var(--spacing-1);padding-bottom:var(--spacing-1);padding-left:var(--spacing-md);padding-right:var(--spacing-3)}.prop-control.svelte-16grkhl input[type=text].svelte-16grkhl:focus,.prop-control.svelte-16grkhl input[type=number].svelte-16grkhl:focus{box-shadow:var(--input-shadow-focus);border-color:var(--input-border-color-focus);background-color:var(--input-background-fill-focus);outline:none}.dropdown-wrapper.svelte-16grkhl.svelte-16grkhl{position:relative;width:100%}.dropdown-wrapper.svelte-16grkhl select.svelte-16grkhl{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--input-background-fill);border:var(--input-border-width) solid var(--border-color-primary);box-shadow:var(--input-shadow);color:var(--input-text-color);font-size:var(--input-text-size);width:100%;cursor:pointer;border-radius:0;padding-top:var(--spacing-1);padding-bottom:var(--spacing-1);padding-left:var(--spacing-md);padding-right:calc(var(--spacing-3) + 1.2em)}.dropdown-arrow-icon.svelte-16grkhl.svelte-16grkhl{position:absolute;top:50%;right:var(--spacing-3);transform:translateY(-50%);width:1em;height:1em;pointer-events:none;z-index:1;background-color:var(--body-text-color-subdued);-webkit-mask-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='currentColor' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M6 8l4 4 4-4'/%3e%3c/svg%3e");mask-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='currentColor' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M6 8l4 4 4-4'%3e%3c/svg%3e")}.dropdown-wrapper.svelte-16grkhl select.svelte-16grkhl:focus{box-shadow:var(--input-shadow-focus);border-color:var(--input-border-color-focus);background-color:var(--input-background-fill-focus);outline:none}.dropdown-wrapper.svelte-16grkhl select option.svelte-16grkhl{background:var(--input-background-fill);color:var(--body-text-color)}.prop-control.svelte-16grkhl input[type=checkbox].svelte-16grkhl{-webkit-appearance:none;-moz-appearance:none;appearance:none;position:relative;width:var(--size-4);height:var(--size-4);border-radius:0;border:1px solid var(--checkbox-border-color);background-color:var(--checkbox-background-color);box-shadow:var(--checkbox-shadow);cursor:pointer;margin:0;transition:background-color .2s,border-color .2s}.prop-control.svelte-16grkhl input[type=checkbox].svelte-16grkhl:hover{border-color:var(--checkbox-border-color-hover);background-color:var(--checkbox-background-color-hover)}.prop-control.svelte-16grkhl input[type=checkbox].svelte-16grkhl:focus{border-color:var(--checkbox-border-color-focus);background-color:var(--checkbox-background-color-focus);outline:none}.prop-control.svelte-16grkhl input[type=checkbox].svelte-16grkhl:checked{background-color:var(--checkbox-background-color-selected);border-color:var(--checkbox-border-color-focus)}.prop-control.svelte-16grkhl input[type=checkbox].svelte-16grkhl:checked:after{content:"";position:absolute;display:block;top:50%;left:50%;width:4px;height:8px;border:solid var(--checkbox-label-text-color-selected);border-width:0 2px 2px 0;transform:translate(-50%,-60%) rotate(45deg)}.slider-container.svelte-16grkhl.svelte-16grkhl{display:flex;align-items:center;gap:var(--spacing-md);width:100%}.slider-container.svelte-16grkhl input[type=range].svelte-16grkhl{--slider-progress:0%;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;cursor:pointer;width:100%}.slider-container.svelte-16grkhl input[type=range].svelte-16grkhl::-webkit-slider-runnable-track{height:8px;border-radius:var(--radius-lg);background:linear-gradient(to right,var(--slider-color) var(--slider-progress),var(--input-background-fill) var(--slider-progress))}.slider-container.svelte-16grkhl input[type=range].svelte-16grkhl::-webkit-slider-thumb{-webkit-appearance:none;-moz-appearance:none;appearance:none;margin-top:-4px;background-color:#fff;border-radius:50%;height:16px;width:16px;border:1px solid var(--border-color-primary);box-shadow:var(--shadow-drop)}.slider-container.svelte-16grkhl input[type=range].svelte-16grkhl::-moz-range-track{height:8px;border-radius:var(--radius-lg);background:linear-gradient(to right,var(--slider-color) var(--slider-progress),var(--input-background-fill) var(--slider-progress))}.slider-container.svelte-16grkhl input[type=range].svelte-16grkhl::-moz-range-thumb{background-color:#fff;border-radius:50%;height:16px;width:16px;border:1px solid var(--border-color-primary);box-shadow:var(--shadow-drop)}.slider-value.svelte-16grkhl.svelte-16grkhl{min-width:40px;text-align:right;font-family:var(--font-mono);font-size:var(--text-xs)}.prop-label-wrapper.svelte-16grkhl.svelte-16grkhl{display:flex;justify-content:flex-end;align-items:center;gap:var(--spacing-sm);width:100%}.tooltip-container.svelte-16grkhl.svelte-16grkhl{position:relative;display:inline-flex;align-items:center;justify-content:center}.tooltip-icon.svelte-16grkhl.svelte-16grkhl{display:flex;align-items:center;justify-content:center;width:14px;height:14px;border-radius:50%;background-color:var(--body-text-color-subdued);color:var(--background-fill-primary);font-size:10px;font-weight:700;cursor:help;-webkit-user-select:none;user-select:none}.tooltip-text.svelte-16grkhl.svelte-16grkhl{visibility:hidden;width:200px;background-color:var(--body-text-color);color:var(--background-fill-primary);text-align:center;border-radius:var(--radius-md);padding:var(--spacing-md);position:absolute;z-index:10;bottom:125%;left:50%;transform:translate(-50%);opacity:0;transition:opacity .3s}.tooltip-container.svelte-16grkhl:hover .tooltip-text.svelte-16grkhl{visibility:visible;opacity:1}.color-picker-container.svelte-16grkhl.svelte-16grkhl{display:flex;align-items:center;gap:var(--spacing-md);width:100%}.color-picker-input.svelte-16grkhl.svelte-16grkhl{width:50px;height:28px;background-color:transparent;border:1px solid var(--border-color-primary);border-radius:var(--radius-sm);cursor:pointer;padding:0}.color-picker-input.svelte-16grkhl.svelte-16grkhl::-webkit-color-swatch-wrapper{padding:2px}.color-picker-input.svelte-16grkhl.svelte-16grkhl::-moz-padding{padding:2px}.color-picker-input.svelte-16grkhl.svelte-16grkhl::-webkit-color-swatch{border:none;border-radius:var(--radius-sm)}.color-picker-input.svelte-16grkhl.svelte-16grkhl::-moz-color-swatch{border:none;border-radius:var(--radius-sm)}.color-picker-value.svelte-16grkhl.svelte-16grkhl{font-family:var(--font-mono);font-size:var(--text-sm);color:var(--body-text-color-subdued)}.prop-control.svelte-16grkhl input.invalid.svelte-16grkhl{border-color:var(--error-border-color, red)!important;box-shadow:0 0 0 1px var(--error-border-color, red)!important}.reset-button-prop.svelte-16grkhl.svelte-16grkhl{display:flex;align-items:center;justify-content:center;background:none;border:none;border-left:1px solid var(--border-color-primary);cursor:pointer;color:var(--body-text-color-subdued);font-size:var(--text-lg);padding:0 var(--spacing-2);visibility:hidden;opacity:0;transition:opacity .15s ease-in-out,color .15s ease-in-out}.reset-button-prop.visible.svelte-16grkhl.svelte-16grkhl{visibility:visible;opacity:1}.reset-button-prop.svelte-16grkhl.svelte-16grkhl:hover{color:var(--body-text-color);background-color:var(--background-fill-secondary-hover)}.reset-button-prop.svelte-16grkhl.svelte-16grkhl:disabled{color:var(--body-text-color-subdued)!important;opacity:.5;cursor:not-allowed;background-color:transparent!important}.prop-control.svelte-16grkhl .disabled.svelte-16grkhl{opacity:.5;pointer-events:none;cursor:not-allowed}.prop-control.svelte-16grkhl .disabled input.svelte-16grkhl{cursor:not-allowed}.reset-button-prop.svelte-16grkhl.svelte-16grkhl:disabled{opacity:.3;cursor:not-allowed;background-color:transparent!important}
|
|
|
|
| 1 |
+
.block.svelte-239wnu{position:relative;margin:0;box-shadow:var(--block-shadow);border-width:var(--block-border-width);border-color:var(--block-border-color);border-radius:var(--block-radius);background:var(--block-background-fill);width:100%;line-height:var(--line-sm)}.block.fullscreen.svelte-239wnu{border-radius:0}.auto-margin.svelte-239wnu{margin-left:auto;margin-right:auto}.block.border_focus.svelte-239wnu{border-color:var(--color-accent)}.block.border_contrast.svelte-239wnu{border-color:var(--body-text-color)}.padded.svelte-239wnu{padding:var(--block-padding)}.hidden.svelte-239wnu{display:none}.flex.svelte-239wnu{display:flex;flex-direction:column}.hide-container.svelte-239wnu:not(.fullscreen){margin:0;box-shadow:none;--block-border-width:0;background:transparent;padding:0;overflow:visible}.resize-handle.svelte-239wnu{position:absolute;bottom:0;right:0;width:10px;height:10px;fill:var(--block-border-color);cursor:nwse-resize}.fullscreen.svelte-239wnu{position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:1000;overflow:auto}.animating.svelte-239wnu{animation:svelte-239wnu-pop-out .1s ease-out forwards}@keyframes svelte-239wnu-pop-out{0%{position:fixed;top:var(--start-top);left:var(--start-left);width:var(--start-width);height:var(--start-height);z-index:100}to{position:fixed;top:0vh;left:0vw;width:100vw;height:100vh;z-index:1000}}.placeholder.svelte-239wnu{border-radius:var(--block-radius);border-width:var(--block-border-width);border-color:var(--block-border-color);border-style:dashed}Tables */ table,tr,td,th{margin-top:var(--spacing-sm);margin-bottom:var(--spacing-sm);padding:var(--spacing-xl)}.md code,.md pre{background:none;font-family:var(--font-mono);font-size:var(--text-sm);text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:2;tab-size:2;-webkit-hyphens:none;hyphens:none}.md pre[class*=language-]::selection,.md pre[class*=language-] ::selection,.md code[class*=language-]::selection,.md code[class*=language-] ::selection{text-shadow:none;background:#b3d4fc}.md pre{padding:1em;margin:.5em 0;overflow:auto;position:relative;margin-top:var(--spacing-sm);margin-bottom:var(--spacing-sm);box-shadow:none;border:none;border-radius:var(--radius-md);background:var(--code-background-fill);padding:var(--spacing-xxl);font-family:var(--font-mono);text-shadow:none;border-radius:var(--radius-sm);white-space:nowrap;display:block;white-space:pre}.md :not(pre)>code{padding:.1em;border-radius:var(--radius-xs);white-space:normal;background:var(--code-background-fill);border:1px solid var(--panel-border-color);padding:var(--spacing-xxs) var(--spacing-xs)}.md .token.comment,.md .token.prolog,.md .token.doctype,.md .token.cdata{color:#708090}.md .token.punctuation{color:#999}.md .token.namespace{opacity:.7}.md .token.property,.md .token.tag,.md .token.boolean,.md .token.number,.md .token.constant,.md .token.symbol,.md .token.deleted{color:#905}.md .token.selector,.md .token.attr-name,.md .token.string,.md .token.char,.md .token.builtin,.md .token.inserted{color:#690}.md .token.atrule,.md .token.attr-value,.md .token.keyword{color:#07a}.md .token.function,.md .token.class-name{color:#dd4a68}.md .token.regex,.md .token.important,.md .token.variable{color:#e90}.md .token.important,.md .token.bold{font-weight:700}.md .token.italic{font-style:italic}.md .token.entity{cursor:help}.dark .md .token.comment,.dark .md .token.prolog,.dark .md .token.cdata{color:#5c6370}.dark .md .token.doctype,.dark .md .token.punctuation,.dark .md .token.entity{color:#abb2bf}.dark .md .token.attr-name,.dark .md .token.class-name,.dark .md .token.boolean,.dark .md .token.constant,.dark .md .token.number,.dark .md .token.atrule{color:#d19a66}.dark .md .token.keyword{color:#c678dd}.dark .md .token.property,.dark .md .token.tag,.dark .md .token.symbol,.dark .md .token.deleted,.dark .md .token.important{color:#e06c75}.dark .md .token.selector,.dark .md .token.string,.dark .md .token.char,.dark .md .token.builtin,.dark .md .token.inserted,.dark .md .token.regex,.dark .md .token.attr-value,.dark .md .token.attr-value>.token.punctuation{color:#98c379}.dark .md .token.variable,.dark .md .token.operator,.dark .md .token.function{color:#61afef}.dark .md .token.url{color:#56b6c2}span.svelte-1m32c2s div[class*=code_wrap]{position:relative}span.svelte-1m32c2s span.katex{font-size:var(--text-lg);direction:ltr}span.svelte-1m32c2s div[class*=code_wrap]>button{z-index:1;cursor:pointer;border-bottom-left-radius:var(--radius-sm);padding:var(--spacing-md);width:25px;height:25px;position:absolute;right:0}span.svelte-1m32c2s .check{opacity:0;z-index:var(--layer-top);transition:opacity .2s;background:var(--code-background-fill);color:var(--body-text-color);position:absolute;top:var(--size-1-5);left:var(--size-1-5)}span.svelte-1m32c2s p:not(:first-child){margin-top:var(--spacing-xxl)}span.svelte-1m32c2s .md-header-anchor{margin-left:-25px;padding-right:8px;line-height:1;color:var(--body-text-color-subdued);opacity:0}span.svelte-1m32c2s h1:hover .md-header-anchor,span.svelte-1m32c2s h2:hover .md-header-anchor,span.svelte-1m32c2s h3:hover .md-header-anchor,span.svelte-1m32c2s h4:hover .md-header-anchor,span.svelte-1m32c2s h5:hover .md-header-anchor,span.svelte-1m32c2s h6:hover .md-header-anchor{opacity:1}span.md.svelte-1m32c2s .md-header-anchor>svg{color:var(--body-text-color-subdued)}span.svelte-1m32c2s table{word-break:break-word}div.svelte-17qq50w>.md.prose{font-weight:var(--block-info-text-weight);font-size:var(--block-info-text-size);line-height:var(--line-sm)}div.svelte-17qq50w>.md.prose *{color:var(--block-info-text-color)}div.svelte-17qq50w{margin-bottom:var(--spacing-md)}span.has-info.svelte-zgrq3{margin-bottom:var(--spacing-xs)}span.svelte-zgrq3:not(.has-info){margin-bottom:var(--spacing-lg)}span.svelte-zgrq3{display:inline-block;position:relative;z-index:var(--layer-4);border:solid var(--block-title-border-width) var(--block-title-border-color);border-radius:var(--block-title-radius);background:var(--block-title-background-fill);padding:var(--block-title-padding);color:var(--block-title-text-color);font-weight:var(--block-title-text-weight);font-size:var(--block-title-text-size);line-height:var(--line-sm)}span[dir=rtl].svelte-zgrq3{display:block}.hide.svelte-zgrq3{margin:0;height:0}label.svelte-13ao5pu.svelte-13ao5pu{display:inline-flex;align-items:center;z-index:var(--layer-2);box-shadow:var(--block-label-shadow);border:var(--block-label-border-width) solid var(--block-label-border-color);border-top:none;border-left:none;border-radius:var(--block-label-radius);background:var(--block-label-background-fill);padding:var(--block-label-padding);pointer-events:none;color:var(--block-label-text-color);font-weight:var(--block-label-text-weight);font-size:var(--block-label-text-size);line-height:var(--line-sm)}.gr-group label.svelte-13ao5pu.svelte-13ao5pu{border-top-left-radius:0}label.float.svelte-13ao5pu.svelte-13ao5pu{position:absolute;top:var(--block-label-margin);left:var(--block-label-margin)}label.svelte-13ao5pu.svelte-13ao5pu:not(.float){position:static;margin-top:var(--block-label-margin);margin-left:var(--block-label-margin)}.hide.svelte-13ao5pu.svelte-13ao5pu{height:0}span.svelte-13ao5pu.svelte-13ao5pu{opacity:.8;margin-right:var(--size-2);width:calc(var(--block-label-text-size) - 1px);height:calc(var(--block-label-text-size) - 1px)}.hide-label.svelte-13ao5pu.svelte-13ao5pu{box-shadow:none;border-width:0;background:transparent;overflow:visible}label[dir=rtl].svelte-13ao5pu.svelte-13ao5pu{border:var(--block-label-border-width) solid var(--block-label-border-color);border-top:none;border-right:none;border-bottom-left-radius:var(--block-radius);border-bottom-right-radius:var(--block-label-radius);border-top-left-radius:var(--block-label-radius)}label[dir=rtl].svelte-13ao5pu span.svelte-13ao5pu{margin-left:var(--size-2);margin-right:0}button.svelte-qgco6m{display:flex;justify-content:center;align-items:center;gap:1px;z-index:var(--layer-2);border-radius:var(--radius-xs);color:var(--block-label-text-color);border:1px solid transparent;padding:var(--spacing-xxs)}button.svelte-qgco6m:hover{background-color:var(--background-fill-secondary)}button[disabled].svelte-qgco6m{opacity:.5;box-shadow:none}button[disabled].svelte-qgco6m:hover{cursor:not-allowed}.padded.svelte-qgco6m{background:var(--bg-color)}button.svelte-qgco6m:hover,button.highlight.svelte-qgco6m{cursor:pointer;color:var(--color-accent)}.padded.svelte-qgco6m:hover{color:var(--block-label-text-color)}span.svelte-qgco6m{padding:0 1px;font-size:10px}div.svelte-qgco6m{display:flex;align-items:center;justify-content:center;transition:filter .2s ease-in-out}.x-small.svelte-qgco6m{width:10px;height:10px}.small.svelte-qgco6m{width:14px;height:14px}.medium.svelte-qgco6m{width:20px;height:20px}.large.svelte-qgco6m{width:22px;height:22px}.pending.svelte-qgco6m{animation:svelte-qgco6m-flash .5s infinite}@keyframes svelte-qgco6m-flash{0%{opacity:.5}50%{opacity:1}to{opacity:.5}}.transparent.svelte-qgco6m{background:transparent;border:none;box-shadow:none}.empty.svelte-3w3rth{display:flex;justify-content:center;align-items:center;margin-top:calc(0px - var(--size-6));height:var(--size-full)}.icon.svelte-3w3rth{opacity:.5;height:var(--size-5);color:var(--body-text-color)}.small.svelte-3w3rth{min-height:calc(var(--size-32) - 20px)}.large.svelte-3w3rth{min-height:calc(var(--size-64) - 20px)}.unpadded_box.svelte-3w3rth{margin-top:0}.small_parent.svelte-3w3rth{min-height:100%!important}.dropdown-arrow.svelte-145leq6,.dropdown-arrow.svelte-ihhdbf{fill:currentColor}.circle.svelte-ihhdbf{fill:currentColor;opacity:.1}svg.svelte-pb9pol{animation:svelte-pb9pol-spin 1.5s linear infinite}@keyframes svelte-pb9pol-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}h2.svelte-1xg7h5n{font-size:var(--text-xl)!important}p.svelte-1xg7h5n,h2.svelte-1xg7h5n{white-space:pre-line}.wrap.svelte-1xg7h5n{display:flex;flex-direction:column;justify-content:center;align-items:center;min-height:var(--size-60);color:var(--block-label-text-color);line-height:var(--line-md);height:100%;padding-top:var(--size-3);text-align:center;margin:auto var(--spacing-lg)}.or.svelte-1xg7h5n{color:var(--body-text-color-subdued);display:flex}.icon-wrap.svelte-1xg7h5n{width:30px;margin-bottom:var(--spacing-lg)}@media (--screen-md){.wrap.svelte-1xg7h5n{font-size:var(--text-lg)}}.hovered.svelte-1xg7h5n{color:var(--color-accent)}div.svelte-q32hvf{border-top:1px solid transparent;display:flex;max-height:100%;justify-content:center;align-items:center;gap:var(--spacing-sm);height:auto;align-items:flex-end;color:var(--block-label-text-color);flex-shrink:0}.show_border.svelte-q32hvf{border-top:1px solid var(--block-border-color);margin-top:var(--spacing-xxl);box-shadow:var(--shadow-drop)}.source-selection.svelte-15ls1gu{display:flex;align-items:center;justify-content:center;border-top:1px solid var(--border-color-primary);width:100%;margin-left:auto;margin-right:auto;height:var(--size-10)}.icon.svelte-15ls1gu{width:22px;height:22px;margin:var(--spacing-lg) var(--spacing-xs);padding:var(--spacing-xs);color:var(--neutral-400);border-radius:var(--radius-md)}.selected.svelte-15ls1gu{color:var(--color-accent)}.icon.svelte-15ls1gu:hover,.icon.svelte-15ls1gu:focus{color:var(--color-accent)}.icon-button-wrapper.svelte-109se4{display:flex;flex-direction:row;align-items:center;justify-content:center;z-index:var(--layer-3);gap:var(--spacing-sm);box-shadow:var(--shadow-drop);border:1px solid var(--border-color-primary);background:var(--block-background-fill);padding:var(--spacing-xxs)}.icon-button-wrapper.hide-top-corner.svelte-109se4{border-top:none;border-right:none;border-radius:var(--block-label-right-radius)}.icon-button-wrapper.display-top-corner.svelte-109se4{border-radius:var(--radius-sm) 0 0 var(--radius-sm);top:var(--spacing-sm);right:-1px}.icon-button-wrapper.svelte-109se4:not(.top-panel){border:1px solid var(--border-color-primary);border-radius:var(--radius-sm)}.top-panel.svelte-109se4{position:absolute;top:var(--block-label-margin);right:var(--block-label-margin);margin:0}.icon-button-wrapper.svelte-109se4 button{margin:var(--spacing-xxs);border-radius:var(--radius-xs);position:relative}.icon-button-wrapper.svelte-109se4 a.download-link:not(:last-child),.icon-button-wrapper.svelte-109se4 button:not(:last-child){margin-right:var(--spacing-xxs)}.icon-button-wrapper.svelte-109se4 a.download-link:not(:last-child):not(.no-border *):after,.icon-button-wrapper.svelte-109se4 button:not(:last-child):not(.no-border *):after{content:"";position:absolute;right:-4.5px;top:15%;height:70%;width:1px;background-color:var(--border-color-primary)}.icon-button-wrapper.svelte-109se4>*{height:100%}svg.svelte-43sxxs.svelte-43sxxs{width:var(--size-20);height:var(--size-20)}svg.svelte-43sxxs path.svelte-43sxxs{fill:var(--loader-color)}div.svelte-43sxxs.svelte-43sxxs{z-index:var(--layer-2)}.margin.svelte-43sxxs.svelte-43sxxs{margin:var(--size-4)}.wrap.svelte-17v219f.svelte-17v219f{display:flex;flex-direction:column;justify-content:center;align-items:center;z-index:var(--layer-2);transition:opacity .1s ease-in-out;border-radius:var(--block-radius);background:var(--block-background-fill);padding:0 var(--size-6);max-height:var(--size-screen-h);overflow:hidden}.wrap.center.svelte-17v219f.svelte-17v219f{top:0;right:0;left:0}.wrap.default.svelte-17v219f.svelte-17v219f{top:0;right:0;bottom:0;left:0}.hide.svelte-17v219f.svelte-17v219f{opacity:0;pointer-events:none}.generating.svelte-17v219f.svelte-17v219f{animation:svelte-17v219f-pulseStart 1s cubic-bezier(.4,0,.6,1),svelte-17v219f-pulse 2s cubic-bezier(.4,0,.6,1) 1s infinite;border:2px solid var(--color-accent);background:transparent;z-index:var(--layer-1);pointer-events:none}.translucent.svelte-17v219f.svelte-17v219f{background:none}@keyframes svelte-17v219f-pulseStart{0%{opacity:0}to{opacity:1}}@keyframes svelte-17v219f-pulse{0%,to{opacity:1}50%{opacity:.5}}.loading.svelte-17v219f.svelte-17v219f{z-index:var(--layer-2);color:var(--body-text-color)}.eta-bar.svelte-17v219f.svelte-17v219f{position:absolute;top:0;right:0;bottom:0;left:0;transform-origin:left;opacity:.8;z-index:var(--layer-1);transition:10ms;background:var(--background-fill-secondary)}.progress-bar-wrap.svelte-17v219f.svelte-17v219f{border:1px solid var(--border-color-primary);background:var(--background-fill-primary);width:55.5%;height:var(--size-4)}.progress-bar.svelte-17v219f.svelte-17v219f{transform-origin:left;background-color:var(--loader-color);width:var(--size-full);height:var(--size-full)}.progress-level.svelte-17v219f.svelte-17v219f{display:flex;flex-direction:column;align-items:center;gap:1;z-index:var(--layer-2);width:var(--size-full)}.progress-level-inner.svelte-17v219f.svelte-17v219f{margin:var(--size-2) auto;color:var(--body-text-color);font-size:var(--text-sm);font-family:var(--font-mono)}.meta-text.svelte-17v219f.svelte-17v219f{position:absolute;bottom:0;right:0;z-index:var(--layer-2);padding:var(--size-1) var(--size-2);font-size:var(--text-sm);font-family:var(--font-mono)}.meta-text-center.svelte-17v219f.svelte-17v219f{display:flex;position:absolute;top:0;right:0;justify-content:center;align-items:center;transform:translateY(var(--size-6));z-index:var(--layer-2);padding:var(--size-1) var(--size-2);font-size:var(--text-sm);font-family:var(--font-mono);text-align:center}.error.svelte-17v219f.svelte-17v219f{box-shadow:var(--shadow-drop);border:solid 1px var(--error-border-color);border-radius:var(--radius-full);background:var(--error-background-fill);padding-right:var(--size-4);padding-left:var(--size-4);color:var(--error-text-color);font-weight:var(--weight-semibold);font-size:var(--text-lg);line-height:var(--line-lg);font-family:var(--font)}.minimal.svelte-17v219f.svelte-17v219f{pointer-events:none}.minimal.svelte-17v219f .progress-text.svelte-17v219f{background:var(--block-background-fill)}.border.svelte-17v219f.svelte-17v219f{border:1px solid var(--border-color-primary)}.clear-status.svelte-17v219f.svelte-17v219f{position:absolute;display:flex;top:var(--size-2);right:var(--size-2);justify-content:flex-end;gap:var(--spacing-sm);z-index:var(--layer-1)}.toast-body.svelte-1pgj5gs{display:flex;position:relative;right:0;left:0;align-items:center;margin:var(--size-6) var(--size-4);margin:auto;border-radius:var(--container-radius);overflow:hidden;pointer-events:auto}.toast-body.error.svelte-1pgj5gs{border:1px solid var(--color-red-700);background:var(--color-red-50)}.dark .toast-body.error.svelte-1pgj5gs{border:1px solid var(--color-red-500);background-color:var(--color-grey-950)}.toast-body.warning.svelte-1pgj5gs{border:1px solid var(--color-yellow-700);background:var(--color-yellow-50)}.dark .toast-body.warning.svelte-1pgj5gs{border:1px solid var(--color-yellow-500);background-color:var(--color-grey-950)}.toast-body.info.svelte-1pgj5gs{border:1px solid var(--color-grey-700);background:var (--color-grey-50)}.dark .toast-body.info.svelte-1pgj5gs{border:1px solid var(--color-grey-500);background-color:var(--color-grey-950)}.toast-body.success.svelte-1pgj5gs{border:1px solid var(--color-green-700);background:var(--color-green-50)}.dark .toast-body.success.svelte-1pgj5gs{border:1px solid var(--color-green-500);background-color:var(--color-grey-950)}.toast-title.svelte-1pgj5gs{display:flex;align-items:center;font-weight:var(--weight-bold);font-size:var(--text-lg);line-height:var(--line-sm)}.toast-title.error.svelte-1pgj5gs{color:var(--color-red-700)}.dark .toast-title.error.svelte-1pgj5gs{color:var(--color-red-50)}.toast-title.warning.svelte-1pgj5gs{color:var(--color-yellow-700)}.dark .toast-title.warning.svelte-1pgj5gs{color:var(--color-yellow-50)}.toast-title.info.svelte-1pgj5gs{color:var(--color-grey-700)}.dark .toast-title.info.svelte-1pgj5gs{color:var(--color-grey-50)}.toast-title.success.svelte-1pgj5gs{color:var(--color-green-700)}.dark .toast-title.success.svelte-1pgj5gs{color:var(--color-green-50)}.toast-close.svelte-1pgj5gs{margin:0 var(--size-3);border-radius:var(--size-3);padding:0px var(--size-1-5);font-size:var(--size-5);line-height:var(--size-5)}.toast-close.error.svelte-1pgj5gs{color:var(--color-red-700)}.dark .toast-close.error.svelte-1pgj5gs{color:var(--color-red-500)}.toast-close.warning.svelte-1pgj5gs{color:var(--color-yellow-700)}.dark .toast-close.warning.svelte-1pgj5gs{color:var(--color-yellow-500)}.toast-close.info.svelte-1pgj5gs{color:var(--color-grey-700)}.dark .toast-close.info.svelte-1pgj5gs{color:var(--color-grey-500)}.toast-close.success.svelte-1pgj5gs{color:var(--color-green-700)}.dark .toast-close.success.svelte-1pgj5gs{color:var(--color-green-500)}.toast-text.svelte-1pgj5gs{font-size:var(--text-lg);word-wrap:break-word;overflow-wrap:break-word;word-break:break-word}.toast-text.error.svelte-1pgj5gs{color:var(--color-red-700)}.dark .toast-text.error.svelte-1pgj5gs{color:var(--color-red-50)}.toast-text.warning.svelte-1pgj5gs{color:var(--color-yellow-700)}.dark .toast-text.warning.svelte-1pgj5gs{color:var(--color-yellow-50)}.toast-text.info.svelte-1pgj5gs{color:var(--color-grey-700)}.dark .toast-text.info.svelte-1pgj5gs{color:var(--color-grey-50)}.toast-text.success.svelte-1pgj5gs{color:var(--color-green-700)}.dark .toast-text.success.svelte-1pgj5gs{color:var(--color-green-50)}.toast-details.svelte-1pgj5gs{margin:var(--size-3) var(--size-3) var(--size-3) 0;width:100%}.toast-icon.svelte-1pgj5gs{display:flex;position:absolute;position:relative;flex-shrink:0;justify-content:center;align-items:center;margin:var(--size-2);border-radius:var(--radius-full);padding:var(--size-1);padding-left:calc(var(--size-1) - 1px);width:35px;height:35px}.toast-icon.error.svelte-1pgj5gs{color:var(--color-red-700)}.dark .toast-icon.error.svelte-1pgj5gs{color:var(--color-red-500)}.toast-icon.warning.svelte-1pgj5gs{color:var(--color-yellow-700)}.dark .toast-icon.warning.svelte-1pgj5gs{color:var(--color-yellow-500)}.toast-icon.info.svelte-1pgj5gs{color:var(--color-grey-700)}.dark .toast-icon.info.svelte-1pgj5gs{color:var(--color-grey-500)}.toast-icon.success.svelte-1pgj5gs{color:var(--color-green-700)}.dark .toast-icon.success.svelte-1pgj5gs{color:var(--color-green-500)}@keyframes svelte-1pgj5gs-countdown{0%{transform:scaleX(1)}to{transform:scaleX(0)}}.timer.svelte-1pgj5gs{position:absolute;bottom:0;left:0;transform-origin:0 0;animation:svelte-1pgj5gs-countdown 10s linear forwards;width:100%;height:var(--size-1)}.timer.error.svelte-1pgj5gs{background:var(--color-red-700)}.dark .timer.error.svelte-1pgj5gs{background:var(--color-red-500)}.timer.warning.svelte-1pgj5gs{background:var(--color-yellow-700)}.dark .timer.warning.svelte-1pgj5gs{background:var(--color-yellow-500)}.timer.info.svelte-1pgj5gs{background:var(--color-grey-700)}.dark .timer.info.svelte-1pgj5gs{background:var(--color-grey-500)}.timer.success.svelte-1pgj5gs{background:var(--color-green-700)}.dark .timer.success.svelte-1pgj5gs{background:var(--color-green-500)}.hidden.svelte-1pgj5gs{display:none}.toast-text.svelte-1pgj5gs a{text-decoration:underline}.toast-wrap.svelte-gatr8h{display:flex;position:fixed;top:var(--size-4);right:var(--size-4);flex-direction:column;align-items:end;gap:var(--size-2);z-index:var(--layer-top);width:calc(100% - var(--size-8))}@media (--screen-sm){.toast-wrap.svelte-gatr8h{width:calc(var(--size-96) + var(--size-10))}}.streaming-bar.svelte-ga0jj6{position:absolute;bottom:0;left:0;right:0;height:4px;background-color:var(--primary-600);animation:svelte-ga0jj6-countdown linear forwards;z-index:1}@keyframes svelte-ga0jj6-countdown{0%{transform:translate(0)}to{transform:translate(-100%)}}:host{display:flex;flex-direction:column;height:100%}.propertysheet-wrapper{overflow:hidden!important;display:flex;flex-direction:column;flex-grow:1}.accordion-header.svelte-1nz1lmm.svelte-1nz1lmm{display:flex;justify-content:space-between;align-items:center;width:100%;cursor:pointer;padding:var(--block-title-padding);background:var(--block-title-background-fill);color:var(--block-title-text-color);flex-shrink:0}.content-wrapper.svelte-1nz1lmm.svelte-1nz1lmm{flex-grow:1;min-height:0}.container.svelte-1nz1lmm.svelte-1nz1lmm{overflow-y:auto;height:auto;max-height:var(--sheet-max-height, 500px);border-radius:0!important;border:1px solid var(--border-color-primary);border-top:none;border-bottom-left-radius:var(--radius-lg);border-bottom-right-radius:var(--radius-lg);background-color:var(--background-fill-secondary)}.closed.svelte-1nz1lmm.svelte-1nz1lmm{display:none}.group-header.svelte-1nz1lmm.svelte-1nz1lmm{display:flex;justify-content:space-between;align-items:center;width:100%;padding:var(--spacing-sm) var(--spacing-md);background-color:var(--input-background-fill);color:var(--body-text-color);text-align:left;cursor:pointer;font-size:var(--text-md);font-weight:var(--font-weight-bold);border:1px solid var(--border-color-primary)}.properties-grid.svelte-1nz1lmm.svelte-1nz1lmm{display:grid;grid-template-columns:1fr 2fr;gap:0;padding:0}.prop-label.svelte-1nz1lmm.svelte-1nz1lmm,.prop-control.svelte-1nz1lmm.svelte-1nz1lmm{padding:var(--spacing-sm) var(--spacing-md);display:flex;align-items:center;border-bottom:1px solid var(--background-fill-secondary)}.prop-label.svelte-1nz1lmm.svelte-1nz1lmm{background-color:var(--background-fill-primary);color:var(--body-text-color);opacity:.7;font-weight:var(--font-weight-semibold);font-size:var(--text-xs);text-align:right;justify-content:flex-end;word-break:break-word}.prop-control.svelte-1nz1lmm.svelte-1nz1lmm{gap:var(--spacing-sm)}.properties-grid.svelte-1nz1lmm>.svelte-1nz1lmm:nth-last-child(-n+2){border-bottom:none}.prop-control.svelte-1nz1lmm input[type=text].svelte-1nz1lmm,.prop-control.svelte-1nz1lmm input[type=number].svelte-1nz1lmm{background-color:var(--input-background-fill);border:var(--input-border-width) solid var(--border-color-primary);box-shadow:var(--input-shadow);color:var(--input-text-color);font-size:var(--input-text-size);border-radius:0;width:100%;padding-top:var(--spacing-1);padding-bottom:var(--spacing-1);padding-left:var(--spacing-md);padding-right:var(--spacing-3)}.prop-control.svelte-1nz1lmm input[type=text].svelte-1nz1lmm:focus,.prop-control.svelte-1nz1lmm input[type=number].svelte-1nz1lmm:focus{box-shadow:var(--input-shadow-focus);border-color:var(--input-border-color-focus);background-color:var(--input-background-fill-focus);outline:none}.dropdown-wrapper.svelte-1nz1lmm.svelte-1nz1lmm{position:relative;width:100%}.dropdown-wrapper.svelte-1nz1lmm select.svelte-1nz1lmm{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:var(--input-background-fill);border:var(--input-border-width) solid var(--border-color-primary);box-shadow:var(--input-shadow);color:var(--input-text-color);font-size:var(--input-text-size);width:100%;cursor:pointer;border-radius:0;padding-top:var(--spacing-1);padding-bottom:var(--spacing-1);padding-left:var(--spacing-md);padding-right:calc(var(--spacing-3) + 1.2em)}.dropdown-arrow-icon.svelte-1nz1lmm.svelte-1nz1lmm{position:absolute;top:50%;right:var(--spacing-3);transform:translateY(-50%);width:1em;height:1em;pointer-events:none;z-index:1;background-color:var(--body-text-color-subdued);-webkit-mask-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='currentColor' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M6 8l4 4 4-4'/%3e%3c/svg%3e");mask-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='currentColor' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M6 8l4 4 4-4'%3e%3c/svg%3e")}.dropdown-wrapper.svelte-1nz1lmm select.svelte-1nz1lmm:focus{box-shadow:var(--input-shadow-focus);border-color:var(--input-border-color-focus);background-color:var(--input-background-fill-focus);outline:none}.dropdown-wrapper.svelte-1nz1lmm select option.svelte-1nz1lmm{background:var(--input-background-fill);color:var(--body-text-color)}.prop-control.svelte-1nz1lmm input[type=checkbox].svelte-1nz1lmm{-webkit-appearance:none;-moz-appearance:none;appearance:none;position:relative;width:var(--size-4);height:var(--size-4);border-radius:0;border:1px solid var(--checkbox-border-color);background-color:var(--checkbox-background-color);box-shadow:var(--checkbox-shadow);cursor:pointer;margin:0;transition:background-color .2s,border-color .2s}.prop-control.svelte-1nz1lmm input[type=checkbox].svelte-1nz1lmm:hover{border-color:var(--checkbox-border-color-hover);background-color:var(--checkbox-background-color-hover)}.prop-control.svelte-1nz1lmm input[type=checkbox].svelte-1nz1lmm:focus{border-color:var(--checkbox-border-color-focus);background-color:var(--checkbox-background-color-focus);outline:none}.prop-control.svelte-1nz1lmm input[type=checkbox].svelte-1nz1lmm:checked{background-color:var(--checkbox-background-color-selected);border-color:var(--checkbox-border-color-focus)}.prop-control.svelte-1nz1lmm input[type=checkbox].svelte-1nz1lmm:checked:after{content:"";position:absolute;display:block;top:50%;left:50%;width:4px;height:8px;border:solid var(--checkbox-label-text-color-selected);border-width:0 2px 2px 0;transform:translate(-50%,-60%) rotate(45deg)}.slider-container.svelte-1nz1lmm.svelte-1nz1lmm{display:flex;align-items:center;gap:var(--spacing-md);width:100%}.slider-container.svelte-1nz1lmm input[type=range].svelte-1nz1lmm{--slider-progress:0%;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;cursor:pointer;width:100%}.slider-container.svelte-1nz1lmm input[type=range].svelte-1nz1lmm::-webkit-slider-runnable-track{height:8px;border-radius:var(--radius-lg);background:linear-gradient(to right,var(--slider-color) var(--slider-progress),var(--input-background-fill) var(--slider-progress))}.slider-container.svelte-1nz1lmm input[type=range].svelte-1nz1lmm::-webkit-slider-thumb{-webkit-appearance:none;-moz-appearance:none;appearance:none;margin-top:-4px;background-color:#fff;border-radius:50%;height:16px;width:16px;border:1px solid var(--border-color-primary);box-shadow:var(--shadow-drop)}.slider-container.svelte-1nz1lmm input[type=range].svelte-1nz1lmm::-moz-range-track{height:8px;border-radius:var(--radius-lg);background:linear-gradient(to right,var(--slider-color) var(--slider-progress),var(--input-background-fill) var(--slider-progress))}.slider-container.svelte-1nz1lmm input[type=range].svelte-1nz1lmm::-moz-range-thumb{background-color:#fff;border-radius:50%;height:16px;width:16px;border:1px solid var(--border-color-primary);box-shadow:var(--shadow-drop)}.slider-value.svelte-1nz1lmm.svelte-1nz1lmm{min-width:40px;text-align:right;font-family:var(--font-mono);font-size:var(--text-xs)}.prop-label-wrapper.svelte-1nz1lmm.svelte-1nz1lmm{display:flex;justify-content:flex-end;align-items:center;gap:var(--spacing-sm);width:100%}.tooltip-container.svelte-1nz1lmm.svelte-1nz1lmm{position:relative;display:inline-flex;align-items:center;justify-content:center}.tooltip-icon.svelte-1nz1lmm.svelte-1nz1lmm{display:flex;align-items:center;justify-content:center;width:14px;height:14px;border-radius:50%;background-color:var(--body-text-color-subdued);color:var(--background-fill-primary);font-size:10px;font-weight:700;cursor:help;-webkit-user-select:none;user-select:none}.tooltip-text.svelte-1nz1lmm.svelte-1nz1lmm{visibility:hidden;width:200px;background-color:var(--body-text-color);color:var(--background-fill-primary);text-align:center;border-radius:var(--radius-md);padding:var(--spacing-md);position:absolute;z-index:10;bottom:125%;left:50%;transform:translate(-50%);opacity:0;transition:opacity .3s}.tooltip-container.svelte-1nz1lmm:hover .tooltip-text.svelte-1nz1lmm{visibility:visible;opacity:1}.color-picker-container.svelte-1nz1lmm.svelte-1nz1lmm{display:flex;align-items:center;gap:var(--spacing-md);width:100%}.color-picker-input.svelte-1nz1lmm.svelte-1nz1lmm{width:50px;height:28px;background-color:transparent;border:1px solid var(--border-color-primary);border-radius:var(--radius-sm);cursor:pointer;padding:0}.color-picker-input.svelte-1nz1lmm.svelte-1nz1lmm::-webkit-color-swatch-wrapper{padding:2px}.color-picker-input.svelte-1nz1lmm.svelte-1nz1lmm::-moz-padding{padding:2px}.color-picker-input.svelte-1nz1lmm.svelte-1nz1lmm::-webkit-color-swatch{border:none;border-radius:var(--radius-sm)}.color-picker-input.svelte-1nz1lmm.svelte-1nz1lmm::-moz-color-swatch{border:none;border-radius:var(--radius-sm)}.color-picker-value.svelte-1nz1lmm.svelte-1nz1lmm{font-family:var(--font-mono);font-size:var(--text-sm);color:var(--body-text-color-subdued)}.prop-control.svelte-1nz1lmm input.invalid.svelte-1nz1lmm{border-color:var(--error-border-color, red)!important;box-shadow:0 0 0 1px var(--error-border-color, red)!important}.reset-button-prop.svelte-1nz1lmm.svelte-1nz1lmm{display:flex;align-items:center;justify-content:center;background:none;border:none;border-left:1px solid var(--border-color-primary);cursor:pointer;color:var(--body-text-color-subdued);font-size:var(--text-lg);padding:0 var(--spacing-2);visibility:hidden;opacity:0;transition:opacity .15s ease-in-out,color .15s ease-in-out}.reset-button-prop.visible.svelte-1nz1lmm.svelte-1nz1lmm{visibility:visible;opacity:1}.reset-button-prop.svelte-1nz1lmm.svelte-1nz1lmm:hover{color:var(--body-text-color);background-color:var(--background-fill-secondary-hover)}.reset-button-prop.svelte-1nz1lmm.svelte-1nz1lmm:disabled{color:var(--body-text-color-subdued)!important;opacity:.5;cursor:not-allowed;background-color:transparent!important}.prop-control.svelte-1nz1lmm .disabled.svelte-1nz1lmm{opacity:.5;pointer-events:none;cursor:not-allowed}.prop-control.svelte-1nz1lmm .disabled input.svelte-1nz1lmm{cursor:not-allowed}.reset-button-prop.svelte-1nz1lmm.svelte-1nz1lmm:disabled{opacity:.3;cursor:not-allowed;background-color:transparent!important}
|
src/demo/app.py
CHANGED
|
@@ -3,115 +3,55 @@ from dataclasses import dataclass, field, asdict
|
|
| 3 |
from typing import Literal
|
| 4 |
from gradio_propertysheet import PropertySheet
|
| 5 |
|
| 6 |
-
# ---
|
|
|
|
| 7 |
@dataclass
|
| 8 |
class ModelSettings:
|
| 9 |
"""Settings for loading models, VAEs, etc."""
|
| 10 |
-
model_type: Literal["SD 1.5", "SDXL", "Pony", "Custom"] = field(
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
)
|
| 14 |
-
custom_model_path: str = field(
|
| 15 |
-
default="/path/to/default.safetensors",
|
| 16 |
-
metadata={"label": "Custom Model Path", "interactive_if": {"field": "model_type", "value": "Custom"}}
|
| 17 |
-
)
|
| 18 |
-
vae_path: str = field(
|
| 19 |
-
default="",
|
| 20 |
-
metadata={"label": "VAE Path (optional)"}
|
| 21 |
-
)
|
| 22 |
|
| 23 |
@dataclass
|
| 24 |
class SamplingSettings:
|
| 25 |
"""Settings for the image sampling process."""
|
| 26 |
-
sampler_name: Literal["Euler", "Euler a", "DPM++ 2M Karras", "UniPC"] = field(
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
)
|
| 30 |
-
steps: int = field(
|
| 31 |
-
default=25,
|
| 32 |
-
metadata={"component": "slider", "minimum": 1, "maximum": 150, "step": 1, "label": "Sampling Steps", "help": "More steps can improve quality."}
|
| 33 |
-
)
|
| 34 |
-
cfg_scale: float = field(
|
| 35 |
-
default=7.0,
|
| 36 |
-
metadata={"component": "slider", "minimum": 1.0, "maximum": 30.0, "step": 0.5, "label": "CFG Scale", "help": "How strongly the prompt is adhered to."}
|
| 37 |
-
)
|
| 38 |
|
| 39 |
@dataclass
|
| 40 |
class ImageSettings:
|
| 41 |
"""Settings for image dimensions."""
|
| 42 |
-
width: int = field(
|
| 43 |
-
|
| 44 |
-
metadata={"component": "slider", "minimum": 512, "maximum": 2048, "step": 64, "label": "Image Width"}
|
| 45 |
-
)
|
| 46 |
-
height: int = field(
|
| 47 |
-
default=1024,
|
| 48 |
-
metadata={"component": "slider", "minimum": 512, "maximum": 2048, "step": 64, "label": "Image Height"}
|
| 49 |
-
)
|
| 50 |
|
| 51 |
@dataclass
|
| 52 |
class PostprocessingSettings:
|
| 53 |
"""Settings for image post-processing effects."""
|
| 54 |
-
restore_faces: bool = field(
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
)
|
| 58 |
-
enable_hr: bool = field(
|
| 59 |
-
default=False,
|
| 60 |
-
metadata={"label": "Hires. fix", "help": "Enable a second pass at a higher resolution."}
|
| 61 |
-
)
|
| 62 |
-
denoising_strength: float = field(
|
| 63 |
-
default=0.45,
|
| 64 |
-
metadata={"component": "slider", "minimum": 0.0, "maximum": 1.0, "step": 0.01, "label": "Denoising Strength", "interactive_if": {"field": "enable_hr", "value": True}}
|
| 65 |
-
)
|
| 66 |
|
| 67 |
@dataclass
|
| 68 |
class AdvancedSettings:
|
| 69 |
"""Advanced and rarely changed settings."""
|
| 70 |
-
clip_skip: int = field(
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
)
|
| 74 |
-
noise_schedule: Literal["Default", "Karras", "Exponential"] = field(
|
| 75 |
-
default="Karras",
|
| 76 |
-
metadata={"component": "dropdown", "label": "Noise Schedule"}
|
| 77 |
-
)
|
| 78 |
-
do_not_scale_cond_uncond: bool = field(
|
| 79 |
-
default=False,
|
| 80 |
-
metadata={"label": "Do not scale cond/uncond"}
|
| 81 |
-
)
|
| 82 |
-
s_churn: int = field(
|
| 83 |
-
default=1,
|
| 84 |
-
metadata={"component": "number_integer", "minimum": 1, "maximum": 12, "label": "S_churn", "help": "S_churn value for generation."}
|
| 85 |
-
)
|
| 86 |
|
| 87 |
@dataclass
|
| 88 |
class ScriptSettings:
|
| 89 |
"""Settings for automation scripts like X/Y/Z plots."""
|
| 90 |
-
script_name: Literal["None", "Prompt matrix", "X/Y/Z plot"] = field(
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
)
|
| 94 |
-
x_values: str = field(
|
| 95 |
-
default="-1, 10, 20",
|
| 96 |
-
metadata={"label": "X axis values", "interactive_if": {"field": "script_name", "value": "X/Y/Z plot"}}
|
| 97 |
-
)
|
| 98 |
-
y_values: str = field(
|
| 99 |
-
default="",
|
| 100 |
-
metadata={"label": "Y axis values", "interactive_if": {"field": "script_name", "value": "X/Y/Z plot"}}
|
| 101 |
-
)
|
| 102 |
|
| 103 |
@dataclass
|
| 104 |
class RenderConfig:
|
| 105 |
"""Main configuration object for rendering, grouping all settings."""
|
| 106 |
-
seed: int = field(
|
| 107 |
-
|
| 108 |
-
metadata={"component": "number_integer", "label": "Seed (-1 for random)", "help": "The random seed for generation."}
|
| 109 |
-
)
|
| 110 |
-
batch_size: int = field(
|
| 111 |
-
default=1,
|
| 112 |
-
metadata={"component": "slider", "minimum": 1, "maximum": 8, "step": 1, "label": "Batch Size"}
|
| 113 |
-
)
|
| 114 |
-
# Nested groups
|
| 115 |
model: ModelSettings = field(default_factory=ModelSettings)
|
| 116 |
sampling: SamplingSettings = field(default_factory=SamplingSettings)
|
| 117 |
image: ImageSettings = field(default_factory=ImageSettings)
|
|
@@ -119,7 +59,6 @@ class RenderConfig:
|
|
| 119 |
scripts: ScriptSettings = field(default_factory=ScriptSettings)
|
| 120 |
advanced: AdvancedSettings = field(default_factory=AdvancedSettings)
|
| 121 |
|
| 122 |
-
|
| 123 |
@dataclass
|
| 124 |
class Lighting:
|
| 125 |
"""Lighting settings for the environment."""
|
|
@@ -133,8 +72,8 @@ class EnvironmentConfig:
|
|
| 133 |
background: Literal["Sky", "Color", "Image"] = field(default="Sky", metadata={"component": "dropdown"})
|
| 134 |
lighting: Lighting = field(default_factory=Lighting)
|
| 135 |
|
| 136 |
-
|
| 137 |
# --- Initial Instances ---
|
|
|
|
| 138 |
initial_render_config = RenderConfig()
|
| 139 |
initial_env_config = EnvironmentConfig()
|
| 140 |
|
|
@@ -144,65 +83,117 @@ with gr.Blocks(title="PropertySheet Demo") as demo:
|
|
| 144 |
gr.Markdown("An example of a realistic application layout using the `PropertySheet` component as a sidebar for settings.")
|
| 145 |
gr.Markdown("<span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_propertysheet'>Component GitHub Code</a></span>")
|
| 146 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
with gr.Row():
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
#gr.Image(label="Main Viewport", height=500, value=None)
|
| 151 |
-
gr.Textbox(label="AI Prompt", lines=3, placeholder="Enter your prompt here...")
|
| 152 |
-
gr.Button("Generate", variant="primary")
|
| 153 |
with gr.Row():
|
| 154 |
output_render_json = gr.JSON(label="Live Render State")
|
| 155 |
output_env_json = gr.JSON(label="Live Environment State")
|
| 156 |
|
| 157 |
-
# Sidebar with Property Sheets on the right
|
| 158 |
with gr.Column(scale=1):
|
| 159 |
render_sheet = PropertySheet(
|
| 160 |
-
value=initial_render_config,
|
| 161 |
label="Render Settings",
|
| 162 |
width=400,
|
| 163 |
-
height=550
|
|
|
|
| 164 |
)
|
| 165 |
environment_sheet = PropertySheet(
|
| 166 |
value=initial_env_config,
|
| 167 |
label="Environment Settings",
|
| 168 |
width=400,
|
| 169 |
-
open=False
|
|
|
|
| 170 |
)
|
| 171 |
|
| 172 |
# --- Event Handlers ---
|
| 173 |
-
def
|
| 174 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
if updated_config is None:
|
| 176 |
-
return
|
| 177 |
|
| 178 |
-
# Example of business logic
|
| 179 |
if updated_config.model.model_type != "Custom":
|
| 180 |
updated_config.model.custom_model_path = "/path/to/default.safetensors"
|
| 181 |
-
|
| 182 |
-
return updated_config, asdict(updated_config)
|
| 183 |
|
| 184 |
-
def handle_env_change(updated_config: EnvironmentConfig | None):
|
| 185 |
-
"""
|
| 186 |
if updated_config is None:
|
| 187 |
-
return
|
| 188 |
-
return updated_config, asdict(updated_config)
|
| 189 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
render_sheet.change(
|
| 191 |
fn=handle_render_change,
|
| 192 |
-
inputs=[render_sheet],
|
| 193 |
-
outputs=[render_sheet, output_render_json]
|
| 194 |
)
|
|
|
|
|
|
|
| 195 |
environment_sheet.change(
|
| 196 |
fn=handle_env_change,
|
| 197 |
-
inputs=[environment_sheet],
|
| 198 |
-
outputs=[environment_sheet, output_env_json]
|
| 199 |
)
|
| 200 |
|
| 201 |
-
# Load initial
|
| 202 |
demo.load(
|
| 203 |
fn=lambda: (asdict(initial_render_config), asdict(initial_env_config)),
|
| 204 |
outputs=[output_render_json, output_env_json]
|
| 205 |
)
|
| 206 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
if __name__ == "__main__":
|
| 208 |
demo.launch()
|
|
|
|
| 3 |
from typing import Literal
|
| 4 |
from gradio_propertysheet import PropertySheet
|
| 5 |
|
| 6 |
+
# --- Configuration Data Models ---
|
| 7 |
+
# These dataclasses define the structure for all settings panels.
|
| 8 |
@dataclass
|
| 9 |
class ModelSettings:
|
| 10 |
"""Settings for loading models, VAEs, etc."""
|
| 11 |
+
model_type: Literal["SD 1.5", "SDXL", "Pony", "Custom"] = field(default="SDXL", metadata={"component": "dropdown", "label": "Base Model"})
|
| 12 |
+
custom_model_path: str = field(default="/path/to/default.safetensors", metadata={"label": "Custom Model Path", "interactive_if": {"field": "model_type", "value": "Custom"}})
|
| 13 |
+
vae_path: str = field(default="", metadata={"label": "VAE Path (optional)"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
@dataclass
|
| 16 |
class SamplingSettings:
|
| 17 |
"""Settings for the image sampling process."""
|
| 18 |
+
sampler_name: Literal["Euler", "Euler a", "DPM++ 2M Karras", "UniPC"] = field(default="DPM++ 2M Karras", metadata={"component": "dropdown", "label": "Sampler", "help": "The algorithm for the diffusion process."})
|
| 19 |
+
steps: int = field(default=25, metadata={"component": "slider", "minimum": 1, "maximum": 150, "step": 1, "label": "Sampling Steps", "help": "More steps can improve quality."})
|
| 20 |
+
cfg_scale: float = field(default=7.0, metadata={"component": "slider", "minimum": 1.0, "maximum": 30.0, "step": 0.5, "label": "CFG Scale", "help": "How strongly the prompt is adhered to."})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
@dataclass
|
| 23 |
class ImageSettings:
|
| 24 |
"""Settings for image dimensions."""
|
| 25 |
+
width: int = field(default=1024, metadata={"component": "slider", "minimum": 512, "maximum": 2048, "step": 64, "label": "Image Width"})
|
| 26 |
+
height: int = field(default=1024, metadata={"component": "slider", "minimum": 512, "maximum": 2048, "step": 64, "label": "Image Height"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
@dataclass
|
| 29 |
class PostprocessingSettings:
|
| 30 |
"""Settings for image post-processing effects."""
|
| 31 |
+
restore_faces: bool = field(default=True, metadata={"label": "Restore Faces", "help": "Use a secondary model to fix distorted faces."})
|
| 32 |
+
enable_hr: bool = field(default=False, metadata={"label": "Hires. fix", "help": "Enable a second pass at a higher resolution."})
|
| 33 |
+
denoising_strength: float = field(default=0.45, metadata={"component": "slider", "minimum": 0.0, "maximum": 1.0, "step": 0.01, "label": "Denoising Strength", "interactive_if": {"field": "enable_hr", "value": True}})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
@dataclass
|
| 36 |
class AdvancedSettings:
|
| 37 |
"""Advanced and rarely changed settings."""
|
| 38 |
+
clip_skip: int = field(default=2, metadata={"component": "slider", "minimum": 1, "maximum": 12, "step": 1, "label": "CLIP Skip", "help": "Skip final layers of the text encoder."})
|
| 39 |
+
noise_schedule: Literal["Default", "Karras", "Exponential"] = field(default="Karras", metadata={"component": "dropdown", "label": "Noise Schedule"})
|
| 40 |
+
do_not_scale_cond_uncond: bool = field(default=False, metadata={"label": "Do not scale cond/uncond"})
|
| 41 |
+
s_churn: int = field(default=1, metadata={"component": "number_integer", "minimum": 1, "maximum": 12, "label": "S_churn", "help": "S_churn value for generation."})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
@dataclass
|
| 44 |
class ScriptSettings:
|
| 45 |
"""Settings for automation scripts like X/Y/Z plots."""
|
| 46 |
+
script_name: Literal["None", "Prompt matrix", "X/Y/Z plot"] = field(default="None", metadata={"component": "dropdown", "label": "Script"})
|
| 47 |
+
x_values: str = field(default="-1, 10, 20", metadata={"label": "X axis values", "interactive_if": {"field": "script_name", "value": "X/Y/Z plot"}})
|
| 48 |
+
y_values: str = field(default="", metadata={"label": "Y axis values", "interactive_if": {"field": "script_name", "value": "X/Y/Z plot"}})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
@dataclass
|
| 51 |
class RenderConfig:
|
| 52 |
"""Main configuration object for rendering, grouping all settings."""
|
| 53 |
+
seed: int = field(default=-1, metadata={"component": "number_integer", "label": "Seed (-1 for random)", "help": "The random seed for generation."})
|
| 54 |
+
batch_size: int = field(default=1, metadata={"component": "slider", "minimum": 1, "maximum": 8, "step": 1, "label": "Batch Size"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
model: ModelSettings = field(default_factory=ModelSettings)
|
| 56 |
sampling: SamplingSettings = field(default_factory=SamplingSettings)
|
| 57 |
image: ImageSettings = field(default_factory=ImageSettings)
|
|
|
|
| 59 |
scripts: ScriptSettings = field(default_factory=ScriptSettings)
|
| 60 |
advanced: AdvancedSettings = field(default_factory=AdvancedSettings)
|
| 61 |
|
|
|
|
| 62 |
@dataclass
|
| 63 |
class Lighting:
|
| 64 |
"""Lighting settings for the environment."""
|
|
|
|
| 72 |
background: Literal["Sky", "Color", "Image"] = field(default="Sky", metadata={"component": "dropdown"})
|
| 73 |
lighting: Lighting = field(default_factory=Lighting)
|
| 74 |
|
|
|
|
| 75 |
# --- Initial Instances ---
|
| 76 |
+
# Create default instances of the configuration objects.
|
| 77 |
initial_render_config = RenderConfig()
|
| 78 |
initial_env_config = EnvironmentConfig()
|
| 79 |
|
|
|
|
| 83 |
gr.Markdown("An example of a realistic application layout using the `PropertySheet` component as a sidebar for settings.")
|
| 84 |
gr.Markdown("<span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_propertysheet'>Component GitHub Code</a></span>")
|
| 85 |
|
| 86 |
+
# --- Persistent State Management ---
|
| 87 |
+
# Use gr.State to hold the application's data. This is the "single source of truth".
|
| 88 |
+
render_state = gr.State(value=initial_render_config)
|
| 89 |
+
env_state = gr.State(value=initial_env_config)
|
| 90 |
+
|
| 91 |
with gr.Row():
|
| 92 |
+
with gr.Column(scale=3):
|
| 93 |
+
generate = gr.Button("Show Settings", variant="primary")
|
|
|
|
|
|
|
|
|
|
| 94 |
with gr.Row():
|
| 95 |
output_render_json = gr.JSON(label="Live Render State")
|
| 96 |
output_env_json = gr.JSON(label="Live Environment State")
|
| 97 |
|
|
|
|
| 98 |
with gr.Column(scale=1):
|
| 99 |
render_sheet = PropertySheet(
|
| 100 |
+
value=initial_render_config,
|
| 101 |
label="Render Settings",
|
| 102 |
width=400,
|
| 103 |
+
height=550,
|
| 104 |
+
visible=False
|
| 105 |
)
|
| 106 |
environment_sheet = PropertySheet(
|
| 107 |
value=initial_env_config,
|
| 108 |
label="Environment Settings",
|
| 109 |
width=400,
|
| 110 |
+
open=False,
|
| 111 |
+
visible=False
|
| 112 |
)
|
| 113 |
|
| 114 |
# --- Event Handlers ---
|
| 115 |
+
def change_visibility(render_config, env_config):
|
| 116 |
+
"""
|
| 117 |
+
Handles the visibility toggle for the property sheets.
|
| 118 |
+
NOTE: This approach of modifying the component object's attribute directly
|
| 119 |
+
is not reliable for tracking state changes in Gradio. A gr.State object is
|
| 120 |
+
the recommended way to manage UI state like visibility.
|
| 121 |
+
"""
|
| 122 |
+
if render_sheet.visible != environment_sheet.visible:
|
| 123 |
+
render_sheet.visible = False
|
| 124 |
+
environment_sheet.visible = False
|
| 125 |
+
|
| 126 |
+
if render_sheet.visible == False and environment_sheet.visible == False:
|
| 127 |
+
render_sheet.visible = True
|
| 128 |
+
environment_sheet.visible = True
|
| 129 |
+
return (
|
| 130 |
+
gr.update(visible=True, value=render_config),
|
| 131 |
+
gr.update(visible=True, value=env_config),
|
| 132 |
+
gr.update(value="Hide Settings")
|
| 133 |
+
)
|
| 134 |
+
else:
|
| 135 |
+
render_sheet.visible = False
|
| 136 |
+
environment_sheet.visible = False
|
| 137 |
+
return (
|
| 138 |
+
gr.update(visible=False, value=render_config),
|
| 139 |
+
gr.update(visible=False, value=env_config),
|
| 140 |
+
gr.update(value="Show Settings")
|
| 141 |
+
)
|
| 142 |
+
|
| 143 |
+
def handle_render_change(updated_config: RenderConfig | None, current_state: RenderConfig):
|
| 144 |
+
"""Processes updates from the render PropertySheet and syncs the state."""
|
| 145 |
if updated_config is None:
|
| 146 |
+
return current_state, asdict(current_state), current_state
|
| 147 |
|
| 148 |
+
# Example of applying business logic
|
| 149 |
if updated_config.model.model_type != "Custom":
|
| 150 |
updated_config.model.custom_model_path = "/path/to/default.safetensors"
|
| 151 |
+
|
| 152 |
+
return updated_config, asdict(updated_config), updated_config
|
| 153 |
|
| 154 |
+
def handle_env_change(updated_config: EnvironmentConfig | None, current_state: EnvironmentConfig):
|
| 155 |
+
"""Processes updates from the environment PropertySheet and syncs the state."""
|
| 156 |
if updated_config is None:
|
| 157 |
+
return current_state, asdict(current_state), current_state
|
| 158 |
+
return updated_config, asdict(updated_config), updated_config
|
| 159 |
|
| 160 |
+
# --- Event Listeners ---
|
| 161 |
+
# Toggle the property sheets' visibility on button click.
|
| 162 |
+
generate.click(
|
| 163 |
+
fn=change_visibility,
|
| 164 |
+
inputs=[render_state, env_state],
|
| 165 |
+
outputs=[render_sheet, environment_sheet, generate]
|
| 166 |
+
)
|
| 167 |
+
|
| 168 |
+
# Syncs changes from the UI back to the state and JSON display.
|
| 169 |
render_sheet.change(
|
| 170 |
fn=handle_render_change,
|
| 171 |
+
inputs=[render_sheet, render_state],
|
| 172 |
+
outputs=[render_sheet, output_render_json, render_state]
|
| 173 |
)
|
| 174 |
+
|
| 175 |
+
# Syncs changes from the UI back to the state and JSON display.
|
| 176 |
environment_sheet.change(
|
| 177 |
fn=handle_env_change,
|
| 178 |
+
inputs=[environment_sheet, env_state],
|
| 179 |
+
outputs=[environment_sheet, output_env_json, env_state]
|
| 180 |
)
|
| 181 |
|
| 182 |
+
# Load initial data into JSON displays on app start.
|
| 183 |
demo.load(
|
| 184 |
fn=lambda: (asdict(initial_render_config), asdict(initial_env_config)),
|
| 185 |
outputs=[output_render_json, output_env_json]
|
| 186 |
)
|
| 187 |
|
| 188 |
+
# Ensure components are populated with state values on load/reload.
|
| 189 |
+
demo.load(
|
| 190 |
+
fn=lambda render_config, env_config: (
|
| 191 |
+
gr.update(value=render_config),
|
| 192 |
+
gr.update(value=env_config)
|
| 193 |
+
),
|
| 194 |
+
inputs=[render_state, env_state],
|
| 195 |
+
outputs=[render_sheet, environment_sheet]
|
| 196 |
+
)
|
| 197 |
+
|
| 198 |
if __name__ == "__main__":
|
| 199 |
demo.launch()
|
src/demo/space.py
CHANGED
|
@@ -3,7 +3,7 @@ import gradio as gr
|
|
| 3 |
from app import demo as app
|
| 4 |
import os
|
| 5 |
|
| 6 |
-
_docs = {'PropertySheet': {'description': 'A Gradio component that renders a dynamic UI from a Python dataclass instance.', 'members': {'__init__': {'value': {'type': 'typing.Optional[typing.Any][Any, None]', 'default': 'None', 'description': 'The initial dataclass instance to render.'}, 'label': {'type': 'str | None', 'default': 'None', 'description': 'The main label for the component, displayed in the accordion header.'}, 'visible': {'type': 'bool', 'default': 'True', 'description': 'If False, the component will be hidden.'}, 'open': {'type': 'bool', 'default': 'True', 'description': 'If False, the accordion will be collapsed by default.'}, 'elem_id': {'type': 'str | None', 'default': 'None', 'description': 'An optional string that is assigned as the id of this component in the DOM.'}, 'scale': {'type': 'int | None', 'default': 'None', 'description': 'The relative size of the component in its container.'}, 'width': {'type': 'int | str | None', 'default': 'None', 'description': 'The width of the component in pixels.'}, 'height': {'type': 'int | str | None', 'default': 'None', 'description': "The maximum height of the component's content area in pixels before scrolling."}, 'min_width': {'type': 'int | None', 'default': 'None', 'description': 'The minimum width of the component in pixels.'}, 'container': {'type': 'bool', 'default': 'True', 'description': 'If True, wraps the component in a container with a background.'}, 'elem_classes': {'type': 'list[str] | str | None', 'default': 'None', 'description': 'An optional list of strings that are assigned as the classes of this component in the DOM.'}}, 'postprocess': {'value': {'type': 'Any', 'description':
|
| 7 |
|
| 8 |
abs_path = os.path.join(os.path.dirname(__file__), "css.css")
|
| 9 |
|
|
@@ -21,7 +21,7 @@ with gr.Blocks(
|
|
| 21 |
# `gradio_propertysheet`
|
| 22 |
|
| 23 |
<div style="display: flex; gap: 7px;">
|
| 24 |
-
<img alt="
|
| 25 |
</div>
|
| 26 |
|
| 27 |
Property sheet
|
|
@@ -43,115 +43,55 @@ from dataclasses import dataclass, field, asdict
|
|
| 43 |
from typing import Literal
|
| 44 |
from gradio_propertysheet import PropertySheet
|
| 45 |
|
| 46 |
-
# ---
|
|
|
|
| 47 |
@dataclass
|
| 48 |
class ModelSettings:
|
| 49 |
\"\"\"Settings for loading models, VAEs, etc.\"\"\"
|
| 50 |
-
model_type: Literal["SD 1.5", "SDXL", "Pony", "Custom"] = field(
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
)
|
| 54 |
-
custom_model_path: str = field(
|
| 55 |
-
default="/path/to/default.safetensors",
|
| 56 |
-
metadata={"label": "Custom Model Path", "interactive_if": {"field": "model_type", "value": "Custom"}}
|
| 57 |
-
)
|
| 58 |
-
vae_path: str = field(
|
| 59 |
-
default="",
|
| 60 |
-
metadata={"label": "VAE Path (optional)"}
|
| 61 |
-
)
|
| 62 |
|
| 63 |
@dataclass
|
| 64 |
class SamplingSettings:
|
| 65 |
\"\"\"Settings for the image sampling process.\"\"\"
|
| 66 |
-
sampler_name: Literal["Euler", "Euler a", "DPM++ 2M Karras", "UniPC"] = field(
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
)
|
| 70 |
-
steps: int = field(
|
| 71 |
-
default=25,
|
| 72 |
-
metadata={"component": "slider", "minimum": 1, "maximum": 150, "step": 1, "label": "Sampling Steps", "help": "More steps can improve quality."}
|
| 73 |
-
)
|
| 74 |
-
cfg_scale: float = field(
|
| 75 |
-
default=7.0,
|
| 76 |
-
metadata={"component": "slider", "minimum": 1.0, "maximum": 30.0, "step": 0.5, "label": "CFG Scale", "help": "How strongly the prompt is adhered to."}
|
| 77 |
-
)
|
| 78 |
|
| 79 |
@dataclass
|
| 80 |
class ImageSettings:
|
| 81 |
\"\"\"Settings for image dimensions.\"\"\"
|
| 82 |
-
width: int = field(
|
| 83 |
-
|
| 84 |
-
metadata={"component": "slider", "minimum": 512, "maximum": 2048, "step": 64, "label": "Image Width"}
|
| 85 |
-
)
|
| 86 |
-
height: int = field(
|
| 87 |
-
default=1024,
|
| 88 |
-
metadata={"component": "slider", "minimum": 512, "maximum": 2048, "step": 64, "label": "Image Height"}
|
| 89 |
-
)
|
| 90 |
|
| 91 |
@dataclass
|
| 92 |
class PostprocessingSettings:
|
| 93 |
\"\"\"Settings for image post-processing effects.\"\"\"
|
| 94 |
-
restore_faces: bool = field(
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
)
|
| 98 |
-
enable_hr: bool = field(
|
| 99 |
-
default=False,
|
| 100 |
-
metadata={"label": "Hires. fix", "help": "Enable a second pass at a higher resolution."}
|
| 101 |
-
)
|
| 102 |
-
denoising_strength: float = field(
|
| 103 |
-
default=0.45,
|
| 104 |
-
metadata={"component": "slider", "minimum": 0.0, "maximum": 1.0, "step": 0.01, "label": "Denoising Strength", "interactive_if": {"field": "enable_hr", "value": True}}
|
| 105 |
-
)
|
| 106 |
|
| 107 |
@dataclass
|
| 108 |
class AdvancedSettings:
|
| 109 |
\"\"\"Advanced and rarely changed settings.\"\"\"
|
| 110 |
-
clip_skip: int = field(
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
)
|
| 114 |
-
noise_schedule: Literal["Default", "Karras", "Exponential"] = field(
|
| 115 |
-
default="Karras",
|
| 116 |
-
metadata={"component": "dropdown", "label": "Noise Schedule"}
|
| 117 |
-
)
|
| 118 |
-
do_not_scale_cond_uncond: bool = field(
|
| 119 |
-
default=False,
|
| 120 |
-
metadata={"label": "Do not scale cond/uncond"}
|
| 121 |
-
)
|
| 122 |
-
s_churn: int = field(
|
| 123 |
-
default=1,
|
| 124 |
-
metadata={"component": "number_integer", "minimum": 1, "maximum": 12, "label": "S_churn", "help": "S_churn value for generation."}
|
| 125 |
-
)
|
| 126 |
|
| 127 |
@dataclass
|
| 128 |
class ScriptSettings:
|
| 129 |
\"\"\"Settings for automation scripts like X/Y/Z plots.\"\"\"
|
| 130 |
-
script_name: Literal["None", "Prompt matrix", "X/Y/Z plot"] = field(
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
)
|
| 134 |
-
x_values: str = field(
|
| 135 |
-
default="-1, 10, 20",
|
| 136 |
-
metadata={"label": "X axis values", "interactive_if": {"field": "script_name", "value": "X/Y/Z plot"}}
|
| 137 |
-
)
|
| 138 |
-
y_values: str = field(
|
| 139 |
-
default="",
|
| 140 |
-
metadata={"label": "Y axis values", "interactive_if": {"field": "script_name", "value": "X/Y/Z plot"}}
|
| 141 |
-
)
|
| 142 |
|
| 143 |
@dataclass
|
| 144 |
class RenderConfig:
|
| 145 |
\"\"\"Main configuration object for rendering, grouping all settings.\"\"\"
|
| 146 |
-
seed: int = field(
|
| 147 |
-
|
| 148 |
-
metadata={"component": "number_integer", "label": "Seed (-1 for random)", "help": "The random seed for generation."}
|
| 149 |
-
)
|
| 150 |
-
batch_size: int = field(
|
| 151 |
-
default=1,
|
| 152 |
-
metadata={"component": "slider", "minimum": 1, "maximum": 8, "step": 1, "label": "Batch Size"}
|
| 153 |
-
)
|
| 154 |
-
# Nested groups
|
| 155 |
model: ModelSettings = field(default_factory=ModelSettings)
|
| 156 |
sampling: SamplingSettings = field(default_factory=SamplingSettings)
|
| 157 |
image: ImageSettings = field(default_factory=ImageSettings)
|
|
@@ -159,7 +99,6 @@ class RenderConfig:
|
|
| 159 |
scripts: ScriptSettings = field(default_factory=ScriptSettings)
|
| 160 |
advanced: AdvancedSettings = field(default_factory=AdvancedSettings)
|
| 161 |
|
| 162 |
-
|
| 163 |
@dataclass
|
| 164 |
class Lighting:
|
| 165 |
\"\"\"Lighting settings for the environment.\"\"\"
|
|
@@ -173,8 +112,8 @@ class EnvironmentConfig:
|
|
| 173 |
background: Literal["Sky", "Color", "Image"] = field(default="Sky", metadata={"component": "dropdown"})
|
| 174 |
lighting: Lighting = field(default_factory=Lighting)
|
| 175 |
|
| 176 |
-
|
| 177 |
# --- Initial Instances ---
|
|
|
|
| 178 |
initial_render_config = RenderConfig()
|
| 179 |
initial_env_config = EnvironmentConfig()
|
| 180 |
|
|
@@ -184,66 +123,118 @@ with gr.Blocks(title="PropertySheet Demo") as demo:
|
|
| 184 |
gr.Markdown("An example of a realistic application layout using the `PropertySheet` component as a sidebar for settings.")
|
| 185 |
gr.Markdown("<span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_propertysheet'>Component GitHub Code</a></span>")
|
| 186 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
with gr.Row():
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
#gr.Image(label="Main Viewport", height=500, value=None)
|
| 191 |
-
gr.Textbox(label="AI Prompt", lines=3, placeholder="Enter your prompt here...")
|
| 192 |
-
gr.Button("Generate", variant="primary")
|
| 193 |
with gr.Row():
|
| 194 |
output_render_json = gr.JSON(label="Live Render State")
|
| 195 |
output_env_json = gr.JSON(label="Live Environment State")
|
| 196 |
|
| 197 |
-
# Sidebar with Property Sheets on the right
|
| 198 |
with gr.Column(scale=1):
|
| 199 |
render_sheet = PropertySheet(
|
| 200 |
-
value=initial_render_config,
|
| 201 |
label="Render Settings",
|
| 202 |
width=400,
|
| 203 |
-
height=550
|
|
|
|
| 204 |
)
|
| 205 |
environment_sheet = PropertySheet(
|
| 206 |
value=initial_env_config,
|
| 207 |
label="Environment Settings",
|
| 208 |
width=400,
|
| 209 |
-
open=False
|
|
|
|
| 210 |
)
|
| 211 |
|
| 212 |
# --- Event Handlers ---
|
| 213 |
-
def
|
| 214 |
-
\"\"\"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
if updated_config is None:
|
| 216 |
-
return
|
| 217 |
|
| 218 |
-
# Example of business logic
|
| 219 |
if updated_config.model.model_type != "Custom":
|
| 220 |
updated_config.model.custom_model_path = "/path/to/default.safetensors"
|
| 221 |
-
|
| 222 |
-
return updated_config, asdict(updated_config)
|
| 223 |
|
| 224 |
-
def handle_env_change(updated_config: EnvironmentConfig | None):
|
| 225 |
-
\"\"\"
|
| 226 |
if updated_config is None:
|
| 227 |
-
return
|
| 228 |
-
return updated_config, asdict(updated_config)
|
| 229 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
render_sheet.change(
|
| 231 |
fn=handle_render_change,
|
| 232 |
-
inputs=[render_sheet],
|
| 233 |
-
outputs=[render_sheet, output_render_json]
|
| 234 |
)
|
|
|
|
|
|
|
| 235 |
environment_sheet.change(
|
| 236 |
fn=handle_env_change,
|
| 237 |
-
inputs=[environment_sheet],
|
| 238 |
-
outputs=[environment_sheet, output_env_json]
|
| 239 |
)
|
| 240 |
|
| 241 |
-
# Load initial
|
| 242 |
demo.load(
|
| 243 |
fn=lambda: (asdict(initial_render_config), asdict(initial_env_config)),
|
| 244 |
outputs=[output_render_json, output_env_json]
|
| 245 |
)
|
| 246 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
if __name__ == "__main__":
|
| 248 |
demo.launch()
|
| 249 |
```
|
|
@@ -276,7 +267,8 @@ The impact on the users predict function varies depending on whether the compone
|
|
| 276 |
|
| 277 |
The code snippet below is accurate in cases where the component is used as both an input and an output.
|
| 278 |
|
| 279 |
-
|
|
|
|
| 280 |
|
| 281 |
```python
|
| 282 |
def predict(
|
|
|
|
| 3 |
from app import demo as app
|
| 4 |
import os
|
| 5 |
|
| 6 |
+
_docs = {'PropertySheet': {'description': 'A Gradio component that renders a dynamic UI from a Python dataclass instance.\nIt allows for nested settings and automatically infers input types.', 'members': {'__init__': {'value': {'type': 'typing.Optional[typing.Any][Any, None]', 'default': 'None', 'description': 'The initial dataclass instance to render.'}, 'label': {'type': 'str | None', 'default': 'None', 'description': 'The main label for the component, displayed in the accordion header.'}, 'visible': {'type': 'bool', 'default': 'True', 'description': 'If False, the component will be hidden.'}, 'open': {'type': 'bool', 'default': 'True', 'description': 'If False, the accordion will be collapsed by default.'}, 'elem_id': {'type': 'str | None', 'default': 'None', 'description': 'An optional string that is assigned as the id of this component in the DOM.'}, 'scale': {'type': 'int | None', 'default': 'None', 'description': 'The relative size of the component in its container.'}, 'width': {'type': 'int | str | None', 'default': 'None', 'description': 'The width of the component in pixels.'}, 'height': {'type': 'int | str | None', 'default': 'None', 'description': "The maximum height of the component's content area in pixels before scrolling."}, 'min_width': {'type': 'int | None', 'default': 'None', 'description': 'The minimum width of the component in pixels.'}, 'container': {'type': 'bool', 'default': 'True', 'description': 'If True, wraps the component in a container with a background.'}, 'elem_classes': {'type': 'list[str] | str | None', 'default': 'None', 'description': 'An optional list of strings that are assigned as the classes of this component in the DOM.'}}, 'postprocess': {'value': {'type': 'Any', 'description': 'The dataclass instance to process.'}}, 'preprocess': {'return': {'type': 'Any', 'description': 'A new, updated instance of the dataclass.'}, 'value': None}}, 'events': {'change': {'type': None, 'default': None, 'description': ''}, 'input': {'type': None, 'default': None, 'description': ''}, 'expand': {'type': None, 'default': None, 'description': ''}, 'collapse': {'type': None, 'default': None, 'description': ''}}}, '__meta__': {'additional_interfaces': {}, 'user_fn_refs': {'PropertySheet': []}}}
|
| 7 |
|
| 8 |
abs_path = os.path.join(os.path.dirname(__file__), "css.css")
|
| 9 |
|
|
|
|
| 21 |
# `gradio_propertysheet`
|
| 22 |
|
| 23 |
<div style="display: flex; gap: 7px;">
|
| 24 |
+
<a href="https://pypi.org/project/gradio_propertysheet/" target="_blank"><img alt="PyPI - Version" src="https://img.shields.io/pypi/v/gradio_propertysheet"></a>
|
| 25 |
</div>
|
| 26 |
|
| 27 |
Property sheet
|
|
|
|
| 43 |
from typing import Literal
|
| 44 |
from gradio_propertysheet import PropertySheet
|
| 45 |
|
| 46 |
+
# --- Configuration Data Models ---
|
| 47 |
+
# These dataclasses define the structure for all settings panels.
|
| 48 |
@dataclass
|
| 49 |
class ModelSettings:
|
| 50 |
\"\"\"Settings for loading models, VAEs, etc.\"\"\"
|
| 51 |
+
model_type: Literal["SD 1.5", "SDXL", "Pony", "Custom"] = field(default="SDXL", metadata={"component": "dropdown", "label": "Base Model"})
|
| 52 |
+
custom_model_path: str = field(default="/path/to/default.safetensors", metadata={"label": "Custom Model Path", "interactive_if": {"field": "model_type", "value": "Custom"}})
|
| 53 |
+
vae_path: str = field(default="", metadata={"label": "VAE Path (optional)"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
@dataclass
|
| 56 |
class SamplingSettings:
|
| 57 |
\"\"\"Settings for the image sampling process.\"\"\"
|
| 58 |
+
sampler_name: Literal["Euler", "Euler a", "DPM++ 2M Karras", "UniPC"] = field(default="DPM++ 2M Karras", metadata={"component": "dropdown", "label": "Sampler", "help": "The algorithm for the diffusion process."})
|
| 59 |
+
steps: int = field(default=25, metadata={"component": "slider", "minimum": 1, "maximum": 150, "step": 1, "label": "Sampling Steps", "help": "More steps can improve quality."})
|
| 60 |
+
cfg_scale: float = field(default=7.0, metadata={"component": "slider", "minimum": 1.0, "maximum": 30.0, "step": 0.5, "label": "CFG Scale", "help": "How strongly the prompt is adhered to."})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
@dataclass
|
| 63 |
class ImageSettings:
|
| 64 |
\"\"\"Settings for image dimensions.\"\"\"
|
| 65 |
+
width: int = field(default=1024, metadata={"component": "slider", "minimum": 512, "maximum": 2048, "step": 64, "label": "Image Width"})
|
| 66 |
+
height: int = field(default=1024, metadata={"component": "slider", "minimum": 512, "maximum": 2048, "step": 64, "label": "Image Height"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
@dataclass
|
| 69 |
class PostprocessingSettings:
|
| 70 |
\"\"\"Settings for image post-processing effects.\"\"\"
|
| 71 |
+
restore_faces: bool = field(default=True, metadata={"label": "Restore Faces", "help": "Use a secondary model to fix distorted faces."})
|
| 72 |
+
enable_hr: bool = field(default=False, metadata={"label": "Hires. fix", "help": "Enable a second pass at a higher resolution."})
|
| 73 |
+
denoising_strength: float = field(default=0.45, metadata={"component": "slider", "minimum": 0.0, "maximum": 1.0, "step": 0.01, "label": "Denoising Strength", "interactive_if": {"field": "enable_hr", "value": True}})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
@dataclass
|
| 76 |
class AdvancedSettings:
|
| 77 |
\"\"\"Advanced and rarely changed settings.\"\"\"
|
| 78 |
+
clip_skip: int = field(default=2, metadata={"component": "slider", "minimum": 1, "maximum": 12, "step": 1, "label": "CLIP Skip", "help": "Skip final layers of the text encoder."})
|
| 79 |
+
noise_schedule: Literal["Default", "Karras", "Exponential"] = field(default="Karras", metadata={"component": "dropdown", "label": "Noise Schedule"})
|
| 80 |
+
do_not_scale_cond_uncond: bool = field(default=False, metadata={"label": "Do not scale cond/uncond"})
|
| 81 |
+
s_churn: int = field(default=1, metadata={"component": "number_integer", "minimum": 1, "maximum": 12, "label": "S_churn", "help": "S_churn value for generation."})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
|
| 83 |
@dataclass
|
| 84 |
class ScriptSettings:
|
| 85 |
\"\"\"Settings for automation scripts like X/Y/Z plots.\"\"\"
|
| 86 |
+
script_name: Literal["None", "Prompt matrix", "X/Y/Z plot"] = field(default="None", metadata={"component": "dropdown", "label": "Script"})
|
| 87 |
+
x_values: str = field(default="-1, 10, 20", metadata={"label": "X axis values", "interactive_if": {"field": "script_name", "value": "X/Y/Z plot"}})
|
| 88 |
+
y_values: str = field(default="", metadata={"label": "Y axis values", "interactive_if": {"field": "script_name", "value": "X/Y/Z plot"}})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
@dataclass
|
| 91 |
class RenderConfig:
|
| 92 |
\"\"\"Main configuration object for rendering, grouping all settings.\"\"\"
|
| 93 |
+
seed: int = field(default=-1, metadata={"component": "number_integer", "label": "Seed (-1 for random)", "help": "The random seed for generation."})
|
| 94 |
+
batch_size: int = field(default=1, metadata={"component": "slider", "minimum": 1, "maximum": 8, "step": 1, "label": "Batch Size"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
model: ModelSettings = field(default_factory=ModelSettings)
|
| 96 |
sampling: SamplingSettings = field(default_factory=SamplingSettings)
|
| 97 |
image: ImageSettings = field(default_factory=ImageSettings)
|
|
|
|
| 99 |
scripts: ScriptSettings = field(default_factory=ScriptSettings)
|
| 100 |
advanced: AdvancedSettings = field(default_factory=AdvancedSettings)
|
| 101 |
|
|
|
|
| 102 |
@dataclass
|
| 103 |
class Lighting:
|
| 104 |
\"\"\"Lighting settings for the environment.\"\"\"
|
|
|
|
| 112 |
background: Literal["Sky", "Color", "Image"] = field(default="Sky", metadata={"component": "dropdown"})
|
| 113 |
lighting: Lighting = field(default_factory=Lighting)
|
| 114 |
|
|
|
|
| 115 |
# --- Initial Instances ---
|
| 116 |
+
# Create default instances of the configuration objects.
|
| 117 |
initial_render_config = RenderConfig()
|
| 118 |
initial_env_config = EnvironmentConfig()
|
| 119 |
|
|
|
|
| 123 |
gr.Markdown("An example of a realistic application layout using the `PropertySheet` component as a sidebar for settings.")
|
| 124 |
gr.Markdown("<span>💻 <a href='https://github.com/DEVAIEXP/gradio_component_propertysheet'>Component GitHub Code</a></span>")
|
| 125 |
|
| 126 |
+
# --- Persistent State Management ---
|
| 127 |
+
# Use gr.State to hold the application's data. This is the "single source of truth".
|
| 128 |
+
render_state = gr.State(value=initial_render_config)
|
| 129 |
+
env_state = gr.State(value=initial_env_config)
|
| 130 |
+
|
| 131 |
with gr.Row():
|
| 132 |
+
with gr.Column(scale=3):
|
| 133 |
+
generate = gr.Button("Show Settings", variant="primary")
|
|
|
|
|
|
|
|
|
|
| 134 |
with gr.Row():
|
| 135 |
output_render_json = gr.JSON(label="Live Render State")
|
| 136 |
output_env_json = gr.JSON(label="Live Environment State")
|
| 137 |
|
|
|
|
| 138 |
with gr.Column(scale=1):
|
| 139 |
render_sheet = PropertySheet(
|
| 140 |
+
value=initial_render_config,
|
| 141 |
label="Render Settings",
|
| 142 |
width=400,
|
| 143 |
+
height=550,
|
| 144 |
+
visible=False
|
| 145 |
)
|
| 146 |
environment_sheet = PropertySheet(
|
| 147 |
value=initial_env_config,
|
| 148 |
label="Environment Settings",
|
| 149 |
width=400,
|
| 150 |
+
open=False,
|
| 151 |
+
visible=False
|
| 152 |
)
|
| 153 |
|
| 154 |
# --- Event Handlers ---
|
| 155 |
+
def change_visibility(render_config, env_config):
|
| 156 |
+
\"\"\"
|
| 157 |
+
Handles the visibility toggle for the property sheets.
|
| 158 |
+
NOTE: This approach of modifying the component object's attribute directly
|
| 159 |
+
is not reliable for tracking state changes in Gradio. A gr.State object is
|
| 160 |
+
the recommended way to manage UI state like visibility.
|
| 161 |
+
\"\"\"
|
| 162 |
+
if render_sheet.visible != environment_sheet.visible:
|
| 163 |
+
render_sheet.visible = False
|
| 164 |
+
environment_sheet.visible = False
|
| 165 |
+
|
| 166 |
+
if render_sheet.visible == False and environment_sheet.visible == False:
|
| 167 |
+
render_sheet.visible = True
|
| 168 |
+
environment_sheet.visible = True
|
| 169 |
+
return (
|
| 170 |
+
gr.update(visible=True, value=render_config),
|
| 171 |
+
gr.update(visible=True, value=env_config),
|
| 172 |
+
gr.update(value="Hide Settings")
|
| 173 |
+
)
|
| 174 |
+
else:
|
| 175 |
+
render_sheet.visible = False
|
| 176 |
+
environment_sheet.visible = False
|
| 177 |
+
return (
|
| 178 |
+
gr.update(visible=False, value=render_config),
|
| 179 |
+
gr.update(visible=False, value=env_config),
|
| 180 |
+
gr.update(value="Show Settings")
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
def handle_render_change(updated_config: RenderConfig | None, current_state: RenderConfig):
|
| 184 |
+
\"\"\"Processes updates from the render PropertySheet and syncs the state.\"\"\"
|
| 185 |
if updated_config is None:
|
| 186 |
+
return current_state, asdict(current_state), current_state
|
| 187 |
|
| 188 |
+
# Example of applying business logic
|
| 189 |
if updated_config.model.model_type != "Custom":
|
| 190 |
updated_config.model.custom_model_path = "/path/to/default.safetensors"
|
| 191 |
+
|
| 192 |
+
return updated_config, asdict(updated_config), updated_config
|
| 193 |
|
| 194 |
+
def handle_env_change(updated_config: EnvironmentConfig | None, current_state: EnvironmentConfig):
|
| 195 |
+
\"\"\"Processes updates from the environment PropertySheet and syncs the state.\"\"\"
|
| 196 |
if updated_config is None:
|
| 197 |
+
return current_state, asdict(current_state), current_state
|
| 198 |
+
return updated_config, asdict(updated_config), updated_config
|
| 199 |
+
|
| 200 |
+
# --- Event Listeners ---
|
| 201 |
+
# Toggle the property sheets' visibility on button click.
|
| 202 |
+
generate.click(
|
| 203 |
+
fn=change_visibility,
|
| 204 |
+
inputs=[render_state, env_state],
|
| 205 |
+
outputs=[render_sheet, environment_sheet, generate]
|
| 206 |
+
)
|
| 207 |
+
|
| 208 |
+
# Syncs changes from the UI back to the state and JSON display.
|
| 209 |
render_sheet.change(
|
| 210 |
fn=handle_render_change,
|
| 211 |
+
inputs=[render_sheet, render_state],
|
| 212 |
+
outputs=[render_sheet, output_render_json, render_state]
|
| 213 |
)
|
| 214 |
+
|
| 215 |
+
# Syncs changes from the UI back to the state and JSON display.
|
| 216 |
environment_sheet.change(
|
| 217 |
fn=handle_env_change,
|
| 218 |
+
inputs=[environment_sheet, env_state],
|
| 219 |
+
outputs=[environment_sheet, output_env_json, env_state]
|
| 220 |
)
|
| 221 |
|
| 222 |
+
# Load initial data into JSON displays on app start.
|
| 223 |
demo.load(
|
| 224 |
fn=lambda: (asdict(initial_render_config), asdict(initial_env_config)),
|
| 225 |
outputs=[output_render_json, output_env_json]
|
| 226 |
)
|
| 227 |
|
| 228 |
+
# Ensure components are populated with state values on load/reload.
|
| 229 |
+
demo.load(
|
| 230 |
+
fn=lambda render_config, env_config: (
|
| 231 |
+
gr.update(value=render_config),
|
| 232 |
+
gr.update(value=env_config)
|
| 233 |
+
),
|
| 234 |
+
inputs=[render_state, env_state],
|
| 235 |
+
outputs=[render_sheet, environment_sheet]
|
| 236 |
+
)
|
| 237 |
+
|
| 238 |
if __name__ == "__main__":
|
| 239 |
demo.launch()
|
| 240 |
```
|
|
|
|
| 267 |
|
| 268 |
The code snippet below is accurate in cases where the component is used as both an input and an output.
|
| 269 |
|
| 270 |
+
- **As input:** Is passed, a new, updated instance of the dataclass.
|
| 271 |
+
- **As output:** Should return, the dataclass instance to process.
|
| 272 |
|
| 273 |
```python
|
| 274 |
def predict(
|
src/frontend/Index.svelte
CHANGED
|
@@ -4,35 +4,57 @@
|
|
| 4 |
import { StatusTracker } from "@gradio/statustracker";
|
| 5 |
import type { LoadingStatus } from "@gradio/statustracker";
|
| 6 |
import type { Gradio } from "@gradio/utils";
|
| 7 |
-
import { onMount } from "svelte";
|
| 8 |
|
| 9 |
// --- Component Props (passed from Gradio backend) ---
|
|
|
|
| 10 |
export let value: Array<{ group_name: string, properties: any[] }> = [];
|
|
|
|
| 11 |
export let label: string | undefined = undefined;
|
|
|
|
| 12 |
export let visible: boolean = true;
|
|
|
|
| 13 |
export let open: boolean = true;
|
|
|
|
| 14 |
export let elem_id: string = "";
|
|
|
|
| 15 |
export let elem_classes: string[] = [];
|
|
|
|
| 16 |
export let container: boolean = false;
|
|
|
|
| 17 |
export let scale: number | null = null;
|
|
|
|
| 18 |
export let min_width: number | undefined = undefined;
|
|
|
|
| 19 |
export let width: number | undefined = undefined;
|
|
|
|
| 20 |
export let height: number | undefined = undefined;
|
|
|
|
| 21 |
export let loading_status: LoadingStatus | undefined = undefined;
|
|
|
|
| 22 |
export let interactive: boolean = true;
|
|
|
|
| 23 |
export let gradio: Gradio<{ change: any, reset: any, input: any, clear_status: never, expand: never, collapse: never }>;
|
| 24 |
|
| 25 |
// --- Internal State ---
|
| 26 |
-
|
| 27 |
-
let
|
|
|
|
|
|
|
|
|
|
| 28 |
$: final_classes = ["propertysheet-wrapper", ...elem_classes];
|
| 29 |
-
|
| 30 |
-
let
|
| 31 |
-
|
| 32 |
-
let
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
/**
|
| 35 |
-
* Validates a property
|
|
|
|
| 36 |
* @param {any} prop - The property object to validate.
|
| 37 |
*/
|
| 38 |
function validate_prop(prop: any) {
|
|
@@ -53,12 +75,12 @@
|
|
| 53 |
}
|
| 54 |
}
|
| 55 |
|
| 56 |
-
|
| 57 |
let dynamic_height: number | undefined;
|
| 58 |
$: dynamic_height = open ? height : undefined;
|
| 59 |
|
| 60 |
/**
|
| 61 |
-
* Iterates through all properties and updates the background of any sliders.
|
| 62 |
*/
|
| 63 |
function updateAllSliders() {
|
| 64 |
if (!Array.isArray(value)) return;
|
|
@@ -73,15 +95,16 @@
|
|
| 73 |
}
|
| 74 |
}
|
| 75 |
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
| 77 |
$: if (Array.isArray(value) && JSON.stringify(value) !== lastValue) {
|
| 78 |
lastValue = JSON.stringify(value);
|
| 79 |
for (const group of value) {
|
| 80 |
-
// Initialize group visibility if not already set.
|
| 81 |
if (groupVisibility[group.group_name] === undefined) {
|
| 82 |
groupVisibility[group.group_name] = true;
|
| 83 |
}
|
| 84 |
-
// Validate properties upon receiving new values.
|
| 85 |
if (Array.isArray(group.properties)) {
|
| 86 |
for (const prop of group.properties) {
|
| 87 |
if (prop.component?.startsWith("number") || prop.component === 'slider') {
|
|
@@ -90,14 +113,14 @@
|
|
| 90 |
}
|
| 91 |
}
|
| 92 |
}
|
| 93 |
-
// Update slider visuals to match new values.
|
| 94 |
updateAllSliders();
|
| 95 |
}
|
| 96 |
|
| 97 |
/**
|
| 98 |
-
* Updates a slider's track background to
|
|
|
|
| 99 |
* @param {any} prop - The slider property object.
|
| 100 |
-
* @param {HTMLInputElement} element - The slider input element.
|
| 101 |
*/
|
| 102 |
function updateSliderBackground(prop: any, element: HTMLInputElement) {
|
| 103 |
if (!element) return;
|
|
@@ -109,7 +132,8 @@
|
|
| 109 |
}
|
| 110 |
|
| 111 |
/**
|
| 112 |
-
* Handles the main accordion toggle
|
|
|
|
| 113 |
*/
|
| 114 |
function handle_toggle() {
|
| 115 |
open = !open;
|
|
@@ -118,7 +142,7 @@
|
|
| 118 |
}
|
| 119 |
|
| 120 |
/**
|
| 121 |
-
* Toggles the visibility of an individual property group.
|
| 122 |
* @param {string} groupName - The name of the group to toggle.
|
| 123 |
*/
|
| 124 |
function toggleGroup(groupName: string) {
|
|
@@ -126,9 +150,9 @@
|
|
| 126 |
}
|
| 127 |
|
| 128 |
/**
|
| 129 |
-
* Utility function to
|
| 130 |
-
* Used
|
| 131 |
-
* @param {string} prop_name - The name of the property.
|
| 132 |
*/
|
| 133 |
function get_prop_value(prop_name: string) {
|
| 134 |
if (!Array.isArray(value)) return undefined;
|
|
@@ -139,19 +163,20 @@
|
|
| 139 |
}
|
| 140 |
return undefined;
|
| 141 |
}
|
| 142 |
-
|
| 143 |
/**
|
| 144 |
-
* Dispatches
|
|
|
|
|
|
|
| 145 |
* @param {"change" | "input"} event_name - The type of event to dispatch.
|
| 146 |
* @param {any} changed_prop - The property object that was modified.
|
| 147 |
*/
|
| 148 |
function dispatch_update(event_name: "change" | "input", changed_prop: any) {
|
| 149 |
if (validationState[changed_prop.name] === false) {
|
| 150 |
-
return;
|
| 151 |
}
|
| 152 |
const payload: Record<string, any> = {};
|
| 153 |
let final_value = changed_prop.value;
|
| 154 |
-
// Ensure correct data type for dispatch.
|
| 155 |
if (changed_prop.component?.startsWith("number") || changed_prop.component === "slider") {
|
| 156 |
final_value = Number(changed_prop.value);
|
| 157 |
} else if (changed_prop.component === "checkbox") {
|
|
@@ -161,13 +186,39 @@
|
|
| 161 |
gradio.dispatch(event_name, payload);
|
| 162 |
}
|
| 163 |
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
}
|
| 168 |
-
|
| 169 |
/**
|
| 170 |
-
* Resets a single property to its initial value.
|
|
|
|
| 171 |
* @param {string} propName - The name of the property to reset.
|
| 172 |
*/
|
| 173 |
function handle_reset_prop(propName: string) {
|
|
@@ -177,7 +228,6 @@
|
|
| 177 |
isResetting = false;
|
| 178 |
return;
|
| 179 |
}
|
| 180 |
-
// Create a new array to trigger Svelte's reactivity.
|
| 181 |
let updatedValue = value.map(group => {
|
| 182 |
if (group.properties) {
|
| 183 |
group.properties = group.properties.map(prop => {
|
|
@@ -194,8 +244,11 @@
|
|
| 194 |
setTimeout(() => { isResetting = false; }, 100);
|
| 195 |
}
|
| 196 |
|
| 197 |
-
|
| 198 |
-
|
|
|
|
|
|
|
|
|
|
| 199 |
lastValue = JSON.stringify(value);
|
| 200 |
if (Array.isArray(value)) {
|
| 201 |
value.forEach(group => {
|
|
@@ -207,12 +260,19 @@
|
|
| 207 |
});
|
| 208 |
}
|
| 209 |
|
| 210 |
-
//
|
| 211 |
-
|
| 212 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
});
|
| 214 |
</script>
|
| 215 |
|
|
|
|
| 216 |
<Block {visible} {elem_id} elem_classes={final_classes} {container} {scale} {min_width} {width}>
|
| 217 |
{#if loading_status}
|
| 218 |
<StatusTracker
|
|
@@ -223,7 +283,7 @@
|
|
| 223 |
/>
|
| 224 |
{/if}
|
| 225 |
|
| 226 |
-
<!-- Main accordion header that toggles the entire component's
|
| 227 |
<button class="accordion-header" on:click={handle_toggle}>
|
| 228 |
{#if label}
|
| 229 |
<span class="label">{label}</span>
|
|
@@ -231,7 +291,7 @@
|
|
| 231 |
<span class="accordion-icon" style:transform={open ? "rotate(0)" : "rotate(-90deg)"}>▼</span>
|
| 232 |
</button>
|
| 233 |
|
| 234 |
-
<!-- Content wrapper that is shown or hidden -->
|
| 235 |
<div class:closed={!open} class="content-wrapper">
|
| 236 |
{#if open}
|
| 237 |
<div class="container" style="--sheet-max-height: {height ? `${height}px` : 'none'}">
|
|
@@ -371,23 +431,18 @@
|
|
| 371 |
</Block>
|
| 372 |
|
| 373 |
<style>
|
| 374 |
-
/*
|
| 375 |
:host {
|
| 376 |
-
/* Make the root component a vertical flex container. */
|
| 377 |
display: flex;
|
| 378 |
flex-direction: column;
|
| 379 |
height: 100%;
|
| 380 |
}
|
| 381 |
-
|
| 382 |
-
/* Target the wrapper div injected by Gradio. */
|
| 383 |
:global(.propertysheet-wrapper) {
|
| 384 |
-
overflow: hidden !important;
|
| 385 |
display: flex;
|
| 386 |
flex-direction: column;
|
| 387 |
flex-grow: 1;
|
| 388 |
}
|
| 389 |
-
|
| 390 |
-
/* --- Main Accordion --- */
|
| 391 |
.accordion-header {
|
| 392 |
display: flex;
|
| 393 |
justify-content: space-between;
|
|
@@ -397,18 +452,16 @@
|
|
| 397 |
padding: var(--block-title-padding);
|
| 398 |
background: var(--block-title-background-fill);
|
| 399 |
color: var(--block-title-text-color);
|
| 400 |
-
flex-shrink: 0;
|
| 401 |
}
|
| 402 |
-
|
| 403 |
.content-wrapper {
|
| 404 |
flex-grow: 1;
|
| 405 |
-
min-height: 0;
|
| 406 |
}
|
| 407 |
-
|
| 408 |
.container {
|
| 409 |
overflow-y: auto;
|
| 410 |
height: auto;
|
| 411 |
-
max-height: var(--sheet-max-height, 500px);
|
| 412 |
border-radius: 0 !important;
|
| 413 |
border: 1px solid var(--border-color-primary);
|
| 414 |
border-top: none;
|
|
@@ -416,12 +469,9 @@
|
|
| 416 |
border-bottom-right-radius: var(--radius-lg);
|
| 417 |
background-color: var(--background-fill-secondary);
|
| 418 |
}
|
| 419 |
-
|
| 420 |
.closed {
|
| 421 |
display: none;
|
| 422 |
}
|
| 423 |
-
|
| 424 |
-
/* --- Property Groups and Grid --- */
|
| 425 |
.group-header {
|
| 426 |
display: flex;
|
| 427 |
justify-content: space-between;
|
|
@@ -436,15 +486,12 @@
|
|
| 436 |
font-weight: var(--font-weight-bold);
|
| 437 |
border: 1px solid var(--border-color-primary);
|
| 438 |
}
|
| 439 |
-
|
| 440 |
-
/* Defines the two-column layout for labels and controls. */
|
| 441 |
.properties-grid {
|
| 442 |
display: grid;
|
| 443 |
grid-template-columns: 1fr 2fr;
|
| 444 |
gap: 0;
|
| 445 |
padding: 0;
|
| 446 |
}
|
| 447 |
-
|
| 448 |
.prop-label,
|
| 449 |
.prop-control {
|
| 450 |
padding: var(--spacing-sm) var(--spacing-md);
|
|
@@ -452,7 +499,6 @@
|
|
| 452 |
align-items: center;
|
| 453 |
border-bottom: 1px solid var(--background-fill-secondary);
|
| 454 |
}
|
| 455 |
-
|
| 456 |
.prop-label {
|
| 457 |
background-color: var(--background-fill-primary);
|
| 458 |
color: var(--body-text-color);
|
|
@@ -463,16 +509,12 @@
|
|
| 463 |
justify-content: flex-end;
|
| 464 |
word-break: break-word;
|
| 465 |
}
|
| 466 |
-
|
| 467 |
.prop-control {
|
| 468 |
gap: var(--spacing-sm);
|
| 469 |
}
|
| 470 |
-
|
| 471 |
.properties-grid > :nth-last-child(-n+2) {
|
| 472 |
border-bottom: none;
|
| 473 |
}
|
| 474 |
-
|
| 475 |
-
/* --- General Input Styles --- */
|
| 476 |
.prop-control input[type="text"],
|
| 477 |
.prop-control input[type="number"] {
|
| 478 |
background-color: var(--input-background-fill);
|
|
@@ -487,7 +529,6 @@
|
|
| 487 |
padding-left: var(--spacing-md);
|
| 488 |
padding-right: var(--spacing-3);
|
| 489 |
}
|
| 490 |
-
|
| 491 |
.prop-control input[type="text"]:focus,
|
| 492 |
.prop-control input[type="number"]:focus {
|
| 493 |
box-shadow: var(--input-shadow-focus);
|
|
@@ -495,13 +536,10 @@
|
|
| 495 |
background-color: var(--input-background-fill-focus);
|
| 496 |
outline: none;
|
| 497 |
}
|
| 498 |
-
|
| 499 |
-
/* --- Dropdown Component Styles --- */
|
| 500 |
.dropdown-wrapper {
|
| 501 |
position: relative;
|
| 502 |
width: 100%;
|
| 503 |
}
|
| 504 |
-
|
| 505 |
.dropdown-wrapper select {
|
| 506 |
-webkit-appearance: none;
|
| 507 |
appearance: none;
|
|
@@ -518,7 +556,6 @@
|
|
| 518 |
padding-left: var(--spacing-md);
|
| 519 |
padding-right: calc(var(--spacing-3) + 1.2em);
|
| 520 |
}
|
| 521 |
-
|
| 522 |
.dropdown-arrow-icon {
|
| 523 |
position: absolute;
|
| 524 |
top: 50%;
|
|
@@ -532,20 +569,16 @@
|
|
| 532 |
-webkit-mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='currentColor' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
| 533 |
mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='currentColor' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M6 8l4 4 4-4'%3e%3c/svg%3e");
|
| 534 |
}
|
| 535 |
-
|
| 536 |
.dropdown-wrapper select:focus {
|
| 537 |
box-shadow: var(--input-shadow-focus);
|
| 538 |
border-color: var(--input-border-color-focus);
|
| 539 |
background-color: var(--input-background-fill-focus);
|
| 540 |
outline: none;
|
| 541 |
}
|
| 542 |
-
|
| 543 |
.dropdown-wrapper select option {
|
| 544 |
background: var(--input-background-fill);
|
| 545 |
color: var(--body-text-color);
|
| 546 |
}
|
| 547 |
-
|
| 548 |
-
/* --- Checkbox Component Styles --- */
|
| 549 |
.prop-control input[type="checkbox"] {
|
| 550 |
-webkit-appearance: none;
|
| 551 |
appearance: none;
|
|
@@ -560,23 +593,19 @@
|
|
| 560 |
margin: 0;
|
| 561 |
transition: background-color 0.2s, border-color 0.2s;
|
| 562 |
}
|
| 563 |
-
|
| 564 |
.prop-control input[type="checkbox"]:hover {
|
| 565 |
border-color: var(--checkbox-border-color-hover);
|
| 566 |
background-color: var(--checkbox-background-color-hover);
|
| 567 |
}
|
| 568 |
-
|
| 569 |
.prop-control input[type="checkbox"]:focus {
|
| 570 |
border-color: var(--checkbox-border-color-focus);
|
| 571 |
background-color: var(--checkbox-background-color-focus);
|
| 572 |
outline: none;
|
| 573 |
}
|
| 574 |
-
|
| 575 |
.prop-control input[type="checkbox"]:checked {
|
| 576 |
background-color: var(--checkbox-background-color-selected);
|
| 577 |
border-color: var(--checkbox-border-color-focus);
|
| 578 |
}
|
| 579 |
-
|
| 580 |
.prop-control input[type="checkbox"]:checked::after {
|
| 581 |
content: "";
|
| 582 |
position: absolute;
|
|
@@ -589,31 +618,25 @@
|
|
| 589 |
border-width: 0 2px 2px 0;
|
| 590 |
transform: translate(-50%, -60%) rotate(45deg);
|
| 591 |
}
|
| 592 |
-
|
| 593 |
-
/* --- Slider Component Styles --- */
|
| 594 |
.slider-container {
|
| 595 |
display: flex;
|
| 596 |
align-items: center;
|
| 597 |
gap: var(--spacing-md);
|
| 598 |
width: 100%;
|
| 599 |
}
|
| 600 |
-
|
| 601 |
.slider-container input[type="range"] {
|
| 602 |
-
--slider-progress: 0%;
|
| 603 |
-webkit-appearance: none;
|
| 604 |
appearance: none;
|
| 605 |
background: transparent;
|
| 606 |
cursor: pointer;
|
| 607 |
width: 100%;
|
| 608 |
}
|
| 609 |
-
|
| 610 |
-
/* The track of the slider, filled with a gradient based on --slider-progress. */
|
| 611 |
.slider-container input[type="range"]::-webkit-slider-runnable-track {
|
| 612 |
height: 8px;
|
| 613 |
border-radius: var(--radius-lg);
|
| 614 |
background: linear-gradient( to right, var(--slider-color) var(--slider-progress), var(--input-background-fill) var(--slider-progress) );
|
| 615 |
}
|
| 616 |
-
|
| 617 |
.slider-container input[type="range"]::-webkit-slider-thumb {
|
| 618 |
-webkit-appearance: none;
|
| 619 |
appearance: none;
|
|
@@ -625,13 +648,11 @@
|
|
| 625 |
border: 1px solid var(--border-color-primary);
|
| 626 |
box-shadow: var(--shadow-drop);
|
| 627 |
}
|
| 628 |
-
|
| 629 |
.slider-container input[type="range"]::-moz-range-track {
|
| 630 |
height: 8px;
|
| 631 |
border-radius: var(--radius-lg);
|
| 632 |
background: linear-gradient( to right, var(--slider-color) var(--slider-progress), var(--input-background-fill) var(--slider-progress) );
|
| 633 |
}
|
| 634 |
-
|
| 635 |
.slider-container input[type="range"]::-moz-range-thumb {
|
| 636 |
background-color: white;
|
| 637 |
border-radius: 50%;
|
|
@@ -640,15 +661,12 @@
|
|
| 640 |
border: 1px solid var(--border-color-primary);
|
| 641 |
box-shadow: var(--shadow-drop);
|
| 642 |
}
|
| 643 |
-
|
| 644 |
.slider-value {
|
| 645 |
min-width: 40px;
|
| 646 |
text-align: right;
|
| 647 |
font-family: var(--font-mono);
|
| 648 |
font-size: var(--text-xs);
|
| 649 |
}
|
| 650 |
-
|
| 651 |
-
/* --- Tooltip Styles --- */
|
| 652 |
.prop-label-wrapper {
|
| 653 |
display: flex;
|
| 654 |
justify-content: flex-end;
|
|
@@ -656,14 +674,12 @@
|
|
| 656 |
gap: var(--spacing-sm);
|
| 657 |
width: 100%;
|
| 658 |
}
|
| 659 |
-
|
| 660 |
.tooltip-container {
|
| 661 |
position: relative;
|
| 662 |
display: inline-flex;
|
| 663 |
align-items: center;
|
| 664 |
justify-content: center;
|
| 665 |
}
|
| 666 |
-
|
| 667 |
.tooltip-icon {
|
| 668 |
display: flex;
|
| 669 |
align-items: center;
|
|
@@ -678,7 +694,6 @@
|
|
| 678 |
cursor: help;
|
| 679 |
user-select: none;
|
| 680 |
}
|
| 681 |
-
|
| 682 |
.tooltip-text {
|
| 683 |
visibility: hidden;
|
| 684 |
width: 200px;
|
|
@@ -695,20 +710,16 @@
|
|
| 695 |
opacity: 0;
|
| 696 |
transition: opacity 0.3s;
|
| 697 |
}
|
| 698 |
-
|
| 699 |
.tooltip-container:hover .tooltip-text {
|
| 700 |
visibility: visible;
|
| 701 |
opacity: 1;
|
| 702 |
}
|
| 703 |
-
|
| 704 |
-
/* --- Color Picker Styles --- */
|
| 705 |
.color-picker-container {
|
| 706 |
display: flex;
|
| 707 |
align-items: center;
|
| 708 |
gap: var(--spacing-md);
|
| 709 |
width: 100%;
|
| 710 |
}
|
| 711 |
-
|
| 712 |
.color-picker-input {
|
| 713 |
width: 50px;
|
| 714 |
height: 28px;
|
|
@@ -718,15 +729,12 @@
|
|
| 718 |
cursor: pointer;
|
| 719 |
padding: 0;
|
| 720 |
}
|
| 721 |
-
|
| 722 |
-
/* Style the inner color swatch for a clean look. */
|
| 723 |
.color-picker-input::-webkit-color-swatch-wrapper {
|
| 724 |
padding: 2px;
|
| 725 |
}
|
| 726 |
.color-picker-input::-moz-padding {
|
| 727 |
padding: 2px;
|
| 728 |
}
|
| 729 |
-
|
| 730 |
.color-picker-input::-webkit-color-swatch {
|
| 731 |
border: none;
|
| 732 |
border-radius: var(--radius-sm);
|
|
@@ -735,20 +743,15 @@
|
|
| 735 |
border: none;
|
| 736 |
border-radius: var(--radius-sm);
|
| 737 |
}
|
| 738 |
-
|
| 739 |
.color-picker-value {
|
| 740 |
font-family: var(--font-mono);
|
| 741 |
font-size: var(--text-sm);
|
| 742 |
color: var(--body-text-color-subdued);
|
| 743 |
}
|
| 744 |
-
|
| 745 |
-
/* --- Validation and State Styles --- */
|
| 746 |
.prop-control input.invalid {
|
| 747 |
border-color: var(--error-border-color, red) !important;
|
| 748 |
box-shadow: 0 0 0 1px var(--error-border-color, red) !important;
|
| 749 |
}
|
| 750 |
-
|
| 751 |
-
/* --- Property Reset Button --- */
|
| 752 |
.reset-button-prop {
|
| 753 |
display: flex;
|
| 754 |
align-items: center;
|
|
@@ -760,44 +763,35 @@
|
|
| 760 |
color: var(--body-text-color-subdued);
|
| 761 |
font-size: var(--text-lg);
|
| 762 |
padding: 0 var(--spacing-2);
|
| 763 |
-
/* Hidden by default, but occupies space to prevent layout shifts. */
|
| 764 |
visibility: hidden;
|
| 765 |
opacity: 0;
|
| 766 |
transition: opacity 150ms ease-in-out, color 150ms ease-in-out;
|
| 767 |
}
|
| 768 |
-
|
| 769 |
-
/* When the .visible class is added via Svelte, the button appears. */
|
| 770 |
.reset-button-prop.visible {
|
| 771 |
visibility: visible;
|
| 772 |
opacity: 1;
|
| 773 |
}
|
| 774 |
-
|
| 775 |
.reset-button-prop:hover {
|
| 776 |
color: var(--body-text-color);
|
| 777 |
background-color: var(--background-fill-secondary-hover);
|
| 778 |
}
|
| 779 |
-
|
| 780 |
.reset-button-prop:disabled {
|
| 781 |
color: var(--body-text-color-subdued) !important;
|
| 782 |
opacity: 0.5;
|
| 783 |
cursor: not-allowed;
|
| 784 |
background-color: transparent !important;
|
| 785 |
}
|
| 786 |
-
|
| 787 |
-
/* --- Disabled State --- */
|
| 788 |
.prop-control .disabled {
|
| 789 |
opacity: 0.5;
|
| 790 |
pointer-events: none;
|
| 791 |
cursor: not-allowed;
|
| 792 |
}
|
| 793 |
-
|
| 794 |
.prop-control .disabled input {
|
| 795 |
cursor: not-allowed;
|
| 796 |
}
|
| 797 |
-
|
| 798 |
.reset-button-prop:disabled {
|
| 799 |
opacity: 0.3;
|
| 800 |
cursor: not-allowed;
|
| 801 |
-
background-color: transparent !important;
|
| 802 |
}
|
| 803 |
</style>
|
|
|
|
| 4 |
import { StatusTracker } from "@gradio/statustracker";
|
| 5 |
import type { LoadingStatus } from "@gradio/statustracker";
|
| 6 |
import type { Gradio } from "@gradio/utils";
|
| 7 |
+
import { onMount, tick } from "svelte";
|
| 8 |
|
| 9 |
// --- Component Props (passed from Gradio backend) ---
|
| 10 |
+
/** The main data structure driving the UI, an array of property groups. */
|
| 11 |
export let value: Array<{ group_name: string, properties: any[] }> = [];
|
| 12 |
+
/** The main label for the component, displayed in the top-level accordion header. */
|
| 13 |
export let label: string | undefined = undefined;
|
| 14 |
+
/** Controls the overall visibility of the component. */
|
| 15 |
export let visible: boolean = true;
|
| 16 |
+
/** If true, the main accordion is open by default. */
|
| 17 |
export let open: boolean = true;
|
| 18 |
+
/** The DOM element ID. */
|
| 19 |
export let elem_id: string = "";
|
| 20 |
+
/** Custom CSS classes for the root element. */
|
| 21 |
export let elem_classes: string[] = [];
|
| 22 |
+
/** If true, wraps the component in a container with a background. */
|
| 23 |
export let container: boolean = false;
|
| 24 |
+
/** The relative size of the component in its container. */
|
| 25 |
export let scale: number | null = null;
|
| 26 |
+
/** The minimum width of the component in pixels. */
|
| 27 |
export let min_width: number | undefined = undefined;
|
| 28 |
+
/** The fixed width of the component in pixels. */
|
| 29 |
export let width: number | undefined = undefined;
|
| 30 |
+
/** The maximum height of the component's content area before scrolling. */
|
| 31 |
export let height: number | undefined = undefined;
|
| 32 |
+
/** The loading status object from Gradio. */
|
| 33 |
export let loading_status: LoadingStatus | undefined = undefined;
|
| 34 |
+
/** If false, all controls are disabled. */
|
| 35 |
export let interactive: boolean = true;
|
| 36 |
+
/** The Gradio event dispatcher instance. */
|
| 37 |
export let gradio: Gradio<{ change: any, reset: any, input: any, clear_status: never, expand: never, collapse: never }>;
|
| 38 |
|
| 39 |
// --- Internal State ---
|
| 40 |
+
/** Tracks the open/closed state of each individual property group. */
|
| 41 |
+
let groupVisibility: Record<string, boolean> = {};
|
| 42 |
+
/** Holds references to slider input elements for direct DOM manipulation (e.g., setting background). */
|
| 43 |
+
let sliderElements: Record<string, HTMLInputElement> = {};
|
| 44 |
+
/** Combines default and user-provided CSS classes. */
|
| 45 |
$: final_classes = ["propertysheet-wrapper", ...elem_classes];
|
| 46 |
+
/** Tracks the validation status (true/false) for each property to apply error styling. */
|
| 47 |
+
let validationState: Record<string, boolean> = {};
|
| 48 |
+
/** A stringified version of the `value` prop, used to detect changes from the backend. */
|
| 49 |
+
let lastValue: string | null = null;
|
| 50 |
+
/** A flag to prevent event loops during property reset operations. */
|
| 51 |
+
let isResetting: boolean = false;
|
| 52 |
+
/** A snapshot of the initial values of all properties, used for the reset functionality. */
|
| 53 |
+
let initialValues: Record<string, any> = {};
|
| 54 |
|
| 55 |
/**
|
| 56 |
+
* Validates a numeric property against its `minimum` and `maximum` constraints.
|
| 57 |
+
* Updates the `validationState` for CSS styling of invalid inputs.
|
| 58 |
* @param {any} prop - The property object to validate.
|
| 59 |
*/
|
| 60 |
function validate_prop(prop: any) {
|
|
|
|
| 75 |
}
|
| 76 |
}
|
| 77 |
|
| 78 |
+
/** Controls the component's content height based on the main accordion's open/closed state. */
|
| 79 |
let dynamic_height: number | undefined;
|
| 80 |
$: dynamic_height = open ? height : undefined;
|
| 81 |
|
| 82 |
/**
|
| 83 |
+
* Iterates through all properties and updates the background visuals of any sliders.
|
| 84 |
*/
|
| 85 |
function updateAllSliders() {
|
| 86 |
if (!Array.isArray(value)) return;
|
|
|
|
| 95 |
}
|
| 96 |
}
|
| 97 |
|
| 98 |
+
/**
|
| 99 |
+
* Reactive block that triggers whenever the `value` prop changes from the backend.
|
| 100 |
+
* It initializes group visibility, validates properties, and updates slider visuals.
|
| 101 |
+
*/
|
| 102 |
$: if (Array.isArray(value) && JSON.stringify(value) !== lastValue) {
|
| 103 |
lastValue = JSON.stringify(value);
|
| 104 |
for (const group of value) {
|
|
|
|
| 105 |
if (groupVisibility[group.group_name] === undefined) {
|
| 106 |
groupVisibility[group.group_name] = true;
|
| 107 |
}
|
|
|
|
| 108 |
if (Array.isArray(group.properties)) {
|
| 109 |
for (const prop of group.properties) {
|
| 110 |
if (prop.component?.startsWith("number") || prop.component === 'slider') {
|
|
|
|
| 113 |
}
|
| 114 |
}
|
| 115 |
}
|
|
|
|
| 116 |
updateAllSliders();
|
| 117 |
}
|
| 118 |
|
| 119 |
/**
|
| 120 |
+
* Updates a slider's track background to visually represent its value as a percentage.
|
| 121 |
+
* It sets the `--slider-progress` CSS custom property.
|
| 122 |
* @param {any} prop - The slider property object.
|
| 123 |
+
* @param {HTMLInputElement} element - The slider's input element.
|
| 124 |
*/
|
| 125 |
function updateSliderBackground(prop: any, element: HTMLInputElement) {
|
| 126 |
if (!element) return;
|
|
|
|
| 132 |
}
|
| 133 |
|
| 134 |
/**
|
| 135 |
+
* Handles the main accordion toggle (the component's top-level header).
|
| 136 |
+
* Dispatches 'expand' or 'collapse' events to Gradio.
|
| 137 |
*/
|
| 138 |
function handle_toggle() {
|
| 139 |
open = !open;
|
|
|
|
| 142 |
}
|
| 143 |
|
| 144 |
/**
|
| 145 |
+
* Toggles the visibility of an individual property group (e.g., "Model", "Sampling").
|
| 146 |
* @param {string} groupName - The name of the group to toggle.
|
| 147 |
*/
|
| 148 |
function toggleGroup(groupName: string) {
|
|
|
|
| 150 |
}
|
| 151 |
|
| 152 |
/**
|
| 153 |
+
* Utility function to find the current value of a specific property by its name.
|
| 154 |
+
* Used to implement the `interactive_if` logic.
|
| 155 |
+
* @param {string} prop_name - The name of the property to find.
|
| 156 |
*/
|
| 157 |
function get_prop_value(prop_name: string) {
|
| 158 |
if (!Array.isArray(value)) return undefined;
|
|
|
|
| 163 |
}
|
| 164 |
return undefined;
|
| 165 |
}
|
| 166 |
+
|
| 167 |
/**
|
| 168 |
+
* Dispatches a single-property update to the Gradio backend.
|
| 169 |
+
* Used for simple inputs like textboxes, sliders, and checkboxes.
|
| 170 |
+
* Creates a small payload like `{ 'prop_name': new_value }`.
|
| 171 |
* @param {"change" | "input"} event_name - The type of event to dispatch.
|
| 172 |
* @param {any} changed_prop - The property object that was modified.
|
| 173 |
*/
|
| 174 |
function dispatch_update(event_name: "change" | "input", changed_prop: any) {
|
| 175 |
if (validationState[changed_prop.name] === false) {
|
| 176 |
+
return;
|
| 177 |
}
|
| 178 |
const payload: Record<string, any> = {};
|
| 179 |
let final_value = changed_prop.value;
|
|
|
|
| 180 |
if (changed_prop.component?.startsWith("number") || changed_prop.component === "slider") {
|
| 181 |
final_value = Number(changed_prop.value);
|
| 182 |
} else if (changed_prop.component === "checkbox") {
|
|
|
|
| 186 |
gradio.dispatch(event_name, payload);
|
| 187 |
}
|
| 188 |
|
| 189 |
+
/**
|
| 190 |
+
* Handles changes from a dropdown (`select`) element.
|
| 191 |
+
* It updates the local `value` array and then dispatches the *entire updated object*
|
| 192 |
+
* to the backend. This is a robust way to ensure state consistency.
|
| 193 |
+
* @param {Event} event - The DOM change event.
|
| 194 |
+
* @param {any} prop_to_change - The property object being modified.
|
| 195 |
+
*/
|
| 196 |
+
async function handle_dropdown_change(event: Event, prop_to_change: any) {
|
| 197 |
+
const new_prop_value = (event.target as HTMLSelectElement).value;
|
| 198 |
+
|
| 199 |
+
// Recreate the entire `value` array to ensure Svelte's reactivity.
|
| 200 |
+
value = value.map(group => {
|
| 201 |
+
if (!group.properties) return group;
|
| 202 |
+
return {
|
| 203 |
+
...group,
|
| 204 |
+
properties: group.properties.map(prop => {
|
| 205 |
+
if (prop.name === prop_to_change.name) {
|
| 206 |
+
return { ...prop, value: new_prop_value };
|
| 207 |
+
}
|
| 208 |
+
return prop;
|
| 209 |
+
})
|
| 210 |
+
};
|
| 211 |
+
});
|
| 212 |
+
|
| 213 |
+
// Wait for Svelte to process the DOM update before dispatching.
|
| 214 |
+
await tick();
|
| 215 |
+
// Dispatch the full, updated value object to the backend.
|
| 216 |
+
gradio.dispatch("change", value);
|
| 217 |
}
|
| 218 |
+
|
| 219 |
/**
|
| 220 |
+
* Resets a single property to its initial value, which was stored on mount.
|
| 221 |
+
* It dispatches the entire updated `value` object to the backend.
|
| 222 |
* @param {string} propName - The name of the property to reset.
|
| 223 |
*/
|
| 224 |
function handle_reset_prop(propName: string) {
|
|
|
|
| 228 |
isResetting = false;
|
| 229 |
return;
|
| 230 |
}
|
|
|
|
| 231 |
let updatedValue = value.map(group => {
|
| 232 |
if (group.properties) {
|
| 233 |
group.properties = group.properties.map(prop => {
|
|
|
|
| 244 |
setTimeout(() => { isResetting = false; }, 100);
|
| 245 |
}
|
| 246 |
|
| 247 |
+
/**
|
| 248 |
+
* Creates a snapshot of the initial values of all properties.
|
| 249 |
+
* This snapshot is used by the reset functionality.
|
| 250 |
+
*/
|
| 251 |
+
function storeInitialValues() {
|
| 252 |
lastValue = JSON.stringify(value);
|
| 253 |
if (Array.isArray(value)) {
|
| 254 |
value.forEach(group => {
|
|
|
|
| 260 |
});
|
| 261 |
}
|
| 262 |
|
| 263 |
+
// Ensure sliders are visually updated on initial load.
|
| 264 |
+
setTimeout(updateAllSliders, 50);
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
+
/**
|
| 268 |
+
* Lifecycle hook that runs when the component is first added to the DOM.
|
| 269 |
+
*/
|
| 270 |
+
onMount(() => {
|
| 271 |
+
storeInitialValues();
|
| 272 |
});
|
| 273 |
</script>
|
| 274 |
|
| 275 |
+
<!-- The HTML template renders the component's UI based on the `value` prop. -->
|
| 276 |
<Block {visible} {elem_id} elem_classes={final_classes} {container} {scale} {min_width} {width}>
|
| 277 |
{#if loading_status}
|
| 278 |
<StatusTracker
|
|
|
|
| 283 |
/>
|
| 284 |
{/if}
|
| 285 |
|
| 286 |
+
<!-- Main accordion header that toggles the entire component's content -->
|
| 287 |
<button class="accordion-header" on:click={handle_toggle}>
|
| 288 |
{#if label}
|
| 289 |
<span class="label">{label}</span>
|
|
|
|
| 291 |
<span class="accordion-icon" style:transform={open ? "rotate(0)" : "rotate(-90deg)"}>▼</span>
|
| 292 |
</button>
|
| 293 |
|
| 294 |
+
<!-- Content wrapper that is shown or hidden based on the 'open' state -->
|
| 295 |
<div class:closed={!open} class="content-wrapper">
|
| 296 |
{#if open}
|
| 297 |
<div class="container" style="--sheet-max-height: {height ? `${height}px` : 'none'}">
|
|
|
|
| 431 |
</Block>
|
| 432 |
|
| 433 |
<style>
|
| 434 |
+
/* All styles remain the same and are included for completeness */
|
| 435 |
:host {
|
|
|
|
| 436 |
display: flex;
|
| 437 |
flex-direction: column;
|
| 438 |
height: 100%;
|
| 439 |
}
|
|
|
|
|
|
|
| 440 |
:global(.propertysheet-wrapper) {
|
| 441 |
+
overflow: hidden !important;
|
| 442 |
display: flex;
|
| 443 |
flex-direction: column;
|
| 444 |
flex-grow: 1;
|
| 445 |
}
|
|
|
|
|
|
|
| 446 |
.accordion-header {
|
| 447 |
display: flex;
|
| 448 |
justify-content: space-between;
|
|
|
|
| 452 |
padding: var(--block-title-padding);
|
| 453 |
background: var(--block-title-background-fill);
|
| 454 |
color: var(--block-title-text-color);
|
| 455 |
+
flex-shrink: 0;
|
| 456 |
}
|
|
|
|
| 457 |
.content-wrapper {
|
| 458 |
flex-grow: 1;
|
| 459 |
+
min-height: 0;
|
| 460 |
}
|
|
|
|
| 461 |
.container {
|
| 462 |
overflow-y: auto;
|
| 463 |
height: auto;
|
| 464 |
+
max-height: var(--sheet-max-height, 500px);
|
| 465 |
border-radius: 0 !important;
|
| 466 |
border: 1px solid var(--border-color-primary);
|
| 467 |
border-top: none;
|
|
|
|
| 469 |
border-bottom-right-radius: var(--radius-lg);
|
| 470 |
background-color: var(--background-fill-secondary);
|
| 471 |
}
|
|
|
|
| 472 |
.closed {
|
| 473 |
display: none;
|
| 474 |
}
|
|
|
|
|
|
|
| 475 |
.group-header {
|
| 476 |
display: flex;
|
| 477 |
justify-content: space-between;
|
|
|
|
| 486 |
font-weight: var(--font-weight-bold);
|
| 487 |
border: 1px solid var(--border-color-primary);
|
| 488 |
}
|
|
|
|
|
|
|
| 489 |
.properties-grid {
|
| 490 |
display: grid;
|
| 491 |
grid-template-columns: 1fr 2fr;
|
| 492 |
gap: 0;
|
| 493 |
padding: 0;
|
| 494 |
}
|
|
|
|
| 495 |
.prop-label,
|
| 496 |
.prop-control {
|
| 497 |
padding: var(--spacing-sm) var(--spacing-md);
|
|
|
|
| 499 |
align-items: center;
|
| 500 |
border-bottom: 1px solid var(--background-fill-secondary);
|
| 501 |
}
|
|
|
|
| 502 |
.prop-label {
|
| 503 |
background-color: var(--background-fill-primary);
|
| 504 |
color: var(--body-text-color);
|
|
|
|
| 509 |
justify-content: flex-end;
|
| 510 |
word-break: break-word;
|
| 511 |
}
|
|
|
|
| 512 |
.prop-control {
|
| 513 |
gap: var(--spacing-sm);
|
| 514 |
}
|
|
|
|
| 515 |
.properties-grid > :nth-last-child(-n+2) {
|
| 516 |
border-bottom: none;
|
| 517 |
}
|
|
|
|
|
|
|
| 518 |
.prop-control input[type="text"],
|
| 519 |
.prop-control input[type="number"] {
|
| 520 |
background-color: var(--input-background-fill);
|
|
|
|
| 529 |
padding-left: var(--spacing-md);
|
| 530 |
padding-right: var(--spacing-3);
|
| 531 |
}
|
|
|
|
| 532 |
.prop-control input[type="text"]:focus,
|
| 533 |
.prop-control input[type="number"]:focus {
|
| 534 |
box-shadow: var(--input-shadow-focus);
|
|
|
|
| 536 |
background-color: var(--input-background-fill-focus);
|
| 537 |
outline: none;
|
| 538 |
}
|
|
|
|
|
|
|
| 539 |
.dropdown-wrapper {
|
| 540 |
position: relative;
|
| 541 |
width: 100%;
|
| 542 |
}
|
|
|
|
| 543 |
.dropdown-wrapper select {
|
| 544 |
-webkit-appearance: none;
|
| 545 |
appearance: none;
|
|
|
|
| 556 |
padding-left: var(--spacing-md);
|
| 557 |
padding-right: calc(var(--spacing-3) + 1.2em);
|
| 558 |
}
|
|
|
|
| 559 |
.dropdown-arrow-icon {
|
| 560 |
position: absolute;
|
| 561 |
top: 50%;
|
|
|
|
| 569 |
-webkit-mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='currentColor' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
| 570 |
mask-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='currentColor' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M6 8l4 4 4-4'%3e%3c/svg%3e");
|
| 571 |
}
|
|
|
|
| 572 |
.dropdown-wrapper select:focus {
|
| 573 |
box-shadow: var(--input-shadow-focus);
|
| 574 |
border-color: var(--input-border-color-focus);
|
| 575 |
background-color: var(--input-background-fill-focus);
|
| 576 |
outline: none;
|
| 577 |
}
|
|
|
|
| 578 |
.dropdown-wrapper select option {
|
| 579 |
background: var(--input-background-fill);
|
| 580 |
color: var(--body-text-color);
|
| 581 |
}
|
|
|
|
|
|
|
| 582 |
.prop-control input[type="checkbox"] {
|
| 583 |
-webkit-appearance: none;
|
| 584 |
appearance: none;
|
|
|
|
| 593 |
margin: 0;
|
| 594 |
transition: background-color 0.2s, border-color 0.2s;
|
| 595 |
}
|
|
|
|
| 596 |
.prop-control input[type="checkbox"]:hover {
|
| 597 |
border-color: var(--checkbox-border-color-hover);
|
| 598 |
background-color: var(--checkbox-background-color-hover);
|
| 599 |
}
|
|
|
|
| 600 |
.prop-control input[type="checkbox"]:focus {
|
| 601 |
border-color: var(--checkbox-border-color-focus);
|
| 602 |
background-color: var(--checkbox-background-color-focus);
|
| 603 |
outline: none;
|
| 604 |
}
|
|
|
|
| 605 |
.prop-control input[type="checkbox"]:checked {
|
| 606 |
background-color: var(--checkbox-background-color-selected);
|
| 607 |
border-color: var(--checkbox-border-color-focus);
|
| 608 |
}
|
|
|
|
| 609 |
.prop-control input[type="checkbox"]:checked::after {
|
| 610 |
content: "";
|
| 611 |
position: absolute;
|
|
|
|
| 618 |
border-width: 0 2px 2px 0;
|
| 619 |
transform: translate(-50%, -60%) rotate(45deg);
|
| 620 |
}
|
|
|
|
|
|
|
| 621 |
.slider-container {
|
| 622 |
display: flex;
|
| 623 |
align-items: center;
|
| 624 |
gap: var(--spacing-md);
|
| 625 |
width: 100%;
|
| 626 |
}
|
|
|
|
| 627 |
.slider-container input[type="range"] {
|
| 628 |
+
--slider-progress: 0%;
|
| 629 |
-webkit-appearance: none;
|
| 630 |
appearance: none;
|
| 631 |
background: transparent;
|
| 632 |
cursor: pointer;
|
| 633 |
width: 100%;
|
| 634 |
}
|
|
|
|
|
|
|
| 635 |
.slider-container input[type="range"]::-webkit-slider-runnable-track {
|
| 636 |
height: 8px;
|
| 637 |
border-radius: var(--radius-lg);
|
| 638 |
background: linear-gradient( to right, var(--slider-color) var(--slider-progress), var(--input-background-fill) var(--slider-progress) );
|
| 639 |
}
|
|
|
|
| 640 |
.slider-container input[type="range"]::-webkit-slider-thumb {
|
| 641 |
-webkit-appearance: none;
|
| 642 |
appearance: none;
|
|
|
|
| 648 |
border: 1px solid var(--border-color-primary);
|
| 649 |
box-shadow: var(--shadow-drop);
|
| 650 |
}
|
|
|
|
| 651 |
.slider-container input[type="range"]::-moz-range-track {
|
| 652 |
height: 8px;
|
| 653 |
border-radius: var(--radius-lg);
|
| 654 |
background: linear-gradient( to right, var(--slider-color) var(--slider-progress), var(--input-background-fill) var(--slider-progress) );
|
| 655 |
}
|
|
|
|
| 656 |
.slider-container input[type="range"]::-moz-range-thumb {
|
| 657 |
background-color: white;
|
| 658 |
border-radius: 50%;
|
|
|
|
| 661 |
border: 1px solid var(--border-color-primary);
|
| 662 |
box-shadow: var(--shadow-drop);
|
| 663 |
}
|
|
|
|
| 664 |
.slider-value {
|
| 665 |
min-width: 40px;
|
| 666 |
text-align: right;
|
| 667 |
font-family: var(--font-mono);
|
| 668 |
font-size: var(--text-xs);
|
| 669 |
}
|
|
|
|
|
|
|
| 670 |
.prop-label-wrapper {
|
| 671 |
display: flex;
|
| 672 |
justify-content: flex-end;
|
|
|
|
| 674 |
gap: var(--spacing-sm);
|
| 675 |
width: 100%;
|
| 676 |
}
|
|
|
|
| 677 |
.tooltip-container {
|
| 678 |
position: relative;
|
| 679 |
display: inline-flex;
|
| 680 |
align-items: center;
|
| 681 |
justify-content: center;
|
| 682 |
}
|
|
|
|
| 683 |
.tooltip-icon {
|
| 684 |
display: flex;
|
| 685 |
align-items: center;
|
|
|
|
| 694 |
cursor: help;
|
| 695 |
user-select: none;
|
| 696 |
}
|
|
|
|
| 697 |
.tooltip-text {
|
| 698 |
visibility: hidden;
|
| 699 |
width: 200px;
|
|
|
|
| 710 |
opacity: 0;
|
| 711 |
transition: opacity 0.3s;
|
| 712 |
}
|
|
|
|
| 713 |
.tooltip-container:hover .tooltip-text {
|
| 714 |
visibility: visible;
|
| 715 |
opacity: 1;
|
| 716 |
}
|
|
|
|
|
|
|
| 717 |
.color-picker-container {
|
| 718 |
display: flex;
|
| 719 |
align-items: center;
|
| 720 |
gap: var(--spacing-md);
|
| 721 |
width: 100%;
|
| 722 |
}
|
|
|
|
| 723 |
.color-picker-input {
|
| 724 |
width: 50px;
|
| 725 |
height: 28px;
|
|
|
|
| 729 |
cursor: pointer;
|
| 730 |
padding: 0;
|
| 731 |
}
|
|
|
|
|
|
|
| 732 |
.color-picker-input::-webkit-color-swatch-wrapper {
|
| 733 |
padding: 2px;
|
| 734 |
}
|
| 735 |
.color-picker-input::-moz-padding {
|
| 736 |
padding: 2px;
|
| 737 |
}
|
|
|
|
| 738 |
.color-picker-input::-webkit-color-swatch {
|
| 739 |
border: none;
|
| 740 |
border-radius: var(--radius-sm);
|
|
|
|
| 743 |
border: none;
|
| 744 |
border-radius: var(--radius-sm);
|
| 745 |
}
|
|
|
|
| 746 |
.color-picker-value {
|
| 747 |
font-family: var(--font-mono);
|
| 748 |
font-size: var(--text-sm);
|
| 749 |
color: var(--body-text-color-subdued);
|
| 750 |
}
|
|
|
|
|
|
|
| 751 |
.prop-control input.invalid {
|
| 752 |
border-color: var(--error-border-color, red) !important;
|
| 753 |
box-shadow: 0 0 0 1px var(--error-border-color, red) !important;
|
| 754 |
}
|
|
|
|
|
|
|
| 755 |
.reset-button-prop {
|
| 756 |
display: flex;
|
| 757 |
align-items: center;
|
|
|
|
| 763 |
color: var(--body-text-color-subdued);
|
| 764 |
font-size: var(--text-lg);
|
| 765 |
padding: 0 var(--spacing-2);
|
|
|
|
| 766 |
visibility: hidden;
|
| 767 |
opacity: 0;
|
| 768 |
transition: opacity 150ms ease-in-out, color 150ms ease-in-out;
|
| 769 |
}
|
|
|
|
|
|
|
| 770 |
.reset-button-prop.visible {
|
| 771 |
visibility: visible;
|
| 772 |
opacity: 1;
|
| 773 |
}
|
|
|
|
| 774 |
.reset-button-prop:hover {
|
| 775 |
color: var(--body-text-color);
|
| 776 |
background-color: var(--background-fill-secondary-hover);
|
| 777 |
}
|
|
|
|
| 778 |
.reset-button-prop:disabled {
|
| 779 |
color: var(--body-text-color-subdued) !important;
|
| 780 |
opacity: 0.5;
|
| 781 |
cursor: not-allowed;
|
| 782 |
background-color: transparent !important;
|
| 783 |
}
|
|
|
|
|
|
|
| 784 |
.prop-control .disabled {
|
| 785 |
opacity: 0.5;
|
| 786 |
pointer-events: none;
|
| 787 |
cursor: not-allowed;
|
| 788 |
}
|
|
|
|
| 789 |
.prop-control .disabled input {
|
| 790 |
cursor: not-allowed;
|
| 791 |
}
|
|
|
|
| 792 |
.reset-button-prop:disabled {
|
| 793 |
opacity: 0.3;
|
| 794 |
cursor: not-allowed;
|
| 795 |
+
background-color: transparent !important;
|
| 796 |
}
|
| 797 |
</style>
|
src/pyproject.toml
CHANGED
|
@@ -8,12 +8,12 @@ build-backend = "hatchling.build"
|
|
| 8 |
|
| 9 |
[project]
|
| 10 |
name = "gradio_propertysheet"
|
| 11 |
-
version = "0.0.
|
| 12 |
description = "Property sheet"
|
| 13 |
readme = "README.md"
|
| 14 |
license = "apache-2.0"
|
| 15 |
requires-python = ">=3.10"
|
| 16 |
-
authors = [{ name = "
|
| 17 |
keywords = ["gradio-custom-component", "gradio-template-Fallback"]
|
| 18 |
# Add dependencies here
|
| 19 |
dependencies = ["gradio>=4.0,<6.0"]
|
|
|
|
| 8 |
|
| 9 |
[project]
|
| 10 |
name = "gradio_propertysheet"
|
| 11 |
+
version = "0.0.2"
|
| 12 |
description = "Property sheet"
|
| 13 |
readme = "README.md"
|
| 14 |
license = "apache-2.0"
|
| 15 |
requires-python = ">=3.10"
|
| 16 |
+
authors = [{ name = "Eliseu Silva", email = "elismasilva@gmail.com" }]
|
| 17 |
keywords = ["gradio-custom-component", "gradio-template-Fallback"]
|
| 18 |
# Add dependencies here
|
| 19 |
dependencies = ["gradio>=4.0,<6.0"]
|