""" 📊 Real-time Progress Tracker for CourseCrafter AI Advanced progress tracking with visual feedback and status updates. """ import gradio as gr import asyncio import time from typing import Dict, List, Any, Optional, Callable, Generator from dataclasses import dataclass from enum import Enum import logging import threading from datetime import datetime logger = logging.getLogger(__name__) class ProgressStatus(Enum): """Progress status enumeration""" PENDING = "pending" ACTIVE = "active" COMPLETE = "complete" ERROR = "error" SKIPPED = "skipped" @dataclass class ProgressStep: """Individual progress step""" id: str name: str description: str emoji: str status: ProgressStatus = ProgressStatus.PENDING progress: float = 0.0 start_time: Optional[datetime] = None end_time: Optional[datetime] = None error_message: Optional[str] = None substeps: List['ProgressStep'] = None def __post_init__(self): if self.substeps is None: self.substeps = [] class RealTimeProgressTracker: """Real-time progress tracking with visual updates""" def __init__(self): self.steps: List[ProgressStep] = [] self.current_step_index = 0 self.overall_progress = 0.0 self.is_running = False self.start_time: Optional[datetime] = None self.end_time: Optional[datetime] = None self.log_entries: List[str] = [] self.update_callbacks: List[Callable] = [] # Initialize default steps self._initialize_default_steps() def _initialize_default_steps(self): """Initialize the default course generation steps""" self.steps = [ ProgressStep( id="research", name="Research & Analysis", description="Gathering information and analyzing the topic", emoji="🔍", substeps=[ ProgressStep("research_web", "Web Search", "Searching for relevant information", "🌐"), ProgressStep("research_academic", "Academic Sources", "Finding scholarly content", "📚"), ProgressStep("research_analysis", "Content Analysis", "Analyzing gathered information", "🧠"), ] ), ProgressStep( id="planning", name="Course Planning", description="Creating the course structure and outline", emoji="📋", substeps=[ ProgressStep("plan_structure", "Course Structure", "Designing lesson flow", "🏗️"), ProgressStep("plan_objectives", "Learning Objectives", "Defining learning goals", "🎯"), ProgressStep("plan_assessment", "Assessment Strategy", "Planning quizzes and activities", "📝"), ] ), ProgressStep( id="content", name="Content Generation", description="Creating engaging lesson content", emoji="✍️", substeps=[ ProgressStep("content_lessons", "Lesson Content", "Writing lesson materials", "📖"), ProgressStep("content_examples", "Examples & Exercises", "Creating practical examples", "💡"), ProgressStep("content_review", "Content Review", "Reviewing and refining content", "🔍"), ] ), ProgressStep( id="assessments", name="Assessment Creation", description="Generating quizzes and flashcards", emoji="🎯", substeps=[ ProgressStep("assess_flashcards", "Flashcards", "Creating study flashcards", "🃏"), ProgressStep("assess_quizzes", "Quizzes", "Generating quiz questions", "❓"), ProgressStep("assess_validation", "Validation", "Validating assessment quality", "✅"), ] ), ProgressStep( id="images", name="Visual Content", description="Generating images and diagrams", emoji="🎨", substeps=[ ProgressStep("images_cover", "Course Cover", "Creating course cover image", "🖼️"), ProgressStep("images_lessons", "Lesson Images", "Generating lesson illustrations", "🎭"), ProgressStep("images_diagrams", "Diagrams", "Creating concept diagrams", "📊"), ] ), ProgressStep( id="finalize", name="Finalization", description="Packaging and finalizing the course", emoji="📦", substeps=[ ProgressStep("final_package", "Course Package", "Assembling course materials", "📁"), ProgressStep("final_metadata", "Metadata", "Adding course metadata", "🏷️"), ProgressStep("final_validation", "Final Validation", "Final quality check", "🔍"), ] ) ] def start_generation(self) -> None: """Start the course generation process""" self.is_running = True self.start_time = datetime.now() self.current_step_index = 0 self.overall_progress = 0.0 self.log_entries = [] self.add_log_entry("🚀 Starting course generation...") self.add_log_entry(f"⏰ Started at {self.start_time.strftime('%H:%M:%S')}") def update_step_progress(self, step_id: str, progress: float, message: str = "") -> None: """Update progress for a specific step""" step = self._find_step_by_id(step_id) if not step: logger.warning(f"Step {step_id} not found") return step.progress = min(100.0, max(0.0, progress)) if step.status == ProgressStatus.PENDING and progress > 0: step.status = ProgressStatus.ACTIVE step.start_time = datetime.now() self.add_log_entry(f"{step.emoji} Started: {step.name}") if progress >= 100.0: step.status = ProgressStatus.COMPLETE step.end_time = datetime.now() duration = (step.end_time - step.start_time).total_seconds() if step.start_time else 0 self.add_log_entry(f"✅ Completed: {step.name} ({duration:.1f}s)") if message: self.add_log_entry(f" {message}") self._update_overall_progress() self._notify_callbacks() def update_substep_progress(self, step_id: str, substep_id: str, progress: float, message: str = "") -> None: """Update progress for a substep""" step = self._find_step_by_id(step_id) if not step: return substep = self._find_substep_by_id(step, substep_id) if not substep: return substep.progress = min(100.0, max(0.0, progress)) if substep.status == ProgressStatus.PENDING and progress > 0: substep.status = ProgressStatus.ACTIVE substep.start_time = datetime.now() self.add_log_entry(f" {substep.emoji} {substep.name}...") if progress >= 100.0: substep.status = ProgressStatus.COMPLETE substep.end_time = datetime.now() if message: self.add_log_entry(f" {message}") # Update parent step progress based on substeps self._update_step_from_substeps(step) self._notify_callbacks() def mark_step_error(self, step_id: str, error_message: str) -> None: """Mark a step as having an error""" step = self._find_step_by_id(step_id) if not step: return step.status = ProgressStatus.ERROR step.error_message = error_message step.end_time = datetime.now() self.add_log_entry(f"❌ Error in {step.name}: {error_message}") self._notify_callbacks() def skip_step(self, step_id: str, reason: str = "") -> None: """Skip a step""" step = self._find_step_by_id(step_id) if not step: return step.status = ProgressStatus.SKIPPED step.progress = 100.0 step.end_time = datetime.now() skip_msg = f"⏭️ Skipped: {step.name}" if reason: skip_msg += f" ({reason})" self.add_log_entry(skip_msg) self._update_overall_progress() self._notify_callbacks() def complete_generation(self, success: bool = True) -> None: """Complete the course generation process""" self.is_running = False self.end_time = datetime.now() if success: self.overall_progress = 100.0 total_time = (self.end_time - self.start_time).total_seconds() if self.start_time else 0 self.add_log_entry(f"🎉 Course generation completed successfully!") self.add_log_entry(f"⏱️ Total time: {total_time:.1f} seconds") else: self.add_log_entry("❌ Course generation failed") self._notify_callbacks() def add_log_entry(self, message: str) -> None: """Add an entry to the progress log""" timestamp = datetime.now().strftime("%H:%M:%S") log_entry = f"[{timestamp}] {message}" self.log_entries.append(log_entry) # Keep only last 100 entries if len(self.log_entries) > 100: self.log_entries = self.log_entries[-100:] def get_progress_display(self) -> Dict[str, Any]: """Get current progress display data""" return { "overall_progress": self.overall_progress, "current_step": self.get_current_step_display(), "step_indicators": self.get_step_indicators(), "progress_log": "\n".join(self.log_entries[-20:]), # Last 20 entries "is_running": self.is_running, "elapsed_time": self.get_elapsed_time() } def get_current_step_display(self) -> str: """Get current step display HTML""" if not self.is_running: if self.overall_progress >= 100: return """