milliyin commited on
Commit
3e216be
Β·
verified Β·
1 Parent(s): 9837c66

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +211 -233
app.py CHANGED
@@ -22,9 +22,8 @@ logger = logging.getLogger(__name__)
22
  MAX_QUEUE_SIZE = 50
23
  MAX_CONCURRENT_REQUESTS = 1 # GPU can only handle 1 request at a time
24
  AVERAGE_PROCESSING_TIME = 15 # seconds
25
- QUEUE_UPDATE_INTERVAL = 2 # seconds
26
 
27
- # ───────── Backend Configuration ─────────
28
  HF_TOKEN = os.getenv("HF_TOKEN")
29
  if not HF_TOKEN:
30
  raise ValueError("HF_TOKEN environment variable is required")
@@ -52,30 +51,26 @@ class QueueManager:
52
  self.queue.append((request_id, user_data, time.time()))
53
  position = len(self.queue)
54
 
55
- # Calculate estimated wait time
56
  processing_count = len(self.processing)
57
  queue_ahead = position - 1
58
 
59
  if processing_count == 0:
60
- # GPU is free, can start immediately
61
  estimated_wait = 0
62
  else:
63
- # GPU is busy, need to wait for current + queue ahead
64
  estimated_wait = (queue_ahead + 1) * self.stats['avg_processing_time']
65
 
66
  logger.info(f"Request {request_id} added to queue. Position: {position}, Est. wait: {estimated_wait:.0f}s")
67
  return position, estimated_wait
68
 
69
- def get_next_requests(self, count: int = 1):
70
  """Get next request to process (only 1 at a time for GPU)"""
71
  with self.lock:
72
  if len(self.processing) >= MAX_CONCURRENT_REQUESTS or len(self.queue) == 0:
73
  return []
74
 
75
- # Get only the next single request
76
  request_id, user_data, timestamp = self.queue.popleft()
77
  self.processing[request_id] = time.time()
78
-
79
  return [(request_id, user_data)]
80
 
81
  def complete_request(self, request_id: str, result):
@@ -102,19 +97,6 @@ class QueueManager:
102
  self.stats['total_failed'] += 1
103
  logger.error(f"Request {request_id} failed: {error_msg}")
104
 
105
- def get_queue_status(self) -> dict:
106
- """Get current queue status"""
107
- with self.lock:
108
- return {
109
- 'queue_length': len(self.queue),
110
- 'processing_count': len(self.processing),
111
- 'total_processed': self.stats['total_processed'],
112
- 'total_failed': self.stats['total_failed'],
113
- 'avg_processing_time': self.stats['avg_processing_time'],
114
- 'max_queue_size': MAX_QUEUE_SIZE,
115
- 'max_concurrent': MAX_CONCURRENT_REQUESTS
116
- }
117
-
118
  def get_request_status(self, request_id: str) -> dict:
119
  """Get status of specific request"""
120
  with self.lock:
@@ -126,7 +108,6 @@ class QueueManager:
126
  processing_time = time.time() - self.processing[request_id]
127
  return {'status': 'processing', 'time': processing_time}
128
  else:
129
- # Check position in queue
130
  for i, (rid, _, _) in enumerate(self.queue):
131
  if rid == request_id:
132
  return {'status': 'queued', 'position': i + 1}
@@ -135,7 +116,6 @@ class QueueManager:
135
  # Global queue manager
136
  queue_manager = QueueManager()
137
 
138
- # ───────── Backend Connection ─────────
139
  backend_status = {
140
  "client": None,
141
  "connected": False,
@@ -167,7 +147,7 @@ def check_backend_connection():
167
  return False, "🟑 Model is starting up. Please wait 3‑4 min."
168
  return False, f"πŸ”΄ Backend error: {e}"
169
 
170
- # Initial connection check
171
  check_backend_connection()
172
 
173
  # ───────── Queue Processing Worker ─────────
@@ -185,10 +165,7 @@ def queue_worker():
185
  request_id, user_data = requests[0]
186
  logger.info(f"Starting processing request {request_id}")
187
 
188
- # Process synchronously since GPU can only handle one
189
  process_single_request(request_id, user_data)
190
-
191
- # Small delay before checking for next request
192
  time.sleep(0.5)
193
 
194
  except Exception as e:
@@ -198,7 +175,6 @@ def queue_worker():
198
  def process_single_request(request_id: str, user_data: dict):
199
  """Process a single request"""
200
  try:
201
- # Extract user data
202
  img_b64 = user_data['image_b64']
203
  category = user_data['category']
204
  gender = user_data['gender']
@@ -241,7 +217,7 @@ def process_single_request(request_id: str, user_data: dict):
241
  worker_thread = threading.Thread(target=queue_worker, daemon=True)
242
  worker_thread.start()
243
 
244
- # ───────── Helper Functions ─────────
245
  def image_to_base64(image: Image.Image) -> str:
246
  if image is None:
247
  return ""
@@ -263,18 +239,14 @@ def base64_to_image(b64: str) -> Optional[Image.Image]:
263
  # ───���───── Request Management ─────────
264
  active_requests = {} # session_id -> request_id
265
 
266
- def submit_request(input_image: Image.Image, category: str, gender: str, session_id: str = None):
267
  """Submit a new request to the queue"""
268
  if input_image is None:
269
  return None, None, "❌ Please upload an image.", gr.update(interactive=True), ""
270
 
271
  try:
272
- # Generate unique request ID
273
  request_id = str(uuid.uuid4())
274
- if session_id:
275
- active_requests[session_id] = request_id
276
 
277
- # Prepare user data
278
  img_b64 = image_to_base64(input_image)
279
  user_data = {
280
  'image_b64': img_b64,
@@ -283,14 +255,13 @@ def submit_request(input_image: Image.Image, category: str, gender: str, session
283
  'timestamp': time.time()
284
  }
285
 
286
- # Add to queue
287
  position, estimated_wait = queue_manager.add_request(request_id, user_data)
288
 
289
  status_msg = f"πŸš€ Request submitted! Position in queue: #{position}"
290
- if position == 1 and queue_manager.get_queue_status()['processing_count'] == 0:
291
  status_msg += " | Starting processing now..."
292
  elif estimated_wait > 0:
293
- status_msg += f" | Estimated wait: {estimated_wait:.0f}s ({int(estimated_wait/60)}m {int(estimated_wait%60)}s)"
294
 
295
  return None, None, status_msg, gr.update(interactive=False), request_id
296
 
@@ -320,7 +291,6 @@ def check_request_status(request_id: str):
320
 
321
  elif status_info['status'] == 'queued':
322
  position = status_info['position']
323
- # Calculate estimated wait for this position
324
  avg_time = queue_manager.stats['avg_processing_time']
325
  estimated_wait = position * avg_time
326
  wait_msg = f" | Est. wait: {int(estimated_wait/60)}m {int(estimated_wait%60)}s" if estimated_wait > 30 else ""
@@ -329,74 +299,85 @@ def check_request_status(request_id: str):
329
  else:
330
  return None, None, "❓ Request not found", gr.update(interactive=True)
331
 
332
- def get_queue_info():
333
- """Get formatted queue information"""
334
- status = queue_manager.get_queue_status()
335
-
336
- info = f"""
337
- <div style="background: linear-gradient(135deg, #e3f2fd, #bbdefb); padding: 15px; border-radius: 10px; margin: 10px 0;">
338
- <h4 style="margin: 0 0 10px 0; color: #1565c0;">πŸ“Š Queue Status</h4>
339
- <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; font-size: 14px;">
340
- <div><strong>Queue Length:</strong> {status['queue_length']}/{status['max_queue_size']}</div>
341
- <div><strong>Processing:</strong> {status['processing_count']}/{status['max_concurrent']}</div>
342
- <div><strong>Completed:</strong> {status['total_processed']}</div>
343
- <div><strong>Failed:</strong> {status['total_failed']}</div>
344
- <div><strong>Avg. Processing:</strong> {status['avg_processing_time']:.1f}s</div>
345
- <div><strong>Last Updated:</strong> {datetime.now().strftime('%H:%M:%S')}</div>
346
- </div>
347
- </div>
348
- """
349
- return info
350
 
351
- # ───────── CSS Styles ─────────
352
  custom_css = """
353
  .gradio-container {
354
  background: linear-gradient(135deg, #3b4371 0%, #2d1b69 25%, #673ab7 50%, #8e24aa 75%, #6a1b9a 100%);
355
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
356
  min-height: 100vh;
357
  }
358
-
359
- .queue-indicator {
360
- background: linear-gradient(135deg, #4CAF50, #45a049);
361
- color: white;
362
- padding: 12px;
363
- border-radius: 10px;
 
 
 
364
  text-align: center;
365
- margin: 10px 0;
366
- font-weight: 600;
367
- box-shadow: 0 4px 15px rgba(76, 175, 80, 0.3);
 
 
368
  }
369
-
370
- .queue-warning {
371
- background: linear-gradient(135deg, #ff9800, #f57c00);
372
  color: white;
 
 
 
 
 
 
 
373
  padding: 12px;
374
- border-radius: 10px;
 
 
375
  text-align: center;
376
- margin: 10px 0;
377
- font-weight: 600;
378
- box-shadow: 0 4px 15px rgba(255, 152, 0, 0.3);
379
  }
380
-
381
- .queue-full {
382
- background: linear-gradient(135deg, #f44336, #d32f2f);
383
- color: white;
384
  padding: 12px;
385
- border-radius: 10px;
386
- text-align: center;
387
- margin: 10px 0;
 
 
 
 
388
  font-weight: 600;
389
- box-shadow: 0 4px 15px rgba(244, 67, 54, 0.3);
390
  }
391
-
392
- .feature-box {
393
- background: #f8fafc;
394
- border: 1px solid #e2e8f0;
395
- padding: 20px;
396
  border-radius: 12px;
397
- margin: 10px 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
398
  }
399
-
400
  button.primary {
401
  background: linear-gradient(135deg, #673ab7, #8e24aa) !important;
402
  border: none !important;
@@ -407,174 +388,171 @@ button.primary {
407
  font-size: 15px !important;
408
  box-shadow: 0 5px 15px rgba(103, 58, 183, 0.4) !important;
409
  }
410
-
411
- .status-panel {
412
- background: rgba(255, 255, 255, 0.95);
413
- border: 2px solid #673ab7;
414
- border-radius: 15px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
  padding: 20px;
 
416
  margin: 10px 0;
417
  }
418
  """
419
 
420
- # ───────── Gradio Interface ─────────
421
- with gr.Blocks(css=custom_css, title="Jewellery Photography Preview - Queue System") as demo:
422
- # Hero Section
423
  gr.HTML("""
424
  <div style="text-align: center; margin-bottom: 20px;">
425
- <h1 style="font-size: 2.5em; color: white; text-shadow: 2px 2px 4px rgba(0,0,0,0.5);">
426
- 🎨 Raresence: AI-Powered Jewellery Photo Preview
427
- </h1>
428
- <p style="color: #e1bee7; font-size: 1.2em;">
429
- Upload a jewellery image, select model, and get professional photos instantly
430
- </p>
431
  </div>
432
  """)
433
 
434
- # Status and Queue Information
435
- with gr.Row():
436
- backend_status_display = gr.HTML()
437
- queue_info_display = gr.HTML()
438
 
439
- # Main Interface
440
- with gr.Row():
441
- with gr.Column(scale=1):
442
- gr.HTML("""
443
- <div class="feature-box">
444
- <h3>πŸ–ΌοΈ Upload Jewellery Image</h3>
445
- <p style="color: #666;">Select a clear jewellery image for best results</p>
446
- </div>
447
- """)
448
- input_img = gr.Image(label="Upload image", type="pil", height=400)
449
-
450
- gr.HTML("""
451
- <div class="feature-box">
452
- <h3>βš™οΈ Settings</h3>
453
- </div>
454
- """)
455
- category = gr.Dropdown(
456
- label="Jewellery category",
457
- choices=["Rings", "Bracelets", "Watches", "Earrings"],
458
- value="Bracelets"
459
- )
460
- gender = gr.Dropdown(
461
- label="Model gender",
462
- choices=["male", "female"],
463
- value="female"
464
- )
465
-
466
- submit_btn = gr.Button("πŸš€ Submit to Queue", variant="primary", size="lg")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
467
 
468
- with gr.Column(scale=1):
469
- gr.HTML("""
470
- <div class="feature-box">
471
- <h3>🎨 AI Generated Results</h3>
472
- <p style="color: #666;">Results will appear here after processing</p>
473
- </div>
474
- """)
475
 
476
- with gr.Tabs():
477
- with gr.TabItem("Final Result"):
478
- result_bg = gr.Image(label="Professional Background", height=400)
479
- with gr.TabItem("Detection Overlay"):
480
- result_overlay = gr.Image(label="Detection Overlay", height=400)
481
 
482
- # Status and Control Panel
483
- with gr.Group():
484
- gr.HTML('<div class="status-panel">')
485
- status_output = gr.Textbox(
486
- label="Request Status",
487
- interactive=False,
488
- value="Ready to submit request"
489
- )
490
-
491
- with gr.Row():
492
- check_status_btn = gr.Button("πŸ”„ Check Status", size="sm")
493
- cancel_btn = gr.Button("❌ Cancel Request", size="sm", variant="secondary")
494
-
495
- gr.HTML('</div>')
 
 
 
496
 
497
  # Hidden state for request tracking
498
  current_request_id = gr.State("")
499
- session_id = gr.State(lambda: str(uuid.uuid4()))
500
 
501
- # Auto-refresh components
502
- def update_displays():
503
- # Update backend status
504
- ok, msg = check_backend_connection()
505
- status_class = "queue-indicator" if ok else "queue-warning"
506
- backend_html = f'<div class="{status_class}">{msg}</div>'
507
-
508
- # Update queue info
509
- queue_html = get_queue_info()
510
-
511
- return backend_html, queue_html
512
-
513
- # Event handlers
514
- submit_btn.click(
515
  fn=submit_request,
516
- inputs=[input_img, category, gender, session_id],
517
- outputs=[result_overlay, result_bg, status_output, submit_btn, current_request_id],
518
- show_progress=True
519
- )
520
-
521
- check_status_btn.click(
522
- fn=check_request_status,
523
- inputs=[current_request_id],
524
- outputs=[result_overlay, result_bg, status_output, submit_btn],
525
- show_progress=False
526
  )
527
 
528
- def cancel_request(request_id, session_id_val):
529
- if session_id_val in active_requests:
530
- del active_requests[session_id_val]
531
- return None, None, "Request cancelled", gr.update(interactive=True), ""
532
-
533
- cancel_btn.click(
534
- fn=cancel_request,
535
- inputs=[current_request_id, session_id],
536
- outputs=[result_overlay, result_bg, status_output, submit_btn, current_request_id]
537
- )
538
-
539
- # Auto-refresh status displays every 3 seconds
540
- def refresh_displays():
541
- return update_displays()
542
-
543
- # Set up periodic refresh
544
- demo.load(refresh_displays, outputs=[backend_status_display, queue_info_display])
545
-
546
- # Periodic status check for active requests
547
- def auto_check_status(request_id):
548
  if request_id:
549
  return check_request_status(request_id)
550
- return None, None, "No active request", gr.update(interactive=True)
551
 
552
- # Footer
553
- gr.HTML("""
554
- <div style="text-align:center;padding:30px;background:rgba(255,255,255,0.1);border-radius:15px;margin:20px 0;">
555
- <h3 style="color:white;">πŸš€ Advanced Queue System</h3>
556
- <p style="color:#e1bee7;">
557
- Intelligent queuing β€’ Real-time status β€’ Fair processing order
558
- </p>
559
- <p style="font-size:12px;color:#d1c4e9;">
560
- Β© 2024 Snapwear AI | Professional AI tools with enterprise-grade queuing
561
- </p>
562
- </div>
563
- """)
564
-
565
- # ───────── Launch Configuration ─────────
566
- if __name__ == "__main__":
567
- logger.info("Starting Jewellery Photography Preview with Advanced Queue System...")
568
 
569
- demo.queue(
570
- max_size=MAX_QUEUE_SIZE + 10, # Allow some buffer in Gradio's queue
571
- default_concurrency_limit=1, # Match our single GPU processing
572
- ).launch(
573
- share=False,
574
- server_name="0.0.0.0",
575
- server_port=7860,
576
- show_error=True,
577
- show_api=False
578
  )
579
-
580
- logger.info(f"Single GPU Queue system ready! Max queue: {MAX_QUEUE_SIZE}, Processing: 1 at a time")
 
 
 
22
  MAX_QUEUE_SIZE = 50
23
  MAX_CONCURRENT_REQUESTS = 1 # GPU can only handle 1 request at a time
24
  AVERAGE_PROCESSING_TIME = 15 # seconds
 
25
 
26
+ # ───────── Backend connection ─────────
27
  HF_TOKEN = os.getenv("HF_TOKEN")
28
  if not HF_TOKEN:
29
  raise ValueError("HF_TOKEN environment variable is required")
 
51
  self.queue.append((request_id, user_data, time.time()))
52
  position = len(self.queue)
53
 
54
+ # Calculate estimated wait time for single GPU
55
  processing_count = len(self.processing)
56
  queue_ahead = position - 1
57
 
58
  if processing_count == 0:
 
59
  estimated_wait = 0
60
  else:
 
61
  estimated_wait = (queue_ahead + 1) * self.stats['avg_processing_time']
62
 
63
  logger.info(f"Request {request_id} added to queue. Position: {position}, Est. wait: {estimated_wait:.0f}s")
64
  return position, estimated_wait
65
 
66
+ def get_next_requests(self):
67
  """Get next request to process (only 1 at a time for GPU)"""
68
  with self.lock:
69
  if len(self.processing) >= MAX_CONCURRENT_REQUESTS or len(self.queue) == 0:
70
  return []
71
 
 
72
  request_id, user_data, timestamp = self.queue.popleft()
73
  self.processing[request_id] = time.time()
 
74
  return [(request_id, user_data)]
75
 
76
  def complete_request(self, request_id: str, result):
 
97
  self.stats['total_failed'] += 1
98
  logger.error(f"Request {request_id} failed: {error_msg}")
99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  def get_request_status(self, request_id: str) -> dict:
101
  """Get status of specific request"""
102
  with self.lock:
 
108
  processing_time = time.time() - self.processing[request_id]
109
  return {'status': 'processing', 'time': processing_time}
110
  else:
 
111
  for i, (rid, _, _) in enumerate(self.queue):
112
  if rid == request_id:
113
  return {'status': 'queued', 'position': i + 1}
 
116
  # Global queue manager
117
  queue_manager = QueueManager()
118
 
 
119
  backend_status = {
120
  "client": None,
121
  "connected": False,
 
147
  return False, "🟑 Model is starting up. Please wait 3‑4 min."
148
  return False, f"πŸ”΄ Backend error: {e}"
149
 
150
+ # initial probe
151
  check_backend_connection()
152
 
153
  # ───────── Queue Processing Worker ─────────
 
165
  request_id, user_data = requests[0]
166
  logger.info(f"Starting processing request {request_id}")
167
 
 
168
  process_single_request(request_id, user_data)
 
 
169
  time.sleep(0.5)
170
 
171
  except Exception as e:
 
175
  def process_single_request(request_id: str, user_data: dict):
176
  """Process a single request"""
177
  try:
 
178
  img_b64 = user_data['image_b64']
179
  category = user_data['category']
180
  gender = user_data['gender']
 
217
  worker_thread = threading.Thread(target=queue_worker, daemon=True)
218
  worker_thread.start()
219
 
220
+ # ───────── Helpers ─────────
221
  def image_to_base64(image: Image.Image) -> str:
222
  if image is None:
223
  return ""
 
239
  # ───���───── Request Management ─────────
240
  active_requests = {} # session_id -> request_id
241
 
242
+ def submit_request(input_image: Image.Image, category: str, gender: str):
243
  """Submit a new request to the queue"""
244
  if input_image is None:
245
  return None, None, "❌ Please upload an image.", gr.update(interactive=True), ""
246
 
247
  try:
 
248
  request_id = str(uuid.uuid4())
 
 
249
 
 
250
  img_b64 = image_to_base64(input_image)
251
  user_data = {
252
  'image_b64': img_b64,
 
255
  'timestamp': time.time()
256
  }
257
 
 
258
  position, estimated_wait = queue_manager.add_request(request_id, user_data)
259
 
260
  status_msg = f"πŸš€ Request submitted! Position in queue: #{position}"
261
+ if position == 1 and len(queue_manager.processing) == 0:
262
  status_msg += " | Starting processing now..."
263
  elif estimated_wait > 0:
264
+ status_msg += f" | Estimated wait: {estimated_wait:.0f}s"
265
 
266
  return None, None, status_msg, gr.update(interactive=False), request_id
267
 
 
291
 
292
  elif status_info['status'] == 'queued':
293
  position = status_info['position']
 
294
  avg_time = queue_manager.stats['avg_processing_time']
295
  estimated_wait = position * avg_time
296
  wait_msg = f" | Est. wait: {int(estimated_wait/60)}m {int(estimated_wait%60)}s" if estimated_wait > 30 else ""
 
299
  else:
300
  return None, None, "❓ Request not found", gr.update(interactive=True)
301
 
302
+ def disable_button():
303
+ return gr.update(interactive=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
 
305
+ # ───────── CSS ─────────
306
  custom_css = """
307
  .gradio-container {
308
  background: linear-gradient(135deg, #3b4371 0%, #2d1b69 25%, #673ab7 50%, #8e24aa 75%, #6a1b9a 100%);
309
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
310
  min-height: 100vh;
311
  }
312
+ .contain {
313
+ background: rgba(255, 255, 255, 0.95);
314
+ border-radius: 15px;
315
+ padding: 25px;
316
+ margin: 15px;
317
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
318
+ backdrop-filter: blur(10px);
319
+ }
320
+ .title-container {
321
  text-align: center;
322
+ margin-bottom: 25px;
323
+ padding: 20px;
324
+ background: linear-gradient(135deg, #673ab7, #8e24aa);
325
+ border-radius: 12px;
326
+ box-shadow: 0 5px 20px rgba(103, 58, 183, 0.4);
327
  }
328
+ .title-container h1 {
 
 
329
  color: white;
330
+ font-size: 2.2em;
331
+ font-weight: bold;
332
+ margin: 0;
333
+ text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.3);
334
+ }
335
+ .info-bar {
336
+ background: linear-gradient(135deg, #7c4dff, #6a1b9a);
337
  padding: 12px;
338
+ border-radius: 8px;
339
+ margin-bottom: 20px;
340
+ color: white;
341
  text-align: center;
342
+ font-weight: 500;
343
+ box-shadow: 0 3px 12px rgba(124, 77, 255, 0.3);
 
344
  }
345
+ .section-header {
346
+ background: linear-gradient(135deg, #e1bee7, #d1c4e9);
 
 
347
  padding: 12px;
348
+ border-radius: 8px;
349
+ margin-bottom: 15px;
350
+ border-left: 4px solid #673ab7;
351
+ }
352
+ .section-header h3 {
353
+ margin: 0;
354
+ color: #333;
355
  font-weight: 600;
 
356
  }
357
+ .input-group {
358
+ background: rgba(255, 255, 255, 0.85);
359
+ padding: 18px;
 
 
360
  border-radius: 12px;
361
+ margin-bottom: 15px;
362
+ border: 1px solid rgba(103, 58, 183, 0.2);
363
+ box-shadow: 0 3px 12px rgba(103, 58, 183, 0.1);
364
+ }
365
+ .result-section {
366
+ background: rgba(255, 255, 255, 0.9);
367
+ padding: 18px;
368
+ border-radius: 12px;
369
+ border: 1px solid rgba(103, 58, 183, 0.2);
370
+ box-shadow: 0 3px 12px rgba(103, 58, 183, 0.1);
371
+ }
372
+ .tip-box {
373
+ background: linear-gradient(135deg, #f3e5f5, #e8eaf6);
374
+ padding: 10px;
375
+ border-radius: 6px;
376
+ margin: 8px 0;
377
+ border-left: 3px solid #673ab7;
378
+ color: #4a148c;
379
+ font-weight: 500;
380
  }
 
381
  button.primary {
382
  background: linear-gradient(135deg, #673ab7, #8e24aa) !important;
383
  border: none !important;
 
388
  font-size: 15px !important;
389
  box-shadow: 0 5px 15px rgba(103, 58, 183, 0.4) !important;
390
  }
391
+ button.primary:hover {
392
+ box-shadow: 0 8px 25px rgba(103, 58, 183, 0.6) !important;
393
+ opacity: 0.9 !important;
394
+ transform: translateY(-2px) !important;
395
+ }
396
+ label {
397
+ color: #4a148c !important;
398
+ font-weight: 600 !important;
399
+ }
400
+ input, textarea, select {
401
+ border: 1px solid rgba(103, 58, 183, 0.3) !important;
402
+ border-radius: 6px !important;
403
+ }
404
+ input:focus, textarea:focus, select:focus {
405
+ border-color: #673ab7 !important;
406
+ box-shadow: 0 0 0 2px rgba(103, 58, 183, 0.2) !important;
407
+ }
408
+ .gr-slider input[type="range"] {
409
+ accent-color: #673ab7 !important;
410
+ }
411
+ input[type="checkbox"] {
412
+ accent-color: #673ab7 !important;
413
+ }
414
+ .preserve-aspect-ratio img {
415
+ object-fit: contain !important;
416
+ width: auto !important;
417
+ max-height: 512px !important;
418
+ }
419
+ .social-links {
420
+ text-align: center;
421
+ margin: 20px 0;
422
+ }
423
+ .social-links a {
424
+ margin: 0 10px;
425
+ padding: 8px 16px;
426
+ background: #667eea;
427
+ color: white;
428
+ text-decoration: none;
429
+ border-radius: 8px;
430
+ transition: all 0.3s ease;
431
+ }
432
+ .social-links a:hover {
433
+ background: #764ba2;
434
+ transform: translateY(-2px);
435
+ }
436
+ .feature-box {
437
+ background: #f8fafc;
438
+ border: 1px solid #e2e8f0;
439
  padding: 20px;
440
+ border-radius: 12px;
441
  margin: 10px 0;
442
  }
443
  """
444
 
445
+ # ───────── Gradio Blocks ─────────
446
+ with gr.Blocks(css=custom_css, title="Jewellery Photography Preview") as demo:
447
+ # Hero
448
  gr.HTML("""
449
  <div style="text-align: center; margin-bottom: 20px;">
450
+ <h1 style="font-size: 2.5em;">🎨 Raresence: AI-Powered Jewellery Photo Preview</h1>
451
+ <p style="color: #666;">Upload a jewellery image, select model, and get professional photos instantly</p>
 
 
 
 
452
  </div>
453
  """)
454
 
455
+ # Status banner
456
+ status_html = gr.HTML()
 
 
457
 
458
+ def _update_status():
459
+ ok, msg = check_backend_connection()
460
+ cls = "status-ready" if ok else ("status-starting" if "🟑" in msg else "status-error")
461
+ return f'<div class="status-banner {cls}">{msg}</div>'
462
+
463
+ status_html.value = _update_status()
464
+ gr.Button("πŸ”„ Check Status").click(fn=_update_status, outputs=status_html)
465
+
466
+ with gr.Column():
467
+ with gr.Row():
468
+
469
+ with gr.Column(scale=0.4):
470
+ gr.HTML("""
471
+ <div class="feature-box"">
472
+ <h3>πŸ–ΌοΈ Upload Jewellery Image</h3>
473
+ <p style="color: #666; font-size: 14px;">Select a clear jewellery image for best results</p>
474
+ </div>
475
+ """)
476
+ gr.Markdown("β€Ž")
477
+ gr.Markdown("β€Ž")
478
+ input_img = gr.Image(label="Upload image", type="pil", height=400)
479
+
480
+ with gr.Column():
481
+ gr.HTML("""
482
+ <div class="feature-box">
483
+ <h3>🎨 AI Generated Results</h3>
484
+ <p style="color: #666; font-size: 14px;">Preview overlay detection and final professional background</p>
485
+ </div>
486
+ """)
487
+
488
+ with gr.Tabs():
489
+ with gr.TabItem("Final result"):
490
+ info2 = gr.Markdown(value="### Final result")
491
+ out_bg = gr.Image(height=400)
492
+ with gr.TabItem("Detection overlay"):
493
+ info1 = gr.Markdown(value="### Detection overlay")
494
+ out_overlay = gr.Image(height=400)
495
+ run_btn = gr.Button("🎯 Generate", elem_id="button", variant="primary")
496
+
497
+ with gr.Row():
498
+ with gr.Column(scale=0.4):
499
+ gr.Markdown(value="Setting")
500
+ category = gr.Dropdown(label="Jewellery category", choices=["Rings", "Bracelets", "Watches", "Earrings"], value="Bracelets")
501
+ gender = gr.Dropdown(label="Model gender", choices=["male", "female"], value="female")
502
 
 
 
 
 
 
 
 
503
 
504
+ out_status = gr.Text(label="Status", interactive=False)
 
 
 
 
505
 
506
+ # ──────── Footer ────────
507
+ gr.HTML("""
508
+ <div style="text-align:center;padding:40px 20px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:16px;margin:30px 0;">
509
+ <h3 style="color:#333;">πŸš€ Powered by Snapwear AI</h3>
510
+ <p style="color:#666;">
511
+ Experience the future of virtual fashion and garment visualization.
512
+ </p>
513
+ <div class="social-links">
514
+ <a href="https://snapwear.io" target="_blank">🌐 Website</a>
515
+ <a href="https://www.instagram.com/snapwearai/" target="_blank">πŸ“Έ Instagram</a>
516
+ <a href="https://huggingface.co/spaces/SnapwearAI/Snapwear-Texture-Transfer" target="_blank">🎨 Pattern Transfer</a>
517
+ </div>
518
+ <p style="font-size:12px;color:#999;margin-top:20px;">
519
+ Β© 2024 Snapwear AI. Professional AI tools for fashion and design.
520
+ </p>
521
+ </div>
522
+ """)
523
 
524
  # Hidden state for request tracking
525
  current_request_id = gr.State("")
 
526
 
527
+ # Wire button β†’ queue system
528
+ run_btn.click(
529
+ fn=disable_button,
530
+ inputs=None,
531
+ outputs=run_btn
532
+ ).then(
 
 
 
 
 
 
 
 
533
  fn=submit_request,
534
+ inputs=[input_img, category, gender],
535
+ outputs=[out_overlay, out_bg, out_status, run_btn, current_request_id],
536
+ show_progress=True,
 
 
 
 
 
 
 
537
  )
538
 
539
+ # Auto-check status every 2 seconds for active requests
540
+ def auto_status_check(request_id):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
541
  if request_id:
542
  return check_request_status(request_id)
543
+ return None, None, "Ready to generate", gr.update(interactive=True)
544
 
545
+ # Set up periodic status checking
546
+ demo.load(lambda: None) # Initial load
 
 
 
 
 
 
 
 
 
 
 
 
 
 
547
 
548
+ # Create a timer that checks status every 2 seconds
549
+ timer = gr.Timer(2) # Check every 2 seconds
550
+ timer.tick(
551
+ fn=auto_status_check,
552
+ inputs=[current_request_id],
553
+ outputs=[out_overlay, out_bg, out_status, run_btn]
 
 
 
554
  )
555
+
556
+ # ───────── Launch ─────────
557
+ if __name__ == "__main__":
558
+ demo.queue(max_size=MAX_QUEUE_SIZE + 10, default_concurrency_limit=1).launch(share=False)