banao-tech commited on
Commit
3cc6090
Β·
verified Β·
1 Parent(s): 0838b93

Update AdminDashboard.py

Browse files
Files changed (1) hide show
  1. AdminDashboard.py +651 -264
AdminDashboard.py CHANGED
@@ -1,309 +1,696 @@
1
  import streamlit as st
2
- import boto3
3
- import time
4
  from datetime import datetime
 
 
 
 
5
  import os
 
 
 
 
6
 
7
- # DynamoDB configuration
8
  DYNAMODB_REGION = os.getenv("DYNAMODB_REGION", "ap-south-1")
9
  SESSION_TABLE = os.getenv("SESSION_TABLE", "SessionTracking")
10
- COURSE_TABLE = os.getenv("COURSE_TABLE", "CourseTracking")
11
 
12
  # AWS Configuration
13
  AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID")
14
  AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY")
 
15
 
16
- def fetch_courses():
17
- """Fetch all courses from DynamoDB"""
18
- try:
19
- dynamodb = boto3.client(
20
- "dynamodb",
21
- region_name=DYNAMODB_REGION,
22
- aws_access_key_id=AWS_ACCESS_KEY_ID,
23
- aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
24
- )
25
-
26
- response = dynamodb.scan(TableName=COURSE_TABLE)
27
- items = response.get("Items", [])
28
- courses = []
29
- for item in items:
30
- course_detail = item.get("course_detail", {}).get("M", {})
31
- courses.append({
32
- "course_id": item.get("course_id", {}).get("N"),
33
- "course_name": item.get("course_name", {}).get("S"),
34
- "total_videos": item.get("total_videos", {}).get("N", "0"),
35
- "created_at": item.get("created_at", {}).get("S", "-"),
36
- "topic_id": course_detail.get("topic_id", {}).get("N", "-"),
37
- "chapter_id": course_detail.get("chapter_id", {}).get("N", "-"),
38
- })
39
- return courses
40
- except Exception as e:
41
- st.error(f"⚠️ Error fetching courses: {e}")
42
- return []
43
 
44
- def fetch_sessions():
45
- """Fetch all sessions from DynamoDB"""
46
- try:
47
- dynamodb = boto3.client(
48
- "dynamodb",
49
- region_name=DYNAMODB_REGION,
50
- aws_access_key_id=AWS_ACCESS_KEY_ID,
51
- aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
52
- )
53
-
54
- response = dynamodb.scan(TableName=SESSION_TABLE)
55
- items = response.get("Items", [])
56
- sessions = []
57
- for item in items:
58
- sessions.append({
59
- "session_id": item.get("session_id", {}).get("S"),
60
- "course_id": item.get("course_id", {}).get("N"),
61
- "topic_id": item.get("topic_id", {}).get("N"),
62
- "topic_title": item.get("topic_title", {}).get("S", "-"),
63
- "status": item.get("status", {}).get("S", "unknown"),
64
- "node": item.get("node", {}).get("S", "-"),
65
- "created_at": item.get("created_at", {}).get("S", "-"),
66
- "updated_at": item.get("updated_at", {}).get("S", "-"),
67
- "video_url": item.get("video_url", {}).get("S", None),
68
- })
69
- return sessions
70
- except Exception as e:
71
- st.error(f"⚠️ Error fetching sessions: {e}")
72
- return []
73
 
74
- def get_sessions_by_course(course_id):
75
- """Get sessions for a specific course"""
76
- all_sessions = fetch_sessions()
77
- return [session for session in all_sessions if session['course_id'] == str(course_id)]
 
 
 
 
 
78
 
79
- def render_admin_dashboard():
80
- """Render the admin dashboard page"""
81
- st.markdown("<h1 style='color:#4F97FF;text-align:center;'>πŸ“Š Admin Dashboard</h1>", unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
- # Auto-refresh controls
84
- col1, col2, col3 = st.columns([1, 1, 2])
85
- with col1:
86
- if st.button("πŸ”„ Refresh Now", key="manual_refresh"):
87
- st.rerun()
88
 
89
- with col2:
90
- auto_refresh = st.toggle("Auto Refresh (30s)", value=False)
 
 
 
 
 
 
 
91
 
92
- # Tabs for different views
93
- tab1, tab2, tab3 = st.tabs(["πŸ“š Courses Overview", "🎬 Sessions Overview", "πŸ“ˆ Course Details"])
 
 
 
 
 
 
 
 
 
 
94
 
95
- with tab1:
96
- render_courses_overview()
 
 
 
 
 
 
 
97
 
98
- with tab2:
99
- render_sessions_overview()
100
-
101
- with tab3:
102
- render_course_details()
 
 
 
 
 
 
103
 
104
- # Auto-refresh mechanism
105
- if auto_refresh:
106
- time.sleep(30)
107
- st.rerun()
108
-
109
- def render_courses_overview():
110
- """Render courses overview tab"""
111
- st.markdown("### πŸ“š All Courses")
112
 
113
- courses = fetch_courses()
 
 
 
 
 
 
114
 
115
- if not courses:
116
- st.warning("No courses found yet.")
117
- st.info("Courses will appear here once you create them from the Course Generation page.")
118
- return
119
 
120
- # Display course count
121
- st.markdown(f"**Total Courses: {len(courses)}**")
 
 
 
 
 
 
 
 
122
 
123
- # Sort courses by created_at (newest first)
124
- try:
125
- courses.sort(key=lambda x: x['created_at'] if x['created_at'] != '-' else '1970-01-01', reverse=True)
126
- except:
127
- pass
 
128
 
129
- # Display courses in cards
130
- for i, course in enumerate(courses):
131
- with st.container():
132
- st.markdown(f"""
133
- <div style="background:#2D2D2D;padding:1.5rem;border-radius:12px;margin-bottom:1rem;border:1px solid #444;">
134
- <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;">
135
- <h4 style="color:#4F97FF;margin:0;">πŸ“š {course['course_name']}</h4>
136
- <span style="background:#10B981;color:black;padding:0.3rem 0.8rem;border-radius:15px;font-size:0.8rem;font-weight:bold;">COURSE ID: {course['course_id']}</span>
137
- </div>
138
- <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:1rem;">
139
- <div>
140
- <b>πŸ“Ή Total Videos:</b><br><span style="color:#E0E0E0;">{course['total_videos']}</span>
141
- </div>
142
- <div>
143
- <b>🎯 Topic ID:</b><br><span style="color:#E0E0E0;">{course['topic_id']}</span>
144
- </div>
145
- <div>
146
- <b>πŸ“– Chapter ID:</b><br><span style="color:#E0E0E0;">{course['chapter_id']}</span>
147
- </div>
148
- </div>
149
- <div style="margin-top:1rem;">
150
- <b>πŸ“… Created:</b> <span style="color:#888;">{course['created_at'][:19] if course['created_at'] != '-' else '-'}</span>
151
- </div>
152
- </div>
153
- """, unsafe_allow_html=True)
154
-
155
- def render_sessions_overview():
156
- """Render sessions overview tab"""
157
- st.markdown("### 🎬 All Sessions")
158
 
159
- sessions = fetch_sessions()
 
 
 
 
 
 
 
160
 
161
- if not sessions:
162
- st.warning("No sessions found yet.")
163
- st.info("Sessions will appear here once you start generating courses.")
164
- return
 
165
 
166
- # Display session count and status summary
167
- col1, col2, col3, col4 = st.columns(4)
 
 
168
 
169
- status_counts = {}
170
- for session in sessions:
171
- status = session['status'].lower()
172
- status_counts[status] = status_counts.get(status, 0) + 1
 
 
 
 
 
 
173
 
174
- with col1:
175
- st.metric("πŸ“Š Total Sessions", len(sessions))
176
- with col2:
177
- completed = status_counts.get('completed', 0)
178
- st.metric("βœ… Completed", completed)
179
- with col3:
180
- processing = status_counts.get('processing', 0) + status_counts.get('in_progress', 0) + status_counts.get('running', 0)
181
- st.metric("⏳ Processing", processing)
182
- with col4:
183
- failed = status_counts.get('failed', 0) + status_counts.get('error', 0)
184
- st.metric("❌ Failed", failed)
185
 
186
- # Sort sessions by updated_at (newest first)
187
- try:
188
- sessions.sort(key=lambda x: x['updated_at'] if x['updated_at'] != '-' else '1970-01-01', reverse=True)
189
- except:
190
- pass
191
 
192
- # Display sessions
193
- for i, session in enumerate(sessions):
194
- status_color = {
195
- 'completed': '#10B981',
196
- 'processing': '#F59E0B',
197
- 'in_progress': '#F59E0B',
198
- 'running': '#F59E0B',
199
- 'failed': '#EF4444',
200
- 'error': '#EF4444'
201
- }.get(session['status'].lower(), '#6B7280')
202
-
203
- with st.container():
204
- st.markdown(f"""
205
- <div style="background:#2D2D2D;padding:1.5rem;border-radius:12px;margin-bottom:1rem;border:1px solid #444;">
206
- <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;">
207
- <h4 style="color:#4F97FF;margin:0;">🎬 {session['topic_title']}</h4>
208
- <span style="background:{status_color};color:white;padding:0.3rem 0.8rem;border-radius:15px;font-size:0.8rem;font-weight:bold;">{session['status'].upper()}</span>
209
- </div>
210
- <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:1rem;">
211
- <div>
212
- <b>πŸ†” Session ID:</b><br><code style="background:#1A1A1A;padding:0.2rem;border-radius:4px;font-size:0.8rem;">{session['session_id'][:20]}...</code>
213
- </div>
214
- <div>
215
- <b>πŸ“š Course ID:</b><br><span style="color:#E0E0E0;">{session['course_id']}</span>
216
- </div>
217
- <div>
218
- <b>🎯 Topic ID:</b><br><span style="color:#E0E0E0;">{session['topic_id']}</span>
219
- </div>
220
- </div>
221
- <div style="margin-top:1rem;display:flex;justify-content:space-between;">
222
- <div><b>πŸ”§ Current Node:</b> <span style="color:#E0E0E0;">{session['node']}</span></div>
223
- <div><b>⏱️ Updated:</b> <span style="color:#888;">{session['updated_at'][:19] if session['updated_at'] != '-' else '-'}</span></div>
224
- </div>
225
- {f"<div style='margin-top:1rem;'><b>πŸŽ₯ Video:</b> <a href='{session['video_url']}' target='_blank' style='color:#4F97FF;'>πŸ“Ί Watch Video</a></div>" if session['video_url'] else ""}
226
- </div>
227
- """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
 
229
- def render_course_details():
230
- """Render detailed course view with sessions"""
231
- st.markdown("### πŸ“ˆ Detailed Course View")
 
 
 
 
 
 
 
 
 
 
232
 
233
- courses = fetch_courses()
 
 
 
234
 
235
- if not courses:
236
- st.warning("No courses found.")
237
- return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
 
239
- # Course selection
240
- course_options = {f"{course['course_name']} (ID: {course['course_id']})": course['course_id'] for course in courses}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
 
242
- if course_options:
243
- selected_course_display = st.selectbox(
244
- "Select a course to view details:",
245
- options=list(course_options.keys()),
246
- key="course_detail_selector"
 
 
247
  )
 
 
 
 
 
 
 
 
 
 
 
248
 
249
- selected_course_id = course_options[selected_course_display]
250
- selected_course = next(course for course in courses if course['course_id'] == str(selected_course_id))
 
 
 
 
 
 
 
251
 
252
- # Display course information
253
- st.markdown(f"""
254
- <div style="background:#2D2D2D;padding:2rem;border-radius:12px;margin-bottom:2rem;border:1px solid #444;">
255
- <h3 style="color:#4F97FF;margin-bottom:1rem;">πŸ“š {selected_course['course_name']}</h3>
256
- <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:1rem;">
257
- <div><b>πŸ†” Course ID:</b> {selected_course['course_id']}</div>
258
- <div><b>πŸ“Ή Total Videos:</b> {selected_course['total_videos']}</div>
259
- <div><b>πŸ“… Created:</b> {selected_course['created_at'][:19] if selected_course['created_at'] != '-' else '-'}</div>
260
- </div>
261
- </div>
262
- """, unsafe_allow_html=True)
263
 
264
- # Get sessions for this course
265
- course_sessions = get_sessions_by_course(selected_course_id)
 
 
 
 
266
 
267
- if course_sessions:
268
- st.markdown(f"#### 🎬 Sessions for this Course ({len(course_sessions)} sessions)")
269
-
270
- # Session status summary for this course
271
- col1, col2, col3 = st.columns(3)
272
- course_status_counts = {}
273
- for session in course_sessions:
274
- status = session['status'].lower()
275
- course_status_counts[status] = course_status_counts.get(status, 0) + 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
 
277
- with col1:
278
- completed = course_status_counts.get('completed', 0)
279
- st.metric("βœ… Completed", completed)
280
- with col2:
281
- processing = course_status_counts.get('processing', 0) + course_status_counts.get('in_progress', 0) + course_status_counts.get('running', 0)
282
- st.metric("⏳ Processing", processing)
283
- with col3:
284
- failed = course_status_counts.get('failed', 0) + course_status_counts.get('error', 0)
285
- st.metric("❌ Failed", failed)
286
 
287
- # Display sessions in a table format
288
- for session in course_sessions:
289
- status_color = {
290
- 'completed': '#10B981',
291
- 'processing': '#F59E0B',
292
- 'in_progress': '#F59E0B',
293
- 'running': '#F59E0B',
294
- 'failed': '#EF4444',
295
- 'error': '#EF4444'
296
- }.get(session['status'].lower(), '#6B7280')
297
-
298
- st.markdown(f"""
299
- <div style="background:#1E1E1E;padding:1rem;border-radius:8px;margin-bottom:0.5rem;border-left:4px solid {status_color};">
300
- <div style="display:flex;justify-content:between;align-items:center;">
301
- <div style="flex:2;"><b>{session['topic_title']}</b></div>
302
- <div style="flex:1;text-align:center;"><span style="background:{status_color};color:white;padding:0.2rem 0.6rem;border-radius:10px;font-size:0.75rem;">{session['status'].upper()}</span></div>
303
- <div style="flex:1;text-align:right;color:#888;font-size:0.8rem;">{session['node']}</div>
304
- </div>
305
- {f"<div style='margin-top:0.5rem;'><a href='{session['video_url']}' target='_blank' style='color:#4F97FF;font-size:0.8rem;'>πŸŽ₯ Watch Video</a></div>" if session['video_url'] else ""}
306
- </div>
307
- """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  else:
309
- st.info("No sessions found for this course yet.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
+ import json
 
3
  from datetime import datetime
4
+ import time
5
+ 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")
13
 
14
+ # DynamoDB configuration for session tracking
15
  DYNAMODB_REGION = os.getenv("DYNAMODB_REGION", "ap-south-1")
16
  SESSION_TABLE = os.getenv("SESSION_TABLE", "SessionTracking")
 
17
 
18
  # AWS Configuration
19
  AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID")
20
  AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY")
21
+ AWS_DEFAULT_REGION = os.getenv("AWS_DEFAULT_REGION", "ap-south-1")
22
 
23
+ # Default values
24
+ DEFAULT_USER_ID = int(os.getenv("DEFAULT_USER_ID", "30"))
25
+ DEFAULT_PERSONALIZATION_ID = int(os.getenv("DEFAULT_PERSONALIZATION_ID", "100"))
26
+ API_TIMEOUT = int(os.getenv("API_TIMEOUT", "30"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
+ # Authentication credentials from environment variables
29
+ VALID_USERNAME = os.getenv("APP_USERNAME")
30
+ VALID_PASSWORD = os.getenv("APP_PASSWORD")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
+ # Course configurations
33
+ COURSE_CONFIGS = {
34
+ "AI Ready Course": {"course_id": 47, "chapter_id": 647},
35
+ "MERN": {"course_id": 48, "chapter_id": 648},
36
+ "Machine Learning": {"course_id": 49, "chapter_id": 649},
37
+ "Data Science": {"course_id": 50, "chapter_id": 650},
38
+ "Flask": {"course_id": 51, "chapter_id": 651},
39
+ "Django": {"course_id": 52, "chapter_id": 652}
40
+ }
41
 
42
+ # Set page configuration
43
+ st.set_page_config(
44
+ page_title="Base Course Personalization",
45
+ layout="wide",
46
+ initial_sidebar_state="collapsed",
47
+ page_icon="πŸ“š",
48
+ menu_items={
49
+ 'Get Help': None,
50
+ 'Report a bug': None,
51
+ 'About': None
52
+ }
53
+ )
54
+
55
+ # Custom CSS for dark theme styling
56
+ st.markdown("""
57
+ <style>
58
+ /* Dark theme colors */
59
+ :root {
60
+ --background-color: #1E1E1E;
61
+ --card-background: #2D2D2D;
62
+ --text-color: #E0E0E0;
63
+ --accent-color: #4F97FF;
64
+ --border-color: #444444;
65
+ --header-color: #4F97FF;
66
+ --subheader-color: #FFFFFF;
67
+ --success-color: #10B981;
68
+ --warning-color: #F59E0B;
69
+ --error-color: #EF4444;
70
+ }
71
 
72
+ /* Main container styling */
73
+ .stApp {
74
+ background-color: var(--background-color);
75
+ color: var(--text-color);
76
+ }
77
 
78
+ /* Headers */
79
+ .main-header {
80
+ font-size: 2.5rem;
81
+ font-weight: 600;
82
+ color: var(--header-color);
83
+ margin-bottom: 1rem;
84
+ text-align: center;
85
+ text-shadow: 0 2px 4px rgba(79, 151, 255, 0.3);
86
+ }
87
 
88
+ .section-header {
89
+ font-size: 1.5rem;
90
+ font-weight: 500;
91
+ color: var(--subheader-color);
92
+ margin-top: 2rem;
93
+ margin-bottom: 1rem;
94
+ padding-bottom: 0.5rem;
95
+ border-bottom: 2px solid var(--border-color);
96
+ display: flex;
97
+ align-items: center;
98
+ gap: 0.5rem;
99
+ }
100
 
101
+ /* Form container */
102
+ div[data-testid="stForm"] {
103
+ background-color: var(--card-background);
104
+ padding: 2rem;
105
+ border-radius: 12px;
106
+ box-shadow: 0 8px 25px rgba(0,0,0,0.4);
107
+ border: 1px solid var(--border-color);
108
+ margin-bottom: 1rem;
109
+ }
110
 
111
+ /* Button styling */
112
+ .stButton>button {
113
+ background: linear-gradient(135deg, var(--accent-color) 0%, #3B82F6 100%);
114
+ color: white;
115
+ border-radius: 8px;
116
+ padding: 0.75rem 2rem;
117
+ font-weight: 600;
118
+ border: none;
119
+ transition: all 0.3s ease;
120
+ box-shadow: 0 4px 15px rgba(79, 151, 255, 0.3);
121
+ }
122
 
123
+ .stButton>button:hover {
124
+ transform: translateY(-2px);
125
+ box-shadow: 0 6px 20px rgba(79, 151, 255, 0.4);
126
+ }
 
 
 
 
127
 
128
+ /* Input fields styling */
129
+ div[data-baseweb="select"] > div {
130
+ background-color: var(--background-color);
131
+ border: 2px solid var(--border-color);
132
+ border-radius: 8px;
133
+ transition: border-color 0.3s ease;
134
+ }
135
 
136
+ div[data-baseweb="select"] > div:focus-within {
137
+ border-color: var(--accent-color);
138
+ box-shadow: 0 0 0 3px rgba(79, 151, 255, 0.1);
139
+ }
140
 
141
+ .stTextInput > div > div > input,
142
+ .stNumberInput > div > div > input,
143
+ .stTextArea > div > div > textarea {
144
+ background-color: var(--background-color);
145
+ color: var(--text-color);
146
+ border: 2px solid var(--border-color);
147
+ border-radius: 8px;
148
+ padding: 0.75rem;
149
+ transition: border-color 0.3s ease;
150
+ }
151
 
152
+ .stTextInput > div > div > input:focus,
153
+ .stNumberInput > div > div > input:focus,
154
+ .stTextArea > div > div > textarea:focus {
155
+ border-color: var(--accent-color);
156
+ box-shadow: 0 0 0 3px rgba(79, 151, 255, 0.1);
157
+ }
158
 
159
+ /* Topic container styling */
160
+ .topic-container {
161
+ background-color: var(--card-background);
162
+ padding: 1.5rem;
163
+ border-radius: 10px;
164
+ margin-bottom: 1rem;
165
+ border: 1px solid var(--border-color);
166
+ position: relative;
167
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
+ .topic-header {
170
+ display: flex;
171
+ align-items: center;
172
+ gap: 0.5rem;
173
+ margin-bottom: 1rem;
174
+ font-weight: 600;
175
+ color: var(--accent-color);
176
+ }
177
 
178
+ /* Radio buttons */
179
+ .stRadio > div {
180
+ background-color: transparent;
181
+ gap: 1rem;
182
+ }
183
 
184
+ .stRadio > div > label > div {
185
+ color: var(--text-color) !important;
186
+ font-weight: 500;
187
+ }
188
 
189
+ /* Labels */
190
+ .stSelectbox > label,
191
+ .stTextInput > label,
192
+ .stNumberInput > label,
193
+ .stTextArea > label,
194
+ .stRadio > label {
195
+ color: var(--text-color) !important;
196
+ font-weight: 500;
197
+ margin-bottom: 0.5rem;
198
+ }
199
 
200
+ /* Toggle switch */
201
+ .stToggle > label {
202
+ color: var(--text-color) !important;
203
+ font-weight: 500;
204
+ }
 
 
 
 
 
 
205
 
206
+ /* Spacing */
207
+ div.block-container {
208
+ padding-top: 2rem;
209
+ max-width: 1200px;
210
+ }
211
 
212
+ hr {
213
+ margin: 2rem 0;
214
+ border: none;
215
+ border-top: 1px solid var(--border-color);
216
+ }
217
+
218
+ /* API Response Container */
219
+ .api-response {
220
+ background-color: #1A1A1A;
221
+ border-radius: 10px;
222
+ padding: 1.5rem;
223
+ border-left: 4px solid var(--accent-color);
224
+ margin: 1rem 0;
225
+ }
226
+
227
+ .session-info {
228
+ background-color: var(--card-background);
229
+ padding: 1.5rem;
230
+ border-radius: 10px;
231
+ margin: 1rem 0;
232
+ border: 1px solid var(--border-color);
233
+ box-shadow: 0 4px 12px rgba(0,0,0,0.2);
234
+ }
235
+
236
+ /* Action buttons container */
237
+ .action-buttons {
238
+ display: flex;
239
+ gap: 1rem;
240
+ margin: 1rem 0;
241
+ justify-content: flex-start;
242
+ }
243
+
244
+ /* Small action buttons */
245
+ .small-button {
246
+ padding: 0.5rem 1rem;
247
+ font-size: 0.875rem;
248
+ }
249
+
250
+ /* Success/Error styling */
251
+ .stSuccess, .stError, .stWarning, .stInfo {
252
+ border-radius: 8px;
253
+ padding: 1rem;
254
+ margin: 1rem 0;
255
+ }
256
+
257
+ /* Login container */
258
+ .login-container {
259
+ background-color: var(--card-background);
260
+ padding: 3rem;
261
+ border-radius: 15px;
262
+ box-shadow: 0 10px 30px rgba(0,0,0,0.5);
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)
276
 
277
+ # Initialize session state for topics and authentication
278
+ if 'topics_list' not in st.session_state:
279
+ st.session_state.topics_list = [{"topic_title": "What is Flask", "chapter_title": "Introduction to Flask"}]
280
+ if 'session_ids' not in st.session_state:
281
+ st.session_state.session_ids = []
282
+ if 'authenticated' not in st.session_state:
283
+ st.session_state.authenticated = False
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 = [
463
+ # Programming Languages
464
+ "Python", "Java", "JavaScript", "TypeScript", "C++", "C#", "C", "Go", "Rust", "Swift",
465
+ "Kotlin", "Scala", "Ruby", "PHP", "Perl", "R", "MATLAB", "Dart", "Objective-C", "Assembly",
466
+ "Haskell", "Erlang", "Elixir", "F#", "Clojure", "Lua", "Julia", "Groovy", "VB.NET", "COBOL",
467
+ "Fortran", "Pascal", "Delphi", "Ada", "Prolog", "Lisp", "Scheme", "OCaml", "ML",
468
+
469
+ # Web Technologies
470
+ "HTML", "CSS", "SASS", "LESS", "Bootstrap", "Tailwind CSS", "Material-UI",
471
+
472
+ # Frontend Frameworks/Libraries
473
+ "React", "Vue.js", "Angular", "Svelte", "Next.js", "Nuxt.js", "Gatsby", "Ember.js",
474
+ "Backbone.js", "jQuery", "Alpine.js", "Lit", "Stencil", "Ionic", "React Native",
475
+ "Flutter", "Xamarin", "Cordova", "PhoneGap",
476
+
477
+ # Backend Frameworks
478
+ "Node.js", "Express.js", "Nest.js", "Django", "Flask", "FastAPI", "Pyramid", "Tornado",
479
+ "Spring Boot", "Spring MVC", "Struts", "Hibernate", "ASP.NET", "ASP.NET Core",
480
+ "Ruby on Rails", "Sinatra", "Laravel", "Symfony", "CodeIgniter", "CakePHP", "Zend",
481
+ "Gin", "Echo", "Fiber", "Actix", "Rocket", "Warp", "Axum",
482
+
483
+ # Mobile Development
484
+ "Android (Java)", "Android (Kotlin)", "iOS (Swift)", "iOS (Objective-C)",
485
+
486
+ # Game Development
487
+ "Unity", "Unreal Engine", "Godot", "GameMaker Studio", "Construct", "Phaser",
488
+
489
+ # Database Technologies
490
+ "MySQL", "PostgreSQL", "SQLite", "Microsoft SQL Server", "Oracle Database",
491
+ "MongoDB", "Redis", "Cassandra", "DynamoDB", "Firebase", "Supabase",
492
+ "CouchDB", "Neo4j", "InfluxDB", "TimescaleDB", "ClickHouse", "Apache Spark",
493
+ "Elasticsearch", "Apache Solr", "Amazon RDS", "Google Cloud SQL",
494
+
495
+ # Cloud & DevOps
496
+ "AWS", "Google Cloud Platform", "Microsoft Azure", "Digital Ocean", "Heroku",
497
+ "Docker", "Kubernetes", "Terraform", "Ansible", "Jenkins", "GitLab CI", "GitHub Actions",
498
+ "Nginx", "Apache", "Linux", "Ubuntu", "CentOS", "Red Hat",
499
+
500
+ # Data Science & AI/ML
501
+ "TensorFlow", "PyTorch", "Scikit-learn", "Pandas", "NumPy", "Matplotlib", "Seaborn",
502
+ "Jupyter", "Apache Airflow", "Apache Kafka", "Apache Flink", "Hadoop", "Spark",
503
+ "Tableau", "Power BI", "D3.js", "Plotly", "OpenCV", "Keras", "XGBoost",
504
+
505
+ # Testing Frameworks
506
+ "Jest", "Mocha", "Chai", "Cypress", "Selenium", "Playwright", "Puppeteer",
507
+ "JUnit", "TestNG", "Mockito", "PyTest", "unittest", "RSpec", "PHPUnit",
508
+
509
+ # Other Technologies
510
+ "GraphQL", "REST API", "gRPC", "WebSocket", "Apache Kafka", "RabbitMQ",
511
+ "Blockchain", "Solidity", "Web3", "Ethereum", "Bitcoin", "Smart Contracts",
512
+ "Microservices", "Serverless", "Lambda Functions", "API Gateway"
513
+ ]
514
 
515
+ # Sort the options alphabetically
516
+ tech_options.sort()
 
 
 
 
 
 
 
517
 
518
+ programming_language = st.selectbox(
519
+ "Programming Language / Technology",
520
+ tech_options,
521
+ index=tech_options.index("Python") if "Python" in tech_options else 0,
522
+ help="Select the primary programming language, framework, or technology for examples"
523
+ )
524
+ with col2:
525
+ st.markdown("<br>", unsafe_allow_html=True) # Add spacing
526
+ toggle_hinglish = st.toggle("Enable Hinglish", value=True, help="Enable mixing of Hindi and English")
527
+
528
+ # Submit button
529
+ st.markdown("<br>", unsafe_allow_html=True)
530
+ col1, col2, col3 = st.columns([1, 2, 1])
531
+ with col2:
532
+ submitted = st.form_submit_button("πŸš€ Generate Course", use_container_width=True)
533
+
534
+ # Handle submission
535
+ if submitted:
536
+ # Use the topics from session state directly
537
+ topics_to_process = st.session_state.topics_list
538
+
539
+ # Get course configuration
540
+ course_config = COURSE_CONFIGS[selected_course]
541
+ course_id = course_config["course_id"]
542
+ chapter_id = course_config["chapter_id"]
543
+
544
+ # Validate inputs
545
+ valid_topics = []
546
+ for topic in topics_to_process:
547
+ if topic["topic_title"].strip() and topic["chapter_title"].strip():
548
+ valid_topics.append(topic)
549
+
550
+ if not valid_topics:
551
+ st.error("❌ Please enter at least one topic with both topic title and chapter title")
552
  else:
553
+ # Show loading state with better animation
554
+ with st.spinner(f"🎬 Generating your {selected_course} course... This may take a few moments."):
555
+ progress_bar = st.progress(0)
556
+ for i in range(100):
557
+ time.sleep(0.02) # Simulating progress
558
+ progress_bar.progress(i + 1)
559
+
560
+ # Hardcoded values
561
+ user_id = DEFAULT_USER_ID
562
+ personalization_id = DEFAULT_PERSONALIZATION_ID
563
+
564
+ # Create user profile (hardcoded)
565
+ user_profile = {
566
+ "personalized": True,
567
+ "user_name": "System User",
568
+ "user_age": 25,
569
+ "user_gender": "male",
570
+ "user_tech_knowledge": "beginner",
571
+ "user_preferred_activity": "coding, learning, technology",
572
+ "user_food": "healthy food, vegetarian",
573
+ "user_physical_activities": "walking, yoga",
574
+ "learning_style": "visual",
575
+ "target_language": target_language,
576
+ "tts_gender": tts_gender,
577
+ "tts_voice": tts_voice,
578
+ "toggle_hinglish": toggle_hinglish,
579
+ "run_visualization": False,
580
+ "subtitle": "",
581
+ "age_group": "18-25"
582
+ }
583
+
584
+ # Create settings
585
+ settings = {
586
+ "target_language": target_language,
587
+ "tts_gender": tts_gender,
588
+ "tts_voice": tts_voice,
589
+ "toggle_hinglish": toggle_hinglish,
590
+ "run_visualization": False,
591
+ "subtitle": "",
592
+ "programming_language": programming_language,
593
+ "slide_colour": "blue",
594
+ "video_type": "base_video"
595
+ }
596
+
597
+ # Generate topics data with user input for titles and course-specific values
598
+ topics_data = []
599
+ for i, topic in enumerate(valid_topics):
600
+ topics_data.append({
601
+ "topic_id": 10834 + i, # Hardcoded with increment
602
+ "topic_title": topic["topic_title"].strip(),
603
+ "chapter_id": chapter_id, # From selected course
604
+ "chapter_title": topic["chapter_title"].strip(), # User input
605
+ "course_id": course_id, # From selected course
606
+ "video_url": f"https://techlearn-dev.s3.ap-south-1.amazonaws.com/course_videos/{course_id}/{chapter_id}/172906{4365+i*50}.mp4", # Course-specific
607
+ "video_duration": 462 + i*20, # Hardcoded with variation
608
+ "sequence_number": i + 1,
609
+ })
610
+
611
+ # Create payload (always multiple topics structure)
612
+ payload = {
613
+ "personalization_id": personalization_id,
614
+ "user_id": user_id,
615
+ "course_id": course_id,
616
+ "total_videos": len(topics_data),
617
+ "created_at": datetime.utcnow().isoformat(),
618
+ "user_profile": user_profile,
619
+ "topics": topics_data,
620
+ "settings": settings
621
+ }
622
+
623
+ # Make API call
624
+ try:
625
+ headers = {
626
+ 'Content-Type': 'application/json'
627
+ }
628
+
629
+ response = requests.post(API_ENDPOINT, json=payload, headers=headers, timeout=API_TIMEOUT)
630
+
631
+ if response.status_code == 200:
632
+ response_data = response.json()
633
+ session_ids = response_data.get("session_ids", [])
634
+
635
+ st.success(f"πŸŽ‰ {selected_course} generation started successfully!")
636
+
637
+ # Store session IDs in session state
638
+ st.session_state.session_ids.extend(session_ids)
639
+
640
+ # Display results in a clean format
641
+ st.markdown("### πŸ“Š Generation Summary")
642
+
643
+ col1, col2 = st.columns(2)
644
+ with col1:
645
+ st.markdown(f"""
646
+ **Course**: {selected_course}
647
+ **Course ID**: {course_id}
648
+ **Programming Language**: {programming_language.capitalize()}
649
+ **Target Language**: {target_language.capitalize()}
650
+ """)
651
+ with col2:
652
+ st.markdown(f"""
653
+ **Voice**: {tts_voice.capitalize()} ({tts_gender.capitalize()})
654
+ **Topics Count**: {len(valid_topics)}
655
+ **Hinglish**: {"Enabled" if toggle_hinglish else "Disabled"}
656
+ **Chapter ID**: {chapter_id}
657
+ """)
658
+
659
+ # Display session IDs
660
+ if session_ids:
661
+ st.markdown("### πŸ” Session Tracking IDs")
662
+ for i, session_id in enumerate(session_ids, 1):
663
+ st.markdown(f'<div class="session-info">πŸ“ <strong>Session {i}:</strong> <code>{session_id}</code></div>', unsafe_allow_html=True)
664
+
665
+ # Display API response
666
+ with st.expander("πŸ“Š View Full API Response", expanded=False):
667
+ st.markdown('<div class="api-response">', unsafe_allow_html=True)
668
+ st.json(response_data)
669
+ st.markdown('</div>', unsafe_allow_html=True)
670
+
671
+ # Information about tracking
672
+ st.info(f"""
673
+ πŸ’‘ **Tracking Information**
674
+ You can track the progress of your {selected_course} course generation using the session IDs above.
675
+ The processing status will be updated in DynamoDB table: {SESSION_TABLE}
676
+ Region: {DYNAMODB_REGION}
677
+
678
+ πŸ‘‰ **Next Steps**: Switch to the "Admin Dashboard" to monitor your session progress in real-time!
679
+ """)
680
+
681
+ else:
682
+ st.error(f"❌ API Error: {response.status_code}")
683
+ if response.text:
684
+ st.error(f"**Error Details**: {response.text}")
685
+
686
+ except requests.exceptions.Timeout:
687
+ st.error("⏰ Request timed out. Please try again later.")
688
+ except requests.exceptions.ConnectionError:
689
+ st.error("🌐 Connection error. Please check your internet connection.")
690
+ except Exception as e:
691
+ st.error(f"❌ API call failed: {str(e)}")
692
+
693
+ # Show payload for debugging
694
+ with st.expander("πŸ› Debug Information", expanded=False):
695
+ st.warning("Request payload for debugging:")
696
+ st.json(payload)