saidinesh07 commited on
Commit
554ae53
Β·
verified Β·
1 Parent(s): 9ef89eb

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +473 -0
app.py ADDED
@@ -0,0 +1,473 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from streamlit_extras.switch_page_button import switch_page
3
+ from streamlit_extras.colored_header import colored_header
4
+ from streamlit_extras.card import card
5
+ from dataclasses import dataclass
6
+ from typing import Dict, List, Optional, Tuple
7
+ from duckduckgo_search import DDGS
8
+ from groq import Groq
9
+ import logging
10
+ from enum import Enum
11
+
12
+ # Set page configuration
13
+ st.set_page_config(
14
+ page_title="Course compass ",
15
+ page_icon="πŸ“š",
16
+ layout="wide",
17
+ initial_sidebar_state="auto"
18
+ )
19
+
20
+ st.markdown("""
21
+ <style>
22
+ .stProgress .st-bo {
23
+ background-color: #3b71ca;
24
+ }
25
+ .stButton button {
26
+ background-color: #3b71ca;
27
+ color: white;
28
+ border-radius: 20px;
29
+ padding: 0.5rem 2rem;
30
+ border: none;
31
+ }
32
+ .stButton button:hover {
33
+ background-color: #2c5494;
34
+ }
35
+ .recommendation-card {
36
+ border: 1px solid #e0e0e0;
37
+ border-radius: 10px;
38
+ padding: 20px;
39
+ margin: 10px 0;
40
+ background-color: white;
41
+ }
42
+ </style>
43
+ """, unsafe_allow_html=True)
44
+
45
+ # Define Enums
46
+ class LearningFormat(str, Enum):
47
+ VIDEO = "Video Lectures"
48
+ INTERACTIVE = "Interactive Exercises"
49
+ TEXT = "Text-based Content"
50
+ PROJECT = "Project-based Learning"
51
+ BLENDED = "Blended Learning"
52
+
53
+ class ExperienceLevel(str, Enum):
54
+ BEGINNER = "Beginner"
55
+ INTERMEDIATE = "Intermediate"
56
+ ADVANCED = "Advanced"
57
+ EXPERT = "Expert"
58
+
59
+ class CareerGoal(str, Enum):
60
+ KNOWLEDGE = "Knowledge Acquisition"
61
+ PROFESSIONAL = "Professional Development"
62
+ CAREER = "Career Transition"
63
+ ACADEMIC = "Academic Requirement"
64
+
65
+ @dataclass
66
+ class UserPreferences:
67
+ name: str
68
+ subject: str
69
+ availability: str
70
+ budget: int
71
+ format: LearningFormat
72
+ experience: ExperienceLevel
73
+ goal: CareerGoal
74
+
75
+ class CourseRecommender:
76
+ def __init__(self, api_key: str):
77
+ self.groq_client = Groq(api_key=api_key) # Use the passed api_key parameter
78
+
79
+ def generate_recommendations(self, preferences: UserPreferences) -> Tuple[str, Optional[List[Dict]]]:
80
+ try:
81
+ prompt = self._create_prompt(preferences)
82
+ response = self._get_ai_response(prompt)
83
+ search_results = self._get_additional_resources(preferences.subject)
84
+ return response, search_results
85
+ except Exception as e:
86
+ logging.error(f"Error generating recommendations: {str(e)}")
87
+ return str(e), None # Return the error message as the first element
88
+
89
+ def _create_prompt(self, preferences: UserPreferences) -> str:
90
+ return f"""As a course recommendation expert, suggest 3-5 specific courses for someone with these preferences:
91
+ - Name: {preferences.name}
92
+ - Subject: {preferences.subject}
93
+ - Time available: {preferences.availability}
94
+ - Budget: ${preferences.budget}
95
+ - Preferred format: {preferences.format}
96
+ - Experience level: {preferences.experience}
97
+ - Learning goal: {preferences.goal}
98
+
99
+ For each course, include:
100
+ 1. Course title and platform
101
+ 2. Price and duration
102
+ 3. Key features
103
+ 4. Why it matches their preferences
104
+ 5.link to redirect to the course
105
+
106
+ Format the response in clear, readable markdown."""
107
+
108
+ def _get_ai_response(self, prompt: str) -> str:
109
+ response = self.groq_client.chat.completions.create(
110
+ messages=[
111
+ {
112
+ "role": "system",
113
+ "content": "You are a helpful course recommendation assistant."
114
+ },
115
+ {
116
+ "role": "user",
117
+ "content": prompt
118
+ }
119
+ ],
120
+ model="mixtral-8x7b-32768",
121
+ temperature=0.7,
122
+ max_tokens=2048
123
+ )
124
+ return response.choices[0].message.content
125
+
126
+ def _get_additional_resources(self, subject: str) -> List[Dict]:
127
+ try:
128
+ with DDGS() as ddgs:
129
+ results = list(ddgs.text(
130
+ f"learn {subject} course tutorial guide",
131
+ max_results=5
132
+ ))
133
+ return results
134
+ except Exception as e:
135
+ logging.error(f"Error fetching additional resources: {str(e)}")
136
+ return []
137
+
138
+ class StreamlitUI:
139
+ def __init__(self):
140
+ self.initialize_session_state()
141
+
142
+ def initialize_session_state(self):
143
+ if 'initialized' not in st.session_state:
144
+ st.session_state.update({
145
+ 'initialized': True,
146
+ 'step': 0,
147
+ 'user_responses': {},
148
+ 'ready_for_recommendations': False,
149
+ 'recommendations_generated': False,
150
+ 'current_recommendations': None,
151
+ 'current_results': None,
152
+ 'error_message': None,
153
+ 'theme_color': '#3b71ca',
154
+ 'api_key_valid': False,
155
+ 'api_key': None
156
+ })
157
+
158
+ def validate_api_key(self, api_key: str) -> bool:
159
+ """Validate if the API key is in the correct format."""
160
+ return api_key.startswith('gsk_') and len(api_key) > 20
161
+
162
+ def render_questionnaire(self) -> bool:
163
+ questions = [
164
+ ("Please enter your name:", "user_name", None),
165
+ ("What subject or skill would you like to learn?", "subject", None),
166
+ ("How many hours per week can you dedicate to learning?", "availability",
167
+ ["1-2 hours", "3-5 hours", "6-10 hours", "More than 10 hours"]),
168
+ ("What is your maximum budget for this course?", "budget", None),
169
+ ("What is your preferred learning format?", "format", [f.value for f in LearningFormat]),
170
+ ("What is your current experience level?", "experience", [e.value for e in ExperienceLevel]),
171
+ ("What is your primary goal?", "goal", [g.value for g in CareerGoal])
172
+ ]
173
+
174
+ if st.session_state.step >= len(questions):
175
+ return self.handle_questionnaire_completion()
176
+
177
+ # Create a container for the questionnaire
178
+ with st.container():
179
+ colored_header(
180
+ label=f"Step {st.session_state.step + 1} of {len(questions)}",
181
+ description="Tell us about your learning preferences",
182
+ color_name="blue-70"
183
+ )
184
+
185
+ progress = min(st.session_state.step / (len(questions) - 1), 1.0)
186
+ st.progress(progress)
187
+
188
+ current_question = questions[st.session_state.step]
189
+ return self.handle_current_question(current_question)
190
+
191
+ def handle_current_question(self, question_data: Tuple) -> bool:
192
+ question, key, options = question_data
193
+
194
+ with st.form(key=f"question_form_{key}"):
195
+ st.write(f"### {question}")
196
+
197
+ if options:
198
+ user_input = st.selectbox(
199
+ "Choose an option:",
200
+ options,
201
+ key=f"input_{key}",
202
+ help=f"Select your preferred {key}"
203
+ )
204
+ elif key == "budget":
205
+ budget_ranges = [
206
+ {"label": "Free Courses Only", "value": 0},
207
+ {"label": "Under $50", "value": 50},
208
+ {"label": "$50 - $100", "value": 100},
209
+ {"label": "$100 - $200", "value": 200},
210
+ {"label": "$200 - $500", "value": 500},
211
+ {"label": "$500 - $1000", "value": 1000},
212
+ {"label": "Over $1000", "value": 1500}
213
+ ]
214
+
215
+ selected_range = st.radio(
216
+ "Select your budget range:",
217
+ options=[r["label"] for r in budget_ranges],
218
+ key="budget_range",
219
+ horizontal=True,
220
+ help="Choose your preferred budget range for the course"
221
+ )
222
+
223
+ # Get the corresponding value for the selected range
224
+ user_input = next(
225
+ (r["value"] for r in budget_ranges if r["label"] == selected_range),
226
+ 100
227
+ )
228
+ else:
229
+ user_input = st.text_input(
230
+ "Your answer:",
231
+ key=f"input_{key}",
232
+ placeholder=f"Enter your {key.replace('_', ' ')}",
233
+ help=f"Enter your {key.replace('_', ' ')}"
234
+ )
235
+
236
+ submit_button = st.form_submit_button(
237
+ "Next" if st.session_state.step < 6 else "Get Recommendations"
238
+ )
239
+
240
+ if submit_button and (user_input or user_input == 0):
241
+ self.save_user_input(key, user_input)
242
+ st.session_state.step += 1
243
+ st.rerun()
244
+
245
+ return False
246
+
247
+ def save_user_input(self, key: str, user_input: str):
248
+ st.session_state.user_responses[key] = user_input
249
+
250
+ def handle_questionnaire_completion(self) -> bool:
251
+ """Handle the completion of the questionnaire and prepare for recommendations."""
252
+ try:
253
+ if not all(key in st.session_state.user_responses for key in ['user_name', 'subject', 'availability', 'budget', 'format', 'experience', 'goal']):
254
+ st.error("Please complete all questions before proceeding.")
255
+ self.reset_session()
256
+ return False
257
+
258
+ preferences = UserPreferences(
259
+ name=st.session_state.user_responses['user_name'],
260
+ subject=st.session_state.user_responses['subject'],
261
+ availability=st.session_state.user_responses['availability'],
262
+ budget=int(st.session_state.user_responses['budget']),
263
+ format=st.session_state.user_responses['format'],
264
+ experience=st.session_state.user_responses['experience'],
265
+ goal=st.session_state.user_responses['goal']
266
+ )
267
+
268
+ with st.spinner('Generating recommendations...'):
269
+ recommendations, additional_results = self.recommender.generate_recommendations(preferences)
270
+
271
+ if isinstance(recommendations, str) and "Error" in recommendations:
272
+ st.error(recommendations)
273
+ return False
274
+
275
+ st.session_state.current_recommendations = recommendations
276
+ st.session_state.current_results = additional_results
277
+ st.session_state.ready_for_recommendations = True
278
+ st.session_state.recommendations_generated = True
279
+ st.session_state.user_name = preferences.name
280
+
281
+ return True
282
+ except Exception as e:
283
+ st.error(f"An error occurred: {str(e)}")
284
+ logging.error(f"Error in handle_questionnaire_completion: {str(e)}")
285
+ return False
286
+
287
+ def reset_session(self):
288
+ """Reset the session state to initial values."""
289
+ api_key = st.session_state.api_key # Preserve API key
290
+ api_key_valid = st.session_state.api_key_valid # Preserve API key validation status
291
+ st.session_state.clear()
292
+ st.session_state.update({
293
+ 'step': 0,
294
+ 'user_responses': {},
295
+ 'ready_for_recommendations': False,
296
+ 'recommendations_generated': False,
297
+ 'current_recommendations': None,
298
+ 'current_results': None,
299
+ 'error_message': None,
300
+ 'api_key': api_key,
301
+ 'api_key_valid': api_key_valid
302
+ })
303
+
304
+ def display_recommendations(self):
305
+ if st.session_state.error_message:
306
+ st.error(st.session_state.error_message)
307
+ if st.button("Try Again", use_container_width=True):
308
+ self.reset_session()
309
+ st.rerun()
310
+ return
311
+
312
+ if not st.session_state.current_recommendations:
313
+ st.warning("No recommendations available. Please start a new search.")
314
+ if st.button("Start New Search", use_container_width=True):
315
+ self.reset_session()
316
+ st.rerun()
317
+ return
318
+
319
+ colored_header(
320
+ label=f"Personalized Course Recommendations for {st.session_state.user_name}",
321
+ description="Based on your preferences, we recommend the following courses:",
322
+ color_name="blue-70"
323
+ )
324
+
325
+ # Display recommendations in a modern card layout
326
+ col1, col2 = st.columns([7, 3])
327
+
328
+ with col1:
329
+ with st.container():
330
+ st.markdown(
331
+ f"""<div class="recommendation-card">
332
+ {st.session_state.current_recommendations}
333
+ </div>""",
334
+ unsafe_allow_html=True
335
+ )
336
+
337
+ with col2:
338
+ with st.container():
339
+ st.write("### Quick Summary")
340
+ st.info(f"""
341
+ **Selected Preferences:**
342
+ - Subject: {st.session_state.user_responses.get('subject')}
343
+ - Format: {st.session_state.user_responses.get('format')}
344
+ - Level: {st.session_state.user_responses.get('experience')}
345
+ """)
346
+
347
+ if st.button("Start New Search", key="new_search", use_container_width=True):
348
+ self.reset_session()
349
+ st.rerun()
350
+
351
+ # Additional resources in a collapsible section
352
+ with st.expander("πŸ“š Additional Learning Resources", expanded=False):
353
+ if st.session_state.current_results:
354
+ for result in st.session_state.current_results[:3]:
355
+ card(
356
+ title=result['title'],
357
+ text=result['body'],
358
+ url=result['href']
359
+ )
360
+ else:
361
+ st.info("No additional resources found.")
362
+
363
+ st.markdown("---")
364
+
365
+ # Footer with helpful information
366
+ col1, col2, col3 = st.columns(3)
367
+ with col1:
368
+ st.info("πŸ” Verify course details on respective platforms")
369
+ with col2:
370
+ st.info("πŸ’‘ Consider prerequisites before enrolling")
371
+ with col3:
372
+ st.info("πŸ“… Check course start dates and deadlines")
373
+
374
+ def generate_recommendations(self):
375
+ """Generate course recommendations based on user preferences."""
376
+ try:
377
+ preferences = UserPreferences(
378
+ name=st.session_state.user_responses.get('user_name', ''),
379
+ subject=st.session_state.user_responses.get('subject', ''),
380
+ availability=st.session_state.user_responses.get('availability', ''),
381
+ budget=int(st.session_state.user_responses.get('budget', 0)),
382
+ format=st.session_state.user_responses.get('format', ''),
383
+ experience=st.session_state.user_responses.get('experience', ''),
384
+ goal=st.session_state.user_responses.get('goal', '')
385
+ )
386
+
387
+ with st.spinner('Generating personalized course recommendations...'):
388
+ recommendations, additional_results = self.recommender.generate_recommendations(preferences)
389
+
390
+ if recommendations:
391
+ st.session_state.current_recommendations = recommendations
392
+ st.session_state.current_results = additional_results
393
+ st.session_state.recommendations_generated = True
394
+ st.session_state.user_name = preferences.name
395
+ else:
396
+ st.session_state.error_message = "Unable to generate recommendations. Please try again."
397
+
398
+ st.rerun()
399
+
400
+ except Exception as e:
401
+ st.session_state.error_message = f"Error generating recommendations: {str(e)}"
402
+ st.rerun()
403
+
404
+ def run(self):
405
+ with st.sidebar:
406
+ st.image("logo.png")
407
+ st.markdown("---")
408
+
409
+ # Add API key input section with updated instructions
410
+ st.markdown("### Groq API Key Setup")
411
+ st.markdown("""
412
+ To use the course recommender, you need a Groq API key.
413
+ If you don't have one, you can get it from:
414
+ [Groq Console](https://console.groq.com/keys) πŸ”‘
415
+ """)
416
+
417
+ api_key = st.text_input(
418
+ "Enter your Groq API Key:",
419
+ type="password",
420
+ help="Enter your Groq API key to use the course recommender",
421
+ key="api_key_input"
422
+ )
423
+
424
+ if api_key:
425
+ if self.validate_api_key(api_key):
426
+ st.session_state.api_key = api_key
427
+ st.session_state.api_key_valid = True
428
+ self.recommender = CourseRecommender(api_key=api_key)
429
+ else:
430
+ st.error("Invalid Groq API key format. It should start with 'gsk_'")
431
+ st.session_state.api_key_valid = False
432
+
433
+ st.markdown("---")
434
+ st.markdown("""
435
+ ### How it works
436
+ 1. Share your preferences
437
+ 2. Get personalized recommendations
438
+ 3. Explore course options
439
+ """)
440
+ st.markdown("---")
441
+ if st.button("Reset Application", use_container_width=True):
442
+ self.reset_session()
443
+ st.rerun()
444
+
445
+ # Main content area with improved API key validation message
446
+ if not st.session_state.api_key_valid:
447
+ st.error("⚠️ Please enter your Groq API key in the sidebar to use the course recommender.")
448
+ st.info("Don't have an API key? Get one for free from [Groq Console](https://console.groq.com/keys)")
449
+ return
450
+
451
+ # Main content area with error handling
452
+ try:
453
+ if not st.session_state.ready_for_recommendations:
454
+ if self.render_questionnaire():
455
+ st.session_state.ready_for_recommendations = True
456
+ st.rerun()
457
+ elif st.session_state.recommendations_generated:
458
+ self.display_recommendations()
459
+ else:
460
+ self.generate_recommendations()
461
+ except Exception as e:
462
+ st.error(f"An unexpected error occurred: {str(e)}")
463
+ logging.error(f"Error in main UI flow: {str(e)}")
464
+ if st.button("Start Over"):
465
+ self.reset_session()
466
+ st.rerun()
467
+
468
+ def main():
469
+ app = StreamlitUI()
470
+ app.run()
471
+
472
+ if __name__ == "__main__":
473
+ main()