Spaces:
Running
Running
""" | |
π¨ UI Components for Course Creator AI | |
Beautiful, modern Gradio components with custom styling and interactions. | |
""" | |
import gradio as gr | |
import json | |
from typing import Dict, List, Any, Optional, Tuple | |
from dataclasses import asdict | |
import logging | |
from ..types import Course, Lesson, Flashcard, Quiz, ImageAsset | |
logger = logging.getLogger(__name__) | |
class CourseGenerationForm: | |
"""Main course generation form component""" | |
def __init__(self): | |
self.current_course = None | |
self.generation_progress = 0 | |
def create_input_form(self) -> gr.Group: | |
"""Create the main input form for course generation""" | |
with gr.Group() as form: | |
gr.HTML(""" | |
<div class="header-section"> | |
<h1>π Course Creater AI</h1> | |
<p class="tagline">Transform any topic into an engaging course with AI</p> | |
</div> | |
""") | |
with gr.Row(): | |
with gr.Column(scale=2): | |
# Main topic input | |
topic_input = gr.Textbox( | |
label="π Course Topic", | |
placeholder="e.g., Introduction to Machine Learning, Python for Beginners, Digital Marketing Basics", | |
lines=2, | |
elem_id="topic-input" | |
) | |
# Course configuration | |
with gr.Row(): | |
difficulty_level = gr.Dropdown( | |
choices=["Beginner", "Intermediate", "Advanced"], | |
value="Intermediate", | |
label="π― Difficulty Level", | |
elem_id="difficulty-select" | |
) | |
duration = gr.Slider( | |
minimum=0.5, | |
maximum=8.0, | |
value=2.0, | |
step=0.5, | |
label="β±οΈ Duration (hours)", | |
elem_id="duration-slider" | |
) | |
with gr.Row(): | |
num_lessons = gr.Slider( | |
minimum=3, | |
maximum=12, | |
value=6, | |
step=1, | |
label="π Number of Lessons", | |
elem_id="lessons-slider" | |
) | |
target_audience = gr.Dropdown( | |
choices=["Students", "Professionals", "Hobbyists", "General Public"], | |
value="General Public", | |
label="π₯ Target Audience", | |
elem_id="audience-select" | |
) | |
with gr.Column(scale=1): | |
# Advanced options | |
gr.HTML("<h3>π§ Advanced Options</h3>") | |
include_images = gr.Checkbox( | |
value=True, | |
label="π¨ Generate Images", | |
elem_id="images-checkbox" | |
) | |
include_quizzes = gr.Checkbox( | |
value=True, | |
label="π― Include Quizzes", | |
elem_id="quizzes-checkbox" | |
) | |
include_flashcards = gr.Checkbox( | |
value=True, | |
label="π Create Flashcards", | |
elem_id="flashcards-checkbox" | |
) | |
content_style = gr.Dropdown( | |
choices=["Conversational", "Technical", "Academic", "Casual"], | |
value="Conversational", | |
label="βοΈ Content Style", | |
elem_id="style-select" | |
) | |
# Generation button | |
with gr.Row(): | |
generate_btn = gr.Button( | |
"π Generate Course", | |
variant="primary", | |
size="lg", | |
elem_id="generate-button" | |
) | |
clear_btn = gr.Button( | |
"ποΈ Clear", | |
variant="secondary", | |
elem_id="clear-button" | |
) | |
return form, { | |
"topic_input": topic_input, | |
"difficulty_level": difficulty_level, | |
"duration": duration, | |
"num_lessons": num_lessons, | |
"target_audience": target_audience, | |
"include_images": include_images, | |
"include_quizzes": include_quizzes, | |
"include_flashcards": include_flashcards, | |
"content_style": content_style, | |
"generate_btn": generate_btn, | |
"clear_btn": clear_btn | |
} | |
class ProgressTracker: | |
"""Real-time progress tracking component""" | |
def __init__(self): | |
self.current_step = 0 | |
self.total_steps = 6 | |
self.step_names = [ | |
"π Researching Topic", | |
"π Planning Course Structure", | |
"βοΈ Generating Content", | |
"π― Creating Assessments", | |
"π¨ Generating Images", | |
"π¦ Finalizing Course" | |
] | |
def create_progress_display(self) -> gr.Group: | |
"""Create progress tracking display""" | |
with gr.Group() as progress_group: | |
gr.HTML("<h3>π Generation Progress</h3>") | |
# Progress bar | |
progress_bar = gr.Progress() | |
# Current step indicator | |
current_step_display = gr.HTML( | |
"<div class='step-indicator'>Ready to generate course</div>", | |
elem_id="step-indicator" | |
) | |
# Detailed progress log | |
progress_log = gr.Textbox( | |
label="π Progress Log", | |
lines=8, | |
max_lines=15, | |
interactive=False, | |
elem_id="progress-log" | |
) | |
# Status indicators | |
with gr.Row(): | |
research_status = gr.HTML("β³ Research", elem_id="research-status") | |
planning_status = gr.HTML("β³ Planning", elem_id="planning-status") | |
content_status = gr.HTML("β³ Content", elem_id="content-status") | |
assessment_status = gr.HTML("β³ Assessment", elem_id="assessment-status") | |
images_status = gr.HTML("β³ Images", elem_id="images-status") | |
finalize_status = gr.HTML("β³ Finalize", elem_id="finalize-status") | |
return progress_group, { | |
"progress_bar": progress_bar, | |
"current_step_display": current_step_display, | |
"progress_log": progress_log, | |
"status_indicators": { | |
"research": research_status, | |
"planning": planning_status, | |
"content": content_status, | |
"assessment": assessment_status, | |
"images": images_status, | |
"finalize": finalize_status | |
} | |
} | |
def update_progress(self, step: int, message: str, log_entry: str = "") -> Tuple[str, str]: | |
"""Update progress display""" | |
self.current_step = step | |
progress_percent = (step / self.total_steps) * 100 | |
# Update step indicator | |
if step < len(self.step_names): | |
step_html = f""" | |
<div class='step-indicator active'> | |
<div class='step-icon'>{self.step_names[step].split()[0]}</div> | |
<div class='step-text'>{self.step_names[step]}</div> | |
<div class='step-message'>{message}</div> | |
</div> | |
""" | |
else: | |
step_html = "<div class='step-indicator complete'>β Course Generation Complete!</div>" | |
return step_html, log_entry | |
class CoursePreview: | |
"""Interactive course preview component""" | |
def __init__(self): | |
self.current_course = None | |
def create_preview_tabs(self) -> gr.Tabs: | |
"""Create tabbed course preview interface""" | |
with gr.Tabs() as preview_tabs: | |
# Course Overview Tab | |
with gr.Tab("π Course Overview", elem_id="overview-tab"): | |
course_overview = self._create_overview_section() | |
# Lessons Tab | |
with gr.Tab("π Lessons", elem_id="lessons-tab"): | |
lessons_section = self._create_lessons_section() | |
# Flashcards Tab | |
with gr.Tab("π Flashcards", elem_id="flashcards-tab"): | |
flashcards_section = self._create_flashcards_section() | |
# Quizzes Tab | |
with gr.Tab("π― Quizzes", elem_id="quizzes-tab"): | |
quizzes_section = self._create_quizzes_section() | |
# Images Tab | |
with gr.Tab("π¨ Images", elem_id="images-tab"): | |
images_section = self._create_images_section() | |
# Export Tab | |
with gr.Tab("π€ Export", elem_id="export-tab"): | |
export_section = self._create_export_section() | |
return preview_tabs, { | |
"course_overview": course_overview, | |
"lessons_section": lessons_section, | |
"flashcards_section": flashcards_section, | |
"quizzes_section": quizzes_section, | |
"images_section": images_section, | |
"export_section": export_section | |
} | |
def _create_overview_section(self) -> Dict[str, Any]: | |
"""Create course overview section""" | |
with gr.Group(): | |
# Course header | |
course_title = gr.HTML( | |
"<h2>Course will appear here after generation</h2>", | |
elem_id="course-title" | |
) | |
course_metadata = gr.HTML( | |
"<div class='course-metadata'>Generate a course to see details</div>", | |
elem_id="course-metadata" | |
) | |
# Course description | |
course_description = gr.Markdown( | |
"Course description will appear here...", | |
elem_id="course-description" | |
) | |
# Learning objectives | |
learning_objectives = gr.HTML( | |
"<div class='learning-objectives'>Learning objectives will appear here</div>", | |
elem_id="learning-objectives" | |
) | |
# Course structure | |
course_structure = gr.HTML( | |
"<div class='course-structure'>Course structure will appear here</div>", | |
elem_id="course-structure" | |
) | |
return { | |
"course_title": course_title, | |
"course_metadata": course_metadata, | |
"course_description": course_description, | |
"learning_objectives": learning_objectives, | |
"course_structure": course_structure | |
} | |
def _create_lessons_section(self) -> Dict[str, Any]: | |
"""Create lessons preview section""" | |
with gr.Group(): | |
# Lesson selector | |
lesson_selector = gr.Dropdown( | |
choices=[], | |
label="π Select Lesson", | |
elem_id="lesson-selector" | |
) | |
# Lesson content display | |
lesson_title = gr.HTML( | |
"<h3>Select a lesson to view content</h3>", | |
elem_id="lesson-title" | |
) | |
lesson_content = gr.Markdown( | |
"Lesson content will appear here...", | |
elem_id="lesson-content" | |
) | |
# Lesson navigation | |
with gr.Row(): | |
prev_lesson_btn = gr.Button( | |
"β¬ οΈ Previous", | |
elem_id="prev-lesson-btn" | |
) | |
next_lesson_btn = gr.Button( | |
"β‘οΈ Next", | |
elem_id="next-lesson-btn" | |
) | |
return { | |
"lesson_selector": lesson_selector, | |
"lesson_title": lesson_title, | |
"lesson_content": lesson_content, | |
"prev_lesson_btn": prev_lesson_btn, | |
"next_lesson_btn": next_lesson_btn | |
} | |
def _create_flashcards_section(self) -> Dict[str, Any]: | |
"""Create flashcards preview section""" | |
with gr.Group(): | |
# Flashcard display | |
flashcard_display = gr.HTML( | |
"<div class='flashcard-container'>Flashcards will appear here</div>", | |
elem_id="flashcard-display" | |
) | |
# Flashcard controls | |
with gr.Row(): | |
flip_card_btn = gr.Button( | |
"π Flip Card", | |
elem_id="flip-card-btn" | |
) | |
prev_card_btn = gr.Button( | |
"β¬ οΈ Previous", | |
elem_id="prev-card-btn" | |
) | |
next_card_btn = gr.Button( | |
"β‘οΈ Next", | |
elem_id="next-card-btn" | |
) | |
# Flashcard progress | |
flashcard_progress = gr.HTML( | |
"<div class='flashcard-progress'>Card 1 of 0</div>", | |
elem_id="flashcard-progress" | |
) | |
return { | |
"flashcard_display": flashcard_display, | |
"flip_card_btn": flip_card_btn, | |
"prev_card_btn": prev_card_btn, | |
"next_card_btn": next_card_btn, | |
"flashcard_progress": flashcard_progress | |
} | |
def _create_quizzes_section(self) -> Dict[str, Any]: | |
"""Create quizzes preview section""" | |
with gr.Group(): | |
# Quiz selector | |
quiz_selector = gr.Dropdown( | |
choices=[], | |
label="π― Select Quiz", | |
elem_id="quiz-selector" | |
) | |
# Quiz display | |
quiz_content = gr.HTML( | |
"<div class='quiz-container'>Select a quiz to begin</div>", | |
elem_id="quiz-content" | |
) | |
# Quiz controls | |
with gr.Row(): | |
start_quiz_btn = gr.Button( | |
"βΆοΈ Start Quiz", | |
variant="primary", | |
elem_id="start-quiz-btn" | |
) | |
reset_quiz_btn = gr.Button( | |
"π Reset", | |
elem_id="reset-quiz-btn" | |
) | |
return { | |
"quiz_selector": quiz_selector, | |
"quiz_content": quiz_content, | |
"start_quiz_btn": start_quiz_btn, | |
"reset_quiz_btn": reset_quiz_btn | |
} | |
def _create_images_section(self) -> Dict[str, Any]: | |
"""Create images gallery section""" | |
with gr.Group(): | |
# Image gallery | |
image_gallery = gr.Gallery( | |
label="π¨ Generated Images", | |
show_label=True, | |
elem_id="image-gallery", | |
columns=3, | |
rows=2, | |
height="auto" | |
) | |
# Image details | |
image_details = gr.HTML( | |
"<div class='image-details'>Select an image to view details</div>", | |
elem_id="image-details" | |
) | |
return { | |
"image_gallery": image_gallery, | |
"image_details": image_details | |
} | |
def _create_export_section(self) -> Dict[str, Any]: | |
"""Create export options section""" | |
with gr.Group(): | |
gr.HTML("<h3>π€ Export Your Course</h3>") | |
# Export format selection | |
with gr.Row(): | |
export_pdf = gr.Checkbox( | |
value=True, | |
label="π PDF Course Book" | |
) | |
export_json = gr.Checkbox( | |
value=True, | |
label="π JSON Data" | |
) | |
export_anki = gr.Checkbox( | |
value=False, | |
label="π Anki Deck" | |
) | |
with gr.Row(): | |
export_notion = gr.Checkbox( | |
value=False, | |
label="π Notion Pages" | |
) | |
export_github = gr.Checkbox( | |
value=False, | |
label="π GitHub Repository" | |
) | |
export_drive = gr.Checkbox( | |
value=False, | |
label="βοΈ Google Drive" | |
) | |
# Export button | |
export_btn = gr.Button( | |
"π¦ Export Course", | |
variant="primary", | |
size="lg", | |
elem_id="export-btn" | |
) | |
# Download links | |
download_links = gr.HTML( | |
"<div class='download-links'>Export files will appear here</div>", | |
elem_id="download-links" | |
) | |
return { | |
"export_options": { | |
"pdf": export_pdf, | |
"json": export_json, | |
"anki": export_anki, | |
"notion": export_notion, | |
"github": export_github, | |
"drive": export_drive | |
}, | |
"export_btn": export_btn, | |
"download_links": download_links | |
} | |
class FlashcardViewer: | |
"""Interactive flashcard viewer component""" | |
def __init__(self): | |
self.current_card_index = 0 | |
self.show_back = False | |
self.flashcards = [] | |
def create_flashcard_interface(self, flashcards: List[Flashcard]) -> gr.Group: | |
"""Create interactive flashcard viewer""" | |
self.flashcards = flashcards | |
with gr.Group() as flashcard_group: | |
gr.HTML("<h3>π Interactive Flashcards</h3>") | |
if not flashcards: | |
gr.HTML("<p>No flashcards available</p>") | |
return flashcard_group, {} | |
# Card counter | |
card_counter = gr.HTML( | |
f"<div class='card-counter'>Card 1 of {len(flashcards)}</div>", | |
elem_id="card-counter" | |
) | |
# Flashcard display | |
with gr.Row(): | |
with gr.Column(scale=1): | |
# Card content | |
card_display = gr.HTML( | |
self._format_flashcard_html(flashcards[0], show_back=False), | |
elem_id="flashcard-display" | |
) | |
# Flip button | |
flip_btn = gr.Button( | |
"π Flip Card", | |
variant="secondary", | |
elem_id="flip-button" | |
) | |
# Navigation buttons | |
with gr.Row(): | |
prev_btn = gr.Button( | |
"β¬ οΈ Previous", | |
variant="secondary", | |
interactive=False, | |
elem_id="prev-button" | |
) | |
next_btn = gr.Button( | |
"β‘οΈ Next", | |
variant="secondary", | |
interactive=len(flashcards) > 1, | |
elem_id="next-button" | |
) | |
return flashcard_group, { | |
"card_counter": card_counter, | |
"card_display": card_display, | |
"flip_btn": flip_btn, | |
"prev_btn": prev_btn, | |
"next_btn": next_btn | |
} | |
def _format_flashcard_html(self, flashcard: Flashcard, show_back: bool = False) -> str: | |
"""Format flashcard as HTML""" | |
if show_back: | |
content = f""" | |
<div class="flashcard flashcard-back"> | |
<div class="flashcard-header">Answer</div> | |
<div class="flashcard-content">{flashcard.back}</div> | |
<div class="flashcard-footer"> | |
<span class="difficulty">{flashcard.difficulty}</span> | |
<span class="tags">{', '.join(flashcard.tags) if flashcard.tags else ''}</span> | |
</div> | |
</div> | |
""" | |
else: | |
content = f""" | |
<div class="flashcard flashcard-front"> | |
<div class="flashcard-header">Question</div> | |
<div class="flashcard-content">{flashcard.front}</div> | |
<div class="flashcard-footer"> | |
<span class="difficulty">{flashcard.difficulty}</span> | |
</div> | |
</div> | |
""" | |
return content | |
class UIHelpers: | |
"""Helper functions for UI components""" | |
def format_course_metadata(course: Course) -> str: | |
"""Format course metadata for display""" | |
metadata_html = f""" | |
<div class="course-metadata"> | |
<div class="metadata-item"> | |
<span class="label">π― Difficulty:</span> | |
<span class="value">{course.difficulty_level}</span> | |
</div> | |
<div class="metadata-item"> | |
<span class="label">β±οΈ Duration:</span> | |
<span class="value">{course.estimated_duration} hours</span> | |
</div> | |
<div class="metadata-item"> | |
<span class="label">π Lessons:</span> | |
<span class="value">{len(course.lessons)}</span> | |
</div> | |
<div class="metadata-item"> | |
<span class="label">π₯ Audience:</span> | |
<span class="value">{course.target_audience}</span> | |
</div> | |
<div class="metadata-item"> | |
<span class="label">π·οΈ Tags:</span> | |
<span class="value">{', '.join(course.tags)}</span> | |
</div> | |
</div> | |
""" | |
return metadata_html | |
def format_learning_objectives(objectives: List[str]) -> str: | |
"""Format learning objectives for display""" | |
objectives_html = """ | |
<div class="learning-objectives"> | |
<h4>π― Learning Objectives</h4> | |
<ul> | |
""" | |
for objective in objectives: | |
objectives_html += f"<li>{objective}</li>" | |
objectives_html += """ | |
</ul> | |
</div> | |
""" | |
return objectives_html | |
def format_flashcard(flashcard: Flashcard, show_back: bool = False) -> str: | |
"""Format flashcard for display""" | |
card_class = "flashcard flipped" if show_back else "flashcard" | |
content = flashcard.back if show_back else flashcard.front | |
flashcard_html = f""" | |
<div class="{card_class}"> | |
<div class="flashcard-content"> | |
<div class="flashcard-category">{flashcard.category}</div> | |
<div class="flashcard-text">{content}</div> | |
<div class="flashcard-difficulty">Difficulty: {flashcard.difficulty}/5</div> | |
</div> | |
</div> | |
""" | |
return flashcard_html | |
def create_error_display(error_message: str) -> str: | |
"""Create error display HTML""" | |
error_html = f""" | |
<div class="error-display"> | |
<div class="error-icon">β</div> | |
<div class="error-message">{error_message}</div> | |
<div class="error-suggestion">Please try again or contact support if the issue persists.</div> | |
</div> | |
""" | |
return error_html | |
def create_success_display(success_message: str) -> str: | |
"""Create success display HTML""" | |
success_html = f""" | |
<div class="success-display"> | |
<div class="success-icon">β </div> | |
<div class="success-message">{success_message}</div> | |
</div> | |
""" | |
return success_html |