Redfire-1234 commited on
Commit
951e5e2
Β·
verified Β·
1 Parent(s): af2a77d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +860 -851
app.py CHANGED
@@ -1,771 +1,276 @@
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
-
509
- import pickle
510
- import faiss
511
- from flask import Flask, request, jsonify, render_template_string
512
- from sentence_transformers import SentenceTransformer
513
- from huggingface_hub import hf_hub_download
514
- import hashlib
515
- import re
516
- import os
517
- from groq import Groq
518
-
519
- app = Flask(__name__)
520
-
521
- print("=" * 50)
522
- print("Loading models and data...")
523
- print("=" * 50)
524
-
525
- # ------------------------------
526
- # Initialize Groq API Client
527
- # ------------------------------
528
- import sys
529
-
530
- # Get API key from Hugging Face Secrets or environment variable
531
- GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "").strip()
532
-
533
- print("=" * 50)
534
- print("Checking Groq API Key...")
535
- print("=" * 50)
536
-
537
- if not GROQ_API_KEY:
538
- print("❌ ERROR: GROQ_API_KEY not found in environment!")
539
- print("")
540
- print("Please add it in:")
541
- print("1. Hugging Face Space Settings β†’ Repository secrets")
542
- print(" Name: GROQ_API_KEY")
543
- print(" Value: Your Groq API key")
544
- print("")
545
- print("2. Get your free key from: https://console.groq.com/keys")
546
- print("=" * 50)
547
- groq_client = None
548
- else:
549
- print(f"βœ“ Groq API key found!")
550
- print(f" Key length: {len(GROQ_API_KEY)} characters")
551
- print(f" Starts with: {GROQ_API_KEY[:15]}...")
552
- print(f" Ends with: ...{GROQ_API_KEY[-10:]}")
553
-
554
- try:
555
- groq_client = Groq(api_key=GROQ_API_KEY)
556
-
557
- # Test the connection with a simple request
558
- print("Testing Groq API connection...")
559
- test_response = groq_client.chat.completions.create(
560
- messages=[{"role": "user", "content": "Hi"}],
561
- model="llama-3.3-70b-versatile",
562
- max_tokens=10
563
- )
564
- print("βœ“ Groq API client initialized and tested successfully!")
565
- print("=" * 50)
566
-
567
- except Exception as e:
568
- print(f"❌ Failed to initialize Groq client!")
569
- print(f"Error type: {type(e).__name__}")
570
- print(f"Error message: {str(e)}")
571
- print("=" * 50)
572
- print("")
573
- print("Common causes:")
574
- print("1. Invalid API key - Get a new one from https://console.groq.com/keys")
575
- print("2. API key has extra spaces - Check for whitespace")
576
- print("3. Network issue - Check internet connection")
577
- print("=" * 50)
578
- groq_client = None
579
- import traceback
580
- traceback.print_exc()
581
-
582
- # ------------------------------
583
- # Load embedding model (CPU)
584
- # ------------------------------
585
- embed_model = SentenceTransformer("all-MiniLM-L6-v2")
586
- print("βœ“ Embedding model loaded")
587
-
588
- # ------------------------------
589
- # Download files from Hugging Face
590
- # ------------------------------
591
- REPO_ID = "Redfire-1234/pcb_tutor"
592
-
593
- print("Downloading subject files from Hugging Face...")
594
-
595
- # Download Biology files
596
- bio_chunks_path = hf_hub_download(repo_id=REPO_ID, filename="bio_chunks.pkl", repo_type="model")
597
- faiss_bio_path = hf_hub_download(repo_id=REPO_ID, filename="faiss_bio.bin", repo_type="model")
598
-
599
- # Download Chemistry files
600
- chem_chunks_path = hf_hub_download(repo_id=REPO_ID, filename="chem_chunks.pkl", repo_type="model")
601
- faiss_chem_path = hf_hub_download(repo_id=REPO_ID, filename="faiss_chem.bin", repo_type="model")
602
-
603
- # Download Physics files
604
- phy_chunks_path = hf_hub_download(repo_id=REPO_ID, filename="phy_chunks.pkl", repo_type="model")
605
- faiss_phy_path = hf_hub_download(repo_id=REPO_ID, filename="faiss_phy.bin", repo_type="model")
606
-
607
- # Load all subjects into memory
608
- SUBJECTS = {
609
- "biology": {
610
- "chunks": pickle.load(open(bio_chunks_path, "rb")),
611
- "index": faiss.read_index(faiss_bio_path)
612
- },
613
- "chemistry": {
614
- "chunks": pickle.load(open(chem_chunks_path, "rb")),
615
- "index": faiss.read_index(faiss_chem_path)
616
- },
617
- "physics": {
618
- "chunks": pickle.load(open(phy_chunks_path, "rb")),
619
- "index": faiss.read_index(faiss_phy_path)
620
- }
621
- }
622
-
623
- print(f"βœ“ Biology: {len(SUBJECTS['biology']['chunks'])} chunks loaded")
624
- print(f"βœ“ Chemistry: {len(SUBJECTS['chemistry']['chunks'])} chunks loaded")
625
- print(f"βœ“ Physics: {len(SUBJECTS['physics']['chunks'])} chunks loaded")
626
-
627
- print("=" * 50)
628
- print("All models loaded successfully!")
629
- print("=" * 50)
630
-
631
- # ------------------------------
632
- # Caching for MCQ generation
633
- # ------------------------------
634
- MCQ_CACHE = {}
635
- MAX_CACHE_SIZE = 100
636
-
637
- def get_cache_key(topic, subject, context_hash):
638
- """Generate a unique cache key"""
639
- return f"{subject}:{topic}:{context_hash}"
640
-
641
- def cache_mcq(key, mcqs):
642
- """Cache generated MCQs with size limit"""
643
- if len(MCQ_CACHE) >= MAX_CACHE_SIZE:
644
- MCQ_CACHE.pop(next(iter(MCQ_CACHE)))
645
- MCQ_CACHE[key] = mcqs
646
-
647
- # ------------------------------
648
- # RAG Search in specific subject
649
- # ------------------------------
650
- def rag_search(query, subject, k=5):
651
- if subject not in SUBJECTS:
652
- return None
653
-
654
- chunks = SUBJECTS[subject]["chunks"]
655
- index = SUBJECTS[subject]["index"]
656
-
657
- q_emb = embed_model.encode([query], show_progress_bar=False).astype("float32")
658
- D, I = index.search(q_emb, k)
659
-
660
- results = []
661
- for idx in I[0]:
662
- if idx < len(chunks):
663
- results.append(chunks[idx])
664
 
665
- return "\n\n".join(results)
666
 
667
  # ------------------------------
668
- # MCQ Generation using Groq API
669
- # ------------------------------
670
- def generate_mcqs(context, topic, subject):
671
- # Check if Groq client is available
672
- if not groq_client:
673
- return "Error: Groq API client not initialized. Please check your GROQ_API_KEY in Space settings."
674
-
675
- # Check cache first
676
- context_hash = hashlib.md5(context.encode()).hexdigest()[:8]
677
- cache_key = get_cache_key(topic, subject, context_hash)
678
-
679
- if cache_key in MCQ_CACHE:
680
- print("βœ“ Using cached MCQs")
681
- return MCQ_CACHE[cache_key]
682
-
683
- print("πŸ€– Generating MCQs using Groq API...")
684
-
685
- # Prompt for MCQ generation
686
- prompt = f"""You are a Class-12 {subject.title()} teacher creating MCQs for students.
687
-
688
- Topic: "{topic}"
689
-
690
- Reference material from textbook:
691
- {context[:1500]}
692
-
693
- TASK: Generate exactly 5 multiple-choice questions based on the reference material above.
694
-
695
- FORMAT (follow this EXACTLY):
696
- Q1. [Clear question based on the material]
697
- A) [First option]
698
- B) [Second option]
699
- C) [Third option]
700
- D) [Fourth option]
701
- Answer: [A/B/C/D] - [Brief explanation why this is correct based on the material]
702
-
703
- REQUIREMENTS:
704
- - Questions must be answerable from the reference material
705
- - All 4 options should be plausible
706
- - The correct answer must be clearly supported by the material
707
- - Keep explanations brief (1-2 sentences)
708
- - Generate all 5 questions in the format above
709
-
710
- Generate 5 MCQs now:"""
711
-
712
- try:
713
- # Call Groq API
714
- chat_completion = groq_client.chat.completions.create(
715
- messages=[
716
- {
717
- "role": "system",
718
- "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."
719
- },
720
- {
721
- "role": "user",
722
- "content": prompt
723
- }
724
- ],
725
- model="llama-3.3-70b-versatile", # Fast and accurate
726
- temperature=0.3,
727
- max_tokens=1500,
728
- top_p=0.9
729
- )
730
-
731
- result = chat_completion.choices[0].message.content.strip()
732
-
733
- # Clean the output
734
- result = clean_mcq_output(result)
735
-
736
- # Cache the result
737
- cache_mcq(cache_key, result)
738
-
739
- print("βœ“ MCQs generated successfully")
740
- return result
741
-
742
- except Exception as e:
743
- print(f"❌ Groq API Error: {e}")
744
- return f"Error generating MCQs: {str(e)}\n\nPlease make sure GROQ_API_KEY is set correctly."
745
-
746
- def clean_mcq_output(text):
747
- """Clean and format the MCQ output"""
748
- lines = text.split('\n')
749
- cleaned_lines = []
750
-
751
- for line in lines:
752
- line = line.strip()
753
-
754
- # Keep question lines, options, and answers
755
- if (re.match(r'^Q\d+\.', line) or
756
- line.startswith(('A)', 'B)', 'C)', 'D)', 'Answer:', 'Correct Answer:')) or
757
- not line):
758
-
759
- # Normalize answer format
760
- if line.startswith('Correct Answer:'):
761
- line = line.replace('Correct Answer:', 'Answer:')
762
-
763
- cleaned_lines.append(line)
764
-
765
- return '\n'.join(cleaned_lines)
766
-
767
- # ------------------------------
768
- # HTML UI
769
  # ------------------------------
770
  HTML_TEMPLATE = """
771
  <!DOCTYPE html>
@@ -906,7 +411,7 @@ HTML_TEMPLATE = """
906
  <h1>πŸŽ“ Class 12 PCB MCQ Generator</h1>
907
  <p style="font-size: 1.1em; margin-bottom: 15px;">
908
  Generate practice MCQs from your textbooks
909
- <span class="api-badge">⚑ Powered by Llama 3.3 70B</span>
910
  </p>
911
  <div>
912
  <span class="subject-tag bio">Biology</span>
@@ -915,148 +420,652 @@ HTML_TEMPLATE = """
915
  </div>
916
  </div>
917
 
918
- <div class="content">
919
- <div class="form-group">
920
- <label for="subject">πŸ“š Select Subject</label>
921
- <select id="subject">
922
- <option value="biology">Biology</option>
923
- <option value="chemistry">Chemistry</option>
924
- <option value="physics">Physics</option>
925
- </select>
926
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
927
 
928
- <div class="form-group">
929
- <label for="topic">✏️ Enter Topic</label>
930
- <input type="text" id="topic" placeholder="e.g., Mitochondria, Chemical Bonding, Newton's Laws">
931
- </div>
932
 
933
- <button onclick="generateMCQs()">πŸš€ Generate 5 MCQs</button>
934
 
935
- <div class="loading" id="loading">
936
- <div class="spinner"></div>
937
- <p style="color: #666; font-size: 16px;">Generating high-quality MCQs...</p>
938
- <p style="color: #999; font-size: 13px; margin-top: 10px;">⚑ Using Llama 3.3 70B via Groq API (5-10 seconds)</p>
939
- </div>
940
 
941
- <div class="result" id="result">
942
- <h3>πŸ“ Generated MCQs:</h3>
943
- <div style="background: #d4edda; padding: 12px; border-radius: 6px; margin-bottom: 15px; color: #155724; font-size: 14px;">
944
- βœ“ <strong>High Accuracy:</strong> Generated by Llama 3.3 70B - One of the most capable models available!
945
- </div>
946
- <div class="mcq-content" id="mcqContent"></div>
947
- </div>
948
- </div>
949
- </div>
950
- <script>
951
- async function generateMCQs() {
952
- const subject = document.getElementById('subject').value;
953
- const topic = document.getElementById('topic').value.trim();
954
 
955
- if (!topic) {
956
- alert('⚠️ Please enter a topic!');
957
- return;
958
- }
959
 
960
- const loading = document.getElementById('loading');
961
- const result = document.getElementById('result');
962
- const btn = document.querySelector('button');
963
 
964
- loading.classList.add('show');
965
- result.classList.remove('show');
966
- btn.disabled = true;
967
- btn.textContent = '⏳ Generating...';
968
 
969
- try {
970
- const response = await fetch('/generate', {
971
- method: 'POST',
972
- headers: {'Content-Type': 'application/json'},
973
- body: JSON.stringify({subject, topic})
974
- });
975
 
976
- const data = await response.json();
977
 
978
- if (data.error) {
979
- alert('❌ Error: ' + data.error);
980
- return;
981
- }
982
 
983
- document.getElementById('mcqContent').textContent = data.mcqs;
984
- result.classList.add('show');
985
- } catch (error) {
986
- alert('❌ Error: ' + error.message);
987
- } finally {
988
- loading.classList.remove('show');
989
- btn.disabled = false;
990
- btn.textContent = 'πŸš€ Generate 5 MCQs';
991
- }
992
- }
993
 
994
- document.getElementById('topic').addEventListener('keypress', function(e) {
995
- if (e.key === 'Enter') {
996
- generateMCQs();
997
- }
998
- });
999
- </script>
1000
- </body>
1001
- </html>
1002
- """
 
1003
 
1004
- # ------------------------------
1005
- # Routes
1006
- # ------------------------------
1007
- @app.route("/")
1008
- def home():
1009
- return render_template_string(HTML_TEMPLATE)
1010
 
1011
- @app.route("/generate", methods=["POST"])
1012
- def generate():
1013
- try:
1014
- data = request.json
1015
- subject = data.get("subject", "").lower()
1016
- topic = data.get("topic", "")
1017
 
1018
- if not topic:
1019
- return jsonify({"error": "Topic is required"}), 400
1020
 
1021
- if subject not in SUBJECTS:
1022
- return jsonify({"error": "Invalid subject"}), 400
1023
 
1024
- print(f"\nπŸ” Searching {subject} for topic: {topic}")
1025
 
1026
- context = rag_search(topic, subject, k=5)
 
1027
 
1028
- if not context or len(context.strip()) < 50:
1029
- return jsonify({"error": f"No relevant content found for topic: {topic}"}), 404
1030
 
1031
- print(f"βœ“ Found context ({len(context)} chars)")
1032
 
1033
- mcqs = generate_mcqs(context, topic, subject)
 
 
1034
 
1035
- return jsonify({"mcqs": mcqs, "subject": subject})
 
 
1036
 
1037
- except Exception as e:
1038
- print(f"❌ Error: {e}")
1039
- import traceback
1040
- traceback.print_exc()
1041
- return jsonify({"error": str(e)}), 500
1042
 
1043
- @app.route("/health")
1044
- def health():
1045
- return jsonify({
1046
- "status": "healthy",
1047
- "model": "llama-3.3-70b-versatile (Groq API)",
1048
- "cache_size": len(MCQ_CACHE)
1049
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1050
 
1051
- @app.route("/cache/clear", methods=["POST"])
1052
- def clear_cache():
1053
- MCQ_CACHE.clear()
1054
- return jsonify({"status": "Cache cleared"})
1055
 
1056
- # ------------------------------
1057
- # Run the App
1058
- # ------------------------------
1059
- if __name__ == "__main__":
1060
- port = int(os.environ.get("PORT", 7860))
1061
- print(f"\nπŸš€ Starting Flask on 0.0.0.0:{port}\n")
1062
- app.run(host="0.0.0.0", port=port, debug=False)
 
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
+ # Initialize Groq API Client
29
+ # ------------------------------
30
+ print("\nStep 1: Checking Groq API Key...")
31
+ print("-" * 50)
32
 
33
+ GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "").strip()
 
 
34
 
35
+ if not GROQ_API_KEY:
36
+ print("❌ GROQ_API_KEY not found!")
37
+ print("\nTo fix this:")
38
+ print("1. Go to: https://console.groq.com/keys")
39
+ print("2. Create a free API key")
40
+ print("3. In HuggingFace Space Settings β†’ Repository secrets")
41
+ print(" Add: Name=GROQ_API_KEY, Value=<your-key>")
42
+ print("4. Restart your Space")
43
+ groq_client = None
44
+ else:
45
+ print(f"βœ“ GROQ_API_KEY found ({len(GROQ_API_KEY)} chars)")
46
+ print(f" First 20 chars: {GROQ_API_KEY[:20]}...")
47
+
48
+ try:
49
+ groq_client = Groq(api_key=GROQ_API_KEY)
50
+
51
+ # Test the API
52
+ print(" Testing API connection...")
53
+ test = groq_client.chat.completions.create(
54
+ messages=[{"role": "user", "content": "test"}],
55
+ model="llama-3.3-70b-versatile",
56
+ max_tokens=5
57
+ )
58
+ print("βœ“ Groq API working!")
59
+
60
+ except Exception as e:
61
+ print(f"❌ Groq API initialization failed:")
62
+ print(f" Error: {str(e)}")
63
+ groq_client = None
64
 
65
+ print("-" * 50)
 
 
66
 
67
+ # ------------------------------
68
+ # Load embedding model (CPU)
69
+ # ------------------------------
70
+ print("\nStep 2: Loading embedding model...")
71
+ embed_model = SentenceTransformer("all-MiniLM-L6-v2")
72
+ print("βœ“ Embedding model loaded")
 
 
 
 
 
 
 
 
 
73
 
74
+ # ------------------------------
75
+ # Download files from Hugging Face
76
+ # ------------------------------
77
+ REPO_ID = "Redfire-1234/pcb_tutor"
78
 
79
+ print("\nStep 3: Downloading subject files...")
80
+ print("-" * 50)
 
 
 
 
 
81
 
82
+ try:
83
+ bio_chunks_path = hf_hub_download(repo_id=REPO_ID, filename="bio_chunks.pkl", repo_type="model")
84
+ faiss_bio_path = hf_hub_download(repo_id=REPO_ID, filename="faiss_bio.bin", repo_type="model")
85
+
86
+ chem_chunks_path = hf_hub_download(repo_id=REPO_ID, filename="chem_chunks.pkl", repo_type="model")
87
+ faiss_chem_path = hf_hub_download(repo_id=REPO_ID, filename="faiss_chem.bin", repo_type="model")
88
+
89
+ phy_chunks_path = hf_hub_download(repo_id=REPO_ID, filename="phy_chunks.pkl", repo_type="model")
90
+ faiss_phy_path = hf_hub_download(repo_id=REPO_ID, filename="faiss_phy.bin", repo_type="model")
91
+
92
+ print("βœ“ All files downloaded")
93
+ except Exception as e:
94
+ print(f"❌ Error downloading files: {e}")
95
+ sys.exit(1)
96
 
97
+ # Load all subjects into memory
98
+ print("\nStep 4: Loading subject data into memory...")
99
+ SUBJECTS = {
100
+ "biology": {
101
+ "chunks": pickle.load(open(bio_chunks_path, "rb")),
102
+ "index": faiss.read_index(faiss_bio_path)
103
+ },
104
+ "chemistry": {
105
+ "chunks": pickle.load(open(chem_chunks_path, "rb")),
106
+ "index": faiss.read_index(faiss_chem_path)
107
+ },
108
+ "physics": {
109
+ "chunks": pickle.load(open(phy_chunks_path, "rb")),
110
+ "index": faiss.read_index(faiss_phy_path)
111
+ }
112
+ }
113
 
114
+ print(f"βœ“ Biology: {len(SUBJECTS['biology']['chunks'])} chunks")
115
+ print(f"βœ“ Chemistry: {len(SUBJECTS['chemistry']['chunks'])} chunks")
116
+ print(f"βœ“ Physics: {len(SUBJECTS['physics']['chunks'])} chunks")
117
 
118
+ print("\n" + "=" * 50)
119
+ print("βœ“ ALL SYSTEMS READY!")
120
+ print("=" * 50 + "\n")
121
 
122
+ # ------------------------------
123
+ # Caching
124
+ # ------------------------------
125
+ MCQ_CACHE = {}
126
+ MAX_CACHE_SIZE = 100
127
 
128
+ def get_cache_key(topic, subject, context_hash):
129
+ return f"{subject}:{topic}:{context_hash}"
 
130
 
131
+ def cache_mcq(key, mcqs):
132
+ if len(MCQ_CACHE) >= MAX_CACHE_SIZE:
133
+ MCQ_CACHE.pop(next(iter(MCQ_CACHE)))
134
+ MCQ_CACHE[key] = mcqs
 
 
135
 
136
+ # ------------------------------
137
+ # RAG Search
138
+ # ------------------------------
139
+ def rag_search(query, subject, k=5):
140
+ if subject not in SUBJECTS:
141
+ return None
142
 
143
+ chunks = SUBJECTS[subject]["chunks"]
144
+ index = SUBJECTS[subject]["index"]
145
 
146
+ q_emb = embed_model.encode([query], show_progress_bar=False).astype("float32")
147
+ D, I = index.search(q_emb, k)
 
148
 
149
+ results = []
150
+ for idx in I[0]:
151
+ if idx < len(chunks):
152
+ results.append(chunks[idx])
 
153
 
154
+ return "\n\n".join(results)
155
 
156
+ # ------------------------------
157
+ # MCQ Generation
158
+ # ------------------------------
159
+ def generate_mcqs(context, topic, subject):
160
+ # Check if Groq is available
161
+ if not groq_client:
162
+ error_msg = """ERROR: Groq API not initialized!
 
 
 
 
 
 
 
 
 
 
163
 
164
+ Please check:
165
+ 1. GROQ_API_KEY is set in Space Settings β†’ Repository secrets
166
+ 2. API key is valid (get one from https://console.groq.com/keys)
167
+ 3. Space has been restarted after adding the key
 
 
 
168
 
169
+ Current status: API key not found or invalid."""
170
+ return error_msg
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
 
172
+ # Check cache
173
+ context_hash = hashlib.md5(context.encode()).hexdigest()[:8]
174
+ cache_key = get_cache_key(topic, subject, context_hash)
175
 
176
+ if cache_key in MCQ_CACHE:
177
+ print("βœ“ Using cached MCQs")
178
+ return MCQ_CACHE[cache_key]
179
 
180
+ print(f"πŸ€– Generating MCQs for {subject} - {topic}")
 
181
 
182
+ prompt = f"""You are a Class-12 {subject.title()} teacher creating MCQs.
183
 
184
+ Topic: "{topic}"
 
 
 
 
185
 
186
+ Reference material from textbook:
187
+ {context[:1500]}
188
+
189
+ Generate exactly 5 multiple-choice questions based on the reference material.
190
+
191
+ FORMAT (follow EXACTLY):
192
+ Q1. [Question based on material]
193
+ A) [Option 1]
194
+ B) [Option 2]
195
+ C) [Option 3]
196
+ D) [Option 4]
197
+ Answer: [A/B/C/D] - [Brief explanation]
198
+
199
+ Q2. [Question based on material]
200
+ A) [Option 1]
201
+ B) [Option 2]
202
+ C) [Option 3]
203
+ D) [Option 4]
204
+ Answer: [A/B/C/D] - [Brief explanation]
205
+
206
+ Continue for Q3, Q4, Q5.
207
+
208
+ REQUIREMENTS:
209
+ - All questions must be answerable from the reference material
210
+ - All 4 options should be plausible
211
+ - Correct answer must be clearly supported by material
212
+ - Keep explanations brief (1-2 sentences)
213
+
214
+ Generate 5 MCQs now:"""
215
+
216
+ try:
217
+ chat_completion = groq_client.chat.completions.create(
218
+ messages=[
219
+ {
220
+ "role": "system",
221
+ "content": "You are an expert Class-12 teacher who creates high-quality MCQs from textbook content. You always follow the exact format specified."
222
+ },
223
+ {
224
+ "role": "user",
225
+ "content": prompt
226
+ }
227
+ ],
228
+ model="llama-3.3-70b-versatile",
229
+ temperature=0.3,
230
+ max_tokens=1500,
231
+ top_p=0.9
232
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
 
234
+ result = chat_completion.choices[0].message.content.strip()
235
+ result = clean_mcq_output(result)
236
+
237
+ cache_mcq(cache_key, result)
238
+
239
+ print("βœ“ MCQs generated successfully")
240
+ return result
241
+
242
+ except Exception as e:
243
+ error_msg = f"""Error calling Groq API: {str(e)}
244
+
245
+ Possible causes:
246
+ 1. Rate limit exceeded (wait a moment)
247
+ 2. Invalid API key
248
+ 3. Network issue
249
+
250
+ Please try again in a few seconds."""
251
+ print(f"❌ Groq API Error: {e}")
252
+ return error_msg
253
+
254
+ def clean_mcq_output(text):
255
+ lines = text.split('\n')
256
+ cleaned_lines = []
257
+
258
+ for line in lines:
259
+ line = line.strip()
260
+
261
+ if (re.match(r'^Q\d+\.', line) or
262
+ line.startswith(('A)', 'B)', 'C)', 'D)', 'Answer:', 'Correct Answer:')) or
263
+ not line):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
 
265
+ if line.startswith('Correct Answer:'):
266
+ line = line.replace('Correct Answer:', 'Answer:')
 
 
267
 
268
+ cleaned_lines.append(line)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
 
270
+ return '\n'.join(cleaned_lines)
271
 
272
  # ------------------------------
273
+ # HTML UI
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  # ------------------------------
275
  HTML_TEMPLATE = """
276
  <!DOCTYPE html>
 
411
  <h1>πŸŽ“ Class 12 PCB MCQ Generator</h1>
412
  <p style="font-size: 1.1em; margin-bottom: 15px;">
413
  Generate practice MCQs from your textbooks
414
+ <span class="api-badge">⚑ Llama 3.3 70B</span>
415
  </p>
416
  <div>
417
  <span class="subject-tag bio">Biology</span>
 
420
  </div>
421
  </div>
422
 
423
+ <div class="content">
424
+ <div class="form-group">
425
+ <label for="subject">πŸ“š Select Subject</label>
426
+ <select id="subject">
427
+ <option value="biology">Biology</option>
428
+ <option value="chemistry">Chemistry</option>
429
+ <option value="physics">Physics</option>
430
+ </select>
431
+ </div>
432
+
433
+ <div class="form-group">
434
+ <label for="topic">✏️ Enter Topic</label>
435
+ <input type="text" id="topic" placeholder="e.g., Mitochondria, Chemical Bonding, Newton's Laws">
436
+ </div>
437
+
438
+ <button onclick="generateMCQs()">πŸš€ Generate 5 MCQs</button>
439
+
440
+ <div class="loading" id="loading">
441
+ <div class="spinner"></div>
442
+ <p style="color: #666; font-size: 16px;">Generating MCQs with AI...</p>
443
+ <p style="color: #999; font-size: 13px; margin-top: 10px;">⚑ Usually takes 5-10 seconds</p>
444
+ </div>
445
+
446
+ <div class="result" id="result">
447
+ <h3>πŸ“ Generated MCQs:</h3>
448
+ <div style="background: #d4edda; padding: 12px; border-radius: 6px; margin-bottom: 15px; color: #155724; font-size: 14px;">
449
+ βœ“ <strong>High Quality:</strong> Generated by Llama 3.3 70B via Groq API
450
+ </div>
451
+ <div class="mcq-content" id="mcqContent"></div>
452
+ </div>
453
+ </div>
454
+ </div>
455
+ <script>
456
+ async function generateMCQs() {
457
+ const subject = document.getElementById('subject').value;
458
+ const topic = document.getElementById('topic').value.trim();
459
+
460
+ if (!topic) {
461
+ alert('⚠️ Please enter a topic!');
462
+ return;
463
+ }
464
+
465
+ const loading = document.getElementById('loading');
466
+ const result = document.getElementById('result');
467
+ const btn = document.querySelector('button');
468
+
469
+ loading.classList.add('show');
470
+ result.classList.remove('show');
471
+ btn.disabled = true;
472
+ btn.textContent = '⏳ Generating...';
473
+
474
+ try {
475
+ const response = await fetch('/generate', {
476
+ method: 'POST',
477
+ headers: {'Content-Type': 'application/json'},
478
+ body: JSON.stringify({subject, topic})
479
+ });
480
+
481
+ const data = await response.json();
482
+
483
+ if (data.error) {
484
+ alert('❌ Error: ' + data.error);
485
+ return;
486
+ }
487
+
488
+ document.getElementById('mcqContent').textContent = data.mcqs;
489
+ result.classList.add('show');
490
+ } catch (error) {
491
+ alert('❌ Error: ' + error.message);
492
+ } finally {
493
+ loading.classList.remove('show');
494
+ btn.disabled = false;
495
+ btn.textContent = 'πŸš€ Generate 5 MCQs';
496
+ }
497
+ }
498
+
499
+ document.getElementById('topic').addEventListener('keypress', function(e) {
500
+ if (e.key === 'Enter') {
501
+ generateMCQs();
502
+ }
503
+ });
504
+ </script>
505
+ </body>
506
+ </html>
507
+ """
508
+
509
+ # ------------------------------
510
+ # Routes
511
+ # ------------------------------
512
+ @app.route("/")
513
+ def home():
514
+ return render_template_string(HTML_TEMPLATE)
515
+
516
+ @app.route("/generate", methods=["POST"])
517
+ def generate():
518
+ try:
519
+ data = request.json
520
+ subject = data.get("subject", "").lower()
521
+ topic = data.get("topic", "")
522
+
523
+ if not topic:
524
+ return jsonify({"error": "Topic is required"}), 400
525
+
526
+ if subject not in SUBJECTS:
527
+ return jsonify({"error": "Invalid subject"}), 400
528
+
529
+ print(f"\nπŸ” Searching {subject} for: {topic}")
530
+
531
+ context = rag_search(topic, subject, k=5)
532
+
533
+ if not context or len(context.strip()) < 50:
534
+ return jsonify({"error": f"No content found for: {topic}"}), 404
535
+
536
+ print(f"βœ“ Context found ({len(context)} chars)")
537
+
538
+ mcqs = generate_mcqs(context, topic, subject)
539
+
540
+ return jsonify({"mcqs": mcqs, "subject": subject})
541
+
542
+ except Exception as e:
543
+ print(f"❌ Error: {e}")
544
+ import traceback
545
+ traceback.print_exc()
546
+ return jsonify({"error": str(e)}), 500
547
+
548
+ @app.route("/health")
549
+ def health():
550
+ return jsonify({
551
+ "status": "healthy",
552
+ "groq_available": groq_client is not None,
553
+ "cache_size": len(MCQ_CACHE)
554
+ })
555
+
556
+ # ------------------------------
557
+ # Run
558
+ # ------------------------------
559
+ if __name__ == "__main__":
560
+ port = int(os.environ.get("PORT", 7860))
561
+ print(f"\nπŸš€ Starting server on port {port}...\n")
562
+ app.run(host="0.0.0.0", port=port, debug=False)
563
+
564
+ # import pickle
565
+ # import faiss
566
+ # from flask import Flask, request, jsonify, render_template_string
567
+ # from sentence_transformers import SentenceTransformer
568
+ # from transformers import AutoTokenizer, AutoModelForCausalLM
569
+ # from huggingface_hub import hf_hub_download
570
+ # import torch
571
+ # import os
572
+ # from functools import lru_cache
573
+ # import hashlib
574
+
575
+ # app = Flask(__name__)
576
+
577
+ # print("=" * 50)
578
+ # print("Loading models and data...")
579
+ # print("=" * 50)
580
+
581
+ # # ------------------------------
582
+ # # Load embedding model (CPU)
583
+ # # ------------------------------
584
+ # embed_model = SentenceTransformer("all-MiniLM-L6-v2")
585
+ # print("βœ“ Embedding model loaded")
586
+
587
+ # # ------------------------------
588
+ # # Download files from Hugging Face
589
+ # # ------------------------------
590
+ # REPO_ID = "Redfire-1234/pcb_tutor"
591
+
592
+ # print("Downloading subject files from Hugging Face...")
593
+
594
+ # # Download Biology files
595
+ # bio_chunks_path = hf_hub_download(repo_id=REPO_ID, filename="bio_chunks.pkl", repo_type="model")
596
+ # faiss_bio_path = hf_hub_download(repo_id=REPO_ID, filename="faiss_bio.bin", repo_type="model")
597
+
598
+ # # Download Chemistry files
599
+ # chem_chunks_path = hf_hub_download(repo_id=REPO_ID, filename="chem_chunks.pkl", repo_type="model")
600
+ # faiss_chem_path = hf_hub_download(repo_id=REPO_ID, filename="faiss_chem.bin", repo_type="model")
601
+
602
+ # # Download Physics files
603
+ # phy_chunks_path = hf_hub_download(repo_id=REPO_ID, filename="phy_chunks.pkl", repo_type="model")
604
+ # faiss_phy_path = hf_hub_download(repo_id=REPO_ID, filename="faiss_phy.bin", repo_type="model")
605
+
606
+ # # Load all subjects into memory
607
+ # SUBJECTS = {
608
+ # "biology": {
609
+ # "chunks": pickle.load(open(bio_chunks_path, "rb")),
610
+ # "index": faiss.read_index(faiss_bio_path)
611
+ # },
612
+ # "chemistry": {
613
+ # "chunks": pickle.load(open(chem_chunks_path, "rb")),
614
+ # "index": faiss.read_index(faiss_chem_path)
615
+ # },
616
+ # "physics": {
617
+ # "chunks": pickle.load(open(phy_chunks_path, "rb")),
618
+ # "index": faiss.read_index(faiss_phy_path)
619
+ # }
620
+ # }
621
+
622
+ # print(f"βœ“ Biology: {len(SUBJECTS['biology']['chunks'])} chunks loaded")
623
+ # print(f"βœ“ Chemistry: {len(SUBJECTS['chemistry']['chunks'])} chunks loaded")
624
+ # print(f"βœ“ Physics: {len(SUBJECTS['physics']['chunks'])} chunks loaded")
625
+
626
+ # # ------------------------------
627
+ # # Load LLM model (CPU) with optimizations
628
+ # # ------------------------------
629
+ # model_name = "Qwen/Qwen2.5-3B-Instruct"
630
+ # print(f"Loading LLM: {model_name}")
631
+ # tokenizer = AutoTokenizer.from_pretrained(model_name)
632
+ # device = "cpu"
633
+
634
+ # # OPTIMIZATION: Load model with better dtype for CPU
635
+ # model = AutoModelForCausalLM.from_pretrained(
636
+ # model_name,
637
+ # torch_dtype=torch.float32,
638
+ # low_cpu_mem_usage=True # Optimization: Better memory management
639
+ # ).to(device)
640
+
641
+ # # OPTIMIZATION: Set model to eval mode and optimize for inference
642
+ # model.eval()
643
+ # if hasattr(torch, 'set_num_threads'):
644
+ # torch.set_num_threads(4) # Optimization: Use multiple CPU threads
645
+
646
+ # print(f"βœ“ LLM loaded on {device}")
647
+
648
+ # print("=" * 50)
649
+ # print("All models loaded successfully!")
650
+ # print("=" * 50)
651
+
652
+ # # ------------------------------
653
+ # # OPTIMIZATION: Add caching for MCQ generation
654
+ # # ------------------------------
655
+ # MCQ_CACHE = {}
656
+ # MAX_CACHE_SIZE = 100
657
+
658
+ # def get_cache_key(topic, subject, context_hash):
659
+ # """Generate a unique cache key"""
660
+ # return f"{subject}:{topic}:{context_hash}"
661
+
662
+ # def cache_mcq(key, mcqs):
663
+ # """Cache generated MCQs with size limit"""
664
+ # if len(MCQ_CACHE) >= MAX_CACHE_SIZE:
665
+ # # Remove oldest entry
666
+ # MCQ_CACHE.pop(next(iter(MCQ_CACHE)))
667
+ # MCQ_CACHE[key] = mcqs
668
+
669
+ # # ------------------------------
670
+ # # RAG Search in specific subject (optimized)
671
+ # # ------------------------------
672
+ # def rag_search(query, subject, k=5):
673
+ # if subject not in SUBJECTS:
674
+ # return None
675
+
676
+ # chunks = SUBJECTS[subject]["chunks"]
677
+ # index = SUBJECTS[subject]["index"]
678
+
679
+ # # OPTIMIZATION: Encode query (already fast with sentence-transformers)
680
+ # q_emb = embed_model.encode([query], show_progress_bar=False).astype("float32")
681
+ # D, I = index.search(q_emb, k)
682
+
683
+ # # Get the actual chunks
684
+ # results = []
685
+ # for idx in I[0]:
686
+ # if idx < len(chunks):
687
+ # results.append(chunks[idx])
688
+
689
+ # return "\n\n".join(results)
690
+
691
+ # # ------------------------------
692
+ # # OPTIMIZED MCQ Generation with reduced tokens
693
+ # # ------------------------------
694
+ # def generate_mcqs(context, topic, subject):
695
+ # # OPTIMIZATION: Check cache first
696
+ # context_hash = hashlib.md5(context.encode()).hexdigest()[:8]
697
+ # cache_key = get_cache_key(topic, subject, context_hash)
698
+
699
+ # if cache_key in MCQ_CACHE:
700
+ # print("βœ“ Using cached MCQs")
701
+ # return MCQ_CACHE[cache_key]
702
+
703
+ # # OPTIMIZATION: Shortened prompt for faster generation
704
+ # prompt = f"""You are a Class-12 {subject.title()} teacher creating MCQs.
705
+ # Topic: "{topic}"
706
+ # Context:
707
+ # {context}
708
+
709
+ # Generate exactly 5 MCQs in this format:
710
+ # Q1. [Question]
711
+ # A) [Option]
712
+ # B) [Option]
713
+ # C) [Option]
714
+ # D) [Option]
715
+ # Correct Answer: [Letter] - [Reason]
716
+
717
+ # Rules: Make correct answer from context, realistic distractors.
718
+ # Generate 5 MCQs:"""
719
+
720
+ # # OPTIMIZATION: Reduced max_length for faster tokenization
721
+ # inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=1536).to(device)
722
+
723
+ # # OPTIMIZATION: Use torch.no_grad() for inference (saves memory)
724
+ # with torch.no_grad():
725
+ # # OPTIMIZATION: Reduced max_new_tokens from 900 to 600 (sufficient for 5 MCQs)
726
+ # # OPTIMIZATION: Reduced temperature from 0.15 to 0.1 (faster, more deterministic)
727
+ # # OPTIMIZATION: Added num_beams=1 (greedy decoding, faster than sampling)
728
+ # outputs = model.generate(
729
+ # **inputs,
730
+ # max_new_tokens=600, # Reduced from 900
731
+ # temperature=0.1, # Reduced from 0.15
732
+ # top_p=0.85, # Slightly adjusted
733
+ # do_sample=True,
734
+ # repetition_penalty=1.15,
735
+ # pad_token_id=tokenizer.eos_token_id # Optimization: Explicit pad token
736
+ # )
737
+
738
+ # result = tokenizer.decode(outputs[0], skip_special_tokens=True)
739
+
740
+ # # Extract only the generated MCQs
741
+ # if "Generate 5 MCQs:" in result:
742
+ # result = result.split("Generate 5 MCQs:")[-1].strip()
743
+
744
+ # # OPTIMIZATION: Cache the result
745
+ # cache_mcq(cache_key, result)
746
+
747
+ # return result
748
+
749
+ # def verify_and_correct_answers(mcqs_text, context):
750
+ # """
751
+ # This function is kept for future enhancements
752
+ # """
753
+ # return mcqs_text
754
+
755
+ # # ------------------------------
756
+ # # HTML UI (with improved loading message)
757
+ # # ------------------------------
758
+ # HTML_TEMPLATE = """
759
+ # <!DOCTYPE html>
760
+ # <html>
761
+ # <head>
762
+ # <title>Class 12 PCB MCQ Generator</title>
763
+ # <style>
764
+ # * { margin: 0; padding: 0; box-sizing: border-box; }
765
+ # body {
766
+ # font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
767
+ # background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
768
+ # min-height: 100vh;
769
+ # padding: 20px;
770
+ # }
771
+ # .container {
772
+ # max-width: 900px;
773
+ # margin: 0 auto;
774
+ # background: white;
775
+ # border-radius: 20px;
776
+ # box-shadow: 0 20px 60px rgba(0,0,0,0.3);
777
+ # overflow: hidden;
778
+ # }
779
+ # .header {
780
+ # background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
781
+ # color: white;
782
+ # padding: 30px;
783
+ # text-align: center;
784
+ # }
785
+ # .header h1 { font-size: 2.5em; margin-bottom: 10px; }
786
+ # .content { padding: 40px; }
787
+ # .form-group {
788
+ # margin-bottom: 25px;
789
+ # }
790
+ # label {
791
+ # display: block;
792
+ # font-weight: 600;
793
+ # margin-bottom: 10px;
794
+ # color: #333;
795
+ # font-size: 16px;
796
+ # }
797
+ # select, input {
798
+ # width: 100%;
799
+ # padding: 15px;
800
+ # border: 2px solid #e0e0e0;
801
+ # border-radius: 10px;
802
+ # font-size: 16px;
803
+ # font-family: inherit;
804
+ # transition: border-color 0.3s;
805
+ # }
806
+ # select:focus, input:focus {
807
+ # outline: none;
808
+ # border-color: #667eea;
809
+ # }
810
+ # button {
811
+ # width: 100%;
812
+ # padding: 18px;
813
+ # background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
814
+ # color: white;
815
+ # border: none;
816
+ # border-radius: 10px;
817
+ # font-size: 18px;
818
+ # font-weight: 600;
819
+ # cursor: pointer;
820
+ # transition: all 0.3s;
821
+ # }
822
+ # button:hover {
823
+ # transform: translateY(-2px);
824
+ # box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4);
825
+ # }
826
+ # button:disabled {
827
+ # background: #ccc;
828
+ # cursor: not-allowed;
829
+ # transform: none;
830
+ # }
831
+ # .result {
832
+ # margin-top: 30px;
833
+ # padding: 25px;
834
+ # background: #f8f9fa;
835
+ # border-radius: 10px;
836
+ # border-left: 4px solid #667eea;
837
+ # display: none;
838
+ # }
839
+ # .result.show { display: block; }
840
+ # .result h3 {
841
+ # color: #667eea;
842
+ # margin-bottom: 20px;
843
+ # font-size: 1.4em;
844
+ # }
845
+ # .mcq-content {
846
+ # background: white;
847
+ # padding: 25px;
848
+ # border-radius: 8px;
849
+ # white-space: pre-wrap;
850
+ # line-height: 1.9;
851
+ # font-size: 15px;
852
+ # }
853
+ # .loading {
854
+ # text-align: center;
855
+ # padding: 30px;
856
+ # display: none;
857
+ # }
858
+ # .loading.show { display: block; }
859
+ # .spinner {
860
+ # border: 4px solid #f3f3f3;
861
+ # border-top: 4px solid #667eea;
862
+ # border-radius: 50%;
863
+ # width: 50px;
864
+ # height: 50px;
865
+ # animation: spin 1s linear infinite;
866
+ # margin: 0 auto 15px;
867
+ # }
868
+ # @keyframes spin {
869
+ # 0% { transform: rotate(0deg); }
870
+ # 100% { transform: rotate(360deg); }
871
+ # }
872
+ # .subject-tag {
873
+ # display: inline-block;
874
+ # padding: 5px 15px;
875
+ # border-radius: 20px;
876
+ # font-size: 13px;
877
+ # font-weight: 600;
878
+ # margin-right: 10px;
879
+ # }
880
+ # .bio { background: #d4edda; color: #155724; }
881
+ # .chem { background: #d1ecf1; color: #0c5460; }
882
+ # .phy { background: #f8d7da; color: #721c24; }
883
+ # .optimization-badge {
884
+ # background: #28a745;
885
+ # color: white;
886
+ # padding: 5px 12px;
887
+ # border-radius: 15px;
888
+ # font-size: 12px;
889
+ # margin-left: 10px;
890
+ # }
891
+ # </style>
892
+ # </head>
893
+ # <body>
894
+ # <div class="container">
895
+ # <div class="header">
896
+ # <h1>πŸŽ“ Class 12 PCB MCQ Generator</h1>
897
+ # <p style="font-size: 1.1em; margin-bottom: 15px;">Generate practice MCQs from your textbooks <span class="optimization-badge">⚑ Optimized</span></p>
898
+ # <div>
899
+ # <span class="subject-tag bio">Biology</span>
900
+ # <span class="subject-tag chem">Chemistry</span>
901
+ # <span class="subject-tag phy">Physics</span>
902
+ # </div>
903
+ # </div>
904
+
905
+ # <div class="content">
906
+ # <div class="form-group">
907
+ # <label for="subject">πŸ“š Select Subject</label>
908
+ # <select id="subject">
909
+ # <option value="biology">Biology</option>
910
+ # <option value="chemistry">Chemistry</option>
911
+ # <option value="physics">Physics</option>
912
+ # </select>
913
+ # </div>
914
 
915
+ # <div class="form-group">
916
+ # <label for="topic">✏️ Enter Topic</label>
917
+ # <input type="text" id="topic" placeholder="e.g., Mitochondria, Chemical Bonding, Newton's Laws">
918
+ # </div>
919
 
920
+ # <button onclick="generateMCQs()">πŸš€ Generate 5 MCQs</button>
921
 
922
+ # <div class="loading" id="loading">
923
+ # <div class="spinner"></div>
924
+ # <p style="color: #666; font-size: 16px;">Generating MCQs... This may take 20-40 seconds</p>
925
+ # <p style="color: #999; font-size: 13px; margin-top: 10px;">⚑ Optimized for faster generation</p>
926
+ # </div>
927
 
928
+ # <div class="result" id="result">
929
+ # <h3>πŸ“ Generated MCQs:</h3>
930
+ # <div style="background: #fff3cd; padding: 12px; border-radius: 6px; margin-bottom: 15px; color: #856404; font-size: 14px;">
931
+ # ⚠️ <strong>Note:</strong> AI-generated answers may occasionally be incorrect. Please verify answers using your textbook.
932
+ # </div>
933
+ # <div class="mcq-content" id="mcqContent"></div>
934
+ # </div>
935
+ # </div>
936
+ # </div>
937
+ # <script>
938
+ # async function generateMCQs() {
939
+ # const subject = document.getElementById('subject').value;
940
+ # const topic = document.getElementById('topic').value.trim();
941
 
942
+ # if (!topic) {
943
+ # alert('��️ Please enter a topic!');
944
+ # return;
945
+ # }
946
 
947
+ # const loading = document.getElementById('loading');
948
+ # const result = document.getElementById('result');
949
+ # const btn = document.querySelector('button');
950
 
951
+ # loading.classList.add('show');
952
+ # result.classList.remove('show');
953
+ # btn.disabled = true;
954
+ # btn.textContent = '⏳ Generating...';
955
 
956
+ # try {
957
+ # const response = await fetch('/generate', {
958
+ # method: 'POST',
959
+ # headers: {'Content-Type': 'application/json'},
960
+ # body: JSON.stringify({subject, topic})
961
+ # });
962
 
963
+ # const data = await response.json();
964
 
965
+ # if (data.error) {
966
+ # alert('❌ Error: ' + data.error);
967
+ # return;
968
+ # }
969
 
970
+ # document.getElementById('mcqContent').textContent = data.mcqs;
971
+ # result.classList.add('show');
972
+ # } catch (error) {
973
+ # alert('❌ Error: ' + error.message);
974
+ # } finally {
975
+ # loading.classList.remove('show');
976
+ # btn.disabled = false;
977
+ # btn.textContent = 'πŸš€ Generate 5 MCQs';
978
+ # }
979
+ # }
980
 
981
+ # // Allow Enter key to submit
982
+ # document.getElementById('topic').addEventListener('keypress', function(e) {
983
+ # if (e.key === 'Enter') {
984
+ # generateMCQs();
985
+ # }
986
+ # });
987
+ # </script>
988
+ # </body>
989
+ # </html>
990
+ # """
991
 
992
+ # # ------------------------------
993
+ # # Routes
994
+ # # ------------------------------
995
+ # @app.route("/")
996
+ # def home():
997
+ # return render_template_string(HTML_TEMPLATE)
998
 
999
+ # @app.route("/generate", methods=["POST"])
1000
+ # def generate():
1001
+ # try:
1002
+ # data = request.json
1003
+ # subject = data.get("subject", "").lower()
1004
+ # topic = data.get("topic", "")
1005
 
1006
+ # if not topic:
1007
+ # return jsonify({"error": "Topic is required"}), 400
1008
 
1009
+ # if subject not in SUBJECTS:
1010
+ # return jsonify({"error": "Invalid subject. Choose biology, chemistry, or physics."}), 400
1011
 
1012
+ # print(f"\nπŸ” Searching {subject} for topic: {topic}")
1013
 
1014
+ # # Retrieve context from RAG
1015
+ # context = rag_search(topic, subject, k=5)
1016
 
1017
+ # if not context or len(context.strip()) < 50:
1018
+ # return jsonify({"error": f"No relevant content found in {subject} for topic: {topic}"}), 404
1019
 
1020
+ # print(f"βœ“ Found context ({len(context)} chars)")
1021
 
1022
+ # # Generate MCQs (now with caching)
1023
+ # print("πŸ€– Generating MCQs...")
1024
+ # mcqs = generate_mcqs(context, topic, subject)
1025
 
1026
+ # print("βœ“ MCQs generated successfully")
1027
+
1028
+ # return jsonify({"mcqs": mcqs, "subject": subject})
1029
 
1030
+ # except Exception as e:
1031
+ # print(f"❌ Error in /generate: {e}")
1032
+ # import traceback
1033
+ # traceback.print_exc()
1034
+ # return jsonify({"error": str(e)}), 500
1035
 
1036
+ # @app.route("/health")
1037
+ # def health():
1038
+ # return jsonify({
1039
+ # "status": "healthy",
1040
+ # "subjects": {
1041
+ # "biology": len(SUBJECTS["biology"]["chunks"]),
1042
+ # "chemistry": len(SUBJECTS["chemistry"]["chunks"]),
1043
+ # "physics": len(SUBJECTS["physics"]["chunks"])
1044
+ # },
1045
+ # "cache_size": len(MCQ_CACHE)
1046
+ # })
1047
+
1048
+ # # OPTIMIZATION: Add cache stats endpoint
1049
+ # @app.route("/cache/stats")
1050
+ # def cache_stats():
1051
+ # return jsonify({
1052
+ # "cached_topics": len(MCQ_CACHE),
1053
+ # "max_cache_size": MAX_CACHE_SIZE,
1054
+ # "cache_keys": list(MCQ_CACHE.keys())
1055
+ # })
1056
+
1057
+ # # OPTIMIZATION: Add cache clear endpoint (optional)
1058
+ # @app.route("/cache/clear", methods=["POST"])
1059
+ # def clear_cache():
1060
+ # MCQ_CACHE.clear()
1061
+ # return jsonify({"status": "Cache cleared"})
1062
+
1063
+ # # ------------------------------
1064
+ # # Run the App
1065
+ # # ------------------------------
1066
+ # if __name__ == "__main__":
1067
+ # port = int(os.environ.get("PORT", 7860))
1068
+ # print(f"\nπŸš€ Starting Flask on 0.0.0.0:{port}\n")
1069
+ # app.run(host="0.0.0.0", port=port, debug=False)
1070
 
 
 
 
 
1071