#!/usr/bin/env python3 """YouTube Summarizer Interactive CLI A beautiful interactive shell application for managing YouTube video summaries. """ import asyncio import json import os import sys import time from pathlib import Path from datetime import datetime, timedelta from typing import Optional, Dict, Any, List, Tuple import logging from enum import Enum import click from rich.console import Console from rich.table import Table from rich.panel import Panel from rich.layout import Layout from rich.live import Live from rich.align import Align from rich.text import Text from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn from rich.prompt import Prompt, Confirm, IntPrompt from rich.markdown import Markdown from rich.syntax import Syntax from rich import box from rich.columns import Columns from rich.tree import Tree # Add parent directory to path for imports sys.path.append(str(Path(__file__).parent.parent)) from backend.cli import SummaryManager, SummaryPipelineCLI from backend.mermaid_renderer import MermaidRenderer, DiagramEnhancer # Initialize Rich console console = Console() logging.basicConfig(level=logging.WARNING) logger = logging.getLogger(__name__) class MenuOption(Enum): """Menu options for the interactive interface.""" ADD_SUMMARY = "1" LIST_SUMMARIES = "2" VIEW_SUMMARY = "3" REGENERATE = "4" REFINE = "5" BATCH_PROCESS = "6" COMPARE = "7" STATISTICS = "8" SETTINGS = "9" HELP = "h" EXIT = "q" class InteractiveSummarizer: """Interactive shell interface for YouTube Summarizer.""" def __init__(self): self.manager = SummaryManager() self.current_model = "deepseek" self.current_length = "standard" self.include_diagrams = False self.session_summaries = [] self.running = True # Color scheme self.primary_color = "cyan" self.secondary_color = "yellow" self.success_color = "green" self.error_color = "red" self.accent_color = "magenta" def clear_screen(self): """Clear the terminal screen.""" os.system('clear' if os.name == 'posix' else 'cls') def display_banner(self): """Display the application banner.""" banner = """ ╔═══════════════════════════════════════════════════════════════════╗ ║ ║ ║ ▄▄▄█████▓ █ ██ ▄▄▄▄ ▓█████ ▄▄▄ ██▓ ║ ║ ▓ ██▒ ▓▒ ██ ▓██▒▓█████▄ ▓█ ▀ ▒████▄ ▓██▒ ║ ║ ▒ ▓██░ ▒░▓██ ▒██░▒██▒ ▄██▒███ ▒██ ▀█▄ ▒██▒ ║ ║ ░ ▓██▓ ░ ▓▓█ ░██░▒██░█▀ ▒▓█ ▄ ░██▄▄▄▄██ ░██░ ║ ║ ▒██▒ ░ ▒▒█████▓ ░▓█ ▀█▓░▒████▒ ▓█ ▓██▒░██░ ║ ║ ▒ ░░ ░▒▓▒ ▒ ▒ ░▒▓███▀▒░░ ▒░ ░ ▒▒ ▓▒█░░▓ ║ ║ ░ ░░▒░ ░ ░ ▒░▒ ░ ░ ░ ░ ▒ ▒▒ ░ ▒ ░ ║ ║ ░ ░░░ ░ ░ ░ ░ ░ ░ ▒ ▒ ░ ║ ║ ░ ░ ░ ░ ░ ░ ░ ║ ║ ░ ║ ║ ║ ║ YouTube Summarizer Interactive CLI ║ ║ Powered by AI Intelligence ║ ║ ║ ╚═══════════════════════════════════════════════════════════════════╝ """ styled_banner = Text(banner, style=f"bold {self.primary_color}") console.print(styled_banner) def display_menu(self): """Display the main menu.""" menu = Panel( "[bold cyan]📹 Main Menu[/bold cyan]\n\n" "[yellow]1.[/yellow] Add New Summary\n" "[yellow]2.[/yellow] List Summaries\n" "[yellow]3.[/yellow] View Summary\n" "[yellow]4.[/yellow] Regenerate Summary\n" "[yellow]5.[/yellow] Refine Summary\n" "[yellow]6.[/yellow] Batch Process\n" "[yellow]7.[/yellow] Compare Summaries\n" "[yellow]8.[/yellow] Statistics\n" "[yellow]9.[/yellow] Settings\n\n" "[dim]h - Help | q - Exit[/dim]", title="[bold magenta]Choose an Option[/bold magenta]", border_style="cyan", box=box.ROUNDED ) console.print(menu) def display_status_bar(self): """Display a status bar with current settings.""" status_items = [ f"[cyan]Model:[/cyan] {self.current_model}", f"[cyan]Length:[/cyan] {self.current_length}", f"[cyan]Diagrams:[/cyan] {'✓' if self.include_diagrams else '✗'}", f"[cyan]Session:[/cyan] {len(self.session_summaries)} summaries" ] status_bar = " | ".join(status_items) console.print(Panel(status_bar, style="dim", box=box.MINIMAL)) async def add_summary_interactive(self): """Interactive flow for adding a new summary.""" self.clear_screen() console.print(Panel("[bold cyan]🎥 Add New Video Summary[/bold cyan]", box=box.DOUBLE)) # Get URL video_url = Prompt.ask("\n[green]Enter YouTube URL[/green]") # Show options console.print("\n[yellow]Configuration Options:[/yellow]") # Model selection models = ["deepseek", "anthropic", "openai", "gemini"] console.print("\nAvailable models:") for i, model in enumerate(models, 1): console.print(f" {i}. {model}") model_choice = IntPrompt.ask("Select model", default=1, choices=["1", "2", "3", "4"]) selected_model = models[model_choice - 1] # Length selection lengths = ["brief", "standard", "detailed"] console.print("\nSummary length:") for i, length in enumerate(lengths, 1): console.print(f" {i}. {length}") length_choice = IntPrompt.ask("Select length", default=2, choices=["1", "2", "3"]) selected_length = lengths[length_choice - 1] # Diagrams include_diagrams = Confirm.ask("\nInclude Mermaid diagrams?", default=False) # Custom prompt use_custom = Confirm.ask("\nUse custom prompt?", default=False) custom_prompt = None if use_custom: console.print("\n[dim]Enter your custom prompt (press Enter twice to finish):[/dim]") lines = [] while True: line = input() if line == "": break lines.append(line) custom_prompt = "\n".join(lines) # Focus areas focus_areas = [] if Confirm.ask("\nAdd focus areas?", default=False): console.print("[dim]Enter focus areas (empty line to finish):[/dim]") while True: area = input("Focus area: ") if not area: break focus_areas.append(area) # Process the video console.print(f"\n[cyan]Processing video with {selected_model}...[/cyan]") pipeline = SummaryPipelineCLI(model=selected_model) with Progress( SpinnerColumn(), TextColumn("[progress.description]{task.description}"), BarColumn(), console=console ) as progress: task = progress.add_task("[cyan]Generating summary...", total=None) try: result = await pipeline.process_video( video_url=video_url, custom_prompt=custom_prompt, summary_length=selected_length, focus_areas=focus_areas if focus_areas else None, include_diagrams=include_diagrams ) # Save to database summary_data = { "video_id": result.get("video_id"), "video_url": video_url, "video_title": result.get("metadata", {}).get("title"), "transcript": result.get("transcript"), "summary": result.get("summary", {}).get("content"), "key_points": result.get("summary", {}).get("key_points"), "main_themes": result.get("summary", {}).get("main_themes"), "model_used": selected_model, "processing_time": result.get("processing_time"), "quality_score": result.get("quality_metrics", {}).get("overall_score"), "summary_length": selected_length, "focus_areas": focus_areas } saved = pipeline.summary_manager.save_summary(summary_data) self.session_summaries.append(saved.id) progress.update(task, description="[green]✓ Summary created successfully!") # Display preview console.print(f"\n[green]✓ Summary created![/green]") console.print(f"[yellow]ID:[/yellow] {saved.id}") console.print(f"[yellow]Title:[/yellow] {saved.video_title}") if saved.summary: preview = saved.summary[:300] + "..." if len(saved.summary) > 300 else saved.summary console.print(f"\n[bold]Preview:[/bold]\n{preview}") # Ask if user wants to view full summary if Confirm.ask("\nView full summary?", default=True): await self.view_summary_interactive(saved.id) except Exception as e: progress.update(task, description=f"[red]✗ Error: {e}") console.print(f"\n[red]Failed to create summary: {e}[/red]") input("\nPress Enter to continue...") def list_summaries_interactive(self): """Interactive listing of summaries.""" self.clear_screen() console.print(Panel("[bold cyan]📚 Summary Library[/bold cyan]", box=box.DOUBLE)) # Get filter options limit = IntPrompt.ask("\nHow many summaries to show?", default=10) summaries = self.manager.list_summaries(limit=limit) if not summaries: console.print("\n[yellow]No summaries found[/yellow]") else: # Create interactive table table = Table(title=f"Recent {len(summaries)} Summaries", box=box.ROUNDED) table.add_column("#", style="dim", width=3) table.add_column("ID", style="cyan", width=8) table.add_column("Title", style="green", width=35) table.add_column("Model", style="yellow", width=10) table.add_column("Created", style="magenta", width=16) table.add_column("Quality", style="blue", width=7) for i, summary in enumerate(summaries, 1): quality = f"{summary.quality_score:.1f}" if summary.quality_score else "N/A" created = summary.created_at.strftime("%Y-%m-%d %H:%M") title = summary.video_title[:32] + "..." if len(summary.video_title or "") > 35 else summary.video_title # Highlight session summaries id_display = summary.id[:8] if summary.id in self.session_summaries: id_display = f"[bold]{id_display}[/bold] ✨" table.add_row( str(i), id_display, title or "Unknown", summary.model_used or "Unknown", created, quality ) console.print(table) # Allow selection if Confirm.ask("\nSelect a summary to view?", default=False): selection = IntPrompt.ask("Enter number", default=1, choices=[str(i) for i in range(1, len(summaries) + 1)]) selected = summaries[selection - 1] asyncio.run(self.view_summary_interactive(selected.id)) input("\nPress Enter to continue...") async def view_summary_interactive(self, summary_id: Optional[str] = None): """Interactive summary viewing with rich formatting.""" self.clear_screen() if not summary_id: summary_id = Prompt.ask("[green]Enter Summary ID[/green]") summary = self.manager.get_summary(summary_id) if not summary: console.print(f"[red]Summary not found: {summary_id}[/red]") input("\nPress Enter to continue...") return # Create a rich layout layout = Layout() layout.split_column( Layout(name="header", size=3), Layout(name="body"), Layout(name="footer", size=3) ) # Header header_text = f"[bold cyan]📄 {summary.video_title or 'Untitled'}[/bold cyan]" layout["header"].update(Panel(header_text, box=box.DOUBLE)) # Body content body_parts = [] # Metadata metadata = Table(box=box.SIMPLE) metadata.add_column("Property", style="yellow") metadata.add_column("Value", style="white") metadata.add_row("ID", summary.id[:12] + "...") metadata.add_row("URL", summary.video_url) metadata.add_row("Model", summary.model_used or "Unknown") metadata.add_row("Created", summary.created_at.strftime("%Y-%m-%d %H:%M") if summary.created_at else "Unknown") metadata.add_row("Quality", f"{summary.quality_score:.2f}" if summary.quality_score else "N/A") body_parts.append(Panel(metadata, title="[bold]Metadata[/bold]", border_style="dim")) # Summary content if summary.summary: # Check for Mermaid diagrams if '```mermaid' in summary.summary: # Split summary by mermaid blocks parts = summary.summary.split('```mermaid') formatted_summary = parts[0] for i, part in enumerate(parts[1:], 1): if '```' in part: diagram_code, rest = part.split('```', 1) formatted_summary += f"\n[cyan]📊 Diagram {i}:[/cyan]\n" formatted_summary += f"[dim]```mermaid{diagram_code}```[/dim]\n" formatted_summary += rest else: formatted_summary += part else: formatted_summary = summary.summary summary_panel = Panel( Markdown(formatted_summary) if len(formatted_summary) < 2000 else formatted_summary, title="[bold]Summary[/bold]", border_style="green" ) body_parts.append(summary_panel) # Key points if summary.key_points: points_tree = Tree("[bold]Key Points[/bold]") for point in summary.key_points: points_tree.add(f"• {point}") body_parts.append(Panel(points_tree, border_style="yellow")) # Main themes if summary.main_themes: themes_list = "\n".join([f"🏷️ {theme}" for theme in summary.main_themes]) body_parts.append(Panel(themes_list, title="[bold]Main Themes[/bold]", border_style="magenta")) # Combine body parts layout["body"].update(Columns(body_parts, equal=False, expand=True)) # Footer with actions footer_text = "[dim]r - Refine | d - Diagrams | e - Export | b - Back[/dim]" layout["footer"].update(Panel(footer_text, box=box.MINIMAL)) console.print(layout) # Handle actions action = Prompt.ask("\n[green]Action[/green]", choices=["r", "d", "e", "b"], default="b") if action == "r": await self.refine_summary_interactive(summary_id) elif action == "d": self.show_diagram_options(summary) elif action == "e": self.export_summary(summary) elif action == "b": return async def refine_summary_interactive(self, summary_id: Optional[str] = None): """Interactive refinement interface.""" self.clear_screen() console.print(Panel("[bold cyan]🔄 Refine Summary[/bold cyan]", box=box.DOUBLE)) if not summary_id: summary_id = Prompt.ask("[green]Enter Summary ID[/green]") summary = self.manager.get_summary(summary_id) if not summary: console.print(f"[red]Summary not found: {summary_id}[/red]") input("\nPress Enter to continue...") return console.print(f"\n[yellow]Refining:[/yellow] {summary.video_title}") console.print(f"[yellow]Current Model:[/yellow] {summary.model_used}") # Display current summary if summary.summary: preview = summary.summary[:400] + "..." if len(summary.summary) > 400 else summary.summary console.print(f"\n[dim]Current summary:[/dim]\n{preview}\n") # Refinement loop refinement_history = [] console.print("[cyan]Interactive Refinement Mode[/cyan]") console.print("[dim]Commands: 'done' to finish | 'undo' to revert | 'help' for tips[/dim]\n") while True: instruction = Prompt.ask("[green]Refinement instruction[/green]") if instruction.lower() == 'done': console.print("[green]✓ Refinement complete![/green]") break if instruction.lower() == 'help': self.show_refinement_tips() continue if instruction.lower() == 'undo': if refinement_history: previous = refinement_history.pop() updates = { "summary": previous['summary'], "key_points": previous.get('key_points'), "main_themes": previous.get('main_themes') } summary = self.manager.update_summary(summary_id, updates) console.print("[yellow]✓ Reverted to previous version[/yellow]") else: console.print("[yellow]No previous versions to revert to[/yellow]") continue # Save current state refinement_history.append({ "summary": summary.summary, "key_points": summary.key_points, "main_themes": summary.main_themes }) # Process refinement with Progress( SpinnerColumn(), TextColumn("[progress.description]{task.description}"), console=console ) as progress: task = progress.add_task(f"[cyan]Applying: {instruction[:50]}...", total=None) try: pipeline = SummaryPipelineCLI(model=summary.model_used or 'deepseek') refinement_prompt = f""" Original summary: {summary.summary} Refinement instruction: {instruction} Please provide an improved summary based on the refinement instruction above. """ result = await pipeline.process_video( video_url=summary.video_url, custom_prompt=refinement_prompt, summary_length=summary.summary_length or 'standard', focus_areas=summary.focus_areas ) updates = { "summary": result.get("summary", {}).get("content"), "key_points": result.get("summary", {}).get("key_points"), "main_themes": result.get("summary", {}).get("main_themes") } summary = self.manager.update_summary(summary_id, updates) progress.update(task, description="[green]✓ Refinement applied!") # Show updated preview if summary.summary: preview = summary.summary[:400] + "..." if len(summary.summary) > 400 else summary.summary console.print(f"\n[green]Updated summary:[/green]\n{preview}\n") except Exception as e: progress.update(task, description=f"[red]✗ Error: {e}") console.print(f"[red]Refinement failed: {e}[/red]") input("\nPress Enter to continue...") def show_refinement_tips(self): """Display refinement tips.""" tips = Panel( "[bold yellow]Refinement Tips:[/bold yellow]\n\n" "• 'Make it more concise' - Shorten the summary\n" "• 'Focus on [topic]' - Emphasize specific aspects\n" "• 'Add implementation details' - Include technical details\n" "• 'Include examples' - Add concrete examples\n" "• 'Add a timeline' - Include chronological information\n" "• 'Include a flowchart for [process]' - Add visual diagram\n" "• 'Make it more actionable' - Focus on practical steps\n" "• 'Simplify the language' - Make it more accessible\n" "• 'Add key statistics' - Include numerical data\n" "• 'Structure as bullet points' - Change formatting", border_style="yellow", box=box.ROUNDED ) console.print(tips) def show_diagram_options(self, summary): """Show diagram-related options for a summary.""" self.clear_screen() console.print(Panel("[bold cyan]📊 Diagram Options[/bold cyan]", box=box.DOUBLE)) if summary.summary and '```mermaid' in summary.summary: # Extract and display diagrams renderer = MermaidRenderer() diagrams = renderer.extract_diagrams(summary.summary) if diagrams: console.print(f"\n[green]Found {len(diagrams)} diagram(s)[/green]\n") for i, diagram in enumerate(diagrams, 1): console.print(f"[yellow]Diagram {i}: {diagram['title']} ({diagram['type']})[/yellow]") # Show ASCII preview ascii_art = renderer.render_to_ascii(diagram) if ascii_art: console.print(Panel(ascii_art, border_style="dim")) if Confirm.ask("\nRender diagrams to files?", default=False): output_dir = f"diagrams/{summary.id}" results = renderer.extract_and_render_all(summary.summary) console.print(f"[green]✓ Rendered to {output_dir}[/green]") else: console.print("[yellow]No diagrams found in this summary[/yellow]") # Suggest diagrams if Confirm.ask("\nWould you like diagram suggestions?", default=True): suggestions = DiagramEnhancer.suggest_diagrams(summary.summary or "") if suggestions: for suggestion in suggestions: console.print(f"\n[yellow]{suggestion['type'].title()} Diagram[/yellow]") console.print(f"[dim]{suggestion['reason']}[/dim]") console.print(Syntax(suggestion['template'], "mermaid", theme="monokai")) input("\nPress Enter to continue...") def export_summary(self, summary): """Export summary to file.""" console.print("\n[cyan]Export Options:[/cyan]") console.print("1. JSON") console.print("2. Markdown") console.print("3. Plain Text") format_choice = IntPrompt.ask("Select format", default=1, choices=["1", "2", "3"]) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") if format_choice == 1: # JSON export filename = f"summary_{summary.id[:8]}_{timestamp}.json" export_data = { "id": summary.id, "video_id": summary.video_id, "video_title": summary.video_title, "video_url": summary.video_url, "summary": summary.summary, "key_points": summary.key_points, "main_themes": summary.main_themes, "model_used": summary.model_used, "created_at": summary.created_at.isoformat() if summary.created_at else None } with open(filename, 'w') as f: json.dump(export_data, f, indent=2) elif format_choice == 2: # Markdown export filename = f"summary_{summary.id[:8]}_{timestamp}.md" with open(filename, 'w') as f: f.write(f"# {summary.video_title}\n\n") f.write(f"**URL:** {summary.video_url}\n") f.write(f"**Model:** {summary.model_used}\n") f.write(f"**Date:** {summary.created_at}\n\n") f.write("## Summary\n\n") f.write(summary.summary or "No summary available") if summary.key_points: f.write("\n\n## Key Points\n\n") for point in summary.key_points: f.write(f"- {point}\n") else: # Plain text export filename = f"summary_{summary.id[:8]}_{timestamp}.txt" with open(filename, 'w') as f: f.write(f"{summary.video_title}\n") f.write("=" * len(summary.video_title or "") + "\n\n") f.write(summary.summary or "No summary available") console.print(f"[green]✓ Exported to {filename}[/green]") def show_statistics(self): """Display statistics dashboard.""" self.clear_screen() console.print(Panel("[bold cyan]📊 Statistics Dashboard[/bold cyan]", box=box.DOUBLE)) from sqlalchemy import func with self.manager.get_session() as session: from backend.models import Summary total = session.query(Summary).count() # Model distribution model_stats = session.query( Summary.model_used, func.count(Summary.id) ).group_by(Summary.model_used).all() # Recent activity recent_date = datetime.utcnow() - timedelta(days=7) recent = session.query(Summary).filter( Summary.created_at >= recent_date ).count() # Average scores avg_quality = session.query(func.avg(Summary.quality_score)).scalar() avg_time = session.query(func.avg(Summary.processing_time)).scalar() # Create statistics panels stats_grid = Table.grid(padding=1) # Total summaries total_panel = Panel( f"[bold cyan]{total}[/bold cyan]\n[dim]Total Summaries[/dim]", border_style="cyan" ) # Recent activity recent_panel = Panel( f"[bold green]{recent}[/bold green]\n[dim]Last 7 Days[/dim]", border_style="green" ) # Average quality quality_panel = Panel( f"[bold yellow]{avg_quality:.1f}[/bold yellow]\n[dim]Avg Quality[/dim]" if avg_quality else "[dim]No data[/dim]", border_style="yellow" ) # Session stats session_panel = Panel( f"[bold magenta]{len(self.session_summaries)}[/bold magenta]\n[dim]This Session[/dim]", border_style="magenta" ) stats_grid.add_row(total_panel, recent_panel, quality_panel, session_panel) console.print(stats_grid) # Model distribution chart if model_stats: console.print("\n[bold]Model Usage:[/bold]") for model, count in model_stats: bar_length = int((count / total) * 40) bar = "█" * bar_length + "░" * (40 - bar_length) percentage = (count / total) * 100 console.print(f" {(model or 'Unknown'):12} {bar} {percentage:.1f}% ({count})") input("\nPress Enter to continue...") def settings_menu(self): """Display settings menu.""" self.clear_screen() console.print(Panel("[bold cyan]⚙️ Settings[/bold cyan]", box=box.DOUBLE)) console.print("\n[yellow]Current Settings:[/yellow]") console.print(f" Default Model: {self.current_model}") console.print(f" Default Length: {self.current_length}") console.print(f" Include Diagrams: {self.include_diagrams}") if Confirm.ask("\nChange settings?", default=False): # Model models = ["deepseek", "anthropic", "openai", "gemini"] console.print("\n[yellow]Select default model:[/yellow]") for i, model in enumerate(models, 1): console.print(f" {i}. {model}") choice = IntPrompt.ask("Choice", default=1) self.current_model = models[choice - 1] # Length lengths = ["brief", "standard", "detailed"] console.print("\n[yellow]Select default length:[/yellow]") for i, length in enumerate(lengths, 1): console.print(f" {i}. {length}") choice = IntPrompt.ask("Choice", default=2) self.current_length = lengths[choice - 1] # Diagrams self.include_diagrams = Confirm.ask("\nInclude diagrams by default?", default=False) console.print("\n[green]✓ Settings updated![/green]") input("\nPress Enter to continue...") def show_help(self): """Display help information.""" self.clear_screen() help_text = """ [bold cyan]YouTube Summarizer Help[/bold cyan] [yellow]Quick Start:[/yellow] 1. Add a new summary with option [1] 2. View your summaries with option [2] 3. Refine summaries with option [5] [yellow]Key Features:[/yellow] • Multi-model support (DeepSeek, Anthropic, OpenAI, Gemini) • Interactive refinement until satisfaction • Mermaid diagram generation and rendering • Batch processing for multiple videos • Summary comparison across models [yellow]Refinement Tips:[/yellow] • Be specific with instructions • Use "undo" to revert changes • Try different models for variety • Add diagrams for visual content [yellow]Keyboard Shortcuts:[/yellow] • q - Exit application • h - Show this help • Numbers 1-9 - Quick menu selection [yellow]Pro Tips:[/yellow] • Start with standard length, refine if needed • Use focus areas for targeted summaries • Export important summaries for backup • Compare models to find best results """ console.print(Panel(help_text, border_style="cyan", box=box.ROUNDED)) input("\nPress Enter to continue...") async def run(self): """Main application loop.""" self.clear_screen() self.display_banner() time.sleep(2) while self.running: self.clear_screen() self.display_status_bar() self.display_menu() choice = Prompt.ask("\n[bold green]Select option[/bold green]", default="2") try: if choice == MenuOption.ADD_SUMMARY.value: await self.add_summary_interactive() elif choice == MenuOption.LIST_SUMMARIES.value: self.list_summaries_interactive() elif choice == MenuOption.VIEW_SUMMARY.value: await self.view_summary_interactive() elif choice == MenuOption.REGENERATE.value: console.print("[yellow]Feature coming soon![/yellow]") input("\nPress Enter to continue...") elif choice == MenuOption.REFINE.value: await self.refine_summary_interactive() elif choice == MenuOption.BATCH_PROCESS.value: console.print("[yellow]Feature coming soon![/yellow]") input("\nPress Enter to continue...") elif choice == MenuOption.COMPARE.value: console.print("[yellow]Feature coming soon![/yellow]") input("\nPress Enter to continue...") elif choice == MenuOption.STATISTICS.value: self.show_statistics() elif choice == MenuOption.SETTINGS.value: self.settings_menu() elif choice == MenuOption.HELP.value: self.show_help() elif choice == MenuOption.EXIT.value: if Confirm.ask("\n[yellow]Are you sure you want to exit?[/yellow]", default=False): self.running = False console.print("\n[cyan]Thank you for using YouTube Summarizer![/cyan]") console.print("[dim]Goodbye! 👋[/dim]\n") else: console.print("[red]Invalid option. Please try again.[/red]") input("\nPress Enter to continue...") except KeyboardInterrupt: console.print("\n[yellow]Operation cancelled[/yellow]") input("\nPress Enter to continue...") except Exception as e: console.print(f"\n[red]Error: {e}[/red]") logger.exception("Error in main loop") input("\nPress Enter to continue...") def main(): """Entry point for the interactive CLI.""" app = InteractiveSummarizer() try: asyncio.run(app.run()) except KeyboardInterrupt: console.print("\n[yellow]Application interrupted[/yellow]") except Exception as e: console.print(f"\n[red]Fatal error: {e}[/red]") logger.exception("Fatal error") if __name__ == "__main__": main()