RefCheck / src /ui.py
voidful's picture
Add RefCheck Gradio Space
11a28db verified
from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich.tree import Tree
import copy
class BibUI:
"""Handles all terminal UI interactions for BibGuard."""
def __init__(self):
self.console = Console()
def show_analysis_report(self, ok_entries, to_fix, to_review, to_remove):
"""Display the initial analysis summary table."""
table = Table(title="πŸ“Š Analysis Report", show_header=True, header_style="bold magenta")
table.add_column("Category", style="cyan")
table.add_column("Count", justify="right")
table.add_column("Description")
table.add_row("βœ… Correct", str(len(ok_entries)), "Entries match valid metadata")
table.add_row("πŸ› οΈ To Fix", str(len(to_fix)), "[green]High confidence auto-fixes[/green]")
table.add_row("πŸ” Review", str(len(to_review)), "[yellow]Ambiguous or low confidence[/yellow]")
table.add_row("πŸ—‘οΈ Remove", str(len(to_remove)), "[red]No metadata found (Hallucinations)[/red]")
self.console.print(table)
if not (to_fix or to_review or to_remove):
self.console.print(Panel("[green]βœ“ No issues found. All entries are valid.[/green]", title="Status"))
def show_manual_review(self, entry, best_res, candidates, apply_fix_func):
"""Display manual review table for a single entry."""
self.console.print(f"\n[bold]Entry: {entry.key}[/bold]")
self.console.print(f"Title: {entry.title}")
self.console.print(f"Year: {entry.year}")
self.console.print(f"Auth: {entry.author}")
cand_table = Table(show_header=True, header_style="bold blue")
cand_table.add_column("#", style="dim", width=4)
cand_table.add_column("Source", style="cyan", width=12)
cand_table.add_column("Conf", justify="right")
cand_table.add_column("Candidate Metadata (Fetched)", style="white")
cand_table.add_column("Proposed Changes", style="green")
for i, cand in enumerate(candidates, 1):
# We need to simulate the fix to show changes
# We pass the apply_fix function to avoid circular dependency or logic duplication
temp_entry = copy.deepcopy(entry)
changes = apply_fix_func(temp_entry, cand.fetched_data)
change_desc = "\n".join(changes) if changes else "[dim]No changes[/dim]"
conf_style = "green" if cand.confidence > 0.7 else "yellow" if cand.confidence > 0.4 else "red"
# Format the candidate's actual metadata
fd = cand.fetched_data
meta_lines = []
if getattr(fd, 'title', None):
meta_lines.append(f"[bold]Title:[/bold] {fd.title[:60] + '...' if len(fd.title) > 60 else fd.title}")
if getattr(fd, 'authors', None):
a_str = " and ".join(fd.authors)
meta_lines.append(f"[bold]Authors:[/bold] {a_str[:60] + '...' if len(a_str) > 60 else a_str}")
if getattr(fd, 'year', None):
meta_lines.append(f"[bold]Year:[/bold] {fd.year}")
if getattr(fd, 'doi', None):
meta_lines.append(f"[bold]DOI:[/bold] {fd.doi}")
meta_desc = "\n".join(meta_lines) if meta_lines else "[dim]No metadata details[/dim]"
cand_table.add_row(
str(i),
cand.source,
f"[{conf_style}]{cand.confidence:.2f}[/{conf_style}]",
meta_desc,
change_desc
)
self.console.print(cand_table)
def show_final_report(self, total, verified, issues, not_found, reports, fixed_count, fixed_details, removed_details):
"""Display the verification status and modification tree."""
# Visual Final Status
status_table = Table(box=None, padding=(0, 2))
status_table.add_column("Metric", style="bold")
status_table.add_column("Value", justify="right")
status_table.add_row("Total Entries", str(total))
status_table.add_row("Verified", f"[green]{verified}[/green]")
status_table.add_row("Issues", f"[red]{issues}[/red]" if issues > 0 else "0")
status_table.add_row("Not Found", f"[yellow]{not_found}[/yellow]" if not_found > 0 else "0")
self.console.print(Panel(status_table, title="πŸ“Š Final Status", expand=False))
if issues > 0:
self.console.print("\n[bold red]⚠ Remaining Issues (Not Auto-Fixed):[/bold red]")
for r in reports:
if r.comparison and r.comparison.has_issues:
self.console.print(f" - [bold]{r.entry.key}[/bold] (Conf: {r.comparison.confidence:.2f}): {', '.join(r.comparison.issues)}")
# Report fixes and removals
if fixed_count > 0 or removed_details:
tree = Tree("✏️ Modifications Report")
if removed_details:
rem_node = tree.add(f"[red]Removed {len(removed_details)} entries[/red]")
for entry, reason in removed_details:
rem_node.add(f"[bold]{entry.key}[/bold]: \"{entry.title}\" ([italic]{reason}[/italic])")
if fixed_count > 0:
fix_node = tree.add(f"[green]Fixed {fixed_count} entries[/green]")
for key, changes in fixed_details.items():
entry_node = fix_node.add(f"[bold]{key}[/bold]")
for change in changes:
entry_node.add(change)
self.console.print(tree)
self.console.print("\n[green]βœ“ Changes applied and saved to file.[/green]")
else:
self.console.print("\n[green]βœ“ No changes were needed.[/green]")
def show_sanitize_report(self, sanitize_fixes: dict):
"""Display sanitization results as a rich tree."""
if not sanitize_fixes:
self.console.print("[green]βœ“ No formatting issues found.[/green]\n")
return
# Category display info
category_info = {
"dblp_id": ("πŸ”’", "DBLP Disambiguation ID Cleanup", "red"),
"corporate_author": ("🏒", "Corporate Author Protection", "yellow"),
"entry_type": ("πŸ“‹", "Entry Type Correction", "cyan"),
"title_case": ("πŸ”€", "Title Capitalization Protection", "blue"),
"doi_mismatch": ("πŸ”—", "DOI Mismatch", "red"),
"future_year": ("πŸ“…", "Future Year Detection", "magenta"),
"field_cleanup": ("🧹", "Junk Field Removal", "dim"),
}
total_fixes = sum(len(fixes) for fixes in sanitize_fixes.values())
tree = Tree(f"🧹 Sanitization Report ({total_fixes} fixes in {len(sanitize_fixes)} entries)")
# Group fixes by category across all entries
by_category = {}
for entry_key, fixes in sanitize_fixes.items():
for fix in fixes:
if fix.category not in by_category:
by_category[fix.category] = []
by_category[fix.category].append(fix)
for cat, fixes in by_category.items():
icon, label, color = category_info.get(cat, ("❓", cat, "white"))
cat_node = tree.add(f"{icon} [{color}]{label} ({len(fixes)})[/{color}]")
for fix in fixes:
cat_node.add(f"[bold]{fix.entry_key}[/bold]: {fix.description}")
self.console.print(tree)
self.console.print("")