HAL1993 commited on
Commit
9c8a3cc
·
verified ·
1 Parent(s): 39cf51b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +250 -47
app.py CHANGED
@@ -27,7 +27,7 @@ logging.basicConfig(
27
  logger = logging.getLogger(__name__)
28
 
29
  # ----------------------------------------------------------------------
30
- # Translation function (GPU‑accelerated)
31
  # ----------------------------------------------------------------------
32
  @spaces.GPU
33
  def translate_albanian_to_english(text: str, language: str = "en"):
@@ -53,12 +53,12 @@ def translate_albanian_to_english(text: str, language: str = "en"):
53
  raise gr.Error("Translation failed. Please try again.")
54
 
55
  # ----------------------------------------------------------------------
56
- # Model loading and preparation
57
  # ----------------------------------------------------------------------
58
  dtype = torch.bfloat16
59
  device = "cuda" if torch.cuda.is_available() else "cpu"
60
 
61
- # Scheduler configuration (Lightning‑optimized)
62
  scheduler_cfg = {
63
  "base_image_seq_len": 256,
64
  "base_shift": math.log(3),
@@ -77,21 +77,21 @@ scheduler_cfg = {
77
  }
78
  scheduler = FlowMatchEulerDiscreteScheduler.from_config(scheduler_cfg)
79
 
80
- # Load the Qwen‑Image‑Edit pipeline
81
  pipe = QwenImageEditPlusPipeline.from_pretrained(
82
  "Qwen/Qwen-Image-Edit-2509",
83
  scheduler=scheduler,
84
  torch_dtype=dtype,
85
  ).to(device)
86
 
87
- # Load and fuse LoRA weights (Lightning)
88
  pipe.load_lora_weights(
89
  "lightx2v/Qwen-Image-Lightning",
90
  weight_name="Qwen-Image-Lightning-4steps-V2.0.safetensors",
91
  )
92
  pipe.fuse_lora()
93
 
94
- # Replace transformer class and set custom attention processor
95
  pipe.transformer.__class__ = QwenImageTransformer2DModel
96
  pipe.transformer.set_attn_processor(QwenDoubleStreamAttnProcessorFA3())
97
 
@@ -106,7 +106,7 @@ MAX_SEED = np.iinfo(np.int32).max
106
  QUALITY_PROMPT = ", high quality, detailed, vibrant, professional lighting"
107
 
108
  # ----------------------------------------------------------------------
109
- # Inference function (GPU‑accelerated, duration hint for Spaces)
110
  # ----------------------------------------------------------------------
111
  @spaces.GPU(duration=40)
112
  def infer(image, prompt):
@@ -117,10 +117,10 @@ def infer(image, prompt):
117
  seed = random.randint(0, MAX_SEED)
118
  generator = torch.Generator(device=device).manual_seed(seed)
119
 
120
- # Translate the prompt from Albanian to English and append quality suffix
121
  prompt_en = translate_albanian_to_english(prompt.strip(), language="en") + QUALITY_PROMPT
122
 
123
- # Load the input image as a PIL Image (if provided)
124
  pil_img = None
125
  if image is not None:
126
  if isinstance(image, Image.Image):
@@ -130,7 +130,7 @@ def infer(image, prompt):
130
  elif hasattr(image, "name"):
131
  pil_img = Image.open(image.name).convert("RGB")
132
 
133
- # Run the pipeline
134
  output = pipe(
135
  image=[pil_img] if pil_img is not None else None,
136
  prompt=prompt_en,
@@ -146,13 +146,13 @@ def infer(image, prompt):
146
  return output[0] if output else None
147
 
148
  # ----------------------------------------------------------------------
149
- # Gradio UI definition
150
  # ----------------------------------------------------------------------
151
  def create_demo():
152
  with gr.Blocks(css="", title="Qwen Image Editor") as demo:
153
- # ------------------------------------------------------------------
154
- # Custom HTML + CSS + JS (enforces /spaceishere path & UI tweaks)
155
- # ------------------------------------------------------------------
156
  gr.HTML(
157
  """
158
  <style>
@@ -161,38 +161,240 @@ def create_demo():
161
  @keyframes glow-hover {0%{box-shadow:0 0 20px rgba(0,255,128,0.7);}50%{box-shadow:0 0 20px rgba(0,255,128,0.9);}100%{box-shadow:0 0 20px rgba(0,255,128,0.7);}}
162
  @keyframes slide {0%{background-position:0% 50%;}50%{background-position:100% 50%;}100%{background-position:0% 50%;}}
163
  @keyframes pulse {0%,100%{opacity:0.7;}50%{opacity:1;}}
164
- body{background:#000!important;color:#fff!important;font-family:'Orbitron',sans-serif;min-height:100vh;margin:0;padding:0;display:flex;justify-content:center;align-items:center;flex-direction:column;}
165
- .gr-blocks,.container{width:100%!important;max-width:100vw!important;margin:0;padding:0;box-sizing:border-box;overflow-x:hidden;background:#000;color:#fff;}
166
- #general_items{width:100%!important;max-width:100vw!important;margin:2rem 0;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#000;color:#fff;}
167
- #input_column{background:#000;border:none;border-radius:8px;padding:1rem;box-shadow:0 0 10px rgba(255,255,255,0.3);width:100%!important;max-width:100vw!important;box-sizing:border-box;color:#fff;}
168
- h1{font-size:5rem;font-weight:700;text-align:center;color:#fff;text-shadow:0 0 8px rgba(255,255,255,0.3);margin:0 auto .5rem;}
169
- #subtitle{font-size:1rem;text-align:center;color:#fff;opacity:.8;margin-bottom:1rem;}
170
- .gradio-component{background:#000;border:none;margin:.75rem 0;width:100%!important;max-width:100vw!important;color:#fff;}
171
- .image-container{aspect-ratio:1/1;width:100%!important;max-width:100vw!important;min-height:500px;height:auto;border:0.5px solid #fff;border-radius:4px;background:#000;box-shadow:0 0 10px rgba(255,255,255,0.3);position:relative;color:#fff;}
172
- .image-container img{width:100%!important;height:auto;display:block;}
173
- .image-container[aria-label="Result Image"].processing{background:#000;position:relative;}
174
- .image-container[aria-label="Result Image"].processing::before{content:"PROCESSING...";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#fff;font-family:'Orbitron',sans-serif;font-size:1.8rem;font-weight:700;text-shadow:0 0 10px rgba(0,255,128,0.8);animation:pulse 1.5s ease-in-out infinite,glow 2s ease-in-out infinite;z-index:9999;width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:#000;border-radius:4px;}
175
- .image-container[aria-label="Result Image"].processing *{display:none!important;}
176
- .image-container[aria-label="Result Image"].processing img{display:none!important;}
177
- input,textarea{background:#000;color:#fff;border:1px solid #fff;border-radius:4px;padding:.5rem;width:100%!important;max-width:100vw!important;box-sizing:border-box;}
178
- input:hover,textarea:hover{box-shadow:0 0 8px rgba(255,255,255,0.3);transition:box-shadow .3s;}
179
- .gr-button-primary{background:linear-gradient(90deg,rgba(0,255,128,0.3),rgba(0,200,100,0.3),rgba(0,255,128,0.3))!important;background-size:200% 100%;animation:slide 4s ease-in-out infinite,glow 3s ease-in-out infinite;color:#fff;border:1px solid #fff;border-radius:6px;padding:.75rem 1.5rem;font-size:1.1rem;font-weight:600;box-shadow:0 0 14px rgba(0,255,128,0.7);transition:box-shadow .3s,transform .3s;width:100%!important;max-width:100vw!important;min-height:48px;cursor:pointer;}
180
- .gr-button-primary:hover{box-shadow:0 0 20px rgba(0,255,128,0.9);animation:slide 4s ease-in-out infinite,glow-hover 3s ease-in-out infinite;transform:scale(1.05);}
181
- button[aria-label="Fullscreen"],button[aria-label="Share"],button[aria-label="Download"]{display:none!important;}
182
- .progress-text,.gr-progress,.gr-prose,.gr-log{display:none!important;}
183
- footer,.gr-button-secondary{display:none!important;}
184
- .gr-group{background:#000;border:none;width:100%!important;max-width:100vw!important;}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  @media (max-width:768px){
186
  h1{font-size:4rem;}
187
- #subtitle{font-size:.9rem;}
188
- .gr-button-primary{padding:.6rem 1rem;font-size:1rem;box-shadow:0 0 10px rgba(0,255,128,0.7);}
189
- .image-container{min-height:300px;box-shadow:0 0 8px rgba(255,255,255,0.3);}
190
- .image-container[aria-label="Result Image"].processing::before{font-size:1.2rem;}
 
 
 
 
 
 
 
 
 
 
191
  }
192
  </style>
193
  <script>
194
  // --------------------------------------------------------------
195
- // Enforce that the UI only works under /spaceishere (or sub‑paths)
196
  // --------------------------------------------------------------
197
  const allowedPath = /^\\/spaceishere(\\/.*)?$/;
198
  if (!allowedPath.test(window.location.pathname)) {
@@ -201,7 +403,7 @@ def create_demo():
201
  }
202
 
203
  // --------------------------------------------------------------
204
- // UI behaviour: hide processing UI, add custom “PROCESSING…” overlay
205
  // --------------------------------------------------------------
206
  document.addEventListener('DOMContentLoaded', () => {
207
  const generateBtn = document.querySelector('.gr-button-primary');
@@ -237,9 +439,9 @@ def create_demo():
237
  """
238
  )
239
 
240
- # ------------------------------------------------------------------
241
  # Layout
242
- # ------------------------------------------------------------------
243
  with gr.Row(elem_id="general_items"):
244
  gr.Markdown("# Image Edit")
245
  gr.Markdown("Edit your images with prompt descriptions", elem_id="subtitle")
@@ -272,16 +474,16 @@ def create_demo():
272
  elem_classes=["gradio-component", "image-container"],
273
  )
274
 
275
- # ------------------------------------------------------------------
276
  # Event bindings
277
- # ------------------------------------------------------------------
278
  run_button.click(fn=infer, inputs=[input_image, prompt], outputs=[result_image])
279
  prompt.submit(fn=infer, inputs=[input_image, prompt], outputs=[result_image])
280
 
281
  return demo
282
 
283
  # ----------------------------------------------------------------------
284
- # FastAPI application with strict routing
285
  # ----------------------------------------------------------------------
286
  app = FastAPI()
287
  demo = create_demo()
@@ -292,8 +494,9 @@ async def catch_all(path: str):
292
  raise HTTPException(status_code=500, detail="Internal Server Error")
293
 
294
  # ----------------------------------------------------------------------
295
- # Entry point (use `python this_file.py` to run locally)
296
  # ----------------------------------------------------------------------
297
  if __name__ == "__main__":
298
  logger.info(f"Gradio version: {gr.__version__}")
299
  demo.queue().launch(share=True)
 
 
27
  logger = logging.getLogger(__name__)
28
 
29
  # ----------------------------------------------------------------------
30
+ # Translation (GPU‑accelerated)
31
  # ----------------------------------------------------------------------
32
  @spaces.GPU
33
  def translate_albanian_to_english(text: str, language: str = "en"):
 
53
  raise gr.Error("Translation failed. Please try again.")
54
 
55
  # ----------------------------------------------------------------------
56
+ # Model loading & preparation
57
  # ----------------------------------------------------------------------
58
  dtype = torch.bfloat16
59
  device = "cuda" if torch.cuda.is_available() else "cpu"
60
 
61
+ # Lightning‑optimized scheduler configuration
62
  scheduler_cfg = {
63
  "base_image_seq_len": 256,
64
  "base_shift": math.log(3),
 
77
  }
78
  scheduler = FlowMatchEulerDiscreteScheduler.from_config(scheduler_cfg)
79
 
80
+ # Load Qwen‑Image‑Edit pipeline
81
  pipe = QwenImageEditPlusPipeline.from_pretrained(
82
  "Qwen/Qwen-Image-Edit-2509",
83
  scheduler=scheduler,
84
  torch_dtype=dtype,
85
  ).to(device)
86
 
87
+ # Load + fuse Lightning LoRA
88
  pipe.load_lora_weights(
89
  "lightx2v/Qwen-Image-Lightning",
90
  weight_name="Qwen-Image-Lightning-4steps-V2.0.safetensors",
91
  )
92
  pipe.fuse_lora()
93
 
94
+ # Replace transformer class & set custom attention processor
95
  pipe.transformer.__class__ = QwenImageTransformer2DModel
96
  pipe.transformer.set_attn_processor(QwenDoubleStreamAttnProcessorFA3())
97
 
 
106
  QUALITY_PROMPT = ", high quality, detailed, vibrant, professional lighting"
107
 
108
  # ----------------------------------------------------------------------
109
+ # Inference (GPU‑accelerated, duration hint for Spaces)
110
  # ----------------------------------------------------------------------
111
  @spaces.GPU(duration=40)
112
  def infer(image, prompt):
 
117
  seed = random.randint(0, MAX_SEED)
118
  generator = torch.Generator(device=device).manual_seed(seed)
119
 
120
+ # Translate prompt + quality suffix
121
  prompt_en = translate_albanian_to_english(prompt.strip(), language="en") + QUALITY_PROMPT
122
 
123
+ # Load input image as PIL.Image (if any)
124
  pil_img = None
125
  if image is not None:
126
  if isinstance(image, Image.Image):
 
130
  elif hasattr(image, "name"):
131
  pil_img = Image.open(image.name).convert("RGB")
132
 
133
+ # Run pipeline
134
  output = pipe(
135
  image=[pil_img] if pil_img is not None else None,
136
  prompt=prompt_en,
 
146
  return output[0] if output else None
147
 
148
  # ----------------------------------------------------------------------
149
+ # Gradio UI
150
  # ----------------------------------------------------------------------
151
  def create_demo():
152
  with gr.Blocks(css="", title="Qwen Image Editor") as demo:
153
+ # --------------------------------------------------------------
154
+ # Custom HTML + full CSS (including top gap) + JS
155
+ # --------------------------------------------------------------
156
  gr.HTML(
157
  """
158
  <style>
 
161
  @keyframes glow-hover {0%{box-shadow:0 0 20px rgba(0,255,128,0.7);}50%{box-shadow:0 0 20px rgba(0,255,128,0.9);}100%{box-shadow:0 0 20px rgba(0,255,128,0.7);}}
162
  @keyframes slide {0%{background-position:0% 50%;}50%{background-position:100% 50%;}100%{background-position:0% 50%;}}
163
  @keyframes pulse {0%,100%{opacity:0.7;}50%{opacity:1;}}
164
+ body{
165
+ background:#000000 !important;
166
+ color:#FFFFFF !important;
167
+ font-family:'Orbitron',sans-serif;
168
+ min-height:100vh;
169
+ margin:0 !important;
170
+ padding:0 !important;
171
+ width:100% !important;
172
+ max-width:100vw !important;
173
+ overflow-x:hidden !important;
174
+ display:flex !important;
175
+ justify-content:center;
176
+ align-items:center;
177
+ flex-direction:column;
178
+ }
179
+ body::before{
180
+ content:"";
181
+ display:block;
182
+ height:600px;
183
+ background:#000000 !important;
184
+ }
185
+ .gr-blocks,.container{
186
+ width:100% !important;
187
+ max-width:100vw !important;
188
+ margin:0 !important;
189
+ padding:0 !important;
190
+ box-sizing:border-box !important;
191
+ overflow-x:hidden !important;
192
+ background:#000000 !important;
193
+ color:#FFFFFF !important;
194
+ }
195
+ #general_items{
196
+ width:100% !important;
197
+ max-width:100vw !important;
198
+ margin:2rem 0 !important;
199
+ display:flex !important;
200
+ flex-direction:column;
201
+ align-items:center;
202
+ justify-content:center;
203
+ background:#000000 !important;
204
+ color:#FFFFFF !important;
205
+ }
206
+ #input_column{
207
+ background:#000000 !important;
208
+ border:none !important;
209
+ border-radius:8px;
210
+ padding:1rem !important;
211
+ box-shadow:0 0 10px rgba(255,255,255,0.3) !important;
212
+ width:100% !important;
213
+ max-width:100vw !important;
214
+ box-sizing:border-box !important;
215
+ color:#FFFFFF !important;
216
+ }
217
+ h1{
218
+ font-size:5rem;
219
+ font-weight:700;
220
+ text-align:center;
221
+ color:#FFFFFF !important;
222
+ text-shadow:0 0 8px rgba(255,255,255,0.3) !important;
223
+ margin:0 auto .5rem;
224
+ display:block;
225
+ max-width:100%;
226
+ }
227
+ #subtitle{
228
+ font-size:1rem;
229
+ text-align:center;
230
+ color:#FFFFFF !important;
231
+ opacity:0.8;
232
+ margin-bottom:1rem;
233
+ display:block;
234
+ max-width:100%;
235
+ }
236
+ .gradio-component{
237
+ background:#000000 !important;
238
+ border:none;
239
+ margin:0.75rem 0;
240
+ width:100% !important;
241
+ max-width:100vw !important;
242
+ color:#FFFFFF !important;
243
+ }
244
+ .image-container{
245
+ aspect-ratio:1/1;
246
+ width:100% !important;
247
+ max-width:100vw !important;
248
+ min-height:500px;
249
+ height:auto;
250
+ border:0.5px solid #FFFFFF !important;
251
+ border-radius:4px;
252
+ box-sizing:border-box !important;
253
+ background:#000000 !important;
254
+ box-shadow:0 0 10px rgba(255,255,255,0.3) !important;
255
+ position:relative;
256
+ color:#FFFFFF !important;
257
+ }
258
+ .image-container img{
259
+ width:100% !important;
260
+ height:auto;
261
+ box-sizing:border-box !important;
262
+ display:block !important;
263
+ }
264
+ /* Hide upload / toolbar elements */
265
+ .image-container[aria-label="Input Image"] .file-upload,
266
+ .image-container[aria-label="Input Image"] .file-preview,
267
+ .image-container[aria-label="Input Image"] .image-actions,
268
+ .image-container[aria-label="Input Image"] .gr-file-upload,
269
+ .image-container[aria-label="Input Image"] .gr-file,
270
+ .image-container[aria-label="Input Image"] .gr-actions,
271
+ .image-container[aria-label="Input Image"] .gr-upload-button,
272
+ .image-container[aria-label="Input Image"] .gr-image-toolbar,
273
+ .image-container[aria-label="Input Image"] .gr-file-actions,
274
+ .image-container[aria-label="Input Image"] .gr-upload-options,
275
+ div[aria-label="Input Image"] > div > div:not(.image-container),
276
+ div[aria-label="Input Image"] .gr-button,
277
+ .image-container[aria-label="Result Image"] .file-upload,
278
+ .image-container[aria-label="Result Image"] .file-preview,
279
+ .image-container[aria-label="Result Image"] .image-actions,
280
+ .image-container[aria-label="Result Image"] .gr-file-upload,
281
+ .image-container[aria-label="Result Image"] .gr-file,
282
+ .image-container[aria-label="Result Image"] .gr-actions,
283
+ .image-container[aria-label="Result Image"] .gr-upload-button,
284
+ .image-container[aria-label="Result Image"] .gr-image-toolbar,
285
+ .image-container[aria-label="Result Image"] .gr-file-actions,
286
+ .image-container[aria-label="Result Image"] .gr-upload-options,
287
+ div[aria-label="Result Image"] > div > div:not(.image-container),
288
+ div[aria-label="Result Image"] .gr-button{
289
+ display:none !important;
290
+ }
291
+ /* Processing overlay for Result Image */
292
+ .image-container[aria-label="Result Image"].processing{
293
+ background:#000000 !important;
294
+ position:relative !important;
295
+ }
296
+ .image-container[aria-label="Result Image"].processing::before{
297
+ content:"PROCESSING...";
298
+ position:absolute !important;
299
+ top:50% !important;
300
+ left:50% !important;
301
+ transform:translate(-50%,-50%) !important;
302
+ color:#FFFFFF !important;
303
+ font-family:'Orbitron',sans-serif !important;
304
+ font-size:1.8rem !important;
305
+ font-weight:700 !important;
306
+ text-align:center !important;
307
+ text-shadow:0 0 10px rgba(0,255,128,0.8) !important;
308
+ animation:pulse 1.5s ease-in-out infinite,glow 2s ease-in-out infinite !important;
309
+ z-index:9999 !important;
310
+ width:100% !important;
311
+ height:100% !important;
312
+ display:flex !important;
313
+ align-items:center !important;
314
+ justify-content:center !important;
315
+ pointer-events:none !important;
316
+ background:#000000 !important;
317
+ border-radius:4px !important;
318
+ box-sizing:border-box !important;
319
+ }
320
+ .image-container[aria-label="Result Image"].processing *{
321
+ display:none !important;
322
+ }
323
+ .image-container[aria-label="Result Image"].processing img{
324
+ display:none !important;
325
+ }
326
+ input,textarea{
327
+ background:#000000 !important;
328
+ color:#FFFFFF !important;
329
+ border:1px solid #FFFFFF !important;
330
+ border-radius:4px;
331
+ padding:0.5rem;
332
+ width:100% !important;
333
+ max-width:100vw !important;
334
+ box-sizing:border-box !important;
335
+ }
336
+ input:hover,textarea:hover{
337
+ box-shadow:0 0 8px rgba(255,255,255,0.3) !important;
338
+ transition:box-shadow 0.3s;
339
+ }
340
+ .gr-button-primary{
341
+ background:linear-gradient(90deg,rgba(0,255,128,0.3),rgba(0,200,100,0.3),rgba(0,255,128,0.3)) !important;
342
+ background-size:200% 100%;
343
+ animation:slide 4s ease-in-out infinite,glow 3s ease-in-out infinite;
344
+ color:#FFFFFF !important;
345
+ border:1px solid #FFFFFF !important;
346
+ border-radius:6px;
347
+ padding:0.75rem 1.5rem;
348
+ font-size:1.1rem;
349
+ font-weight:600;
350
+ box-shadow:0 0 14px rgba(0,255,128,0.7) !important;
351
+ transition:box-shadow 0.3s,transform 0.3s;
352
+ width:100% !important;
353
+ max-width:100vw !important;
354
+ min-height:48px;
355
+ cursor:pointer;
356
+ }
357
+ .gr-button-primary:hover{
358
+ box-shadow:0 0 20px rgba(0,255,128,0.9) !important;
359
+ animation:slide 4s ease-in-out infinite,glow-hover 3s ease-in-out infinite;
360
+ transform:scale(1.05);
361
+ }
362
+ button[aria-label="Fullscreen"],button[aria-label="Share"],button[aria-label="Download"]{
363
+ display:none !important;
364
+ }
365
+ .progress-text,.gr-progress,.gr-prose,.gr-log{
366
+ display:none !important;
367
+ }
368
+ footer,.gr-button-secondary{
369
+ display:none !important;
370
+ }
371
+ .gr-group{
372
+ background:#000000 !important;
373
+ border:none !important;
374
+ width:100% !important;
375
+ max-width:100vw !important;
376
+ }
377
  @media (max-width:768px){
378
  h1{font-size:4rem;}
379
+ #subtitle{font-size:0.9rem;}
380
+ .gr-button-primary{
381
+ padding:0.6rem 1rem;
382
+ font-size:1rem;
383
+ box-shadow:0 0 10px rgba(0,255,128,0.7) !important;
384
+ animation:slide 4s ease-in-out infinite,glow 3s ease-in-out infinite;
385
+ }
386
+ .image-container{
387
+ min-height:300px;
388
+ box-shadow:0 0 8px rgba(255,255,255,0.3) !important;
389
+ }
390
+ .image-container[aria-label="Result Image"].processing::before{
391
+ font-size:1.2rem !important;
392
+ }
393
  }
394
  </style>
395
  <script>
396
  // --------------------------------------------------------------
397
+ // Enforce loading only under /spaceishere (or any sub‑path)
398
  // --------------------------------------------------------------
399
  const allowedPath = /^\\/spaceishere(\\/.*)?$/;
400
  if (!allowedPath.test(window.location.pathname)) {
 
403
  }
404
 
405
  // --------------------------------------------------------------
406
+ // UI behaviour: processing overlay & hide progress elements
407
  // --------------------------------------------------------------
408
  document.addEventListener('DOMContentLoaded', () => {
409
  const generateBtn = document.querySelector('.gr-button-primary');
 
439
  """
440
  )
441
 
442
+ # --------------------------------------------------------------
443
  # Layout
444
+ # --------------------------------------------------------------
445
  with gr.Row(elem_id="general_items"):
446
  gr.Markdown("# Image Edit")
447
  gr.Markdown("Edit your images with prompt descriptions", elem_id="subtitle")
 
474
  elem_classes=["gradio-component", "image-container"],
475
  )
476
 
477
+ # --------------------------------------------------------------
478
  # Event bindings
479
+ # --------------------------------------------------------------
480
  run_button.click(fn=infer, inputs=[input_image, prompt], outputs=[result_image])
481
  prompt.submit(fn=infer, inputs=[input_image, prompt], outputs=[result_image])
482
 
483
  return demo
484
 
485
  # ----------------------------------------------------------------------
486
+ # FastAPI app with strict routing (only /spaceishere allowed)
487
  # ----------------------------------------------------------------------
488
  app = FastAPI()
489
  demo = create_demo()
 
494
  raise HTTPException(status_code=500, detail="Internal Server Error")
495
 
496
  # ----------------------------------------------------------------------
497
+ # Entry point
498
  # ----------------------------------------------------------------------
499
  if __name__ == "__main__":
500
  logger.info(f"Gradio version: {gr.__version__}")
501
  demo.queue().launch(share=True)
502
+ ```$