banao-tech/base-course-personalization
1
1import streamlit as st2import json3from datetime import datetime4import time5import requests6import boto37import uuid8import os9 10# Load configuration from environment variables (Hugging Face Spaces)11API_ENDPOINT = os.getenv("API_ENDPOINT", "https://68p3txfrz2.execute-api.ap-south-1.amazonaws.com/dev/process")12 13# DynamoDB configuration for session tracking14DYNAMODB_REGION = os.getenv("DYNAMODB_REGION", "ap-south-1") 15SESSION_TABLE = os.getenv("SESSION_TABLE", "SessionTracking")16 17# AWS Configuration18AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID")19AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY")20AWS_DEFAULT_REGION = os.getenv("AWS_DEFAULT_REGION", "ap-south-1")21 22# Default values23DEFAULT_USER_ID = int(os.getenv("DEFAULT_USER_ID", "30")) 24DEFAULT_PERSONALIZATION_ID = int(os.getenv("DEFAULT_PERSONALIZATION_ID", "100"))25DEFAULT_COURSE_ID = int(os.getenv("DEFAULT_COURSE_ID", "47"))26DEFAULT_CHAPTER_ID = int(os.getenv("DEFAULT_CHAPTER_ID", "647"))27API_TIMEOUT = int(os.getenv("API_TIMEOUT", "30"))28 29# Authentication credentials from environment variables30VALID_USERNAME = os.getenv("APP_USERNAME")31VALID_PASSWORD = os.getenv("APP_PASSWORD")32 33# ============================================34# DOMAIN CONFIGURATION (Scalable & Generic)35# ============================================36DOMAIN_CONFIG = {37 "system_design": {38 "requires_topic_analysis": True,39 "requires_optional_params": True,40 "default_programming_language": "system_design",41 "has_design_levels": True,42 "design_levels": ["HLD (High-Level Design)", "LLD (Low-Level Design)", "Both"]43 },44 "programming": {45 "requires_topic_analysis": True,46 "requires_optional_params": True,47 "default_programming_language": None,48 "has_design_levels": False49 },50 "ai_ml": {51 "requires_topic_analysis": True,52 "requires_optional_params": True,53 "default_programming_language": "python",54 "has_design_levels": False55 },56 "data_engineering": {57 "requires_topic_analysis": True,58 "requires_optional_params": True,59 "default_programming_language": None,60 "has_design_levels": False61 },62 "devops": {63 "requires_topic_analysis": True,64 "requires_optional_params": True,65 "default_programming_language": None,66 "has_design_levels": False67 }68}69 70# Set page configuration71st.set_page_config(72 page_title="Base Course Personalization",73 layout="wide",74 initial_sidebar_state="collapsed",75 page_icon="๐",76 menu_items={77 'Get Help': None,78 'Report a bug': None,79 'About': None80 }81)82 83# Custom CSS for dark theme styling (keep existing CSS)84st.markdown("""85<style>86 /* Dark theme colors */87 :root {88 --background-color: #1E1E1E;89 --card-background: #2D2D2D;90 --text-color: #E0E0E0;91 --accent-color: #4F97FF;92 --border-color: #444444;93 --header-color: #4F97FF;94 --subheader-color: #FFFFFF;95 --success-color: #10B981;96 --warning-color: #F59E0B;97 --error-color: #EF4444;98 }99 100 /* Main container styling */101 .stApp {102 background-color: var(--background-color);103 color: var(--text-color);104 }105 106 /* Headers */107 .main-header {108 font-size: 2.5rem;109 font-weight: 600;110 color: var(--header-color);111 margin-bottom: 1rem;112 text-align: center;113 text-shadow: 0 2px 4px rgba(79, 151, 255, 0.3);114 }115 116 .section-header {117 font-size: 1.5rem;118 font-weight: 500;119 color: var(--subheader-color);120 margin-top: 2rem;121 margin-bottom: 1rem;122 padding-bottom: 0.5rem;123 border-bottom: 2px solid var(--border-color);124 display: flex;125 align-items: center;126 gap: 0.5rem;127 }128 129 /* Form container */130 div[data-testid="stForm"] {131 background-color: var(--card-background);132 padding: 2rem;133 border-radius: 12px;134 box-shadow: 0 8px 25px rgba(0,0,0,0.4);135 border: 1px solid var(--border-color);136 margin-bottom: 1rem;137 }138 139 /* Button styling */140 .stButton>button {141 background: linear-gradient(135deg, var(--accent-color) 0%, #3B82F6 100%);142 color: white;143 border-radius: 8px;144 padding: 0.75rem 2rem;145 font-weight: 600;146 border: none;147 transition: all 0.3s ease;148 box-shadow: 0 4px 15px rgba(79, 151, 255, 0.3);149 }150 151 .stButton>button:hover {152 transform: translateY(-2px);153 box-shadow: 0 6px 20px rgba(79, 151, 255, 0.4);154 }155 156 /* Input fields styling */157 div[data-baseweb="select"] > div,158 div[data-baseweb="multi-select"] > div {159 background-color: var(--background-color);160 border: 2px solid var(--border-color);161 border-radius: 8px;162 transition: border-color 0.3s ease;163 }164 165 div[data-baseweb="select"] > div:focus-within,166 div[data-baseweb="multi-select"] > div:focus-within {167 border-color: var(--accent-color);168 box-shadow: 0 0 0 3px rgba(79, 151, 255, 0.1);169 }170 171 .stTextInput > div > div > input,172 .stNumberInput > div > div > input,173 .stTextArea > div > div > textarea {174 background-color: var(--background-color);175 color: var(--text-color);176 border: 2px solid var(--border-color);177 border-radius: 8px;178 padding: 0.75rem;179 transition: border-color 0.3s ease;180 }181 182 .stTextInput > div > div > input:focus,183 .stNumberInput > div > div > input:focus,184 .stTextArea > div > div > textarea:focus {185 border-color: var(--accent-color);186 box-shadow: 0 0 0 3px rgba(79, 151, 255, 0.1);187 }188 189 /* Topic container styling */190 .topic-container {191 background-color: var(--card-background);192 padding: 1.5rem;193 border-radius: 10px;194 margin-bottom: 1rem;195 border: 1px solid var(--border-color);196 position: relative;197 }198 199 .topic-header {200 display: flex;201 align-items: center;202 gap: 0.5rem;203 margin-bottom: 1rem;204 font-weight: 600;205 color: var(--accent-color);206 }207 208 /* Radio buttons */209 .stRadio > div {210 background-color: transparent;211 gap: 1rem;212 }213 214 .stRadio > div > label > div {215 color: var(--text-color) !important;216 font-weight: 500;217 }218 219 /* Labels */220 .stSelectbox > label,221 .stTextInput > label,222 .stNumberInput > label,223 .stTextArea > label,224 .stRadio > label {225 color: var(--text-color) !important;226 font-weight: 500;227 margin-bottom: 0.5rem;228 }229 230 /* Toggle switch */231 .stToggle > label {232 color: var(--text-color) !important;233 font-weight: 500;234 }235 236 /* Spacing */237 div.block-container {238 padding-top: 2rem;239 max-width: 1200px;240 }241 242 hr {243 margin: 2rem 0;244 border: none;245 border-top: 1px solid var(--border-color);246 }247 248 /* API Response Container */249 .api-response {250 background-color: #1A1A1A;251 border-radius: 10px;252 padding: 1.5rem;253 border-left: 4px solid var(--accent-color);254 margin: 1rem 0;255 }256 257 .session-info {258 background-color: var(--card-background);259 padding: 1.5rem;260 border-radius: 10px;261 margin: 1rem 0;262 border: 1px solid var(--border-color);263 box-shadow: 0 4px 12px rgba(0,0,0,0.2);264 }265 266 /* Action buttons container */267 .action-buttons {268 display: flex;269 gap: 1rem;270 margin: 1rem 0;271 justify-content: flex-start;272 }273 274 /* Small action buttons */275 .small-button {276 padding: 0.5rem 1rem;277 font-size: 0.875rem;278 }279 280 /* Success/Error styling */281 .stSuccess, .stError, .stWarning, .stInfo {282 border-radius: 8px;283 padding: 1rem;284 margin: 1rem 0;285 }286 287 /* Login container */288 .login-container {289 background-color: var(--card-background);290 padding: 3rem;291 border-radius: 15px;292 box-shadow: 0 10px 30px rgba(0,0,0,0.5);293 border: 1px solid var(--border-color);294 }295 296 /* Admin Dashboard specific styles */297 .admin-card {298 background-color: var(--card-background);299 padding: 2rem;300 border-radius: 12px;301 border: 1px solid var(--border-color);302 box-shadow: 0 4px 12px rgba(0,0,0,0.2);303 margin-bottom: 2rem;304 }305 306 .status-badge {307 padding: 0.3rem 0.8rem;308 border-radius: 20px;309 font-size: 0.8rem;310 font-weight: 600;311 text-transform: uppercase;312 letter-spacing: 0.5px;313 }314 315 .status-completed { background-color: #10B981; color: white; }316 .status-started { background-color: #F59E0B; color: white; }317 .status-failed { background-color: #EF4444; color: white; }318 319 .download-link {320 background-color: var(--accent-color);321 color: white;322 padding: 0.5rem 1rem;323 border-radius: 6px;324 text-decoration: none;325 font-size: 0.9rem;326 font-weight: 500;327 transition: background-color 0.3s ease;328 }329 330 .download-link:hover {331 background-color: #3B82F6;332 color: white;333 text-decoration: none;334 }335</style>336""", unsafe_allow_html=True)337 338# Initialize session state for topics and authentication339if 'topics_list' not in st.session_state:340 st.session_state.topics_list = [{341 "topic_title": "What is Flask", 342 "chapter_title": "Introduction to Flask",343 "manual_id": "" # Added field344 }]345if 'session_ids' not in st.session_state:346 st.session_state.session_ids = []347if 'authenticated' not in st.session_state:348 st.session_state.authenticated = False349if 'current_page' not in st.session_state:350 st.session_state.current_page = "Course Generation"351if 'manual_session_id' not in st.session_state:352 st.session_state.manual_session_id = ""353 354# Initialize DynamoDB client355@st.cache_resource356def get_dynamodb_client():357 """Initialize DynamoDB client with credentials from environment"""358 try:359 if AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY:360 return boto3.client(361 'dynamodb',362 region_name=DYNAMODB_REGION,363 aws_access_key_id=AWS_ACCESS_KEY_ID,364 aws_secret_access_key=AWS_SECRET_ACCESS_KEY365 )366 else:367 # Try to use default credentials (IAM role, etc.)368 return boto3.client('dynamodb', region_name=DYNAMODB_REGION)369 except Exception as e:370 st.error(f"Failed to initialize DynamoDB client: {e}")371 return None372 373# Initialize S3 client374@st.cache_resource375def get_s3_client():376 """Initialize S3 client with credentials from environment"""377 try:378 if AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY:379 return boto3.client(380 's3',381 region_name=AWS_DEFAULT_REGION,382 aws_access_key_id=AWS_ACCESS_KEY_ID,383 aws_secret_access_key=AWS_SECRET_ACCESS_KEY384 )385 else:386 return boto3.client('s3', region_name=AWS_DEFAULT_REGION)387 except Exception as e:388 st.error(f"Failed to initialize S3 client: {e}")389 return None390 391def get_session_data(session_id):392 """Get session data from DynamoDB"""393 dynamodb = get_dynamodb_client()394 if not dynamodb:395 return None396 397 try:398 response = dynamodb.get_item(399 TableName=SESSION_TABLE,400 Key={'session_id': {'S': session_id}}401 )402 403 if 'Item' in response:404 item = response['Item']405 return {406 'session_id': item.get('session_id', {}).get('S', ''),407 'status': item.get('status', {}).get('S', ''),408 'node': item.get('node', {}).get('S', ''),409 'course_id': item.get('course_id', {}).get('N', '0'),410 'topic_id': item.get('topic_id', {}).get('N', '0'),411 'topic_title': item.get('topic_title', {}).get('S', ''),412 'video_url': item.get('video_url', {}).get('S', ''),413 'created_at': item.get('created_at', {}).get('S', ''),414 'updated_at': item.get('updated_at', {}).get('S', '')415 }416 except Exception as e:417 st.error(f"Error fetching session data: {e}")418 419 return None420 421def update_session_status(session_id, status, node=None, video_url=None):422 """Update session status in DynamoDB"""423 dynamodb = get_dynamodb_client()424 if not dynamodb:425 return False426 427 try:428 now = datetime.utcnow().isoformat() + "Z"429 430 update_expr = "SET updated_at = :u, #st = :s"431 expr_attr_names = {"#st": "status", "#ut": "updated_at"}432 expr_attr_values = {":u": {"S": now}, ":s": {"S": status}}433 434 if node:435 update_expr += ", #nd = :n"436 expr_attr_names["#nd"] = "node"437 expr_attr_values[":n"] = {"S": node}438 439 if video_url:440 update_expr += ", video_url = :v"441 expr_attr_values[":v"] = {"S": video_url}442 443 dynamodb.update_item(444 TableName=SESSION_TABLE,445 Key={'session_id': {'S': session_id}},446 UpdateExpression=update_expr,447 ExpressionAttributeNames=expr_attr_names,448 ExpressionAttributeValues=expr_attr_values449 )450 return True451 except Exception as e:452 st.error(f"Error updating session: {e}")453 return False454 455def generate_s3_presigned_url(s3_key, bucket='tech-learn-state', expiry=3600):456 """Generate presigned URL for S3 object"""457 s3_client = get_s3_client()458 if not s3_client:459 return None460 461 try:462 url = s3_client.generate_presigned_url(463 'get_object',464 Params={'Bucket': bucket, 'Key': s3_key},465 ExpiresIn=expiry466 )467 return url468 except Exception as e:469 st.error(f"Error generating presigned URL: {e}")470 return None471 472def render_admin_dashboard():473 """Render the simplified admin dashboard"""474 st.markdown('<div class="section-header">๐๏ธ Admin Dashboard - Session Monitoring</div>', unsafe_allow_html=True)475 476 # Session lookup section477 st.markdown('<div class="admin-card">', unsafe_allow_html=True)478 st.markdown("### ๐ Session Lookup")479 480 col1, col2 = st.columns([3, 1])481 with col1:482 session_id_input = st.text_input(483 "Enter Session ID", 484 placeholder="e.g., sess_abc123 or 2025-01-15-12-30-45-abc123",485 help="Enter the session ID to monitor"486 )487 with col2:488 st.markdown("<br>", unsafe_allow_html=True)489 lookup_button = st.button("๐ Lookup", use_container_width=True)490 491 st.markdown('</div>', unsafe_allow_html=True)492 493 # Display session data494 if lookup_button and session_id_input:495 session_data = get_session_data(session_id_input.strip())496 497 if session_data:498 st.markdown('<div class="admin-card">', unsafe_allow_html=True)499 st.markdown(f"### ๐ Session Details: `{session_id_input}`")500 501 # Status badge502 status = session_data['status']503 status_class = f"status-{status.lower()}" if status.lower() in ['completed', 'started', 'failed'] else "status-started"504 st.markdown(f'<span class="status-badge {status_class}">{status}</span>', unsafe_allow_html=True)505 506 # Session info507 col1, col2 = st.columns(2)508 with col1:509 st.markdown(f"""510 **Node**: {session_data['node']} 511 **Course ID**: {session_data['course_id']} 512 **Topic ID**: {session_data['topic_id']} 513 **Topic Title**: {session_data['topic_title']}514 """)515 with col2:516 st.markdown(f"""517 **Created**: {session_data['created_at'][:19] if session_data['created_at'] else 'N/A'} 518 **Updated**: {session_data['updated_at'][:19] if session_data['updated_at'] else 'N/A'} 519 **Status**: {session_data['status']}520 """)521 522 # Video URL section523 if session_data['video_url']:524 st.markdown("### ๐ฅ Video Download")525 st.markdown(f'<a href="{session_data["video_url"]}" class="download-link" target="_blank">๐ฅ Download Video</a>', unsafe_allow_html=True)526 else:527 st.info("No video URL available for this session")528 529 st.markdown('</div>', unsafe_allow_html=True)530 531 # Session management section532 st.markdown('<div class="admin-card">', unsafe_allow_html=True)533 st.markdown("### โ๏ธ Session Management")534 535 col1, col2, col3 = st.columns(3)536 537 with col1:538 new_status = st.selectbox("Update Status", ["STARTED", "COMPLETED", "FAILED"], key="status_update")539 if st.button("๐ Update Status"):540 if update_session_status(session_id_input.strip(), new_status):541 st.success(f"Status updated to {new_status}")542 st.rerun()543 else:544 st.error("Failed to update status")545 546 with col2:547 new_node = st.text_input("Update Node", placeholder="e.g., SlideCreation", key="node_update")548 if st.button("๐ Update Node"):549 if new_node.strip() and update_session_status(session_id_input.strip(), session_data['status'], new_node.strip()):550 st.success(f"Node updated to {new_node}")551 st.rerun()552 else:553 st.error("Failed to update node")554 555 with col3:556 # S3 URL Generator557 s3_key_input = st.text_input("S3 Key", placeholder="video_states/session_id/final_video/...", key="s3_key")558 if st.button("๐ Generate Video URL"):559 if s3_key_input.strip():560 video_url = generate_s3_presigned_url(s3_key_input.strip())561 if video_url:562 if update_session_status(session_id_input.strip(), session_data['status'], video_url=video_url):563 st.success("Video URL generated and updated!")564 st.code(video_url)565 st.rerun()566 else:567 st.error("Failed to update video URL")568 else:569 st.error("Failed to generate presigned URL")570 else:571 st.error("Please enter S3 key")572 573 st.markdown('</div>', unsafe_allow_html=True)574 575 else:576 st.error(f"โ Session not found: `{session_id_input}`")577 578 # Recent sessions from session state579 if st.session_state.session_ids:580 st.markdown('<div class="admin-card">', unsafe_allow_html=True)581 st.markdown("### ๐ Recent Sessions from Current App Session")582 583 for session_id in st.session_state.session_ids[-10:]: # Show last 10584 session_data = get_session_data(session_id)585 if session_data:586 with st.expander(f"๐ {session_id} - {session_data['status']}", expanded=False):587 col1, col2 = st.columns(2)588 with col1:589 st.write(f"**Node**: {session_data['node']}")590 st.write(f"**Topic**: {session_data['topic_title']}")591 st.write(f"**Status**: {session_data['status']}")592 with col2:593 st.write(f"**Updated**: {session_data['updated_at'][:19] if session_data['updated_at'] else 'N/A'}")594 if session_data['video_url']:595 st.markdown(f'<a href="{session_data["video_url"]}" class="download-link" target="_blank">๐ฅ Download</a>', unsafe_allow_html=True)596 597 st.markdown('</div>', unsafe_allow_html=True)598 599# Authentication check600if not st.session_state.authenticated:601 st.markdown('<h1 class="main-header">๐ Login to Course Management</h1>', unsafe_allow_html=True)602 603 # Check if credentials are configured604 if not VALID_USERNAME or not VALID_PASSWORD:605 st.error("โ Authentication credentials not configured. Please contact administrator.")606 st.stop()607 608 col1, col2, col3 = st.columns([1, 2, 1])609 with col2:610 with st.container():611 st.markdown('<div class="login-container">', unsafe_allow_html=True)612 613 with st.form("login_form"):614 st.markdown('<div class="section-header">๐ Authentication Required</div>', unsafe_allow_html=True)615 616 username = st.text_input("Username", placeholder="Enter username", key="login_username")617 password = st.text_input("Password", placeholder="Enter password", type="password", key="login_password")618 619 login_submitted = st.form_submit_button("๐ Login", use_container_width=True)620 621 if login_submitted:622 if VALID_USERNAME and VALID_PASSWORD and username == VALID_USERNAME and password == VALID_PASSWORD:623 st.session_state.authenticated = True624 st.success("Login successful! Redirecting...")625 time.sleep(1)626 st.rerun()627 else:628 st.error("โ Invalid username or password")629 630 st.markdown('</div>', unsafe_allow_html=True)631 632 st.markdown("---")633 st.markdown("""634 <div style="text-align: center; color: #888888;">635 <small>Please contact administrator for access credentials</small>636 </div>637 """, unsafe_allow_html=True)638 st.stop()639 640# Navigation and Header (after login)641col1, col2, col3, col4 = st.columns([3, 2, 2, 1])642with col1:643 st.markdown('<h1 class="main-header">๐ Course Management System</h1>', unsafe_allow_html=True)644 645with col2:646 page = st.selectbox(647 "๐ Navigate to:",648 ["Course Generation", "Admin Dashboard"],649 index=0 if st.session_state.current_page == "Course Generation" else 1,650 key="page_selector"651 )652 st.session_state.current_page = page653 654with col4:655 if st.button("๐ Logout", key="logout_btn"):656 st.session_state.authenticated = False657 st.session_state.topics_list = [{658 "topic_title": "What is Flask", 659 "chapter_title": "Introduction to Flask"660 }]661 st.session_state.session_ids = []662 st.session_state.current_page = "Course Generation"663 st.session_state.manual_session_id = ""664 st.rerun()665 666# Page routing667if st.session_state.current_page == "Admin Dashboard":668 render_admin_dashboard()669else:670 # Course Generation Page671 672 # NEW: Manual Session ID Section673 st.markdown('<div class="section-header">๐ฏ Session ID Configuration</div>', unsafe_allow_html=True)674 675 col1, col2 = st.columns([3, 2])676 with col1:677 manual_session_id_input = st.text_input(678 "Manual Session ID (Optional)",679 value=st.session_state.manual_session_id,680 placeholder="Leave empty for auto-generated session ID (e.g., sess_abc12345)",681 help="Provide a custom session ID or leave empty to auto-generate"682 )683 st.session_state.manual_session_id = manual_session_id_input684 with col2:685 st.info("๐ก If provided, this session ID will be used instead of auto-generating one")686 687 # Topics Section (Outside form for dynamic interaction)688 st.markdown('<div class="section-header">๐ Topics Configuration</div>', unsafe_allow_html=True)689 690 # Display existing topics with better styling691 for i, topic in enumerate(st.session_state.topics_list):692 st.markdown(f'<div class="topic-container">', unsafe_allow_html=True)693 st.markdown(f'<div class="topic-header">๐ Topic {i+1}</div>', unsafe_allow_html=True)694 695 col1, col2, col3 = st.columns([4, 4, 0.5])696 with col1:697 topic_title = st.text_input("Topic Title", value=topic["topic_title"], key=f"topic_title_{i}")698 st.session_state.topics_list[i]["topic_title"] = topic_title699 700 with col2:701 chapter_title = st.text_input("Chapter Title", value=topic["chapter_title"], key=f"chapter_title_{i}")702 st.session_state.topics_list[i]["chapter_title"] = chapter_title703 704 with col3:705 if len(st.session_state.topics_list) > 1:706 st.markdown("<br>", unsafe_allow_html=True)707 if st.button("๐๏ธ", key=f"remove_{i}"):708 st.session_state.topics_list.pop(i)709 st.rerun()710 st.markdown('</div>', unsafe_allow_html=True)711 712 # Add/Remove topic buttons outside the form713 st.markdown('<div class="action-buttons">', unsafe_allow_html=True)714 col1, col2, col3, col4 = st.columns([2, 2, 2, 4])715 with col1:716 if st.button("โ Add Topic", key="add_topic", help="Add a new topic"):717 st.session_state.topics_list.append({718 "topic_title": f"Topic {len(st.session_state.topics_list) + 1}", 719 "chapter_title": f"Chapter {len(st.session_state.topics_list) + 1}"720 })721 st.rerun()722 723 with col2:724 if st.button("๐ Reset All", key="reset_topics", help="Reset to default topics"):725 st.session_state.topics_list = [{726 "topic_title": "What is Flask", 727 "chapter_title": "Introduction to Flask"728 }]729 st.rerun()730 st.markdown('</div>', unsafe_allow_html=True)731 732 # Language & Voice Settings (OUTSIDE FORM for interactivity)733 st.markdown(734 '<div class="section-header">๐ฃ๏ธ Language & Voice Settings</div>',735 unsafe_allow_html=True,736 )737 738 col1, col2, col3 = st.columns(3)739 740 with col1:741 target_languages = st.multiselect(742 "Target Languages",743 ["english", "hindi", "marathi", "kannada", "punjabi", "gujarati","tamil","telugu","bengali","malayalam"],744 default=["english"],745 format_func=lambda x: x.capitalize(),746 help="Select one or more target languages for content generation",747 placeholder="Select target languages",748 )749 750 751 with col2:752 tts_gender = st.selectbox(753 "Voice Gender",754 ["male", "female"],755 index=0,756 format_func=lambda x: x.capitalize(),757 help="Select the voice gender for text-to-speech",758 )759 760 with col3:761 # Define voice options based on gender762 if tts_gender == "male":763 voice_options = [764 "Puck",765 "Charon",766 "Fenrir",767 "Orus",768 "Achird",769 "Algenib",770 "Algieba",771 "Alnilam",772 "Enceladus",773 "Iapetus",774 "Rasalgethi",775 "Sadachbia"776 ]777 default_index = 1 # Charon778 else: # female779 voice_options = [780 "Aoede",781 "Kore",782 "Leda",783 "Zephyr",784 "Autonoe",785 "Callirhoe",786 "Despina",787 "Erinome",788 "Gacrux",789 "Laomedeia",790 "Pulcherrima",791 "Sulafat",792 "Vindemiatrix",793 "Achernar"794 ]795 default_index = 0 # Aoede796 797 # Reset voice when gender changes798 if "selected_voice" not in st.session_state or st.session_state.get("last_gender") != tts_gender:799 st.session_state.selected_voice = voice_options[default_index]800 st.session_state.last_gender = tts_gender801 802 tts_voice = st.selectbox(803 "Voice Style (Gemini)",804 voice_options,805 index=default_index,806 key="voice_selector",807 help=f"Select the Gemini {tts_gender} voice for text-to-speech",808 )809 810 # Update session state811 st.session_state.selected_voice = tts_voice812 813 # Course Type & Content Section (Dynamic from DOMAIN_CONFIG)814 st.markdown('<div class="section-header">๐ Course Type & Content</div>', unsafe_allow_html=True)815 col1, col2 = st.columns(2)816 with col1:817 # Generate course type options dynamically from DOMAIN_CONFIG818 course_type_options = [key.replace("_", " ").title() for key in DOMAIN_CONFIG.keys()]819 820 course_type = st.selectbox(821 "Course Type",822 course_type_options,823 index=0,824 help="Select the type of course content to generate appropriate slide templates"825 )826 827 with col2:828 # Normalize course type for config lookup829 normalized_course_type_lookup = course_type.lower().replace(" ", "_")830 domain_config = DOMAIN_CONFIG.get(normalized_course_type_lookup, {})831 832 # Show design level selector if domain requires it833 if domain_config.get("has_design_levels", False):834 design_level = st.selectbox(835 "Design Level",836 domain_config.get("design_levels", []),837 index=0,838 help="Choose the level of design detail"839 )840 else:841 design_level = None842 843 # Main Form (for technical settings and submission)844 with st.form("personalization_form", clear_on_submit=False):845 # Technical Settings Section846 st.markdown('<div class="section-header">๐ป Technical Settings</div>', unsafe_allow_html=True)847 848 col1, col2 = st.columns(2)849 with col1:850 # Comprehensive list of programming languages, frameworks, and databases851 tech_options = [852 "System Design",853 # Programming Languages854 "Python", "Java", "JavaScript", "TypeScript", "C++", "C#", "C", "Go", "Rust", "Swift", 855 "Kotlin", "Scala", "Ruby", "PHP", "Perl", "R", "MATLAB", "Dart", "Objective-C", "Assembly",856 "Haskell", "Erlang", "Elixir", "F#", "Clojure", "Lua", "Julia", "Groovy", "VB.NET", "COBOL",857 "Fortran", "Pascal", "Delphi", "Ada", "Prolog", "Lisp", "Scheme", "OCaml", "ML",858 859 # Web Technologies860 "HTML", "CSS", "SASS", "LESS", "Bootstrap", "Tailwind CSS", "Material-UI",861 862 # Frontend Frameworks/Libraries863 "React", "Vue.js", "Angular", "Svelte", "Next.js", "Nuxt.js", "Gatsby", "Ember.js", 864 "Backbone.js", "jQuery", "Alpine.js", "Lit", "Stencil", "Ionic", "React Native", 865 "Flutter", "Xamarin", "Cordova", "PhoneGap",866 867 # Backend Frameworks868 "Node.js", "Express.js", "Nest.js", "Django", "Flask", "FastAPI", "Pyramid", "Tornado",869 "Spring Boot", "Spring MVC", "Struts", "Hibernate", "ASP.NET", "ASP.NET Core", 870 "Ruby on Rails", "Sinatra", "Laravel", "Symfony", "CodeIgniter", "CakePHP", "Zend",871 "Gin", "Echo", "Fiber", "Actix", "Rocket", "Warp", "Axum",872 873 # Mobile Development874 "Android (Java)", "Android (Kotlin)", "iOS (Swift)", "iOS (Objective-C)", 875 876 # Game Development877 "Unity", "Unreal Engine", "Godot", "GameMaker Studio", "Construct", "Phaser",878 879 # Database Technologies880 "MySQL", "PostgreSQL", "SQLite", "Microsoft SQL Server", "Oracle Database", 881 "MongoDB", "Redis", "Cassandra", "DynamoDB", "Firebase", "Supabase", 882 "CouchDB", "Neo4j", "InfluxDB", "TimescaleDB", "ClickHouse", "Apache Spark",883 "Elasticsearch", "Apache Solr", "Amazon RDS", "Google Cloud SQL",884 885 # Cloud & DevOps886 "AWS", "Google Cloud Platform", "Microsoft Azure", "Digital Ocean", "Heroku",887 "Docker", "Kubernetes", "Terraform", "Ansible", "Jenkins", "GitLab CI", "GitHub Actions",888 "Nginx", "Apache", "Linux", "Ubuntu", "CentOS", "Red Hat",889 890 # Data Science & AI/ML891 "TensorFlow", "PyTorch", "Scikit-learn", "Pandas", "NumPy", "Matplotlib", "Seaborn",892 "Jupyter", "Apache Airflow", "Apache Kafka", "Apache Flink", "Hadoop", "Spark",893 "Tableau", "Power BI", "D3.js", "Plotly", "OpenCV", "Keras", "XGBoost",894 895 # Testing Frameworks896 "Jest", "Mocha", "Chai", "Cypress", "Selenium", "Playwright", "Puppeteer",897 "JUnit", "TestNG", "Mockito", "PyTest", "unittest", "RSpec", "PHPUnit",898 899 # Other Technologies900 "GraphQL", "REST API", "gRPC", "WebSocket", "Apache Kafka", "RabbitMQ", 901 "Blockchain", "Solidity", "Web3", "Ethereum", "Bitcoin", "Smart Contracts",902 "Microservices", "Serverless", "Lambda Functions", "API Gateway","System Design"903 ]904 905 # Sort the options alphabetically906 tech_options.sort()907 908 # Use domain config to determine dropdown behavior909 default_prog_lang = domain_config.get("default_programming_language")910 911 if default_prog_lang:912 # Domain has a default programming language (disable dropdown)913 tech_knowledge_disabled = True914 # Find the display name for the default language915 if default_prog_lang == "system_design":916 default_index = tech_options.index("System Design") if "System Design" in tech_options else 0917 elif default_prog_lang == "python":918 default_index = tech_options.index("Python") if "Python" in tech_options else 0919 else:920 default_index = 0921 else:922 # No default, allow user selection923 tech_knowledge_disabled = False924 default_index = tech_options.index("Python") if "Python" in tech_options else 0925 926 programming_language = st.selectbox(927 "Programming Language / Technology", 928 tech_options,929 index=default_index,930 disabled=tech_knowledge_disabled,931 help="Select the primary programming language, framework, or technology for examples"932 )933 with col2:934 st.markdown("<br>", unsafe_allow_html=True)935 toggle_hinglish = st.toggle("Enable Hinglish", value=True, help="Enable mixing of Hindi and English")936 # NEW: Maths & Visualization toggles937 st.markdown("""938 <style>939 .toggle-label {940 color: white !important;941 font-weight: 500;942 font-size: 14px;943 }944 </style>945 """, unsafe_allow_html=True)946 947 col3, col4 = st.columns(2)948 949 with col3:950 st.markdown('<span class="toggle-label">Enable Maths Rendering</span>', unsafe_allow_html=True)951 enable_maths = st.toggle("", value=True, key="maths_toggle")952 953 with col4:954 st.markdown('<span class="toggle-label">Enable Visualizations</span>', unsafe_allow_html=True)955 enable_visualizations = st.toggle("", value=False, key="viz_toggle")956 957 958 # Submit button959 st.markdown("<br>", unsafe_allow_html=True)960 col1, col2, col3 = st.columns([1, 2, 1])961 with col2:962 submitted = st.form_submit_button("๐ Generate Course", use_container_width=True)963 964 # Validate topics (must be done before checking submitted)965 topics_to_process = st.session_state.topics_list966 valid_topics = []967 for topic in topics_to_process:968 if topic["topic_title"].strip() and topic["chapter_title"].strip():969 valid_topics.append(topic)970 971 # Check if no valid topics when form is submitted972 if submitted and not valid_topics:973 st.error("โ Please enter at least one topic with both topic title and chapter title")974 st.stop()975 976 # Handle submission977 if submitted and len(valid_topics) >= 1:978 979 # Validate voice-gender consistency980 male_voices = [981 "Puck", "Charon", "Fenrir", "Orus", "Achird",982 "Algenib", "Algieba", "Alnilam", "Enceladus",983 "Iapetus", "Rasalgethi", "Sadachbia"984 ]985 female_voices = [986 "Aoede", "Kore", "Leda", "Zephyr", "Autonoe",987 "Callirhoe", "Despina", "Erinome", "Gacrux",988 "Laomedeia", "Pulcherrima", "Sulafat",989 "Vindemiatrix", "Achernar"990 ]991 992 # Check if selected voice matches selected gender993 if tts_gender == "male" and tts_voice not in male_voices:994 st.error(f"Voice {tts_voice} is not a male voice. Please select a different voice.")995 st.stop()996 if tts_gender == "female" and tts_voice not in female_voices:997 st.error(f"Voice {tts_voice} is not a female voice. Please select a different voice.")998 st.stop()999 1000 # Use default course configuration1001 course_id = DEFAULT_COURSE_ID1002 chapter_id = DEFAULT_CHAPTER_ID1003 user_id = DEFAULT_USER_ID 1004 personalization_id = DEFAULT_PERSONALIZATION_ID1005 1006 try:1007 headers = {"Content-Type": "application/json"}1008 1009 # ============================================1010 # NORMALIZATION LAYER (Domain-Agnostic)1011 # ============================================1012 1013 # Normalize all enum/domain fields1014 normalized_course_type = course_type.lower().replace(" ", "_")1015 normalized_programming_language = programming_language.lower().replace(" ", "_").replace("-", "_")1016 normalized_video_type = "base_video"1017 1018 # Get domain configuration1019 domain_config = DOMAIN_CONFIG.get(normalized_course_type, {})1020 1021 # Override programming language if domain has a default1022 # ONLY if user did not explicitly choose one--- added this change1023 if domain_config.get("default_programming_language") and programming_language.strip() == "":1024 normalized_programming_language = domain_config["default_programming_language"]1025 1026 # Ensure target_language is always a list1027 if isinstance(target_languages, str):1028 target_languages_list = [target_languages]1029 else:1030 target_languages_list = target_languages1031 1032 # ============================================1033 # Build user profile1034 # ============================================1035 user_profile = {1036 "personalized": True,1037 "username": "System User",1038 "user_age": 25,1039 "user_gender": tts_gender,1040 "user_tech_knowledge": "beginner",1041 "user_preferred_activity": ["coding", "learning", "technology"],1042 "user_food": ["healthy food", "vegetarian"],1043 "user_physical_activities": ["walking", "yoga"],1044 "learning_style": "visual",1045 "target_language": target_languages_list1046 }1047 1048 # ============================================1049 # Build settings1050 # ============================================1051 settings = {1052 "target_language": target_languages_list,1053 "toggleHinglish": toggle_hinglish,1054 "enableMaths": enable_maths,1055 "enableVisualizations": enable_visualizations,1056 "subtitle": "",1057 "programming_language": normalized_programming_language,1058 "slide_colour": "blue",1059 "video_type": normalized_video_type,1060 "tts_provider": "gemini",1061 "tts_gender": tts_gender,1062 "tts_voice": tts_voice,1063 "run_visualization": False,1064 "age_group": "18-25"1065 }1066 1067 # Build topics data1068 global_manual_id = st.session_state.manual_session_id.strip()1069 topics_data = []1070 1071 for i, topic in enumerate(valid_topics):1072 topic_entry = {1073 "topic_id": 10834 + i,1074 "topic_title": topic["topic_title"].strip(),1075 "chapter_id": chapter_id,1076 "chapter_title": topic["chapter_title"].strip(),1077 "course_id": course_id,1078 "course_name": "Base Course",1079 "video_url": f"https://techlearn-dev.s3.ap-south-1.amazonaws.com/course_videos/{course_id}/{chapter_id}/1729064365{i}50.mp4",1080 "video_duration": 462 + (i * 20),1081 "sequence_number": i + 11082 }1083 1084 if len(valid_topics) > 1:1085 # Extract prefix and increment the last number1086 parts = global_manual_id.rsplit("_", 1)1087 if len(parts) == 2:1088 prefix = parts[0]1089 try:1090 base_num = int(parts[1])1091 topic_entry["manual_session_id"] = f"{prefix}_{base_num + i}"1092 except ValueError:1093 # Fallback: append index if last part is not a number1094 topic_entry["manual_session_id"] = f"{global_manual_id}_{i+1}"1095 else:1096 # No underscore found: append index1097 topic_entry["manual_session_id"] = f"{global_manual_id}_{i+1}"1098 else:1099 # Single topic: use manual ID as-is1100 topic_entry["manual_session_id"] = global_manual_id1101 1102 topics_data.append(topic_entry)1103 1104 # ============================================1105 # Build planning metadata (Configuration-Driven)1106 # ============================================1107 video_plan = {}1108 topic_analysis = {}1109 1110 # Build video_plan for each topic (generic)1111 for topic in valid_topics:1112 topic_title = topic["topic_title"].strip()1113 video_plan[topic_title] = {1114 "slide_format": normalized_course_type,1115 "programming_language": normalized_programming_language,1116 "template": "architecture" if normalized_course_type == "system_design" else "code_tutorial"1117 }1118 1119 # Add domain-specific fields if applicable1120 if normalized_course_type == "system_design" and design_level:1121 # Parse design level1122 if "HLD" in design_level:1123 level_code = "HLD"1124 elif "LLD" in design_level:1125 level_code = "LLD"1126 else:1127 level_code = "BOTH"1128 1129 video_plan[topic_title]["design_level"] = level_code1130 video_plan[topic_title]["include_architecture_diagram"] = True1131 video_plan[topic_title]["include_components"] = True1132 video_plan[topic_title]["include_data_flow"] = True1133 else:1134 video_plan[topic_title]["include_code_examples"] = True1135 1136 # Build topic_analysis if domain requires it1137 if domain_config.get("requires_topic_analysis", False):1138 topic_analysis = {1139 "type": normalized_course_type,1140 "detected_domain": normalized_course_type,1141 "programming_language": normalized_programming_language1142 }1143 1144 # Add domain-specific analysis fields1145 if normalized_course_type == "system_design" and design_level:1146 topic_analysis["design_level"] = level_code1147 topic_analysis["requires_architecture_diagrams"] = True1148 topic_analysis["slide_template_type"] = "system_design"1149 else:1150 topic_analysis["requires_code_examples"] = True1151 topic_analysis["slide_template_type"] = normalized_course_type1152 1153 # ============================================1154 # Build final payload with planning metadata1155 # ============================================1156 1157 # Always send course_name (never None)1158 course_name = "Base Course"1159 1160 # Build optional_params dynamically1161 optional_params = {1162 "video_plan": video_plan,1163 "course_type": normalized_course_type1164 }1165 1166 # Add design_level if applicable1167 if design_level:1168 optional_params["design_level"] = design_level.split("(")[0].strip()1169 1170 # Build final payload1171 payload = {1172 "personalization_id": personalization_id,1173 "user_id": user_id,1174 "course_id": course_id,1175 "course_name": course_name,1176 "total_videos": len(topics_data),1177 "created_at": datetime.utcnow().isoformat(),1178 "user_profile": user_profile,1179 "topics": topics_data,1180 "settings": settings,1181 "manual_session_id": global_manual_id,1182 "optional_params": optional_params,1183 "topic_analysis": topic_analysis1184 }1185 1186 # Display generation summary1187 with st.spinner(f"๐ฌ Generating your {course_type} course... This may take a few moments."):1188 progress_bar = st.progress(0)1189 for i in range(100):1190 time.sleep(0.02)1191 progress_bar.progress(i + 1)1192 1193 # Make API call1194 response = requests.post(API_ENDPOINT, json=payload, headers=headers, timeout=API_TIMEOUT)1195 1196 if response.status_code == 200:1197 response_data = response.json()1198 session_ids = response_data.get("session_ids", [])1199 1200 st.success(f"โ
{course_type} course generation started successfully!")