Akshayram1 commited on
Commit
81c5fc8
Β·
verified Β·
1 Parent(s): 3a8ec46

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +537 -203
app.py CHANGED
@@ -1,56 +1,192 @@
1
  import streamlit as st
2
- from crewai import Agent, Crew, Task, Process, LLM
3
  from typing import List
4
  import os
5
  from dotenv import load_dotenv
6
  from crewai_tools import SerperDevTool
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  # Load environment variables
8
  load_dotenv()
9
- from pydantic import BaseModel,Field
10
- from typing import List, Optional,Dict
11
- from enum import Enum
12
- import json
13
 
14
  class ExpertiseLevel(str, Enum):
15
  BEGINNER = "beginner"
16
  INTERMEDIATE = "intermediate"
17
  ADVANCED = "advanced"
18
 
19
- # ====== Fixed Learning Material Model ======
20
  class LearningMaterial(BaseModel):
21
  title: str
22
  url: str
23
- type: str=Field(...,description="video, article, or exercise")
24
  description: str
25
 
26
- class MaterialCollection(BaseModel): # Renamed for clarity
27
  materials: List[LearningMaterial]
28
 
29
- class QuizQuestion(BaseModel):
30
- question: str
31
- options: List[str]
32
- correct_answer: int
33
- explanation: str
34
-
35
- class QuizFormat(BaseModel):
36
- questions: List[QuizQuestion]
37
-
38
-
39
- class Quiz(BaseModel):
40
- topic: str
41
- questions: List[QuizQuestion]
42
-
43
- # Fix Quiz model structure
44
  class QuizQuestion(BaseModel):
45
  question: str
46
  options: List[str]
47
  correct_answer: int # Index of correct option (0-based)
48
  explanation: str
49
 
50
- class Quiz(BaseModel): # Renamed from QuizFormat
51
  questions: List[QuizQuestion]
52
 
53
- # Corrected ProjectIdea model (represents a SINGLE project)
54
  class ProjectIdea(BaseModel):
55
  title: str
56
  description: str
@@ -59,205 +195,403 @@ class ProjectIdea(BaseModel):
59
  required_skills: List[str]
60
  learning_outcomes: List[str]
61
 
62
- # Container for multiple projects
63
  class Projects(BaseModel):
64
  projects: List[ProjectIdea]
65
 
66
  # Initialize LLM and search tool
67
- llm = LLM('ollama/llama3.2', temperature=0.7)
68
- search_tool = SerperDevTool(serper_api_key=os.getenv("SERPER_API_KEY"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
  def main():
73
- st.title("Educational Content Generator")
 
 
 
 
 
 
 
74
 
75
- # Sidebar for inputs
76
  with st.sidebar:
77
- st.header("Configuration")
78
- topics = st.text_area("Enter topics (one per line)",
79
- help="Enter the topics you want to learn about")
80
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  expertise_level = st.selectbox(
82
  "Select your expertise level",
83
  options=[level.value for level in ExpertiseLevel],
 
84
  help="Choose your current level of expertise"
85
  )
86
-
 
 
 
 
 
 
 
 
 
 
87
  # Main content area
88
- if st.button("Generate Content"):
 
 
 
89
  if not topics:
90
- st.error("Please enter at least one topic")
91
  return
92
 
93
  topic_list = [topic.strip() for topic in topics.split('\n') if topic.strip()]
94
 
95
- with st.spinner("Generating personalized content..."):
96
- try:
97
-
98
- learning_material_agent = Agent(
99
- role='Learning Material Curator',
100
- goal='Curate high-quality learning materials based on user topics and expertise level',
101
- backstory="""You are an expert educational content curator with years of experience
102
- in finding the best learning resources for students at different levels. You know how
103
- to identify reliable and high-quality educational content from reputable sources.""",
104
- llm=llm,
105
- verbose=True
106
- )
107
-
108
- quiz_creator_agent = Agent(
109
- role='Quiz Creator',
110
- goal='Create engaging and educational quizzes to test understanding',
111
- backstory="""You are an experienced educator who specializes in creating
112
- effective assessment questions that test understanding while promoting learning.""",
113
- llm=llm,
114
- verbose=True
115
- )
116
-
117
- project_suggestion_agent = Agent(
118
- role='Project Advisor',
119
- goal='Suggest practical projects that match user expertise and interests',
120
- backstory="""You are a project-based learning expert who knows how to create
121
- engaging hands-on projects that reinforce learning objectives.""",
122
- llm=llm,
123
- verbose=True
124
- )
125
- #
126
- create_learning_material_task = Task(
127
- description=f"""{topics}.
128
- Explain {topics} to a {expertise_level} level.
129
- Include a mix of videos, articles, and practical exercises.
130
- Ensure all materials are from reputable sources and are current.
131
- Include GitHub repos for practical exercises. Verify source credibility before including.
132
- Format response as: {{
133
- "materials": [
134
- {{
135
- "title": "...",
136
- "url": "...",
137
- "type": "...",
138
- "description": "..."
139
- }}
140
- ]
141
- }}""",
142
- agent=learning_material_agent,
143
- expected_output=MaterialCollection.schema_json()
144
- )
145
-
146
- create_quiz_task = Task(
147
- description=f"Create a comprehensive quiz for {topics} at {expertise_level} level.",
148
- agent=quiz_creator_agent,
149
- expected_output=Quiz.schema_json(),
150
- output_pydantic=Quiz
151
- )
152
-
153
- create_project_suggestion_task = Task(
154
- description=f"""Suggest ONLY 5 BEST practical project ideas for {topics}.
155
- Projects should be suitable for {expertise_level} level.
156
- Include title, description, difficulty, estimated duration, required skills, and learning outcomes.
157
- Suggest projects that have recent community activity (check GitHub).
158
- Include links to relevant documentation.
159
- Projects should be engaging and reinforce key concepts.""",
160
- agent=project_suggestion_agent,
161
- expected_output=Projects.schema_json(),
162
- output_pydantic=Projects
163
- )
164
-
165
- # Create and run crew
166
- crew = Crew(
167
- agents=[learning_material_agent, quiz_creator_agent,project_suggestion_agent],
168
- tasks=[create_learning_material_task, create_quiz_task, create_project_suggestion_task],
169
- process=Process.sequential
170
- )
171
- result = crew.kickoff({"topics":topic_list,"expertise_level":ExpertiseLevel(expertise_level)})
172
-
173
- # Display results in tabs
174
- tab1, tab2, tab3 = st.tabs(["Learning Materials", "Quiz", "Project Ideas"])
175
-
176
- with tab1:
177
- st.header("Curated Learning Materials")
178
- materials = create_learning_material_task.output.raw
179
-
180
- try:
181
- # Parse the raw JSON string into a Python dictionary
182
- materials_json = json.loads(materials)
183
- if 'materials' in materials_json:
184
- for material in materials_json['materials']:
185
- with st.container():
186
- st.subheader(material['title'])
187
- col1, col2 = st.columns([1, 3])
188
- with col1:
189
- st.write("**Type:**")
190
- st.write("**URL:**")
191
- st.write("**Description:**")
192
- with col2:
193
- st.write(material['type'].capitalize())
194
- st.markdown(f"[Link]({material['url']})")
195
- st.write(material['description'])
196
- st.divider()
197
- except json.JSONDecodeError as e:
198
- st.error("Error parsing learning materials")
199
- st.write(materials) # Fallback to display raw output
200
-
201
- with tab2:
202
- st.header("Knowledge Quiz")
203
- quiz = create_quiz_task.output.raw
204
-
205
- try:
206
- quiz_json = json.loads(quiz)
207
- if 'questions' in quiz_json:
208
- for i, question in enumerate(quiz_json['questions'], 1):
209
- with st.container():
210
- st.subheader(f"Question {i}")
211
- st.write(question['question'])
212
-
213
- # Display options in a cleaner format
214
- for j, option in enumerate(question['options'], 1):
215
- if j == question['correct_answer']:
216
- st.markdown(f"**{j}. {option} βœ“**")
217
- else:
218
- st.write(f"{j}. {option}")
219
-
220
- # Show explanation in an expander
221
- with st.expander("See Explanation"):
222
- st.write(question['explanation'])
223
- st.divider()
224
- except json.JSONDecodeError as e:
225
- st.error("Error parsing quiz")
226
- st.write(quiz) # Fallback to display raw output
227
-
228
- with tab3:
229
- st.header("Suggested Projects")
230
- projects = result.pydantic
231
- if projects and hasattr(projects, 'projects'):
232
- for project in projects.projects:
233
- with st.container():
234
- st.subheader(f"πŸ“‹ {project.title}")
235
-
236
- # Create two columns for project details
237
- col1, col2 = st.columns([2, 1])
238
-
239
- with col1:
240
- st.markdown("**Description:**")
241
- st.write(project.description)
242
-
243
- with col2:
244
- st.markdown("**Quick Info:**")
245
- st.write(f"πŸ”· **Difficulty:** {project.difficulty}")
246
- st.write(f"⏱️ **Duration:** {project.estimated_duration}")
247
-
248
- # Skills and outcomes in expandable sections
249
- with st.expander("Required Skills"):
250
- for skill in project.required_skills:
251
- st.markdown(f"β€’ {skill}")
252
-
253
- with st.expander("Learning Outcomes"):
254
- for outcome in project.learning_outcomes:
255
- st.markdown(f"β€’ {outcome}")
256
-
257
- st.divider()
258
-
259
- except Exception as e:
260
- st.error(f"An error occurred: {str(e)}")
261
 
262
  if __name__ == "__main__":
263
- main()
 
1
  import streamlit as st
2
+ from crewai import Agent, Crew, Task, Process
3
  from typing import List
4
  import os
5
  from dotenv import load_dotenv
6
  from crewai_tools import SerperDevTool
7
+ import json
8
+ from pydantic import BaseModel, Field
9
+ from typing import List, Optional, Dict
10
+ from enum import Enum
11
+ from langchain_google_genai import ChatGoogleGenerativeAI
12
+
13
+ # Page configuration
14
+ st.set_page_config(
15
+ page_title="Learning Path Generator",
16
+ page_icon="πŸŽ“",
17
+ layout="wide",
18
+ initial_sidebar_state="expanded"
19
+ )
20
+
21
+ # Custom CSS
22
+ st.markdown("""
23
+ <style>
24
+ .main-header {
25
+ font-size: 2.5rem;
26
+ font-weight: 700;
27
+ color: #1E88E5;
28
+ margin-bottom: 1rem;
29
+ }
30
+ .sub-header {
31
+ font-size: 1.8rem;
32
+ font-weight: 600;
33
+ color: #333;
34
+ margin-top: 2rem;
35
+ margin-bottom: 1rem;
36
+ }
37
+ .card {
38
+ background-color: #f9f9f9;
39
+ border-radius: 10px;
40
+ padding: 20px;
41
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
42
+ margin-bottom: 20px;
43
+ }
44
+ .material-card {
45
+ background-color: white;
46
+ border-left: 5px solid #4CAF50;
47
+ padding: 15px;
48
+ margin-bottom: 15px;
49
+ border-radius: 5px;
50
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
51
+ }
52
+ .video-card {
53
+ border-left-color: #FF5722;
54
+ }
55
+ .article-card {
56
+ border-left-color: #2196F3;
57
+ }
58
+ .exercise-card {
59
+ border-left-color: #9C27B0;
60
+ }
61
+ .badge {
62
+ display: inline-block;
63
+ padding: 5px 10px;
64
+ border-radius: 15px;
65
+ font-size: 0.8rem;
66
+ font-weight: 600;
67
+ color: white;
68
+ margin-right: 10px;
69
+ }
70
+ .badge-video {
71
+ background-color: #FF5722;
72
+ }
73
+ .badge-article {
74
+ background-color: #2196F3;
75
+ }
76
+ .badge-exercise {
77
+ background-color: #9C27B0;
78
+ }
79
+ .badge-beginner {
80
+ background-color: #4CAF50;
81
+ }
82
+ .badge-intermediate {
83
+ background-color: #FF9800;
84
+ }
85
+ .badge-advanced {
86
+ background-color: #F44336;
87
+ }
88
+ .quiz-question {
89
+ background-color: white;
90
+ border-radius: 8px;
91
+ padding: 20px;
92
+ margin-bottom: 20px;
93
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
94
+ }
95
+ .quiz-option {
96
+ padding: 10px;
97
+ background-color: #f5f5f5;
98
+ border-radius: 5px;
99
+ margin-bottom: 10px;
100
+ cursor: pointer;
101
+ }
102
+ .quiz-option-correct {
103
+ background-color: #e8f5e9;
104
+ border-left: 5px solid #4CAF50;
105
+ }
106
+ .project-card {
107
+ background-color: white;
108
+ border-radius: 8px;
109
+ padding: 20px;
110
+ margin-bottom: 20px;
111
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
112
+ }
113
+ .project-header {
114
+ display: flex;
115
+ justify-content: space-between;
116
+ align-items: center;
117
+ margin-bottom: 15px;
118
+ }
119
+ .footer {
120
+ text-align: center;
121
+ padding: 20px;
122
+ color: #666;
123
+ font-size: 0.9rem;
124
+ }
125
+ .loading-container {
126
+ display: flex;
127
+ flex-direction: column;
128
+ align-items: center;
129
+ justify-content: center;
130
+ padding: 50px;
131
+ }
132
+ .progress-bar {
133
+ width: 100%;
134
+ height: 20px;
135
+ background-color: #f0f0f0;
136
+ border-radius: 10px;
137
+ overflow: hidden;
138
+ margin-bottom: 20px;
139
+ }
140
+ .progress {
141
+ height: 100%;
142
+ background-color: #4CAF50;
143
+ width: 0%;
144
+ animation: progress 2s ease infinite;
145
+ }
146
+ @keyframes progress {
147
+ 0% { width: 0%; }
148
+ 50% { width: 100%; }
149
+ 100% { width: 0%; }
150
+ }
151
+ .gemini-badge {
152
+ background-color: #8E24AA;
153
+ color: white;
154
+ padding: 5px 10px;
155
+ border-radius: 15px;
156
+ font-size: 0.8rem;
157
+ font-weight: 600;
158
+ display: inline-block;
159
+ }
160
+ </style>
161
+ """, unsafe_allow_html=True)
162
+
163
  # Load environment variables
164
  load_dotenv()
 
 
 
 
165
 
166
  class ExpertiseLevel(str, Enum):
167
  BEGINNER = "beginner"
168
  INTERMEDIATE = "intermediate"
169
  ADVANCED = "advanced"
170
 
171
+ # Model definitions
172
  class LearningMaterial(BaseModel):
173
  title: str
174
  url: str
175
+ type: str = Field(..., description="video, article, or exercise")
176
  description: str
177
 
178
+ class MaterialCollection(BaseModel):
179
  materials: List[LearningMaterial]
180
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  class QuizQuestion(BaseModel):
182
  question: str
183
  options: List[str]
184
  correct_answer: int # Index of correct option (0-based)
185
  explanation: str
186
 
187
+ class Quiz(BaseModel):
188
  questions: List[QuizQuestion]
189
 
 
190
  class ProjectIdea(BaseModel):
191
  title: str
192
  description: str
 
195
  required_skills: List[str]
196
  learning_outcomes: List[str]
197
 
 
198
  class Projects(BaseModel):
199
  projects: List[ProjectIdea]
200
 
201
  # Initialize LLM and search tool
202
+ def initialize_services():
203
+ # Initialize Gemini model
204
+ gemini_llm = ChatGoogleGenerativeAI(
205
+ model="gemini-2.0-flash-lite",
206
+ google_api_key=os.getenv("GOOGLE_API_KEY"),
207
+ temperature=0.7,
208
+ convert_system_message_to_human=True
209
+ )
210
+
211
+ # Initialize search tool
212
+ search_tool = SerperDevTool(serper_api_key=os.getenv("SERPER_API_KEY"))
213
+
214
+ return gemini_llm, search_tool
215
+
216
+ def create_agents_and_tasks(topics, expertise_level, llm):
217
+ # Create agents
218
+ learning_material_agent = Agent(
219
+ role='Learning Material Curator',
220
+ goal='Curate high-quality learning materials based on user topics and expertise level',
221
+ backstory="""You are an expert educational content curator with years of experience
222
+ in finding the best learning resources for students at different levels. You know how
223
+ to identify reliable and high-quality educational content from reputable sources.""",
224
+ llm=llm,
225
+ verbose=True
226
+ )
227
+
228
+ quiz_creator_agent = Agent(
229
+ role='Quiz Creator',
230
+ goal='Create engaging and educational quizzes to test understanding',
231
+ backstory="""You are an experienced educator who specializes in creating
232
+ effective assessment questions that test understanding while promoting learning.""",
233
+ llm=llm,
234
+ verbose=True
235
+ )
236
+
237
+ project_suggestion_agent = Agent(
238
+ role='Project Advisor',
239
+ goal='Suggest practical projects that match user expertise and interests',
240
+ backstory="""You are a project-based learning expert who knows how to create
241
+ engaging hands-on projects that reinforce learning objectives.""",
242
+ llm=llm,
243
+ verbose=True
244
+ )
245
+
246
+ # Create tasks
247
+ create_learning_material_task = Task(
248
+ description=f"""{topics}.
249
+ Explain {topics} to a {expertise_level} level.
250
+ Include a mix of videos, articles, and practical exercises.
251
+ Ensure all materials are from reputable sources and are current.
252
+ Include GitHub repos for practical exercises. Verify source credibility before including.
253
+ Format response as: {{
254
+ "materials": [
255
+ {{
256
+ "title": "...",
257
+ "url": "...",
258
+ "type": "...",
259
+ "description": "..."
260
+ }}
261
+ ]
262
+ }}""",
263
+ agent=learning_material_agent,
264
+ expected_output=MaterialCollection.schema_json()
265
+ )
266
+
267
+ create_quiz_task = Task(
268
+ description=f"Create a comprehensive quiz for {topics} at {expertise_level} level.",
269
+ agent=quiz_creator_agent,
270
+ expected_output=Quiz.schema_json(),
271
+ output_pydantic=Quiz
272
+ )
273
+
274
+ create_project_suggestion_task = Task(
275
+ description=f"""Suggest ONLY 5 BEST practical project ideas for {topics}.
276
+ Projects should be suitable for {expertise_level} level.
277
+ Include title, description, difficulty, estimated duration, required skills, and learning outcomes.
278
+ Suggest projects that have recent community activity (check GitHub).
279
+ Include links to relevant documentation.
280
+ Projects should be engaging and reinforce key concepts.""",
281
+ agent=project_suggestion_agent,
282
+ expected_output=Projects.schema_json(),
283
+ output_pydantic=Projects
284
+ )
285
+
286
+ return (
287
+ [learning_material_agent, quiz_creator_agent, project_suggestion_agent],
288
+ [create_learning_material_task, create_quiz_task, create_project_suggestion_task]
289
+ )
290
+
291
+ def display_learning_materials(materials):
292
+ st.markdown("<div class='sub-header'>πŸ“š Curated Learning Materials</div>", unsafe_allow_html=True)
293
+
294
+ try:
295
+ # Parse the raw JSON string into a Python dictionary
296
+ materials_json = json.loads(materials)
297
+
298
+ # Group materials by type
299
+ videos = []
300
+ articles = []
301
+ exercises = []
302
+
303
+ if 'materials' in materials_json:
304
+ for material in materials_json['materials']:
305
+ if material['type'].lower() == 'video':
306
+ videos.append(material)
307
+ elif material['type'].lower() == 'article':
308
+ articles.append(material)
309
+ elif material['type'].lower() == 'exercise':
310
+ exercises.append(material)
311
+
312
+ # Display materials by type
313
+ if videos:
314
+ st.markdown("### πŸŽ₯ Videos")
315
+ for material in videos:
316
+ st.markdown(f"""
317
+ <div class='material-card video-card'>
318
+ <div><span class='badge badge-video'>Video</span> <strong>{material['title']}</strong></div>
319
+ <div style='margin: 10px 0;'>{material['description']}</div>
320
+ <a href='{material['url']}' target='_blank'>Watch Video β†’</a>
321
+ </div>
322
+ """, unsafe_allow_html=True)
323
+
324
+ if articles:
325
+ st.markdown("### πŸ“„ Articles")
326
+ for material in articles:
327
+ st.markdown(f"""
328
+ <div class='material-card article-card'>
329
+ <div><span class='badge badge-article'>Article</span> <strong>{material['title']}</strong></div>
330
+ <div style='margin: 10px 0;'>{material['description']}</div>
331
+ <a href='{material['url']}' target='_blank'>Read Article β†’</a>
332
+ </div>
333
+ """, unsafe_allow_html=True)
334
+
335
+ if exercises:
336
+ st.markdown("### πŸ’» Exercises")
337
+ for material in exercises:
338
+ st.markdown(f"""
339
+ <div class='material-card exercise-card'>
340
+ <div><span class='badge badge-exercise'>Exercise</span> <strong>{material['title']}</strong></div>
341
+ <div style='margin: 10px 0;'>{material['description']}</div>
342
+ <a href='{material['url']}' target='_blank'>Start Exercise β†’</a>
343
+ </div>
344
+ """, unsafe_allow_html=True)
345
+ except json.JSONDecodeError as e:
346
+ st.error("Error parsing learning materials")
347
+ st.write(materials) # Fallback to display raw output
348
+
349
+ def display_quiz(quiz):
350
+ st.markdown("<div class='sub-header'>🧠 Knowledge Quiz</div>", unsafe_allow_html=True)
351
+
352
+ try:
353
+ quiz_json = json.loads(quiz)
354
+ if 'questions' in quiz_json:
355
+ for i, question in enumerate(quiz_json['questions'], 1):
356
+ st.markdown(f"""
357
+ <div class='quiz-question'>
358
+ <h3>Question {i}</h3>
359
+ <p><strong>{question['question']}</strong></p>
360
+ </div>
361
+ """, unsafe_allow_html=True)
362
+
363
+ # Display options
364
+ for j, option in enumerate(question['options'], 1):
365
+ correct_index = question['correct_answer']
366
+
367
+ # Check if this is the correct answer (add 1 since our display is 1-indexed)
368
+ is_correct = (j == correct_index + 1)
369
+
370
+ # Create option class based on correctness
371
+ option_class = "quiz-option quiz-option-correct" if is_correct else "quiz-option"
372
+
373
+ st.markdown(f"""
374
+ <div class='{option_class}'>
375
+ {j}. {option} {' βœ“' if is_correct else ''}
376
+ </div>
377
+ """, unsafe_allow_html=True)
378
+
379
+ # Show explanation in an expander
380
+ with st.expander("See Explanation"):
381
+ st.write(question['explanation'])
382
+
383
+ except json.JSONDecodeError as e:
384
+ st.error("Error parsing quiz")
385
+ st.write(quiz) # Fallback to display raw output
386
 
387
+ def display_projects(projects):
388
+ st.markdown("<div class='sub-header'>πŸš€ Suggested Projects</div>", unsafe_allow_html=True)
389
+
390
+ if projects and hasattr(projects, 'projects'):
391
+ for i, project in enumerate(projects.projects, 1):
392
+ # Set badge class based on difficulty
393
+ badge_class = ""
394
+ if project.difficulty == "beginner":
395
+ badge_class = "badge-beginner"
396
+ elif project.difficulty == "intermediate":
397
+ badge_class = "badge-intermediate"
398
+ elif project.difficulty == "advanced":
399
+ badge_class = "badge-advanced"
400
+
401
+ st.markdown(f"""
402
+ <div class='project-card'>
403
+ <div class='project-header'>
404
+ <h3>Project #{i}: {project.title}</h3>
405
+ <div>
406
+ <span class='badge {badge_class}'>{project.difficulty.capitalize()}</span>
407
+ <span>⏱️ {project.estimated_duration}</span>
408
+ </div>
409
+ </div>
410
+ <p>{project.description}</p>
411
+ <hr style='margin: 15px 0;'>
412
+ </div>
413
+ """, unsafe_allow_html=True)
414
+
415
+ # Skills and outcomes in expandable sections
416
+ col1, col2 = st.columns(2)
417
+
418
+ with col1:
419
+ with st.expander("πŸ“‹ Required Skills"):
420
+ for skill in project.required_skills:
421
+ st.markdown(f"β€’ {skill}")
422
+
423
+ with col2:
424
+ with st.expander("🎯 Learning Outcomes"):
425
+ for outcome in project.learning_outcomes:
426
+ st.markdown(f"β€’ {outcome}")
427
 
428
+ def render_welcome_screen():
429
+ col1, col2, col3 = st.columns([1, 3, 1])
430
+
431
+ with col2:
432
+ st.markdown("""
433
+ <div style="text-align: center; padding: 2rem;">
434
+ <h1 style="color: #1E88E5;">πŸŽ“ Learning Path Generator</h1>
435
+ <p style="font-size: 1.2rem; margin: 20px 0;">
436
+ Generate personalized learning paths, quizzes, and project ideas with AI assistance.
437
+ </p>
438
+ <span class="gemini-badge">Powered by Gemini 2.0</span>
439
+ </div>
440
+ """, unsafe_allow_html=True)
441
+
442
+ st.markdown("""
443
+ <div class="card">
444
+ <h3>How It Works:</h3>
445
+ <ol>
446
+ <li>Enter your learning topics in the sidebar</li>
447
+ <li>Select your expertise level</li>
448
+ <li>Click "Generate Learning Path" to create personalized content</li>
449
+ </ol>
450
+ </div>
451
+ """, unsafe_allow_html=True)
452
+
453
+ # Feature highlights
454
+ st.markdown("""
455
+ <div style="display: flex; gap: 20px; margin-top: 20px;">
456
+ <div style="flex: 1; padding: 20px; background-color: #e8f5e9; border-radius: 10px; text-align: center;">
457
+ <h3>πŸ“š Curated Resources</h3>
458
+ <p>Get hand-picked learning materials tailored to your level</p>
459
+ </div>
460
+ <div style="flex: 1; padding: 20px; background-color: #e3f2fd; border-radius: 10px; text-align: center;">
461
+ <h3>🧠 Interactive Quizzes</h3>
462
+ <p>Test your knowledge with custom quizzes</p>
463
+ </div>
464
+ <div style="flex: 1; padding: 20px; background-color: #fff3e0; border-radius: 10px; text-align: center;">
465
+ <h3>πŸš€ Project Ideas</h3>
466
+ <p>Apply your skills with hands-on projects</p>
467
+ </div>
468
+ </div>
469
+ """, unsafe_allow_html=True)
470
 
471
  def main():
472
+ # Initialize session state
473
+ if 'generation_complete' not in st.session_state:
474
+ st.session_state.generation_complete = False
475
+ if 'results' not in st.session_state:
476
+ st.session_state.results = None
477
+
478
+ # Header
479
+ st.markdown("<div class='main-header'>πŸŽ“ Learning Path Generator</div>", unsafe_allow_html=True)
480
 
481
+ # Sidebar for inputs with enhanced styling
482
  with st.sidebar:
483
+ st.image("https://www.svgrepo.com/show/374122/learning.svg", width=80)
484
+ st.markdown("<h2>Configure Your Learning Path</h2>", unsafe_allow_html=True)
 
485
 
486
+ # Gemini badge
487
+ st.markdown("<div style='display: flex; justify-content: center; margin-bottom: 20px;'><span class='gemini-badge'>Powered by Gemini 2.0</span></div>", unsafe_allow_html=True)
488
+
489
+ st.markdown("### Topics")
490
+ topics = st.text_area(
491
+ "Enter topics to learn (one per line)",
492
+ placeholder="Example:\nPython Data Science\nMachine Learning\nDeep Learning",
493
+ help="Enter the topics you want to learn about",
494
+ height=150
495
+ )
496
+
497
+ st.markdown("### Your Level")
498
  expertise_level = st.selectbox(
499
  "Select your expertise level",
500
  options=[level.value for level in ExpertiseLevel],
501
+ format_func=lambda x: x.capitalize(),
502
  help="Choose your current level of expertise"
503
  )
504
+
505
+ generate_btn = st.button("πŸš€ Generate Learning Path", use_container_width=True, type="primary")
506
+
507
+ st.markdown("---")
508
+ st.markdown("""
509
+ <div style="font-size: 0.8rem; color: #666;">
510
+ Powered by CrewAI and Gemini 2.0<br>
511
+ Β© 2025 Learning Path Generator
512
+ </div>
513
+ """, unsafe_allow_html=True)
514
+
515
  # Main content area
516
+ if not st.session_state.generation_complete and not generate_btn:
517
+ render_welcome_screen()
518
+
519
+ if generate_btn:
520
  if not topics:
521
+ st.error("⚠️ Please enter at least one topic")
522
  return
523
 
524
  topic_list = [topic.strip() for topic in topics.split('\n') if topic.strip()]
525
 
526
+ # Show a more visually appealing loading state
527
+ st.markdown("""
528
+ <div class='loading-container'>
529
+ <h2>Generating Your Personalized Learning Path...</h2>
530
+ <div class='progress-bar'>
531
+ <div class='progress'></div>
532
+ </div>
533
+ <p>Our AI experts are crafting the perfect resources for you using Google's Gemini 2.0.<br>This may take a minute or two.</p>
534
+ </div>
535
+ """, unsafe_allow_html=True)
536
+
537
+ try:
538
+ # Initialize Gemini LLM and tools
539
+ gemini_llm, search_tool = initialize_services()
540
+
541
+ # Create agents and tasks with Gemini
542
+ agents, tasks = create_agents_and_tasks(topics, expertise_level, gemini_llm)
543
+
544
+ # Create and run crew
545
+ crew = Crew(
546
+ agents=agents,
547
+ tasks=tasks,
548
+ process=Process.sequential
549
+ )
550
+
551
+ result = crew.kickoff({"topics": topic_list, "expertise_level": ExpertiseLevel(expertise_level)})
552
+
553
+ # Store results in session state
554
+ st.session_state.results = {
555
+ "materials": tasks[0].output.raw,
556
+ "quiz": tasks[1].output.raw,
557
+ "projects": result.pydantic
558
+ }
559
+ st.session_state.generation_complete = True
560
+
561
+ # Rerun to display results
562
+ st.rerun()
563
+
564
+ except Exception as e:
565
+ st.error(f"🚨 An error occurred: {str(e)}")
566
+ # Add more specific error handling for Gemini API issues
567
+ if "google_api_key" in str(e).lower():
568
+ st.error("There seems to be an issue with the Google API key. Please check your environment variables.")
569
+ elif "quota" in str(e).lower():
570
+ st.error("API quota exceeded. Please try again later.")
571
+
572
+ # Display results if generation is complete
573
+ if st.session_state.generation_complete and st.session_state.results:
574
+ results = st.session_state.results
575
+
576
+ # Create tabs with icons
577
+ tab1, tab2, tab3 = st.tabs(["πŸ“š Learning Materials", "🧠 Quiz", "πŸš€ Project Ideas"])
578
+
579
+ with tab1:
580
+ display_learning_materials(results["materials"])
581
+
582
+ with tab2:
583
+ display_quiz(results["quiz"])
584
+
585
+ with tab3:
586
+ display_projects(results["projects"])
587
+
588
+ # Add footer
589
+ st.markdown("""
590
+ <div class='footer'>
591
+ <p>Need to regenerate? Update your preferences in the sidebar and click 'Generate Learning Path' again.</p>
592
+ <p>Β© 2025 Learning Path Generator β€’ Powered by Google Gemini 2.0</p>
593
+ </div>
594
+ """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
595
 
596
  if __name__ == "__main__":
597
+ main()