Redfire-1234 commited on
Commit
1e38aff
Β·
verified Β·
1 Parent(s): 9b86e1d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +620 -116
app.py CHANGED
@@ -1,13 +1,519 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import pickle
2
  import faiss
3
  from flask import Flask, request, jsonify, render_template_string
4
  from sentence_transformers import SentenceTransformer
5
- from transformers import AutoTokenizer, AutoModelForCausalLM
6
  from huggingface_hub import hf_hub_download
7
- import torch
8
- import os
9
- from functools import lru_cache
10
  import hashlib
 
 
 
11
 
12
  app = Flask(__name__)
13
 
@@ -15,6 +521,18 @@ print("=" * 50)
15
  print("Loading models and data...")
16
  print("=" * 50)
17
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  # ------------------------------
19
  # Load embedding model (CPU)
20
  # ------------------------------
@@ -60,34 +578,12 @@ print(f"βœ“ Biology: {len(SUBJECTS['biology']['chunks'])} chunks loaded")
60
  print(f"βœ“ Chemistry: {len(SUBJECTS['chemistry']['chunks'])} chunks loaded")
61
  print(f"βœ“ Physics: {len(SUBJECTS['physics']['chunks'])} chunks loaded")
62
 
63
- # ------------------------------
64
- # Load LLM model (CPU) with optimizations
65
- # ------------------------------
66
- model_name = "Qwen/Qwen2.5-3B-Instruct"
67
- print(f"Loading LLM: {model_name}")
68
- tokenizer = AutoTokenizer.from_pretrained(model_name)
69
- device = "cpu"
70
-
71
- # OPTIMIZATION: Load model with better dtype for CPU
72
- model = AutoModelForCausalLM.from_pretrained(
73
- model_name,
74
- torch_dtype=torch.float32,
75
- low_cpu_mem_usage=True # Optimization: Better memory management
76
- ).to(device)
77
-
78
- # OPTIMIZATION: Set model to eval mode and optimize for inference
79
- model.eval()
80
- if hasattr(torch, 'set_num_threads'):
81
- torch.set_num_threads(4) # Optimization: Use multiple CPU threads
82
-
83
- print(f"βœ“ LLM loaded on {device}")
84
-
85
  print("=" * 50)
86
  print("All models loaded successfully!")
87
  print("=" * 50)
88
 
89
  # ------------------------------
90
- # OPTIMIZATION: Add caching for MCQ generation
91
  # ------------------------------
92
  MCQ_CACHE = {}
93
  MAX_CACHE_SIZE = 100
@@ -99,12 +595,11 @@ def get_cache_key(topic, subject, context_hash):
99
  def cache_mcq(key, mcqs):
100
  """Cache generated MCQs with size limit"""
101
  if len(MCQ_CACHE) >= MAX_CACHE_SIZE:
102
- # Remove oldest entry
103
  MCQ_CACHE.pop(next(iter(MCQ_CACHE)))
104
  MCQ_CACHE[key] = mcqs
105
 
106
  # ------------------------------
107
- # RAG Search in specific subject (optimized)
108
  # ------------------------------
109
  def rag_search(query, subject, k=5):
110
  if subject not in SUBJECTS:
@@ -113,11 +608,9 @@ def rag_search(query, subject, k=5):
113
  chunks = SUBJECTS[subject]["chunks"]
114
  index = SUBJECTS[subject]["index"]
115
 
116
- # OPTIMIZATION: Encode query (already fast with sentence-transformers)
117
  q_emb = embed_model.encode([query], show_progress_bar=False).astype("float32")
118
  D, I = index.search(q_emb, k)
119
 
120
- # Get the actual chunks
121
  results = []
122
  for idx in I[0]:
123
  if idx < len(chunks):
@@ -126,10 +619,10 @@ def rag_search(query, subject, k=5):
126
  return "\n\n".join(results)
127
 
128
  # ------------------------------
129
- # OPTIMIZED MCQ Generation with reduced tokens
130
  # ------------------------------
131
  def generate_mcqs(context, topic, subject):
132
- # OPTIMIZATION: Check cache first
133
  context_hash = hashlib.md5(context.encode()).hexdigest()[:8]
134
  cache_key = get_cache_key(topic, subject, context_hash)
135
 
@@ -137,60 +630,92 @@ def generate_mcqs(context, topic, subject):
137
  print("βœ“ Using cached MCQs")
138
  return MCQ_CACHE[cache_key]
139
 
140
- # OPTIMIZATION: Shortened prompt for faster generation
141
- prompt = f"""You are a Class-12 {subject.title()} teacher creating MCQs.
142
- Topic: "{topic}"
143
- Context:
144
- {context}
145
-
146
- Generate exactly 5 MCQs in this format:
147
- Q1. [Question]
148
- A) [Option]
149
- B) [Option]
150
- C) [Option]
151
- D) [Option]
152
- Correct Answer: [Letter] - [Reason]
153
-
154
- Rules: Make correct answer from context, realistic distractors.
155
- Generate 5 MCQs:"""
156
 
157
- # OPTIMIZATION: Reduced max_length for faster tokenization
158
- inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=1536).to(device)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
- # OPTIMIZATION: Use torch.no_grad() for inference (saves memory)
161
- with torch.no_grad():
162
- # OPTIMIZATION: Reduced max_new_tokens from 900 to 600 (sufficient for 5 MCQs)
163
- # OPTIMIZATION: Reduced temperature from 0.15 to 0.1 (faster, more deterministic)
164
- # OPTIMIZATION: Added num_beams=1 (greedy decoding, faster than sampling)
165
- outputs = model.generate(
166
- **inputs,
167
- max_new_tokens=600, # Reduced from 900
168
- temperature=0.1, # Reduced from 0.15
169
- top_p=0.85, # Slightly adjusted
170
- do_sample=True,
171
- repetition_penalty=1.15,
172
- pad_token_id=tokenizer.eos_token_id # Optimization: Explicit pad token
 
 
 
 
173
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
- result = tokenizer.decode(outputs[0], skip_special_tokens=True)
176
-
177
- # Extract only the generated MCQs
178
- if "Generate 5 MCQs:" in result:
179
- result = result.split("Generate 5 MCQs:")[-1].strip()
180
-
181
- # OPTIMIZATION: Cache the result
182
- cache_mcq(cache_key, result)
 
 
 
 
 
183
 
184
- return result
185
-
186
- def verify_and_correct_answers(mcqs_text, context):
187
- """
188
- This function is kept for future enhancements
189
- """
190
- return mcqs_text
191
 
192
  # ------------------------------
193
- # HTML UI (with improved loading message)
194
  # ------------------------------
195
  HTML_TEMPLATE = """
196
  <!DOCTYPE html>
@@ -221,9 +746,7 @@ HTML_TEMPLATE = """
221
  }
222
  .header h1 { font-size: 2.5em; margin-bottom: 10px; }
223
  .content { padding: 40px; }
224
- .form-group {
225
- margin-bottom: 25px;
226
- }
227
  label {
228
  display: block;
229
  font-weight: 600;
@@ -317,8 +840,8 @@ HTML_TEMPLATE = """
317
  .bio { background: #d4edda; color: #155724; }
318
  .chem { background: #d1ecf1; color: #0c5460; }
319
  .phy { background: #f8d7da; color: #721c24; }
320
- .optimization-badge {
321
- background: #28a745;
322
  color: white;
323
  padding: 5px 12px;
324
  border-radius: 15px;
@@ -331,7 +854,10 @@ HTML_TEMPLATE = """
331
  <div class="container">
332
  <div class="header">
333
  <h1>πŸŽ“ Class 12 PCB MCQ Generator</h1>
334
- <p style="font-size: 1.1em; margin-bottom: 15px;">Generate practice MCQs from your textbooks <span class="optimization-badge">⚑ Optimized</span></p>
 
 
 
335
  <div>
336
  <span class="subject-tag bio">Biology</span>
337
  <span class="subject-tag chem">Chemistry</span>
@@ -358,14 +884,14 @@ HTML_TEMPLATE = """
358
 
359
  <div class="loading" id="loading">
360
  <div class="spinner"></div>
361
- <p style="color: #666; font-size: 16px;">Generating MCQs... This may take 20-40 seconds</p>
362
- <p style="color: #999; font-size: 13px; margin-top: 10px;">⚑ Optimized for faster generation</p>
363
  </div>
364
 
365
  <div class="result" id="result">
366
  <h3>πŸ“ Generated MCQs:</h3>
367
- <div style="background: #fff3cd; padding: 12px; border-radius: 6px; margin-bottom: 15px; color: #856404; font-size: 14px;">
368
- ⚠️ <strong>Note:</strong> AI-generated answers may occasionally be incorrect. Please verify answers using your textbook.
369
  </div>
370
  <div class="mcq-content" id="mcqContent"></div>
371
  </div>
@@ -415,7 +941,6 @@ HTML_TEMPLATE = """
415
  }
416
  }
417
 
418
- // Allow Enter key to submit
419
  document.getElementById('topic').addEventListener('keypress', function(e) {
420
  if (e.key === 'Enter') {
421
  generateMCQs();
@@ -444,28 +969,23 @@ def generate():
444
  return jsonify({"error": "Topic is required"}), 400
445
 
446
  if subject not in SUBJECTS:
447
- return jsonify({"error": "Invalid subject. Choose biology, chemistry, or physics."}), 400
448
 
449
  print(f"\nπŸ” Searching {subject} for topic: {topic}")
450
 
451
- # Retrieve context from RAG
452
  context = rag_search(topic, subject, k=5)
453
 
454
  if not context or len(context.strip()) < 50:
455
- return jsonify({"error": f"No relevant content found in {subject} for topic: {topic}"}), 404
456
 
457
  print(f"βœ“ Found context ({len(context)} chars)")
458
 
459
- # Generate MCQs (now with caching)
460
- print("πŸ€– Generating MCQs...")
461
  mcqs = generate_mcqs(context, topic, subject)
462
 
463
- print("βœ“ MCQs generated successfully")
464
-
465
  return jsonify({"mcqs": mcqs, "subject": subject})
466
 
467
  except Exception as e:
468
- print(f"❌ Error in /generate: {e}")
469
  import traceback
470
  traceback.print_exc()
471
  return jsonify({"error": str(e)}), 500
@@ -474,24 +994,10 @@ def generate():
474
  def health():
475
  return jsonify({
476
  "status": "healthy",
477
- "subjects": {
478
- "biology": len(SUBJECTS["biology"]["chunks"]),
479
- "chemistry": len(SUBJECTS["chemistry"]["chunks"]),
480
- "physics": len(SUBJECTS["physics"]["chunks"])
481
- },
482
  "cache_size": len(MCQ_CACHE)
483
  })
484
 
485
- # OPTIMIZATION: Add cache stats endpoint
486
- @app.route("/cache/stats")
487
- def cache_stats():
488
- return jsonify({
489
- "cached_topics": len(MCQ_CACHE),
490
- "max_cache_size": MAX_CACHE_SIZE,
491
- "cache_keys": list(MCQ_CACHE.keys())
492
- })
493
-
494
- # OPTIMIZATION: Add cache clear endpoint (optional)
495
  @app.route("/cache/clear", methods=["POST"])
496
  def clear_cache():
497
  MCQ_CACHE.clear()
@@ -505,5 +1011,3 @@ if __name__ == "__main__":
505
  print(f"\nπŸš€ Starting Flask on 0.0.0.0:{port}\n")
506
  app.run(host="0.0.0.0", port=port, debug=False)
507
 
508
-
509
-
 
1
+ # import pickle
2
+ # import faiss
3
+ # from flask import Flask, request, jsonify, render_template_string
4
+ # from sentence_transformers import SentenceTransformer
5
+ # from transformers import AutoTokenizer, AutoModelForCausalLM
6
+ # from huggingface_hub import hf_hub_download
7
+ # import torch
8
+ # import os
9
+ # from functools import lru_cache
10
+ # import hashlib
11
+
12
+ # app = Flask(__name__)
13
+
14
+ # print("=" * 50)
15
+ # print("Loading models and data...")
16
+ # print("=" * 50)
17
+
18
+ # # ------------------------------
19
+ # # Load embedding model (CPU)
20
+ # # ------------------------------
21
+ # embed_model = SentenceTransformer("all-MiniLM-L6-v2")
22
+ # print("βœ“ Embedding model loaded")
23
+
24
+ # # ------------------------------
25
+ # # Download files from Hugging Face
26
+ # # ------------------------------
27
+ # REPO_ID = "Redfire-1234/pcb_tutor"
28
+
29
+ # print("Downloading subject files from Hugging Face...")
30
+
31
+ # # Download Biology files
32
+ # bio_chunks_path = hf_hub_download(repo_id=REPO_ID, filename="bio_chunks.pkl", repo_type="model")
33
+ # faiss_bio_path = hf_hub_download(repo_id=REPO_ID, filename="faiss_bio.bin", repo_type="model")
34
+
35
+ # # Download Chemistry files
36
+ # chem_chunks_path = hf_hub_download(repo_id=REPO_ID, filename="chem_chunks.pkl", repo_type="model")
37
+ # faiss_chem_path = hf_hub_download(repo_id=REPO_ID, filename="faiss_chem.bin", repo_type="model")
38
+
39
+ # # Download Physics files
40
+ # phy_chunks_path = hf_hub_download(repo_id=REPO_ID, filename="phy_chunks.pkl", repo_type="model")
41
+ # faiss_phy_path = hf_hub_download(repo_id=REPO_ID, filename="faiss_phy.bin", repo_type="model")
42
+
43
+ # # Load all subjects into memory
44
+ # SUBJECTS = {
45
+ # "biology": {
46
+ # "chunks": pickle.load(open(bio_chunks_path, "rb")),
47
+ # "index": faiss.read_index(faiss_bio_path)
48
+ # },
49
+ # "chemistry": {
50
+ # "chunks": pickle.load(open(chem_chunks_path, "rb")),
51
+ # "index": faiss.read_index(faiss_chem_path)
52
+ # },
53
+ # "physics": {
54
+ # "chunks": pickle.load(open(phy_chunks_path, "rb")),
55
+ # "index": faiss.read_index(faiss_phy_path)
56
+ # }
57
+ # }
58
+
59
+ # print(f"βœ“ Biology: {len(SUBJECTS['biology']['chunks'])} chunks loaded")
60
+ # print(f"βœ“ Chemistry: {len(SUBJECTS['chemistry']['chunks'])} chunks loaded")
61
+ # print(f"βœ“ Physics: {len(SUBJECTS['physics']['chunks'])} chunks loaded")
62
+
63
+ # # ------------------------------
64
+ # # Load LLM model (CPU) with optimizations
65
+ # # ------------------------------
66
+ # model_name = "Qwen/Qwen2.5-3B-Instruct"
67
+ # print(f"Loading LLM: {model_name}")
68
+ # tokenizer = AutoTokenizer.from_pretrained(model_name)
69
+ # device = "cpu"
70
+
71
+ # # OPTIMIZATION: Load model with better dtype for CPU
72
+ # model = AutoModelForCausalLM.from_pretrained(
73
+ # model_name,
74
+ # torch_dtype=torch.float32,
75
+ # low_cpu_mem_usage=True # Optimization: Better memory management
76
+ # ).to(device)
77
+
78
+ # # OPTIMIZATION: Set model to eval mode and optimize for inference
79
+ # model.eval()
80
+ # if hasattr(torch, 'set_num_threads'):
81
+ # torch.set_num_threads(4) # Optimization: Use multiple CPU threads
82
+
83
+ # print(f"βœ“ LLM loaded on {device}")
84
+
85
+ # print("=" * 50)
86
+ # print("All models loaded successfully!")
87
+ # print("=" * 50)
88
+
89
+ # # ------------------------------
90
+ # # OPTIMIZATION: Add caching for MCQ generation
91
+ # # ------------------------------
92
+ # MCQ_CACHE = {}
93
+ # MAX_CACHE_SIZE = 100
94
+
95
+ # def get_cache_key(topic, subject, context_hash):
96
+ # """Generate a unique cache key"""
97
+ # return f"{subject}:{topic}:{context_hash}"
98
+
99
+ # def cache_mcq(key, mcqs):
100
+ # """Cache generated MCQs with size limit"""
101
+ # if len(MCQ_CACHE) >= MAX_CACHE_SIZE:
102
+ # # Remove oldest entry
103
+ # MCQ_CACHE.pop(next(iter(MCQ_CACHE)))
104
+ # MCQ_CACHE[key] = mcqs
105
+
106
+ # # ------------------------------
107
+ # # RAG Search in specific subject (optimized)
108
+ # # ------------------------------
109
+ # def rag_search(query, subject, k=5):
110
+ # if subject not in SUBJECTS:
111
+ # return None
112
+
113
+ # chunks = SUBJECTS[subject]["chunks"]
114
+ # index = SUBJECTS[subject]["index"]
115
+
116
+ # # OPTIMIZATION: Encode query (already fast with sentence-transformers)
117
+ # q_emb = embed_model.encode([query], show_progress_bar=False).astype("float32")
118
+ # D, I = index.search(q_emb, k)
119
+
120
+ # # Get the actual chunks
121
+ # results = []
122
+ # for idx in I[0]:
123
+ # if idx < len(chunks):
124
+ # results.append(chunks[idx])
125
+
126
+ # return "\n\n".join(results)
127
+
128
+ # # ------------------------------
129
+ # # OPTIMIZED MCQ Generation with reduced tokens
130
+ # # ------------------------------
131
+ # def generate_mcqs(context, topic, subject):
132
+ # # OPTIMIZATION: Check cache first
133
+ # context_hash = hashlib.md5(context.encode()).hexdigest()[:8]
134
+ # cache_key = get_cache_key(topic, subject, context_hash)
135
+
136
+ # if cache_key in MCQ_CACHE:
137
+ # print("βœ“ Using cached MCQs")
138
+ # return MCQ_CACHE[cache_key]
139
+
140
+ # # OPTIMIZATION: Shortened prompt for faster generation
141
+ # prompt = f"""You are a Class-12 {subject.title()} teacher creating MCQs.
142
+ # Topic: "{topic}"
143
+ # Context:
144
+ # {context}
145
+
146
+ # Generate exactly 5 MCQs in this format:
147
+ # Q1. [Question]
148
+ # A) [Option]
149
+ # B) [Option]
150
+ # C) [Option]
151
+ # D) [Option]
152
+ # Correct Answer: [Letter] - [Reason]
153
+
154
+ # Rules: Make correct answer from context, realistic distractors.
155
+ # Generate 5 MCQs:"""
156
+
157
+ # # OPTIMIZATION: Reduced max_length for faster tokenization
158
+ # inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=1536).to(device)
159
+
160
+ # # OPTIMIZATION: Use torch.no_grad() for inference (saves memory)
161
+ # with torch.no_grad():
162
+ # # OPTIMIZATION: Reduced max_new_tokens from 900 to 600 (sufficient for 5 MCQs)
163
+ # # OPTIMIZATION: Reduced temperature from 0.15 to 0.1 (faster, more deterministic)
164
+ # # OPTIMIZATION: Added num_beams=1 (greedy decoding, faster than sampling)
165
+ # outputs = model.generate(
166
+ # **inputs,
167
+ # max_new_tokens=600, # Reduced from 900
168
+ # temperature=0.1, # Reduced from 0.15
169
+ # top_p=0.85, # Slightly adjusted
170
+ # do_sample=True,
171
+ # repetition_penalty=1.15,
172
+ # pad_token_id=tokenizer.eos_token_id # Optimization: Explicit pad token
173
+ # )
174
+
175
+ # result = tokenizer.decode(outputs[0], skip_special_tokens=True)
176
+
177
+ # # Extract only the generated MCQs
178
+ # if "Generate 5 MCQs:" in result:
179
+ # result = result.split("Generate 5 MCQs:")[-1].strip()
180
+
181
+ # # OPTIMIZATION: Cache the result
182
+ # cache_mcq(cache_key, result)
183
+
184
+ # return result
185
+
186
+ # def verify_and_correct_answers(mcqs_text, context):
187
+ # """
188
+ # This function is kept for future enhancements
189
+ # """
190
+ # return mcqs_text
191
+
192
+ # # ------------------------------
193
+ # # HTML UI (with improved loading message)
194
+ # # ------------------------------
195
+ # HTML_TEMPLATE = """
196
+ # <!DOCTYPE html>
197
+ # <html>
198
+ # <head>
199
+ # <title>Class 12 PCB MCQ Generator</title>
200
+ # <style>
201
+ # * { margin: 0; padding: 0; box-sizing: border-box; }
202
+ # body {
203
+ # font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
204
+ # background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
205
+ # min-height: 100vh;
206
+ # padding: 20px;
207
+ # }
208
+ # .container {
209
+ # max-width: 900px;
210
+ # margin: 0 auto;
211
+ # background: white;
212
+ # border-radius: 20px;
213
+ # box-shadow: 0 20px 60px rgba(0,0,0,0.3);
214
+ # overflow: hidden;
215
+ # }
216
+ # .header {
217
+ # background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
218
+ # color: white;
219
+ # padding: 30px;
220
+ # text-align: center;
221
+ # }
222
+ # .header h1 { font-size: 2.5em; margin-bottom: 10px; }
223
+ # .content { padding: 40px; }
224
+ # .form-group {
225
+ # margin-bottom: 25px;
226
+ # }
227
+ # label {
228
+ # display: block;
229
+ # font-weight: 600;
230
+ # margin-bottom: 10px;
231
+ # color: #333;
232
+ # font-size: 16px;
233
+ # }
234
+ # select, input {
235
+ # width: 100%;
236
+ # padding: 15px;
237
+ # border: 2px solid #e0e0e0;
238
+ # border-radius: 10px;
239
+ # font-size: 16px;
240
+ # font-family: inherit;
241
+ # transition: border-color 0.3s;
242
+ # }
243
+ # select:focus, input:focus {
244
+ # outline: none;
245
+ # border-color: #667eea;
246
+ # }
247
+ # button {
248
+ # width: 100%;
249
+ # padding: 18px;
250
+ # background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
251
+ # color: white;
252
+ # border: none;
253
+ # border-radius: 10px;
254
+ # font-size: 18px;
255
+ # font-weight: 600;
256
+ # cursor: pointer;
257
+ # transition: all 0.3s;
258
+ # }
259
+ # button:hover {
260
+ # transform: translateY(-2px);
261
+ # box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4);
262
+ # }
263
+ # button:disabled {
264
+ # background: #ccc;
265
+ # cursor: not-allowed;
266
+ # transform: none;
267
+ # }
268
+ # .result {
269
+ # margin-top: 30px;
270
+ # padding: 25px;
271
+ # background: #f8f9fa;
272
+ # border-radius: 10px;
273
+ # border-left: 4px solid #667eea;
274
+ # display: none;
275
+ # }
276
+ # .result.show { display: block; }
277
+ # .result h3 {
278
+ # color: #667eea;
279
+ # margin-bottom: 20px;
280
+ # font-size: 1.4em;
281
+ # }
282
+ # .mcq-content {
283
+ # background: white;
284
+ # padding: 25px;
285
+ # border-radius: 8px;
286
+ # white-space: pre-wrap;
287
+ # line-height: 1.9;
288
+ # font-size: 15px;
289
+ # }
290
+ # .loading {
291
+ # text-align: center;
292
+ # padding: 30px;
293
+ # display: none;
294
+ # }
295
+ # .loading.show { display: block; }
296
+ # .spinner {
297
+ # border: 4px solid #f3f3f3;
298
+ # border-top: 4px solid #667eea;
299
+ # border-radius: 50%;
300
+ # width: 50px;
301
+ # height: 50px;
302
+ # animation: spin 1s linear infinite;
303
+ # margin: 0 auto 15px;
304
+ # }
305
+ # @keyframes spin {
306
+ # 0% { transform: rotate(0deg); }
307
+ # 100% { transform: rotate(360deg); }
308
+ # }
309
+ # .subject-tag {
310
+ # display: inline-block;
311
+ # padding: 5px 15px;
312
+ # border-radius: 20px;
313
+ # font-size: 13px;
314
+ # font-weight: 600;
315
+ # margin-right: 10px;
316
+ # }
317
+ # .bio { background: #d4edda; color: #155724; }
318
+ # .chem { background: #d1ecf1; color: #0c5460; }
319
+ # .phy { background: #f8d7da; color: #721c24; }
320
+ # .optimization-badge {
321
+ # background: #28a745;
322
+ # color: white;
323
+ # padding: 5px 12px;
324
+ # border-radius: 15px;
325
+ # font-size: 12px;
326
+ # margin-left: 10px;
327
+ # }
328
+ # </style>
329
+ # </head>
330
+ # <body>
331
+ # <div class="container">
332
+ # <div class="header">
333
+ # <h1>πŸŽ“ Class 12 PCB MCQ Generator</h1>
334
+ # <p style="font-size: 1.1em; margin-bottom: 15px;">Generate practice MCQs from your textbooks <span class="optimization-badge">⚑ Optimized</span></p>
335
+ # <div>
336
+ # <span class="subject-tag bio">Biology</span>
337
+ # <span class="subject-tag chem">Chemistry</span>
338
+ # <span class="subject-tag phy">Physics</span>
339
+ # </div>
340
+ # </div>
341
+
342
+ # <div class="content">
343
+ # <div class="form-group">
344
+ # <label for="subject">πŸ“š Select Subject</label>
345
+ # <select id="subject">
346
+ # <option value="biology">Biology</option>
347
+ # <option value="chemistry">Chemistry</option>
348
+ # <option value="physics">Physics</option>
349
+ # </select>
350
+ # </div>
351
+
352
+ # <div class="form-group">
353
+ # <label for="topic">✏️ Enter Topic</label>
354
+ # <input type="text" id="topic" placeholder="e.g., Mitochondria, Chemical Bonding, Newton's Laws">
355
+ # </div>
356
+
357
+ # <button onclick="generateMCQs()">πŸš€ Generate 5 MCQs</button>
358
+
359
+ # <div class="loading" id="loading">
360
+ # <div class="spinner"></div>
361
+ # <p style="color: #666; font-size: 16px;">Generating MCQs... This may take 20-40 seconds</p>
362
+ # <p style="color: #999; font-size: 13px; margin-top: 10px;">⚑ Optimized for faster generation</p>
363
+ # </div>
364
+
365
+ # <div class="result" id="result">
366
+ # <h3>πŸ“ Generated MCQs:</h3>
367
+ # <div style="background: #fff3cd; padding: 12px; border-radius: 6px; margin-bottom: 15px; color: #856404; font-size: 14px;">
368
+ # ⚠️ <strong>Note:</strong> AI-generated answers may occasionally be incorrect. Please verify answers using your textbook.
369
+ # </div>
370
+ # <div class="mcq-content" id="mcqContent"></div>
371
+ # </div>
372
+ # </div>
373
+ # </div>
374
+ # <script>
375
+ # async function generateMCQs() {
376
+ # const subject = document.getElementById('subject').value;
377
+ # const topic = document.getElementById('topic').value.trim();
378
+
379
+ # if (!topic) {
380
+ # alert('⚠️ Please enter a topic!');
381
+ # return;
382
+ # }
383
+
384
+ # const loading = document.getElementById('loading');
385
+ # const result = document.getElementById('result');
386
+ # const btn = document.querySelector('button');
387
+
388
+ # loading.classList.add('show');
389
+ # result.classList.remove('show');
390
+ # btn.disabled = true;
391
+ # btn.textContent = '⏳ Generating...';
392
+
393
+ # try {
394
+ # const response = await fetch('/generate', {
395
+ # method: 'POST',
396
+ # headers: {'Content-Type': 'application/json'},
397
+ # body: JSON.stringify({subject, topic})
398
+ # });
399
+
400
+ # const data = await response.json();
401
+
402
+ # if (data.error) {
403
+ # alert('❌ Error: ' + data.error);
404
+ # return;
405
+ # }
406
+
407
+ # document.getElementById('mcqContent').textContent = data.mcqs;
408
+ # result.classList.add('show');
409
+ # } catch (error) {
410
+ # alert('❌ Error: ' + error.message);
411
+ # } finally {
412
+ # loading.classList.remove('show');
413
+ # btn.disabled = false;
414
+ # btn.textContent = 'πŸš€ Generate 5 MCQs';
415
+ # }
416
+ # }
417
+
418
+ # // Allow Enter key to submit
419
+ # document.getElementById('topic').addEventListener('keypress', function(e) {
420
+ # if (e.key === 'Enter') {
421
+ # generateMCQs();
422
+ # }
423
+ # });
424
+ # </script>
425
+ # </body>
426
+ # </html>
427
+ # """
428
+
429
+ # # ------------------------------
430
+ # # Routes
431
+ # # ------------------------------
432
+ # @app.route("/")
433
+ # def home():
434
+ # return render_template_string(HTML_TEMPLATE)
435
+
436
+ # @app.route("/generate", methods=["POST"])
437
+ # def generate():
438
+ # try:
439
+ # data = request.json
440
+ # subject = data.get("subject", "").lower()
441
+ # topic = data.get("topic", "")
442
+
443
+ # if not topic:
444
+ # return jsonify({"error": "Topic is required"}), 400
445
+
446
+ # if subject not in SUBJECTS:
447
+ # return jsonify({"error": "Invalid subject. Choose biology, chemistry, or physics."}), 400
448
+
449
+ # print(f"\nπŸ” Searching {subject} for topic: {topic}")
450
+
451
+ # # Retrieve context from RAG
452
+ # context = rag_search(topic, subject, k=5)
453
+
454
+ # if not context or len(context.strip()) < 50:
455
+ # return jsonify({"error": f"No relevant content found in {subject} for topic: {topic}"}), 404
456
+
457
+ # print(f"βœ“ Found context ({len(context)} chars)")
458
+
459
+ # # Generate MCQs (now with caching)
460
+ # print("πŸ€– Generating MCQs...")
461
+ # mcqs = generate_mcqs(context, topic, subject)
462
+
463
+ # print("βœ“ MCQs generated successfully")
464
+
465
+ # return jsonify({"mcqs": mcqs, "subject": subject})
466
+
467
+ # except Exception as e:
468
+ # print(f"❌ Error in /generate: {e}")
469
+ # import traceback
470
+ # traceback.print_exc()
471
+ # return jsonify({"error": str(e)}), 500
472
+
473
+ # @app.route("/health")
474
+ # def health():
475
+ # return jsonify({
476
+ # "status": "healthy",
477
+ # "subjects": {
478
+ # "biology": len(SUBJECTS["biology"]["chunks"]),
479
+ # "chemistry": len(SUBJECTS["chemistry"]["chunks"]),
480
+ # "physics": len(SUBJECTS["physics"]["chunks"])
481
+ # },
482
+ # "cache_size": len(MCQ_CACHE)
483
+ # })
484
+
485
+ # # OPTIMIZATION: Add cache stats endpoint
486
+ # @app.route("/cache/stats")
487
+ # def cache_stats():
488
+ # return jsonify({
489
+ # "cached_topics": len(MCQ_CACHE),
490
+ # "max_cache_size": MAX_CACHE_SIZE,
491
+ # "cache_keys": list(MCQ_CACHE.keys())
492
+ # })
493
+
494
+ # # OPTIMIZATION: Add cache clear endpoint (optional)
495
+ # @app.route("/cache/clear", methods=["POST"])
496
+ # def clear_cache():
497
+ # MCQ_CACHE.clear()
498
+ # return jsonify({"status": "Cache cleared"})
499
+
500
+ # # ------------------------------
501
+ # # Run the App
502
+ # # ------------------------------
503
+ # if __name__ == "__main__":
504
+ # port = int(os.environ.get("PORT", 7860))
505
+ # print(f"\nπŸš€ Starting Flask on 0.0.0.0:{port}\n")
506
+ # app.run(host="0.0.0.0", port=port, debug=False)
507
+
508
  import pickle
509
  import faiss
510
  from flask import Flask, request, jsonify, render_template_string
511
  from sentence_transformers import SentenceTransformer
 
512
  from huggingface_hub import hf_hub_download
 
 
 
513
  import hashlib
514
+ import re
515
+ import os
516
+ from groq import Groq
517
 
518
  app = Flask(__name__)
519
 
 
521
  print("Loading models and data...")
522
  print("=" * 50)
523
 
524
+ # ------------------------------
525
+ # Initialize Groq API Client
526
+ # ------------------------------
527
+ # Get your free API key from: https://console.groq.com/keys
528
+ GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "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") # Set this in your environment
529
+ if not GROQ_API_KEY:
530
+ print("⚠️ WARNING: GROQ_API_KEY not set. Get one from https://console.groq.com/keys")
531
+ print("Set it with: export GROQ_API_KEY='your-key-here'")
532
+
533
+ groq_client = Groq(api_key=GROQ_API_KEY)
534
+ print("βœ“ Groq API client initialized")
535
+
536
  # ------------------------------
537
  # Load embedding model (CPU)
538
  # ------------------------------
 
578
  print(f"βœ“ Chemistry: {len(SUBJECTS['chemistry']['chunks'])} chunks loaded")
579
  print(f"βœ“ Physics: {len(SUBJECTS['physics']['chunks'])} chunks loaded")
580
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
581
  print("=" * 50)
582
  print("All models loaded successfully!")
583
  print("=" * 50)
584
 
585
  # ------------------------------
586
+ # Caching for MCQ generation
587
  # ------------------------------
588
  MCQ_CACHE = {}
589
  MAX_CACHE_SIZE = 100
 
595
  def cache_mcq(key, mcqs):
596
  """Cache generated MCQs with size limit"""
597
  if len(MCQ_CACHE) >= MAX_CACHE_SIZE:
 
598
  MCQ_CACHE.pop(next(iter(MCQ_CACHE)))
599
  MCQ_CACHE[key] = mcqs
600
 
601
  # ------------------------------
602
+ # RAG Search in specific subject
603
  # ------------------------------
604
  def rag_search(query, subject, k=5):
605
  if subject not in SUBJECTS:
 
608
  chunks = SUBJECTS[subject]["chunks"]
609
  index = SUBJECTS[subject]["index"]
610
 
 
611
  q_emb = embed_model.encode([query], show_progress_bar=False).astype("float32")
612
  D, I = index.search(q_emb, k)
613
 
 
614
  results = []
615
  for idx in I[0]:
616
  if idx < len(chunks):
 
619
  return "\n\n".join(results)
620
 
621
  # ------------------------------
622
+ # MCQ Generation using Groq API
623
  # ------------------------------
624
  def generate_mcqs(context, topic, subject):
625
+ # Check cache first
626
  context_hash = hashlib.md5(context.encode()).hexdigest()[:8]
627
  cache_key = get_cache_key(topic, subject, context_hash)
628
 
 
630
  print("βœ“ Using cached MCQs")
631
  return MCQ_CACHE[cache_key]
632
 
633
+ print("πŸ€– Generating MCQs using Groq API...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
634
 
635
+ # Prompt for MCQ generation
636
+ prompt = f"""You are a Class-12 {subject.title()} teacher creating MCQs for students.
637
+
638
+ Topic: "{topic}"
639
+
640
+ Reference material from textbook:
641
+ {context[:1500]}
642
+
643
+ TASK: Generate exactly 5 multiple-choice questions based on the reference material above.
644
+
645
+ FORMAT (follow this EXACTLY):
646
+ Q1. [Clear question based on the material]
647
+ A) [First option]
648
+ B) [Second option]
649
+ C) [Third option]
650
+ D) [Fourth option]
651
+ Answer: [A/B/C/D] - [Brief explanation why this is correct based on the material]
652
+
653
+ REQUIREMENTS:
654
+ - Questions must be answerable from the reference material
655
+ - All 4 options should be plausible
656
+ - The correct answer must be clearly supported by the material
657
+ - Keep explanations brief (1-2 sentences)
658
+ - Generate all 5 questions in the format above
659
+
660
+ Generate 5 MCQs now:"""
661
 
662
+ try:
663
+ # Call Groq API
664
+ chat_completion = groq_client.chat.completions.create(
665
+ messages=[
666
+ {
667
+ "role": "system",
668
+ "content": "You are an expert Class-12 teacher who creates high-quality multiple-choice questions from textbook content. You always follow the exact format specified."
669
+ },
670
+ {
671
+ "role": "user",
672
+ "content": prompt
673
+ }
674
+ ],
675
+ model="llama-3.3-70b-versatile", # Fast and accurate
676
+ temperature=0.3,
677
+ max_tokens=1500,
678
+ top_p=0.9
679
  )
680
+
681
+ result = chat_completion.choices[0].message.content.strip()
682
+
683
+ # Clean the output
684
+ result = clean_mcq_output(result)
685
+
686
+ # Cache the result
687
+ cache_mcq(cache_key, result)
688
+
689
+ print("βœ“ MCQs generated successfully")
690
+ return result
691
+
692
+ except Exception as e:
693
+ print(f"❌ Groq API Error: {e}")
694
+ return f"Error generating MCQs: {str(e)}\n\nPlease make sure GROQ_API_KEY is set correctly."
695
+
696
+ def clean_mcq_output(text):
697
+ """Clean and format the MCQ output"""
698
+ lines = text.split('\n')
699
+ cleaned_lines = []
700
 
701
+ for line in lines:
702
+ line = line.strip()
703
+
704
+ # Keep question lines, options, and answers
705
+ if (re.match(r'^Q\d+\.', line) or
706
+ line.startswith(('A)', 'B)', 'C)', 'D)', 'Answer:', 'Correct Answer:')) or
707
+ not line):
708
+
709
+ # Normalize answer format
710
+ if line.startswith('Correct Answer:'):
711
+ line = line.replace('Correct Answer:', 'Answer:')
712
+
713
+ cleaned_lines.append(line)
714
 
715
+ return '\n'.join(cleaned_lines)
 
 
 
 
 
 
716
 
717
  # ------------------------------
718
+ # HTML UI
719
  # ------------------------------
720
  HTML_TEMPLATE = """
721
  <!DOCTYPE html>
 
746
  }
747
  .header h1 { font-size: 2.5em; margin-bottom: 10px; }
748
  .content { padding: 40px; }
749
+ .form-group { margin-bottom: 25px; }
 
 
750
  label {
751
  display: block;
752
  font-weight: 600;
 
840
  .bio { background: #d4edda; color: #155724; }
841
  .chem { background: #d1ecf1; color: #0c5460; }
842
  .phy { background: #f8d7da; color: #721c24; }
843
+ .api-badge {
844
+ background: #17a2b8;
845
  color: white;
846
  padding: 5px 12px;
847
  border-radius: 15px;
 
854
  <div class="container">
855
  <div class="header">
856
  <h1>πŸŽ“ Class 12 PCB MCQ Generator</h1>
857
+ <p style="font-size: 1.1em; margin-bottom: 15px;">
858
+ Generate practice MCQs from your textbooks
859
+ <span class="api-badge">⚑ Powered by Llama 3.3 70B</span>
860
+ </p>
861
  <div>
862
  <span class="subject-tag bio">Biology</span>
863
  <span class="subject-tag chem">Chemistry</span>
 
884
 
885
  <div class="loading" id="loading">
886
  <div class="spinner"></div>
887
+ <p style="color: #666; font-size: 16px;">Generating high-quality MCQs...</p>
888
+ <p style="color: #999; font-size: 13px; margin-top: 10px;">⚑ Using Llama 3.3 70B via Groq API (5-10 seconds)</p>
889
  </div>
890
 
891
  <div class="result" id="result">
892
  <h3>πŸ“ Generated MCQs:</h3>
893
+ <div style="background: #d4edda; padding: 12px; border-radius: 6px; margin-bottom: 15px; color: #155724; font-size: 14px;">
894
+ βœ“ <strong>High Accuracy:</strong> Generated by Llama 3.3 70B - One of the most capable models available!
895
  </div>
896
  <div class="mcq-content" id="mcqContent"></div>
897
  </div>
 
941
  }
942
  }
943
 
 
944
  document.getElementById('topic').addEventListener('keypress', function(e) {
945
  if (e.key === 'Enter') {
946
  generateMCQs();
 
969
  return jsonify({"error": "Topic is required"}), 400
970
 
971
  if subject not in SUBJECTS:
972
+ return jsonify({"error": "Invalid subject"}), 400
973
 
974
  print(f"\nπŸ” Searching {subject} for topic: {topic}")
975
 
 
976
  context = rag_search(topic, subject, k=5)
977
 
978
  if not context or len(context.strip()) < 50:
979
+ return jsonify({"error": f"No relevant content found for topic: {topic}"}), 404
980
 
981
  print(f"βœ“ Found context ({len(context)} chars)")
982
 
 
 
983
  mcqs = generate_mcqs(context, topic, subject)
984
 
 
 
985
  return jsonify({"mcqs": mcqs, "subject": subject})
986
 
987
  except Exception as e:
988
+ print(f"❌ Error: {e}")
989
  import traceback
990
  traceback.print_exc()
991
  return jsonify({"error": str(e)}), 500
 
994
  def health():
995
  return jsonify({
996
  "status": "healthy",
997
+ "model": "llama-3.3-70b-versatile (Groq API)",
 
 
 
 
998
  "cache_size": len(MCQ_CACHE)
999
  })
1000
 
 
 
 
 
 
 
 
 
 
 
1001
  @app.route("/cache/clear", methods=["POST"])
1002
  def clear_cache():
1003
  MCQ_CACHE.clear()
 
1011
  print(f"\nπŸš€ Starting Flask on 0.0.0.0:{port}\n")
1012
  app.run(host="0.0.0.0", port=port, debug=False)
1013