acecalisto3 commited on
Commit
fd61735
·
verified ·
1 Parent(s): 43087c2

Update app2.py

Browse files
Files changed (1) hide show
  1. app2.py +159 -89
app2.py CHANGED
@@ -14,6 +14,7 @@ from datetime import datetime
14
  from typing import List, Dict, Optional, Union, Tuple, Any
15
  from pathlib import Path
16
  from urllib.parse import urlparse, urljoin
 
17
  import requests
18
  import validators
19
  import gradio as gr
@@ -928,7 +929,6 @@ def generate_qr_codes(data: Union[str, Dict, List], combined: bool = True) -> Li
928
  return []
929
 
930
 
931
- # --- Chatbot Logic ---
932
  def respond_to_chat(
933
  message: str,
934
  chat_history: List[Tuple[str, str]],
@@ -1219,6 +1219,31 @@ def respond_to_chat(
1219
  return chat_history, chatbot_data, new_filtered_df_state
1220
 
1221
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1222
  # --- Gradio Interface Definition ---
1223
  def create_modern_interface():
1224
  """Create a modern and visually appealing Gradio interface"""
@@ -1233,15 +1258,6 @@ def create_modern_interface():
1233
  --error-color: #f56565;
1234
  --warning-color: #ed8936;
1235
  }
1236
- /* Container styling */
1237
- .container {
1238
- max-width: 1200px;
1239
- margin: auto;
1240
- padding: 2rem;
1241
- background-color: var(--background-color);
1242
- border-radius: 1rem;
1243
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
1244
- }
1245
  /* Component styling */
1246
  .input-container {
1247
  background-color: white;
@@ -1264,19 +1280,10 @@ def create_modern_interface():
1264
  background-color: var(--accent-color);
1265
  transform: translateY(-1px);
1266
  }
1267
- /* Status messages */
1268
- .status {
1269
- padding: 1rem;
1270
- border-radius: 0.375rem;
1271
- margin: 1rem 0;
1272
- }
1273
- .status.success { background-color: #f0fff4; color: var(--success-color); }
1274
- .status.error { background-color: #fff5f5; color: var(--error-color); }
1275
- .status.warning { background-color: #fffaf0; color: var(--warning-color); }
1276
  /* Gallery styling */
1277
  .gallery {
1278
  display: grid;
1279
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
1280
  gap: 1rem;
1281
  padding: 1rem;
1282
  background-color: white;
@@ -1315,6 +1322,19 @@ def create_modern_interface():
1315
  max-width: 150px;
1316
  max-height: 150px;
1317
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
1318
  """
1319
  with gr.Blocks(css=css, title="Advanced Data Processor & QR Generator") as interface:
1320
  interface.head += """
@@ -1336,8 +1356,27 @@ def create_modern_interface():
1336
  }
1337
  console.log("Enabled QR Code Indices:", enabledStates);
1338
  }
 
 
 
 
 
 
 
 
 
 
 
1339
  </script>
1340
  """
 
 
 
 
 
 
 
 
1341
  with gr.Row():
1342
  crawl_depth_slider = gr.Slider(
1343
  label="Crawl Depth",
@@ -1348,12 +1387,6 @@ def create_modern_interface():
1348
  interactive=True,
1349
  info="Select the maximum depth for crawling links (0-10)."
1350
  )
1351
- qr_code_paths = gr.State([])
1352
- chatbot_data = gr.State(None)
1353
- gr.Markdown("""
1354
- # 🌐 Advanced Data Processing & QR Code Generator
1355
- Transform your data into beautifully designed, sequenced QR codes with our cutting-edge processor.
1356
- """)
1357
  with gr.Tab("📝 URL Processing"):
1358
  url_input = gr.Textbox(
1359
  label="Enter URLs (comma or newline separated)",
@@ -1385,30 +1418,42 @@ def create_modern_interface():
1385
  )
1386
  generate_qr_toggle = gr.Checkbox(
1387
  label="Generate QR Codes",
1388
- value=False, # Default to False as per task
1389
  info="Enable to generate QR codes for the processed data."
1390
  )
1391
  process_btn = gr.Button(
1392
  "🔄 Process & Generate QR",
1393
  variant="primary"
1394
  )
1395
- output_json = gr.JSON(label="Processed Data")
1396
- output_gallery = gr.Gallery(
1397
- label="Generated QR Codes",
1398
- columns=3,
1399
- height=400,
1400
- show_label=True
1401
- )
1402
- output_text = gr.Textbox(
1403
- label="Processing Status",
1404
- interactive=False
1405
- )
 
 
 
 
 
 
 
 
 
 
 
1406
  with gr.Tab("🖼️ QR Code Viewport") as viewport_tab:
1407
  viewport_output = gr.HTML(label="QR Code Sequence Viewport")
1408
  enabled_qr_codes = gr.State([])
 
1409
  with gr.Tab("🤖 Chat with Data") as chat_tab:
1410
  chat_history = gr.State([])
1411
- chatbot = gr.Chatbot(label="Data Chatbot")
1412
  filtered_chatbot_df_state = gr.State(None) # To store the filtered DataFrame
1413
  with gr.Row():
1414
  chat_input = gr.Textbox(label="Your Message", placeholder="Ask me about the processed data...")
@@ -1416,7 +1461,7 @@ def create_modern_interface():
1416
  with gr.Row():
1417
  download_full_json_btn = gr.Button("Download Full JSON")
1418
  download_filtered_json_btn = gr.Button("Download Filtered JSON")
1419
- download_file_output = gr.File(label="Download Data", interactive=False) # For triggering download
1420
  clear_chat_btn = gr.Button("Clear Chat History")
1421
 
1422
  def load_example():
@@ -1454,22 +1499,35 @@ def create_modern_interface():
1454
  def update_viewport(paths, enabled_states):
1455
  if not paths:
1456
  return "<p>No QR codes generated yet.</p>"
 
 
 
 
 
 
 
1457
  num_qr_codes = len(paths)
1458
  cols = math.ceil(math.sqrt(num_qr_codes))
1459
- cols = max(1, min(cols, 6))
1460
- viewport_html = f'<div class="viewport-container" style="grid-template-columns: repeat({cols}, 1fr);">'
 
 
1461
  if enabled_states is None or len(enabled_states) != num_qr_codes or not enabled_states:
1462
  enabled_states = list(range(num_qr_codes))
 
1463
  for i, path in enumerate(paths):
1464
  is_enabled = i in enabled_states
1465
- border = "border: 2px solid green;" if is_enabled else "border: 2px solid lightgray;"
1466
  opacity = "opacity: 1.0;" if is_enabled else "opacity: 0.5;"
1467
- viewport_html += f'<div class="viewport-item" id="qr_item_{i}">'
1468
- viewport_html += f'<img src="/file={path}" style="{border} {opacity}" alt="QR Code {i + 1}">'
1469
- viewport_html += f'<label><input type="checkbox" data-index="{i}" {"checked" if is_enabled else ""} onchange="updateEnabledStates(this)"> Enable</label>'
1470
- viewport_html += '</div>'
1471
- viewport_html += '</div>'
1472
- return viewport_html
 
 
 
1473
 
1474
  def process_inputs(urls, files, text, combine, crawl_depth, generate_qr_enabled):
1475
  """Process all inputs and generate QR codes based on toggle"""
@@ -1557,7 +1615,8 @@ def create_modern_interface():
1557
  final_json_output,
1558
  [str(path) for path in qr_paths],
1559
  "\n".join(processing_status_messages),
1560
- final_json_output
 
1561
  )
1562
 
1563
  def on_qr_generation(qr_paths_list):
@@ -1565,18 +1624,28 @@ def create_modern_interface():
1565
  initial_enabled_states = list(range(num_qrs))
1566
  return qr_paths_list, initial_enabled_states
1567
 
 
1568
  example_btn.click(load_example, inputs=[], outputs=text_input)
1569
  clear_btn.click(clear_input, inputs=[], outputs=[url_input, file_input, text_input, chatbot_data])
 
1570
  process_btn.click(
1571
  process_inputs,
1572
  inputs=[url_input, file_input, text_input, combine_data, crawl_depth_slider, generate_qr_toggle],
1573
- outputs=[output_json, output_gallery, output_text, chatbot_data]
1574
  ).then(
1575
  on_qr_generation,
1576
  inputs=[output_gallery],
1577
  outputs=[qr_code_paths, enabled_qr_codes]
1578
  )
 
 
 
 
 
 
 
1579
  viewport_tab.select(update_viewport, inputs=[qr_code_paths, enabled_qr_codes], outputs=[viewport_output])
 
1580
  send_msg_btn.click(
1581
  respond_to_chat,
1582
  inputs=[chat_input, chat_history, chatbot_data, filtered_chatbot_df_state],
@@ -1586,6 +1655,7 @@ def create_modern_interface():
1586
  inputs=None,
1587
  outputs=chat_input
1588
  )
 
1589
  chat_input.submit(
1590
  respond_to_chat,
1591
  inputs=[chat_input, chat_history, chatbot_data, filtered_chatbot_df_state],
@@ -1595,19 +1665,35 @@ def create_modern_interface():
1595
  inputs=None,
1596
  outputs=chat_input
1597
  )
 
1598
  clear_chat_btn.click(
1599
- lambda: ([], None),
1600
  inputs=None,
1601
- outputs=[chatbot, filtered_chatbot_df_state]
1602
  )
1603
 
1604
- def download_json_data(data_df: Optional[pd.DataFrame], filename_prefix: str) -> Optional[str]:
1605
- if data_df is None or data_df.empty:
1606
  logger.info(f"No data provided for download with prefix '{filename_prefix}'.")
1607
  return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1608
  try:
1609
- data_list = data_df.to_dict(orient='records')
1610
- json_str = json.dumps(data_list, indent=2, ensure_ascii=False)
1611
  timestamp = int(time.time())
1612
  filename = f"{filename_prefix}_{timestamp}.json"
1613
  file_path = TEMP_DIR / filename
@@ -1619,23 +1705,18 @@ def create_modern_interface():
1619
  logger.error(f"Error creating JSON file for {filename_prefix}: {e}")
1620
  return None
1621
 
 
1622
  def handle_download_full_json(current_chatbot_data_state: Optional[List[Dict]]) -> Optional[str]:
1623
  if not current_chatbot_data_state:
1624
  logger.info("No full data available to download.")
 
1625
  return None
1626
- try:
1627
- df_to_download = pd.DataFrame(current_chatbot_data_state)
1628
- if df_to_download.empty:
1629
- logger.info("Full data resulted in an empty DataFrame. Nothing to download.")
1630
- return None
1631
- except Exception as e:
1632
- logger.error(f"Error converting full chatbot_data to DataFrame for download: {e}")
1633
- return None
1634
- return download_json_data(df_to_download, "full_data")
1635
 
1636
  def handle_download_filtered_json(current_filtered_df_state: Optional[pd.DataFrame]) -> Optional[str]:
1637
  if current_filtered_df_state is None or current_filtered_df_state.empty:
1638
  logger.info("No filtered data available to download.")
 
1639
  return None
1640
  return download_json_data(current_filtered_df_state, "filtered_data")
1641
 
@@ -1649,33 +1730,22 @@ def create_modern_interface():
1649
  inputs=[filtered_chatbot_df_state],
1650
  outputs=[download_file_output]
1651
  )
 
1652
  gr.Markdown("""
1653
  ### 🚀 Features
1654
- - **Enhanced URL Scraping**: Extracts HTML text, title, meta description, links, and attempts parsing JSON/XML from URLs based on content type. Supports crawling links up to a specified depth. **(Now performs real fetching)**
1655
- - **Advanced File Processing**: Reads various text-based files (.txt, .md, .log etc.), HTML, XML, CSV, and attempts text extraction from common documents (.pdf, .docx, .rtf, .odt - *requires extra dependencies*). **(Now performs real file processing)**
1656
- - **Smart JSON Handling**: Parses valid JSON from direct input, files (.json or content), or URLs.
1657
- - **Archive Support**: Extracts and processes supported files from .zip, .tar, .gz archives. **(Now performs real extraction)**
1658
- - **Robust Encoding Detection**: Uses `chardet` for reliable character encoding identification.
1659
- - **Structured Output**: Provides a consistent JSON output format containing raw content (if applicable), extracted data, and processing notes for each processed item.
1660
- - **Sequential QR Codes**: Maintains data integrity across multiple codes by chunking the combined/individual processed data, **including positional sequencing tags `{startN}` and `{endN}` in the QR code content**.
1661
- - **QR Code Viewport**: Visualize generated QR codes in a sequenced square grid with options to enable/disable individual codes for selective scanning/sharing.
1662
- - **Modern Design**: Clean, responsive interface with visual feedback.
1663
- - **Data Chatbot**: Interact conversationally with the processed JSON data to ask questions about its structure, content, or request specific information.
1664
  ### 💡 Tips
1665
- 1. **URLs**: Enter multiple URLs separated by commas or newlines. The processor will attempt to fetch and structure the content based on its type, following links up to the specified **Crawl Depth**.
1666
- 2. **Files**: Upload any type of file. The processor will attempt to handle supported text-based files, archives (.zip, .tar, .gz), and specific document/structured formats.
1667
- 3. **JSON**: Use the "Direct JSON Input" tab for pasting JSON data. The system also tries to detect JSON content in file uploads and URLs. Use the "Load Example" button to see a sample JSON structure.
1668
- 4. **Dependencies**: Processing PDF, DOCX, RTF, and ODT files requires installing optional Python libraries (`PyPDF2`, `python-docx`, `pyth`, `odfpy`). Check the console logs for warnings if a library is missing.
1669
- 5. **QR Codes**: Choose whether to "Combine all data into sequence" or generate separate sequences for each input item. **Remember to check the "Generate QR Codes" checkbox!**
1670
- 6. **Processing**: Monitor the "Processing Status" box for real-time updates and notes about errors or processing steps.
1671
- 7. **Output**: The "Processed Data" JSON box shows the structured data extracted from your inputs. The "Generated QR Codes" gallery shows the QR code images.
1672
- 8. **Chatbot**: After processing data, go to the "Chat with Data" tab to ask questions about the JSON output.
1673
- ### ⚙️ QR Code Viewport Instructions
1674
- 1. Navigate to the **QR Code Viewport** tab after generating QR codes.
1675
- 2. The generated QR codes will be displayed in a grid based on their total count.
1676
- 3. Use the checkboxes below each QR code to enable or disable it for visual selection. Enabled codes have a green border and full opacity.
1677
- 4. This viewport is currently for visualization and selection *within the UI*; it doesn't change the generated files themselves. You would manually select which physical QR codes to scan based on this view.
1678
  """)
 
1679
  return interface
1680
 
1681
 
 
14
  from typing import List, Dict, Optional, Union, Tuple, Any
15
  from pathlib import Path
16
  from urllib.parse import urlparse, urljoin
17
+
18
  import requests
19
  import validators
20
  import gradio as gr
 
929
  return []
930
 
931
 
 
932
  def respond_to_chat(
933
  message: str,
934
  chat_history: List[Tuple[str, str]],
 
1219
  return chat_history, chatbot_data, new_filtered_df_state
1220
 
1221
 
1222
+ def create_qr_zip(qr_paths: List[str]) -> Optional[str]:
1223
+ """Creates a zip archive from a list of QR code image paths."""
1224
+ if not qr_paths:
1225
+ logger.warning("Attempted to create a zip archive, but no QR code paths were provided.")
1226
+ return None # Return None to prevent Gradio from attempting a download
1227
+ try:
1228
+ timestamp = int(time.time())
1229
+ zip_filename = f"qr_code_collection_{timestamp}.zip"
1230
+ zip_filepath = TEMP_DIR / zip_filename
1231
+
1232
+ with zipfile.ZipFile(zip_filepath, 'w') as zipf:
1233
+ for path_str in qr_paths:
1234
+ path = Path(path_str)
1235
+ if path.exists():
1236
+ # Use path.name to avoid storing the full directory structure in the zip
1237
+ zipf.write(path, arcname=path.name)
1238
+ else:
1239
+ logger.warning(f"QR code file not found, skipping: {path_str}")
1240
+
1241
+ logger.info(f"Successfully created QR code zip archive: {zip_filepath}")
1242
+ return str(zip_filepath)
1243
+ except Exception as e:
1244
+ logger.error(f"Failed to create QR code zip archive: {e}")
1245
+ return None
1246
+
1247
  # --- Gradio Interface Definition ---
1248
  def create_modern_interface():
1249
  """Create a modern and visually appealing Gradio interface"""
 
1258
  --error-color: #f56565;
1259
  --warning-color: #ed8936;
1260
  }
 
 
 
 
 
 
 
 
 
1261
  /* Component styling */
1262
  .input-container {
1263
  background-color: white;
 
1280
  background-color: var(--accent-color);
1281
  transform: translateY(-1px);
1282
  }
 
 
 
 
 
 
 
 
 
1283
  /* Gallery styling */
1284
  .gallery {
1285
  display: grid;
1286
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
1287
  gap: 1rem;
1288
  padding: 1rem;
1289
  background-color: white;
 
1322
  max-width: 150px;
1323
  max-height: 150px;
1324
  }
1325
+ /* --- NEW: Fullscreen Enhancements --- */
1326
+ #fullscreen-viewport-wrapper:fullscreen {
1327
+ background-color: var(--background-color) !important;
1328
+ overflow-y: auto;
1329
+ padding: 2rem;
1330
+ }
1331
+ #fullscreen-viewport-wrapper:fullscreen .viewport-container {
1332
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
1333
+ }
1334
+ #fullscreen-viewport-wrapper:fullscreen .viewport-item img {
1335
+ max-width: none;
1336
+ max-height: none;
1337
+ }
1338
  """
1339
  with gr.Blocks(css=css, title="Advanced Data Processor & QR Generator") as interface:
1340
  interface.head += """
 
1356
  }
1357
  console.log("Enabled QR Code Indices:", enabledStates);
1358
  }
1359
+ function goFullscreen(elementId) {
1360
+ const elem = document.getElementById(elementId);
1361
+ if (!elem) return;
1362
+ if (elem.requestFullscreen) {
1363
+ elem.requestFullscreen();
1364
+ } else if (elem.webkitRequestFullscreen) { /* Safari */
1365
+ elem.webkitRequestFullscreen();
1366
+ } else if (elem.msRequestFullscreen) { /* IE11 */
1367
+ elem.msRequestFullscreen();
1368
+ }
1369
+ }
1370
  </script>
1371
  """
1372
+ qr_code_paths = gr.State([])
1373
+ chatbot_data = gr.State(None)
1374
+
1375
+ gr.Markdown("""
1376
+ # 🌐 Advanced Data Processing & QR Code Generator
1377
+ Transform your data into beautifully designed, sequenced QR codes with our cutting-edge processor.
1378
+ """)
1379
+
1380
  with gr.Row():
1381
  crawl_depth_slider = gr.Slider(
1382
  label="Crawl Depth",
 
1387
  interactive=True,
1388
  info="Select the maximum depth for crawling links (0-10)."
1389
  )
 
 
 
 
 
 
1390
  with gr.Tab("📝 URL Processing"):
1391
  url_input = gr.Textbox(
1392
  label="Enter URLs (comma or newline separated)",
 
1418
  )
1419
  generate_qr_toggle = gr.Checkbox(
1420
  label="Generate QR Codes",
1421
+ value=False,
1422
  info="Enable to generate QR codes for the processed data."
1423
  )
1424
  process_btn = gr.Button(
1425
  "🔄 Process & Generate QR",
1426
  variant="primary"
1427
  )
1428
+
1429
+ # --- NEW: Two-Column Output Layout ---
1430
+ with gr.Row():
1431
+ with gr.Column(scale=1):
1432
+ output_json = gr.JSON(label="Processed Data")
1433
+ with gr.Column(scale=1):
1434
+ output_gallery = gr.Gallery(
1435
+ label="Generated QR Codes",
1436
+ columns=None,
1437
+ height="auto",
1438
+ show_label=True,
1439
+ elem_classes=["gallery"]
1440
+ )
1441
+ download_qrs_btn = gr.Button("⬇️ Download All QR Codes as ZIP")
1442
+ qr_zip_output = gr.File(label="Download QR Code ZIP", interactive=False)
1443
+ output_text = gr.Textbox(
1444
+ label="Processing Status",
1445
+ interactive=False,
1446
+ lines=8
1447
+ )
1448
+ # --- End of New Layout ---
1449
+
1450
  with gr.Tab("🖼️ QR Code Viewport") as viewport_tab:
1451
  viewport_output = gr.HTML(label="QR Code Sequence Viewport")
1452
  enabled_qr_codes = gr.State([])
1453
+
1454
  with gr.Tab("🤖 Chat with Data") as chat_tab:
1455
  chat_history = gr.State([])
1456
+ chatbot = gr.Chatbot(label="Data Chatbot", height=500)
1457
  filtered_chatbot_df_state = gr.State(None) # To store the filtered DataFrame
1458
  with gr.Row():
1459
  chat_input = gr.Textbox(label="Your Message", placeholder="Ask me about the processed data...")
 
1461
  with gr.Row():
1462
  download_full_json_btn = gr.Button("Download Full JSON")
1463
  download_filtered_json_btn = gr.Button("Download Filtered JSON")
1464
+ download_file_output = gr.File(label="Download Data", interactive=False)
1465
  clear_chat_btn = gr.Button("Clear Chat History")
1466
 
1467
  def load_example():
 
1499
  def update_viewport(paths, enabled_states):
1500
  if not paths:
1501
  return "<p>No QR codes generated yet.</p>"
1502
+
1503
+ # Wrapper div with an ID for fullscreen targeting
1504
+ html_content = '<div id="fullscreen-viewport-wrapper" style="padding:1rem; border: 1px solid #ddd; border-radius: 0.5rem;">'
1505
+
1506
+ # Fullscreen button
1507
+ html_content += '<button onclick="goFullscreen(\'fullscreen-viewport-wrapper\')" class="primary-button" style="margin-bottom: 1rem;">View Fullscreen</button>'
1508
+
1509
  num_qr_codes = len(paths)
1510
  cols = math.ceil(math.sqrt(num_qr_codes))
1511
+ cols = max(1, min(cols, 8))
1512
+
1513
+ html_content += f'<div class="viewport-container" style="grid-template-columns: repeat({cols}, 1fr);">'
1514
+
1515
  if enabled_states is None or len(enabled_states) != num_qr_codes or not enabled_states:
1516
  enabled_states = list(range(num_qr_codes))
1517
+
1518
  for i, path in enumerate(paths):
1519
  is_enabled = i in enabled_states
1520
+ border = "border: 2px solid var(--success-color);" if is_enabled else "border: 2px solid #ccc;"
1521
  opacity = "opacity: 1.0;" if is_enabled else "opacity: 0.5;"
1522
+ html_content += f'<div class="viewport-item" id="qr_item_{i}">'
1523
+ html_content += f'<img src="/file={path}" style="{border} {opacity}" alt="QR Code {i + 1}">'
1524
+ html_content += f'<label style="font-size: 0.8em; margin-top: 4px;"><input type="checkbox" data-index="{i}" {"checked" if is_enabled else ""} onchange="updateEnabledStates(this)"> Enable</label>'
1525
+ html_content += '</div>'
1526
+
1527
+ html_content += '</div>'
1528
+ html_content += '</div>'
1529
+
1530
+ return html_content
1531
 
1532
  def process_inputs(urls, files, text, combine, crawl_depth, generate_qr_enabled):
1533
  """Process all inputs and generate QR codes based on toggle"""
 
1615
  final_json_output,
1616
  [str(path) for path in qr_paths],
1617
  "\n".join(processing_status_messages),
1618
+ final_json_output,
1619
+ None
1620
  )
1621
 
1622
  def on_qr_generation(qr_paths_list):
 
1624
  initial_enabled_states = list(range(num_qrs))
1625
  return qr_paths_list, initial_enabled_states
1626
 
1627
+ # Event Handlers
1628
  example_btn.click(load_example, inputs=[], outputs=text_input)
1629
  clear_btn.click(clear_input, inputs=[], outputs=[url_input, file_input, text_input, chatbot_data])
1630
+
1631
  process_btn.click(
1632
  process_inputs,
1633
  inputs=[url_input, file_input, text_input, combine_data, crawl_depth_slider, generate_qr_toggle],
1634
+ outputs=[output_json, output_gallery, output_text, chatbot_data, qr_zip_output]
1635
  ).then(
1636
  on_qr_generation,
1637
  inputs=[output_gallery],
1638
  outputs=[qr_code_paths, enabled_qr_codes]
1639
  )
1640
+
1641
+ download_qrs_btn.click(
1642
+ fn=create_qr_zip,
1643
+ inputs=[qr_code_paths],
1644
+ outputs=[qr_zip_output]
1645
+ )
1646
+
1647
  viewport_tab.select(update_viewport, inputs=[qr_code_paths, enabled_qr_codes], outputs=[viewport_output])
1648
+
1649
  send_msg_btn.click(
1650
  respond_to_chat,
1651
  inputs=[chat_input, chat_history, chatbot_data, filtered_chatbot_df_state],
 
1655
  inputs=None,
1656
  outputs=chat_input
1657
  )
1658
+
1659
  chat_input.submit(
1660
  respond_to_chat,
1661
  inputs=[chat_input, chat_history, chatbot_data, filtered_chatbot_df_state],
 
1665
  inputs=None,
1666
  outputs=chat_input
1667
  )
1668
+
1669
  clear_chat_btn.click(
1670
+ lambda: ([], None, []),
1671
  inputs=None,
1672
+ outputs=[chatbot, filtered_chatbot_df_state, chat_history]
1673
  )
1674
 
1675
+ def download_json_data(data: Optional[Union[pd.DataFrame, List[Dict]]], filename_prefix: str) -> Optional[str]:
1676
+ if data is None:
1677
  logger.info(f"No data provided for download with prefix '{filename_prefix}'.")
1678
  return None
1679
+
1680
+ data_to_dump = None
1681
+ if isinstance(data, pd.DataFrame):
1682
+ if data.empty:
1683
+ logger.info(f"DataFrame for '{filename_prefix}' is empty. Nothing to download.")
1684
+ return None
1685
+ data_to_dump = data.to_dict(orient='records')
1686
+ elif isinstance(data, list):
1687
+ if not data:
1688
+ logger.info(f"List for '{filename_prefix}' is empty. Nothing to download.")
1689
+ return None
1690
+ data_to_dump = data
1691
+
1692
+ if data_to_dump is None:
1693
+ return None
1694
+
1695
  try:
1696
+ json_str = json.dumps(data_to_dump, indent=2, ensure_ascii=False)
 
1697
  timestamp = int(time.time())
1698
  filename = f"{filename_prefix}_{timestamp}.json"
1699
  file_path = TEMP_DIR / filename
 
1705
  logger.error(f"Error creating JSON file for {filename_prefix}: {e}")
1706
  return None
1707
 
1708
+
1709
  def handle_download_full_json(current_chatbot_data_state: Optional[List[Dict]]) -> Optional[str]:
1710
  if not current_chatbot_data_state:
1711
  logger.info("No full data available to download.")
1712
+ gr.Warning("No data has been processed yet!")
1713
  return None
1714
+ return download_json_data(current_chatbot_data_state, "full_data_collection")
 
 
 
 
 
 
 
 
1715
 
1716
  def handle_download_filtered_json(current_filtered_df_state: Optional[pd.DataFrame]) -> Optional[str]:
1717
  if current_filtered_df_state is None or current_filtered_df_state.empty:
1718
  logger.info("No filtered data available to download.")
1719
+ gr.Warning("No filtered data to download. Please filter data in the chat first.")
1720
  return None
1721
  return download_json_data(current_filtered_df_state, "filtered_data")
1722
 
 
1730
  inputs=[filtered_chatbot_df_state],
1731
  outputs=[download_file_output]
1732
  )
1733
+
1734
  gr.Markdown("""
1735
  ### 🚀 Features
1736
+ - **Enhanced URL Scraping**: Extracts HTML text, title, meta description, links, and attempts parsing JSON/XML from URLs based on content type. Supports crawling links up to a specified depth.
1737
+ - **Advanced File Processing**: Reads various text-based files, HTML, XML, CSV, and attempts text extraction from common documents (.pdf, .docx, .rtf, .odt).
1738
+ - **Archive Support**: Extracts and processes supported files from .zip, .tar, .gz archives.
1739
+ - **Data Chatbot**: Interact conversationally with the processed JSON data to ask questions, filter, and get insights.
1740
+ - **Sequential QR Codes**: Chunks large data and embeds sequencing info for reconstruction.
1741
+ - **QR Code Viewport**: A dedicated tab with a **fullscreen mode** for viewing the entire QR code collection.
1742
+ - **Bulk Download**: Download all generated QR codes as a single ZIP file.
 
 
 
1743
  ### 💡 Tips
1744
+ 1. **Layout**: The output is split into two columns: raw JSON on the left, and the QR Code gallery + status log on the right. This prevents the status log from hiding the QR codes.
1745
+ 2. **Fullscreen**: For the best viewing experience of all QR codes, navigate to the **"QR Code Viewport"** tab and click the **"View Fullscreen"** button.
1746
+ 3. **Download**: Use the **"Download All QR Codes as ZIP"** button located directly under the QR code gallery to save all images at once.
 
 
 
 
 
 
 
 
 
 
1747
  """)
1748
+
1749
  return interface
1750
 
1751