sdiazlor commited on
Commit
986b94a
·
1 Parent(s): 9d61ded

update the style

Browse files
Files changed (4) hide show
  1. README.md +2 -2
  2. app.py +40 -39
  3. pruna-logo.png +0 -0
  4. theme.py +831 -0
README.md CHANGED
@@ -1,8 +1,8 @@
1
  ---
2
  title: P-H3-Image
3
  emoji: 🖼️
4
- colorFrom: gray
5
- colorTo: gray
6
  sdk: gradio
7
  sdk_version: 6.28.0
8
  python_version: "3.12"
 
1
  ---
2
  title: P-H3-Image
3
  emoji: 🖼️
4
+ colorFrom: purple
5
+ colorTo: indigo
6
  sdk: gradio
7
  sdk_version: 6.28.0
8
  python_version: "3.12"
app.py CHANGED
@@ -25,6 +25,7 @@ from torchvision.io import encode_jpeg
25
  import p_h3_image_fast as fast
26
  from diffusers import ModularPipeline
27
  from examples import EXAMPLES
 
28
 
29
  REPO, REVISION = "PrunaAI/P-H3-Image", "ee4a685de1fbd4337a50bcf9b3c407e4f8463d49"
30
  # Canvases (width, height), multiples of 32: 4 and 8 MP as tested, 16 MP from the same ratios.
@@ -205,42 +206,42 @@ def edit(gallery, prompt, resolution, upsample):
205
  return path, upsampled, _info(timing, size, seed)
206
 
207
 
208
- with gr.Blocks(title="P-H3-Image") as demo:
209
- gr.Markdown("# P-H3-Image\nPruna's image model for text-to-image and image editing, based on MiniMax-H3. "
210
- "By default every request goes through the prompt upsampler first. "
211
- "[Model card](https://huggingface.co/PrunaAI/P-H3-Image)")
212
- with gr.Tab("Text to image"):
213
- with gr.Row():
214
- with gr.Column():
215
- t_prompt = gr.Textbox(label="Prompt", lines=4)
216
- with gr.Row():
217
- t_aspect = gr.Dropdown(list(RATIOS), value="4:3", label="Aspect ratio")
218
- t_resolution = gr.Radio(T2I_RESOLUTIONS, value="8 MP", label="Resolution")
219
- t_upsample = gr.Checkbox(value=True, label="Prompt upsampling")
220
- t_button = gr.Button("Generate", variant="primary")
221
- with gr.Column():
222
- t_image = gr.Image(label="Result", type="filepath")
223
- t_info = gr.Markdown()
224
- with gr.Accordion("Upsampled prompt", open=False):
225
- t_upsampled = gr.Textbox(show_label=False, lines=10)
226
- gr.Examples([[prompt, aspect] for _, aspect, prompt in EXAMPLES], inputs=[t_prompt, t_aspect],
227
- example_labels=[label for label, _, _ in EXAMPLES])
228
- t_button.click(text_to_image, [t_prompt, t_aspect, t_resolution, t_upsample], [t_image, t_upsampled, t_info])
229
- with gr.Tab("Edit"):
230
- with gr.Row():
231
- with gr.Column():
232
- e_images = gr.Gallery(label=f"Images (the first one is edited, up to {MAX_IMAGES})", type="pil",
233
- columns=4, height=260, interactive=True)
234
- e_prompt = gr.Textbox(label="Edit", lines=3, placeholder="e.g. put the fox from <Picture 2> on the bench")
235
- e_resolution = gr.Radio(EDIT_RESOLUTIONS, value="8 MP", label="Resolution",
236
- info="The first image's aspect ratio at this size.")
237
- e_upsample = gr.Checkbox(value=True, label="Prompt upsampling")
238
- e_button = gr.Button("Edit", variant="primary")
239
- with gr.Column():
240
- e_image = gr.Image(label="Result", type="filepath")
241
- e_info = gr.Markdown()
242
- with gr.Accordion("Upsampled prompt", open=False):
243
- e_upsampled = gr.Textbox(show_label=False, lines=10)
244
- e_button.click(edit, [e_images, e_prompt, e_resolution, e_upsample], [e_image, e_upsampled, e_info])
245
-
246
- demo.queue(max_size=20).launch()
 
25
  import p_h3_image_fast as fast
26
  from diffusers import ModularPipeline
27
  from examples import EXAMPLES
28
+ from theme import CSS, HEAD, THEME, render_footer, render_header
29
 
30
  REPO, REVISION = "PrunaAI/P-H3-Image", "ee4a685de1fbd4337a50bcf9b3c407e4f8463d49"
31
  # Canvases (width, height), multiples of 32: 4 and 8 MP as tested, 16 MP from the same ratios.
 
206
  return path, upsampled, _info(timing, size, seed)
207
 
208
 
209
+ with gr.Blocks(title="P-H3-Image", fill_width=True) as demo:
210
+ render_header()
211
+ with gr.Tabs(elem_classes="main-tabs"):
212
+ with gr.Tab("Text to image"):
213
+ with gr.Row():
214
+ with gr.Column():
215
+ t_prompt = gr.Textbox(label="Prompt", lines=4)
216
+ with gr.Row():
217
+ t_aspect = gr.Dropdown(list(RATIOS), value="4:3", label="Aspect ratio")
218
+ t_resolution = gr.Radio(T2I_RESOLUTIONS, value="8 MP", label="Resolution")
219
+ t_upsample = gr.Checkbox(value=True, label="Prompt upsampling")
220
+ t_button = gr.Button("Generate", variant="primary")
221
+ with gr.Column():
222
+ t_image = gr.Image(label="Result", type="filepath")
223
+ t_info = gr.Markdown()
224
+ with gr.Accordion("Upsampled prompt", open=False):
225
+ t_upsampled = gr.Textbox(show_label=False, lines=10)
226
+ gr.Examples([[prompt, aspect] for _, aspect, prompt in EXAMPLES], inputs=[t_prompt, t_aspect],
227
+ example_labels=[label for label, _, _ in EXAMPLES])
228
+ t_button.click(text_to_image, [t_prompt, t_aspect, t_resolution, t_upsample], [t_image, t_upsampled, t_info])
229
+ with gr.Tab("Edit"):
230
+ with gr.Row():
231
+ with gr.Column():
232
+ e_images = gr.Gallery(label=f"Images (the first one is edited, up to {MAX_IMAGES})", type="pil",
233
+ columns=4, height=260, interactive=True)
234
+ e_prompt = gr.Textbox(label="Edit", lines=3, placeholder="e.g. put the fox from <Picture 2> on the bench")
235
+ e_resolution = gr.Radio(EDIT_RESOLUTIONS, value="8 MP", label="Resolution",
236
+ info="The first image's aspect ratio at this size.")
237
+ e_upsample = gr.Checkbox(value=True, label="Prompt upsampling")
238
+ e_button = gr.Button("Edit", variant="primary")
239
+ with gr.Column():
240
+ e_image = gr.Image(label="Result", type="filepath")
241
+ e_info = gr.Markdown()
242
+ with gr.Accordion("Upsampled prompt", open=False):
243
+ e_upsampled = gr.Textbox(show_label=False, lines=10)
244
+ e_button.click(edit, [e_images, e_prompt, e_resolution, e_upsample], [e_image, e_upsampled, e_info])
245
+ render_footer()
246
+
247
+ demo.queue(max_size=20).launch(theme=THEME, css=CSS, head=HEAD)
pruna-logo.png ADDED
theme.py ADDED
@@ -0,0 +1,831 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """P-Bench visual language: tokens, Gradio theme, header, and footer."""
2
+
3
+ import base64
4
+ from pathlib import Path
5
+
6
+ import gradio as gr
7
+
8
+ _LOGO_PATH = Path(__file__).resolve().parent / "pruna-logo.png"
9
+ _LOGO_DATA_URI = (
10
+ "data:image/png;base64,"
11
+ + base64.b64encode(_LOGO_PATH.read_bytes()).decode("ascii")
12
+ if _LOGO_PATH.exists()
13
+ else "https://framerusercontent.com/assets/EUwwqhBQJ7gF86kTZTh8SMO4B94.svg"
14
+ )
15
+
16
+ THEME = gr.themes.Base(
17
+ primary_hue=gr.themes.Color(
18
+ c50="#faf5ff",
19
+ c100="#f3e8ff",
20
+ c200="#e9d5ff",
21
+ c300="#d8b4fe",
22
+ c400="#c084fc",
23
+ c500="#9334e9",
24
+ c600="#9334e9",
25
+ c700="#7e22ce",
26
+ c800="#6b21a8",
27
+ c900="#3b0764",
28
+ c950="#120b1b",
29
+ ),
30
+ secondary_hue="zinc",
31
+ neutral_hue="zinc",
32
+ font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui", "sans-serif"],
33
+ font_mono=[gr.themes.GoogleFont("IBM Plex Mono"), "ui-monospace", "monospace"],
34
+ ).set(
35
+ body_background_fill="#f6f3fa",
36
+ body_background_fill_dark="#120b1b",
37
+ body_text_color="#18181b",
38
+ body_text_color_dark="#fafafa",
39
+ body_text_color_subdued="#71717a",
40
+ body_text_color_subdued_dark="#a3a3a3",
41
+ background_fill_primary="#f6f3fa",
42
+ background_fill_primary_dark="#120b1b",
43
+ background_fill_secondary="#ffffff",
44
+ background_fill_secondary_dark="#171021",
45
+ block_background_fill="#ffffff",
46
+ block_background_fill_dark="#1d1429",
47
+ block_border_color="#e4d7f2",
48
+ block_border_color_dark="#4a3962bf",
49
+ block_label_text_color="#71717a",
50
+ block_label_text_color_dark="#a3a3a3",
51
+ block_title_text_color="#18181b",
52
+ block_title_text_color_dark="#fafafa",
53
+ border_color_primary="#e4d7f2",
54
+ border_color_primary_dark="#4a3962bf",
55
+ button_primary_background_fill="#9334e9",
56
+ button_primary_background_fill_dark="#9334e9",
57
+ button_primary_background_fill_hover="#7c22ce",
58
+ button_primary_background_fill_hover_dark="#8f35ff",
59
+ button_primary_text_color="#ffffff",
60
+ button_primary_text_color_dark="#ffffff",
61
+ button_secondary_background_fill="#ffffff",
62
+ button_secondary_background_fill_dark="#ffffff05",
63
+ button_secondary_background_fill_hover="#f3e8ff",
64
+ button_secondary_background_fill_hover_dark="rgba(147, 52, 233, 0.10)",
65
+ button_secondary_text_color="#18181b",
66
+ button_secondary_text_color_dark="#fafafa",
67
+ button_secondary_border_color="#e4d7f2",
68
+ button_secondary_border_color_dark="#d8b4fe26",
69
+ input_background_fill="#ffffff",
70
+ input_background_fill_dark="#171021",
71
+ input_border_color="#e4d7f2",
72
+ input_border_color_dark="#4a3962bf",
73
+ input_placeholder_color="#71717a",
74
+ input_placeholder_color_dark="#a3a3a3",
75
+ link_text_color="#6d28d9",
76
+ link_text_color_dark="#bf8bff",
77
+ link_text_color_hover="#7c22ce",
78
+ link_text_color_hover_dark="#d2a8ff",
79
+ shadow_drop="none",
80
+ shadow_drop_lg="none",
81
+ )
82
+
83
+ # Visual tokens from https://www.pruna.ai/, https://playground.pruna.ai/, and PrunaAI/P-Bench.
84
+ CSS = """
85
+ :root, :host, .dark, :host(.dark) {
86
+ --pruna-bg-primary: #120b1b;
87
+ --pruna-bg-card: #171021;
88
+ --pruna-bg-elevated: #1d1429;
89
+ --pruna-bg-header: #17101f;
90
+ --pruna-border: rgba(74, 57, 98, 0.75);
91
+ --pruna-hairline: rgba(255, 255, 255, 0.08);
92
+ --pruna-text-primary: #fafafa;
93
+ --pruna-text-body: #d4d4d4;
94
+ --pruna-text-muted: #a3a3a3;
95
+ --pruna-accent: #9334e9;
96
+ --pruna-accent-bright: #8f35ff;
97
+ --pruna-lavender: #d8b4fe;
98
+ --pruna-link: #bf8bff;
99
+ --pruna-font: "Inter", ui-sans-serif, system-ui, -apple-system, sans-serif;
100
+ --pruna-glow: radial-gradient(ellipse 60% 35% at 50% -5%, rgba(147, 52, 233, 0.18), transparent 55%);
101
+ --pruna-card-bg: color-mix(in oklab, #1d1429 92%, black 8%);
102
+ --pruna-card-shadow: 0 24px 55px rgba(0, 0, 0, 0.42), inset 0 1px rgba(255, 255, 255, 0.04);
103
+ --pruna-menu-border: rgba(255, 255, 255, 0.08);
104
+ --pruna-menu-bg: rgba(255, 255, 255, 0.03);
105
+ --pruna-menu-hover-bg: rgba(255, 255, 255, 0.05);
106
+ --pruna-menu-hover-color: #e4e4e7;
107
+ --pruna-tab-hover: #e4e4e7;
108
+ --pruna-tab-selected: #ffffff;
109
+ --pruna-input-bg: rgba(255, 255, 255, 0.03);
110
+ --pruna-input-border: rgba(255, 255, 255, 0.08);
111
+ --pruna-focus-border: rgba(143, 53, 255, 0.45);
112
+ --pruna-focus-ring: 0 0 0 3px rgba(143, 53, 255, 0.16);
113
+ --pruna-accordion-bg: rgba(255, 255, 255, 0.02);
114
+ --pruna-accordion-border: rgba(216, 180, 254, 0.15);
115
+ --pruna-dropdown-hover: #2a1844;
116
+ color-scheme: dark;
117
+ }
118
+
119
+ .light, :host(.light) {
120
+ --pruna-bg-primary: #f6f3fa;
121
+ --pruna-bg-card: #ffffff;
122
+ --pruna-bg-elevated: #ffffff;
123
+ --pruna-bg-header: #f3eef8;
124
+ --pruna-border: rgba(126, 34, 206, 0.16);
125
+ --pruna-hairline: rgba(24, 24, 27, 0.08);
126
+ --pruna-text-primary: #18181b;
127
+ --pruna-text-body: #3f3f46;
128
+ --pruna-text-muted: #71717a;
129
+ --pruna-accent: #9334e9;
130
+ --pruna-accent-bright: #7c22ce;
131
+ --pruna-lavender: #7e22ce;
132
+ --pruna-link: #6d28d9;
133
+ --pruna-glow: radial-gradient(ellipse 60% 35% at 50% -5%, rgba(147, 52, 233, 0.10), transparent 55%);
134
+ --pruna-card-bg: #ffffff;
135
+ --pruna-card-shadow: 0 10px 28px rgba(88, 28, 135, 0.08);
136
+ --pruna-menu-border: var(--pruna-border);
137
+ --pruna-menu-bg: var(--pruna-bg-card);
138
+ --pruna-menu-hover-bg: rgba(147, 52, 233, 0.06);
139
+ --pruna-menu-hover-color: var(--pruna-text-primary);
140
+ --pruna-tab-hover: var(--pruna-text-primary);
141
+ --pruna-tab-selected: var(--pruna-text-primary);
142
+ --pruna-input-bg: var(--pruna-bg-card);
143
+ --pruna-input-border: var(--pruna-border);
144
+ --pruna-focus-border: rgba(124, 34, 206, 0.45);
145
+ --pruna-focus-ring: 0 0 0 3px rgba(143, 53, 255, 0.16);
146
+ --pruna-accordion-bg: var(--pruna-bg-card);
147
+ --pruna-accordion-border: var(--pruna-border);
148
+ --pruna-dropdown-hover: #f3e8ff;
149
+ color-scheme: light;
150
+ }
151
+
152
+ html {
153
+ width: 100% !important;
154
+ max-width: 100% !important;
155
+ min-width: 0 !important;
156
+ overflow-x: hidden;
157
+ overflow-y: auto;
158
+ -webkit-text-size-adjust: 100%;
159
+ text-size-adjust: 100%;
160
+ -webkit-tap-highlight-color: transparent;
161
+ }
162
+ body, gradio-app {
163
+ width: 100% !important;
164
+ max-width: 100% !important;
165
+ min-width: 0 !important;
166
+ overflow: visible;
167
+ -webkit-tap-highlight-color: transparent;
168
+ }
169
+ html, body, gradio-app, .gradio-container, .main,
170
+ .gradio-container .main,
171
+ .gradio-container .fillable,
172
+ .gradio-container .app,
173
+ .gradio-container .wrap,
174
+ .gradio-container .contain {
175
+ background-color: var(--pruna-bg-primary) !important;
176
+ background-image: var(--pruna-glow) !important;
177
+ background-repeat: no-repeat !important;
178
+ background-attachment: scroll !important;
179
+ color: var(--pruna-text-primary) !important;
180
+ font-family: var(--pruna-font) !important;
181
+ }
182
+ @media (min-width: 701px) and (hover: hover) and (pointer: fine) {
183
+ html, body, gradio-app, .gradio-container, .main,
184
+ .gradio-container .main,
185
+ .gradio-container .fillable,
186
+ .gradio-container .app {
187
+ background-attachment: fixed !important;
188
+ }
189
+ }
190
+ button, a, label, input, select, textarea,
191
+ [role="button"], [role="tab"], [role="radio"],
192
+ .gradio-container * {
193
+ -webkit-tap-highlight-color: transparent !important;
194
+ }
195
+
196
+ :root, :host, .dark, .light, gradio-app, .gradio-container {
197
+ --input-radius: 10px !important;
198
+ --container-radius: 10px !important;
199
+ --block-radius: 10px !important;
200
+ --embed-radius: 10px !important;
201
+ --table-radius: 10px !important;
202
+ --checkbox-border-radius: 4px !important;
203
+ --button-large-radius: 8px !important;
204
+ --button-medium-radius: 8px !important;
205
+ --button-small-radius: 8px !important;
206
+ --radius-md: 8px !important;
207
+ --radius-lg: 10px !important;
208
+ --radius-xl: 10px !important;
209
+ --radius-2xl: 10px !important;
210
+ --radius-3xl: 10px !important;
211
+ --radius-xxl: 10px !important;
212
+ }
213
+
214
+ .gradio-container {
215
+ width: 100% !important;
216
+ max-width: 1200px !important;
217
+ min-width: 0 !important;
218
+ margin: 0 auto !important;
219
+ padding-top: 0 !important;
220
+ padding-left: 20px !important;
221
+ padding-right: 20px !important;
222
+ box-sizing: border-box !important;
223
+ overflow-x: hidden;
224
+ }
225
+
226
+ .gradio-container .main,
227
+ .gradio-container .wrap,
228
+ .gradio-container .contain,
229
+ .gradio-container .block,
230
+ .gradio-container .form,
231
+ .gradio-container .row,
232
+ .gradio-container .column,
233
+ .gradio-container .gap,
234
+ .gradio-container .html-container,
235
+ .gradio-container .prose,
236
+ .gradio-container .padded,
237
+ .main-tabs,
238
+ .main-tabs .tab-wrapper,
239
+ .main-tabs .tabitem {
240
+ max-width: 100% !important;
241
+ min-width: 0 !important;
242
+ }
243
+
244
+ .app-header-host,
245
+ .app-header-host .html-container,
246
+ .app-header-host .prose,
247
+ .app-header-host.block,
248
+ .app-header-host .padded {
249
+ border: none !important;
250
+ background: transparent !important;
251
+ box-shadow: none !important;
252
+ padding: 0 !important;
253
+ margin: 0 auto !important;
254
+ width: 100% !important;
255
+ max-width: none !important;
256
+ display: block !important;
257
+ text-align: center !important;
258
+ overflow: visible !important;
259
+ }
260
+ .app-header {
261
+ position: relative;
262
+ display: flex;
263
+ flex-direction: column;
264
+ align-items: stretch;
265
+ width: 100%;
266
+ margin: 8px 0 0;
267
+ padding: 4px 0 10px;
268
+ text-align: center;
269
+ }
270
+ .app-header-bar {
271
+ display: grid;
272
+ grid-template-columns: minmax(36px, 1fr) auto minmax(36px, 1fr);
273
+ align-items: center;
274
+ width: 100%;
275
+ column-gap: 8px;
276
+ }
277
+ .app-header .theme-toggle,
278
+ button.theme-toggle {
279
+ position: static !important;
280
+ top: auto;
281
+ right: auto;
282
+ grid-column: 3;
283
+ justify-self: end;
284
+ display: inline-flex !important;
285
+ align-items: center !important;
286
+ justify-content: center !important;
287
+ width: 36px !important;
288
+ height: 36px !important;
289
+ min-width: 36px !important;
290
+ min-height: 36px !important;
291
+ margin: 0 !important;
292
+ padding: 0 !important;
293
+ border: 1px solid var(--pruna-menu-border) !important;
294
+ border-radius: 10px !important;
295
+ background: var(--pruna-menu-bg) !important;
296
+ color: var(--pruna-text-muted) !important;
297
+ box-shadow: none !important;
298
+ appearance: none !important;
299
+ -webkit-appearance: none !important;
300
+ box-sizing: border-box !important;
301
+ font: inherit !important;
302
+ line-height: 0 !important;
303
+ cursor: pointer;
304
+ z-index: 6;
305
+ }
306
+ .app-header .theme-toggle:hover,
307
+ .app-header .theme-toggle:active,
308
+ button.theme-toggle:hover,
309
+ button.theme-toggle:active {
310
+ color: var(--pruna-menu-hover-color) !important;
311
+ background: var(--pruna-menu-hover-bg) !important;
312
+ border-color: var(--pruna-menu-border) !important;
313
+ box-shadow: none !important;
314
+ }
315
+ .app-header .theme-toggle:focus,
316
+ button.theme-toggle:focus { outline: none !important; }
317
+ .app-header .theme-toggle:focus-visible,
318
+ button.theme-toggle:focus-visible {
319
+ color: var(--pruna-menu-hover-color) !important;
320
+ background: var(--pruna-menu-hover-bg) !important;
321
+ border-color: var(--pruna-focus-border) !important;
322
+ box-shadow: var(--pruna-focus-ring) !important;
323
+ }
324
+ .app-header .theme-toggle svg,
325
+ button.theme-toggle svg {
326
+ width: 18px;
327
+ height: 18px;
328
+ flex-shrink: 0;
329
+ }
330
+ .app-header .theme-toggle .theme-icon-sun,
331
+ button.theme-toggle .theme-icon-sun { display: block !important; }
332
+ .app-header .theme-toggle .theme-icon-moon,
333
+ button.theme-toggle .theme-icon-moon { display: none !important; }
334
+ .app-header .theme-toggle[data-mode="light"] .theme-icon-sun,
335
+ button.theme-toggle[data-mode="light"] .theme-icon-sun { display: none !important; }
336
+ .app-header .theme-toggle[data-mode="light"] .theme-icon-moon,
337
+ button.theme-toggle[data-mode="light"] .theme-icon-moon { display: block !important; }
338
+ .app-header-brand {
339
+ display: inline-flex !important;
340
+ flex-direction: row;
341
+ align-items: center;
342
+ justify-content: center;
343
+ gap: 12px;
344
+ grid-column: 2;
345
+ justify-self: center;
346
+ width: auto !important;
347
+ max-width: 100%;
348
+ margin: 0;
349
+ }
350
+ .app-header-logo {
351
+ display: block !important;
352
+ width: 44px !important;
353
+ height: 52px !important;
354
+ margin: 0 !important;
355
+ object-fit: contain;
356
+ flex: 0 0 auto;
357
+ }
358
+ .gradio-container .app-header-brand h1,
359
+ .app-header .app-header-brand h1 {
360
+ display: block !important;
361
+ width: max-content !important;
362
+ max-width: 100% !important;
363
+ flex: 0 0 auto !important;
364
+ margin: 0 !important;
365
+ padding: 0 !important;
366
+ text-align: center !important;
367
+ font-size: clamp(1.9rem, 3vw, 2.4rem) !important;
368
+ font-weight: 700 !important;
369
+ letter-spacing: -0.035em;
370
+ line-height: 1 !important;
371
+ color: var(--pruna-text-primary) !important;
372
+ }
373
+ .app-header-tagline,
374
+ .prose .app-header-tagline,
375
+ .gradio-container .app-header-tagline {
376
+ display: block !important;
377
+ width: 100% !important;
378
+ margin: 8px 0 0 !important;
379
+ padding: 0 !important;
380
+ text-align: center !important;
381
+ color: var(--pruna-text-muted) !important;
382
+ font-size: 0.95rem !important;
383
+ line-height: 1.45 !important;
384
+ font-weight: 400 !important;
385
+ }
386
+
387
+ .tabs { gap: 0 !important; }
388
+ .main-tabs > .tab-wrapper,
389
+ .main-tabs > .tab-nav,
390
+ .main-tabs > .tab-container {
391
+ height: auto !important;
392
+ min-height: 0 !important;
393
+ padding: 0 !important;
394
+ margin: 0 0 16px !important;
395
+ justify-content: center !important;
396
+ width: 100% !important;
397
+ overflow: visible !important;
398
+ }
399
+ .main-tabs .tab-container,
400
+ .main-tabs .tab-nav {
401
+ height: auto !important;
402
+ min-height: 0 !important;
403
+ justify-content: center !important;
404
+ flex-wrap: wrap !important;
405
+ overflow: visible !important;
406
+ max-width: 100% !important;
407
+ gap: 2px;
408
+ }
409
+ .main-tabs .tab-container::after,
410
+ .main-tabs .tab-nav::after {
411
+ background: var(--pruna-hairline) !important;
412
+ }
413
+ .main-tabs .tab-container > button,
414
+ .main-tabs .tab-nav > button,
415
+ .main-tabs button[role="tab"] {
416
+ font-family: var(--pruna-font) !important;
417
+ font-size: 0.95rem !important;
418
+ font-weight: 600 !important;
419
+ letter-spacing: -0.015em !important;
420
+ color: var(--pruna-text-muted) !important;
421
+ background: transparent !important;
422
+ border: none !important;
423
+ border-radius: 0 !important;
424
+ box-shadow: none !important;
425
+ padding: 12px 20px !important;
426
+ min-height: 44px !important;
427
+ height: auto !important;
428
+ }
429
+ .main-tabs .tab-container > button:hover:not(.selected),
430
+ .main-tabs .tab-nav > button:hover:not(.selected),
431
+ .main-tabs button[role="tab"]:hover:not(.selected):not([aria-selected="true"]) {
432
+ color: var(--pruna-tab-hover) !important;
433
+ background: transparent !important;
434
+ }
435
+ .main-tabs .tab-container > button.selected,
436
+ .main-tabs .tab-container > button[aria-selected="true"],
437
+ .main-tabs .tab-nav > button.selected,
438
+ .main-tabs .tab-nav > button[aria-selected="true"],
439
+ .main-tabs button[role="tab"][aria-selected="true"] {
440
+ color: var(--pruna-tab-selected) !important;
441
+ background: transparent !important;
442
+ }
443
+ .main-tabs .tab-container > button.selected::after,
444
+ .main-tabs .tab-container > button[aria-selected="true"]::after,
445
+ .main-tabs .tab-nav > button.selected::after,
446
+ .main-tabs .tab-nav > button[aria-selected="true"]::after,
447
+ .main-tabs button[role="tab"][aria-selected="true"]::after {
448
+ background: var(--pruna-accent-bright) !important;
449
+ height: 2px !important;
450
+ }
451
+ .main-tabs .tabitem {
452
+ padding: 8px 0 0 !important;
453
+ border-radius: 0 !important;
454
+ overflow: visible !important;
455
+ }
456
+
457
+ .gradio-container h1, .markdown h1, .md h1, .prose h1 {
458
+ margin: 0 0 0.65rem !important;
459
+ padding: 0 !important;
460
+ color: var(--pruna-text-primary) !important;
461
+ font-family: var(--pruna-font) !important;
462
+ font-size: 1.5rem !important;
463
+ font-weight: 700 !important;
464
+ letter-spacing: -0.03em !important;
465
+ line-height: 1.2 !important;
466
+ text-transform: none !important;
467
+ }
468
+ .markdown, .md, .prose,
469
+ .gradio-container .markdown,
470
+ .gradio-container .prose {
471
+ overflow-wrap: break-word;
472
+ max-width: 100%;
473
+ }
474
+ .markdown p, .md p, .prose p,
475
+ .gradio-container .markdown p {
476
+ margin: 0 0 0.8rem !important;
477
+ color: var(--pruna-text-body) !important;
478
+ font-size: 0.95rem !important;
479
+ font-weight: 400 !important;
480
+ line-height: 1.55 !important;
481
+ }
482
+
483
+ label, .block-label span {
484
+ color: var(--pruna-text-muted) !important;
485
+ font-size: 0.8rem !important;
486
+ font-weight: 500 !important;
487
+ }
488
+ a { color: var(--pruna-link); }
489
+
490
+ button.primary, button.lg.primary, .gr-button-primary, .primary-button,
491
+ button.primary:hover, button.primary:active, button.primary:focus,
492
+ .gr-button-primary:hover, .gr-button-primary:active {
493
+ background: var(--pruna-accent) !important;
494
+ border: 1px solid color-mix(in oklab, var(--pruna-accent) 72%, transparent) !important;
495
+ color: #fff !important;
496
+ border-radius: 8px !important;
497
+ box-shadow: inset 0 1px #fff3 !important;
498
+ font-weight: 600 !important;
499
+ outline: none !important;
500
+ }
501
+ button.secondary, button.lg.secondary, .gr-button-secondary,
502
+ .gallery-item, .examples button {
503
+ border-radius: 10px !important;
504
+ }
505
+ .block, .gr-group, .form, .panel, .gr-accordion {
506
+ border-color: var(--pruna-border) !important;
507
+ background: transparent !important;
508
+ border-radius: 10px !important;
509
+ }
510
+ .wrap-inner, .secondary-wrap {
511
+ border: 1px solid var(--pruna-input-border) !important;
512
+ border-radius: 10px !important;
513
+ background: var(--pruna-input-bg) !important;
514
+ min-height: 40px !important;
515
+ box-shadow: none !important;
516
+ }
517
+ .wrap-inner:focus-within {
518
+ border-color: var(--pruna-focus-border) !important;
519
+ box-shadow: var(--pruna-focus-ring) !important;
520
+ }
521
+ input:not([type="checkbox"]):not([type="radio"]), textarea, select {
522
+ background: var(--pruna-bg-card) !important;
523
+ border-color: var(--pruna-border) !important;
524
+ color: var(--pruna-text-primary) !important;
525
+ border-radius: 10px !important;
526
+ }
527
+ input[type="checkbox"] {
528
+ border-radius: 4px !important;
529
+ }
530
+ input:focus, textarea:focus, select:focus {
531
+ border-color: var(--pruna-focus-border) !important;
532
+ box-shadow: var(--pruna-focus-ring) !important;
533
+ outline: none !important;
534
+ }
535
+ .image-container, .upload-container, .image-frame,
536
+ [data-testid="image"], [data-testid="gallery"] {
537
+ border-radius: 10px !important;
538
+ overflow: hidden;
539
+ }
540
+
541
+ .accordion,
542
+ .accordion.block {
543
+ border: 1px solid var(--pruna-accordion-border) !important;
544
+ border-radius: 10px !important;
545
+ background: var(--pruna-accordion-bg) !important;
546
+ box-shadow: none !important;
547
+ overflow: hidden !important;
548
+ }
549
+ .accordion > .label-wrap,
550
+ .accordion .label-wrap {
551
+ border: none !important;
552
+ background: transparent !important;
553
+ box-shadow: none !important;
554
+ padding: 11px 14px !important;
555
+ font-size: 0.92rem !important;
556
+ font-weight: 500 !important;
557
+ color: var(--pruna-text-primary) !important;
558
+ letter-spacing: -0.01em !important;
559
+ }
560
+ .accordion > .label-wrap:hover,
561
+ .accordion .label-wrap:hover {
562
+ background: var(--pruna-menu-hover-bg) !important;
563
+ }
564
+
565
+ .options,
566
+ ul.options {
567
+ position: absolute !important;
568
+ top: calc(100% + 4px) !important;
569
+ bottom: auto !important;
570
+ left: 0 !important;
571
+ right: auto !important;
572
+ width: 100% !important;
573
+ max-height: min(280px, 60vh) !important;
574
+ background: var(--pruna-bg-card) !important;
575
+ color: var(--pruna-text-primary) !important;
576
+ border: 1px solid var(--pruna-border) !important;
577
+ border-radius: 10px !important;
578
+ overflow: auto !important;
579
+ z-index: 50 !important;
580
+ }
581
+ .options .item,
582
+ .options li { color: var(--pruna-text-primary) !important; }
583
+ .options .item:hover,
584
+ .options li:hover,
585
+ .options .item.selected {
586
+ background: var(--pruna-dropdown-hover) !important;
587
+ color: var(--pruna-text-primary) !important;
588
+ }
589
+
590
+ .page-footer-host,
591
+ .page-footer-host .html-container,
592
+ .page-footer-host .prose,
593
+ .page-footer-host.block,
594
+ .page-footer-host .padded {
595
+ border: none !important;
596
+ background: transparent !important;
597
+ box-shadow: none !important;
598
+ padding: 0 !important;
599
+ margin: 0 !important;
600
+ width: 100% !important;
601
+ max-width: none !important;
602
+ overflow: visible !important;
603
+ }
604
+ .page-footer {
605
+ margin: 32px 0 16px;
606
+ padding: 20px 0 0;
607
+ border-top: 1px solid var(--pruna-hairline);
608
+ }
609
+ .page-footer h3 {
610
+ margin: 0 0 10px !important;
611
+ font-size: 1.05rem !important;
612
+ font-weight: 600 !important;
613
+ letter-spacing: -0.015em !important;
614
+ text-transform: none !important;
615
+ color: var(--pruna-text-primary) !important;
616
+ }
617
+ .community-footer-links {
618
+ display: flex;
619
+ flex-wrap: wrap;
620
+ gap: 8px 16px;
621
+ align-items: center;
622
+ }
623
+ .community-footer-links a {
624
+ color: var(--pruna-text-muted) !important;
625
+ text-decoration: none !important;
626
+ font-size: 0.9rem !important;
627
+ font-weight: 500 !important;
628
+ }
629
+ .community-footer-links a:hover { color: var(--pruna-link) !important; }
630
+
631
+ footer, .footer { display: none !important; }
632
+
633
+ @media (max-width: 700px) {
634
+ .gradio-container {
635
+ padding-left: 12px !important;
636
+ padding-right: 12px !important;
637
+ }
638
+ .app-header {
639
+ padding: 4px 0 8px;
640
+ margin: 4px 0 0;
641
+ }
642
+ .app-header-logo {
643
+ width: 36px !important;
644
+ height: 42px !important;
645
+ }
646
+ .app-header-brand h1,
647
+ .gradio-container .app-header-brand h1 {
648
+ font-size: 1.55rem !important;
649
+ }
650
+ .app-header-tagline { font-size: 0.88rem !important; padding: 0 8px !important; }
651
+ .main-tabs > .tab-wrapper,
652
+ .main-tabs > .tab-nav,
653
+ .main-tabs > .tab-container { margin: 4px 0 14px !important; }
654
+ .main-tabs .tab-container > button,
655
+ .main-tabs .tab-nav > button,
656
+ .main-tabs button[role="tab"] {
657
+ padding: 10px 12px !important;
658
+ font-size: 0.88rem !important;
659
+ min-height: 40px !important;
660
+ }
661
+ .community-footer-links { gap: 10px 14px; }
662
+ }
663
+ """
664
+
665
+ HEAD = """
666
+ <script>
667
+ (function () {
668
+ var mode = "dark";
669
+ try {
670
+ var stored = localStorage.getItem("pruna-theme");
671
+ if (stored === "light" || stored === "dark") mode = stored;
672
+ else {
673
+ var param = new URLSearchParams(location.search).get("__theme");
674
+ if (param === "light" || param === "dark") mode = param;
675
+ }
676
+ } catch (e) {}
677
+ document.documentElement.classList.toggle("dark", mode === "dark");
678
+ document.documentElement.dataset.theme = mode;
679
+ })();
680
+ </script>
681
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
682
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
683
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
684
+ <script>
685
+ (() => {
686
+ const STORAGE_KEY = "pruna-theme";
687
+
688
+ const currentMode = () => {
689
+ try {
690
+ const stored = localStorage.getItem(STORAGE_KEY);
691
+ if (stored === "light" || stored === "dark") return stored;
692
+ } catch (e) {}
693
+ const param = new URLSearchParams(location.search).get("__theme");
694
+ if (param === "light" || param === "dark") return param;
695
+ return "dark";
696
+ };
697
+
698
+ const queryAll = (selector) => {
699
+ const found = [...document.querySelectorAll(selector)];
700
+ document.querySelectorAll("gradio-app").forEach((app) => {
701
+ if (app.shadowRoot) found.push(...app.shadowRoot.querySelectorAll(selector));
702
+ });
703
+ return found;
704
+ };
705
+
706
+ const applyMode = (mode, persist) => {
707
+ const dark = mode === "dark";
708
+ const targets = [
709
+ document.documentElement,
710
+ document.body,
711
+ ...queryAll(".gradio-container"),
712
+ ...document.querySelectorAll("gradio-app"),
713
+ ].filter(Boolean);
714
+ targets.forEach((el) => {
715
+ el.classList.toggle("dark", dark);
716
+ el.classList.toggle("light", !dark);
717
+ });
718
+ document.documentElement.dataset.theme = mode;
719
+ const label = dark ? "Switch to light mode" : "Switch to dark mode";
720
+ queryAll(".theme-toggle").forEach((btn) => {
721
+ btn.setAttribute("data-mode", mode);
722
+ btn.setAttribute("aria-label", label);
723
+ btn.setAttribute("title", label);
724
+ });
725
+ if (persist) {
726
+ try { localStorage.setItem(STORAGE_KEY, mode); } catch (e) {}
727
+ try {
728
+ const url = new URL(location.href);
729
+ url.searchParams.set("__theme", mode);
730
+ history.replaceState(null, "", url);
731
+ } catch (e) {}
732
+ }
733
+ };
734
+
735
+ applyMode(currentMode(), false);
736
+
737
+ document.addEventListener("click", (event) => {
738
+ const path = typeof event.composedPath === "function" ? event.composedPath() : [event.target];
739
+ const btn = path.find((el) => el.classList && el.classList.contains("theme-toggle"));
740
+ if (!btn) return;
741
+ event.preventDefault();
742
+ applyMode(currentMode() === "dark" ? "light" : "dark", true);
743
+ });
744
+
745
+ const sync = () => applyMode(currentMode(), false);
746
+ document.addEventListener("DOMContentLoaded", sync);
747
+ window.addEventListener("load", () => {
748
+ sync();
749
+ setTimeout(sync, 80);
750
+ setTimeout(sync, 400);
751
+ });
752
+
753
+ const startObserver = () => {
754
+ if (!document.body || window.__prunaThemeObserver) return;
755
+ window.__prunaThemeObserver = true;
756
+ const onMutations = (mutations) => {
757
+ const mode = currentMode();
758
+ const isDark = document.documentElement.classList.contains("dark");
759
+ const added = mutations.flatMap((mutation) => [...mutation.addedNodes]).filter((node) => node.nodeType === 1);
760
+ const hasContainer = added.some((node) => (
761
+ node.classList?.contains("gradio-container") ||
762
+ node.querySelector?.(".gradio-container, .theme-toggle")
763
+ ));
764
+ if ((mode === "dark") !== isDark || hasContainer) applyMode(mode, false);
765
+ };
766
+ new MutationObserver(onMutations).observe(document.body, { childList: true, subtree: true });
767
+ document.querySelectorAll("gradio-app").forEach((app) => {
768
+ if (app.shadowRoot) {
769
+ new MutationObserver(onMutations).observe(app.shadowRoot, { childList: true, subtree: true });
770
+ }
771
+ });
772
+ };
773
+ if (document.body) startObserver();
774
+ else document.addEventListener("DOMContentLoaded", startObserver);
775
+ })();
776
+ </script>
777
+ """
778
+
779
+
780
+ def render_header(
781
+ title: str = "P-H3-Image",
782
+ tagline: str = (
783
+ "Pruna's image model for text-to-image and image editing, based on MiniMax-H3. "
784
+ "By default every request goes through the prompt upsampler first. "
785
+ '<a href="https://huggingface.co/PrunaAI/P-H3-Image">Model card</a>'
786
+ ),
787
+ ) -> None:
788
+ gr.HTML(
789
+ f"""
790
+ <header class="app-header">
791
+ <div class="app-header-bar">
792
+ <div class="app-header-brand">
793
+ <img class="app-header-logo" src="{_LOGO_DATA_URI}" alt="" />
794
+ <h1>{title}</h1>
795
+ </div>
796
+ <button type="button" class="theme-toggle" data-mode="dark" aria-label="Switch to light mode" title="Switch to light mode">
797
+ <svg class="theme-icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
798
+ <circle cx="12" cy="12" r="4"></circle>
799
+ <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"></path>
800
+ </svg>
801
+ <svg class="theme-icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
802
+ <path d="M21 14.5A8.5 8.5 0 1 1 9.5 3 7 7 0 0 0 21 14.5z"></path>
803
+ </svg>
804
+ </button>
805
+ </div>
806
+ <p class="app-header-tagline">{tagline}</p>
807
+ </header>
808
+ """,
809
+ padding=False,
810
+ elem_classes="app-header-host",
811
+ )
812
+
813
+
814
+ def render_footer() -> None:
815
+ gr.HTML(
816
+ """
817
+ <div class="page-footer">
818
+ <h3>Join the Pruna AI community</h3>
819
+ <div class="community-footer-links">
820
+ <a rel="nofollow" href="https://x.com/PrunaAI" target="_blank">X</a>
821
+ <a rel="nofollow" href="https://www.linkedin.com/company/pruna-ai" target="_blank">LinkedIn</a>
822
+ <a rel="nofollow" href="https://www.instagram.com/pruna.ai/" target="_blank">Instagram</a>
823
+ <a rel="nofollow" href="https://discord.gg/JFQmtFKCjd" target="_blank">Discord</a>
824
+ <a rel="nofollow" href="https://github.com/PrunaAI/pruna" target="_blank">GitHub</a>
825
+ <a rel="nofollow" href="https://www.pruna.ai/" target="_blank">pruna.ai</a>
826
+ </div>
827
+ </div>
828
+ """,
829
+ padding=False,
830
+ elem_classes="page-footer-host",
831
+ )