""" Animation Report Display Backend Captures frames from pipeline stages and generates an interactive HTML report showing before/after states for each transformative stage. """ import time from dataclasses import dataclass, field from datetime import datetime from pathlib import Path from typing import Any from engine.display.streaming import compute_diff @dataclass class CapturedFrame: """A captured frame with metadata.""" stage: str buffer: list[str] timestamp: float frame_number: int diff_from_previous: dict[str, Any] | None = None @dataclass class StageCapture: """Captures frames for a single pipeline stage.""" name: str frames: list[CapturedFrame] = field(default_factory=list) start_time: float = field(default_factory=time.time) end_time: float = 0.0 def add_frame( self, buffer: list[str], frame_number: int, previous_buffer: list[str] | None = None, ) -> None: """Add a captured frame.""" timestamp = time.time() diff = None if previous_buffer is not None: diff_data = compute_diff(previous_buffer, buffer) diff = { "changed_lines": len(diff_data.changed_lines), "total_lines": len(buffer), "width": diff_data.width, "height": diff_data.height, } frame = CapturedFrame( stage=self.name, buffer=list(buffer), timestamp=timestamp, frame_number=frame_number, diff_from_previous=diff, ) self.frames.append(frame) def finish(self) -> None: """Mark capture as finished.""" self.end_time = time.time() class AnimationReportDisplay: """ Display backend that captures frames for animation report generation. Instead of rendering to terminal, this display captures the buffer at each stage and stores it for later HTML report generation. """ width: int = 80 height: int = 24 def __init__(self, output_dir: str = "./reports"): """ Initialize the animation report display. Args: output_dir: Directory where reports will be saved """ self.output_dir = Path(output_dir) self.output_dir.mkdir(parents=True, exist_ok=True) self._stages: dict[str, StageCapture] = {} self._current_stage: str = "" self._previous_buffer: list[str] | None = None self._frame_number: int = 0 self._total_frames: int = 0 self._start_time: float = 0.0 def init(self, width: int, height: int, reuse: bool = False) -> None: """Initialize display with dimensions.""" self.width = width self.height = height self._start_time = time.time() def show(self, buffer: list[str], border: bool = False) -> None: """ Capture a frame for the current stage. Args: buffer: The frame buffer to capture border: Border flag (ignored) """ if not self._current_stage: # If no stage is set, use a default name self._current_stage = "final" if self._current_stage not in self._stages: self._stages[self._current_stage] = StageCapture(self._current_stage) stage = self._stages[self._current_stage] stage.add_frame(buffer, self._frame_number, self._previous_buffer) self._previous_buffer = list(buffer) self._frame_number += 1 self._total_frames += 1 def start_stage(self, stage_name: str) -> None: """ Start capturing frames for a new stage. Args: stage_name: Name of the stage (e.g., "noise", "fade", "firehose") """ if self._current_stage and self._current_stage in self._stages: # Finish previous stage self._stages[self._current_stage].finish() self._current_stage = stage_name self._previous_buffer = None # Reset for new stage def clear(self) -> None: """Clear the display (no-op for report display).""" pass def cleanup(self) -> None: """Cleanup resources.""" # Finish current stage if self._current_stage and self._current_stage in self._stages: self._stages[self._current_stage].finish() def get_dimensions(self) -> tuple[int, int]: """Get current dimensions.""" return (self.width, self.height) def get_stages(self) -> dict[str, StageCapture]: """Get all captured stages.""" return self._stages def generate_report(self, title: str = "Animation Report") -> Path: """ Generate an HTML report with captured frames and animations. Args: title: Title of the report Returns: Path to the generated HTML file """ report_path = self.output_dir / f"animation_report_{int(time.time())}.html" html_content = self._build_html(title) report_path.write_text(html_content) return report_path def _build_html(self, title: str) -> str: """Build the HTML content for the report.""" # Collect all frames across stages all_frames = [] for stage_name, stage in self._stages.items(): for frame in stage.frames: all_frames.append(frame) # Sort frames by timestamp all_frames.sort(key=lambda f: f.timestamp) # Build stage sections stages_html = "" for stage_name, stage in self._stages.items(): stages_html += self._build_stage_section(stage_name, stage) # Build full HTML html = f"""