|
""" |
|
π¨ 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): |
|
|
|
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" |
|
) |
|
|
|
|
|
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): |
|
|
|
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" |
|
) |
|
|
|
|
|
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 = gr.Progress() |
|
|
|
|
|
current_step_display = gr.HTML( |
|
"<div class='step-indicator'>Ready to generate course</div>", |
|
elem_id="step-indicator" |
|
) |
|
|
|
|
|
progress_log = gr.Textbox( |
|
label="π Progress Log", |
|
lines=8, |
|
max_lines=15, |
|
interactive=False, |
|
elem_id="progress-log" |
|
) |
|
|
|
|
|
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 |
|
|
|
|
|
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: |
|
|
|
with gr.Tab("π Course Overview", elem_id="overview-tab"): |
|
course_overview = self._create_overview_section() |
|
|
|
|
|
with gr.Tab("π Lessons", elem_id="lessons-tab"): |
|
lessons_section = self._create_lessons_section() |
|
|
|
|
|
with gr.Tab("π Flashcards", elem_id="flashcards-tab"): |
|
flashcards_section = self._create_flashcards_section() |
|
|
|
|
|
with gr.Tab("π― Quizzes", elem_id="quizzes-tab"): |
|
quizzes_section = self._create_quizzes_section() |
|
|
|
|
|
with gr.Tab("π¨ Images", elem_id="images-tab"): |
|
images_section = self._create_images_section() |
|
|
|
|
|
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_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 = gr.Markdown( |
|
"Course description will appear here...", |
|
elem_id="course-description" |
|
) |
|
|
|
|
|
learning_objectives = gr.HTML( |
|
"<div class='learning-objectives'>Learning objectives will appear here</div>", |
|
elem_id="learning-objectives" |
|
) |
|
|
|
|
|
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 = gr.Dropdown( |
|
choices=[], |
|
label="π Select Lesson", |
|
elem_id="lesson-selector" |
|
) |
|
|
|
|
|
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" |
|
) |
|
|
|
|
|
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 = gr.HTML( |
|
"<div class='flashcard-container'>Flashcards will appear here</div>", |
|
elem_id="flashcard-display" |
|
) |
|
|
|
|
|
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 = 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 = gr.Dropdown( |
|
choices=[], |
|
label="π― Select Quiz", |
|
elem_id="quiz-selector" |
|
) |
|
|
|
|
|
quiz_content = gr.HTML( |
|
"<div class='quiz-container'>Select a quiz to begin</div>", |
|
elem_id="quiz-content" |
|
) |
|
|
|
|
|
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 = gr.Gallery( |
|
label="π¨ Generated Images", |
|
show_label=True, |
|
elem_id="image-gallery", |
|
columns=3, |
|
rows=2, |
|
height="auto" |
|
) |
|
|
|
|
|
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>") |
|
|
|
|
|
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_btn = gr.Button( |
|
"π¦ Export Course", |
|
variant="primary", |
|
size="lg", |
|
elem_id="export-btn" |
|
) |
|
|
|
|
|
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 = gr.HTML( |
|
f"<div class='card-counter'>Card 1 of {len(flashcards)}</div>", |
|
elem_id="card-counter" |
|
) |
|
|
|
|
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
|
|
card_display = gr.HTML( |
|
self._format_flashcard_html(flashcards[0], show_back=False), |
|
elem_id="flashcard-display" |
|
) |
|
|
|
|
|
flip_btn = gr.Button( |
|
"π Flip Card", |
|
variant="secondary", |
|
elem_id="flip-button" |
|
) |
|
|
|
|
|
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""" |
|
|
|
@staticmethod |
|
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 |
|
|
|
@staticmethod |
|
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 |
|
|
|
@staticmethod |
|
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 |
|
|
|
@staticmethod |
|
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 |
|
|
|
@staticmethod |
|
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 |