Improved report management UI with custom HTML checkboxes, dark theme styling, and robust error handling
This commit is contained in:
parent
1a2cdc4c60
commit
577eed1f5e
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue