youtube-summarizer/backend/interactive_cli.py

840 lines
35 KiB
Python

#!/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()