| """ |
| 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 |
|
|
| |
| from config import Config |
|
|
| |
| from utils.progress_manager import ProgressManager |
| from utils.quiz_handler import QuizHandler |
|
|
| |
| 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 |
|
|
| |
| progress_manager = ProgressManager() |
| quiz_handler = QuizHandler() |
| COURSE_DATA = load_course_data() |
|
|
| |
| 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: |
| |
| |
| 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> |
| """) |
| |
| |
| with gr.Row(): |
| with gr.Column(scale=1): |
| progress_display = gr.HTML(value=update_progress_display()) |
| |
| |
| with gr.Tabs(): |
| |
| |
| with gr.TabItem("π Course Overview"): |
| if COURSE_DATA: |
| create_course_overview() |
| |
| |
| if COURSE_DATA: |
| for module in COURSE_DATA['modules']: |
| with gr.TabItem(f"Module {module['id']}: {module['title'][:25]}..."): |
| create_module_tab(module) |
| |
| |
| with gr.TabItem("π Capstone Project"): |
| create_capstone_tab() |
| |
| |
| with gr.TabItem("π Resources"): |
| create_resources_tab() |
| |
| |
| 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> |
| """) |
| |
| |
| 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>") |
| |
| |
| 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""" |
| |
| |
| 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 |
| |
| |
| 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> |
| """) |
| |
| |
| 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> |
| """) |
| |
| |
| 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> |
| """) |
| |
| |
| 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>") |
| |
| |
| 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>") |
| |
| |
| 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> |
| """) |
| |
| |
| 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>") |
| |
| |
| 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> |
| """) |
| |
| |
| 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> |
| """) |
|
|
| |
| 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 |
| ) |
|
|