SkillSync / app.py
yekkala's picture
Update app.py
d44ea94 verified
"""
SkillSync: Generative AI Mastery Course Platform
Coursera-inspired design for Hugging Face Spaces
"""
import gradio as gr
import json
import os
from datetime import datetime
# Import configuration
from config import Config
# Import utilities
from utils.progress_manager import ProgressManager
from utils.quiz_handler import QuizHandler
# Load course data
def load_course_data():
"""Load course data from JSON file"""
try:
with open('data/course_data.json', 'r') as f:
return json.load(f)
except Exception as e:
print(f"Error loading course data: {e}")
return None
# Initialize managers
progress_manager = ProgressManager()
quiz_handler = QuizHandler()
COURSE_DATA = load_course_data()
# Custom CSS
CUSTOM_CSS = """
/* Main Styles */
.gradio-container {
max-width: 1400px !important;
margin: 0 auto !important;
padding: 0 !important;
}
/* Hero Section */
.hero-section {
background: linear-gradient(135deg, #0056D2 0%, #0044A8 100%);
color: white;
padding: 48px 32px;
margin-bottom: 24px;
border-radius: 16px;
}
/* Module Cards */
.module-card {
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
margin-bottom: 16px;
overflow: hidden;
border: 1px solid #E0E0E0;
transition: all 0.3s ease;
}
.module-card:hover {
box-shadow: 0 4px 16px rgba(0,0,0,0.12);
transform: translateY(-2px);
}
/* Progress Bar */
.progress-bar {
background: #E0E0E0;
border-radius: 8px;
height: 8px;
overflow: hidden;
}
.progress-fill {
background: linear-gradient(90deg, #0056D2 0%, #0044A8 100%);
height: 100%;
border-radius: 8px;
transition: width 0.5s ease;
}
/* Stats Cards */
.stat-card {
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 20px;
text-align: center;
border: 1px solid rgba(255, 255, 255, 0.2);
}
/* Lesson Items */
.lesson-item {
padding: 16px 24px;
border-bottom: 1px solid #F5F5F5;
display: flex;
align-items: center;
gap: 12px;
}
.lesson-item:hover {
background: #FAFAFA;
}
.lesson-icon {
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
}
.video-icon { background: #E3F2FD; color: #1565C0; }
.reading-icon { background: #E8F5E9; color: #2E7D32; }
.quiz-icon { background: #FFF3E0; color: #EF6C00; }
.assignment-icon { background: #FCE4EC; color: #C2185B; }
/* Quiz Styles */
.quiz-option {
padding: 16px 20px;
border: 2px solid #E0E0E0;
border-radius: 8px;
margin-bottom: 12px;
cursor: pointer;
transition: all 0.2s ease;
}
.quiz-option:hover {
border-color: #0056D2;
background: #E8F0FE;
}
/* Buttons */
.primary-btn {
background: #0056D2 !important;
color: white !important;
border: none !important;
border-radius: 8px !important;
padding: 12px 24px !important;
font-weight: 600 !important;
}
.primary-btn:hover {
background: #0044A8 !important;
}
/* Instructor Card */
.instructor-card {
display: flex;
align-items: center;
gap: 16px;
padding: 16px;
background: #F5F5F5;
border-radius: 8px;
margin-bottom: 12px;
}
.instructor-avatar {
width: 56px;
height: 56px;
border-radius: 50%;
background: linear-gradient(135deg, #0056D2 0%, #0044A8 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 700;
font-size: 18px;
}
/* Tags */
.tag {
display: inline-block;
padding: 4px 12px;
border-radius: 16px;
font-size: 12px;
font-weight: 600;
margin-right: 8px;
}
.tag-beginner { background: #E8F5E9; color: #2E7D32; }
.tag-intermediate { background: #FFF3E0; color: #EF6C00; }
.tag-advanced { background: #FCE4EC; color: #C2185B; }
/* Accordion */
.accordion-content {
padding: 0 24px 24px;
}
/* Responsive */
@media (max-width: 768px) {
.hero-section {
padding: 32px 16px;
}
.stat-card {
padding: 16px;
}
}
"""
def create_app():
"""Create the main Gradio application"""
with gr.Blocks(
theme=gr.themes.Soft(
primary_hue=Config.PRIMARY_HUE,
secondary_hue=Config.SECONDARY_HUE
),
css=CUSTOM_CSS,
title=Config.APP_TITLE
) as app:
# Header
gr.HTML("""
<div class="hero-section">
<h1 style="font-size: 2.5rem; font-weight: 700; margin-bottom: 16px;">πŸŽ“ SkillSync: Generative AI Mastery</h1>
<p style="font-size: 1.25rem; opacity: 0.95; margin-bottom: 32px;">From Fundamentals to Production Deployment</p>
<div style="display: flex; gap: 16px; flex-wrap: wrap;">
<div class="stat-card">
<div style="font-size: 2rem; font-weight: 700;">5</div>
<div style="font-size: 14px; opacity: 0.9;">Modules</div>
</div>
<div class="stat-card">
<div style="font-size: 2rem; font-weight: 700;">25+</div>
<div style="font-size: 14px; opacity: 0.9;">Hours</div>
</div>
<div class="stat-card">
<div style="font-size: 2rem; font-weight: 700;">5</div>
<div style="font-size: 14px; opacity: 0.9;">Quizzes</div>
</div>
<div class="stat-card">
<div style="font-size: 2rem; font-weight: 700;">6</div>
<div style="font-size: 14px; opacity: 0.9;">Projects</div>
</div>
</div>
</div>
""")
# Progress Display
with gr.Row():
with gr.Column(scale=1):
progress_display = gr.HTML(value=update_progress_display())
# Main Tabs
with gr.Tabs():
# Course Overview Tab
with gr.TabItem("πŸ“š Course Overview"):
if COURSE_DATA:
create_course_overview()
# Module Tabs
if COURSE_DATA:
for module in COURSE_DATA['modules']:
with gr.TabItem(f"Module {module['id']}: {module['title'][:25]}..."):
create_module_tab(module)
# Capstone Tab
with gr.TabItem("πŸ† Capstone Project"):
create_capstone_tab()
# Resources Tab
with gr.TabItem("πŸ“– Resources"):
create_resources_tab()
# Footer
gr.HTML("""
<div style="text-align: center; padding: 32px 16px; border-top: 1px solid #E0E0E0; margin-top: 32px;">
<p style="font-weight: 600; color: #1F1F1F;">Β© 2025 SkillSync</p>
<p style="color: #757575; font-size: 14px; margin-top: 8px;">Master the future of AI with our comprehensive Generative AI course.</p>
</div>
""")
return app
def update_progress_display():
"""Update the progress display HTML"""
if not COURSE_DATA:
return ""
total_lessons = sum(len(m['lessons']) for m in COURSE_DATA['modules'])
completed = progress_manager.get_total_completed()
percentage = int((completed / total_lessons) * 100) if total_lessons > 0 else 0
quizzes_passed = progress_manager.get_quizzes_passed()
return f"""
<div style="background: white; border-radius: 12px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); margin-bottom: 24px;">
<h3 style="margin-bottom: 16px; color: #1F1F1F;">πŸ“Š Your Progress</h3>
<div style="display: flex; gap: 24px; flex-wrap: wrap;">
<div style="flex: 1; min-width: 150px;">
<div style="font-size: 32px; font-weight: 700; color: #0056D2;">{percentage}%</div>
<div style="color: #757575; font-size: 14px;">Overall Completion</div>
</div>
<div style="flex: 1; min-width: 150px;">
<div style="font-size: 32px; font-weight: 700; color: #0056D2;">{completed}/{total_lessons}</div>
<div style="color: #757575; font-size: 14px;">Lessons Completed</div>
</div>
<div style="flex: 1; min-width: 150px;">
<div style="font-size: 32px; font-weight: 700; color: #0056D2;">{quizzes_passed}/5</div>
<div style="color: #757575; font-size: 14px;">Quizzes Passed</div>
</div>
</div>
<div style="margin-top: 16px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
<span style="font-size: 14px; color: #757575;">Course Progress</span>
<span style="font-size: 14px; font-weight: 600; color: #0056D2;">{percentage}%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" style="width: {percentage}%;"></div>
</div>
</div>
</div>
"""
def create_course_overview():
"""Create the course overview section"""
course = COURSE_DATA.get('course', {})
gr.HTML(f"""
<div style="background: white; border-radius: 12px; padding: 32px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); margin-bottom: 24px;">
<h2 style="margin-bottom: 16px; color: #1F1F1F;">{course.get('title', 'Generative AI Mastery')}</h2>
<p style="color: #757575; font-size: 18px; margin-bottom: 24px;">{course.get('subtitle', '')}</p>
<p style="color: #1F1F1F; line-height: 1.8;">{course.get('description', '')}</p>
<div style="display: flex; gap: 16px; margin: 24px 0; flex-wrap: wrap;">
<span class="tag tag-intermediate">{course.get('level', 'Intermediate')}</span>
<span class="tag tag-beginner">{course.get('duration', '25+ hours')}</span>
<span class="tag tag-beginner">Certificate Available</span>
</div>
</div>
""")
# What You'll Learn
gr.HTML("""
<div style="background: white; border-radius: 12px; padding: 32px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); margin-bottom: 24px;">
<h3 style="margin-bottom: 16px; color: #1F1F1F;">🎯 What You'll Learn</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 16px;">
""")
for skill in course.get('skills', [])[:8]:
gr.HTML(f"""
<div style="display: flex; align-items: center; gap: 12px;">
<div style="width: 24px; height: 24px; background: #E8F5E9; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: #2E7D32;">βœ“</div>
<span style="color: #1F1F1F;">{skill}</span>
</div>
""")
gr.HTML("</div></div>")
# Instructors
gr.HTML("""
<div style="background: white; border-radius: 12px; padding: 32px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); margin-bottom: 24px;">
<h3 style="margin-bottom: 16px; color: #1F1F1F;">πŸ‘¨β€πŸ« Instructors</h3>
""")
for instructor in course.get('instructors', []):
gr.HTML(f"""
<div class="instructor-card">
<div class="instructor-avatar">{instructor.get('image', 'AI')}</div>
<div>
<div style="font-weight: 600; color: #1F1F1F;">{instructor.get('name', 'Instructor')}</div>
<div style="color: #757575; font-size: 14px;">{instructor.get('title', '')}</div>
<div style="color: #757575; font-size: 12px; margin-top: 4px;">⭐ {instructor.get('rating', 4.8)} β€’ {instructor.get('students', 0):,} students</div>
</div>
</div>
""")
gr.HTML("</div>")
def create_module_tab(module):
"""Create a module tab with all content"""
# Calculate progress
completed_lessons = progress_manager.get_completed_lessons(module['id'])
total_lessons = len(module['lessons'])
progress_pct = int((len(completed_lessons) / total_lessons) * 100) if total_lessons > 0 else 0
# Module Header
gr.HTML(f"""
<div style="background: white; border-radius: 12px; padding: 32px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); margin-bottom: 24px;">
<div style="display: flex; align-items: center; gap: 16px; margin-bottom: 16px;">
<div style="width: 56px; height: 56px; background: linear-gradient(135deg, #0056D2 0%, #0044A8 100%); border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-size: 24px; font-weight: 700;">{module['id']}</div>
<div>
<h2 style="margin: 0; color: #1F1F1F;">{module['title']}</h2>
<p style="color: #757575; margin: 4px 0 0 0;">{module.get('subtitle', '')}</p>
</div>
</div>
<p style="color: #1F1F1F; line-height: 1.8; margin-bottom: 24px;">{module['description']}</p>
<div style="display: flex; gap: 24px; flex-wrap: wrap; margin-bottom: 16px;">
<div style="display: flex; align-items: center; gap: 8px;">
<span style="color: #757575;">⏱️</span>
<span style="color: #1F1F1F;">{module.get('duration', '5 hours')}</span>
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<span style="color: #757575;">πŸ“š</span>
<span style="color: #1F1F1F;">{total_lessons} lessons</span>
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<span style="color: #757575;">πŸ“Š</span>
<span style="color: #1F1F1F;">Level: {module.get('level', 'Intermediate')}</span>
</div>
</div>
<div>
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
<span style="font-size: 14px; color: #757575;">Progress: {len(completed_lessons)}/{total_lessons} lessons</span>
<span style="font-size: 14px; font-weight: 600; color: #0056D2;">{progress_pct}%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" style="width: {progress_pct}%;"></div>
</div>
</div>
</div>
""")
# Learning Objectives
with gr.Accordion("🎯 Learning Objectives", open=False):
objectives = module.get('learning_objectives', [])
for obj in objectives:
gr.HTML(f"""
<div style="display: flex; align-items: flex-start; gap: 12px; margin-bottom: 12px; padding: 12px; background: #F5F5F5; border-radius: 8px;">
<div style="width: 24px; height: 24px; background: #E8F5E9; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: #2E7D32; flex-shrink: 0;">βœ“</div>
<span style="color: #1F1F1F; line-height: 1.6;">{obj}</span>
</div>
""")
# Lessons
gr.HTML("<h3 style='margin: 24px 0 16px 0; color: #1F1F1F;'>πŸ“š Lessons</h3>")
for lesson in module['lessons']:
is_completed = lesson['id'] in completed_lessons
status_icon = "βœ…" if is_completed else "⬜"
icon_class = "video-icon" if lesson['type'] == "video" else "reading-icon"
icon_emoji = "🎬" if lesson['type'] == "video" else "πŸ“–"
gr.HTML(f"""
<div class="lesson-item">
<div class="lesson-icon {icon_class}">{icon_emoji}</div>
<div style="flex: 1;">
<div style="font-weight: 600; color: #1F1F1F;">{lesson['title']}</div>
<div style="color: #757575; font-size: 14px;">{lesson['duration']} β€’ {lesson['type'].title()}</div>
</div>
<div style="font-size: 20px;">{status_icon}</div>
</div>
""")
# Mark Lesson Complete
gr.HTML("<div style='margin-top: 24px;'>")
lesson_dropdown = gr.Dropdown(
choices=[lesson['title'] for lesson in module['lessons']],
label="Mark Lesson as Complete",
info="Select a lesson you've completed"
)
mark_btn = gr.Button("βœ… Mark as Complete", variant="primary")
status_msg = gr.HTML()
def mark_complete(module_id, lesson_title):
for lesson in module['lessons']:
if lesson['title'] == lesson_title:
result = progress_manager.mark_lesson_complete(module_id, lesson['id'])
if result:
return f"<div style='background: #E8F5E9; color: #2E7D32; padding: 16px; border-radius: 8px;'>βœ… Successfully marked '{lesson_title}' as complete!</div>"
else:
return f"<div style='background: #FFF3E0; color: #EF6C00; padding: 16px; border-radius: 8px;'>ℹ️ '{lesson_title}' is already marked as complete.</div>"
return "<div style='background: #FFEBEE; color: #C62828; padding: 16px; border-radius: 8px;'>❌ Lesson not found.</div>"
mark_btn.click(
fn=lambda x: mark_complete(module['id'], x),
inputs=[lesson_dropdown],
outputs=[status_msg]
)
gr.HTML("</div>")
# Assignment Section
if 'assignment' in module:
assignment = module['assignment']
with gr.Accordion(f"πŸ“ Assignment: {assignment['title']}", open=False):
gr.HTML(f"""
<div style="margin-bottom: 24px;">
<p style="color: #1F1F1F; line-height: 1.8;">{assignment['description']}</p>
<div style="display: flex; gap: 16px; margin-top: 16px;">
<span style="color: #757575;">⏱️ {assignment.get('time_estimate', '3-4 hours')}</span>
<span style="color: #757575;">πŸ“Š {assignment.get('difficulty', 'Intermediate')}</span>
</div>
</div>
""")
gr.HTML("<h4 style='margin-bottom: 12px; color: #1F1F1F;'>Tasks:</h4>")
for i, task in enumerate(assignment.get('tasks', [])):
if isinstance(task, dict):
gr.HTML(f"<div style='margin-bottom: 8px; padding: 12px; background: #F5F5F5; border-radius: 8px;'><strong>{i+1}.</strong> {task.get('title', task.get('description', ''))}</div>")
else:
gr.HTML(f"<div style='margin-bottom: 8px; padding: 12px; background: #F5F5F5; border-radius: 8px;'><strong>{i+1}.</strong> {task}</div>")
gr.HTML("<h4 style='margin: 16px 0 12px 0; color: #1F1F1F;'>Deliverables:</h4>")
for item in assignment.get('deliverables', []):
gr.HTML(f"<div style='margin-bottom: 8px;'>β€’ {item}</div>")
# Quiz Section
if 'quiz' in module:
quiz = module['quiz']
with gr.Accordion(f"πŸ“ {quiz['title']}", open=False):
current_score = progress_manager.get_quiz_score(module['id'])
gr.HTML(f"""
<div style="background: #E8F0FE; padding: 16px; border-radius: 8px; margin-bottom: 24px;">
<p style="margin: 0; color: #0056D2;"><strong>Instructions:</strong> Answer all {len(quiz['questions'])} questions. You need 80% to pass.</p>
<p style="margin: 8px 0 0 0; color: #0056D2;"><strong>Current Best Score:</strong> {current_score}%</p>
</div>
""")
question_inputs = []
for i, question in enumerate(quiz['questions']):
q_input = gr.Radio(
choices=question['options'],
label=f"Question {i+1}: {question['question']}",
type="index"
)
question_inputs.append(q_input)
submit_btn = gr.Button("Submit Quiz", variant="primary")
result_display = gr.HTML()
def evaluate_quiz(module_id, *answers):
evaluation = quiz_handler.evaluate_answers(quiz['questions'], answers)
progress_manager.save_quiz_score(module_id, evaluation['score'])
result_html = f"""
<div style="background: {'#E8F5E9' if evaluation['passed'] else '#FFF3E0'}; padding: 24px; border-radius: 12px; margin-top: 16px;">
<h3 style="margin: 0 0 8px 0; color: {'#2E7D32' if evaluation['passed'] else '#EF6C00'};">
{'πŸŽ‰ Congratulations!' if evaluation['passed'] else 'πŸ“š Keep Learning!'}
</h3>
<p style="margin: 0; color: #1F1F1F; font-size: 24px; font-weight: 700;">Score: {evaluation['score']}%</p>
<p style="margin: 8px 0 0 0; color: #757575;">{evaluation['correct_count']} out of {evaluation['total_questions']} correct</p>
<p style="margin: 8px 0 0 0; color: #757575;">{'You passed!' if evaluation['passed'] else 'You need 80% to pass. Try again!'}</p>
</div>
"""
return result_html
submit_btn.click(
fn=lambda *answers: evaluate_quiz(module['id'], *answers),
inputs=question_inputs,
outputs=[result_display]
)
def create_capstone_tab():
"""Create the capstone project tab"""
capstone = COURSE_DATA.get('capstone', {})
gr.HTML(f"""
<div style="background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%); border-radius: 12px; padding: 32px; margin-bottom: 24px;">
<h2 style="margin: 0 0 8px 0; color: #1F1F1F;">πŸ† {capstone.get('title', 'Capstone Project')}</h2>
<p style="margin: 0; color: #1F1F1F; opacity: 0.9;">{capstone.get('subtitle', '')}</p>
</div>
<div style="background: white; border-radius: 12px; padding: 32px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); margin-bottom: 24px;">
<p style="color: #1F1F1F; line-height: 1.8;">{capstone.get('description', '')}</p>
<div style="display: flex; gap: 24px; margin-top: 16px;">
<span style="color: #757575;">⏱️ {capstone.get('duration', '20-25 hours')}</span>
<span style="color: #757575;">πŸ“Š {capstone.get('difficulty', 'Advanced')}</span>
</div>
</div>
""")
# Requirements
with gr.Accordion("πŸ“‹ Requirements", open=True):
for i, req in enumerate(capstone.get('requirements', [])):
if isinstance(req, dict):
gr.HTML(f"""
<div style="margin-bottom: 16px; padding: 16px; background: #F5F5F5; border-radius: 8px;">
<h4 style="margin: 0 0 8px 0; color: #1F1F1F;">{i+1}. {req.get('title', '')}</h4>
<p style="margin: 0; color: #757575;">{req.get('description', '')}</p>
</div>
""")
else:
gr.HTML(f"<div style='margin-bottom: 12px; padding: 12px; background: #F5F5F5; border-radius: 8px;'>{i+1}. {req}</div>")
# Evaluation Criteria
with gr.Accordion("πŸ“Š Evaluation Criteria", open=False):
criteria = capstone.get('evaluation_criteria', {})
for criterion, weight in criteria.items():
gr.HTML(f"""
<div style="display: flex; justify-content: space-between; padding: 12px; background: #F5F5F5; border-radius: 8px; margin-bottom: 8px;">
<span style="color: #1F1F1F; text-transform: capitalize;">{criterion.replace('_', ' ')}</span>
<span style="color: #0056D2; font-weight: 600;">{weight}</span>
</div>
""")
# Submission Form
gr.HTML("<h3 style='margin: 24px 0 16px 0; color: #1F1F1F;'>πŸš€ Submit Your Project</h3>")
with gr.Row():
with gr.Column():
project_title = gr.Textbox(
label="Project Title",
placeholder="Enter your project title"
)
project_description = gr.Textbox(
label="Project Description",
lines=5,
placeholder="Describe your project, approach, and key features"
)
project_url = gr.Textbox(
label="Demo URL (Optional)",
placeholder="https://your-demo-url.com"
)
project_file = gr.File(
label="Upload Project Files",
file_types=[".zip", ".pdf", ".ipynb"]
)
submit_btn = gr.Button("Submit Project", variant="primary")
submission_status = gr.HTML()
def submit_project(title, desc, url, file):
success, message = progress_manager.submit_capstone(title, desc, url, file)
if success:
return f"""
<div style="background: #E8F5E9; padding: 24px; border-radius: 12px;">
<h3 style="margin: 0 0 8px 0; color: #2E7D32;">βœ… Project Submitted Successfully!</h3>
<p style="margin: 0; color: #1F1F1F;"><strong>Title:</strong> {title}</p>
<p style="margin: 8px 0 0 0; color: #757575;">Your project will be reviewed within 5-7 business days.</p>
</div>
"""
else:
return f"""
<div style="background: #FFEBEE; padding: 16px; border-radius: 8px; color: #C62828;">
❌ {message}
</div>
"""
submit_btn.click(
fn=submit_project,
inputs=[project_title, project_description, project_url, project_file],
outputs=[submission_status]
)
def create_resources_tab():
"""Create the resources tab"""
resources = COURSE_DATA.get('resources', {})
with gr.Tabs():
with gr.TabItem("πŸ“š Books"):
gr.HTML("<div style='padding: 16px;'>")
for book in resources.get('books', []):
gr.HTML(f"""
<div style="background: white; border: 1px solid #E0E0E0; border-radius: 8px; padding: 16px; margin-bottom: 12px;">
<div style="font-weight: 600; color: #1F1F1F; margin-bottom: 4px;">{book.get('title', '')}</div>
<div style="color: #757575; font-size: 14px;">by {book.get('authors', '')}</div>
</div>
""")
gr.HTML("</div>")
with gr.TabItem("πŸ”— Online Resources"):
gr.HTML("<div style='padding: 16px;'>")
for resource in resources.get('online_resources', []):
gr.HTML(f"""
<div style="background: white; border: 1px solid #E0E0E0; border-radius: 8px; padding: 16px; margin-bottom: 12px;">
<a href="{resource.get('url', '#')}" target="_blank" style="color: #0056D2; font-weight: 600; text-decoration: none;">{resource.get('name', '')}</a>
</div>
""")
gr.HTML("</div>")
with gr.TabItem("πŸ› οΈ Tools"):
gr.HTML("<div style='padding: 16px;'>")
for tool in resources.get('tools', []):
gr.HTML(f"""
<div style="background: white; border: 1px solid #E0E0E0; border-radius: 8px; padding: 16px; margin-bottom: 12px;">
<div style="font-weight: 600; color: #1F1F1F; margin-bottom: 4px;">{tool.get('name', '')}</div>
<div style="color: #757575; font-size: 14px;">{tool.get('description', '')}</div>
</div>
""")
gr.HTML("</div>")
with gr.TabItem("πŸ“Ί Video Courses"):
gr.HTML("""
<div style="padding: 16px;">
<div style="background: white; border: 1px solid #E0E0E0; border-radius: 8px; padding: 16px; margin-bottom: 12px;">
<div style="font-weight: 600; color: #1F1F1F; margin-bottom: 4px;">Stanford CS229: Machine Learning</div>
<div style="color: #757575; font-size: 14px;">Comprehensive ML course from Stanford University</div>
</div>
<div style="background: white; border: 1px solid #E0E0E0; border-radius: 8px; padding: 16px; margin-bottom: 12px;">
<div style="font-weight: 600; color: #1F1F1F; margin-bottom: 4px;">Fast.ai Practical Deep Learning</div>
<div style="color: #757575; font-size: 14px;">Hands-on deep learning course</div>
</div>
<div style="background: white; border: 1px solid #E0E0E0; border-radius: 8px; padding: 16px; margin-bottom: 12px;">
<div style="font-weight: 600; color: #1F1F1F; margin-bottom: 4px;">DeepLearning.AI</div>
<div style="color: #757575; font-size: 14px;">Courses by Andrew Ng</div>
</div>
</div>
""")
# Launch the app
if __name__ == "__main__":
print("Starting SkillSync: Generative AI Mastery...")
print(f"Version: {Config.APP_VERSION}")
print("-" * 50)
app = create_app()
app.launch(
server_name=Config.GRADIO_SERVER_NAME,
server_port=Config.GRADIO_SERVER_PORT,
share=Config.GRADIO_SHARE
)