cryogenic22 commited on
Commit
48f28da
·
verified ·
1 Parent(s): 0665cef

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +259 -290
app.py CHANGED
@@ -1,294 +1,263 @@
1
- import streamlit as st
2
- import os
3
- from learning_platform import LearningPlatform, CourseBuilder
4
- import asyncio
5
- import nest_asyncio
6
  from datetime import datetime
7
- import sqlite3
8
-
9
- nest_asyncio.apply()
10
-
11
- # Page config
12
- st.set_page_config(page_title="MicroGuru", page_icon="🎓", layout="wide")
13
-
14
- # Custom CSS - Nice blue with white and pale orange scheme
15
- st.markdown("""
16
- <style>
17
- body {
18
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
19
- color: #f0f8ff;
20
- background-color: #1d3557;
21
- }
22
- .module-card {
23
- padding: 20px;
24
- background: #f1faee;
25
- border-radius: 16px;
26
- border: 1px solid #a8dadc;
27
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
28
- margin: 20px 0;
29
- }
30
- .quiz-card {
31
- padding: 15px;
32
- background: #f1faee;
33
- border-radius: 12px;
34
- border: 1px solid #a8dadc;
35
- margin: 15px 0;
36
- }
37
- .progress-bar {
38
- height: 20px;
39
- background: #a8dadc;
40
- border-radius: 12px;
41
- overflow: hidden;
42
- }
43
- .progress-value {
44
- height: 100%;
45
- background: #457b9d;
46
- transition: width 0.5s ease-in-out;
47
- }
48
- button[title="Previous Module"], button[title="Next Module"], button[title="Start Course"] {
49
- background-color: #457b9d;
50
- color: #f1faee;
51
- border: none;
52
- border-radius: 20px;
53
- padding: 8px 16px;
54
- margin: 10px 0;
55
- font-size: 16px;
56
- transition: background-color 0.3s ease;
57
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
58
- }
59
- button[title="Previous Module"]:hover, button[title="Next Module"]:hover, button[title="Start Course"]:hover {
60
- background-color: #2b6cb0;
61
- box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
62
- }
63
- </style>
64
- """, unsafe_allow_html=True)
65
-
66
- # Initialize SQLite Database
67
- conn = sqlite3.connect('user_progress.db', check_same_thread=False)
68
- cursor = conn.cursor()
69
- cursor.execute('''CREATE TABLE IF NOT EXISTS progress (
70
- user TEXT,
71
- topic TEXT,
72
- current_module INTEGER,
73
- completion_date TEXT)''')
74
- conn.commit()
75
-
76
- # Initialize session state
77
- if 'learning_path' not in st.session_state:
78
- st.session_state.learning_path = None
79
- if 'current_module' not in st.session_state:
80
- st.session_state.current_module = 0
81
- if 'completed_courses' not in st.session_state:
82
- st.session_state.completed_courses = []
83
- if 'user_progress' not in st.session_state:
84
- st.session_state.user_progress = {}
85
- if 'agent_logs' not in st.session_state:
86
- st.session_state.agent_logs = []
87
-
88
- # Initialize platform
89
- api_key = os.environ.get('OPENAI_API_KEY')
90
- if not api_key:
91
- st.error("⚠️ OpenAI API key not found.")
92
- st.stop()
93
- platform = LearningPlatform(api_key)
94
-
95
- def display_course_summary(path):
96
- """Display course summary and start button"""
97
- st.markdown(f"### 🎓 {path.topic}")
98
- st.write(path.description)
99
-
100
- st.markdown("#### 📚 Learning Objectives")
101
- for module in path.modules:
102
- st.markdown(f"- **{module.title}**")
103
- for obj in module.objectives:
104
- st.write(f" - {obj}")
105
-
106
- if st.button("Start Course 🚀"):
107
- st.session_state.learning_path = path
108
- st.session_state.current_module = 0
109
- st.experimental_rerun()
110
-
111
- def display_progress_bar():
112
- """Display course progress bar"""
113
- if st.session_state.learning_path:
114
- path = st.session_state.learning_path
115
- total_modules = len(path.modules)
116
-
117
- if total_modules > 0:
118
- current = st.session_state.current_module + 1
119
- progress = (current / total_modules) * 100
120
-
121
- st.markdown(f"""
122
- <div class="progress-bar">
123
- <div class="progress-value" style="width: {progress}%"></div>
124
- </div>
125
- <p style="text-align: center; font-weight: 500; color: #f1faee;">Module {current}/{total_modules}</p>
126
- """, unsafe_allow_html=True)
127
  else:
128
- st.info("No modules available yet. Please create a new course.")
129
-
130
- def display_module_content(module):
131
- """Display module content and quiz"""
132
- st.markdown(f"""
133
- <div class="module-card">
134
- <h3 style="color: #1d3557;">{module.title}</h3>
135
- """, unsafe_allow_html=True)
136
-
137
- # Display objectives
138
- st.subheader("Learning Objectives")
139
- for obj in module.objectives:
140
- st.write(f"- {obj}")
141
-
142
- # Display prerequisites if any
143
- if module.prerequisites:
144
- st.subheader("Prerequisites")
145
- for prereq in module.prerequisites:
146
- st.write(f"- {prereq}")
147
-
148
- if not module.is_complete:
149
- st.info("🔄 Content is being generated...")
150
- return
151
-
152
- # Display sections
153
- for section in module.sections:
154
- st.subheader(section.title)
155
- st.write(section.content)
156
-
157
- st.subheader("Key Points")
158
- for point in section.key_points:
159
- st.write(f"- {point}")
160
-
161
- if section.examples:
162
- st.subheader("Examples")
163
- for example in section.examples:
164
- st.code(example)
165
-
166
- # Quiz section
167
- st.subheader("Knowledge Check")
168
- for i, question in enumerate(section.quiz_questions):
169
- st.write(f"**Q{i+1}: {question['question']}**")
170
- answer = st.radio(
171
- "Choose your answer:",
172
- question['options'],
173
- key=f"quiz_{section.title}_{i}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  )
175
-
176
- if st.button("Check Answer", key=f"check_{section.title}_{i}"):
177
- if answer == question['correct_answer']:
178
- st.success(f"✅ Correct! {question['explanation']}")
179
- st.session_state.user_progress[section.title] = st.session_state.user_progress.get(section.title, 0) + 1
180
- else:
181
- st.error(f"❌ Incorrect. {question['explanation']}")
182
-
183
- def create_new_course():
184
- """Handle new course creation"""
185
- st.markdown("### 🚀 Start Your Learning Journey")
186
-
187
- topic = st.text_input("What would you like to learn?")
188
- difficulty = st.select_slider(
189
- "Choose difficulty level:",
190
- options=["beginner", "intermediate", "advanced"],
191
- value="intermediate"
192
- )
193
-
194
- if st.button("Create Course 🎯"):
195
- with st.spinner('Our AI agents are crafting your personalized course...'):
196
- try:
197
- async def create_course():
198
- return await platform.create_course(topic, difficulty)
199
-
200
- path = asyncio.run(create_course())
201
- display_course_summary(path)
202
- except Exception as e:
203
- st.session_state.agent_logs.append(f"Error: {str(e)}")
204
- st.error(f"Error: {str(e)}")
205
-
206
- def main():
207
- st.title("🎓 MicroGuru")
208
-
209
- # Navigation
210
- pages = {
211
- "🏠 Home": create_new_course,
212
- "📚 Current Course": lambda: display_current_course(),
213
- "🏆 Completed Courses": lambda: display_completed_courses()
214
- }
215
-
216
- page = st.sidebar.radio("Navigation", list(pages.keys()))
217
-
218
- if page == "📚 Current Course" and not st.session_state.learning_path:
219
- st.info("👋 Welcome! Start by creating a new course from the Home page.")
220
- page = "🏠 Home"
221
-
222
- pages[page]()
223
-
224
- def display_current_course():
225
- """Display current course content"""
226
- path = st.session_state.learning_path
227
- if not path:
228
- st.info("👋 Welcome! Start by creating a new course from the Home page.")
229
- return
230
-
231
- display_progress_bar()
232
-
233
- col1, col2 = st.columns([7, 3])
234
-
235
- with col1:
236
- current_module = path.modules[st.session_state.current_module]
237
- display_module_content(current_module)
238
-
239
- with col2:
240
- st.markdown("### 📌 Navigation")
241
-
242
- # Previous module button
243
- if st.session_state.current_module > 0:
244
- if st.button("⬅️ Previous Module", key="prev_module"):
245
- st.session_state.current_module -= 1
246
- save_progress(path, st.session_state.current_module)
247
- st.experimental_rerun()
248
-
249
- # Next module button
250
- next_idx = st.session_state.current_module + 1
251
- if next_idx < len(path.modules):
252
- if path.modules[next_idx].is_complete:
253
- if st.button("Next Module ➡️", key="next_module"):
254
- st.session_state.current_module = next_idx
255
- save_progress(path, st.session_state.current_module)
256
- st.experimental_rerun()
257
- else:
258
- with st.spinner("Generating next module as we speak..."):
259
- asyncio.run(
260
- platform.generate_next_module(path, next_idx)
261
- )
262
- st.experimental_rerun()
 
 
 
 
 
 
 
 
 
 
 
263
  else:
264
- st.success("🎉 You have completed all modules!")
265
- if path not in st.session_state.completed_courses:
266
- path.completion_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
267
- st.session_state.completed_courses.append(path)
268
- save_progress(path, len(path.modules))
269
-
270
- def display_completed_courses():
271
- """Display completed courses"""
272
- st.markdown("### 🏆 Completed Courses")
273
-
274
- if not st.session_state.completed_courses:
275
- st.info("Complete your first course to see it here!")
276
- return
277
-
278
- for course in st.session_state.completed_courses:
279
- st.markdown(f"""
280
- <div class="module-card">
281
- <h4 style="color: #1d3557;">{course.topic}</h4>
282
- <p style="color: #f1faee;">{course.description}</p>
283
- <p><small style="color: #a8dadc;">Completed on: {course.completion_date}</small></p>
284
- </div>
285
- """, unsafe_allow_html=True)
286
-
287
- def save_progress(path, current_module):
288
- """Save user progress to SQLite database"""
289
- cursor.execute('''INSERT OR REPLACE INTO progress (user, topic, current_module, completion_date) VALUES (?, ?, ?, ?)''',
290
- ('user1', path.topic, current_module, None))
291
- conn.commit()
292
-
293
- if __name__ == "__main__":
294
- main()
 
1
+ import logging
2
+ from typing import List, Dict, Any
3
+ from dataclasses import dataclass, field
 
 
4
  from datetime import datetime
5
+ import json
6
+ from langchain.chat_models import ChatOpenAI
7
+ from langchain.embeddings import OpenAIEmbeddings
8
+ from langchain_community.vectorstores import FAISS
9
+
10
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
11
+
12
+ @dataclass
13
+ class Section:
14
+ title: str
15
+ content: str = ""
16
+ key_points: List[str] = field(default_factory=list)
17
+ examples: List[str] = field(default_factory=list)
18
+ quiz_questions: List[Dict[str, Any]] = field(default_factory=list)
19
+ is_complete: bool = False
20
+
21
+ @dataclass
22
+ class CourseModule:
23
+ title: str
24
+ objectives: List[str]
25
+ prerequisites: List[str] = field(default_factory=list)
26
+ sections: List[Section] = field(default_factory=list)
27
+ is_complete: bool = False
28
+
29
+ @dataclass
30
+ class LearningPath:
31
+ topic: str
32
+ description: str
33
+ modules: List[CourseModule]
34
+ difficulty_level: str
35
+ created_at: datetime
36
+ is_generating: bool = True
37
+
38
+ class CourseBuilder:
39
+ def __init__(self, api_key: str, agent_logs: list):
40
+ self.api_key = api_key
41
+ self.agent_logs = agent_logs
42
+ self.llm = ChatOpenAI(
43
+ temperature=0.7,
44
+ model="gpt-4",
45
+ openai_api_key=api_key,
46
+ max_retries=3,
47
+ retry_on_timeout=True,
48
+ timeout=60
49
+ )
50
+ self.embeddings = OpenAIEmbeddings(openai_api_key=api_key)
51
+ self.vector_store = FAISS.from_texts(
52
+ ["Initial course content"],
53
+ embedding=self.embeddings
54
+ )
55
+ self.prompts = CoursePrompts()
56
+
57
+ async def plan_course(self, topic: str, difficulty: str) -> LearningPath:
58
+ logging.info(f"Planning course for topic: {topic}, difficulty: {difficulty}")
59
+ prompt = self.prompts.course_planning_prompt().format(topic=topic, difficulty=difficulty)
60
+ logging.info(f"Sending prompt: {prompt}")
61
+
62
+ try:
63
+ response = await self.llm.apredict(prompt)
64
+ logging.info(f"Received response: {response}")
65
+ except Exception as e:
66
+ logging.error(f"Error getting response from API: {str(e)}")
67
+ self.agent_logs.append(f"Error getting response from API: {str(e)}")
68
+ raise e
69
+
70
+ if not response.strip():
71
+ logging.error("Empty response from API")
72
+ self.agent_logs.append("Empty response from API")
73
+ raise ValueError("Empty response from API")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  else:
75
+ self.agent_logs.append(f"Raw response from API: {response}")
76
+
77
+ try:
78
+ course_plan = json.loads(response)
79
+ logging.info(f"Parsed course plan: {course_plan}")
80
+ except json.JSONDecodeError as e:
81
+ logging.error(f"Invalid JSON response from API: {str(e)}\nResponse: {response}")
82
+ self.agent_logs.append(f"Invalid JSON response from API: {str(e)}\nResponse: {response}")
83
+ raise ValueError(f"Invalid JSON response from API: {str(e)}\nResponse: {response}")
84
+
85
+ modules = []
86
+ for module_data in course_plan.get("modules", []):
87
+ if "title" not in module_data:
88
+ logging.error(f"Missing 'title' field in module: {module_data}")
89
+ self.agent_logs.append(f"Missing 'title' field in module: {module_data}")
90
+ raise ValueError("Invalid module data: missing 'title' field")
91
+
92
+ module = CourseModule(
93
+ title=module_data["title"],
94
+ objectives=module_data.get("objectives", []),
95
+ prerequisites=module_data.get("prerequisites", []),
96
+ sections=[
97
+ Section(
98
+ title=section["title"],
99
+ content=section.get("content", ""),
100
+ key_points=section.get("key_points", []),
101
+ examples=section.get("examples", []),
102
+ quiz_questions=section.get("quiz_questions", [])
103
+ ) for section in module_data.get("sections", [])
104
+ ]
105
+ )
106
+ modules.append(module)
107
+
108
+ learning_path = LearningPath(
109
+ topic=topic,
110
+ description=course_plan.get("description", ""),
111
+ modules=modules,
112
+ difficulty_level=difficulty,
113
+ created_at=datetime.now(),
114
+ is_generating=False
115
+ )
116
+
117
+ # Store embeddings for course plan for future reference
118
+ self.vector_store.add_texts(
119
+ [json.dumps(course_plan)],
120
+ metadatas=[{"type": "course_plan", "topic": topic}]
121
+ )
122
+
123
+ logging.info(f"Created learning path: {learning_path}")
124
+ return learning_path
125
+
126
+ async def create_module_content(self, module: CourseModule) -> List[Section]:
127
+ logging.info(f"Creating content for module: {module.title}")
128
+ prompt = self.prompts.module_content_prompt().format(
129
+ title=module.title,
130
+ objectives=", ".join(module.objectives)
131
+ )
132
+ logging.info(f"Sending prompt: {prompt}")
133
+
134
+ try:
135
+ response = await self.llm.apredict(prompt)
136
+ logging.info(f"Received response: {response}")
137
+ except Exception as e:
138
+ logging.error(f"Error getting response from API: {str(e)}")
139
+ self.agent_logs.append(f"Error getting response from API: {str(e)}")
140
+ raise e
141
+
142
+ if not response.strip():
143
+ logging.error("Empty response from API")
144
+ self.agent_logs.append("Empty response from API")
145
+ raise ValueError("Empty response from API")
146
+
147
+ try:
148
+ content_json = json.loads(response)
149
+ logging.info(f"Parsed module content: {content_json}")
150
+ except json.JSONDecodeError as e:
151
+ logging.error(f"Invalid JSON response from API: {str(e)}\nResponse: {response}")
152
+ self.agent_logs.append(f"Invalid JSON response from API: {str(e)}\nResponse: {response}")
153
+ raise ValueError(f"Invalid JSON response from API: {str(e)}\nResponse: {response}")
154
+
155
+ sections = [Section(**section) for section in content_json.get("sections", [])]
156
+
157
+ # Store embeddings for module content
158
+ for section in sections:
159
+ self.vector_store.add_texts(
160
+ [section.content],
161
+ metadatas=[{"type": "module_content", "module": module.title}]
162
  )
163
+
164
+ logging.info(f"Created {len(sections)} sections for module: {module.title}")
165
+ return sections
166
+
167
+ async def answer_user_question(self, topic: str, module_title: str, question: str) -> str:
168
+ logging.info(f"Answering user question for topic: {topic}, module: {module_title}, question: {question}")
169
+ prompt = self.prompts.user_question_prompt().format(
170
+ topic=topic,
171
+ module_title=module_title,
172
+ question=question
173
+ )
174
+ logging.info(f"Sending prompt: {prompt}")
175
+
176
+ try:
177
+ response = await self.llm.apredict(prompt)
178
+ logging.info(f"Received response: {response}")
179
+ except Exception as e:
180
+ logging.error(f"Error getting response from API: {str(e)}")
181
+ self.agent_logs.append(f"Error getting response from API: {str(e)}")
182
+ raise e
183
+
184
+ if not response.strip():
185
+ logging.error("Empty response from API")
186
+ self.agent_logs.append("Empty response from API")
187
+ raise ValueError("Empty response from API")
188
+
189
+ return response
190
+
191
+ class CoursePrompts:
192
+ @staticmethod
193
+ def course_planning_prompt() -> str:
194
+ return """As a course planning expert, design a structured learning path for {topic} at {difficulty} level.
195
+
196
+ Requirements:
197
+ 1. 5-7 progressive modules
198
+ 2. Clear prerequisites and objectives
199
+ 3. Practical applications
200
+ 4. Real-world examples
201
+ 5. A compelling description that excites the learner about the journey ahead
202
+ 6. Each module should contain content, quiz questions, and be designed to progressively enhance understanding.
203
+ Return a structured JSON with detailed content for each module.
204
+ """
205
+
206
+ @staticmethod
207
+ def module_content_prompt() -> str:
208
+ return """Create engaging module content for the module titled '{title}' with the following objectives:
209
+
210
+ Objectives: {objectives}
211
+
212
+ Include:
213
+ 1. Clear explanations with practical examples to deepen understanding
214
+ 2. Real-world applications relevant to the topic
215
+ 3. Key points that summarize each section concisely
216
+ 4. A set of 3-5 quiz questions for each section, with answers and explanations
217
+ 5. Encourage learners to think critically and ask questions related to '{title}'
218
+ Return a structured JSON with detailed content for each section.
219
+ """
220
+
221
+ @staticmethod
222
+ def user_question_prompt() -> str:
223
+ return """As an AI assistant, provide a clear and informative answer to the user's question based on the course topic '{topic}', the module '{module_title}', and the related content.
224
+
225
+ User Question: {question}
226
+
227
+ Ensure your response is helpful, easy to understand, and relevant to the course content.
228
+ """
229
+
230
+ class LearningPlatform:
231
+ def __init__(self, api_key: str = None):
232
+ self.api_key = api_key or os.getenv("OPENAI_API_KEY")
233
+ self.agent_logs = []
234
+ self.course_builder = CourseBuilder(self.api_key, self.agent_logs)
235
+
236
+ async def create_course(self, topic: str, difficulty: str) -> LearningPath:
237
+ try:
238
+ learning_path = await self.course_builder.plan_course(topic, difficulty)
239
+ # Log successful course creation
240
+ self.agent_logs.append(f"Successfully created course: {learning_path.topic}")
241
+ return learning_path
242
+ except Exception as e:
243
+ self.agent_logs.append(f"Course creation error: {str(e)}")
244
+ raise Exception(f"Course creation error: {str(e)}")
245
+
246
+ async def generate_next_module(self, path: LearningPath, module_index: int):
247
+ if module_index < len(path.modules):
248
+ module = path.modules[module_index]
249
+ if not module.is_complete:
250
+ self.agent_logs.append(f"Generating content for module: {module.title}")
251
+ sections = await self.course_builder.create_module_content(module)
252
+ module.sections = sections
253
+ module.is_complete = True
254
+ self.agent_logs.append(f"Module '{module.title}' is now complete.")
255
+
256
+ async def handle_user_question(self, path: LearningPath, module_index: int, question: str) -> str:
257
+ if module_index < len(path.modules):
258
+ module = path.modules[module_index]
259
+ self.agent_logs.append(f"Answering user question for module: {module.title}")
260
+ answer = await self.course_builder.answer_user_question(path.topic, module.title, question)
261
+ return answer
262
  else:
263
+ raise ValueError("Invalid module index")