mknolan commited on
Commit
1cf83cb
·
verified ·
1 Parent(s): ee821bb

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +273 -150
app.py CHANGED
@@ -37,52 +37,39 @@ gui_stats = {
37
  'tensor_issues': 0
38
  }
39
 
40
- # Function to get stats for UI display
41
- def get_debug_stats():
42
- uptime = datetime.datetime.now() - gui_stats['start_time']
43
- hours, remainder = divmod(uptime.seconds, 3600)
44
- minutes, seconds = divmod(remainder, 60)
45
- uptime_str = f"{hours}h {minutes}m {seconds}s"
46
-
47
- return {
48
- 'errors': gui_stats['errors'],
49
- 'warnings': gui_stats['warnings'],
50
- 'last_error': gui_stats['last_error'],
51
- 'last_error_time': gui_stats['last_error_time'],
52
- 'last_warning': gui_stats['last_warning'],
53
- 'last_warning_time': gui_stats['last_warning_time'],
54
- 'operations': gui_stats['operations_completed'],
55
- 'uptime': uptime_str,
56
- 'tensor_issues': gui_stats['tensor_issues']
57
- }
58
 
59
- # Function to format debug stats as HTML
60
- def format_debug_stats_html():
61
- stats = get_debug_stats()
62
-
63
- error_color = "#ff5555" if stats['errors'] > 0 else "#555555"
64
- warning_color = "#ffaa00" if stats['warnings'] > 0 else "#555555"
65
-
66
- html = f"""
67
- <div style="margin: 10px 0; padding: 10px; border: 1px solid #ddd; border-radius: 4px; background-color: #f9f9f9;">
68
- <div style="display: flex; justify-content: space-between;">
69
- <div style="flex: 1;">
70
- <p><strong>Errors:</strong> <span style="color: {error_color};">{stats['errors']}</span></p>
71
- <p><strong>Warnings:</strong> <span style="color: {warning_color};">{stats['warnings']}</span></p>
72
- <p><strong>Operations:</strong> {stats['operations']}</p>
73
- </div>
74
- <div style="flex: 1;">
75
- <p><strong>Uptime:</strong> {stats['uptime']}</p>
76
- <p><strong>Tensor Issues:</strong> {stats['tensor_issues']}</p>
77
- </div>
78
- </div>
79
- <div style="margin-top: 10px; border-top: 1px solid #ddd; padding-top: 10px;">
80
- <p><strong>Last Error:</strong> {stats['last_error_time']} - {stats['last_error']}</p>
81
- <p><strong>Last Warning:</strong> {stats['last_warning_time']} - {stats['last_warning']}</p>
82
- </div>
83
- </div>
84
- """
85
- return html
 
 
86
 
87
  # Custom logging handler that captures logs for GUI display
88
  class GUILogHandler(logging.Handler):
@@ -137,52 +124,52 @@ class GUILogHandler(logging.Handler):
137
  with self.lock:
138
  self.log_entries = []
139
 
140
- # Constants
141
- IMAGENET_MEAN = (0.485, 0.456, 0.406)
142
- IMAGENET_STD = (0.229, 0.224, 0.225)
143
-
144
- # Configuration
145
- MODEL_NAME = "OpenGVLab/InternVL2_5-8B" # Smaller model for faster loading
146
- IMAGE_SIZE = 448
147
- OUTPUT_DIR = "saved_outputs" # Changed to a visible repo directory
148
-
149
- # Create output directory if it doesn't exist
150
- os.makedirs(OUTPUT_DIR, exist_ok=True)
151
-
152
- # Set up logging to write to saved_outputs directory
153
- log_file = os.path.join(OUTPUT_DIR, f"debug_log_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.log")
154
-
155
- # Create a GUI log handler
156
- gui_log_handler = GUILogHandler(max_entries=500)
157
- gui_log_handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s'))
158
- gui_log_handler.setLevel(logging.DEBUG)
159
-
160
- # Configure logging
161
- logging.basicConfig(
162
- level=logging.DEBUG,
163
- format='%(asctime)s [%(levelname)s] %(message)s',
164
- handlers=[
165
- logging.FileHandler(log_file),
166
- logging.StreamHandler(sys.stdout),
167
- gui_log_handler
168
- ]
169
- )
170
-
171
- # Create a logger
172
- logger = logging.getLogger("internvl_analyzer")
173
- logger.setLevel(logging.DEBUG)
174
 
175
- # Log startup information
176
- logger.info("="*50)
177
- logger.info("InternVL2.5 Image Analyzer starting up")
178
- logger.info(f"Log file: {log_file}")
179
- logger.info(f"Python version: {sys.version}")
180
- logger.info(f"Torch version: {torch.__version__}")
181
- logger.info(f"CUDA available: {torch.cuda.is_available()}")
182
- if torch.cuda.is_available():
183
- logger.info(f"CUDA version: {torch.version.cuda}")
184
- logger.info(f"GPU: {torch.cuda.get_device_name(0)}")
185
- logger.info("="*50)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
 
187
  # Function to log tensor info for debugging
188
  def log_tensor_info(tensor, name="tensor"):
@@ -585,83 +572,109 @@ def analyze_dual_images(model, tokenizer, image1, image2, prompt):
585
  return error_msg
586
 
587
  # Function to process PDF files
588
- def process_pdf(pdf_path=None, pdf_bytes=None):
589
- """Convert PDF file to a list of PIL images."""
590
  try:
591
- logger.info(f"Processing PDF: {pdf_path}")
592
- logger.debug(f"Current working directory: {os.getcwd()}")
593
 
594
- if pdf_path:
595
- # Convert PDF file pages to PIL images
596
- logger.info(f"Converting PDF from path: {pdf_path}")
597
- logger.debug(f"PDF path exists: {os.path.exists(pdf_path)}")
598
- logger.debug(f"PDF path is file: {os.path.isfile(pdf_path)}")
599
- logger.debug(f"PDF file size: {os.path.getsize(pdf_path) if os.path.exists(pdf_path) else 'N/A'} bytes")
 
 
600
 
 
601
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
602
  images = convert_from_path(pdf_path)
603
- logger.info(f"PDF successfully converted to {len(images)} images")
604
- except Exception as pdf_err:
605
- logger.error(f"Error in convert_from_path: {str(pdf_err)}")
 
606
  logger.error(traceback.format_exc())
607
- # Try with different parameters
608
- logger.info("Attempting alternative PDF conversion")
 
609
  try:
610
- images = convert_from_path(
611
- pdf_path,
612
- dpi=150, # Lower DPI for better compatibility
613
- use_pdftocairo=False, # Try different backend
614
- strict=False # Be more lenient with errors
615
- )
616
- logger.info(f"Alternative conversion successful: {len(images)} images")
617
- except Exception as alt_err:
618
- logger.error(f"Alternative conversion also failed: {str(alt_err)}")
 
619
  logger.error(traceback.format_exc())
 
620
  raise
621
- elif pdf_bytes:
622
- # Convert PDF bytes to PIL images
623
- logger.info("Converting PDF from bytes")
624
- logger.debug(f"PDF bytes size: {len(pdf_bytes)} bytes")
 
 
 
625
 
626
  try:
627
- images = convert_from_bytes(pdf_bytes)
628
- logger.info(f"PDF bytes successfully converted to {len(images)} images")
629
- except Exception as bytes_err:
630
- logger.error(f"Error in convert_from_bytes: {str(bytes_err)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
631
  logger.error(traceback.format_exc())
632
- # Try with different parameters
633
- logger.info("Attempting alternative PDF bytes conversion")
634
- try:
635
- images = convert_from_bytes(
636
- pdf_bytes,
637
- dpi=150, # Lower DPI
638
- use_pdftocairo=False,
639
- strict=False
640
- )
641
- logger.info(f"Alternative bytes conversion successful: {len(images)} images")
642
- except Exception as alt_bytes_err:
643
- logger.error(f"Alternative bytes conversion also failed: {str(alt_bytes_err)}")
644
- logger.error(traceback.format_exc())
645
- raise
646
  else:
647
- logger.error("No PDF source provided")
648
- return None
649
-
650
- # Validate and log the output
651
- if not images:
652
- logger.error("PDF conversion returned empty list")
653
  return None
654
 
655
- # Log details about the first few converted images
656
- for i, img in enumerate(images[:2]): # Log first 2 pages
657
- logger.debug(f"PDF Page {i+1}: size={img.size}, mode={img.mode}")
658
-
659
- logger.info(f"PDF successfully processed, returning {len(images)} images")
660
- return images
661
  except Exception as e:
662
- logger.error(f"Fatal error in process_pdf: {str(e)}")
663
  logger.error(traceback.format_exc())
664
- return None
 
 
 
 
 
 
 
 
 
665
 
666
  # Function to analyze images with a prompt
667
  def analyze_with_prompt(image_input, prompt):
@@ -1551,6 +1564,24 @@ def get_latest_log_content():
1551
 
1552
  # Main function
1553
  def main():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1554
  # Load the model
1555
  model, tokenizer = load_model()
1556
 
@@ -1580,6 +1611,14 @@ def main():
1580
  def get_debug_logs(num_lines=50):
1581
  return gui_log_handler.get_logs(last_n=num_lines)
1582
 
 
 
 
 
 
 
 
 
1583
  # Function to update logs in real-time
1584
  def update_logs(history):
1585
  latest = gui_log_handler.get_latest()
@@ -1601,6 +1640,90 @@ def main():
1601
  gr.Markdown("# InternVL2.5 Image Analyzer")
1602
  gr.Markdown("Analyze images using the InternVL2.5 model. You can upload individual images or analyze all images in a folder.")
1603
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1604
  # Debug mode toggle and panel
1605
  with gr.Accordion("Debug Console", open=False) as debug_accordion:
1606
  with gr.Row():
 
37
  'tensor_issues': 0
38
  }
39
 
40
+ # Constants
41
+ IMAGENET_MEAN = (0.485, 0.456, 0.406)
42
+ IMAGENET_STD = (0.229, 0.224, 0.225)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
+ # Configuration
45
+ MODEL_NAME = "OpenGVLab/InternVL2_5-8B" # Smaller model for faster loading
46
+ IMAGE_SIZE = 448
47
+ OUTPUT_DIR = "saved_outputs" # Changed to a visible repo directory
48
+ LOGS_DIR = os.path.join(OUTPUT_DIR, "logs")
49
+
50
+ # Create output and logs directories if they don't exist
51
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
52
+ os.makedirs(LOGS_DIR, exist_ok=True)
53
+
54
+ # Set up logging to write to saved_outputs/logs directory
55
+ timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
56
+ log_file = os.path.join(LOGS_DIR, f"debug_log_{timestamp}.log")
57
+ latest_log = os.path.join(LOGS_DIR, "latest_debug.log")
58
+
59
+ # Configure basic logging first
60
+ logging.basicConfig(
61
+ level=logging.DEBUG,
62
+ format='%(asctime)s [%(levelname)s] %(message)s',
63
+ handlers=[
64
+ logging.FileHandler(log_file),
65
+ logging.FileHandler(latest_log, mode='w'), # Overwrite the latest log file
66
+ logging.StreamHandler(sys.stdout)
67
+ ]
68
+ )
69
+
70
+ # Get the root logger
71
+ logger = logging.getLogger()
72
+ logger.setLevel(logging.DEBUG)
73
 
74
  # Custom logging handler that captures logs for GUI display
75
  class GUILogHandler(logging.Handler):
 
124
  with self.lock:
125
  self.log_entries = []
126
 
127
+ # Function to get stats for UI display
128
+ def get_debug_stats():
129
+ uptime = datetime.datetime.now() - gui_stats['start_time']
130
+ hours, remainder = divmod(uptime.seconds, 3600)
131
+ minutes, seconds = divmod(remainder, 60)
132
+ uptime_str = f"{hours}h {minutes}m {seconds}s"
133
+
134
+ return {
135
+ 'errors': gui_stats['errors'],
136
+ 'warnings': gui_stats['warnings'],
137
+ 'last_error': gui_stats['last_error'],
138
+ 'last_error_time': gui_stats['last_error_time'],
139
+ 'last_warning': gui_stats['last_warning'],
140
+ 'last_warning_time': gui_stats['last_warning_time'],
141
+ 'operations': gui_stats['operations_completed'],
142
+ 'uptime': uptime_str,
143
+ 'tensor_issues': gui_stats['tensor_issues']
144
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
146
+ # Function to format debug stats as HTML
147
+ def format_debug_stats_html():
148
+ stats = get_debug_stats()
149
+
150
+ error_color = "#ff5555" if stats['errors'] > 0 else "#555555"
151
+ warning_color = "#ffaa00" if stats['warnings'] > 0 else "#555555"
152
+
153
+ html = f"""
154
+ <div style="margin: 10px 0; padding: 10px; border: 1px solid #ddd; border-radius: 4px; background-color: #f9f9f9;">
155
+ <div style="display: flex; justify-content: space-between;">
156
+ <div style="flex: 1;">
157
+ <p><strong>Errors:</strong> <span style="color: {error_color};">{stats['errors']}</span></p>
158
+ <p><strong>Warnings:</strong> <span style="color: {warning_color};">{stats['warnings']}</span></p>
159
+ <p><strong>Operations:</strong> {stats['operations']}</p>
160
+ </div>
161
+ <div style="flex: 1;">
162
+ <p><strong>Uptime:</strong> {stats['uptime']}</p>
163
+ <p><strong>Tensor Issues:</strong> {stats['tensor_issues']}</p>
164
+ </div>
165
+ </div>
166
+ <div style="margin-top: 10px; border-top: 1px solid #ddd; padding-top: 10px;">
167
+ <p><strong>Last Error:</strong> {stats['last_error_time']} - {stats['last_error']}</p>
168
+ <p><strong>Last Warning:</strong> {stats['last_warning_time']} - {stats['last_warning']}</p>
169
+ </div>
170
+ </div>
171
+ """
172
+ return html
173
 
174
  # Function to log tensor info for debugging
175
  def log_tensor_info(tensor, name="tensor"):
 
572
  return error_msg
573
 
574
  # Function to process PDF files
575
+ def process_pdf(pdf_path=None, pdf_file=None):
576
+ """Process a PDF file and return a list of PIL images."""
577
  try:
578
+ logger.info(f"Processing PDF: path={pdf_path}, file_upload={pdf_file is not None}")
 
579
 
580
+ if pdf_path is not None and os.path.exists(pdf_path):
581
+ # Log file details
582
+ file_size = os.path.getsize(pdf_path) / 1024 # KB
583
+ logger.info(f"PDF file details: path={pdf_path}, size={file_size:.2f} KB")
584
+
585
+ # Direct debug output to console to ensure visibility
586
+ print(f"[DEBUG] Processing PDF from path: {pdf_path}")
587
+ print(f"[DEBUG] File exists: {os.path.exists(pdf_path)}, Size: {file_size:.2f} KB")
588
 
589
+ # First try to use convert_from_path with detailed logging
590
  try:
591
+ logger.debug(f"Converting PDF to images using convert_from_path: {pdf_path}")
592
+ with open(pdf_path, 'rb') as f:
593
+ file_content = f.read()
594
+ logger.debug(f"PDF file read: {len(file_content)} bytes")
595
+
596
+ # Log file header for validation
597
+ if len(file_content) >= 8:
598
+ header_hex = ' '.join([f'{b:02x}' for b in file_content[:8]])
599
+ logger.info(f"PDF header hex: {header_hex}")
600
+ print(f"[DEBUG] PDF header hex: {header_hex}")
601
+
602
+ # Check for valid PDF header
603
+ if not file_content.startswith(b'%PDF'):
604
+ logger.warning(f"File does not have PDF header: {pdf_path}")
605
+ print(f"[WARNING] File does not have PDF header: {pdf_path}")
606
+
607
  images = convert_from_path(pdf_path)
608
+ logger.info(f"PDF converted successfully using convert_from_path: {len(images)} pages")
609
+ return images
610
+ except Exception as path_err:
611
+ logger.error(f"Error converting PDF using path method: {str(path_err)}")
612
  logger.error(traceback.format_exc())
613
+ print(f"[ERROR] Convert from path failed: {str(path_err)}")
614
+
615
+ # Try fallback method - convert from bytes
616
  try:
617
+ logger.debug("Falling back to convert_from_bytes method")
618
+ with open(pdf_path, 'rb') as pdf_file:
619
+ pdf_data = pdf_file.read()
620
+ logger.debug(f"Read {len(pdf_data)} bytes from PDF file")
621
+
622
+ images = convert_from_bytes(pdf_data)
623
+ logger.info(f"PDF converted successfully using convert_from_bytes: {len(images)} pages")
624
+ return images
625
+ except Exception as bytes_err:
626
+ logger.error(f"Error converting PDF using bytes method: {str(bytes_err)}")
627
  logger.error(traceback.format_exc())
628
+ print(f"[ERROR] Convert from bytes also failed: {str(bytes_err)}")
629
  raise
630
+
631
+ elif pdf_file is not None:
632
+ logger.info("Processing uploaded PDF file")
633
+ print(f"[DEBUG] Processing uploaded PDF file")
634
+
635
+ if hasattr(pdf_file, 'name'):
636
+ logger.debug(f"Uploaded PDF filename: {pdf_file.name}")
637
 
638
  try:
639
+ # Creating a temporary file from the uploaded file
640
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as temp_file:
641
+ temp_file.write(pdf_file.read())
642
+ temp_path = temp_file.name
643
+
644
+ logger.debug(f"Created temporary file: {temp_path}")
645
+ print(f"[DEBUG] Created temp file: {temp_path}")
646
+
647
+ # Now convert from the temp file
648
+ images = convert_from_path(temp_path)
649
+ logger.info(f"PDF converted successfully: {len(images)} pages")
650
+
651
+ # Clean up
652
+ os.unlink(temp_path)
653
+ return images
654
+ except Exception as upload_err:
655
+ logger.error(f"Error processing uploaded PDF: {str(upload_err)}")
656
  logger.error(traceback.format_exc())
657
+ print(f"[ERROR] Processing uploaded PDF failed: {str(upload_err)}")
658
+ raise
 
 
 
 
 
 
 
 
 
 
 
 
659
  else:
660
+ error_msg = "No PDF file provided (both pdf_path and pdf_file are None or invalid)"
661
+ logger.error(error_msg)
662
+ print(f"[ERROR] {error_msg}")
 
 
 
663
  return None
664
 
 
 
 
 
 
 
665
  except Exception as e:
666
+ logger.error(f"Critical error in PDF processing: {str(e)}")
667
  logger.error(traceback.format_exc())
668
+ print(f"[CRITICAL] PDF processing failed: {str(e)}")
669
+ print(traceback.format_exc())
670
+
671
+ # Update error statistics
672
+ gui_stats['errors'] += 1
673
+ gui_stats['last_error'] = f"PDF processing error: {str(e)}"
674
+ gui_stats['last_error_time'] = datetime.datetime.now().strftime("%H:%M:%S")
675
+
676
+ # Reraise for proper handling
677
+ raise
678
 
679
  # Function to analyze images with a prompt
680
  def analyze_with_prompt(image_input, prompt):
 
1564
 
1565
  # Main function
1566
  def main():
1567
+ # Create log handler for UI
1568
+ gui_log_handler = GUILogHandler(max_entries=500)
1569
+ gui_log_handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s'))
1570
+ gui_log_handler.setLevel(logging.DEBUG)
1571
+ logger.addHandler(gui_log_handler)
1572
+
1573
+ # Log startup information
1574
+ logger.info("="*50)
1575
+ logger.info("InternVL2.5 Image Analyzer starting up")
1576
+ logger.info(f"Log file: {log_file}")
1577
+ logger.info(f"Python version: {sys.version}")
1578
+ logger.info(f"Torch version: {torch.__version__}")
1579
+ logger.info(f"CUDA available: {torch.cuda.is_available()}")
1580
+ if torch.cuda.is_available():
1581
+ logger.info(f"CUDA version: {torch.version.cuda}")
1582
+ logger.info(f"GPU: {torch.cuda.get_device_name(0)}")
1583
+ logger.info("="*50)
1584
+
1585
  # Load the model
1586
  model, tokenizer = load_model()
1587
 
 
1611
  def get_debug_logs(num_lines=50):
1612
  return gui_log_handler.get_logs(last_n=num_lines)
1613
 
1614
+ # Function to read the full log file
1615
+ def read_log_file():
1616
+ try:
1617
+ with open(latest_log, 'r') as f:
1618
+ return f.read()
1619
+ except Exception as e:
1620
+ return f"Error reading log file: {str(e)}"
1621
+
1622
  # Function to update logs in real-time
1623
  def update_logs(history):
1624
  latest = gui_log_handler.get_latest()
 
1640
  gr.Markdown("# InternVL2.5 Image Analyzer")
1641
  gr.Markdown("Analyze images using the InternVL2.5 model. You can upload individual images or analyze all images in a folder.")
1642
 
1643
+ # Direct logs view tab
1644
+ with gr.Tab("Logs View"):
1645
+ gr.Markdown("## View Application Logs")
1646
+ gr.Markdown("This tab displays the complete application logs for debugging purposes.")
1647
+
1648
+ with gr.Row():
1649
+ with gr.Column(scale=4):
1650
+ full_logs = gr.Textbox(
1651
+ label="Full Log File",
1652
+ value=read_log_file(),
1653
+ lines=25,
1654
+ max_lines=30,
1655
+ autoscroll=False
1656
+ )
1657
+ with gr.Column(scale=1):
1658
+ refresh_logs_btn = gr.Button("Refresh Logs")
1659
+ log_info = gr.Markdown(f"Log file location: {latest_log}")
1660
+
1661
+ # Stats display
1662
+ stats_html = gr.HTML(format_debug_stats_html())
1663
+
1664
+ # Function to handle automatic log refresh
1665
+ def refresh_logs():
1666
+ return read_log_file(), format_debug_stats_html()
1667
+
1668
+ refresh_logs_btn.click(
1669
+ fn=refresh_logs,
1670
+ inputs=[],
1671
+ outputs=[full_logs, stats_html]
1672
+ )
1673
+
1674
+ # Auto-refresh logs every 10 seconds
1675
+ gr.on(
1676
+ triggers=[],
1677
+ fn=refresh_logs,
1678
+ inputs=[],
1679
+ outputs=[full_logs, stats_html],
1680
+ every=10
1681
+ )
1682
+
1683
+ # Options for log files
1684
+ log_files = [f for f in os.listdir(LOGS_DIR) if f.endswith('.log')]
1685
+ log_dropdown = gr.Dropdown(
1686
+ label="Available Log Files",
1687
+ choices=log_files,
1688
+ value="latest_debug.log" if "latest_debug.log" in log_files else None
1689
+ )
1690
+
1691
+ # Function to load a specific log file
1692
+ def load_log_file(filename):
1693
+ if not filename:
1694
+ return "No log file selected"
1695
+
1696
+ try:
1697
+ with open(os.path.join(LOGS_DIR, filename), 'r') as f:
1698
+ return f.read()
1699
+ except Exception as e:
1700
+ return f"Error reading {filename}: {str(e)}"
1701
+
1702
+ log_dropdown.change(
1703
+ fn=load_log_file,
1704
+ inputs=[log_dropdown],
1705
+ outputs=[full_logs]
1706
+ )
1707
+
1708
+ # Download log file button
1709
+ def get_current_log_file(filename):
1710
+ if not filename:
1711
+ return None
1712
+
1713
+ log_path = os.path.join(LOGS_DIR, filename)
1714
+ if os.path.exists(log_path):
1715
+ return log_path
1716
+ return None
1717
+
1718
+ download_log_btn = gr.Button("Download Selected Log")
1719
+ log_file_download = gr.File(label="Log File for Download")
1720
+
1721
+ download_log_btn.click(
1722
+ fn=get_current_log_file,
1723
+ inputs=[log_dropdown],
1724
+ outputs=[log_file_download]
1725
+ )
1726
+
1727
  # Debug mode toggle and panel
1728
  with gr.Accordion("Debug Console", open=False) as debug_accordion:
1729
  with gr.Row():