Improved report management UI with custom HTML checkboxes, dark theme styling, and robust error handling

This commit is contained in:
Steve White 2025-03-19 13:10:01 -05:00
parent 1a2cdc4c60
commit 577eed1f5e
1 changed files with 857 additions and 66 deletions

View File

@ -35,8 +35,21 @@ class GradioInterface:
self.sub_question_executor = get_sub_question_executor()
self.results_dir = Path(__file__).parent.parent / "results"
self.results_dir.mkdir(exist_ok=True)
self.reports_dir = Path(__file__).parent.parent
# Create a dedicated reports directory with subdirectories
self.reports_dir = Path(__file__).parent.parent / "reports"
self.reports_dir.mkdir(exist_ok=True)
# Create daily subdirectory for organization
self.reports_daily_dir = self.reports_dir / datetime.now().strftime("%Y-%m-%d")
self.reports_daily_dir.mkdir(exist_ok=True)
# Create a metadata file to track reports
self.reports_metadata_file = self.reports_dir / "reports_metadata.json"
if not self.reports_metadata_file.exists():
with open(self.reports_metadata_file, "w") as f:
json.dump({"reports": []}, f, indent=2)
self.detail_level_manager = get_report_detail_level_manager()
self.config = Config()
@ -206,7 +219,7 @@ class GradioInterface:
Path to the generated report
"""
try:
# Create a timestamped output file
# Create a timestamped output file in the daily directory
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
model_suffix = ""
@ -225,7 +238,12 @@ class GradioInterface:
print(f"Extracted model name: {custom_model}")
print(f"Using model suffix: {model_suffix}")
output_file = self.reports_dir / f"report_{timestamp}{model_suffix}.md"
# Create a unique report ID
import hashlib
report_id = f"{timestamp}_{hashlib.md5(query.encode()).hexdigest()[:8]}"
# Define the output file path in the daily directory
output_file = self.reports_daily_dir / f"report_{report_id}{model_suffix}.md"
# Get detail level configuration
config = self.detail_level_manager.get_detail_level_config(detail_level)
@ -256,10 +274,6 @@ class GradioInterface:
default_model = detail_config.get("model", "unknown")
print(f"Default model for {detail_level} detail level: {default_model}")
# First set the detail level, which will set the default model for this detail level
self.report_generator.set_detail_level(detail_level)
print(f"After setting detail level, report generator model is: {self.report_generator.model_name}")
# Then explicitly override with custom model if provided
if custom_model:
# Extract the actual model name from the display name format
@ -523,6 +537,19 @@ class GradioInterface:
print(f"Report saved to: {output_file}")
# Update report metadata
self._update_report_metadata(report_id, {
"id": report_id,
"timestamp": timestamp,
"query": query,
"detail_level": detail_level,
"query_type": query_type,
"model": custom_model if custom_model else config.get("model", "default"),
"file_path": str(output_file),
"file_size": output_file.stat().st_size,
"creation_date": datetime.now().isoformat()
})
return report, str(output_file)
except Exception as e:
@ -545,6 +572,107 @@ class GradioInterface:
# Remove content between <thinking> and </thinking> tags
import re
return re.sub(r'<thinking>.*?</thinking>', '', text, flags=re.DOTALL)
def _update_report_metadata(self, report_id, metadata):
"""
Update the report metadata file with new report information.
Args:
report_id (str): Unique identifier for the report
metadata (dict): Report metadata to store
"""
try:
# Load existing metadata
with open(self.reports_metadata_file, 'r') as f:
all_metadata = json.load(f)
# Check if report already exists
existing_report = None
for i, report in enumerate(all_metadata.get('reports', [])):
if report.get('id') == report_id:
existing_report = i
break
# Update or add the report metadata
if existing_report is not None:
all_metadata['reports'][existing_report] = metadata
else:
all_metadata['reports'].append(metadata)
# Save updated metadata
with open(self.reports_metadata_file, 'w') as f:
json.dump(all_metadata, f, indent=2)
print(f"Updated metadata for report {report_id}")
except Exception as e:
print(f"Error updating report metadata: {str(e)}")
def get_all_reports(self):
"""
Get all report metadata.
Returns:
list: List of report metadata dictionaries
"""
try:
# Load metadata
with open(self.reports_metadata_file, 'r') as f:
all_metadata = json.load(f)
# Return reports sorted by creation date (newest first)
reports = all_metadata.get('reports', [])
return sorted(reports, key=lambda x: x.get('creation_date', ''), reverse=True)
except Exception as e:
print(f"Error getting report metadata: {str(e)}")
return []
def delete_report(self, report_id):
"""
Delete a report and its metadata.
Args:
report_id (str): ID of the report to delete
Returns:
bool: True if successful, False otherwise
"""
try:
# Load metadata
with open(self.reports_metadata_file, 'r') as f:
all_metadata = json.load(f)
# Find the report
report_to_delete = None
for report in all_metadata.get('reports', []):
if report.get('id') == report_id:
report_to_delete = report
break
if not report_to_delete:
print(f"Report {report_id} not found")
return False
# Delete the report file
file_path = report_to_delete.get('file_path')
if file_path and Path(file_path).exists():
Path(file_path).unlink()
print(f"Deleted report file: {file_path}")
# Remove from metadata
all_metadata['reports'] = [r for r in all_metadata.get('reports', []) if r.get('id') != report_id]
# Save updated metadata
with open(self.reports_metadata_file, 'w') as f:
json.dump(all_metadata, f, indent=2)
print(f"Deleted report {report_id} from metadata")
return True
except Exception as e:
print(f"Error deleting report: {str(e)}")
return False
def get_available_models(self):
"""
@ -600,6 +728,331 @@ class GradioInterface:
self.model_name_to_description = model_name_to_description
return descriptions
def _get_reports_for_display(self):
"""Get reports formatted for display in the UI"""
reports = self.get_all_reports()
display_data = []
for report in reports:
# Format timestamp for display
timestamp = report.get('timestamp', '')
creation_date = report.get('creation_date', '')
if creation_date:
try:
# Convert ISO format to datetime and format for display
dt = datetime.fromisoformat(creation_date)
formatted_date = dt.strftime('%Y-%m-%d %H:%M:%S')
except:
formatted_date = creation_date
else:
formatted_date = timestamp
# Format file size
file_size = report.get('file_size', 0)
if file_size < 1024:
formatted_size = f"{file_size} B"
elif file_size < 1024 * 1024:
formatted_size = f"{file_size / 1024:.1f} KB"
else:
formatted_size = f"{file_size / (1024 * 1024):.1f} MB"
# Add row to display data
display_data.append([
report.get('id', ''),
report.get('query', '')[:50] + ('...' if len(report.get('query', '')) > 50 else ''),
report.get('model', '').split('/')[-1], # Show only the model name without provider
report.get('detail_level', ''),
formatted_date,
formatted_size,
Path(report.get('file_path', '')).name, # Just the filename
])
return display_data
def _delete_selected_reports(self, selected_choices):
"""Delete selected reports
Args:
selected_choices (list): List of selected checkbox values in format "ID: Query (Model)"
Returns:
tuple: Updated reports table data and updated checkbox choices
"""
if not selected_choices:
# If no reports are selected, just refresh the display
reports_data = self._get_reports_for_display()
choices = self._get_report_choices(reports_data)
return reports_data, choices, []
print(f"Selected choices for deletion: {selected_choices}")
# Extract report IDs from selected choices
selected_report_ids = []
for choice in selected_choices:
try:
# Split at the first colon to get the ID
if ':' in choice:
report_id = choice.split(':', 1)[0].strip()
selected_report_ids.append(report_id)
else:
print(f"Warning: Invalid choice format: {choice}")
except Exception as e:
print(f"Error processing choice {choice}: {e}")
print(f"Deleting report IDs: {selected_report_ids}")
# Delete selected reports
deleted_count = 0
for report_id in selected_report_ids:
if self.delete_report(report_id):
deleted_count += 1
print(f"Successfully deleted report: {report_id}")
else:
print(f"Failed to delete report: {report_id}")
print(f"Deleted {deleted_count} reports")
# Refresh the table and choices
reports_data = self._get_reports_for_display()
choices = self._get_report_choices(reports_data)
return reports_data, choices, []
def _download_selected_reports(self, selected_choices):
"""Prepare selected reports for download
Args:
selected_choices (list): List of selected checkbox values in format "ID: Query (Model)"
Returns:
list: List of file paths to download
"""
if not selected_choices:
return []
print(f"Selected choices for download: {selected_choices}")
# Extract report IDs from selected choices
selected_report_ids = []
for choice in selected_choices:
try:
# Split at the first colon to get the ID
if ':' in choice:
report_id = choice.split(':', 1)[0].strip()
selected_report_ids.append(report_id)
else:
print(f"Warning: Invalid choice format: {choice}")
except Exception as e:
print(f"Error processing choice {choice}: {e}")
print(f"Extracted report IDs: {selected_report_ids}")
# Get file paths for selected reports
all_reports = self.get_all_reports()
files_to_download = []
for report_id in selected_report_ids:
report = next((r for r in all_reports if r.get('id') == report_id), None)
if report and "file_path" in report:
file_path = report["file_path"]
# Verify the file exists
if os.path.exists(file_path):
files_to_download.append(file_path)
print(f"Added file for download: {file_path}")
else:
print(f"Warning: File does not exist: {file_path}")
else:
print(f"Warning: Could not find report with ID {report_id}")
return files_to_download
def _get_report_choices(self, reports_data):
"""Generate choices for the checkbox group based on reports data
Args:
reports_data (list): List of report data rows
Returns:
list: List of choices for the checkbox group in format "ID: Query (Model)"
"""
choices = []
# If reports_data is empty, return an empty list
if not reports_data:
return []
# Get all reports from the metadata file to ensure IDs are available
all_reports = self.get_all_reports()
# Create a mapping of report IDs to their full data
report_map = {report.get('id', ''): report for report in all_reports}
for row in reports_data:
try:
report_id = row[0]
if not report_id:
continue
# Get data from the table row
query = row[1]
model = row[2]
# Format: "ID: Query (Model)"
choice_text = f"{report_id}: {query} ({model})"
choices.append(choice_text)
except (IndexError, TypeError) as e:
print(f"Error processing report row: {e}")
continue
return choices
def _cleanup_old_reports(self, days):
"""Delete reports older than the specified number of days
Args:
days (int): Number of days to keep reports for
Returns:
list: Updated reports table data
"""
try:
if days <= 0:
print("Cleanup skipped - days parameter is 0 or negative")
return self._get_reports_for_display()
# Calculate cutoff date
from datetime import timedelta
cutoff_date = datetime.now() - timedelta(days=days)
cutoff_str = cutoff_date.isoformat()
print(f"Cleaning up reports older than {cutoff_date.strftime('%Y-%m-%d %H:%M:%S')}")
# Get all reports
all_reports = self.get_all_reports()
print(f"Found {len(all_reports)} total reports")
reports_to_delete = []
# Find reports older than cutoff date
for report in all_reports:
creation_date = report.get('creation_date', '')
if not creation_date:
print(f"Warning: Report {report.get('id')} has no creation date")
continue
if creation_date < cutoff_str:
reports_to_delete.append(report.get('id'))
print(f"Marking report {report.get('id')} from {creation_date} for deletion")
print(f"Found {len(reports_to_delete)} reports to delete")
# Delete old reports
deleted_count = 0
for report_id in reports_to_delete:
if self.delete_report(report_id):
deleted_count += 1
print(f"Successfully deleted {deleted_count} reports")
# Refresh the table
updated_display = self._get_reports_for_display()
print(f"Returning updated display with {len(updated_display)} reports")
return updated_display
except Exception as e:
print(f"Error in cleanup_old_reports: {e}")
import traceback
traceback.print_exc()
# Return current display data in case of error
return self._get_reports_for_display()
def migrate_existing_reports(self):
"""Migrate existing reports from the root directory to the reports directory structure
Returns:
str: Status message indicating the result of the migration
"""
import re
import shutil
import os
# Pattern to match report files like report_20250317_122351_llama-3.3-70b-versatile.md
report_pattern = re.compile(r'report_(?P<date>\d{8})_(?P<time>\d{6})_?(?P<model>.*?)?\.md$')
# Get the root directory
root_dir = Path(__file__).parent.parent
# Find all report files in the root directory
migrated_count = 0
for file_path in root_dir.glob('report_*.md'):
if not file_path.is_file():
continue
# Extract information from the filename
match = report_pattern.match(file_path.name)
if not match:
continue
date_str = match.group('date')
time_str = match.group('time')
model = match.group('model') or 'unknown'
# Format date for directory structure (YYYY-MM-DD)
try:
year = date_str[:4]
month = date_str[4:6]
day = date_str[6:8]
formatted_date = f"{year}-{month}-{day}"
# Create timestamp for metadata
timestamp = f"{year}-{month}-{day} {time_str[:2]}:{time_str[2:4]}:{time_str[4:6]}"
creation_date = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S").isoformat()
except ValueError:
# If date parsing fails, use current date
formatted_date = datetime.now().strftime("%Y-%m-%d")
creation_date = datetime.now().isoformat()
# Create directory for the date if it doesn't exist
date_dir = self.reports_dir / formatted_date
date_dir.mkdir(exist_ok=True)
# Generate a unique report ID
report_id = f"{date_str}_{time_str}"
# Copy the file to the new location
new_file_path = date_dir / file_path.name
shutil.copy2(file_path, new_file_path)
# Read the report content to extract query if possible
query = ""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read(1000) # Read just the beginning to find the query
# Try to extract query from title or first few lines
title_match = re.search(r'#\s*(.+?)\n', content)
if title_match:
query = title_match.group(1).strip()
else:
# Just use the first line as query
query = content.split('\n')[0].strip()
except Exception as e:
print(f"Error reading file {file_path}: {e}")
# Create metadata for the report
file_size = os.path.getsize(file_path)
metadata = {
"id": report_id,
"query": query,
"model": model,
"detail_level": "unknown", # We don't know the detail level from the filename
"timestamp": timestamp,
"creation_date": creation_date,
"file_path": str(new_file_path),
"file_size": file_size
}
# Update the metadata file
self._update_report_metadata(report_id, metadata)
migrated_count += 1
return f"Migrated {migrated_count} existing reports to the new directory structure."
def create_interface(self):
"""
@ -624,53 +1077,9 @@ class GradioInterface:
"""
)
# Create tabs for different sections
with gr.Tabs() as tabs:
with gr.TabItem("Search"):
with gr.Row():
with gr.Column(scale=4):
search_query_input = gr.Textbox(
label="Research Query",
placeholder="Enter your research question here...",
lines=3
)
with gr.Column(scale=1):
search_num_results = gr.Slider(
minimum=5,
maximum=50,
value=20,
step=5,
label="Results Per Engine"
)
search_use_reranker = gr.Checkbox(
label="Use Semantic Reranker",
value=True,
info="Uses Jina AI's reranker for more relevant results"
)
search_button = gr.Button("Search", variant="primary")
gr.Examples(
examples=[
["What are the latest advancements in quantum computing?"],
["Compare transformer and RNN architectures for NLP tasks"],
["Explain the environmental impact of electric vehicles"],
["What recent actions has Trump taken regarding tariffs?"],
["What are the recent papers on large language model alignment?"],
["What are the main research findings on climate change adaptation strategies in agriculture?"]
],
inputs=search_query_input
)
with gr.Row():
with gr.Column():
search_results_output = gr.Markdown(label="Results")
with gr.Row():
with gr.Column():
search_file_output = gr.Textbox(
label="Results saved to file",
interactive=False
)
# Report Generation Tab
with gr.TabItem("Generate Report"):
with gr.Row():
with gr.Column(scale=4):
@ -779,19 +1188,163 @@ class GradioInterface:
gr.Markdown(f"### Detail Levels\n{detail_levels_info}")
gr.Markdown(f"### Query Types\n{query_types_info}")
# Report Management Tab
with gr.TabItem("Manage Reports"):
with gr.Row():
gr.Markdown("## Report Management")
with gr.Row():
gr.Markdown("Select reports to download or delete. You can also filter and sort the reports.")
# Create a state to store the current reports
reports_state = gr.State([])
# Only include one view of the reports with a clean selection interface
with gr.Row():
with gr.Column():
gr.Markdown("### Reports")
# Get the reports data
reports_data = self._get_reports_for_display()
# This hidden table is just used to store the data
reports_table = gr.Dataframe(
headers=["ID", "Query", "Model", "Detail Level", "Created", "Size", "Filename"],
datatype=["str", "str", "str", "str", "str", "str", "str"],
value=reports_data,
visible=False, # Hide this table
interactive=False
)
# Get the choices for the checkbox group
initial_choices = self._get_report_choices(reports_data)
print(f"Initial choices generated: {len(initial_choices)}")
if not initial_choices:
initial_choices = ["No reports available"]
# Use a cleaner component approach with better styling
gr.Markdown("##### Select reports below for download or deletion")
# Create a completely custom HTML solution for maximum control
# Prepare the HTML for the checkboxes
html_choices = []
for i, choice in enumerate(initial_choices):
html_choices.append(f'<div style="padding: 5px; margin-bottom: 8px;">')
html_choices.append(f'<label style="display: block; width: 100%; cursor: pointer; color: #eee;">')
html_choices.append(f'<input type="checkbox" id="report-{i}" name="report" value="{choice}"> {choice}')
html_choices.append('</label>')
html_choices.append('</div>')
# Create the HTML string with all the checkbox markup and JavaScript functionality
html_content = f"""
<div style="border: 1px solid #555; border-radius: 5px; margin-bottom: 15px; background-color: #2d2d2d; color: #eee;">
<div style="padding: 10px; border-bottom: 1px solid #555; background-color: #3a3a3a;">
<label style="display: block; font-weight: bold; cursor: pointer;">
<input type="checkbox" id="select-all-checkbox" onclick="toggleAllReports()"> Check/Uncheck All
</label>
</div>
<div id="reports-container" style="max-height: 500px; overflow-y: auto; padding: 10px;">
{''.join(html_choices)}
</div>
</div>
<script>
// Toggle all checkboxes
function toggleAllReports() {{
const checkAll = document.getElementById('select-all-checkbox');
const checkboxes = document.getElementsByName('report');
for (let i = 0; i < checkboxes.length; i++) {{
checkboxes[i].checked = checkAll.checked;
}}
updateHiddenField();
}}
// Get selected values and update the hidden field
function updateHiddenField() {{
const checkboxes = document.getElementsByName('report');
const selected = [];
for (let i = 0; i < checkboxes.length; i++) {{
if (checkboxes[i].checked) {{
selected.push(checkboxes[i].value);
}}
}}
// Find the hidden field and set its value
// This needs to match the ID we give to the gr.CheckboxGroup below
const hiddenField = document.querySelector('#reports-hidden-value textarea');
if (hiddenField) {{
// Make sure we always have valid JSON, even if empty
hiddenField.value = JSON.stringify(selected);
console.log('Updated hidden field with: ' + hiddenField.value);
// Trigger a change event to notify Gradio
const event = new Event('input', {{ bubbles: true }});
hiddenField.dispatchEvent(event);
}}
}}
// Add event listeners to all checkbox changes
document.addEventListener('DOMContentLoaded', function() {{
const checkboxes = document.getElementsByName('report');
for (let i = 0; i < checkboxes.length; i++) {{
checkboxes[i].addEventListener('change', updateHiddenField);
}}
}});
</script>
"""
# Create HTML component with our custom checkbox implementation
custom_html = gr.HTML(html_content)
# Create a hidden Textbox to store the selected values as JSON
reports_checkboxes = gr.Textbox(
value="[]", # Empty array as initial value
visible=False, # Hide this
elem_id="reports-hidden-value"
)
gr.Markdown("*Check the boxes next to the reports you want to manage*")
# Buttons for report management
with gr.Row():
with gr.Column(scale=1):
refresh_button = gr.Button("Refresh List")
with gr.Column(scale=1):
download_button = gr.Button("Download Selected")
with gr.Column(scale=1):
delete_button = gr.Button("Delete Selected", variant="stop")
with gr.Column(scale=2):
cleanup_days = gr.Slider(
minimum=0,
maximum=90,
value=30,
step=1,
label="Delete Reports Older Than (Days)",
info="Set to 0 to disable automatic cleanup"
)
cleanup_button = gr.Button("Clean Up Old Reports")
# File download component
with gr.Row():
file_output = gr.File(
label="Downloaded Reports",
file_count="multiple",
type="filepath",
interactive=False
)
# Status message
with gr.Row():
status_message = gr.Markdown("")
# Migration button for existing reports
with gr.Row():
with gr.Column():
gr.Markdown("### Migrate Existing Reports")
gr.Markdown("Use this button to migrate existing reports from the root directory to the new reports directory structure.")
migrate_button = gr.Button("Migrate Existing Reports", variant="primary")
# Set up event handlers
search_button.click(
fn=self.process_query,
inputs=[search_query_input, search_num_results, search_use_reranker],
outputs=[search_results_output, search_file_output]
)
# Connect the progress callback to the report button
# Progress display is now handled entirely by Gradio's built-in progress tracking
# Update the progress tracking in the generate_report method
async def generate_report_with_progress(query, detail_level, query_type, model_name, rerank, token_budget, initial_results, final_results):
async def generate_report_with_progress(query, detail_level, query_type, model_name, process_thinking, initial_results, final_results):
# Set up progress tracking
progress_data = gr.Progress(track_tqdm=True)
@ -799,17 +1352,255 @@ class GradioInterface:
print(f"Model selected from UI dropdown: {model_name}")
# Call the original generate_report method
result = await self.generate_report(query, detail_level, query_type, model_name, rerank, token_budget, initial_results, final_results)
result = await self.generate_report(
query,
detail_level,
query_type,
model_name,
None, # results_file is now None since we removed the search tab
process_thinking,
initial_results,
final_results
)
return result
report_button.click(
fn=lambda q, d, t, m, r, p, i, f: asyncio.run(generate_report_with_progress(q, d, t, m, r, p, i, f)),
fn=lambda q, d, t, m, p, i, f: asyncio.run(generate_report_with_progress(q, d, t, m, p, i, f)),
inputs=[report_query_input, report_detail_level, report_query_type, report_custom_model,
search_file_output, report_process_thinking, initial_results_slider, final_results_slider],
report_process_thinking, initial_results_slider, final_results_slider],
outputs=[report_output, report_file_output]
)
# Report Management Tab Event Handlers
def refresh_reports():
reports_data = self._get_reports_for_display()
choices = self._get_report_choices(reports_data)
return reports_data, choices
refresh_button.click(
fn=refresh_reports,
inputs=[],
outputs=[reports_table, reports_checkboxes]
)
# Add wrapper to parse JSON and handle download
def download_with_logging(selected_json):
try:
# Parse the JSON string from the hidden textbox
import json
print(f"Raw selected_json: '{selected_json}'")
# Make sure we have valid JSON before parsing
if not selected_json or selected_json.strip() == "":
selected = []
else:
# Handle potential edge cases by cleaning up the input
cleaned_json = selected_json.strip()
if not (cleaned_json.startswith('[') and cleaned_json.endswith(']')):
cleaned_json = f"[{cleaned_json}]"
selected = json.loads(cleaned_json)
print(f"Download button clicked with selections: {selected}")
files = self._download_selected_reports(selected)
print(f"Files prepared for download: {len(files)}")
return files
except Exception as e:
print(f"Error processing selections for download: {e}")
import traceback
traceback.print_exc()
return []
# Connect download button directly to our handler
download_button.click(
fn=download_with_logging,
inputs=reports_checkboxes, # Now contains JSON string of selections
outputs=file_output
)
# No need for toggle functionality as it's handled by JavaScript in the HTML component
# Add logging wrapper for delete function
def delete_with_logging(selected):
print(f"Delete button clicked with selections: {selected}")
updated_table, updated_choices, message = self._delete_selected_reports(selected)
print(f"After deletion: {len(updated_table)} reports, {len(updated_choices)} choices")
return updated_table, updated_choices, message
# Update delete handler to parse JSON with improved error handling
def delete_with_reset(selected_json):
try:
# Parse the JSON string from the hidden textbox
import json
print(f"Raw selected_json for delete: '{selected_json}'")
# Make sure we have valid JSON before parsing
if not selected_json or selected_json.strip() == "":
selected = []
else:
# Handle potential edge cases by cleaning up the input
cleaned_json = selected_json.strip()
if not (cleaned_json.startswith('[') and cleaned_json.endswith(']')):
cleaned_json = f"[{cleaned_json}]"
selected = json.loads(cleaned_json)
print(f"Delete button clicked with selections: {selected}")
updated_table, updated_choices, message = self._delete_selected_reports(selected)
print(f"After deletion: {len(updated_table)} reports, {len(updated_choices)} choices")
# Generate new HTML after deletion
html_choices = []
for i, choice in enumerate(updated_choices):
html_choices.append(f'<div style="padding: 5px; margin-bottom: 8px;">')
html_choices.append(f'<label style="display: block; width: 100%; cursor: pointer;">')
html_choices.append(f'<input type="checkbox" id="report-{i}" name="report" value="{choice}"> {choice}')
html_choices.append('</label>')
html_choices.append('</div>')
html_content = f"""
<div style="border: 1px solid #ddd; border-radius: 5px; margin-bottom: 15px;">
<div style="padding: 10px; border-bottom: 1px solid #eee; background-color: #f8f8f8;">
<label style="display: block; font-weight: bold; cursor: pointer;">
<input type="checkbox" id="select-all-checkbox" onclick="toggleAllReports()"> Check/Uncheck All
</label>
</div>
<div id="reports-container" style="max-height: 500px; overflow-y: auto; padding: 10px;">
{''.join(html_choices)}
</div>
</div>
<script>
// Toggle all checkboxes
function toggleAllReports() {{
const checkAll = document.getElementById('select-all-checkbox');
const checkboxes = document.getElementsByName('report');
for (let i = 0; i < checkboxes.length; i++) {{
checkboxes[i].checked = checkAll.checked;
}}
updateHiddenField();
}}
// Get selected values and update the hidden field
function updateHiddenField() {{
const checkboxes = document.getElementsByName('report');
const selected = [];
for (let i = 0; i < checkboxes.length; i++) {{
if (checkboxes[i].checked) {{
selected.push(checkboxes[i].value);
}}
}}
// Find the hidden field and set its value
const hiddenField = document.querySelector('#reports-hidden-value textarea');
if (hiddenField) {{
hiddenField.value = JSON.stringify(selected);
// Trigger a change event to notify Gradio
const event = new Event('input', {{ bubbles: true }});
hiddenField.dispatchEvent(event);
}}
}}
// Add event listeners to all checkbox changes
document.addEventListener('DOMContentLoaded', function() {{
const checkboxes = document.getElementsByName('report');
for (let i = 0; i < checkboxes.length; i++) {{
checkboxes[i].addEventListener('change', updateHiddenField);
}}
}});
</script>
"""
# Reset hidden field
return updated_table, html_content, "[]", message
except Exception as e:
print(f"Error processing selections: {e}")
return reports_table, custom_html.value, "[]", f"Error: {str(e)}"
delete_button.click(
fn=delete_with_reset,
inputs=reports_checkboxes,
outputs=[reports_table, custom_html, reports_checkboxes, status_message]
).then(
fn=lambda msg: f"{msg} Selected reports deleted successfully.",
inputs=[status_message],
outputs=[status_message]
)
def cleanup_with_refresh(days):
updated_table = self._cleanup_old_reports(days)
choices = self._get_report_choices(updated_table)
message = f"Reports older than {days} days have been deleted."
print(message)
return updated_table, choices, message
# Note: We need to make sure this runs properly and updates both the table and checkboxes
# The built-in Gradio progress tracking (gr.Progress) is used instead
# This is passed to the generate_report method and handles progress updates
cleanup_button.click(
fn=cleanup_with_refresh,
inputs=cleanup_days,
outputs=[reports_table, reports_checkboxes, status_message]
).then(
# Add a then function to ensure the UI updates properly
fn=lambda: "Report list has been refreshed.",
inputs=[],
outputs=[status_message]
)
# Migration button event handler
def migrate_and_refresh():
print("Starting migration of existing reports...")
status = self.migrate_existing_reports()
print("Migration completed, refreshing display...")
reports_data = self._get_reports_for_display()
print(f"Got {len(reports_data)} reports for display")
choices = self._get_report_choices(reports_data)
print(f"Generated {len(choices)} choices for selection")
return status, reports_data, choices
migrate_button.click(
fn=migrate_and_refresh,
inputs=[],
outputs=[status_message, reports_table, reports_checkboxes]
).then(
# Add a confirmation message after migration completes
fn=lambda msg: f"{msg} Report list has been refreshed.",
inputs=[status_message],
outputs=[status_message]
)
# Initialize the checkboxes when the table is first loaded
# reports_table.change(
# fn=lambda table: self._get_report_choices(table),
# inputs=reports_table,
# outputs=reports_checkboxes
# )
# Initialize both the table and checkboxes on page load
def init_reports_ui():
print("Initializing reports UI...")
reports_data = self._get_reports_for_display()
choices = self._get_report_choices(reports_data)
# Log the actual choices for debugging
print(f"Initializing reports UI with {len(reports_data)} reports and {len(choices)} choices")
for i, choice in enumerate(choices[:5]):
print(f"Sample choice {i}: {choice}")
if len(choices) > 5:
print(f"...and {len(choices) - 5} more choices")
status = "Reports management initialized successfully."
return reports_data, choices, status
interface.load(
fn=init_reports_ui,
inputs=[],
outputs=[reports_table, reports_checkboxes, status_message]
)
return interface
def launch(self, **kwargs):