Mansuba commited on
Commit
bb774a2
·
verified ·
1 Parent(s): 779ead7

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +937 -0
app.py ADDED
@@ -0,0 +1,937 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """app.ipynb
3
+
4
+ Automatically generated by Colab.
5
+
6
+ Original file is located at
7
+ https://colab.research.google.com/drive/1e_M_kKgA4L3dmmiCjbOrNT3hBfnny_9P
8
+
9
+ #Core system
10
+ """
11
+
12
+ # core_system.py - Modified with fixed exam functionality
13
+
14
+ import os
15
+ import json
16
+ import datetime
17
+ import time
18
+ from datetime import timedelta
19
+ from typing import List, Dict, Any, Optional
20
+
21
+ # LLM Integration using LangChain
22
+ class LLMService:
23
+ def __init__(self, api_key):
24
+ self.api_key = api_key
25
+ # Changed from ChatOpenAI to ChatGroq
26
+ try:
27
+ from langchain_groq import ChatGroq
28
+ self.chat_model = ChatGroq(
29
+ model="llama3-70b-8192", # Using a Groq compatible model
30
+ temperature=0.2,
31
+ groq_api_key=api_key
32
+ )
33
+ except ImportError:
34
+ # Fallback to direct API calls if langchain_groq is not available
35
+ import requests
36
+ self.chat_model = None
37
+
38
+ def create_chain(self, template: str, output_key: str = "result"):
39
+ if self.chat_model:
40
+ from langchain.prompts import ChatPromptTemplate
41
+ from langchain.chains import LLMChain
42
+
43
+ chat_prompt = ChatPromptTemplate.from_template(template)
44
+ return LLMChain(
45
+ llm=self.chat_model,
46
+ prompt=chat_prompt,
47
+ output_key=output_key,
48
+ verbose=True
49
+ )
50
+ return None
51
+
52
+ def get_completion(self, prompt: str) -> str:
53
+ if self.chat_model:
54
+ chain = self.create_chain(prompt)
55
+ response = chain.invoke({"input": ""})
56
+ return response["result"]
57
+ else:
58
+ # Direct API call if langchain is not available
59
+ import requests
60
+ headers = {
61
+ "Authorization": f"Bearer {self.api_key}",
62
+ "Content-Type": "application/json"
63
+ }
64
+ data = {
65
+ "model": "llama3-70b-8192",
66
+ "messages": [{"role": "user", "content": prompt}],
67
+ "temperature": 0.7,
68
+ "max_tokens": 2048
69
+ }
70
+ response = requests.post(
71
+ "https://api.groq.com/openai/v1/chat/completions",
72
+ headers=headers,
73
+ json=data
74
+ )
75
+
76
+ if response.status_code == 200:
77
+ return response.json()["choices"][0]["message"]["content"]
78
+ else:
79
+ raise Exception(f"API Error: {response.status_code} - {response.text}")
80
+
81
+ def generate_module_content(self, day: int, topic: str) -> str:
82
+ prompt = f"""
83
+ Create a comprehensive Python programming module for Day {day} covering {topic}.
84
+ The module should follow this structure in Markdown format:
85
+
86
+ # [Module Title]
87
+
88
+ ## Introduction
89
+ [A brief introduction to the day's topics]
90
+
91
+ ## Section 1: [Section Title]
92
+ [Detailed explanation of concepts]
93
+
94
+ ### Code Examples
95
+ ```python
96
+ # Example code with comments
97
+ ```
98
+
99
+ ### Practice Exercises
100
+ [2-3 exercises with clear instructions]
101
+
102
+ ## Section 2: [Section Title]
103
+ [Repeat the pattern for all relevant topics]
104
+
105
+ Make sure the content is:
106
+ - Comprehensive but focused on the day's topic
107
+ - Includes clear examples with comments
108
+ - Has practice exercises that build skills progressively
109
+ - Uses proper Markdown formatting
110
+ """
111
+ return self.get_completion(prompt)
112
+
113
+ def generate_exam_questions(self, day: int, topic: str, previous_mistakes: List[Dict] = None) -> List[Dict]:
114
+ mistake_context = ""
115
+ if previous_mistakes and len(previous_mistakes) > 0:
116
+ mistakes = "\n".join([
117
+ f"- Question: {m['question']}\n Wrong Answer: {m['user_answer']}\n Correct Answer: {m['correct_answer']}"
118
+ for m in previous_mistakes[:3]
119
+ ])
120
+ mistake_context = f"""
121
+ Include variations of questions related to these previous mistakes:
122
+ {mistakes}
123
+ """
124
+
125
+ prompt = f"""
126
+ Create a 1-hour Python exam for Day {day} covering {topic}.
127
+ {mistake_context}
128
+
129
+ Include 5 questions with a mix of:
130
+ - Multiple-choice (4 options each)
131
+ - Short-answer (requiring 1-3 lines of text)
132
+ - Coding exercises (simple functions or snippets)
133
+
134
+ Return your response as a JSON array where each question is an object with these fields:
135
+ - question_type: "multiple-choice", "short-answer", or "coding"
136
+ - question_text: The full question text
137
+ - options: Array of options (for multiple-choice only)
138
+ - correct_answer: The correct answer or solution
139
+ - explanation: Detailed explanation of the correct answer
140
+ - difficulty: Number from 1 (easiest) to 5 (hardest)
141
+
142
+ Example:
143
+ [
144
+ {{
145
+ "question_type": "multiple-choice",
146
+ "question_text": "What is the output of print(3 * '4' + '5')?",
147
+ "options": ["12", "445", "4445", "Error"],
148
+ "correct_answer": "4445",
149
+ "explanation": "The * operator with a string repeats it, and + concatenates strings",
150
+ "difficulty": 2
151
+ }},
152
+ {{
153
+ "question_type": "coding",
154
+ "question_text": "Write a function that returns the sum of all even numbers in a list.",
155
+ "options": null,
156
+ "correct_answer": "def sum_even(numbers):\\n return sum(x for x in numbers if x % 2 == 0)",
157
+ "explanation": "This solution uses a generator expression with the sum function to add only even numbers",
158
+ "difficulty": 3
159
+ }}
160
+ ]
161
+
162
+ ONLY return the valid JSON array. Do NOT include any explanatory text or code fences.
163
+ """
164
+
165
+ result = self.get_completion(prompt)
166
+
167
+ # Clean up potential formatting issues
168
+ result = result.strip()
169
+ if result.startswith("```json"):
170
+ result = result.split("```json")[1]
171
+ if result.endswith("```"):
172
+ result = result.rsplit("```", 1)[0]
173
+
174
+ try:
175
+ return json.loads(result)
176
+ except json.JSONDecodeError as e:
177
+ print(f"JSON decode error: {e}")
178
+ print(f"Raw response: {result}")
179
+ # Fall back to creating a minimal structure
180
+ return [{"question_type": "short-answer",
181
+ "question_text": "There was an error generating questions. Please describe what you've learned today.",
182
+ "options": None,
183
+ "correct_answer": "Any reasonable summary",
184
+ "explanation": "This is a backup question",
185
+ "difficulty": 1}]
186
+
187
+ def evaluate_answer(self, question: Dict, user_answer: str) -> Dict:
188
+ prompt = f"""
189
+ Grade this response to a Python programming question:
190
+
191
+ Question Type: {question["question_type"]}
192
+ Question: {question["question_text"]}
193
+ Correct Answer: {question["correct_answer"]}
194
+ Student's Answer: {user_answer}
195
+
196
+ Return your evaluation as a JSON object with these fields:
197
+ - is_correct: boolean (true/false)
198
+ - feedback: detailed explanation of what was correct/incorrect
199
+ - correct_solution: the correct solution with explanation if the answer was wrong
200
+
201
+ For coding questions, be somewhat lenient - focus on logic correctness rather than exact syntax matching.
202
+ For multiple choice, it must match the correct option.
203
+ For short answer, assess if the key concepts are present and correct.
204
+
205
+ ONLY return the valid JSON object. Do NOT include any explanatory text.
206
+ """
207
+
208
+ result = self.get_completion(prompt)
209
+
210
+ # Clean up potential formatting issues
211
+ result = result.strip()
212
+ if result.startswith("```json"):
213
+ result = result.split("```json")[1]
214
+ if result.endswith("```"):
215
+ result = result.rsplit("```", 1)[0]
216
+
217
+ try:
218
+ return json.loads(result)
219
+ except json.JSONDecodeError as e:
220
+ print(f"JSON decode error: {e}")
221
+ print(f"Raw response: {result}")
222
+ # Return a fallback response
223
+ return {
224
+ "is_correct": False,
225
+ "feedback": "There was an error evaluating your answer. Please try again.",
226
+ "correct_solution": question["correct_answer"]
227
+ }
228
+
229
+ def answer_student_question(self, question: str, context: Optional[str] = None) -> str:
230
+ context_text = f"Context from previous questions: {context}\n\n" if context else ""
231
+
232
+ prompt = f"""
233
+ {context_text}You are an expert Python tutor. Answer this student's question clearly with explanations and examples:
234
+
235
+ {question}
236
+
237
+ - Use code examples where appropriate
238
+ - Break down complex concepts step by step
239
+ - Be comprehensive but concise
240
+ - Use proper Markdown formatting for code
241
+ """
242
+
243
+ return self.get_completion(prompt)
244
+
245
+ # Content Generator with simplified storage
246
+ class ContentGenerator:
247
+ def __init__(self, api_key):
248
+ self.llm_service = LLMService(api_key)
249
+ # Simplified in-memory storage
250
+ self.modules = []
251
+ self.questions = []
252
+ self.responses = []
253
+ self.chat_logs = []
254
+
255
+ def generate_module(self, day: int) -> tuple:
256
+ day_topics = {
257
+ 1: "Python fundamentals (variables, data types, control structures)",
258
+ 2: "Intermediate Python (functions, modules, error handling)",
259
+ 3: "Advanced Python (file I/O, object-oriented programming, key libraries)"
260
+ }
261
+ topic = day_topics.get(day, "Python programming")
262
+
263
+ content = self.llm_service.generate_module_content(day, topic)
264
+
265
+ # Extract title from content
266
+ title = f"Day {day} Python Module"
267
+ if content.startswith("# "):
268
+ title_line = content.split("\n", 1)[0]
269
+ title = title_line.replace("# ", "").strip()
270
+
271
+ # Save to in-memory storage
272
+ module_id = len(self.modules) + 1
273
+ self.modules.append({
274
+ "id": module_id,
275
+ "day": day,
276
+ "title": title,
277
+ "content": content,
278
+ "created_at": datetime.datetime.utcnow()
279
+ })
280
+
281
+ return content, module_id
282
+
283
+ def generate_exam(self, day: int, module_id: int, previous_mistakes: List = None) -> tuple:
284
+ day_topics = {
285
+ 1: "Python fundamentals (variables, data types, control structures)",
286
+ 2: "Intermediate Python (functions, modules, error handling)",
287
+ 3: "Advanced Python (file I/O, object-oriented programming, key libraries)"
288
+ }
289
+ topic = day_topics.get(day, "Python programming")
290
+
291
+ # Generate questions for this day's topics
292
+ try:
293
+ questions_data = self.llm_service.generate_exam_questions(day, topic, previous_mistakes)
294
+
295
+ if not questions_data:
296
+ raise ValueError("Failed to generate exam questions")
297
+
298
+ saved_questions = []
299
+ for q_data in questions_data:
300
+ question_id = len(self.questions) + 1
301
+
302
+ question = {
303
+ "id": question_id,
304
+ "module_id": module_id,
305
+ "question_type": q_data["question_type"],
306
+ "question_text": q_data["question_text"],
307
+ "options": q_data.get("options"),
308
+ "correct_answer": q_data["correct_answer"],
309
+ "explanation": q_data["explanation"],
310
+ "difficulty": q_data.get("difficulty", 3)
311
+ }
312
+
313
+ self.questions.append(question)
314
+ saved_questions.append(question)
315
+
316
+ return questions_data, saved_questions
317
+ except Exception as e:
318
+ print(f"Error generating exam: {str(e)}")
319
+ # Create a simple fallback question
320
+ fallback_question = {
321
+ "question_type": "short-answer",
322
+ "question_text": f"Explain a key concept you learned in Day {day} about {topic}.",
323
+ "options": None,
324
+ "correct_answer": "Any reasonable explanation",
325
+ "explanation": "This is a fallback question due to an error in question generation",
326
+ "difficulty": 2
327
+ }
328
+
329
+ question_id = len(self.questions) + 1
330
+ question = {
331
+ "id": question_id,
332
+ "module_id": module_id,
333
+ "question_type": fallback_question["question_type"],
334
+ "question_text": fallback_question["question_text"],
335
+ "options": fallback_question.get("options"),
336
+ "correct_answer": fallback_question["correct_answer"],
337
+ "explanation": fallback_question["explanation"],
338
+ "difficulty": fallback_question["difficulty"]
339
+ }
340
+
341
+ self.questions.append(question)
342
+ return [fallback_question], [question]
343
+
344
+ def grade_response(self, question_id: int, user_answer: str) -> Dict:
345
+ # Find question in memory
346
+ question = next((q for q in self.questions if q["id"] == question_id), None)
347
+
348
+ if not question:
349
+ return {"error": "Question not found"}
350
+
351
+ try:
352
+ feedback_data = self.llm_service.evaluate_answer(question, user_answer)
353
+
354
+ # Save response to in-memory storage
355
+ response_id = len(self.responses) + 1
356
+ response = {
357
+ "id": response_id,
358
+ "question_id": question_id,
359
+ "user_answer": user_answer,
360
+ "is_correct": feedback_data.get("is_correct", False),
361
+ "feedback": feedback_data.get("feedback", ""),
362
+ "timestamp": datetime.datetime.utcnow()
363
+ }
364
+ self.responses.append(response)
365
+
366
+ return feedback_data
367
+ except Exception as e:
368
+ print(f"Error grading response: {str(e)}")
369
+ # Create a fallback response
370
+ response_id = len(self.responses) + 1
371
+ response = {
372
+ "id": response_id,
373
+ "question_id": question_id,
374
+ "user_answer": user_answer,
375
+ "is_correct": False,
376
+ "feedback": f"Error evaluating answer: {str(e)}",
377
+ "timestamp": datetime.datetime.utcnow()
378
+ }
379
+ self.responses.append(response)
380
+
381
+ return {
382
+ "is_correct": False,
383
+ "feedback": f"Error evaluating answer: {str(e)}",
384
+ "correct_solution": question["correct_answer"]
385
+ }
386
+
387
+ def get_previous_mistakes(self, day: int) -> List:
388
+ """Get mistakes from previous days to inform adaptive content"""
389
+ if day <= 1:
390
+ return []
391
+
392
+ previous_day = day - 1
393
+
394
+ # Find modules from previous day
395
+ previous_modules = [m for m in self.modules if m["day"] == previous_day]
396
+
397
+ if not previous_modules:
398
+ return []
399
+
400
+ module_ids = [module["id"] for module in previous_modules]
401
+ questions = [q for q in self.questions if q["module_id"] in module_ids]
402
+
403
+ if not questions:
404
+ return []
405
+
406
+ question_ids = [question["id"] for question in questions]
407
+ incorrect_responses = [r for r in self.responses if r["question_id"] in question_ids and not r["is_correct"]]
408
+
409
+ mistakes = []
410
+ for response in incorrect_responses:
411
+ question = next((q for q in self.questions if q["id"] == response["question_id"]), None)
412
+ if question:
413
+ mistakes.append({
414
+ "question": question["question_text"],
415
+ "user_answer": response["user_answer"],
416
+ "correct_answer": question["correct_answer"]
417
+ })
418
+
419
+ return mistakes
420
+
421
+ def answer_question(self, user_question: str, related_question_id: Optional[int] = None) -> str:
422
+ # Get context from related question if available
423
+ context = None
424
+ if related_question_id:
425
+ question = next((q for q in self.questions if q["id"] == related_question_id), None)
426
+ if question:
427
+ context = f"Question: {question['question_text']}\nCorrect Answer: {question['correct_answer']}\nExplanation: {question['explanation']}"
428
+
429
+ response = self.llm_service.answer_student_question(user_question, context)
430
+
431
+ # Log the interaction
432
+ chat_log_id = len(self.chat_logs) + 1
433
+ chat_log = {
434
+ "id": chat_log_id,
435
+ "user_question": user_question,
436
+ "ai_response": response,
437
+ "related_question_id": related_question_id,
438
+ "timestamp": datetime.datetime.utcnow()
439
+ }
440
+ self.chat_logs.append(chat_log)
441
+
442
+ return response
443
+
444
+ # Learning System Class
445
+ class LearningSystem:
446
+ def __init__(self, api_key):
447
+ self.content_generator = ContentGenerator(api_key)
448
+ self.current_day = 1
449
+ self.current_module_id = None
450
+ self.exam_start_time = None
451
+ self.exam_in_progress = False
452
+ self.exam_questions = []
453
+ self.questions_data = [] # Store the questions data for display
454
+
455
+ def generate_day_content(self):
456
+ content, module_id = self.content_generator.generate_module(self.current_day)
457
+ self.current_module_id = module_id
458
+ return content
459
+
460
+ def start_exam(self):
461
+ try:
462
+ if not self.current_module_id:
463
+ # Check if we already have a module for this day
464
+ existing_module = next((m for m in self.content_generator.modules if m["day"] == self.current_day), None)
465
+ if existing_module:
466
+ self.current_module_id = existing_module["id"]
467
+ else:
468
+ # Generate content for the day if not already done
469
+ content, module_id = self.content_generator.generate_module(self.current_day)
470
+ self.current_module_id = module_id
471
+
472
+ # Get previous mistakes for adaptive learning
473
+ previous_mistakes = self.content_generator.get_previous_mistakes(self.current_day)
474
+
475
+ # Generate exam questions
476
+ self.questions_data, self.exam_questions = self.content_generator.generate_exam(
477
+ self.current_day,
478
+ self.current_module_id,
479
+ previous_mistakes
480
+ )
481
+
482
+ if not self.questions_data or not self.exam_questions:
483
+ return "Failed to generate exam questions. Please try again."
484
+
485
+ self.exam_start_time = datetime.datetime.now()
486
+ self.exam_in_progress = True
487
+
488
+ # Format the exam for display
489
+ exam_text = f"# Day {self.current_day} Python Exam\n\n"
490
+ exam_text += f"**Time Limit:** 1 hour\n"
491
+ exam_text += f"**Start Time:** {self.exam_start_time.strftime('%H:%M:%S')}\n"
492
+ exam_text += f"**End Time:** {(self.exam_start_time + timedelta(hours=1)).strftime('%H:%M:%S')}\n\n"
493
+
494
+ # Add adaptive learning notice if applicable
495
+ if previous_mistakes and len(previous_mistakes) > 0:
496
+ exam_text += f"**Note:** This exam includes questions based on topics you had difficulty with previously.\n\n"
497
+
498
+ for i, question in enumerate(self.questions_data):
499
+ exam_text += f"## Question {i+1}: {question['question_type'].title()}\n\n"
500
+ exam_text += f"{question['question_text']}\n\n"
501
+
502
+ if question['question_type'] == "multiple-choice" and question.get('options'):
503
+ for j, option in enumerate(question['options']):
504
+ exam_text += f"- {chr(65+j)}. {option}\n"
505
+
506
+ exam_text += "\n"
507
+
508
+ exam_text += "## Instructions for submitting answers:\n\n"
509
+ exam_text += "1. For multiple-choice questions, input the letter of your answer (A, B, C, or D)\n"
510
+ exam_text += "2. For short-answer questions, write your complete answer\n"
511
+ exam_text += "3. For coding questions, write your complete code solution\n"
512
+ exam_text += "4. **Separate each answer with two line breaks**\n\n"
513
+
514
+ return exam_text
515
+ except Exception as e:
516
+ self.exam_in_progress = False
517
+ return f"Error starting exam: {str(e)}"
518
+
519
+ def submit_exam(self, answers_text):
520
+ try:
521
+ if not self.exam_in_progress:
522
+ return "No exam is currently in progress. Please start an exam first."
523
+
524
+ if not self.exam_questions:
525
+ return "No exam questions available. Please restart the exam."
526
+
527
+ # Check time
528
+ current_time = datetime.datetime.now()
529
+ if current_time > self.exam_start_time + timedelta(hours=1):
530
+ time_overrun = current_time - (self.exam_start_time + timedelta(hours=1))
531
+ overrun_minutes = time_overrun.total_seconds() / 60
532
+ time_notice = f"Time limit exceeded by {overrun_minutes:.1f} minutes. Your answers are being processed anyway."
533
+ else:
534
+ time_notice = "Exam completed within the time limit."
535
+
536
+ # Split answers by question (double newline separator)
537
+ answers = [ans.strip() for ans in answers_text.split("\n\n") if ans.strip()]
538
+
539
+ feedback_text = f"# Day {self.current_day} Exam Results\n\n"
540
+ feedback_text += f"{time_notice}\n\n"
541
+
542
+ correct_count = 0
543
+ total_evaluated = 0
544
+
545
+ # Ensure we don't exceed the number of questions
546
+ num_questions = min(len(self.exam_questions), len(answers))
547
+
548
+ # If the user provided fewer answers than questions, fill in blanks
549
+ while len(answers) < len(self.exam_questions):
550
+ answers.append("")
551
+
552
+ for i in range(len(self.exam_questions)):
553
+ question = self.exam_questions[i]
554
+ answer = answers[i] if i < len(answers) else ""
555
+
556
+ # Handle empty answers
557
+ if not answer:
558
+ feedback_text += f"## Question {i+1}\n\n"
559
+ feedback_text += "**Your Answer:** No answer provided\n\n"
560
+ feedback_text += "**Result:** Incorrect\n\n"
561
+ feedback_text += f"**Correct Solution:** {question['correct_answer']}\n\n"
562
+ total_evaluated += 1
563
+ continue
564
+
565
+ try:
566
+ # Grade the response
567
+ feedback = self.content_generator.grade_response(question["id"], answer)
568
+ total_evaluated += 1
569
+
570
+ # Format feedback
571
+ feedback_text += f"## Question {i+1}\n\n"
572
+ feedback_text += f"**Your Answer:**\n{answer}\n\n"
573
+ feedback_text += f"**Result:** {'✅ Correct' if feedback.get('is_correct', False) else '❌ Incorrect'}\n\n"
574
+ feedback_text += f"**Feedback:**\n{feedback.get('feedback', '')}\n\n"
575
+
576
+ if feedback.get('is_correct', False):
577
+ correct_count += 1
578
+ else:
579
+ feedback_text += f"**Correct Solution:**\n{feedback.get('correct_solution', '')}\n\n"
580
+ except Exception as e:
581
+ feedback_text += f"## Question {i+1}\n\n"
582
+ feedback_text += f"**Error grading answer:** {str(e)}\n\n"
583
+
584
+ # Calculate score
585
+ if total_evaluated > 0:
586
+ score = correct_count / total_evaluated * 100
587
+ else:
588
+ score = 0
589
+
590
+ feedback_text += f"# Final Score: {score:.1f}%\n\n"
591
+
592
+ # Suggestions for improvement
593
+ if score < 100:
594
+ feedback_text += "## Suggestions for Improvement\n\n"
595
+ if score < 60:
596
+ feedback_text += "- Review the fundamental concepts again\n"
597
+ feedback_text += "- Practice more with the code examples\n"
598
+ feedback_text += "- Use the Q&A Sandbox to ask about difficult topics\n"
599
+ elif score < 80:
600
+ feedback_text += "- Focus on the specific areas where you made mistakes\n"
601
+ feedback_text += "- Try rewriting the solutions for incorrect answers\n"
602
+ else:
603
+ feedback_text += "- Great job! Just a few minor issues to review\n"
604
+ feedback_text += "- Look at the explanations for the few questions you missed\n"
605
+ else:
606
+ feedback_text += "## Excellent Work!\n\n"
607
+ feedback_text += "You've mastered today's content. Ready for the next day's material!\n"
608
+
609
+ self.exam_in_progress = False
610
+ return feedback_text
611
+ except Exception as e:
612
+ self.exam_in_progress = False
613
+ return f"Error submitting exam: {str(e)}"
614
+
615
+ def answer_sandbox_question(self, question):
616
+ return self.content_generator.answer_question(question)
617
+
618
+ def advance_to_next_day(self):
619
+ if self.current_day < 3:
620
+ self.current_day += 1
621
+ self.current_module_id = None
622
+ self.exam_questions = []
623
+ return f"Advanced to Day {self.current_day}."
624
+ else:
625
+ return "You have completed the 3-day curriculum."
626
+
627
+ def get_learning_progress(self):
628
+ try:
629
+ modules = self.content_generator.modules
630
+ questions = self.content_generator.questions
631
+ responses = self.content_generator.responses
632
+
633
+ total_questions = len(questions)
634
+ answered_questions = len(responses)
635
+ correct_answers = sum(1 for r in responses if r["is_correct"])
636
+
637
+ if answered_questions > 0:
638
+ accuracy = correct_answers / answered_questions * 100
639
+ else:
640
+ accuracy = 0
641
+
642
+ report = "# Learning Progress Summary\n\n"
643
+ report += f"## Overall Statistics\n"
644
+ report += f"- Total modules completed: {len(modules)}\n"
645
+ report += f"- Total questions attempted: {answered_questions}/{total_questions}\n"
646
+ report += f"- Overall accuracy: {accuracy:.1f}%\n\n"
647
+
648
+ # Day-by-day progress with adaptive learning info
649
+ for day in range(1, 4):
650
+ day_modules = [m for m in modules if m["day"] == day]
651
+
652
+ report += f"## Day {day}: "
653
+ if day_modules:
654
+ report += f"{day_modules[0]['title']}\n"
655
+
656
+ day_questions = [q for q in questions if q["module_id"] in [m["id"] for m in day_modules]]
657
+ day_responses = [r for r in responses if r["question_id"] in [q["id"] for q in day_questions]]
658
+
659
+ day_total = len(day_questions)
660
+ day_answered = len(day_responses)
661
+ day_correct = sum(1 for r in day_responses if r["is_correct"])
662
+
663
+ if day_answered > 0:
664
+ day_accuracy = day_correct / day_answered * 100
665
+ report += f"- **Exam Score:** {day_accuracy:.1f}%\n"
666
+ else:
667
+ report += "- **Exam:** Not taken yet\n"
668
+
669
+ report += f"- Questions attempted: {day_answered}/{day_total}\n"
670
+
671
+ # Show adaptive learning details
672
+ if day > 1:
673
+ previous_mistakes = self.content_generator.get_previous_mistakes(day)
674
+ if previous_mistakes:
675
+ report += f"- **Adaptive Learning:** {len(previous_mistakes)} topics from Day {day-1} reinforced\n"
676
+
677
+ # Show exam results if available
678
+ if day_answered > 0:
679
+ report += "### Exam Performance\n"
680
+
681
+ # Group by question type
682
+ question_types = set(q["question_type"] for q in day_questions)
683
+ for q_type in question_types:
684
+ type_questions = [q for q in day_questions if q["question_type"] == q_type]
685
+ type_responses = [r for r in day_responses if r["question_id"] in [q["id"] for q in type_questions]]
686
+ type_correct = sum(1 for r in type_responses if r["is_correct"])
687
+
688
+ if type_responses:
689
+ type_accuracy = type_correct / len(type_responses) * 100
690
+ report += f"- **{q_type.title()}:** {type_accuracy:.1f}% correct\n"
691
+
692
+ # Common mistakes
693
+ incorrect_responses = [r for r in day_responses if not r["is_correct"]]
694
+ if incorrect_responses:
695
+ report += "\n### Areas for Improvement\n"
696
+
697
+ for resp in incorrect_responses[:3]: # Show top 3 mistakes
698
+ question = next((q for q in questions if q["id"] == resp["question_id"]), None)
699
+ if question:
700
+ report += f"- **Question:** {question['question_text'][:100]}...\n"
701
+ report += f" **Your Answer:** {resp['user_answer'][:100]}...\n"
702
+ report += f" **Correct Answer:** {question['correct_answer'][:100]}...\n\n"
703
+ else:
704
+ report += "Not started yet\n"
705
+
706
+ report += "\n"
707
+
708
+ # Learning recommendations
709
+ report += "## Recommendations\n\n"
710
+ if correct_answers < answered_questions * 0.7:
711
+ report += "- Review the modules before moving to the next day\n"
712
+ report += "- Focus on practicing code examples\n"
713
+ report += "- Use the Q&A Sandbox to clarify difficult concepts\n"
714
+ else:
715
+ report += "- Continue with the current pace\n"
716
+ report += "- Try to implement small projects using what you've learned\n"
717
+
718
+ return report
719
+ except Exception as e:
720
+ return f"Error generating progress report: {str(e)}"
721
+
722
+ """#gradio"""
723
+
724
+ # Gradio UI - Modified for Google Colab
725
+ import os
726
+ import gradio as gr
727
+
728
+ # Note: We're not importing from core_system
729
+ # Instead, we'll use the classes already defined in the previous cell
730
+
731
+ def create_interface():
732
+ # System initialization section
733
+ def initialize_system(api_key_value):
734
+ if not api_key_value or len(api_key_value) < 10: # Basic validation
735
+ return "Please enter a valid API key.", gr.update(visible=False), None
736
+
737
+ try:
738
+ # Test API connection
739
+ test_service = LLMService(api_key_value)
740
+ test_response = test_service.get_completion("Say hello")
741
+
742
+ if len(test_response) > 0:
743
+ learning_system = LearningSystem(api_key_value)
744
+ return "✅ System initialized successfully! You can now use the learning system.", gr.update(visible=True), learning_system
745
+ else:
746
+ return "❌ API connection test failed. Please check your API key.", gr.update(visible=False), None
747
+ except Exception as e:
748
+ return f"❌ Error initializing system: {str(e)}", gr.update(visible=False), None
749
+
750
+ with gr.Blocks(title="AI-Powered Python Learning System", theme="soft") as interface:
751
+ # Store learning system state
752
+ learning_system_state = gr.State(None)
753
+
754
+ # Header
755
+ gr.Markdown(
756
+ """
757
+ <div style="text-align: center; margin-bottom: 20px;">
758
+ <h1 style="color: #4a69bd; font-size: 2.5em;">AI-Powered Python Learning System</h1>
759
+ <p style="font-size: 1.2em; color: #444;">Master Python programming with personalized AI tutoring</p>
760
+ </div>
761
+ """
762
+ )
763
+
764
+ # API Key input - outside the tabs
765
+ with gr.Row():
766
+ # Try to get API key from environment variable
767
+ API_KEY = os.environ.get("GROQ_API_KEY", "")
768
+ api_key_input = gr.Textbox(
769
+ label="Enter your Groq API Key",
770
+ placeholder="gsk_...",
771
+ type="password",
772
+ value=API_KEY # Use environment variable if available
773
+ )
774
+ init_btn = gr.Button("Initialize System", variant="primary")
775
+
776
+ init_status = gr.Markdown("Enter your Groq API key and click 'Initialize System' to begin.")
777
+
778
+ # Main interface container - hidden until initialized
779
+ with gr.Column(visible=False) as main_interface:
780
+ with gr.Tabs() as tabs:
781
+ # Content & Learning tab
782
+ with gr.Tab("Content & Learning"):
783
+ with gr.Row():
784
+ day_display = gr.Markdown("## Current Day: 1")
785
+
786
+ with gr.Row():
787
+ generate_content_btn = gr.Button("Generate Today's Content", variant="primary")
788
+ next_day_btn = gr.Button("Advance to Next Day", variant="secondary")
789
+
790
+ content_display = gr.Markdown("Click 'Generate Today's Content' to begin.")
791
+
792
+ # Exam tab
793
+ with gr.Tab("Exam"):
794
+ with gr.Row():
795
+ start_exam_btn = gr.Button("Start Exam", variant="primary")
796
+
797
+ exam_display = gr.Markdown("Click 'Start Exam' to begin the assessment.")
798
+
799
+ with gr.Row():
800
+ exam_answers = gr.Textbox(
801
+ label="Enter your answers (separate each answer with two line breaks)",
802
+ placeholder="Answer 1\n\nAnswer 2\n\nAnswer 3...",
803
+ lines=15
804
+ )
805
+
806
+ submit_exam_btn = gr.Button("Submit Exam", variant="primary")
807
+
808
+ exam_feedback = gr.Markdown("Your exam results will appear here.")
809
+
810
+ # Q&A Sandbox tab
811
+ with gr.Tab("Q&A Sandbox"):
812
+ with gr.Row():
813
+ question_input = gr.Textbox(
814
+ label="Ask any question about Python",
815
+ placeholder="Enter your question here...",
816
+ lines=3
817
+ )
818
+
819
+ ask_btn = gr.Button("Ask Question", variant="primary")
820
+
821
+ answer_display = gr.Markdown("Ask a question to get started.")
822
+
823
+ # Progress Report tab
824
+ with gr.Tab("Progress Report"):
825
+ with gr.Row():
826
+ report_btn = gr.Button("Generate Progress Report", variant="primary")
827
+
828
+ progress_display = gr.Markdown("Click 'Generate Progress Report' to see your learning statistics.")
829
+
830
+ # Custom functions to handle state
831
+ def generate_content(learning_system):
832
+ if not learning_system:
833
+ return "Please initialize the system first."
834
+ return learning_system.generate_day_content()
835
+
836
+ def advance_day(learning_system):
837
+ if not learning_system:
838
+ return "Please initialize the system first.", "## Current Day: 1"
839
+ result = learning_system.advance_to_next_day()
840
+ return result, f"## Current Day: {learning_system.current_day}"
841
+
842
+ def start_exam(learning_system):
843
+ if not learning_system:
844
+ return "Please initialize the system first."
845
+ try:
846
+ exam_content = learning_system.start_exam()
847
+ return exam_content
848
+ except Exception as e:
849
+ return f"Error starting exam: {str(e)}"
850
+
851
+ def submit_exam(learning_system, answers):
852
+ if not learning_system:
853
+ return "Please initialize the system first."
854
+ if not answers.strip():
855
+ return "Please provide answers before submitting."
856
+
857
+ try:
858
+ feedback = learning_system.submit_exam(answers)
859
+ return feedback
860
+ except Exception as e:
861
+ return f"Error evaluating exam: {str(e)}"
862
+
863
+ def ask_question(learning_system, question):
864
+ if not learning_system:
865
+ return "Please initialize the system first."
866
+ if not question.strip():
867
+ return "Please enter a question."
868
+
869
+ try:
870
+ answer = learning_system.answer_sandbox_question(question)
871
+ return answer
872
+ except Exception as e:
873
+ return f"Error processing question: {str(e)}"
874
+
875
+ def generate_progress_report(learning_system):
876
+ if not learning_system:
877
+ return "Please initialize the system first."
878
+
879
+ try:
880
+ report = learning_system.get_learning_progress()
881
+ return report
882
+ except Exception as e:
883
+ return f"Error generating progress report: {str(e)}"
884
+
885
+ # Set up event handlers
886
+ init_btn.click(
887
+ initialize_system,
888
+ inputs=[api_key_input],
889
+ outputs=[init_status, main_interface, learning_system_state]
890
+ )
891
+
892
+ generate_content_btn.click(
893
+ generate_content,
894
+ inputs=[learning_system_state],
895
+ outputs=[content_display]
896
+ )
897
+
898
+ next_day_btn.click(
899
+ advance_day,
900
+ inputs=[learning_system_state],
901
+ outputs=[content_display, day_display]
902
+ )
903
+
904
+ start_exam_btn.click(
905
+ start_exam,
906
+ inputs=[learning_system_state],
907
+ outputs=[exam_display]
908
+ )
909
+
910
+ submit_exam_btn.click(
911
+ submit_exam,
912
+ inputs=[learning_system_state, exam_answers],
913
+ outputs=[exam_feedback]
914
+ )
915
+
916
+ ask_btn.click(
917
+ ask_question,
918
+ inputs=[learning_system_state, question_input],
919
+ outputs=[answer_display]
920
+ )
921
+
922
+ report_btn.click(
923
+ generate_progress_report,
924
+ inputs=[learning_system_state],
925
+ outputs=[progress_display]
926
+ )
927
+
928
+ return interface
929
+
930
+ # Create and launch the interface
931
+ # For Colab, make sure to install gradio first if you haven't
932
+ # !pip install gradio
933
+ interface = create_interface()
934
+ interface.launch(share=True)
935
+
936
+
937
+