Redfire-1234 commited on
Commit
3b74fae
Β·
verified Β·
1 Parent(s): 7a7db8c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +892 -0
app.py CHANGED
@@ -1,3 +1,895 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import pickle
2
  import faiss
3
  from flask import Flask, request, jsonify, render_template_string
 
1
+ # import pickle
2
+ # import faiss
3
+ # from flask import Flask, request, jsonify, render_template_string
4
+ # from sentence_transformers import SentenceTransformer
5
+ # from huggingface_hub import hf_hub_download
6
+ # import hashlib
7
+ # import re
8
+ # import os
9
+ # import sys
10
+
11
+ # # Import Groq
12
+ # try:
13
+ # from groq import Groq
14
+ # GROQ_AVAILABLE = True
15
+ # except ImportError:
16
+ # print("❌ ERROR: groq package not installed!")
17
+ # print("Add 'groq' to requirements.txt")
18
+ # GROQ_AVAILABLE = False
19
+ # sys.exit(1)
20
+
21
+ # app = Flask(__name__)
22
+
23
+ # print("=" * 50)
24
+ # print("STARTING MCQ GENERATOR APP")
25
+ # print("=" * 50)
26
+
27
+ # # ------------------------------
28
+ # # Chapter Names (Actual Textbook Chapters)
29
+ # # ------------------------------
30
+ # CHAPTER_NAMES = {
31
+ # "biology": [
32
+ # "Reproduction in Lower and Higher Plants",
33
+ # "Reproduction in Lower and Higher Animals",
34
+ # "Inheritance and Variation",
35
+ # "Molecular Basis of Inheritance",
36
+ # "Origin and Evolution of Life",
37
+ # "Plant Water Relation",
38
+ # "Plant Growth and Mineral Nutrition",
39
+ # "Respiration and Circulation",
40
+ # "Control and Co-ordination",
41
+ # "Human Health and Diseases",
42
+ # "Enhancement of Food Production",
43
+ # "Biotechnology",
44
+ # "Organisms and Populations",
45
+ # "Ecosystems and Energy Flow",
46
+ # "Biodiversity, Conservation and Environmental Issues"
47
+ # ],
48
+ # "chemistry": [
49
+ # "Solid State",
50
+ # "Solutions",
51
+ # "Ionic Equilibria",
52
+ # "Chemical Thermodynamics",
53
+ # "Electrochemistry",
54
+ # "Chemical Kinetics",
55
+ # "Elements of Groups 16, 17 and 18",
56
+ # "Transition and Inner transition Elements",
57
+ # "Coordination Compounds",
58
+ # "Halogen Derivatives",
59
+ # "Alcohols, Phenols and Ethers",
60
+ # "Aldehydes, Ketones and Carboxylic acids",
61
+ # "Amines",
62
+ # "Biomolecules",
63
+ # "Introduction to Polymer Chemistry",
64
+ # "Green Chemistry and Nanochemistry"
65
+ # ],
66
+ # "physics": [
67
+ # "Rotational Dynamics",
68
+ # "Mechanical Properties of Fluids",
69
+ # "Kinetic Theory of Gases and Radiation",
70
+ # "Thermodynamics",
71
+ # "Oscillations",
72
+ # "Superposition of Waves",
73
+ # "Wave Optics",
74
+ # "Electrostatics",
75
+ # "Current Electricity",
76
+ # "Magnetic Fields due to Electric Current",
77
+ # "Magnetic Materials",
78
+ # "Electromagnetic induction",
79
+ # "AC Circuits",
80
+ # "Dual Nature of Radiation and Matter",
81
+ # "Structure of Atoms and Nuclei",
82
+ # "Semiconductor Devices"
83
+ # ]
84
+ # }
85
+
86
+ # # ------------------------------
87
+ # # Initialize Groq API Client
88
+ # # ------------------------------
89
+ # print("\nStep 1: Checking Groq API Key...")
90
+ # print("-" * 50)
91
+
92
+ # GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "").strip()
93
+
94
+ # if not GROQ_API_KEY:
95
+ # print("❌ GROQ_API_KEY not found!")
96
+ # print("\nTo fix this:")
97
+ # print("1. Go to: https://console.groq.com/keys")
98
+ # print("2. Create a free API key")
99
+ # print("3. In HuggingFace Space Settings β†’ Repository secrets")
100
+ # print(" Add: Name=GROQ_API_KEY, Value=<your-key>")
101
+ # print("4. Restart your Space")
102
+ # groq_client = None
103
+ # else:
104
+ # print(f"βœ“ GROQ_API_KEY found ({len(GROQ_API_KEY)} chars)")
105
+ # print(f" First 20 chars: {GROQ_API_KEY[:20]}...")
106
+
107
+ # try:
108
+ # groq_client = Groq(api_key=GROQ_API_KEY)
109
+
110
+ # # Test the API
111
+ # print(" Testing API connection...")
112
+ # test = groq_client.chat.completions.create(
113
+ # messages=[{"role": "user", "content": "test"}],
114
+ # model="llama-3.3-70b-versatile",
115
+ # max_tokens=5
116
+ # )
117
+ # print("βœ“ Groq API working!")
118
+
119
+ # except Exception as e:
120
+ # print(f"❌ Groq API initialization failed:")
121
+ # print(f" Error: {str(e)}")
122
+ # groq_client = None
123
+
124
+ # print("-" * 50)
125
+
126
+ # # ------------------------------
127
+ # # Load embedding model (CPU)
128
+ # # ------------------------------
129
+ # print("\nStep 2: Loading embedding model...")
130
+ # embed_model = SentenceTransformer("all-MiniLM-L6-v2")
131
+ # print("βœ“ Embedding model loaded")
132
+
133
+ # # ------------------------------
134
+ # # Download files from Hugging Face
135
+ # # ------------------------------
136
+ # REPO_ID = "Redfire-1234/pcb_tutor"
137
+
138
+ # print("\nStep 3: Downloading subject files...")
139
+ # print("-" * 50)
140
+
141
+ # try:
142
+ # bio_chunks_path = hf_hub_download(repo_id=REPO_ID, filename="bio_chunks.pkl", repo_type="model")
143
+ # faiss_bio_path = hf_hub_download(repo_id=REPO_ID, filename="faiss_bio.bin", repo_type="model")
144
+
145
+ # chem_chunks_path = hf_hub_download(repo_id=REPO_ID, filename="chem_chunks.pkl", repo_type="model")
146
+ # faiss_chem_path = hf_hub_download(repo_id=REPO_ID, filename="faiss_chem.bin", repo_type="model")
147
+
148
+ # phy_chunks_path = hf_hub_download(repo_id=REPO_ID, filename="phy_chunks.pkl", repo_type="model")
149
+ # faiss_phy_path = hf_hub_download(repo_id=REPO_ID, filename="faiss_phy.bin", repo_type="model")
150
+
151
+ # print("βœ“ All files downloaded")
152
+ # except Exception as e:
153
+ # print(f"❌ Error downloading files: {e}")
154
+ # sys.exit(1)
155
+
156
+ # # Load all subjects into memory
157
+ # print("\nStep 4: Loading subject data into memory...")
158
+ # SUBJECTS = {
159
+ # "biology": {
160
+ # "chunks": pickle.load(open(bio_chunks_path, "rb")),
161
+ # "index": faiss.read_index(faiss_bio_path)
162
+ # },
163
+ # "chemistry": {
164
+ # "chunks": pickle.load(open(chem_chunks_path, "rb")),
165
+ # "index": faiss.read_index(faiss_chem_path)
166
+ # },
167
+ # "physics": {
168
+ # "chunks": pickle.load(open(phy_chunks_path, "rb")),
169
+ # "index": faiss.read_index(faiss_phy_path)
170
+ # }
171
+ # }
172
+
173
+ # print(f"βœ“ Biology: {len(SUBJECTS['biology']['chunks'])} chunks")
174
+ # print(f"βœ“ Chemistry: {len(SUBJECTS['chemistry']['chunks'])} chunks")
175
+ # print(f"βœ“ Physics: {len(SUBJECTS['physics']['chunks'])} chunks")
176
+
177
+ # print("\n" + "=" * 50)
178
+ # print("βœ“ ALL SYSTEMS READY!")
179
+ # print("=" * 50 + "\n")
180
+
181
+ # # ------------------------------
182
+ # # Caching
183
+ # # ------------------------------
184
+ # MCQ_CACHE = {}
185
+ # MAX_CACHE_SIZE = 100
186
+
187
+ # def get_cache_key(topic, subject, context_hash):
188
+ # return f"{subject}:{topic}:{context_hash}"
189
+
190
+ # def cache_mcq(key, mcqs):
191
+ # if len(MCQ_CACHE) >= MAX_CACHE_SIZE:
192
+ # MCQ_CACHE.pop(next(iter(MCQ_CACHE)))
193
+ # MCQ_CACHE[key] = mcqs
194
+
195
+ # # ------------------------------
196
+ # # RAG Search
197
+ # # ------------------------------
198
+ # def rag_search(query, subject, k=5):
199
+ # if subject not in SUBJECTS:
200
+ # return None
201
+
202
+ # chunks = SUBJECTS[subject]["chunks"]
203
+ # index = SUBJECTS[subject]["index"]
204
+
205
+ # q_emb = embed_model.encode([query], show_progress_bar=False).astype("float32")
206
+ # D, I = index.search(q_emb, k)
207
+
208
+ # results = []
209
+ # for idx in I[0]:
210
+ # if idx < len(chunks):
211
+ # results.append(chunks[idx])
212
+
213
+ # return "\n\n".join(results)
214
+
215
+ # # ------------------------------
216
+ # # Topic Validation (Check if topic belongs to subject)
217
+ # # ------------------------------
218
+ # def validate_topic_subject(topic, subject):
219
+ # """
220
+ # Validate if the topic belongs to the selected subject using LLM
221
+ # Returns True if valid, False otherwise
222
+ # """
223
+ # if not groq_client:
224
+ # return True # Skip validation if API not available
225
+
226
+ # validation_prompt = f"""You are a Class 12 PCB subject expert. Determine if the following topic belongs to {subject.title()}.
227
+
228
+ # Topic: "{topic}"
229
+ # Subject: {subject.title()}
230
+
231
+ # Class 12 {subject.title()} covers:
232
+ # {"- Reproduction, Genetics, Evolution, Plant Physiology, Human Systems, Ecology, Biotechnology" if subject == "biology" else ""}
233
+ # {"- Solid State, Solutions, Thermodynamics, Electrochemistry, Organic Chemistry, Coordination Compounds" if subject == "chemistry" else ""}
234
+ # {"- Rotational Dynamics, Fluids, Thermodynamics, Waves, Optics, Electromagnetism, Modern Physics, Semiconductors" if subject == "physics" else ""}
235
+
236
+ # Answer ONLY with "YES" if the topic belongs to {subject.title()}, or "NO" if it belongs to a different subject.
237
+
238
+ # Answer:"""
239
+
240
+ # try:
241
+ # response = groq_client.chat.completions.create(
242
+ # messages=[
243
+ # {
244
+ # "role": "system",
245
+ # "content": f"You are an expert at identifying which subject a topic belongs to. Answer only YES or NO."
246
+ # },
247
+ # {
248
+ # "role": "user",
249
+ # "content": validation_prompt
250
+ # }
251
+ # ],
252
+ # model="llama-3.3-70b-versatile",
253
+ # temperature=0.1,
254
+ # max_tokens=10
255
+ # )
256
+
257
+ # result = response.choices[0].message.content.strip().upper()
258
+
259
+ # if "YES" in result:
260
+ # print(f"βœ“ Topic '{topic}' validated for {subject}")
261
+ # return True
262
+ # else:
263
+ # print(f"❌ Topic '{topic}' does NOT belong to {subject}")
264
+ # return False
265
+
266
+ # except Exception as e:
267
+ # print(f"⚠️ Validation failed: {e}")
268
+ # return True # Allow on error to avoid blocking
269
+
270
+ # # ------------------------------
271
+ # # Chapter Detection (Using Actual Chapter Names)
272
+ # # ------------------------------
273
+ # def detect_chapter_from_list(context, topic, subject):
274
+ # """
275
+ # Detect chapter using the actual chapter list by matching keywords
276
+ # Returns None if topic doesn't match the subject
277
+ # """
278
+ # if subject not in CHAPTER_NAMES:
279
+ # return None
280
+
281
+ # chapters = CHAPTER_NAMES[subject]
282
+ # combined_text = (topic + " " + context[:1000]).lower()
283
+
284
+ # # Score each chapter based on keyword matching
285
+ # scores = {}
286
+ # for chapter in chapters:
287
+ # score = 0
288
+ # chapter_words = chapter.lower().split()
289
+
290
+ # # Check if chapter words appear in the content
291
+ # for word in chapter_words:
292
+ # if len(word) > 3: # Ignore small words like "and", "the"
293
+ # if word in combined_text:
294
+ # score += 1
295
+
296
+ # # Bonus if topic is similar to chapter name
297
+ # topic_words = topic.lower().split()
298
+ # for t_word in topic_words:
299
+ # if len(t_word) > 3 and t_word in chapter.lower():
300
+ # score += 2
301
+
302
+ # if score > 0:
303
+ # scores[chapter] = score
304
+
305
+ # # Return chapter with highest score
306
+ # if scores:
307
+ # best_chapter = max(scores.items(), key=lambda x: x[1])[0]
308
+ # print(f"βœ“ Matched chapter: {best_chapter} (score: {scores[best_chapter]})")
309
+ # return best_chapter
310
+
311
+ # # Fallback: Use LLM to choose from the list
312
+ # return detect_chapter_with_llm(context, topic, subject, chapters)
313
+
314
+ # def detect_chapter_with_llm(context, topic, subject, chapters):
315
+ # """
316
+ # Use LLM to pick the correct chapter from the provided list
317
+ # Also verifies if the topic belongs to the subject
318
+ # """
319
+ # if not groq_client:
320
+ # return None
321
+
322
+ # chapter_list = "\n".join([f"{i+1}. {ch}" for i, ch in enumerate(chapters)])
323
+
324
+ # detection_prompt = f"""Based on the following textbook content and topic, identify which chapter from the Class 12 {subject.title()} textbook this content belongs to.
325
+
326
+ # Topic: {topic}
327
+
328
+ # Content snippet:
329
+ # {context[:600]}
330
+
331
+ # Available {subject.title()} chapters:
332
+ # {chapter_list}
333
+
334
+ # IMPORTANT: If the topic and content do NOT belong to {subject.title()}, respond with "NOT_MATCHING".
335
+
336
+ # If it matches, respond with ONLY the chapter number and name exactly as listed (e.g., "5. Origin and Evolution of Life").
337
+
338
+ # Response:"""
339
+
340
+ # try:
341
+ # response = groq_client.chat.completions.create(
342
+ # messages=[
343
+ # {
344
+ # "role": "system",
345
+ # "content": f"You are an expert at identifying which chapter textbook content belongs to. You can recognize when content doesn't match the subject. If the topic is from a different subject than {subject.title()}, respond with 'NOT_MATCHING'."
346
+ # },
347
+ # {
348
+ # "role": "user",
349
+ # "content": detection_prompt
350
+ # }
351
+ # ],
352
+ # model="llama-3.3-70b-versatile",
353
+ # temperature=0.1,
354
+ # max_tokens=50
355
+ # )
356
+
357
+ # result = response.choices[0].message.content.strip()
358
+
359
+ # # Check if topic doesn't match the subject
360
+ # if "NOT_MATCHING" in result.upper() or "NOT MATCHING" in result.upper():
361
+ # print(f"⚠️ Topic '{topic}' doesn't belong to {subject}")
362
+ # return None
363
+
364
+ # # Extract chapter name from response (remove number prefix if present)
365
+ # chapter = re.sub(r'^\d+\.\s*', '', result).strip()
366
+
367
+ # # Verify it's in our list
368
+ # for ch in chapters:
369
+ # if ch.lower() in chapter.lower() or chapter.lower() in ch.lower():
370
+ # print(f"βœ“ LLM detected chapter: {ch}")
371
+ # return ch
372
+
373
+ # print(f"⚠️ LLM response not in list: {result}")
374
+ # return None
375
+
376
+ # except Exception as e:
377
+ # print(f"⚠️ Chapter detection failed: {e}")
378
+ # return None
379
+
380
+ # # ------------------------------
381
+ # # MCQ Generation
382
+ # # ------------------------------
383
+ # def generate_mcqs(context, topic, subject, num_questions=5):
384
+ # # Check if Groq is available
385
+ # if not groq_client:
386
+ # error_msg = """ERROR: Groq API not initialized!
387
+ # Please check:
388
+ # 1. GROQ_API_KEY is set in Space Settings β†’ Repository secrets
389
+ # 2. API key is valid (get one from https://console.groq.com/keys)
390
+ # 3. Space has been restarted after adding the key
391
+ # Current status: API key not found or invalid."""
392
+ # return error_msg, None
393
+
394
+ # # Check cache
395
+ # context_hash = hashlib.md5(context.encode()).hexdigest()[:8]
396
+ # cache_key = get_cache_key(topic, subject, context_hash) + f":{num_questions}"
397
+
398
+ # if cache_key in MCQ_CACHE:
399
+ # print("βœ“ Using cached MCQs")
400
+ # return MCQ_CACHE[cache_key]["mcqs"], MCQ_CACHE[cache_key]["chapter"]
401
+
402
+ # print(f"πŸ€– Generating {num_questions} MCQs for {subject} - {topic}")
403
+
404
+ # # Detect the chapter from our actual chapter list
405
+ # chapter = detect_chapter_from_list(context, topic, subject)
406
+
407
+ # # If chapter is None, topic doesn't belong to this subject
408
+ # if chapter is None:
409
+ # error_msg = f"❌ The topic '{topic}' does not belong to {subject.title()}.\n\nPlease enter a topic related to {subject.title()} or select the correct subject."
410
+ # print(f"⚠️ Topic mismatch: '{topic}' not in {subject}")
411
+ # return error_msg, None
412
+
413
+ # prompt = f"""You are a Class-12 {subject.title()} teacher creating MCQs.
414
+ # Topic: "{topic}"
415
+ # Chapter: "{chapter}"
416
+ # Reference material from textbook:
417
+ # {context[:1500]}
418
+
419
+ # Generate exactly {num_questions} multiple-choice questions based on the reference material.
420
+
421
+ # FORMAT (follow EXACTLY):
422
+ # Q1. [Question based on material]
423
+ # A) [Option 1]
424
+ # B) [Option 2]
425
+ # C) [Option 3]
426
+ # D) [Option 4]
427
+ # Answer: [A/B/C/D] - [Brief explanation]
428
+
429
+ # Q2. [Question based on material]
430
+ # A) [Option 1]
431
+ # B) [Option 2]
432
+ # C) [Option 3]
433
+ # D) [Option 4]
434
+ # Answer: [A/B/C/D] - [Brief explanation]
435
+
436
+ # Continue for Q3, Q4, Q5{"..." if num_questions > 5 else ""}.
437
+
438
+ # REQUIREMENTS:
439
+ # - All questions must be answerable from the reference material
440
+ # - All 4 options should be plausible
441
+ # - Correct answer must be clearly supported by material
442
+ # - Keep explanations brief (1-2 sentences)
443
+
444
+ # Generate {num_questions} MCQs now:"""
445
+
446
+ # try:
447
+ # # Adjust max_tokens based on number of questions
448
+ # max_tokens = min(3000, 300 * num_questions)
449
+
450
+ # chat_completion = groq_client.chat.completions.create(
451
+ # messages=[
452
+ # {
453
+ # "role": "system",
454
+ # "content": "You are an expert Class-12 teacher who creates high-quality MCQs from textbook content. You always follow the exact format specified."
455
+ # },
456
+ # {
457
+ # "role": "user",
458
+ # "content": prompt
459
+ # }
460
+ # ],
461
+ # model="llama-3.3-70b-versatile",
462
+ # temperature=0.3,
463
+ # max_tokens=max_tokens,
464
+ # top_p=0.9
465
+ # )
466
+
467
+ # result = chat_completion.choices[0].message.content.strip()
468
+ # result = clean_mcq_output(result)
469
+
470
+ # # Cache both MCQs and chapter
471
+ # cache_mcq(cache_key, {"mcqs": result, "chapter": chapter})
472
+
473
+ # print("βœ“ MCQs generated successfully")
474
+ # return result, chapter
475
+
476
+ # except Exception as e:
477
+ # error_msg = f"""Error calling Groq API: {str(e)}
478
+ # Possible causes:
479
+ # 1. Rate limit exceeded (wait a moment)
480
+ # 2. Invalid API key
481
+ # 3. Network issue
482
+ # Please try again in a few seconds."""
483
+ # print(f"❌ Groq API Error: {e}")
484
+ # return error_msg, chapter
485
+
486
+ # def clean_mcq_output(text):
487
+ # lines = text.split('\n')
488
+ # cleaned_lines = []
489
+
490
+ # for line in lines:
491
+ # line = line.strip()
492
+
493
+ # if (re.match(r'^Q\d+\.', line) or
494
+ # line.startswith(('A)', 'B)', 'C)', 'D)', 'Answer:', 'Correct Answer:')) or
495
+ # not line):
496
+
497
+ # if line.startswith('Correct Answer:'):
498
+ # line = line.replace('Correct Answer:', 'Answer:')
499
+
500
+ # cleaned_lines.append(line)
501
+
502
+ # return '\n'.join(cleaned_lines)
503
+
504
+ # # ------------------------------
505
+ # # HTML UI
506
+ # # ------------------------------
507
+ # HTML_TEMPLATE = """
508
+ # <!DOCTYPE html>
509
+ # <html>
510
+ # <head>
511
+ # <title>Class 12 PCB MCQ Generator</title>
512
+ # <style>
513
+ # * { margin: 0; padding: 0; box-sizing: border-box; }
514
+ # body {
515
+ # font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
516
+ # background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
517
+ # min-height: 100vh;
518
+ # padding: 20px;
519
+ # }
520
+ # .container {
521
+ # max-width: 900px;
522
+ # margin: 0 auto;
523
+ # background: white;
524
+ # border-radius: 20px;
525
+ # box-shadow: 0 20px 60px rgba(0,0,0,0.3);
526
+ # overflow: hidden;
527
+ # }
528
+ # .header {
529
+ # background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
530
+ # color: white;
531
+ # padding: 30px;
532
+ # text-align: center;
533
+ # }
534
+ # .header h1 { font-size: 2.5em; margin-bottom: 10px; }
535
+ # .content { padding: 40px; }
536
+ # .form-group { margin-bottom: 25px; }
537
+ # label {
538
+ # display: block;
539
+ # font-weight: 600;
540
+ # margin-bottom: 10px;
541
+ # color: #333;
542
+ # font-size: 16px;
543
+ # }
544
+ # select, input {
545
+ # width: 100%;
546
+ # padding: 15px;
547
+ # border: 2px solid #e0e0e0;
548
+ # border-radius: 10px;
549
+ # font-size: 16px;
550
+ # font-family: inherit;
551
+ # transition: border-color 0.3s;
552
+ # }
553
+ # select:focus, input:focus {
554
+ # outline: none;
555
+ # border-color: #667eea;
556
+ # }
557
+ # button {
558
+ # width: 100%;
559
+ # padding: 18px;
560
+ # background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
561
+ # color: white;
562
+ # border: none;
563
+ # border-radius: 10px;
564
+ # font-size: 18px;
565
+ # font-weight: 600;
566
+ # cursor: pointer;
567
+ # transition: all 0.3s;
568
+ # }
569
+ # button:hover {
570
+ # transform: translateY(-2px);
571
+ # box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4);
572
+ # }
573
+ # button:disabled {
574
+ # background: #ccc;
575
+ # cursor: not-allowed;
576
+ # transform: none;
577
+ # }
578
+ # .result {
579
+ # margin-top: 30px;
580
+ # padding: 25px;
581
+ # background: #f8f9fa;
582
+ # border-radius: 10px;
583
+ # border-left: 4px solid #667eea;
584
+ # display: none;
585
+ # }
586
+ # .result.show { display: block; }
587
+ # .result h3 {
588
+ # color: #667eea;
589
+ # margin-bottom: 20px;
590
+ # font-size: 1.4em;
591
+ # }
592
+ # .chapter-info {
593
+ # background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
594
+ # color: white;
595
+ # padding: 15px 20px;
596
+ # border-radius: 8px;
597
+ # margin-bottom: 20px;
598
+ # display: flex;
599
+ # align-items: center;
600
+ # gap: 10px;
601
+ # font-size: 16px;
602
+ # }
603
+ # .chapter-icon {
604
+ # font-size: 24px;
605
+ # }
606
+ # .chapter-text {
607
+ # flex: 1;
608
+ # }
609
+ # .chapter-name {
610
+ # font-weight: 700;
611
+ # font-size: 18px;
612
+ # }
613
+ # .mcq-content {
614
+ # background: white;
615
+ # padding: 25px;
616
+ # border-radius: 8px;
617
+ # white-space: pre-wrap;
618
+ # line-height: 1.9;
619
+ # font-size: 15px;
620
+ # }
621
+ # .loading {
622
+ # text-align: center;
623
+ # padding: 30px;
624
+ # display: none;
625
+ # }
626
+ # .loading.show { display: block; }
627
+ # .spinner {
628
+ # border: 4px solid #f3f3f3;
629
+ # border-top: 4px solid #667eea;
630
+ # border-radius: 50%;
631
+ # width: 50px;
632
+ # height: 50px;
633
+ # animation: spin 1s linear infinite;
634
+ # margin: 0 auto 15px;
635
+ # }
636
+ # @keyframes spin {
637
+ # 0% { transform: rotate(0deg); }
638
+ # 100% { transform: rotate(360deg); }
639
+ # }
640
+ # .subject-tag {
641
+ # display: inline-block;
642
+ # padding: 5px 15px;
643
+ # border-radius: 20px;
644
+ # font-size: 13px;
645
+ # font-weight: 600;
646
+ # margin-right: 10px;
647
+ # }
648
+ # .bio { background: #d4edda; color: #155724; }
649
+ # .chem { background: #d1ecf1; color: #0c5460; }
650
+ # .phy { background: #f8d7da; color: #721c24; }
651
+ # .api-badge {
652
+ # background: #17a2b8;
653
+ # color: white;
654
+ # padding: 5px 12px;
655
+ # border-radius: 15px;
656
+ # font-size: 12px;
657
+ # margin-left: 10px;
658
+ # }
659
+ # </style>
660
+ # </head>
661
+ # <body>
662
+ # <div class="container">
663
+ # <div class="header">
664
+ # <h1>πŸŽ“ Class 12 PCB MCQ Generator</h1>
665
+ # <p style="font-size: 1.1em; margin-bottom: 15px;">
666
+ # Generate practice MCQs from your textbooks
667
+ # <span class="api-badge">⚑ Llama 3.3 70B</span>
668
+ # </p>
669
+ # <div>
670
+ # <span class="subject-tag bio">Biology</span>
671
+ # <span class="subject-tag chem">Chemistry</span>
672
+ # <span class="subject-tag phy">Physics</span>
673
+ # </div>
674
+ # </div>
675
+
676
+ # <div class="content">
677
+ # <div class="form-group">
678
+ # <label for="subject">πŸ“š Select Subject</label>
679
+ # <select id="subject">
680
+ # <option value="biology">Biology</option>
681
+ # <option value="chemistry">Chemistry</option>
682
+ # <option value="physics">Physics</option>
683
+ # </select>
684
+ # </div>
685
+
686
+ # <div class="form-group">
687
+ # <label for="topic">✏️ Enter Topic</label>
688
+ # <input type="text" id="topic" placeholder="e.g., Mitochondria, Chemical Bonding, Newton's Laws">
689
+ # </div>
690
+
691
+ # <div class="form-group">
692
+ # <label for="numQuestions">πŸ”’ Number of MCQs</label>
693
+ # <select id="numQuestions">
694
+ # <option value="1">1 MCQ</option>
695
+ # <option value="2">2 MCQs</option>
696
+ # <option value="3">3 MCQs</option>
697
+ # <option value="4">4 MCQs</option>
698
+ # <option value="5" selected>5 MCQs</option>
699
+ # <option value="6">6 MCQs</option>
700
+ # <option value="7">7 MCQs</option>
701
+ # <option value="8">8 MCQs</option>
702
+ # <option value="9">9 MCQs</option>
703
+ # <option value="10">10 MCQs</option>
704
+ # <option value="11">11 MCQs</option>
705
+ # <option value="12">12 MCQs</option>
706
+ # <option value="13">13 MCQs</option>
707
+ # <option value="14">14 MCQs</option>
708
+ # <option value="15">15 MCQs</option>
709
+ # <option value="16">16 MCQs</option>
710
+ # <option value="17">17 MCQs</option>
711
+ # <option value="18">18 MCQs</option>
712
+ # <option value="19">19 MCQs</option>
713
+ # <option value="20">20 MCQs</option>
714
+ # </select>
715
+ # </div>
716
+
717
+ # <button onclick="generateMCQs()">πŸš€ Generate MCQs</button>
718
+
719
+ # <div class="loading" id="loading">
720
+ # <div class="spinner"></div>
721
+ # <p style="color: #666; font-size: 16px;">Generating MCQs with AI...</p>
722
+ # <p style="color: #999; font-size: 13px; margin-top: 10px;">⚑ Detecting chapter from textbook...</p>
723
+ # </div>
724
+
725
+ # <div class="result" id="result">
726
+ # <h3>πŸ“ Generated MCQs:</h3>
727
+
728
+ # <div class="chapter-info" id="chapterInfo" style="display: none;">
729
+ # <span class="chapter-icon">πŸ“–</span>
730
+ # <div class="chapter-text">
731
+ # <div style="font-size: 13px; opacity: 0.9;">Chapter:</div>
732
+ # <div class="chapter-name" id="chapterName"></div>
733
+ # </div>
734
+ # </div>
735
+
736
+ # <div style="background: #d4edda; padding: 12px; border-radius: 6px; margin-bottom: 15px; color: #155724; font-size: 14px;">
737
+ # βœ“ <strong>High Quality:</strong> Generated by Llama 3.3 70B via Groq API
738
+ # </div>
739
+ # <div class="mcq-content" id="mcqContent"></div>
740
+ # </div>
741
+ # </div>
742
+ # </div>
743
+ # <script>
744
+ # async function generateMCQs() {
745
+ # const subject = document.getElementById('subject').value;
746
+ # const topic = document.getElementById('topic').value.trim();
747
+ # const numQuestions = parseInt(document.getElementById('numQuestions').value);
748
+
749
+ # if (!topic) {
750
+ # alert('⚠️ Please enter a topic!');
751
+ # return;
752
+ # }
753
+
754
+ # const loading = document.getElementById('loading');
755
+ # const result = document.getElementById('result');
756
+ # const btn = document.querySelector('button');
757
+ # const chapterInfo = document.getElementById('chapterInfo');
758
+
759
+ # loading.classList.add('show');
760
+ # result.classList.remove('show');
761
+ # chapterInfo.style.display = 'none';
762
+ # btn.disabled = true;
763
+ # btn.textContent = '⏳ Generating...';
764
+
765
+ # try {
766
+ # const response = await fetch('/generate', {
767
+ # method: 'POST',
768
+ # headers: {'Content-Type': 'application/json'},
769
+ # body: JSON.stringify({subject, topic, num_questions: numQuestions})
770
+ # });
771
+
772
+ # const data = await response.json();
773
+
774
+ # if (data.error) {
775
+ # alert('❌ Error: ' + data.error);
776
+ # return;
777
+ # }
778
+
779
+ # // Display chapter info
780
+ # if (data.chapter && data.chapter !== 'Unknown Chapter') {
781
+ # document.getElementById('chapterName').textContent = data.chapter;
782
+ # chapterInfo.style.display = 'flex';
783
+ # }
784
+
785
+ # document.getElementById('mcqContent').textContent = data.mcqs;
786
+ # result.classList.add('show');
787
+ # } catch (error) {
788
+ # alert('❌ Error: ' + error.message);
789
+ # } finally {
790
+ # loading.classList.remove('show');
791
+ # btn.disabled = false;
792
+ # btn.textContent = 'πŸš€ Generate MCQs';
793
+ # }
794
+ # }
795
+
796
+ # document.getElementById('topic').addEventListener('keypress', function(e) {
797
+ # if (e.key === 'Enter') {
798
+ # generateMCQs();
799
+ # }
800
+ # });
801
+ # </script>
802
+ # </body>
803
+ # </html>
804
+ # """
805
+
806
+ # # ------------------------------
807
+ # # Routes
808
+ # # ------------------------------
809
+ # @app.route("/")
810
+ # def home():
811
+ # return render_template_string(HTML_TEMPLATE)
812
+
813
+ # @app.route("/generate", methods=["POST"])
814
+ # def generate():
815
+ # try:
816
+ # data = request.json
817
+ # subject = data.get("subject", "").lower()
818
+ # topic = data.get("topic", "")
819
+ # num_questions = data.get("num_questions", 5)
820
+
821
+ # # Validate num_questions
822
+ # try:
823
+ # num_questions = int(num_questions)
824
+ # if num_questions < 1 or num_questions > 20:
825
+ # num_questions = 5
826
+ # except:
827
+ # num_questions = 5
828
+
829
+ # if not topic:
830
+ # return jsonify({"error": "Topic is required"}), 400
831
+
832
+ # if subject not in SUBJECTS:
833
+ # return jsonify({"error": "Invalid subject"}), 400
834
+
835
+ # print(f"\nπŸ” Validating topic for {subject}...")
836
+
837
+ # # STEP 1: Validate if topic belongs to subject (BEFORE RAG search)
838
+ # if not validate_topic_subject(topic, subject):
839
+ # subject_names = {
840
+ # "biology": "Biology",
841
+ # "chemistry": "Chemistry",
842
+ # "physics": "Physics"
843
+ # }
844
+ # error_msg = f"The topic '{topic}' does not appear to be related to {subject_names[subject]}.\n\nPlease either:\nβ€’ Enter a {subject_names[subject]}-related topic, or\nβ€’ Select the correct subject for this topic"
845
+ # return jsonify({"error": error_msg}), 400
846
+
847
+ # print(f"βœ“ Topic validated for {subject}")
848
+ # print(f"πŸ” Searching {subject} for: {topic}")
849
+
850
+ # # STEP 2: RAG search
851
+ # context = rag_search(topic, subject, k=5)
852
+
853
+ # if not context or len(context.strip()) < 50:
854
+ # return jsonify({"error": f"No content found for: {topic}"}), 404
855
+
856
+ # print(f"βœ“ Context found ({len(context)} chars)")
857
+
858
+ # # STEP 3: Generate MCQs
859
+ # mcqs, chapter = generate_mcqs(context, topic, subject, num_questions)
860
+
861
+ # # Check if there was a subject mismatch
862
+ # if chapter is None:
863
+ # return jsonify({"error": mcqs}), 400
864
+
865
+ # return jsonify({
866
+ # "mcqs": mcqs,
867
+ # "subject": subject,
868
+ # "chapter": chapter
869
+ # })
870
+
871
+ # except Exception as e:
872
+ # print(f"❌ Error: {e}")
873
+ # import traceback
874
+ # traceback.print_exc()
875
+ # return jsonify({"error": str(e)}), 500
876
+
877
+ # @app.route("/health")
878
+ # def health():
879
+ # return jsonify({
880
+ # "status": "healthy",
881
+ # "groq_available": groq_client is not None,
882
+ # "cache_size": len(MCQ_CACHE)
883
+ # })
884
+
885
+ # # ------------------------------
886
+ # # Run
887
+ # # ------------------------------
888
+ # if __name__ == "__main__":
889
+ # port = int(os.environ.get("PORT", 7860))
890
+ # print(f"\nπŸš€ Starting server on port {port}...\n")
891
+ # app.run(host="0.0.0.0", port=port, debug=False)
892
+
893
  import pickle
894
  import faiss
895
  from flask import Flask, request, jsonify, render_template_string