banao-tech commited on
Commit
4b1e532
Β·
verified Β·
1 Parent(s): bfb8926

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +441 -166
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
- /* Navigation tab styling */
267
- .nav-tabs {
268
  background-color: var(--card-background);
269
- padding: 1rem;
270
- border-radius: 10px;
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
- # Authentication check
288
- if not st.session_state.authenticated:
289
- st.markdown('<h1 class="main-header">πŸ” Login to Course Management</h1>', unsafe_allow_html=True)
290
-
291
- # Check if credentials are configured
292
- if not VALID_USERNAME or not VALID_PASSWORD:
293
- st.error("❌ Authentication credentials not configured. Please contact administrator.")
294
- st.stop()
295
-
296
- col1, col2, col3 = st.columns([1, 2, 1])
297
- with col2:
298
- with st.container():
299
- st.markdown('<div class="login-container">', unsafe_allow_html=True)
300
-
301
- with st.form("login_form"):
302
- st.markdown('<div class="section-header">πŸ”‘ Authentication Required</div>', unsafe_allow_html=True)
303
-
304
- username = st.text_input("Username", placeholder="Enter username", key="login_username")
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
- with col4:
343
- if st.button("πŸ”“ Logout", key="logout_btn"):
344
- st.session_state.authenticated = False
345
- st.session_state.topics_list = [{"topic_title": "What is Flask", "chapter_title": "Introduction to Flask"}]
346
- st.session_state.session_ids = []
347
- st.session_state.current_page = "Course Generation"
348
- st.rerun()
 
 
 
 
 
 
 
 
 
 
349
 
350
- # Page routing
351
- if st.session_state.current_page == "Admin Dashboard":
352
- render_admin_dashboard()
353
- else:
354
- # Course Generation Page (Original Content)
355
-
356
- # Course Selection Section
357
- st.markdown('<div class="section-header">πŸ“š Course Selection</div>', unsafe_allow_html=True)
358
-
359
- col1, col2 = st.columns([2, 3])
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
- with col2:
369
- st.info(f"**Selected:** {selected_course} (Course ID: {COURSE_CONFIGS[selected_course]['course_id']})")
370
 
371
- # Topics Section (Outside form for dynamic interaction)
372
- st.markdown('<div class="section-header">πŸ“‹ Topics Configuration</div>', unsafe_allow_html=True)
373
-
374
- # Display existing topics with better styling
375
- for i, topic in enumerate(st.session_state.topics_list):
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
- col1, col2, col3 = st.columns([3, 3, 1])
380
- with col1:
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
- with col2:
390
- chapter_title = st.text_input(
391
- "Chapter Title",
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
- with col3:
399
- if len(st.session_state.topics_list) > 1:
400
- st.markdown("<br>", unsafe_allow_html=True) # Add spacing
401
- if st.button("πŸ—‘οΈ", key=f"remove_{i}", help="Remove this topic"):
402
- st.session_state.topics_list.pop(i)
403
- st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
404
 
405
- st.markdown('</div>', unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
406
 
407
- # Add/Remove topic buttons outside the form
408
- st.markdown('<div class="action-buttons">', unsafe_allow_html=True)
409
- col1, col2, col3, col4 = st.columns([2, 2, 2, 4])
 
 
 
 
 
 
410
  with col1:
411
- if st.button("βž• Add Topic", key="add_topic", help="Add a new topic"):
412
- st.session_state.topics_list.append({
413
- "topic_title": f"Topic {len(st.session_state.topics_list) + 1}",
414
- "chapter_title": f"Chapter {len(st.session_state.topics_list) + 1}"
415
- })
416
- st.rerun()
417
  with col2:
418
- if st.button("πŸ”„ Reset All", key="reset_topics", help="Reset to default topics"):
419
- st.session_state.topics_list = [{"topic_title": "What is Flask", "chapter_title": "Introduction to Flask"}]
420
- st.rerun()
421
  st.markdown('</div>', unsafe_allow_html=True)
422
-
423
- # Main Form (for settings and submission)
424
- with st.form("personalization_form", clear_on_submit=False):
425
- # Language & Voice Settings Section
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
- col1, col2 = st.columns(2)
 
 
 
 
 
 
 
 
 
 
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