|
|
import streamlit as st |
|
|
import json |
|
|
from datetime import datetime |
|
|
import time |
|
|
import requests |
|
|
import boto3 |
|
|
import uuid |
|
|
import os |
|
|
|
|
|
|
|
|
API_ENDPOINT = os.getenv("API_ENDPOINT", "https://oau6sljd4l.execute-api.ap-south-1.amazonaws.com/production/process") |
|
|
|
|
|
|
|
|
DYNAMODB_REGION = os.getenv("DYNAMODB_REGION", "ap-south-1") |
|
|
SESSION_TABLE = os.getenv("SESSION_TABLE", "SessionTracking") |
|
|
|
|
|
|
|
|
AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID") |
|
|
AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY") |
|
|
AWS_DEFAULT_REGION = os.getenv("AWS_DEFAULT_REGION", "ap-south-1") |
|
|
|
|
|
|
|
|
DEFAULT_COURSE_ID = int(os.getenv("DEFAULT_COURSE_ID", "47")) |
|
|
DEFAULT_USER_ID = int(os.getenv("DEFAULT_USER_ID", "30")) |
|
|
DEFAULT_PERSONALIZATION_ID = int(os.getenv("DEFAULT_PERSONALIZATION_ID", "100")) |
|
|
API_TIMEOUT = int(os.getenv("API_TIMEOUT", "30")) |
|
|
|
|
|
|
|
|
VALID_USERNAME = os.getenv("APP_USERNAME") |
|
|
VALID_PASSWORD = os.getenv("APP_PASSWORD") |
|
|
|
|
|
|
|
|
st.set_page_config( |
|
|
page_title="Base Course Personalization", |
|
|
layout="wide", |
|
|
initial_sidebar_state="collapsed", |
|
|
page_icon="π", |
|
|
menu_items={ |
|
|
'Get Help': None, |
|
|
'Report a bug': None, |
|
|
'About': None |
|
|
} |
|
|
) |
|
|
|
|
|
|
|
|
st.markdown(""" |
|
|
<style> |
|
|
/* Dark theme colors */ |
|
|
:root { |
|
|
--background-color: #1E1E1E; |
|
|
--card-background: #2D2D2D; |
|
|
--text-color: #E0E0E0; |
|
|
--accent-color: #4F97FF; |
|
|
--border-color: #444444; |
|
|
--header-color: #4F97FF; |
|
|
--subheader-color: #FFFFFF; |
|
|
--success-color: #10B981; |
|
|
--warning-color: #F59E0B; |
|
|
--error-color: #EF4444; |
|
|
} |
|
|
|
|
|
/* Main container styling */ |
|
|
.stApp { |
|
|
background-color: var(--background-color); |
|
|
color: var(--text-color); |
|
|
} |
|
|
|
|
|
/* Headers */ |
|
|
.main-header { |
|
|
font-size: 2.5rem; |
|
|
font-weight: 600; |
|
|
color: var(--header-color); |
|
|
margin-bottom: 1rem; |
|
|
text-align: center; |
|
|
text-shadow: 0 2px 4px rgba(79, 151, 255, 0.3); |
|
|
} |
|
|
|
|
|
.section-header { |
|
|
font-size: 1.5rem; |
|
|
font-weight: 500; |
|
|
color: var(--subheader-color); |
|
|
margin-top: 2rem; |
|
|
margin-bottom: 1rem; |
|
|
padding-bottom: 0.5rem; |
|
|
border-bottom: 2px solid var(--border-color); |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 0.5rem; |
|
|
} |
|
|
|
|
|
/* Form container */ |
|
|
div[data-testid="stForm"] { |
|
|
background-color: var(--card-background); |
|
|
padding: 2rem; |
|
|
border-radius: 12px; |
|
|
box-shadow: 0 8px 25px rgba(0,0,0,0.4); |
|
|
border: 1px solid var(--border-color); |
|
|
margin-bottom: 1rem; |
|
|
} |
|
|
|
|
|
/* Button styling */ |
|
|
.stButton>button { |
|
|
background: linear-gradient(135deg, var(--accent-color) 0%, #3B82F6 100%); |
|
|
color: white; |
|
|
border-radius: 8px; |
|
|
padding: 0.75rem 2rem; |
|
|
font-weight: 600; |
|
|
border: none; |
|
|
transition: all 0.3s ease; |
|
|
box-shadow: 0 4px 15px rgba(79, 151, 255, 0.3); |
|
|
} |
|
|
|
|
|
.stButton>button:hover { |
|
|
transform: translateY(-2px); |
|
|
box-shadow: 0 6px 20px rgba(79, 151, 255, 0.4); |
|
|
} |
|
|
|
|
|
/* Input fields styling */ |
|
|
div[data-baseweb="select"] > div { |
|
|
background-color: var(--background-color); |
|
|
border: 2px solid var(--border-color); |
|
|
border-radius: 8px; |
|
|
transition: border-color 0.3s ease; |
|
|
} |
|
|
|
|
|
div[data-baseweb="select"] > div:focus-within { |
|
|
border-color: var(--accent-color); |
|
|
box-shadow: 0 0 0 3px rgba(79, 151, 255, 0.1); |
|
|
} |
|
|
|
|
|
.stTextInput > div > div > input, |
|
|
.stNumberInput > div > div > input, |
|
|
.stTextArea > div > div > textarea { |
|
|
background-color: var(--background-color); |
|
|
color: var(--text-color); |
|
|
border: 2px solid var(--border-color); |
|
|
border-radius: 8px; |
|
|
padding: 0.75rem; |
|
|
transition: border-color 0.3s ease; |
|
|
} |
|
|
|
|
|
.stTextInput > div > div > input:focus, |
|
|
.stNumberInput > div > div > input:focus, |
|
|
.stTextArea > div > div > textarea:focus { |
|
|
border-color: var(--accent-color); |
|
|
box-shadow: 0 0 0 3px rgba(79, 151, 255, 0.1); |
|
|
} |
|
|
|
|
|
/* Topic container styling */ |
|
|
.topic-container { |
|
|
background-color: var(--card-background); |
|
|
padding: 1.5rem; |
|
|
border-radius: 10px; |
|
|
margin-bottom: 1rem; |
|
|
border: 1px solid var(--border-color); |
|
|
position: relative; |
|
|
} |
|
|
|
|
|
.topic-header { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 0.5rem; |
|
|
margin-bottom: 1rem; |
|
|
font-weight: 600; |
|
|
color: var(--accent-color); |
|
|
} |
|
|
|
|
|
/* Radio buttons */ |
|
|
.stRadio > div { |
|
|
background-color: transparent; |
|
|
gap: 1rem; |
|
|
} |
|
|
|
|
|
.stRadio > div > label > div { |
|
|
color: var(--text-color) !important; |
|
|
font-weight: 500; |
|
|
} |
|
|
|
|
|
/* Labels */ |
|
|
.stSelectbox > label, |
|
|
.stTextInput > label, |
|
|
.stNumberInput > label, |
|
|
.stTextArea > label, |
|
|
.stRadio > label { |
|
|
color: var(--text-color) !important; |
|
|
font-weight: 500; |
|
|
margin-bottom: 0.5rem; |
|
|
} |
|
|
|
|
|
/* Toggle switch */ |
|
|
.stToggle > label { |
|
|
color: var(--text-color) !important; |
|
|
font-weight: 500; |
|
|
} |
|
|
|
|
|
/* Spacing */ |
|
|
div.block-container { |
|
|
padding-top: 2rem; |
|
|
max-width: 1200px; |
|
|
} |
|
|
|
|
|
hr { |
|
|
margin: 2rem 0; |
|
|
border: none; |
|
|
border-top: 1px solid var(--border-color); |
|
|
} |
|
|
|
|
|
/* API Response Container */ |
|
|
.api-response { |
|
|
background-color: #1A1A1A; |
|
|
border-radius: 10px; |
|
|
padding: 1.5rem; |
|
|
border-left: 4px solid var(--accent-color); |
|
|
margin: 1rem 0; |
|
|
} |
|
|
|
|
|
.session-info { |
|
|
background-color: var(--card-background); |
|
|
padding: 1.5rem; |
|
|
border-radius: 10px; |
|
|
margin: 1rem 0; |
|
|
border: 1px solid var(--border-color); |
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.2); |
|
|
} |
|
|
|
|
|
/* Action buttons container */ |
|
|
.action-buttons { |
|
|
display: flex; |
|
|
gap: 1rem; |
|
|
margin: 1rem 0; |
|
|
justify-content: flex-start; |
|
|
} |
|
|
|
|
|
/* Small action buttons */ |
|
|
.small-button { |
|
|
padding: 0.5rem 1rem; |
|
|
font-size: 0.875rem; |
|
|
} |
|
|
|
|
|
/* Success/Error styling */ |
|
|
.stSuccess, .stError, .stWarning, .stInfo { |
|
|
border-radius: 8px; |
|
|
padding: 1rem; |
|
|
margin: 1rem 0; |
|
|
} |
|
|
|
|
|
/* Login container */ |
|
|
.login-container { |
|
|
background-color: var(--card-background); |
|
|
padding: 3rem; |
|
|
border-radius: 15px; |
|
|
box-shadow: 0 10px 30px rgba(0,0,0,0.5); |
|
|
border: 1px solid var(--border-color); |
|
|
} |
|
|
</style> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
if 'topics_list' not in st.session_state: |
|
|
st.session_state.topics_list = [{"topic_title": "What is Flask", "chapter_title": "Introduction to Flask"}] |
|
|
if 'session_ids' not in st.session_state: |
|
|
st.session_state.session_ids = [] |
|
|
if 'authenticated' not in st.session_state: |
|
|
st.session_state.authenticated = False |
|
|
|
|
|
|
|
|
if not st.session_state.authenticated: |
|
|
st.markdown('<h1 class="main-header">π Login to Course Personalization</h1>', unsafe_allow_html=True) |
|
|
|
|
|
col1, col2, col3 = st.columns([1, 2, 1]) |
|
|
with col2: |
|
|
with st.container(): |
|
|
st.markdown('<div class="login-container">', unsafe_allow_html=True) |
|
|
|
|
|
with st.form("login_form"): |
|
|
st.markdown('<div class="section-header">π Authentication Required</div>', unsafe_allow_html=True) |
|
|
|
|
|
username = st.text_input("Username", placeholder="Enter username", key="login_username") |
|
|
password = st.text_input("Password", placeholder="Enter password", type="password", key="login_password") |
|
|
|
|
|
login_submitted = st.form_submit_button("π Login", use_container_width=True) |
|
|
|
|
|
if login_submitted: |
|
|
if username == VALID_USERNAME and password == VALID_PASSWORD: |
|
|
st.session_state.authenticated = True |
|
|
st.success("Login successful! Redirecting...") |
|
|
time.sleep(1) |
|
|
st.rerun() |
|
|
else: |
|
|
st.error("β Invalid username or password") |
|
|
|
|
|
st.markdown('</div>', unsafe_allow_html=True) |
|
|
|
|
|
st.markdown("---") |
|
|
st.markdown(f""" |
|
|
<div style="text-align: center; color: #888888;"> |
|
|
<small>Demo Credentials: {VALID_USERNAME} / {VALID_PASSWORD if len(VALID_PASSWORD) <= 10 else VALID_PASSWORD[:3] + '...'}</small> |
|
|
</div> |
|
|
""", unsafe_allow_html=True) |
|
|
st.stop() |
|
|
|
|
|
|
|
|
st.markdown('<h1 class="main-header">π Base Course Personalization</h1>', unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
col1, col2, col3 = st.columns([2, 1, 1]) |
|
|
with col3: |
|
|
if st.button("π Logout", key="logout_btn"): |
|
|
st.session_state.authenticated = False |
|
|
st.session_state.topics_list = [{"topic_title": "What is Flask", "chapter_title": "Introduction to Flask"}] |
|
|
st.session_state.session_ids = [] |
|
|
st.rerun() |
|
|
|
|
|
|
|
|
with st.form("personalization_form", clear_on_submit=False): |
|
|
|
|
|
st.markdown('<div class="section-header">π Topics Configuration</div>', unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
topic_mode = st.radio( |
|
|
"Select Topic Mode", |
|
|
["Single Topic", "Multiple Topics"], |
|
|
horizontal=True, |
|
|
help="Choose whether to process one topic or multiple topics" |
|
|
) |
|
|
|
|
|
if topic_mode == "Single Topic": |
|
|
st.markdown('<div class="topic-container">', unsafe_allow_html=True) |
|
|
st.markdown('<div class="topic-header">π Single Topic Configuration</div>', unsafe_allow_html=True) |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
with col1: |
|
|
single_topic_title = st.text_input("Topic Title", value="What is Flask", help="Enter the topic title", key="single_topic") |
|
|
with col2: |
|
|
single_chapter_title = st.text_input("Chapter Title", value="Introduction to Flask", help="Enter the chapter title", key="single_chapter") |
|
|
|
|
|
st.markdown('</div>', unsafe_allow_html=True) |
|
|
|
|
|
else: |
|
|
st.markdown("### π Multiple Topics Configuration") |
|
|
|
|
|
|
|
|
for i, topic in enumerate(st.session_state.topics_list): |
|
|
st.markdown(f'<div class="topic-container">', unsafe_allow_html=True) |
|
|
st.markdown(f'<div class="topic-header">π Topic {i+1}</div>', unsafe_allow_html=True) |
|
|
|
|
|
col1, col2, col3 = st.columns([3, 3, 1]) |
|
|
with col1: |
|
|
topic_title = st.text_input( |
|
|
"Topic Title", |
|
|
value=topic["topic_title"], |
|
|
key=f"topic_title_{i}", |
|
|
help="Enter the topic title" |
|
|
) |
|
|
st.session_state.topics_list[i]["topic_title"] = topic_title |
|
|
|
|
|
with col2: |
|
|
chapter_title = st.text_input( |
|
|
"Chapter Title", |
|
|
value=topic["chapter_title"], |
|
|
key=f"chapter_title_{i}", |
|
|
help="Enter the chapter title" |
|
|
) |
|
|
st.session_state.topics_list[i]["chapter_title"] = chapter_title |
|
|
|
|
|
with col3: |
|
|
if len(st.session_state.topics_list) > 1: |
|
|
st.markdown("<br>", unsafe_allow_html=True) |
|
|
if st.button("ποΈ", key=f"remove_{i}", help="Remove this topic"): |
|
|
st.session_state.topics_list.pop(i) |
|
|
st.rerun() |
|
|
|
|
|
st.markdown('</div>', unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
st.markdown('<div class="section-header">π£οΈ Language & Voice Settings</div>', unsafe_allow_html=True) |
|
|
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
with col1: |
|
|
target_language = st.selectbox( |
|
|
"Target Language", |
|
|
["english", "hindi", "marathi", "kannada", "punjabi","gujarati"], |
|
|
index=0, |
|
|
format_func=lambda x: x.capitalize(), |
|
|
help="Select the target language for content generation" |
|
|
) |
|
|
|
|
|
with col2: |
|
|
tts_gender = st.selectbox( |
|
|
"Voice Gender", |
|
|
["male", "female"], |
|
|
index=0, |
|
|
format_func=lambda x: x.capitalize(), |
|
|
help="Select the voice gender for text-to-speech" |
|
|
) |
|
|
|
|
|
with col3: |
|
|
tts_voice = st.selectbox( |
|
|
"Voice Style", |
|
|
["onyx", "echo", "soprano", "alto", "robotic"], |
|
|
index=0, |
|
|
format_func=lambda x: x.capitalize(), |
|
|
help="Select the voice style for text-to-speech" |
|
|
) |
|
|
|
|
|
|
|
|
st.markdown('<div class="section-header">π» Technical Settings</div>', unsafe_allow_html=True) |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
with col1: |
|
|
programming_language = st.selectbox( |
|
|
"Programming Language", |
|
|
["python", "java", "javascript", "c++", "go"], |
|
|
index=0, |
|
|
format_func=lambda x: x.capitalize(), |
|
|
help="Select the primary programming language for examples" |
|
|
) |
|
|
with col2: |
|
|
st.markdown("<br>", unsafe_allow_html=True) |
|
|
toggle_hinglish = st.toggle("Enable Hinglish", value=True, help="Enable mixing of Hindi and English") |
|
|
|
|
|
|
|
|
st.markdown("<br>", unsafe_allow_html=True) |
|
|
col1, col2, col3 = st.columns([1, 2, 1]) |
|
|
with col2: |
|
|
submitted = st.form_submit_button("π Generate Base Course", use_container_width=True) |
|
|
|
|
|
|
|
|
if topic_mode == "Multiple Topics": |
|
|
st.markdown('<div class="action-buttons">', unsafe_allow_html=True) |
|
|
col1, col2, col3, col4 = st.columns([2, 2, 2, 4]) |
|
|
with col1: |
|
|
if st.button(" Add Topic", key="add_topic", help="Add a new topic"): |
|
|
st.session_state.topics_list.append({ |
|
|
"topic_title": f"Topic {len(st.session_state.topics_list) + 1}", |
|
|
"chapter_title": f"Chapter {len(st.session_state.topics_list) + 1}" |
|
|
}) |
|
|
st.rerun() |
|
|
with col2: |
|
|
if st.button(" Reset All", key="reset_topics", help="Reset to default topics"): |
|
|
st.session_state.topics_list = [{"topic_title": "What is Flask", "chapter_title": "Introduction to Flask"}] |
|
|
st.rerun() |
|
|
st.markdown('</div>', unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
if submitted: |
|
|
|
|
|
if topic_mode == "Single Topic": |
|
|
topics_to_process = [{"topic_title": single_topic_title, "chapter_title": single_chapter_title}] |
|
|
else: |
|
|
topics_to_process = st.session_state.topics_list |
|
|
|
|
|
|
|
|
valid_topics = [] |
|
|
for topic in topics_to_process: |
|
|
if topic["topic_title"].strip() and topic["chapter_title"].strip(): |
|
|
valid_topics.append(topic) |
|
|
|
|
|
if not valid_topics: |
|
|
st.error("β Please enter at least one topic with both topic title and chapter title") |
|
|
else: |
|
|
|
|
|
with st.spinner("π¬ Generating your personalized course... This may take a few moments."): |
|
|
progress_bar = st.progress(0) |
|
|
for i in range(100): |
|
|
time.sleep(0.02) |
|
|
progress_bar.progress(i + 1) |
|
|
|
|
|
|
|
|
course_id = DEFAULT_COURSE_ID |
|
|
user_id = DEFAULT_USER_ID |
|
|
personalization_id = DEFAULT_PERSONALIZATION_ID |
|
|
|
|
|
|
|
|
user_profile = { |
|
|
"personalized": True, |
|
|
"user_name": "System User", |
|
|
"user_age": 25, |
|
|
"user_gender": "male", |
|
|
"user_tech_knowledge": "beginner", |
|
|
"user_preferred_activity": "coding, learning, technology", |
|
|
"user_food": "healthy food, vegetarian", |
|
|
"user_physical_activities": "walking, yoga", |
|
|
"learning_style": "visual", |
|
|
"target_language": target_language, |
|
|
"tts_gender": tts_gender, |
|
|
"tts_voice": tts_voice, |
|
|
"toggle_hinglish": toggle_hinglish, |
|
|
"run_visualization": False, |
|
|
"subtitle": "", |
|
|
"age_group": "18-25" |
|
|
} |
|
|
|
|
|
|
|
|
settings = { |
|
|
"target_language": target_language, |
|
|
"tts_gender": tts_gender, |
|
|
"tts_voice": tts_voice, |
|
|
"toggle_hinglish": toggle_hinglish, |
|
|
"run_visualization": False, |
|
|
"subtitle": "", |
|
|
"programming_language": programming_language, |
|
|
"slide_colour": "blue", |
|
|
"video_type": "base_video" |
|
|
} |
|
|
|
|
|
|
|
|
topics_data = [] |
|
|
for i, topic in enumerate(valid_topics): |
|
|
topics_data.append({ |
|
|
"topic_id": 10834 + i, |
|
|
"topic_title": topic["topic_title"].strip(), |
|
|
"chapter_id": 647, |
|
|
"chapter_title": topic["chapter_title"].strip(), |
|
|
"course_id": course_id, |
|
|
"video_url": f"https://techlearn-dev.s3.ap-south-1.amazonaws.com/course_videos/47/647/172906{4365+i*50}.mp4", |
|
|
"video_duration": 462 + i*20, |
|
|
"sequence_number": i + 1, |
|
|
}) |
|
|
|
|
|
|
|
|
payload = { |
|
|
"personalization_id": personalization_id, |
|
|
"user_id": user_id, |
|
|
"course_id": course_id, |
|
|
"total_videos": len(topics_data), |
|
|
"created_at": datetime.utcnow().isoformat(), |
|
|
"user_profile": user_profile, |
|
|
"topics": topics_data, |
|
|
"settings": settings |
|
|
} |
|
|
|
|
|
|
|
|
try: |
|
|
headers = { |
|
|
'Content-Type': 'application/json' |
|
|
} |
|
|
|
|
|
response = requests.post(API_ENDPOINT, json=payload, headers=headers, timeout=API_TIMEOUT) |
|
|
|
|
|
if response.status_code == 200: |
|
|
response_data = response.json() |
|
|
session_ids = response_data.get("session_ids", []) |
|
|
|
|
|
st.success(" Base Course started successfully!") |
|
|
|
|
|
|
|
|
st.session_state.session_ids.extend(session_ids) |
|
|
|
|
|
|
|
|
st.markdown("### Generation Summary") |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
with col1: |
|
|
st.markdown(f""" |
|
|
**Course ID**: `{course_id}` |
|
|
**Programming Language**: {programming_language.capitalize()} |
|
|
**Target Language**: {target_language.capitalize()} |
|
|
""") |
|
|
with col2: |
|
|
st.markdown(f""" |
|
|
**Voice**: {tts_voice.capitalize()} ({tts_gender.capitalize()}) |
|
|
**Topics Count**: {len(valid_topics)} |
|
|
**Hinglish**: {"Enabled" if toggle_hinglish else "Disabled"} |
|
|
""") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
else: |
|
|
st.error(f" API Error: {response.status_code}") |
|
|
if response.text: |
|
|
st.error(f"**Error Details**: {response.text}") |
|
|
|
|
|
except requests.exceptions.Timeout: |
|
|
st.error(" Request timed out. Please try again later.") |
|
|
except requests.exceptions.ConnectionError: |
|
|
st.error("π Connection error. Please check your internet connection.") |
|
|
except Exception as e: |
|
|
st.error(f" API call failed: {str(e)}") |
|
|
|
|
|
|
|
|
with st.expander("π Debug Information", expanded=False): |
|
|
st.warning("Request payload for debugging:") |
|
|
st.json(payload) |
|
|
|