Update app.py
Browse files
app.py
CHANGED
|
@@ -6,7 +6,6 @@ import requests
|
|
| 6 |
import boto3
|
| 7 |
import uuid
|
| 8 |
import os
|
| 9 |
-
from AdminDashboard import render_admin_dashboard
|
| 10 |
|
| 11 |
# Load configuration from environment variables (Hugging Face Spaces)
|
| 12 |
API_ENDPOINT = os.getenv("API_ENDPOINT", "https://oau6sljd4l.execute-api.ap-south-1.amazonaws.com/production/process")
|
|
@@ -263,13 +262,44 @@ st.markdown("""
|
|
| 263 |
border: 1px solid var(--border-color);
|
| 264 |
}
|
| 265 |
|
| 266 |
-
/*
|
| 267 |
-
.
|
| 268 |
background-color: var(--card-background);
|
| 269 |
-
padding:
|
| 270 |
-
border-radius:
|
| 271 |
-
margin-bottom: 2rem;
|
| 272 |
border: 1px solid var(--border-color);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
}
|
| 274 |
</style>
|
| 275 |
""", unsafe_allow_html=True)
|
|
@@ -284,179 +314,160 @@ if 'authenticated' not in st.session_state:
|
|
| 284 |
if 'current_page' not in st.session_state:
|
| 285 |
st.session_state.current_page = "Course Generation"
|
| 286 |
|
| 287 |
-
#
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
password = st.text_input("Password", placeholder="Enter password", type="password", key="login_password")
|
| 306 |
-
|
| 307 |
-
login_submitted = st.form_submit_button("π Login", use_container_width=True)
|
| 308 |
-
|
| 309 |
-
if login_submitted:
|
| 310 |
-
if VALID_USERNAME and VALID_PASSWORD and username == VALID_USERNAME and password == VALID_PASSWORD:
|
| 311 |
-
st.session_state.authenticated = True
|
| 312 |
-
st.success("Login successful! Redirecting...")
|
| 313 |
-
time.sleep(1)
|
| 314 |
-
st.rerun()
|
| 315 |
-
else:
|
| 316 |
-
st.error("β Invalid username or password")
|
| 317 |
-
|
| 318 |
-
st.markdown('</div>', unsafe_allow_html=True)
|
| 319 |
-
|
| 320 |
-
st.markdown("---")
|
| 321 |
-
st.markdown("""
|
| 322 |
-
<div style="text-align: center; color: #888888;">
|
| 323 |
-
<small>Please contact administrator for access credentials</small>
|
| 324 |
-
</div>
|
| 325 |
-
""", unsafe_allow_html=True)
|
| 326 |
-
st.stop()
|
| 327 |
-
|
| 328 |
-
# Navigation and Header (after login)
|
| 329 |
-
col1, col2, col3, col4 = st.columns([3, 2, 2, 1])
|
| 330 |
-
with col1:
|
| 331 |
-
st.markdown('<h1 class="main-header">π Course Management System</h1>', unsafe_allow_html=True)
|
| 332 |
-
|
| 333 |
-
with col2:
|
| 334 |
-
page = st.selectbox(
|
| 335 |
-
"π Navigate to:",
|
| 336 |
-
["Course Generation", "Admin Dashboard"],
|
| 337 |
-
index=0 if st.session_state.current_page == "Course Generation" else 1,
|
| 338 |
-
key="page_selector"
|
| 339 |
-
)
|
| 340 |
-
st.session_state.current_page = page
|
| 341 |
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 349 |
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
with col1:
|
| 361 |
-
selected_course = st.selectbox(
|
| 362 |
-
"Select Course Type",
|
| 363 |
-
options=list(COURSE_CONFIGS.keys()),
|
| 364 |
-
index=0,
|
| 365 |
-
help="Choose the course you want to generate content for"
|
| 366 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 367 |
|
| 368 |
-
|
| 369 |
-
st.info(f"**Selected:** {selected_course} (Course ID: {COURSE_CONFIGS[selected_course]['course_id']})")
|
| 370 |
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
st.markdown(f'<div class="topic-container">', unsafe_allow_html=True)
|
| 377 |
-
st.markdown(f'<div class="topic-header">π Topic {i+1}</div>', unsafe_allow_html=True)
|
| 378 |
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
topic_title = st.text_input(
|
| 382 |
-
"Topic Title",
|
| 383 |
-
value=topic["topic_title"],
|
| 384 |
-
key=f"topic_title_{i}",
|
| 385 |
-
help="Enter the topic title"
|
| 386 |
-
)
|
| 387 |
-
st.session_state.topics_list[i]["topic_title"] = topic_title
|
| 388 |
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
value=topic["chapter_title"],
|
| 393 |
-
key=f"chapter_title_{i}",
|
| 394 |
-
help="Enter the chapter title"
|
| 395 |
-
)
|
| 396 |
-
st.session_state.topics_list[i]["chapter_title"] = chapter_title
|
| 397 |
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 404 |
|
| 405 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 406 |
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 410 |
with col1:
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
st.rerun()
|
| 417 |
with col2:
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 422 |
-
|
| 423 |
-
#
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
st.markdown('<div class="section-header">π£οΈ Language & Voice Settings</div>', unsafe_allow_html=True)
|
| 427 |
-
|
| 428 |
-
col1, col2, col3 = st.columns(3)
|
| 429 |
-
with col1:
|
| 430 |
-
target_language = st.selectbox(
|
| 431 |
-
"Target Language",
|
| 432 |
-
["english", "hindi", "marathi", "kannada", "punjabi","gujarati"],
|
| 433 |
-
index=0,
|
| 434 |
-
format_func=lambda x: x.capitalize(),
|
| 435 |
-
help="Select the target language for content generation"
|
| 436 |
-
)
|
| 437 |
-
|
| 438 |
-
with col2:
|
| 439 |
-
tts_gender = st.selectbox(
|
| 440 |
-
"Voice Gender",
|
| 441 |
-
["male", "female"],
|
| 442 |
-
index=0,
|
| 443 |
-
format_func=lambda x: x.capitalize(),
|
| 444 |
-
help="Select the voice gender for text-to-speech"
|
| 445 |
-
)
|
| 446 |
-
|
| 447 |
-
with col3:
|
| 448 |
-
tts_voice = st.selectbox(
|
| 449 |
-
"Voice Style",
|
| 450 |
-
["alloy", "echo", "fable", "onyx", "nova", "shimmer"],
|
| 451 |
-
index=3, # Default to "onyx"
|
| 452 |
-
format_func=lambda x: x.capitalize(),
|
| 453 |
-
help="Select the voice style for text-to-speech"
|
| 454 |
-
)
|
| 455 |
-
|
| 456 |
-
# Technical Settings Section
|
| 457 |
-
st.markdown('<div class="section-header">π» Technical Settings</div>', unsafe_allow_html=True)
|
| 458 |
|
| 459 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 460 |
with col1:
|
| 461 |
# Comprehensive list of programming languages, frameworks, and databases
|
| 462 |
tech_options = [
|
|
@@ -695,4 +706,268 @@ else:
|
|
| 695 |
# Show payload for debugging
|
| 696 |
with st.expander("π Debug Information", expanded=False):
|
| 697 |
st.warning("Request payload for debugging:")
|
| 698 |
-
st.json(payload)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
import boto3
|
| 7 |
import uuid
|
| 8 |
import os
|
|
|
|
| 9 |
|
| 10 |
# Load configuration from environment variables (Hugging Face Spaces)
|
| 11 |
API_ENDPOINT = os.getenv("API_ENDPOINT", "https://oau6sljd4l.execute-api.ap-south-1.amazonaws.com/production/process")
|
|
|
|
| 262 |
border: 1px solid var(--border-color);
|
| 263 |
}
|
| 264 |
|
| 265 |
+
/* Admin Dashboard specific styles */
|
| 266 |
+
.admin-card {
|
| 267 |
background-color: var(--card-background);
|
| 268 |
+
padding: 2rem;
|
| 269 |
+
border-radius: 12px;
|
|
|
|
| 270 |
border: 1px solid var(--border-color);
|
| 271 |
+
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
| 272 |
+
margin-bottom: 2rem;
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
.status-badge {
|
| 276 |
+
padding: 0.3rem 0.8rem;
|
| 277 |
+
border-radius: 20px;
|
| 278 |
+
font-size: 0.8rem;
|
| 279 |
+
font-weight: 600;
|
| 280 |
+
text-transform: uppercase;
|
| 281 |
+
letter-spacing: 0.5px;
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
.status-completed { background-color: #10B981; color: white; }
|
| 285 |
+
.status-started { background-color: #F59E0B; color: white; }
|
| 286 |
+
.status-failed { background-color: #EF4444; color: white; }
|
| 287 |
+
|
| 288 |
+
.download-link {
|
| 289 |
+
background-color: var(--accent-color);
|
| 290 |
+
color: white;
|
| 291 |
+
padding: 0.5rem 1rem;
|
| 292 |
+
border-radius: 6px;
|
| 293 |
+
text-decoration: none;
|
| 294 |
+
font-size: 0.9rem;
|
| 295 |
+
font-weight: 500;
|
| 296 |
+
transition: background-color 0.3s ease;
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
.download-link:hover {
|
| 300 |
+
background-color: #3B82F6;
|
| 301 |
+
color: white;
|
| 302 |
+
text-decoration: none;
|
| 303 |
}
|
| 304 |
</style>
|
| 305 |
""", unsafe_allow_html=True)
|
|
|
|
| 314 |
if 'current_page' not in st.session_state:
|
| 315 |
st.session_state.current_page = "Course Generation"
|
| 316 |
|
| 317 |
+
# Initialize DynamoDB client
|
| 318 |
+
@st.cache_resource
|
| 319 |
+
def get_dynamodb_client():
|
| 320 |
+
"""Initialize DynamoDB client with credentials from environment"""
|
| 321 |
+
try:
|
| 322 |
+
if AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY:
|
| 323 |
+
return boto3.client(
|
| 324 |
+
'dynamodb',
|
| 325 |
+
region_name=DYNAMODB_REGION,
|
| 326 |
+
aws_access_key_id=AWS_ACCESS_KEY_ID,
|
| 327 |
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY
|
| 328 |
+
)
|
| 329 |
+
else:
|
| 330 |
+
# Try to use default credentials (IAM role, etc.)
|
| 331 |
+
return boto3.client('dynamodb', region_name=DYNAMODB_REGION)
|
| 332 |
+
except Exception as e:
|
| 333 |
+
st.error(f"Failed to initialize DynamoDB client: {e}")
|
| 334 |
+
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
|
| 336 |
+
# Initialize S3 client
|
| 337 |
+
@st.cache_resource
|
| 338 |
+
def get_s3_client():
|
| 339 |
+
"""Initialize S3 client with credentials from environment"""
|
| 340 |
+
try:
|
| 341 |
+
if AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY:
|
| 342 |
+
return boto3.client(
|
| 343 |
+
's3',
|
| 344 |
+
region_name=AWS_DEFAULT_REGION,
|
| 345 |
+
aws_access_key_id=AWS_ACCESS_KEY_ID,
|
| 346 |
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY
|
| 347 |
+
)
|
| 348 |
+
else:
|
| 349 |
+
return boto3.client('s3', region_name=AWS_DEFAULT_REGION)
|
| 350 |
+
except Exception as e:
|
| 351 |
+
st.error(f"Failed to initialize S3 client: {e}")
|
| 352 |
+
return None
|
| 353 |
|
| 354 |
+
def get_session_data(session_id):
|
| 355 |
+
"""Get session data from DynamoDB"""
|
| 356 |
+
dynamodb = get_dynamodb_client()
|
| 357 |
+
if not dynamodb:
|
| 358 |
+
return None
|
| 359 |
+
|
| 360 |
+
try:
|
| 361 |
+
response = dynamodb.get_item(
|
| 362 |
+
TableName=SESSION_TABLE,
|
| 363 |
+
Key={'session_id': {'S': session_id}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 364 |
)
|
| 365 |
+
|
| 366 |
+
if 'Item' in response:
|
| 367 |
+
item = response['Item']
|
| 368 |
+
return {
|
| 369 |
+
'session_id': item.get('session_id', {}).get('S', ''),
|
| 370 |
+
'status': item.get('status', {}).get('S', ''),
|
| 371 |
+
'node': item.get('node', {}).get('S', ''),
|
| 372 |
+
'course_id': item.get('course_id', {}).get('N', '0'),
|
| 373 |
+
'topic_id': item.get('topic_id', {}).get('N', '0'),
|
| 374 |
+
'topic_title': item.get('topic_title', {}).get('S', ''),
|
| 375 |
+
'video_url': item.get('video_url', {}).get('S', ''),
|
| 376 |
+
'created_at': item.get('created_at', {}).get('S', ''),
|
| 377 |
+
'updated_at': item.get('updated_at', {}).get('S', '')
|
| 378 |
+
}
|
| 379 |
+
except Exception as e:
|
| 380 |
+
st.error(f"Error fetching session data: {e}")
|
| 381 |
|
| 382 |
+
return None
|
|
|
|
| 383 |
|
| 384 |
+
def update_session_status(session_id, status, node=None, video_url=None):
|
| 385 |
+
"""Update session status in DynamoDB"""
|
| 386 |
+
dynamodb = get_dynamodb_client()
|
| 387 |
+
if not dynamodb:
|
| 388 |
+
return False
|
|
|
|
|
|
|
| 389 |
|
| 390 |
+
try:
|
| 391 |
+
now = datetime.utcnow().isoformat() + "Z"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 392 |
|
| 393 |
+
update_expr = "SET updated_at = :u, #st = :s"
|
| 394 |
+
expr_attr_names = {"#st": "status", "#ut": "updated_at"}
|
| 395 |
+
expr_attr_values = {":u": {"S": now}, ":s": {"S": status}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 396 |
|
| 397 |
+
if node:
|
| 398 |
+
update_expr += ", #nd = :n"
|
| 399 |
+
expr_attr_names["#nd"] = "node"
|
| 400 |
+
expr_attr_values[":n"] = {"S": node}
|
| 401 |
+
|
| 402 |
+
if video_url:
|
| 403 |
+
update_expr += ", video_url = :v"
|
| 404 |
+
expr_attr_values[":v"] = {"S": video_url}
|
| 405 |
+
|
| 406 |
+
dynamodb.update_item(
|
| 407 |
+
TableName=SESSION_TABLE,
|
| 408 |
+
Key={'session_id': {'S': session_id}},
|
| 409 |
+
UpdateExpression=update_expr,
|
| 410 |
+
ExpressionAttributeNames=expr_attr_names,
|
| 411 |
+
ExpressionAttributeValues=expr_attr_values
|
| 412 |
+
)
|
| 413 |
+
return True
|
| 414 |
+
except Exception as e:
|
| 415 |
+
st.error(f"Error updating session: {e}")
|
| 416 |
+
return False
|
| 417 |
+
|
| 418 |
+
def generate_s3_presigned_url(s3_key, bucket='tech-learn-state', expiry=3600):
|
| 419 |
+
"""Generate presigned URL for S3 object"""
|
| 420 |
+
s3_client = get_s3_client()
|
| 421 |
+
if not s3_client:
|
| 422 |
+
return None
|
| 423 |
|
| 424 |
+
try:
|
| 425 |
+
url = s3_client.generate_presigned_url(
|
| 426 |
+
'get_object',
|
| 427 |
+
Params={'Bucket': bucket, 'Key': s3_key},
|
| 428 |
+
ExpiresIn=expiry
|
| 429 |
+
)
|
| 430 |
+
return url
|
| 431 |
+
except Exception as e:
|
| 432 |
+
st.error(f"Error generating presigned URL: {e}")
|
| 433 |
+
return None
|
| 434 |
|
| 435 |
+
def render_admin_dashboard():
|
| 436 |
+
"""Render the simplified admin dashboard"""
|
| 437 |
+
st.markdown('<div class="section-header">ποΈ Admin Dashboard - Session Monitoring</div>', unsafe_allow_html=True)
|
| 438 |
+
|
| 439 |
+
# Session lookup section
|
| 440 |
+
st.markdown('<div class="admin-card">', unsafe_allow_html=True)
|
| 441 |
+
st.markdown("### π Session Lookup")
|
| 442 |
+
|
| 443 |
+
col1, col2 = st.columns([3, 1])
|
| 444 |
with col1:
|
| 445 |
+
session_id_input = st.text_input(
|
| 446 |
+
"Enter Session ID",
|
| 447 |
+
placeholder="e.g., 2025-01-15-12-30-45-abc123",
|
| 448 |
+
help="Enter the session ID to monitor"
|
| 449 |
+
)
|
|
|
|
| 450 |
with col2:
|
| 451 |
+
st.markdown("<br>", unsafe_allow_html=True)
|
| 452 |
+
lookup_button = st.button("π Lookup", use_container_width=True)
|
| 453 |
+
|
| 454 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 455 |
+
|
| 456 |
+
# Display session data
|
| 457 |
+
if lookup_button and session_id_input:
|
| 458 |
+
session_data = get_session_data(session_id_input.strip())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 459 |
|
| 460 |
+
if session_data:
|
| 461 |
+
st.markdown('<div class="admin-card">', unsafe_allow_html=True)
|
| 462 |
+
st.markdown(f"### π Session Details: `{session_id_input}`")
|
| 463 |
+
|
| 464 |
+
# Status badge
|
| 465 |
+
status = session_data['status']
|
| 466 |
+
status_class = f"status-{status.lower()}" if status.lower() in ['completed', 'started', 'failed'] else "status-started"
|
| 467 |
+
st.markdown(f'<span class="status-badge {status_class}">{status}</span>', unsafe_allow_html=True)
|
| 468 |
+
|
| 469 |
+
# Session info
|
| 470 |
+
col1, col2 = st.columns(2)
|
| 471 |
with col1:
|
| 472 |
# Comprehensive list of programming languages, frameworks, and databases
|
| 473 |
tech_options = [
|
|
|
|
| 706 |
# Show payload for debugging
|
| 707 |
with st.expander("π Debug Information", expanded=False):
|
| 708 |
st.warning("Request payload for debugging:")
|
| 709 |
+
st.json(payload))
|
| 710 |
+
with col1:
|
| 711 |
+
st.markdown(f"""
|
| 712 |
+
**Node**: {session_data['node']}
|
| 713 |
+
**Course ID**: {session_data['course_id']}
|
| 714 |
+
**Topic ID**: {session_data['topic_id']}
|
| 715 |
+
**Topic Title**: {session_data['topic_title']}
|
| 716 |
+
""")
|
| 717 |
+
with col2:
|
| 718 |
+
st.markdown(f"""
|
| 719 |
+
**Created**: {session_data['created_at'][:19] if session_data['created_at'] else 'N/A'}
|
| 720 |
+
**Updated**: {session_data['updated_at'][:19] if session_data['updated_at'] else 'N/A'}
|
| 721 |
+
**Status**: {session_data['status']}
|
| 722 |
+
""")
|
| 723 |
+
|
| 724 |
+
# Video URL section
|
| 725 |
+
if session_data['video_url']:
|
| 726 |
+
st.markdown("### π₯ Video Download")
|
| 727 |
+
st.markdown(f'<a href="{session_data["video_url"]}" class="download-link" target="_blank">π₯ Download Video</a>', unsafe_allow_html=True)
|
| 728 |
+
else:
|
| 729 |
+
st.info("No video URL available for this session")
|
| 730 |
+
|
| 731 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
| 732 |
+
|
| 733 |
+
# Session management section
|
| 734 |
+
st.markdown('<div class="admin-card">', unsafe_allow_html=True)
|
| 735 |
+
st.markdown("### βοΈ Session Management")
|
| 736 |
+
|
| 737 |
+
col1, col2, col3 = st.columns(3)
|
| 738 |
+
|
| 739 |
+
with col1:
|
| 740 |
+
new_status = st.selectbox("Update Status", ["STARTED", "COMPLETED", "FAILED"], key="status_update")
|
| 741 |
+
if st.button("π Update Status"):
|
| 742 |
+
if update_session_status(session_id_input.strip(), new_status):
|
| 743 |
+
st.success(f"Status updated to {new_status}")
|
| 744 |
+
st.rerun()
|
| 745 |
+
else:
|
| 746 |
+
st.error("Failed to update status")
|
| 747 |
+
|
| 748 |
+
with col2:
|
| 749 |
+
new_node = st.text_input("Update Node", placeholder="e.g., SlideCreation", key="node_update")
|
| 750 |
+
if st.button("π Update Node"):
|
| 751 |
+
if new_node.strip() and update_session_status(session_id_input.strip(), session_data['status'], new_node.strip()):
|
| 752 |
+
st.success(f"Node updated to {new_node}")
|
| 753 |
+
st.rerun()
|
| 754 |
+
else:
|
| 755 |
+
st.error("Failed to update node")
|
| 756 |
+
|
| 757 |
+
with col3:
|
| 758 |
+
# S3 URL Generator
|
| 759 |
+
s3_key_input = st.text_input("S3 Key", placeholder="video_states/session_id/final_video/...", key="s3_key")
|
| 760 |
+
if st.button("π Generate Video URL"):
|
| 761 |
+
if s3_key_input.strip():
|
| 762 |
+
video_url = generate_s3_presigned_url(s3_key_input.strip())
|
| 763 |
+
if video_url:
|
| 764 |
+
if update_session_status(session_id_input.strip(), session_data['status'], video_url=video_url):
|
| 765 |
+
st.success("Video URL generated and updated!")
|
| 766 |
+
st.code(video_url)
|
| 767 |
+
st.rerun()
|
| 768 |
+
else:
|
| 769 |
+
st.error("Failed to update video URL")
|
| 770 |
+
else:
|
| 771 |
+
st.error("Failed to generate presigned URL")
|
| 772 |
+
else:
|
| 773 |
+
st.error("Please enter S3 key")
|
| 774 |
+
|
| 775 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
| 776 |
+
|
| 777 |
+
else:
|
| 778 |
+
st.error(f"β Session not found: `{session_id_input}`")
|
| 779 |
+
|
| 780 |
+
# Recent sessions from session state
|
| 781 |
+
if st.session_state.session_ids:
|
| 782 |
+
st.markdown('<div class="admin-card">', unsafe_allow_html=True)
|
| 783 |
+
st.markdown("### π Recent Sessions from Current App Session")
|
| 784 |
+
|
| 785 |
+
for session_id in st.session_state.session_ids[-10:]: # Show last 10
|
| 786 |
+
session_data = get_session_data(session_id)
|
| 787 |
+
if session_data:
|
| 788 |
+
with st.expander(f"π {session_id} - {session_data['status']}", expanded=False):
|
| 789 |
+
col1, col2 = st.columns(2)
|
| 790 |
+
with col1:
|
| 791 |
+
st.write(f"**Node**: {session_data['node']}")
|
| 792 |
+
st.write(f"**Topic**: {session_data['topic_title']}")
|
| 793 |
+
st.write(f"**Status**: {session_data['status']}")
|
| 794 |
+
with col2:
|
| 795 |
+
st.write(f"**Updated**: {session_data['updated_at'][:19] if session_data['updated_at'] else 'N/A'}")
|
| 796 |
+
if session_data['video_url']:
|
| 797 |
+
st.markdown(f'<a href="{session_data["video_url"]}" class="download-link" target="_blank">π₯ Download</a>', unsafe_allow_html=True)
|
| 798 |
+
|
| 799 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
| 800 |
+
|
| 801 |
+
# Authentication check
|
| 802 |
+
if not st.session_state.authenticated:
|
| 803 |
+
st.markdown('<h1 class="main-header">π Login to Course Management</h1>', unsafe_allow_html=True)
|
| 804 |
+
|
| 805 |
+
# Check if credentials are configured
|
| 806 |
+
if not VALID_USERNAME or not VALID_PASSWORD:
|
| 807 |
+
st.error("β Authentication credentials not configured. Please contact administrator.")
|
| 808 |
+
st.stop()
|
| 809 |
+
|
| 810 |
+
col1, col2, col3 = st.columns([1, 2, 1])
|
| 811 |
+
with col2:
|
| 812 |
+
with st.container():
|
| 813 |
+
st.markdown('<div class="login-container">', unsafe_allow_html=True)
|
| 814 |
+
|
| 815 |
+
with st.form("login_form"):
|
| 816 |
+
st.markdown('<div class="section-header">π Authentication Required</div>', unsafe_allow_html=True)
|
| 817 |
+
|
| 818 |
+
username = st.text_input("Username", placeholder="Enter username", key="login_username")
|
| 819 |
+
password = st.text_input("Password", placeholder="Enter password", type="password", key="login_password")
|
| 820 |
+
|
| 821 |
+
login_submitted = st.form_submit_button("π Login", use_container_width=True)
|
| 822 |
+
|
| 823 |
+
if login_submitted:
|
| 824 |
+
if VALID_USERNAME and VALID_PASSWORD and username == VALID_USERNAME and password == VALID_PASSWORD:
|
| 825 |
+
st.session_state.authenticated = True
|
| 826 |
+
st.success("Login successful! Redirecting...")
|
| 827 |
+
time.sleep(1)
|
| 828 |
+
st.rerun()
|
| 829 |
+
else:
|
| 830 |
+
st.error("β Invalid username or password")
|
| 831 |
+
|
| 832 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
| 833 |
+
|
| 834 |
+
st.markdown("---")
|
| 835 |
+
st.markdown("""
|
| 836 |
+
<div style="text-align: center; color: #888888;">
|
| 837 |
+
<small>Please contact administrator for access credentials</small>
|
| 838 |
+
</div>
|
| 839 |
+
""", unsafe_allow_html=True)
|
| 840 |
+
st.stop()
|
| 841 |
+
|
| 842 |
+
# Navigation and Header (after login)
|
| 843 |
+
col1, col2, col3, col4 = st.columns([3, 2, 2, 1])
|
| 844 |
+
with col1:
|
| 845 |
+
st.markdown('<h1 class="main-header">π Course Management System</h1>', unsafe_allow_html=True)
|
| 846 |
+
|
| 847 |
+
with col2:
|
| 848 |
+
page = st.selectbox(
|
| 849 |
+
"π Navigate to:",
|
| 850 |
+
["Course Generation", "Admin Dashboard"],
|
| 851 |
+
index=0 if st.session_state.current_page == "Course Generation" else 1,
|
| 852 |
+
key="page_selector"
|
| 853 |
+
)
|
| 854 |
+
st.session_state.current_page = page
|
| 855 |
+
|
| 856 |
+
with col4:
|
| 857 |
+
if st.button("π Logout", key="logout_btn"):
|
| 858 |
+
st.session_state.authenticated = False
|
| 859 |
+
st.session_state.topics_list = [{"topic_title": "What is Flask", "chapter_title": "Introduction to Flask"}]
|
| 860 |
+
st.session_state.session_ids = []
|
| 861 |
+
st.session_state.current_page = "Course Generation"
|
| 862 |
+
st.rerun()
|
| 863 |
+
|
| 864 |
+
# Page routing
|
| 865 |
+
if st.session_state.current_page == "Admin Dashboard":
|
| 866 |
+
render_admin_dashboard()
|
| 867 |
+
else:
|
| 868 |
+
# Course Generation Page (Original Content)
|
| 869 |
+
|
| 870 |
+
# Course Selection Section
|
| 871 |
+
st.markdown('<div class="section-header">π Course Selection</div>', unsafe_allow_html=True)
|
| 872 |
+
|
| 873 |
+
col1, col2 = st.columns([2, 3])
|
| 874 |
+
with col1:
|
| 875 |
+
selected_course = st.selectbox(
|
| 876 |
+
"Select Course Type",
|
| 877 |
+
options=list(COURSE_CONFIGS.keys()),
|
| 878 |
+
index=0,
|
| 879 |
+
help="Choose the course you want to generate content for"
|
| 880 |
+
)
|
| 881 |
+
|
| 882 |
+
with col2:
|
| 883 |
+
st.info(f"**Selected:** {selected_course} (Course ID: {COURSE_CONFIGS[selected_course]['course_id']})")
|
| 884 |
+
|
| 885 |
+
# Topics Section (Outside form for dynamic interaction)
|
| 886 |
+
st.markdown('<div class="section-header">π Topics Configuration</div>', unsafe_allow_html=True)
|
| 887 |
+
|
| 888 |
+
# Display existing topics with better styling
|
| 889 |
+
for i, topic in enumerate(st.session_state.topics_list):
|
| 890 |
+
st.markdown(f'<div class="topic-container">', unsafe_allow_html=True)
|
| 891 |
+
st.markdown(f'<div class="topic-header">π Topic {i+1}</div>', unsafe_allow_html=True)
|
| 892 |
+
|
| 893 |
+
col1, col2, col3 = st.columns([3, 3, 1])
|
| 894 |
+
with col1:
|
| 895 |
+
topic_title = st.text_input(
|
| 896 |
+
"Topic Title",
|
| 897 |
+
value=topic["topic_title"],
|
| 898 |
+
key=f"topic_title_{i}",
|
| 899 |
+
help="Enter the topic title"
|
| 900 |
+
)
|
| 901 |
+
st.session_state.topics_list[i]["topic_title"] = topic_title
|
| 902 |
+
|
| 903 |
+
with col2:
|
| 904 |
+
chapter_title = st.text_input(
|
| 905 |
+
"Chapter Title",
|
| 906 |
+
value=topic["chapter_title"],
|
| 907 |
+
key=f"chapter_title_{i}",
|
| 908 |
+
help="Enter the chapter title"
|
| 909 |
+
)
|
| 910 |
+
st.session_state.topics_list[i]["chapter_title"] = chapter_title
|
| 911 |
+
|
| 912 |
+
with col3:
|
| 913 |
+
if len(st.session_state.topics_list) > 1:
|
| 914 |
+
st.markdown("<br>", unsafe_allow_html=True) # Add spacing
|
| 915 |
+
if st.button("ποΈ", key=f"remove_{i}", help="Remove this topic"):
|
| 916 |
+
st.session_state.topics_list.pop(i)
|
| 917 |
+
st.rerun()
|
| 918 |
+
|
| 919 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
| 920 |
+
|
| 921 |
+
# Add/Remove topic buttons outside the form
|
| 922 |
+
st.markdown('<div class="action-buttons">', unsafe_allow_html=True)
|
| 923 |
+
col1, col2, col3, col4 = st.columns([2, 2, 2, 4])
|
| 924 |
+
with col1:
|
| 925 |
+
if st.button("β Add Topic", key="add_topic", help="Add a new topic"):
|
| 926 |
+
st.session_state.topics_list.append({
|
| 927 |
+
"topic_title": f"Topic {len(st.session_state.topics_list) + 1}",
|
| 928 |
+
"chapter_title": f"Chapter {len(st.session_state.topics_list) + 1}"
|
| 929 |
+
})
|
| 930 |
+
st.rerun()
|
| 931 |
+
with col2:
|
| 932 |
+
if st.button("π Reset All", key="reset_topics", help="Reset to default topics"):
|
| 933 |
+
st.session_state.topics_list = [{"topic_title": "What is Flask", "chapter_title": "Introduction to Flask"}]
|
| 934 |
+
st.rerun()
|
| 935 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
| 936 |
+
|
| 937 |
+
# Main Form (for settings and submission)
|
| 938 |
+
with st.form("personalization_form", clear_on_submit=False):
|
| 939 |
+
# Language & Voice Settings Section
|
| 940 |
+
st.markdown('<div class="section-header">π£οΈ Language & Voice Settings</div>', unsafe_allow_html=True)
|
| 941 |
+
|
| 942 |
+
col1, col2, col3 = st.columns(3)
|
| 943 |
+
with col1:
|
| 944 |
+
target_language = st.selectbox(
|
| 945 |
+
"Target Language",
|
| 946 |
+
["english", "hindi", "marathi", "kannada", "punjabi","gujarati"],
|
| 947 |
+
index=0,
|
| 948 |
+
format_func=lambda x: x.capitalize(),
|
| 949 |
+
help="Select the target language for content generation"
|
| 950 |
+
)
|
| 951 |
+
|
| 952 |
+
with col2:
|
| 953 |
+
tts_gender = st.selectbox(
|
| 954 |
+
"Voice Gender",
|
| 955 |
+
["male", "female"],
|
| 956 |
+
index=0,
|
| 957 |
+
format_func=lambda x: x.capitalize(),
|
| 958 |
+
help="Select the voice gender for text-to-speech"
|
| 959 |
+
)
|
| 960 |
+
|
| 961 |
+
with col3:
|
| 962 |
+
tts_voice = st.selectbox(
|
| 963 |
+
"Voice Style",
|
| 964 |
+
["alloy", "echo", "fable", "onyx", "nova", "shimmer"],
|
| 965 |
+
index=3, # Default to "onyx"
|
| 966 |
+
format_func=lambda x: x.capitalize(),
|
| 967 |
+
help="Select the voice style for text-to-speech"
|
| 968 |
+
)
|
| 969 |
+
|
| 970 |
+
# Technical Settings Section
|
| 971 |
+
st.markdown('<div class="section-header">π» Technical Settings</div>', unsafe_allow_html=True)
|
| 972 |
+
|
| 973 |
+
col1, col2 = st.columns(2
|