CoolFace
Apppublic

banao-tech/base-course-personalization

sourceHugging Faceupdated 8mo agoView on Hugging Face
1likes
app.py1257 linesDownload Raw Back to root
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!")

Showing the first 1,200 of 1257 lines. Download the file for the rest.