tui.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. """
  2. Terminal User Interface (TUI) for progress display.
  3. """
  4. import shutil
  5. from typing import Optional
  6. class ProgressDisplay:
  7. """Beautiful, terminal-aware progress display."""
  8. def __init__(self):
  9. """Initialize progress display."""
  10. self.terminal_width = shutil.get_terminal_size((80, 20)).columns
  11. self.current_step = ""
  12. self.current_substep = ""
  13. def clear_line(self) -> None:
  14. """Clear the current line."""
  15. print('\r' + ' ' * self.terminal_width + '\r', end='', flush=True)
  16. def update_width(self) -> None:
  17. """Update terminal width (call if terminal is resized)."""
  18. self.terminal_width = shutil.get_terminal_size((80, 20)).columns
  19. def print_header(self, text: str) -> None:
  20. """Print a formatted header."""
  21. self.update_width()
  22. print()
  23. print("=" * self.terminal_width)
  24. centered = self._center_text(text)
  25. print(centered)
  26. print("=" * self.terminal_width)
  27. print()
  28. def print_section(self, text: str) -> None:
  29. """Print a formatted section."""
  30. self.update_width()
  31. print()
  32. print(f"▶ {text}")
  33. print()
  34. def print_info(self, text: str, indent: int = 0) -> None:
  35. """Print an info line."""
  36. prefix = " " * indent
  37. print(f"{prefix}ℹ {text}")
  38. def print_success(self, text: str, indent: int = 0) -> None:
  39. """Print a success message."""
  40. prefix = " " * indent
  41. print(f"{prefix}✓ {text}")
  42. def print_warning(self, text: str, indent: int = 0) -> None:
  43. """Print a warning message."""
  44. prefix = " " * indent
  45. print(f"{prefix}⚠ {text}")
  46. def print_error(self, text: str, indent: int = 0) -> None:
  47. """Print an error message."""
  48. prefix = " " * indent
  49. print(f"{prefix}✗ {text}")
  50. def print_progress_bar(self, current: int, total: int, label: str = "",
  51. bar_width: Optional[int] = None) -> None:
  52. """
  53. Print a progress bar that adapts to terminal width.
  54. Args:
  55. current: Current progress value
  56. total: Total value
  57. label: Optional label for the progress
  58. bar_width: Optional custom bar width (defaults to terminal width - 30)
  59. """
  60. self.update_width()
  61. if bar_width is None:
  62. bar_width = max(10, self.terminal_width - 40)
  63. if total == 0:
  64. percentage = 0
  65. filled = 0
  66. else:
  67. percentage = (current / total) * 100
  68. filled = int((current / total) * bar_width)
  69. bar = "█" * filled + "░" * (bar_width - filled)
  70. status = f"{current:>{len(str(total))}}/{total}"
  71. if label:
  72. output = f" {label:<20} │{bar}│ {status} ({percentage:5.1f}%)"
  73. else:
  74. output = f" │{bar}│ {status} ({percentage:5.1f}%)"
  75. # Truncate if too long
  76. if len(output) > self.terminal_width - 2:
  77. output = output[:self.terminal_width - 5] + "..."
  78. print(output)
  79. def print_stats(self, stats: dict) -> None:
  80. """
  81. Print formatted statistics.
  82. Args:
  83. stats: Dictionary of stat_name -> stat_value
  84. """
  85. self.update_width()
  86. print()
  87. for key, value in stats.items():
  88. # Format the key-value pair
  89. line = f" {key}: {value}"
  90. print(line)
  91. print()
  92. def _center_text(self, text: str) -> str:
  93. """Center text within terminal width."""
  94. available_width = self.terminal_width - 2 # Account for padding
  95. if len(text) >= available_width:
  96. return text[:available_width]
  97. left_pad = (available_width - len(text)) // 2
  98. right_pad = available_width - len(text) - left_pad
  99. return " " * left_pad + text + " " * right_pad
  100. def print_banner(self, text: str) -> None:
  101. """Print a decorative banner."""
  102. self.update_width()
  103. print()
  104. print("╔" + "═" * (self.terminal_width - 2) + "╗")
  105. print("║" + self._center_text(text) + "║")
  106. print("╚" + "═" * (self.terminal_width - 2) + "╝")
  107. print()
  108. def print_step(self, step_number: int, total_steps: int, description: str) -> None:
  109. """
  110. Print a numbered step.
  111. Args:
  112. step_number: Current step number
  113. total_steps: Total number of steps
  114. description: Description of the step
  115. """
  116. print(f"\n[{step_number}/{total_steps}] {description}")
  117. def print_file_info(self, filename: str, total_minutes: int, total_hours: float,
  118. chunk_count: int) -> None:
  119. """Print file information."""
  120. self.update_width()
  121. print()
  122. print(f" 📄 File: {filename}")
  123. print(f" ⏱ Duration: {total_minutes} minutes ({total_hours:.2f} hours)")
  124. print(f" 📦 Chunks: {chunk_count} (estimated based on 32k token limit)")
  125. print()
  126. def print_chunk_status(self, chunk_id: int, total_chunks: int,
  127. status: str, details: str = "") -> None:
  128. """
  129. Print status of a single chunk.
  130. Args:
  131. chunk_id: Chunk identifier (1-indexed)
  132. total_chunks: Total number of chunks
  133. status: Status emoji/icon
  134. details: Additional details
  135. """
  136. detail_str = f" - {details}" if details else ""
  137. print(f" Chunk {chunk_id:>3}/{total_chunks}: {status}{detail_str}")