Talha812 commited on
Commit
77ea30f
Β·
verified Β·
1 Parent(s): 2cc3be2

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +915 -0
app.py ADDED
@@ -0,0 +1,915 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ============================================================
2
+ # πŸ“¦ IMPORTS
3
+ # ============================================================
4
+
5
+ import re
6
+ import os
7
+ import json
8
+ import faiss
9
+ import gradio as gr
10
+ import numpy as np
11
+ import pandas as pd
12
+ import requests
13
+
14
+ from sentence_transformers import SentenceTransformer
15
+ from groq import Groq
16
+
17
+
18
+ # ============================================================
19
+ # πŸ”‘ GROQ API KEY
20
+ # ============================================================
21
+
22
+ GROQ_API_KEY = os.getenv('GRAPI')
23
+
24
+ client = Groq(api_key=GROQ_API_KEY)
25
+
26
+
27
+ # ============================================================
28
+ # πŸ“˜ GOOGLE DOC LOADER
29
+ # ============================================================
30
+
31
+ DOC_ID = "1utErkC3Xa8hhiQul7tzil9SjRydGEKGEBpVgaCp6qXM"
32
+
33
+ def load_google_doc():
34
+
35
+ url = f"https://docs.google.com/document/d/{DOC_ID}/export?format=txt"
36
+
37
+ text = requests.get(url).text
38
+
39
+ return text
40
+
41
+
42
+ raw_text = load_google_doc()
43
+
44
+
45
+ # ============================================================
46
+ # πŸ“š SPLIT CHAPTERS
47
+ # ============================================================
48
+
49
+ def split_units(text):
50
+
51
+ units = {}
52
+
53
+ splits = re.split(r"(UNIT\s+\d+.*?)\n", text, flags=re.I)
54
+
55
+ for i in range(1, len(splits), 2):
56
+
57
+ title = splits[i].strip()
58
+
59
+ content = splits[i+1]
60
+
61
+ units[title] = content
62
+
63
+ return units
64
+
65
+
66
+ units_data = split_units(raw_text)
67
+
68
+ print("βœ… Loaded Units:")
69
+ print(list(units_data.keys()))
70
+
71
+
72
+ # ============================================================
73
+ # πŸ” EMBEDDING MODEL
74
+ # ============================================================
75
+
76
+ embedding_model = SentenceTransformer(
77
+ "all-MiniLM-L6-v2"
78
+ )
79
+
80
+
81
+ # ============================================================
82
+ # βœ‚οΈ CHUNKING
83
+ # ============================================================
84
+
85
+ def chunk_text(text, chunk_size=150):
86
+
87
+ words = text.split()
88
+
89
+ chunks = []
90
+
91
+ for i in range(0, len(words), chunk_size):
92
+
93
+ chunk = " ".join(words[i:i+chunk_size])
94
+
95
+ chunks.append(chunk)
96
+
97
+ return chunks
98
+
99
+
100
+ all_chunks = []
101
+ chunk_unit_map = []
102
+
103
+ for unit, text in units_data.items():
104
+
105
+ chunks = chunk_text(text)
106
+
107
+ all_chunks.extend(chunks)
108
+
109
+ chunk_unit_map.extend([unit]*len(chunks))
110
+
111
+
112
+ # ============================================================
113
+ # 🧠 CREATE FAISS VECTOR DB
114
+ # ============================================================
115
+
116
+ embeddings = embedding_model.encode(all_chunks)
117
+
118
+ if len(embeddings.shape) == 1:
119
+
120
+ embeddings = np.expand_dims(embeddings, axis=0)
121
+
122
+ dimension = embeddings.shape[1]
123
+
124
+ index = faiss.IndexFlatL2(dimension)
125
+
126
+ index.add(np.array(embeddings).astype("float32"))
127
+
128
+ print("βœ… FAISS INDEX READY")
129
+
130
+
131
+ # ============================================================
132
+ # πŸ” RETRIEVE CONTEXT
133
+ # ============================================================
134
+
135
+ def retrieve_context(question, unit):
136
+
137
+ q_embedding = embedding_model.encode([question])
138
+
139
+ D, I = index.search(
140
+ np.array(q_embedding).astype("float32"),
141
+ 5
142
+ )
143
+
144
+ retrieved = []
145
+
146
+ for idx in I[0]:
147
+
148
+ if chunk_unit_map[idx] == unit:
149
+
150
+ retrieved.append(all_chunks[idx])
151
+
152
+ return "\n".join(retrieved)
153
+
154
+
155
+ # ============================================================
156
+ # πŸ€– GROQ HELPER
157
+ # ============================================================
158
+
159
+ def groq_call(prompt):
160
+
161
+ completion = client.chat.completions.create(
162
+
163
+ model="llama-3.3-70b-versatile",
164
+
165
+ messages=[
166
+ {
167
+ "role": "user",
168
+ "content": prompt
169
+ }
170
+ ],
171
+
172
+ temperature=0.5
173
+ )
174
+
175
+ return completion.choices[0].message.content
176
+
177
+
178
+ # ============================================================
179
+ # πŸ“š VOCAB EXTRACTION
180
+ # ============================================================
181
+
182
+ def extract_vocabulary(unit):
183
+
184
+ context = units_data[unit][:7000]
185
+
186
+ prompt = f"""
187
+ You are an FSC English linguistic AI.
188
+
189
+ Extract ONLY 5 important contextual vocabulary words.
190
+
191
+ Return STRICT JSON:
192
+
193
+ [
194
+ {{
195
+ "word":"...",
196
+ "meaning_en":"...",
197
+ "meaning_ur":"...",
198
+ "context_sentence":"..."
199
+ }}
200
+ ]
201
+
202
+ TEXT:
203
+ {context}
204
+ """
205
+
206
+ result = groq_call(prompt)
207
+
208
+ try:
209
+
210
+ data = json.loads(result)
211
+
212
+ return data
213
+
214
+ except:
215
+
216
+ return []
217
+
218
+
219
+ # ============================================================
220
+ # 🧠 METACOGNITIVE ENGINE
221
+ # ============================================================
222
+
223
+ student_analytics = []
224
+
225
+
226
+ def build_mcq(word_data):
227
+
228
+ word = word_data["word"]
229
+
230
+ meaning = word_data["meaning_en"]
231
+
232
+ prompt = f"""
233
+ Create one FSC-level MCQ.
234
+
235
+ Word: {word}
236
+ Meaning: {meaning}
237
+
238
+ Return STRICT JSON:
239
+
240
+ {{
241
+ "question":"...",
242
+ "options": {{
243
+ "A":"...",
244
+ "B":"...",
245
+ "C":"..."
246
+ }},
247
+ "answer":"A/B/C"
248
+ }}
249
+ """
250
+
251
+ result = groq_call(prompt)
252
+
253
+ try:
254
+
255
+ return json.loads(result)
256
+
257
+ except:
258
+
259
+ return None
260
+
261
+
262
+ # ============================================================
263
+ # 🎯 PROCESS ANSWER
264
+ # ============================================================
265
+
266
+ def evaluate_answer(
267
+ word,
268
+ correct_answer,
269
+ selected_answer,
270
+ confidence
271
+ ):
272
+
273
+ correct = selected_answer == correct_answer
274
+
275
+ feedback = ""
276
+
277
+ autonomy_points = 0
278
+
279
+ if correct:
280
+
281
+ if confidence <= 2:
282
+
283
+ feedback = (
284
+ f"βœ… Correct! "
285
+ f"You were unsure, but contextual clues helped you."
286
+ )
287
+
288
+ elif confidence >= 4:
289
+
290
+ feedback = (
291
+ f"πŸ”₯ Excellent! "
292
+ f"You understood the lexical context confidently."
293
+ )
294
+
295
+ autonomy_points += 10
296
+
297
+ else:
298
+
299
+ if confidence >= 4:
300
+
301
+ feedback = (
302
+ f"⚠️ High confidence but incorrect answer.\n"
303
+ f"You may need stronger contextual inference."
304
+ )
305
+
306
+ else:
307
+
308
+ feedback = (
309
+ f"❌ Incorrect.\n"
310
+ f"Try analyzing surrounding contextual clues."
311
+ )
312
+
313
+ student_analytics.append({
314
+
315
+ "word": word,
316
+
317
+ "correct": correct,
318
+
319
+ "confidence": confidence
320
+ })
321
+
322
+ return feedback, autonomy_points
323
+
324
+
325
+ # ============================================================
326
+ # πŸŒ‰ BILINGUAL SCAFFOLD
327
+ # ============================================================
328
+
329
+ def scaffold(word_data, level):
330
+
331
+ if level == 1:
332
+
333
+ return f"""
334
+ 🧠 English Scaffold:
335
+
336
+ {word_data['meaning_en']}
337
+ """
338
+
339
+ if level == 2:
340
+
341
+ return f"""
342
+ 🧠 Urdu Scaffold:
343
+
344
+ {word_data['meaning_ur']}
345
+ """
346
+
347
+ return ""
348
+
349
+
350
+ # ============================================================
351
+ # πŸ“Š ANALYTICS DASHBOARD
352
+ # ============================================================
353
+
354
+ def generate_dashboard():
355
+
356
+ if len(student_analytics) == 0:
357
+
358
+ return pd.DataFrame()
359
+
360
+ df = pd.DataFrame(student_analytics)
361
+
362
+ return df
363
+
364
+
365
+ # ============================================================
366
+ # 🎨 CUSTOM CSS
367
+ # ============================================================
368
+
369
+ custom_css = """
370
+
371
+ /* =========================
372
+ GLOBAL
373
+ ========================= */
374
+
375
+ body{
376
+ background:#F1F5F9 !important;
377
+ font-family:'Inter',sans-serif;
378
+ color:#111827 !important;
379
+ }
380
+
381
+ /* =========================
382
+ MAIN APP CONTAINER
383
+ ========================= */
384
+
385
+ .gradio-container{
386
+ max-width:1200px !important;
387
+ margin:auto !important;
388
+
389
+ background:white !important;
390
+
391
+ border-radius:24px;
392
+
393
+ padding:35px !important;
394
+
395
+ box-shadow:
396
+ 0px 8px 30px rgba(0,0,0,0.08);
397
+
398
+ color:#111827 !important;
399
+ }
400
+
401
+
402
+ /* =========================
403
+ TITLES
404
+ ========================= */
405
+
406
+ .main-title{
407
+ text-align:center;
408
+
409
+ font-size:52px;
410
+
411
+ font-weight:800;
412
+
413
+ color:#0F172A !important;
414
+
415
+ margin-bottom:8px;
416
+ }
417
+
418
+ .subtitle{
419
+ text-align:center;
420
+
421
+ font-size:20px;
422
+
423
+ color:#475569 !important;
424
+
425
+ margin-bottom:35px;
426
+ }
427
+
428
+
429
+ /* =========================
430
+ CARDS
431
+ ========================= */
432
+
433
+ .card{
434
+ background:#FFFFFF !important;
435
+
436
+ border:1px solid #E2E8F0 !important;
437
+
438
+ border-radius:20px;
439
+
440
+ padding:22px;
441
+
442
+ margin-bottom:22px;
443
+
444
+ box-shadow:
445
+ 0px 2px 12px rgba(0,0,0,0.05);
446
+ }
447
+
448
+
449
+ /* =========================
450
+ TEXT COLORS
451
+ ========================= */
452
+
453
+ h1,h2,h3,h4,h5,h6{
454
+ color:#0F172A !important;
455
+ }
456
+
457
+ p,span,div,label{
458
+ color:#111827 !important;
459
+ }
460
+
461
+
462
+ /* =========================
463
+ TARGET WORD
464
+ ========================= */
465
+
466
+ .target-word{
467
+ font-size:40px;
468
+
469
+ font-weight:700;
470
+
471
+ color:#0284C7 !important;
472
+
473
+ margin-bottom:10px;
474
+ }
475
+
476
+ .context-text{
477
+ font-size:18px;
478
+
479
+ line-height:1.8;
480
+
481
+ color:#1E293B !important;
482
+ }
483
+
484
+
485
+ /* =========================
486
+ SECTION LABELS
487
+ ========================= */
488
+
489
+ .section-label{
490
+ display:inline-block;
491
+
492
+ background:#E0F2FE;
493
+
494
+ color:#0369A1 !important;
495
+
496
+ padding:8px 14px;
497
+
498
+ border-radius:10px;
499
+
500
+ font-size:15px;
501
+
502
+ font-weight:700;
503
+
504
+ margin-bottom:16px;
505
+ }
506
+
507
+
508
+ /* =========================
509
+ INPUTS
510
+ ========================= */
511
+
512
+ textarea,
513
+ input,
514
+ select{
515
+
516
+ background:#FFFFFF !important;
517
+
518
+ color:#111827 !important;
519
+
520
+ border:1.5px solid #CBD5E1 !important;
521
+
522
+ border-radius:12px !important;
523
+
524
+ padding:12px !important;
525
+
526
+ font-size:16px !important;
527
+ }
528
+
529
+
530
+ /* =========================
531
+ DROPDOWN
532
+ ========================= */
533
+
534
+ .gr-dropdown{
535
+ color:#111827 !important;
536
+ }
537
+
538
+
539
+ /* =========================
540
+ RADIO BUTTONS
541
+ ========================= */
542
+
543
+ .gr-radio label{
544
+
545
+ background:#F8FAFC !important;
546
+
547
+ border:1px solid #CBD5E1 !important;
548
+
549
+ border-radius:12px;
550
+
551
+ padding:12px 16px;
552
+
553
+ margin-right:10px;
554
+
555
+ transition:0.2s;
556
+ }
557
+
558
+ .gr-radio label:hover{
559
+ background:#E0F2FE !important;
560
+ }
561
+
562
+
563
+ /* =========================
564
+ BUTTONS
565
+ ========================= */
566
+
567
+ button{
568
+
569
+ background:#0EA5E9 !important;
570
+
571
+ color:white !important;
572
+
573
+ border:none !important;
574
+
575
+ border-radius:14px !important;
576
+
577
+ font-size:17px !important;
578
+
579
+ font-weight:700 !important;
580
+
581
+ padding:14px 22px !important;
582
+
583
+ transition:0.25s ease;
584
+ }
585
+
586
+ button:hover{
587
+
588
+ background:#0284C7 !important;
589
+
590
+ transform:translateY(-1px);
591
+ }
592
+
593
+
594
+ /* =========================
595
+ SLIDER
596
+ ========================= */
597
+
598
+ input[type="range"]{
599
+ accent-color:#0EA5E9 !important;
600
+ }
601
+
602
+
603
+ /* =========================
604
+ OUTPUT BOXES
605
+ ========================= */
606
+
607
+ .output-box{
608
+
609
+ background:#F8FAFC !important;
610
+
611
+ border:1px solid #E2E8F0 !important;
612
+
613
+ border-radius:16px;
614
+
615
+ padding:18px;
616
+
617
+ color:#111827 !important;
618
+ }
619
+
620
+
621
+ /* =========================
622
+ CHATBOT / MARKDOWN
623
+ ========================= */
624
+
625
+ .markdown-text{
626
+ color:#111827 !important;
627
+ }
628
+
629
+
630
+ /* =========================
631
+ MOBILE RESPONSIVE
632
+ ========================= */
633
+
634
+ @media(max-width:768px){
635
+
636
+ .main-title{
637
+ font-size:34px;
638
+ }
639
+
640
+ .subtitle{
641
+ font-size:16px;
642
+ }
643
+
644
+ .target-word{
645
+ font-size:28px;
646
+ }
647
+
648
+ .context-text{
649
+ font-size:16px;
650
+ }
651
+
652
+ .gradio-container{
653
+ padding:18px !important;
654
+ }
655
+ }
656
+
657
+ """
658
+
659
+
660
+ # ============================================================
661
+ # πŸš€ UI FUNCTIONS
662
+ # ============================================================
663
+
664
+ current_vocab = []
665
+ current_mcq = None
666
+ current_word_data = None
667
+
668
+
669
+ def load_chapter(unit):
670
+
671
+ global current_vocab
672
+ global current_word_data
673
+ global current_mcq
674
+
675
+ current_vocab = extract_vocabulary(unit)
676
+
677
+ if len(current_vocab) == 0:
678
+
679
+ return (
680
+ "❌ Could not extract vocabulary.",
681
+ "",
682
+ gr.update(choices=[]),
683
+ ""
684
+ )
685
+
686
+ current_word_data = current_vocab[0]
687
+
688
+ current_mcq = build_mcq(current_word_data)
689
+
690
+ vocab_text = f"""
691
+ # πŸ“˜ Target Word
692
+
693
+ ### {current_word_data['word']}
694
+
695
+ ### Context Sentence:
696
+ {current_word_data['context_sentence']}
697
+ """
698
+
699
+ return (
700
+
701
+ vocab_text,
702
+
703
+ current_mcq["question"],
704
+
705
+ gr.update(
706
+ choices=list(
707
+ current_mcq["options"].values()
708
+ )
709
+ ),
710
+
711
+ ""
712
+ )
713
+
714
+
715
+ def submit_answer(selected, confidence):
716
+
717
+ global current_mcq
718
+ global current_word_data
719
+
720
+ options = current_mcq["options"]
721
+
722
+ reverse_map = {
723
+ v:k for k,v in options.items()
724
+ }
725
+
726
+ selected_letter = reverse_map[selected]
727
+
728
+ feedback, points = evaluate_answer(
729
+
730
+ current_word_data["word"],
731
+
732
+ current_mcq["answer"],
733
+
734
+ selected_letter,
735
+
736
+ confidence
737
+ )
738
+
739
+ return f"""
740
+ # 🧠 Feedback
741
+
742
+ {feedback}
743
+
744
+ ⭐ Autonomy Points: {points}
745
+ """
746
+
747
+
748
+ def show_english_scaffold():
749
+
750
+ global current_word_data
751
+
752
+ return scaffold(current_word_data, 1)
753
+
754
+
755
+ def show_urdu_scaffold():
756
+
757
+ global current_word_data
758
+
759
+ return scaffold(current_word_data, 2)
760
+
761
+
762
+ def show_dashboard():
763
+
764
+ df = generate_dashboard()
765
+
766
+ return df
767
+
768
+
769
+ # ============================================================
770
+ # 🌟 GRADIO UI
771
+ # ============================================================
772
+
773
+ with gr.Blocks(
774
+ css=custom_css,
775
+ theme=gr.themes.Soft()
776
+ ) as demo:
777
+
778
+ gr.HTML("""
779
+ <div class='main-title'>
780
+ πŸš€ LexiMetrica
781
+ </div>
782
+
783
+ <div class='subtitle'>
784
+ AI Metacognitive Lexical Enhancement Framework
785
+ </div>
786
+ """)
787
+
788
+ with gr.Row():
789
+
790
+ chapter_dropdown = gr.Dropdown(
791
+
792
+ choices=list(units_data.keys()),
793
+
794
+ label="πŸ“š Select Chapter"
795
+ )
796
+
797
+ load_btn = gr.Button(
798
+ "Load Chapter",
799
+ variant="primary"
800
+ )
801
+
802
+ with gr.Row():
803
+
804
+ vocab_output = gr.Markdown()
805
+
806
+ with gr.Row():
807
+
808
+ mcq_question = gr.Markdown()
809
+
810
+ with gr.Row():
811
+
812
+ mcq_radio = gr.Radio(
813
+ choices=[],
814
+ label="Choose Meaning"
815
+ )
816
+
817
+ confidence_slider = gr.Slider(
818
+
819
+ minimum=1,
820
+
821
+ maximum=5,
822
+
823
+ step=1,
824
+
825
+ label="🧠 Confidence Level"
826
+ )
827
+
828
+ submit_btn = gr.Button(
829
+ "Submit Answer",
830
+ variant="primary"
831
+ )
832
+
833
+ feedback_output = gr.Markdown()
834
+
835
+ gr.Markdown("---")
836
+
837
+ gr.Markdown("## πŸŒ‰ Need a Bridge?")
838
+
839
+ with gr.Row():
840
+
841
+ english_btn = gr.Button(
842
+ "English Scaffold"
843
+ )
844
+
845
+ urdu_btn = gr.Button(
846
+ "Urdu Scaffold"
847
+ )
848
+
849
+ scaffold_output = gr.Markdown()
850
+
851
+ gr.Markdown("---")
852
+
853
+ dashboard_btn = gr.Button(
854
+ "πŸ“Š Show Analytics Dashboard"
855
+ )
856
+
857
+ dashboard_output = gr.Dataframe()
858
+
859
+ # ======================================
860
+ # EVENTS
861
+ # ======================================
862
+
863
+ load_btn.click(
864
+
865
+ fn=load_chapter,
866
+
867
+ inputs=chapter_dropdown,
868
+
869
+ outputs=[
870
+ vocab_output,
871
+ mcq_question,
872
+ mcq_radio,
873
+ scaffold_output
874
+ ]
875
+ )
876
+
877
+ submit_btn.click(
878
+
879
+ fn=submit_answer,
880
+
881
+ inputs=[
882
+ mcq_radio,
883
+ confidence_slider
884
+ ],
885
+
886
+ outputs=feedback_output
887
+ )
888
+
889
+ english_btn.click(
890
+
891
+ fn=show_english_scaffold,
892
+
893
+ outputs=scaffold_output
894
+ )
895
+
896
+ urdu_btn.click(
897
+
898
+ fn=show_urdu_scaffold,
899
+
900
+ outputs=scaffold_output
901
+ )
902
+
903
+ dashboard_btn.click(
904
+
905
+ fn=show_dashboard,
906
+
907
+ outputs=dashboard_output
908
+ )
909
+
910
+
911
+ # ============================================================
912
+ # πŸš€ LAUNCH
913
+ # ============================================================
914
+
915
+ demo.launch(debug=True)