Decoo commited on
Commit
024a7b9
ยท
1 Parent(s): d028365

modified app.py

Browse files
Files changed (1) hide show
  1. app.py +71 -499
app.py CHANGED
@@ -2,8 +2,13 @@ import gradio as gr
2
  import json
3
  import os
4
  from pathlib import Path
 
 
 
 
 
 
5
 
6
- # Data folder paths
7
  DATA_BASE = "Viewer/Data/Our_system"
8
  FOLDER_MAP = {
9
  "qa_subtopics": f"{DATA_BASE}/QA+Topics",
@@ -12,6 +17,64 @@ FOLDER_MAP = {
12
  "summary_sdgs": f"{DATA_BASE}/Summary+SDG"
13
  }
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  def get_available_events():
16
  """Get all available events from JSON files"""
17
  events = set()
@@ -20,34 +83,10 @@ def get_available_events():
20
  for file in os.listdir(folder):
21
  if file.endswith('_combined_data.json'):
22
  event_name = file.replace('_combined_data.json', '')
23
- # Skip pure numeric names
24
  if not event_name.isdigit():
25
  events.add(event_name)
26
  return sorted(list(events))
27
 
28
- def load_json_data(event_base, format_type, view_type):
29
- """Load JSON data for selected event and view"""
30
- if not event_base:
31
- return None
32
-
33
- key = f"{format_type}_{view_type}"
34
- folder = FOLDER_MAP.get(key)
35
-
36
- if not folder:
37
- return None
38
-
39
- filepath = os.path.join(folder, f"{event_base}_combined_data.json")
40
-
41
- if not os.path.exists(filepath):
42
- return None
43
-
44
- try:
45
- with open(filepath, 'r', encoding='utf-8') as f:
46
- return json.load(f)
47
- except Exception as e:
48
- print(f"Error loading {filepath}: {e}")
49
- return None
50
-
51
  def save_feedback(event, format_type, view_type, rating, comment):
52
  """Save feedback to JSON file"""
53
  if not event:
@@ -82,482 +121,13 @@ def save_feedback(event, format_type, view_type, rating, comment):
82
 
83
  return "โœ… Thank you for your feedback!"
84
 
85
- def create_full_html(json_data=None):
86
- """Create complete HTML with embedded data"""
87
-
88
- # Convert data to JavaScript
89
- js_data = "null"
90
- if json_data:
91
- js_data = json.dumps(json_data, ensure_ascii=False)
92
-
93
- return f"""
94
- <!DOCTYPE html>
95
- <html lang="en">
96
- <head>
97
- <meta charset="UTF-8">
98
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
99
- <style>
100
- /* Reset Gradio styles */
101
- .gradio-container {{ padding: 0 !important; }}
102
-
103
- * {{ margin: 0; padding: 0; box-sizing: border-box; }}
104
- body, html {{
105
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
106
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
107
- min-height: 100vh;
108
- color: #333;
109
- overflow-x: hidden;
110
- }}
111
- .viewer-container {{
112
- max-width: 1400px;
113
- margin: 0 auto;
114
- padding: 20px;
115
- }}
116
- .content {{
117
- background: rgba(255, 255, 255, 0.95);
118
- border-radius: 15px;
119
- padding: 30px;
120
- box-shadow: 0 10px 30px rgba(0,0,0,0.2);
121
- backdrop-filter: blur(10px);
122
- min-height: 400px;
123
- }}
124
- .file-section {{
125
- margin-bottom: 40px;
126
- border: 2px solid #e1e5e9;
127
- border-radius: 12px;
128
- overflow: hidden;
129
- }}
130
- .file-header {{
131
- background: linear-gradient(135deg, #667eea, #764ba2);
132
- color: white;
133
- padding: 20px;
134
- font-size: 1.3em;
135
- font-weight: 600;
136
- cursor: pointer;
137
- position: relative;
138
- user-select: none;
139
- }}
140
- .file-header::after {{
141
- content: '+';
142
- position: absolute;
143
- right: 20px;
144
- top: 50%;
145
- transform: translateY(-50%);
146
- font-size: 1.6em;
147
- transition: transform 0.3s ease;
148
- }}
149
- .file-header.active::after {{
150
- content: 'โˆ’';
151
- }}
152
- .file-content {{
153
- max-height: 0;
154
- overflow: hidden;
155
- transition: max-height 0.4s ease-out;
156
- }}
157
- .file-header.active + .file-content {{
158
- max-height: 10000px;
159
- padding: 25px;
160
- }}
161
- .summary {{
162
- background: #f8f9fa;
163
- border-left: 4px solid #667eea;
164
- padding: 20px;
165
- margin: 20px 0;
166
- border-radius: 0 8px 8px 0;
167
- line-height: 1.6;
168
- white-space: pre-wrap;
169
- }}
170
- .cluster {{
171
- margin: 30px 0;
172
- border: 1px solid #dee2e6;
173
- border-radius: 10px;
174
- overflow: hidden;
175
- }}
176
- .cluster-header {{
177
- background: linear-gradient(45deg, #28a745, #20c997);
178
- color: white;
179
- padding: 15px 20px;
180
- font-weight: 600;
181
- font-size: 1.1em;
182
- cursor: pointer;
183
- position: relative;
184
- user-select: none;
185
- }}
186
- .cluster-header::after {{
187
- content: '+';
188
- position: absolute;
189
- right: 20px;
190
- top: 50%;
191
- transform: translateY(-50%);
192
- font-size: 1.4em;
193
- transition: transform 0.3s ease;
194
- }}
195
- .cluster-header.active::after {{
196
- content: 'โˆ’';
197
- }}
198
- .cluster-content {{
199
- max-height: 0;
200
- overflow: hidden;
201
- transition: max-height 0.4s ease-out;
202
- }}
203
- .cluster-header.active + .cluster-content {{
204
- max-height: 10000px;
205
- padding: 20px;
206
- }}
207
- .cluster-summary {{
208
- background: #e8f5e8;
209
- border-radius: 8px;
210
- padding: 15px;
211
- line-height: 1.6;
212
- white-space: pre-wrap;
213
- }}
214
- .qa-item {{
215
- margin-bottom: 20px;
216
- padding: 15px;
217
- background: #f8f9fa;
218
- border-radius: 8px;
219
- border-left: 4px solid #007bff;
220
- }}
221
- .question {{
222
- font-weight: 600;
223
- color: #495057;
224
- margin-bottom: 10px;
225
- font-size: 1.05em;
226
- }}
227
- .answer {{
228
- color: #6c757d;
229
- line-height: 1.5;
230
- white-space: pre-wrap;
231
- }}
232
- .citation {{
233
- display: inline;
234
- background: #007bff;
235
- color: white;
236
- padding: 2px 6px;
237
- border-radius: 4px;
238
- font-size: 0.85em;
239
- font-weight: 600;
240
- cursor: pointer;
241
- margin: 0 2px;
242
- transition: all 0.2s ease;
243
- }}
244
- .citation:hover {{
245
- background: #0056b3;
246
- transform: translateY(-1px);
247
- }}
248
- .no-data {{
249
- text-align: center;
250
- color: #6c757d;
251
- font-style: italic;
252
- padding: 40px;
253
- }}
254
- .modal {{
255
- display: none;
256
- position: fixed;
257
- z-index: 10000;
258
- left: 0;
259
- top: 0;
260
- width: 100%;
261
- height: 100%;
262
- background-color: rgba(0, 0, 0, 0.6);
263
- backdrop-filter: blur(3px);
264
- }}
265
- .modal-content {{
266
- background: white;
267
- margin: 5% auto;
268
- padding: 0;
269
- border-radius: 15px;
270
- width: 90%;
271
- max-width: 700px;
272
- max-height: 80vh;
273
- overflow-y: auto;
274
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
275
- animation: modalSlideIn 0.3s ease-out;
276
- }}
277
- @keyframes modalSlideIn {{
278
- from {{ transform: translateY(-50px); opacity: 0; }}
279
- to {{ transform: translateY(0); opacity: 1; }}
280
- }}
281
- .modal-header {{
282
- background: linear-gradient(135deg, #667eea, #764ba2);
283
- color: white;
284
- padding: 20px;
285
- border-radius: 15px 15px 0 0;
286
- display: flex;
287
- justify-content: space-between;
288
- align-items: center;
289
- }}
290
- .modal-title {{
291
- font-size: 1.2em;
292
- font-weight: 600;
293
- }}
294
- .close {{
295
- color: white;
296
- font-size: 28px;
297
- font-weight: bold;
298
- cursor: pointer;
299
- line-height: 1;
300
- transition: opacity 0.2s;
301
- }}
302
- .close:hover {{
303
- opacity: 0.7;
304
- }}
305
- .modal-body {{
306
- padding: 25px;
307
- }}
308
- .context-label {{
309
- font-weight: 600;
310
- color: #495057;
311
- margin-bottom: 8px;
312
- margin-top: 15px;
313
- font-size: 1.05em;
314
- }}
315
- .context-content {{
316
- background: #f8f9fa;
317
- padding: 15px;
318
- border-radius: 8px;
319
- line-height: 1.6;
320
- border-left: 4px solid #007bff;
321
- margin-bottom: 15px;
322
- }}
323
- .context-url a {{
324
- color: #007bff;
325
- text-decoration: none;
326
- word-break: break-all;
327
- }}
328
- .context-url a:hover {{
329
- text-decoration: underline;
330
- }}
331
- </style>
332
- </head>
333
- <body>
334
- <div class="viewer-container">
335
- <div class="content" id="content">
336
- <div class="no-data"><h2>โณ Loading data...</h2></div>
337
- </div>
338
- </div>
339
-
340
- <div id="citationModal" class="modal">
341
- <div class="modal-content">
342
- <div class="modal-header">
343
- <h3 class="modal-title">Citation Details</h3>
344
- <span class="close">&times;</span>
345
- </div>
346
- <div class="modal-body">
347
- <div class="context-label">Context:</div>
348
- <div class="context-content" id="modalContext"></div>
349
- <div class="context-label">Title:</div>
350
- <div class="context-content" id="modalTitle"></div>
351
- <div class="context-label">Source URL:</div>
352
- <div class="context-url"><a id="modalUrl" href="#" target="_blank"></a></div>
353
- </div>
354
- </div>
355
- </div>
356
-
357
- <script>
358
- let currentData = {js_data};
359
-
360
- function renderContent() {{
361
- const content = document.getElementById('content');
362
-
363
- if (!currentData) {{
364
- content.innerHTML = '<div class="no-data"><h2>โš ๏ธ No data available</h2><p>Please select an event from the controls above</p></div>';
365
- return;
366
- }}
367
-
368
- const clusters = processFileData(currentData);
369
-
370
- content.innerHTML = `
371
- <div class="file-section">
372
- <div class="file-header">
373
- ๐Ÿ“„ ${{escapeHtml(currentData.file_name || 'Report')}}
374
- </div>
375
- <div class="file-content">
376
- <div class="summary"><strong>Summary:</strong><br>${{processCitations(escapeHtml(currentData.summary || 'No summary available'), currentData.summary_contexts || {{}})}}}</div>
377
- ${{renderClusters(clusters)}}
378
- </div>
379
- </div>
380
- `;
381
-
382
- initializeCollapsibles();
383
- initializeCitations();
384
- }}
385
-
386
- function processFileData(data) {{
387
- if (Array.isArray(data.clusters)) return data.clusters;
388
-
389
- const ignore = ['file_name', 'summary', 'summary_contexts'];
390
- const clusters = [];
391
-
392
- for (const key in data) {{
393
- if (!data.hasOwnProperty(key) || ignore.includes(key)) continue;
394
- const val = data[key];
395
-
396
- if (Array.isArray(val)) {{
397
- const hasQA = val.some(v => v.question || v.Question);
398
- if (hasQA) {{
399
- clusters.push({{ cluster_headline: key, questions_and_answers: val }});
400
- }} else {{
401
- const summary = val.map(v => v.cluster_summary || v.summary || '').filter(Boolean).join('\\n\\n');
402
- clusters.push({{
403
- cluster_headline: key,
404
- cluster_summary: summary || 'No summary available',
405
- used_contexts: val[0]?.used_contexts || {{}}
406
- }});
407
- }}
408
- }}
409
- }}
410
- return clusters;
411
- }}
412
-
413
- function renderClusters(clusters) {{
414
- if (!clusters || clusters.length === 0) {{
415
- return '<div class="no-data">No content available</div>';
416
- }}
417
- return clusters.map(c => `
418
- <div class="cluster">
419
- <div class="cluster-header">
420
- ๐ŸŽฏ ${{escapeHtml(c.cluster_headline || 'Cluster')}}
421
- </div>
422
- <div class="cluster-content">
423
- ${{c.questions_and_answers ? renderQA(c.questions_and_answers) : renderSummary(c)}}
424
- </div>
425
- </div>
426
- `).join('');
427
- }}
428
-
429
- function renderSummary(cluster) {{
430
- return `<div class="cluster-summary">${{processCitations(escapeHtml(cluster.cluster_summary || 'No summary available'), cluster.used_contexts || {{}})}}</div>`;
431
- }}
432
-
433
- function renderQA(qas) {{
434
- return qas.map(qa => `
435
- <div class="qa-item">
436
- <div class="question">โ“ ${{escapeHtml(qa.question || qa.Question || 'No question provided')}}</div>
437
- <div class="answer">${{processCitations(escapeHtml(qa.updated_retrieved_answer || qa.retrieved_answer || qa.answer || qa.updated_answer || 'No answer available'), qa.used_contexts || qa.summary_contexts || {{}})}}</div>
438
- </div>
439
- `).join('');
440
- }}
441
-
442
- function processCitations(text, contexts) {{
443
- if (!text || !contexts) return text || '';
444
- return text.replace(/\\[(\\d+)\\]/g, (match, id) => {{
445
- const ctx = contexts[id];
446
- if (ctx) {{
447
- const data = JSON.stringify({{
448
- context: ctx.context || '',
449
- title: ctx.title || '',
450
- url: ctx.url || ''
451
- }});
452
- return `<span class="citation" data-context="${{escapeHtml(data)}}">[${{id}}]</span>`;
453
- }}
454
- return match;
455
- }});
456
- }}
457
-
458
- function initializeCollapsibles() {{
459
- document.querySelectorAll('.file-header, .cluster-header').forEach(header => {{
460
- header.onclick = function() {{
461
- this.classList.toggle('active');
462
- }};
463
- }});
464
- }}
465
-
466
- function initializeCitations() {{
467
- document.querySelectorAll('.citation').forEach(citation => {{
468
- citation.onclick = function() {{
469
- try {{
470
- const data = JSON.parse(this.getAttribute('data-context'));
471
- document.getElementById('modalContext').textContent = data.context || 'No context available';
472
- document.getElementById('modalTitle').textContent = data.title || 'No title available';
473
- const urlElem = document.getElementById('modalUrl');
474
- urlElem.textContent = data.url || 'No URL provided';
475
- urlElem.href = data.url || '#';
476
- document.getElementById('citationModal').style.display = 'block';
477
- }} catch(e) {{
478
- console.error('Error showing citation:', e);
479
- }}
480
- }};
481
- }});
482
- }}
483
-
484
- const modal = document.getElementById('citationModal');
485
- const closeBtn = document.querySelector('.close');
486
-
487
- closeBtn.onclick = () => modal.style.display = 'none';
488
- window.onclick = e => {{ if (e.target === modal) modal.style.display = 'none'; }};
489
- document.onkeydown = e => {{ if (e.key === 'Escape') modal.style.display = 'none'; }};
490
-
491
- function escapeHtml(text) {{
492
- if (text === null || text === undefined) return '';
493
- return String(text)
494
- .replace(/&/g, '&amp;')
495
- .replace(/</g, '&lt;')
496
- .replace(/>/g, '&gt;')
497
- .replace(/"/g, '&quot;')
498
- .replace(/'/g, '&#039;');
499
- }}
500
-
501
- // Auto-render on load
502
- if (currentData) {{
503
- renderContent();
504
- }}
505
- </script>
506
- </body>
507
- </html>
508
- """
509
-
510
- # Gradio Interface
511
- with gr.Blocks(title="Report Viewer", theme=gr.themes.Soft(), css=".gradio-container { padding: 0 !important; }") as demo:
512
  gr.Markdown("# ๐Ÿ“Š Our Report Viewer")
513
- gr.Markdown("Select an event and view to visualize the data")
514
 
515
  with gr.Tab("๐Ÿ“– Viewer"):
516
- with gr.Row():
517
- with gr.Column(scale=1):
518
- event_dropdown = gr.Dropdown(
519
- choices=get_available_events(),
520
- label="Select Event",
521
- value=None,
522
- interactive=True
523
- )
524
-
525
- view_type = gr.Radio(
526
- choices=["subtopics", "sdgs"],
527
- label="Arranged by",
528
- value="subtopics"
529
- )
530
-
531
- format_type = gr.Radio(
532
- choices=["qa", "summary"],
533
- label="Presented as",
534
- value="qa"
535
- )
536
-
537
- refresh_btn = gr.Button("๐Ÿ”„ Load Data", variant="primary")
538
-
539
- html_output = gr.HTML(create_full_html())
540
-
541
- def update_viewer(event, fmt, view):
542
- if not event:
543
- return create_full_html()
544
-
545
- data = load_json_data(event, fmt, view)
546
- return create_full_html(data)
547
-
548
- # Update on button click
549
- refresh_btn.click(
550
- update_viewer,
551
- inputs=[event_dropdown, format_type, view_type],
552
- outputs=html_output
553
- )
554
-
555
- # Also update on dropdown change
556
- event_dropdown.change(
557
- update_viewer,
558
- inputs=[event_dropdown, format_type, view_type],
559
- outputs=html_output
560
- )
561
 
562
  with gr.Tab("๐Ÿ’ฌ Feedback"):
563
  gr.Markdown("### Leave your feedback on the results")
@@ -603,4 +173,6 @@ with gr.Blocks(title="Report Viewer", theme=gr.themes.Soft(), css=".gradio-conta
603
  )
604
 
605
  if __name__ == "__main__":
606
- demo.launch()
 
 
 
2
  import json
3
  import os
4
  from pathlib import Path
5
+ from threading import Thread
6
+ from flask import Flask, send_from_directory, jsonify
7
+ import socket
8
+
9
+ # Flask app to serve static files
10
+ flask_app = Flask(__name__)
11
 
 
12
  DATA_BASE = "Viewer/Data/Our_system"
13
  FOLDER_MAP = {
14
  "qa_subtopics": f"{DATA_BASE}/QA+Topics",
 
17
  "summary_sdgs": f"{DATA_BASE}/Summary+SDG"
18
  }
19
 
20
+ @flask_app.route('/Data/<path:filepath>')
21
+ def serve_data(filepath):
22
+ """Serve JSON files from Data folder"""
23
+ return send_from_directory('Viewer/Data', filepath)
24
+
25
+ @flask_app.route('/api/list-files/<folder_key>')
26
+ def list_files(folder_key):
27
+ """List files in a specific folder"""
28
+ folder = FOLDER_MAP.get(folder_key)
29
+ if not folder or not os.path.exists(folder):
30
+ return jsonify([])
31
+
32
+ files = [f for f in os.listdir(folder) if f.endswith('_combined_data.json')]
33
+ return jsonify(files)
34
+
35
+ def find_free_port():
36
+ """Find a free port for Flask"""
37
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
38
+ s.bind(('', 0))
39
+ s.listen(1)
40
+ port = s.getsockname()[1]
41
+ return port
42
+
43
+ def run_flask(port):
44
+ """Run Flask server"""
45
+ flask_app.run(host='0.0.0.0', port=port, debug=False, use_reloader=False)
46
+
47
+ # Start Flask server in background
48
+ FLASK_PORT = find_free_port()
49
+ flask_thread = Thread(target=run_flask, args=(FLASK_PORT,), daemon=True)
50
+ flask_thread.start()
51
+
52
+ print(f"Flask server started on port {FLASK_PORT}")
53
+
54
+ # Read your original HTML and inject Flask port
55
+ with open("Viewer/viewer_v2.html", "r", encoding="utf-8") as f:
56
+ original_html = f.read()
57
+
58
+ # Modify the folderMap to use Flask server
59
+ modified_html = original_html.replace(
60
+ 'const folderMap = {',
61
+ f'''const FLASK_PORT = {FLASK_PORT};
62
+ const folderMap = {{'''
63
+ ).replace(
64
+ '"./Data/Our_system/QA+Topics"',
65
+ f'"http://localhost:{FLASK_PORT}/Data/Our_system/QA+Topics"'
66
+ ).replace(
67
+ '"./Data/Our_system/QA+SDGs"',
68
+ f'"http://localhost:{FLASK_PORT}/Data/Our_system/QA+SDGs"'
69
+ ).replace(
70
+ '"./Data/Our_system/Summary+Topics"',
71
+ f'"http://localhost:{FLASK_PORT}/Data/Our_system/Summary+Topics"'
72
+ ).replace(
73
+ '"./Data/Our_system/Summary+SDG"',
74
+ f'"http://localhost:{FLASK_PORT}/Data/Our_system/Summary+SDG"'
75
+ )
76
+
77
+ # Feedback functions
78
  def get_available_events():
79
  """Get all available events from JSON files"""
80
  events = set()
 
83
  for file in os.listdir(folder):
84
  if file.endswith('_combined_data.json'):
85
  event_name = file.replace('_combined_data.json', '')
 
86
  if not event_name.isdigit():
87
  events.add(event_name)
88
  return sorted(list(events))
89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  def save_feedback(event, format_type, view_type, rating, comment):
91
  """Save feedback to JSON file"""
92
  if not event:
 
121
 
122
  return "โœ… Thank you for your feedback!"
123
 
124
+ # Create Gradio interface
125
+ with gr.Blocks(title="Report Viewer", theme=gr.themes.Soft()) as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  gr.Markdown("# ๐Ÿ“Š Our Report Viewer")
127
+ gr.Markdown("Explore events organized by topics or SDGs")
128
 
129
  with gr.Tab("๐Ÿ“– Viewer"):
130
+ gr.HTML(modified_html)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
 
132
  with gr.Tab("๐Ÿ’ฌ Feedback"):
133
  gr.Markdown("### Leave your feedback on the results")
 
173
  )
174
 
175
  if __name__ == "__main__":
176
+ import time
177
+ time.sleep(1) # Wait for Flask to start
178
+ demo.launch(server_port=7860)