| 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): |
| |
| |
| 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" |
| |
| |
| 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.""" |
| |
| 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)}") |
| |
| |
| 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_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)") |
| |
| |
| 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("") |
|
|
|
|