Spaces:
Sleeping
Sleeping
| # ============================================================ | |
| # English Learning Assistant — TinyKAN-Distill | |
| # Author: Mouna Khlifi — University of Kairouan, Tunisia | |
| # ============================================================ | |
| import gradio as gr | |
| import torch | |
| import torch.nn as nn | |
| import numpy as np | |
| from sentence_transformers import SentenceTransformer | |
| import sys, os, random | |
| import google.generativeai as genai | |
| os.system("pip install -q git+https://github.com/KindXiaoming/pykan.git google-generativeai") | |
| sys.path.insert(0, '/tmp/pykan') | |
| from kan import KAN | |
| class TinyKANDistilled(nn.Module): | |
| def __init__(self, input_dim=9, grid=3): | |
| super().__init__() | |
| self.kan_main = KAN(width=[[input_dim,0],[16,0],[8,0],[1,0]], grid=grid) | |
| self.linear_branch = nn.Sequential(nn.Linear(input_dim,8), nn.GELU(), nn.Linear(8,1)) | |
| self.gate = nn.Sequential(nn.Linear(input_dim,8), nn.ReLU(), nn.Linear(8,2), nn.Softmax(dim=1)) | |
| self.skip = nn.Linear(input_dim, 1) | |
| def forward(self, x): | |
| k = self.kan_main(x); l = self.linear_branch(x) | |
| g = self.gate(x) | |
| return (g * torch.cat([k,l],dim=1)).sum(dim=1,keepdim=True) + 0.05*self.skip(x) | |
| def get_gates(self, x): | |
| with torch.no_grad(): | |
| return self.gate(x).cpu().numpy()[0] | |
| DEVICE = "cpu" | |
| encoders = { | |
| "MiniLM": SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2"), | |
| "MPNet": SentenceTransformer("sentence-transformers/all-mpnet-base-v2"), | |
| "RoBERTa": SentenceTransformer("sentence-transformers/stsb-roberta-base"), | |
| } | |
| tinykan = TinyKANDistilled(9, 3) | |
| tinykan.load_state_dict(torch.load("tinykan_cosine.pth", map_location=DEVICE, weights_only=False)) | |
| tinykan.eval() | |
| GEMINI_KEY = os.environ.get("GEMINI_API_KEY", "") | |
| gemini_model = None | |
| if GEMINI_KEY: | |
| genai.configure(api_key=GEMINI_KEY) | |
| gemini_model = genai.GenerativeModel("gemini-1.5-flash") | |
| EXERCISES = { | |
| "translation": [ | |
| {"fr": "Le soleil se lève à l'est.", "en": "The sun rises in the east.", "level": "A1"}, | |
| {"fr": "Il fait beau aujourd'hui.", "en": "The weather is nice today.", "level": "A1"}, | |
| {"fr": "J'ai un chat et un chien.", "en": "I have a cat and a dog.", "level": "A1"}, | |
| {"fr": "J'apprends l'anglais depuis deux ans.", "en": "I have been learning English for two years.", "level": "A2"}, | |
| {"fr": "Pouvez-vous me montrer le chemin vers la gare ?", "en": "Could you show me the way to the train station?", "level": "A2"}, | |
| {"fr": "Nous avons visité Paris l'été dernier.", "en": "We visited Paris last summer.", "level": "A2"}, | |
| {"fr": "Cette décision pourrait avoir des conséquences majeures.", "en": "This decision could have major consequences.", "level": "B1"}, | |
| {"fr": "La technologie transforme notre façon de communiquer.", "en": "Technology is transforming the way we communicate.", "level": "B1"}, | |
| {"fr": "Il est important de protéger l'environnement.", "en": "It is important to protect the environment.", "level": "B1"}, | |
| {"fr": "Les recherches scientifiques ont démontré l'efficacité de cette méthode.", "en": "Scientific research has demonstrated the effectiveness of this method.", "level": "B2"}, | |
| {"fr": "Les énergies renouvelables représentent l'avenir de notre planète.", "en": "Renewable energies represent the future of our planet.", "level": "B2"}, | |
| {"fr": "Il est impératif que nous prenions des mesures immédiates.", "en": "It is imperative that we take immediate action.", "level": "C1"}, | |
| {"fr": "La mondialisation a engendré des transformations socio-économiques sans précédent.", "en": "Globalization has brought about unprecedented socio-economic transformations.", "level": "C2"}, | |
| ], | |
| "error_correction": [ | |
| {"wrong": "She don't like to go at school every days.", "correct": "She doesn't like to go to school every day.", "level": "A2", "error_type": "Grammar + Preposition + Plural"}, | |
| {"wrong": "He have been work here since three years.", "correct": "He has been working here for three years.", "level": "B1", "error_type": "Grammar (Present Perfect Continuous) + Preposition"}, | |
| {"wrong": "She is more smarter than her sister.", "correct": "She is smarter than her sister.", "level": "B1", "error_type": "Grammar (Double comparative)"}, | |
| {"wrong": "The goverment anounced a new enviromental policy.", "correct": "The government announced a new environmental policy.", "level": "B2", "error_type": "Spelling"}, | |
| {"wrong": "If I would have more time, I will study more.", "correct": "If I had more time, I would study more.", "level": "B2", "error_type": "Conjugation (Conditional type 2)"}, | |
| {"wrong": "Despite of his efforts, he don't succeed to pass the exam.", "correct": "Despite his efforts, he did not succeed in passing the exam.", "level": "C1", "error_type": "Grammar + Preposition + Conjugation"}, | |
| ], | |
| "fill_blank": [ | |
| {"display": "She _____ (study) English for five years before she moved to London.", "hint": "Past Perfect", "answer": "had studied", "level": "B1", "error_type": "Conjugation — Past Perfect"}, | |
| {"display": "The new policy will have a significant _____ on the economy. (affect / effect)", "hint": "Noun form required", "answer": "effect", "level": "B2", "error_type": "Vocabulary — affect vs. effect"}, | |
| {"display": "She has been interested _____ linguistics since her undergraduate studies.", "hint": "Preposition after 'interested'", "answer": "in", "level": "B1", "error_type": "Prepositional usage"}, | |
| {"display": "_____ research published in Nature suggests a breakthrough in cancer treatment.", "hint": "Definite article — specific reference", "answer": "The", "level": "C1", "error_type": "Article usage — definite"}, | |
| {"display": "By the time she arrives, we _____ (finish) the project.", "hint": "Future Perfect", "answer": "will have finished", "level": "B2", "error_type": "Conjugation — Future Perfect"}, | |
| ], | |
| "vocabulary": [ | |
| {"fr_def": "Personne qui travaille avec vous dans la même organisation\n/ A person who works with you in the same organisation", "answer": "colleague", "synonyms": "co-worker, associate, teammate", "level": "A2", "error_type": "Professional vocabulary"}, | |
| {"fr_def": "Rendre quelque chose plus simple à comprendre ou à réaliser\n/ To make something easier to understand or accomplish", "answer": "simplify", "synonyms": "streamline, facilitate, clarify", "level": "B1", "error_type": "Action verb — register"}, | |
| {"fr_def": "Qui ne peut pas être évité ou empêché\n/ That which cannot be avoided or prevented", "answer": "inevitable", "synonyms": "unavoidable, inescapable, certain", "level": "B2", "error_type": "Academic adjective"}, | |
| {"fr_def": "Ensemble des règles juridiques régissant une société\n/ The body of laws governing a society", "answer": "legislation", "synonyms": "law, regulation, statute, ordinance", "level": "B2", "error_type": "Legal and academic vocabulary"}, | |
| {"fr_def": "Étude scientifique du langage et de sa structure\n/ The scientific study of language and its structure", "answer": "linguistics", "synonyms": "language study, philology", "level": "C1", "error_type": "Academic discipline — terminology"}, | |
| ], | |
| "comprehension": [ | |
| { | |
| "passage": "Artificial intelligence (AI) has rapidly transformed numerous sectors, including healthcare, education, and transportation. Machine learning algorithms can now diagnose diseases with accuracy comparable to experienced physicians. However, ethical concerns about data privacy and algorithmic bias remain significant challenges that must be addressed by policymakers and researchers alike.", | |
| "question": "According to the passage, what two ethical concerns are identified as significant challenges?\n/ Selon le texte, quelles sont les deux préoccupations éthiques identifiées comme défis majeurs ?", | |
| "answer": "data privacy and algorithmic bias", | |
| "level": "B2", "error_type": "Reading comprehension — information retrieval" | |
| }, | |
| { | |
| "passage": "The Industrial Revolution, which began in Britain in the late 18th century, fundamentally altered the nature of work and society. The shift from agrarian economies to industrial ones displaced millions of workers while simultaneously creating new forms of employment. This period of transition was marked by both unprecedented economic growth and severe social inequalities.", | |
| "question": "What two contrasting outcomes of the Industrial Revolution does the author describe?\n/ Quels deux résultats contrastés de la Révolution industrielle l'auteur décrit-il ?", | |
| "answer": "unprecedented economic growth and severe social inequalities", | |
| "level": "C1", "error_type": "Reading comprehension — synthesis and contrast" | |
| }, | |
| ] | |
| } | |
| store = {"ref": "", "display": "", "type": "", "level": "B1", "error_type": "", | |
| "pct": 0, "g_kan": 0, "g_lin": 0, "cos": [0,0,0]} | |
| def compute_similarity(s1, s2): | |
| cos = [] | |
| for enc in encoders.values(): | |
| e1 = enc.encode([s1], convert_to_numpy=True) | |
| e2 = enc.encode([s2], convert_to_numpy=True) | |
| e1 /= np.maximum(np.linalg.norm(e1,axis=1,keepdims=True),1e-8) | |
| e2 /= np.maximum(np.linalg.norm(e2,axis=1,keepdims=True),1e-8) | |
| cos.append(float((e1*e2).sum())) | |
| c1,c2,c3 = cos | |
| feat = np.array([[c1,c2,c3,abs(c1-c2),abs(c1-c3),abs(c2-c3),(c1+c2+c3)/3,max(cos),min(cos)]],dtype=np.float32) | |
| feat_t = torch.tensor(feat) | |
| with torch.no_grad(): | |
| score = torch.sigmoid(tinykan(feat_t)).item() | |
| gates = tinykan.get_gates(feat_t) | |
| return round(score*100,1), round(float(gates[0])*100,1), round(float(gates[1])*100,1), [round(c*100,1) for c in cos] | |
| def analyze_errors(user_ans, reference): | |
| import re | |
| def clean(s): return set(re.sub(r'[.,!?;:\-]', '', s.lower()).split()) | |
| u = clean(user_ans); r = clean(reference) | |
| return r - u, u - r, u & r | |
| def get_gemini_feedback(display, user_ans, reference, pct, level, ex_type, error_type): | |
| if not gemini_model: | |
| return "" | |
| try: | |
| prompt = f"""You are an academic English language instructor assessing a {level}-level learner. | |
| Exercise type: {ex_type} | Linguistic focus: {error_type} | |
| Task: {display} | |
| Learner response: "{user_ans}" | |
| Reference answer: "{reference}" | |
| TinyKAN semantic similarity score: {pct}% | |
| Provide concise structured bilingual feedback: | |
| **English Assessment:** | |
| - Accuracy: [correct/incorrect elements] | |
| - Errors identified: [grammar / spelling / vocabulary / structure] | |
| - Linguistic rule: [relevant rule explained briefly] | |
| - Recommendation: [concrete improvement strategy] | |
| **Évaluation en français :** | |
| - Précision: [éléments corrects/incorrects] | |
| - Erreurs: [grammaire / orthographe / vocabulaire / structure] | |
| - Règle linguistique: [règle pertinente expliquée brièvement] | |
| - Recommandation: [stratégie d'amélioration concrète]""" | |
| r = gemini_model.generate_content(prompt) | |
| return r.text | |
| except: | |
| return "" | |
| def new_exercise(level, exercise_type, name): | |
| pool = [e for e in EXERCISES.get(exercise_type, EXERCISES["translation"]) if e.get("level") == level] | |
| if not pool: pool = EXERCISES.get(exercise_type, EXERCISES["translation"]) | |
| ex = random.choice(pool) | |
| store["level"] = level; store["type"] = exercise_type | |
| cfg = { | |
| "translation": ("#eff6ff","#1d4ed8","#bfdbfe","🔤 Translation (FR → EN)"), | |
| "error_correction": ("#fff7ed","#b45309","#fde68a","✏️ Error Correction"), | |
| "fill_blank": ("#f0fdf4","#15803d","#bbf7d0","📝 Fill in the Blank"), | |
| "vocabulary": ("#faf5ff","#6d28d9","#ddd6fe","📖 Vocabulary"), | |
| "comprehension": ("#fff1f2","#be123c","#fecdd3","📄 Reading Comprehension"), | |
| } | |
| bg,accent,border,label = cfg.get(exercise_type, ("#f8fafc","#1d4ed8","#e2e8f0","Exercise")) | |
| if exercise_type == "translation": | |
| store["ref"] = ex["en"]; store["display"] = ex["fr"]; store["error_type"] = "Semantic similarity — FR to EN translation" | |
| body = f"""<p style="font-size:13px;color:#475569;margin:0 0 10px;"> | |
| Translate the following sentence into English. / | |
| Traduisez la phrase suivante en anglais. | |
| </p> | |
| <div style="background:white;border-radius:8px;padding:16px;text-align:center; | |
| font-size:19px;font-weight:700;color:#1e3a8a;border:1px solid {border}; | |
| letter-spacing:0.2px;line-height:1.4;"> | |
| {ex['fr']} | |
| </div>""" | |
| elif exercise_type == "error_correction": | |
| store["ref"] = ex["correct"]; store["display"] = ex["wrong"]; store["error_type"] = ex["error_type"] | |
| body = f"""<p style="font-size:13px;color:#475569;margin:0 0 10px;"> | |
| Rewrite the sentence correctly, correcting all linguistic errors. / | |
| Réécrivez la phrase correctement en corrigeant toutes les erreurs linguistiques. | |
| </p> | |
| <div style="background:white;border-radius:8px;padding:16px;text-align:center; | |
| font-size:16px;color:#78350f;font-style:italic; | |
| border:2px dashed {accent};line-height:1.5;"> | |
| “{ex['wrong']}” | |
| </div>""" | |
| elif exercise_type == "fill_blank": | |
| store["ref"] = ex["answer"]; store["display"] = ex["display"]; store["error_type"] = ex["error_type"] | |
| body = f"""<p style="font-size:13px;color:#475569;margin:0 0 10px;"> | |
| Complete the sentence with the appropriate word or verb form. / | |
| Complétez la phrase avec le mot ou la forme verbale appropriée. | |
| </p> | |
| <div style="background:white;border-radius:8px;padding:16px;text-align:center; | |
| font-size:16px;font-weight:600;color:#1e293b;border:1px solid {border};line-height:1.5;"> | |
| {ex['display']} | |
| </div> | |
| <p style="font-size:12px;color:{accent};margin:8px 0 0;"> | |
| <b>Grammatical hint / Indice grammatical :</b> {ex['hint']} | |
| </p>""" | |
| elif exercise_type == "vocabulary": | |
| store["ref"] = ex["answer"]; store["display"] = ex["fr_def"]; store["error_type"] = ex["error_type"] | |
| body = f"""<p style="font-size:13px;color:#475569;margin:0 0 10px;"> | |
| Identify the English term corresponding to the definition below. / | |
| Identifiez le terme anglais correspondant à la définition ci-dessous. | |
| </p> | |
| <div style="background:white;border-radius:8px;padding:16px;text-align:center; | |
| font-size:15px;font-weight:600;color:#4c1d95;border:1px solid {border}; | |
| line-height:1.6;white-space:pre-line;"> | |
| {ex['fr_def']} | |
| </div> | |
| <p style="font-size:12px;color:{accent};margin:8px 0 0;"> | |
| <b>Related terms / Termes associés :</b> {ex['synonyms']} | |
| </p>""" | |
| else: | |
| store["ref"] = ex["answer"]; store["display"] = ex["passage"]; store["error_type"] = ex["error_type"] | |
| body = f"""<p style="font-size:13px;color:#475569;margin:0 0 10px;"> | |
| Read the following passage carefully and answer the question below. / | |
| Lisez attentivement le texte suivant et répondez à la question ci-dessous. | |
| </p> | |
| <div style="background:white;border-radius:8px;padding:14px;font-size:13px; | |
| color:#1e293b;line-height:1.8;border:1px solid {border};margin-bottom:10px;"> | |
| {ex['passage']} | |
| </div> | |
| <div style="background:{bg};border-radius:8px;padding:10px;font-size:14px; | |
| font-weight:700;color:{accent};white-space:pre-line;"> | |
| {ex['question']} | |
| </div>""" | |
| task_html = f""" | |
| <div style="background:{bg};border-left:5px solid {accent};border-radius:10px;padding:18px 20px;"> | |
| <div style="font-size:10px;font-weight:700;color:{accent};letter-spacing:1.2px;margin-bottom:12px;text-transform:uppercase;"> | |
| {label} — CEFR Level {level} | |
| </div> | |
| {body} | |
| </div>""" | |
| return task_html, "", gr.update(visible=False), gr.update(visible=False) | |
| def evaluate(user_answer): | |
| if not user_answer.strip() or not store["ref"]: | |
| return gr.update(visible=False), gr.update(visible=False) | |
| pct, g_kan, g_lin, cos_scores = compute_similarity(user_answer.strip(), store["ref"]) | |
| store["pct"]=pct; store["g_kan"]=g_kan; store["g_lin"]=g_lin; store["cos"]=cos_scores | |
| missing, extra, correct = analyze_errors(user_answer, store["ref"]) | |
| if pct >= 85: | |
| emoji,label = "🏆","Excellent" | |
| color,bg,border = "#166534","#f0fdf4","#bbf7d0" | |
| msg_en = "The response demonstrates high semantic accuracy and strong linguistic command." | |
| msg_fr = "La réponse témoigne d'une grande précision sémantique et d'une solide maîtrise linguistique." | |
| elif pct >= 70: | |
| emoji,label = "✅","Satisfactory / Satisfaisant" | |
| color,bg,border = "#1e40af","#eff6ff","#bfdbfe" | |
| msg_en = "The response is semantically adequate with minor lexical or structural deviations." | |
| msg_fr = "La réponse est sémantiquement adéquate avec des écarts lexicaux ou structurels mineurs." | |
| elif pct >= 50: | |
| emoji,label = "⚠️","Partially Correct / Partiellement correct" | |
| color,bg,border = "#92400e","#fffbeb","#fde68a" | |
| msg_en = "The response conveys the general idea but requires significant linguistic revision." | |
| msg_fr = "La réponse transmet l'idée générale mais nécessite une révision linguistique significative." | |
| else: | |
| emoji,label = "❌","Insufficient / Insuffisant" | |
| color,bg,border = "#991b1b","#fef2f2","#fecaca" | |
| msg_en = "The response deviates substantially from the reference. A thorough revision is required." | |
| msg_fr = "La réponse s'écarte substantiellement de la référence. Une révision approfondie est nécessaire." | |
| result_html = f""" | |
| <div style="font-family:'Segoe UI',Arial,sans-serif;font-size:14px;"> | |
| <div style="background:{bg};border:2px solid {border};border-radius:12px;padding:18px;margin-bottom:12px;"> | |
| <div style="font-size:9px;font-weight:700;color:{color};letter-spacing:1.5px;margin-bottom:10px;text-transform:uppercase;"> | |
| Section I — Semantic Similarity Score / Score de Similarité Sémantique | |
| </div> | |
| <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;"> | |
| <div style="display:flex;align-items:center;gap:10px;"> | |
| <span style="font-size:32px;">{emoji}</span> | |
| <div> | |
| <div style="font-size:18px;font-weight:800;color:{color};line-height:1.2;">{label}</div> | |
| <div style="font-size:11px;color:#6b7280;font-style:italic;margin-top:2px;"> | |
| Computed by TinyKAN-Distill — Kolmogorov–Arnold Network (32,080 parameters) | |
| </div> | |
| </div> | |
| </div> | |
| <div style="background:white;border-radius:10px;padding:8px 18px; | |
| border:2px solid {border};text-align:center;min-width:90px;"> | |
| <div style="font-size:40px;font-weight:900;color:{color};line-height:1;">{pct}%</div> | |
| <div style="font-size:10px;color:#9ca3af;margin-top:1px;">similarity</div> | |
| </div> | |
| </div> | |
| <div style="background:rgba(0,0,0,0.07);border-radius:99px;height:8px;margin-bottom:12px;"> | |
| <div style="background:{color};width:{min(pct,100)}%;height:8px;border-radius:99px;"></div> | |
| </div> | |
| <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;"> | |
| <div style="background:white;padding:10px 12px;border-radius:8px;border-left:3px solid #2563eb;"> | |
| <div style="font-size:9px;font-weight:700;color:#1d4ed8;letter-spacing:0.8px;margin-bottom:3px;">ENGLISH ASSESSMENT</div> | |
| <div style="font-size:12px;color:#1e293b;line-height:1.5;">{msg_en}</div> | |
| </div> | |
| <div style="background:white;padding:10px 12px;border-radius:8px;border-left:3px solid #16a34a;"> | |
| <div style="font-size:9px;font-weight:700;color:#15803d;letter-spacing:0.8px;margin-bottom:3px;">ÉVALUATION</div> | |
| <div style="font-size:12px;color:#1e293b;line-height:1.5;">{msg_fr}</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div style="background:white;border:1px solid #e2e8f0;border-radius:12px;padding:14px;margin-bottom:12px;"> | |
| <div style="font-size:9px;font-weight:700;color:#1e3a8a;letter-spacing:1.5px;margin-bottom:10px;text-transform:uppercase;"> | |
| Section II — Answer Comparison & Linguistic Analysis / Comparaison et Analyse Linguistique | |
| </div> | |
| <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:10px;"> | |
| <div style="background:#fef2f2;border-radius:8px;padding:10px;border:1px solid #fecaca;"> | |
| <div style="font-size:9px;font-weight:700;color:#dc2626;letter-spacing:0.8px;margin-bottom:4px;">LEARNER RESPONSE / RÉPONSE</div> | |
| <div style="font-size:13px;color:#1e293b;">{user_answer}</div> | |
| </div> | |
| <div style="background:#f0fdf4;border-radius:8px;padding:10px;border:1px solid #bbf7d0;"> | |
| <div style="font-size:9px;font-weight:700;color:#166534;letter-spacing:0.8px;margin-bottom:4px;">REFERENCE ANSWER / RÉPONSE DE RÉFÉRENCE</div> | |
| <div style="font-size:13px;color:#1e293b;font-style:italic;">{store['ref']}</div> | |
| </div> | |
| </div> | |
| <table style="width:100%;border-collapse:collapse;font-size:12px;"> | |
| <thead> | |
| <tr style="background:#334155;color:white;"> | |
| <th style="padding:7px 12px;text-align:left;font-weight:600;font-size:11px;">Category / Catégorie</th> | |
| <th style="padding:7px 12px;text-align:left;font-weight:600;font-size:11px;">Words / Mots</th> | |
| <th style="padding:7px 12px;text-align:center;font-weight:600;font-size:11px;">N</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr style="background:#f0fdf4;"> | |
| <td style="padding:7px 12px;border:1px solid #e9f5ea;color:#166534;font-weight:600;">✅ Correct vocabulary<br><span style="font-size:10px;font-weight:400;color:#6b7280;">Vocabulaire correct</span></td> | |
| <td style="padding:7px 12px;border:1px solid #e9f5ea;color:#15803d;font-style:italic;">{', '.join(sorted(correct)[:12]) if correct else '—'}</td> | |
| <td style="padding:7px 12px;border:1px solid #e9f5ea;text-align:center;font-weight:700;color:#166534;">{len(correct)}</td> | |
| </tr> | |
| <tr style="background:#fff8f8;"> | |
| <td style="padding:7px 12px;border:1px solid #fde8e8;color:#dc2626;font-weight:600;">❌ Missing words<br><span style="font-size:10px;font-weight:400;color:#6b7280;">Mots manquants</span></td> | |
| <td style="padding:7px 12px;border:1px solid #fde8e8;color:#dc2626;font-style:italic;">{', '.join(sorted(missing)[:12]) if missing else '—'}</td> | |
| <td style="padding:7px 12px;border:1px solid #fde8e8;text-align:center;font-weight:700;color:#dc2626;">{len(missing)}</td> | |
| </tr> | |
| <tr style="background:#fffdf0;"> | |
| <td style="padding:7px 12px;border:1px solid #fef3cd;color:#b45309;font-weight:600;">⚠️ Extraneous words<br><span style="font-size:10px;font-weight:400;color:#6b7280;">Mots en trop</span></td> | |
| <td style="padding:7px 12px;border:1px solid #fef3cd;color:#b45309;font-style:italic;">{', '.join(sorted(extra)[:12]) if extra else '—'}</td> | |
| <td style="padding:7px 12px;border:1px solid #fef3cd;text-align:center;font-weight:700;color:#b45309;">{len(extra)}</td> | |
| </tr> | |
| <tr style="background:#faf5ff;"> | |
| <td style="padding:7px 12px;border:1px solid #ede9fe;color:#6d28d9;font-weight:600;">🎯 Linguistic category<br><span style="font-size:10px;font-weight:400;color:#6b7280;">Catégorie d'erreur</span></td> | |
| <td style="padding:7px 12px;border:1px solid #ede9fe;color:#6d28d9;" colspan="2">{store['error_type']}</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div>""" | |
| gemini_fb = get_gemini_feedback( | |
| store["display"], user_answer, store["ref"], | |
| pct, store["level"], store["type"], store["error_type"] | |
| ) | |
| gemini_html = "" | |
| if gemini_fb: | |
| gemini_html = f""" | |
| <div style="font-family:'Segoe UI',Arial,sans-serif;background:white; | |
| border:1px solid #e2e8f0;border-radius:12px;padding:16px;"> | |
| <div style="font-size:9px;font-weight:700;color:#1e3a8a;letter-spacing:1.5px;margin-bottom:10px;text-transform:uppercase;"> | |
| Section III — AI Pedagogical Analysis / Analyse Pédagogique par Intelligence Artificielle | |
| </div> | |
| <div style="background:#f8fafc;border-radius:8px;padding:10px;margin-bottom:10px; | |
| font-size:11px;color:#64748b;border-left:3px solid #94a3b8;line-height:1.6;"> | |
| Generated by <b>Google Gemini 1.5 Flash</b> based on the semantic score | |
| computed by <b>TinyKAN-Distill</b> ({pct}%).<br> | |
| Généré par <b>Google Gemini 1.5 Flash</b> sur la base du score calculé | |
| par <b>TinyKAN-Distill</b> ({pct}%). | |
| </div> | |
| <div style="font-size:13px;color:#374151;line-height:1.9;white-space:pre-wrap;">{gemini_fb}</div> | |
| </div>""" | |
| pipeline_html = f""" | |
| <div style="font-family:'Segoe UI',Arial,sans-serif;margin-top:14px;padding-top:14px; | |
| border-top:1px solid #e2e8f0;"> | |
| <div style="font-size:9px;font-weight:700;color:#1e3a8a;letter-spacing:1.5px;margin-bottom:10px;text-transform:uppercase;"> | |
| Last Computation Pipeline / Dernier Pipeline de Calcul | |
| </div> | |
| <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:10px;"> | |
| <div style="text-align:center;background:#eff6ff;padding:10px;border-radius:8px;border:1px solid #bfdbfe;"> | |
| <div style="font-size:10px;color:#64748b;margin-bottom:3px;">MiniLM-L6-v2 (22M)</div> | |
| <div style="font-size:20px;font-weight:700;color:#1d4ed8;">{cos_scores[0]}%</div> | |
| <div style="font-size:10px;color:#94a3b8;">cosine similarity</div> | |
| </div> | |
| <div style="text-align:center;background:#faf5ff;padding:10px;border-radius:8px;border:1px solid #ddd6fe;"> | |
| <div style="font-size:10px;color:#64748b;margin-bottom:3px;">MPNet-base-v2 (110M)</div> | |
| <div style="font-size:20px;font-weight:700;color:#7c3aed;">{cos_scores[1]}%</div> | |
| <div style="font-size:10px;color:#94a3b8;">cosine similarity</div> | |
| </div> | |
| <div style="text-align:center;background:#f0f9ff;padding:10px;border-radius:8px;border:1px solid #bae6fd;"> | |
| <div style="font-size:10px;color:#64748b;margin-bottom:3px;">RoBERTa-STS (125M)</div> | |
| <div style="font-size:20px;font-weight:700;color:#0891b2;">{cos_scores[2]}%</div> | |
| <div style="font-size:10px;color:#94a3b8;">cosine similarity</div> | |
| </div> | |
| </div> | |
| <div style="background:#f1f5f9;border-radius:8px;padding:10px;font-size:12px;color:#374151; | |
| display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:6px;"> | |
| <span>TinyKAN fusion score: <b style="color:#1d4ed8;font-size:15px;">{pct}%</b></span> | |
| <span style="color:#64748b;"> | |
| KAN spline branch: <b>{g_kan}%</b> | Linear bypass: <b>{g_lin}%</b> | |
| </span> | |
| </div> | |
| </div>""" | |
| return gr.update(visible=True), gr.update(visible=True), result_html, gemini_html, pipeline_html | |
| # ── CORRECTION ICI : 7 → 9 datasets + ajout SICK-R et STS22 ── | |
| TECH_HTML = """ | |
| <div style="font-family:'Segoe UI',Arial,sans-serif;padding:4px 0;"> | |
| <div style="font-size:13px;font-weight:700;color:#1e293b;margin-bottom:4px;"> | |
| Multi-Dataset Evaluation — TinyKAN-Distill (Pearson r / Spearman ρ) | |
| </div> | |
| <div style="font-size:12px;color:#64748b;margin-bottom:12px;font-style:italic;"> | |
| Evaluated on 9 STS benchmarks using a 9-dimensional cosine-similarity pipeline. | |
| Évalué sur 9 benchmarks STS avec un pipeline cosine à 9 dimensions. | |
| </div> | |
| <table style="width:100%;border-collapse:collapse;font-size:12px;margin-bottom:14px;"> | |
| <thead> | |
| <tr style="background:#1e3a8a;color:white;"> | |
| <th style="padding:9px 14px;text-align:left;font-weight:600;">Dataset</th> | |
| <th style="padding:9px 14px;text-align:center;font-weight:600;">Pearson r ↑</th> | |
| <th style="padding:9px 14px;text-align:center;font-weight:600;">95% CI</th> | |
| <th style="padding:9px 14px;text-align:center;font-weight:600;">Spearman ρ ↑</th> | |
| <th style="padding:9px 14px;text-align:center;font-weight:600;">N pairs</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| <tr style="background:#eff6ff;font-weight:700;"> | |
| <td style="padding:8px 14px;border:1px solid #e2e8f0;">⭐ STS-Benchmark <span style="font-size:10px;font-weight:400;">(primary)</span></td> | |
| <td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;color:#1d4ed8;">0.8791</td> | |
| <td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">[0.8662, 0.8909]</td> | |
| <td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;color:#1d4ed8;">0.8752</td> | |
| <td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">1,379</td> | |
| </tr> | |
| <tr><td style="padding:8px 14px;border:1px solid #e2e8f0;">STS-2012</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">0.8813</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">[0.8699, 0.8911]</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">0.7949</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">3,108</td></tr> | |
| <tr style="background:#f8fafc;"><td style="padding:8px 14px;border:1px solid #e2e8f0;">STS-2013</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">0.9046</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">[0.8949, 0.9149]</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">0.8936</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">1,500</td></tr> | |
| <tr><td style="padding:8px 14px;border:1px solid #e2e8f0;">STS-2014</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">0.9050</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">[0.8984, 0.9116]</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">0.8815</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">3,750</td></tr> | |
| <tr style="background:#f8fafc;"><td style="padding:8px 14px;border:1px solid #e2e8f0;">STS-2015 🏆</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;color:#166534;font-weight:700;">0.9137</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">[0.9063, 0.9207]</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;color:#166534;font-weight:700;">0.9158</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">3,000</td></tr> | |
| <tr><td style="padding:8px 14px;border:1px solid #e2e8f0;">STS-2016</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">0.8541</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">[0.8386, 0.8690]</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">0.8582</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">1,186</td></tr> | |
| <tr style="background:#f8fafc;"><td style="padding:8px 14px;border:1px solid #e2e8f0;">STS-2017 (en-en)</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">0.8917</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">[0.8600, 0.9164]</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">0.8837</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">250</td></tr> | |
| <tr><td style="padding:8px 14px;border:1px solid #e2e8f0;">SICK-R</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">0.8315</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">[0.8244, 0.8381]</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">0.8066</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">9,927</td></tr> | |
| <tr style="background:#fff8e1;"><td style="padding:8px 14px;border:1px solid #e2e8f0;">STS22 (en) <span style="font-size:10px;color:#b45309;">harder benchmark</span></td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;color:#b45309;">0.6706</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">[0.5675, 0.7638]</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;color:#b45309;">0.6557</td><td style="padding:8px 14px;border:1px solid #e2e8f0;text-align:center;">197</td></tr> | |
| </tbody> | |
| </table> | |
| <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:12px;"> | |
| <div style="background:#f8fafc;border-radius:10px;padding:12px;border:1px solid #e2e8f0;"> | |
| <div style="font-size:10px;font-weight:700;color:#1e3a8a;margin-bottom:8px;letter-spacing:0.8px;">TINYKAN ARCHITECTURE</div> | |
| <table style="width:100%;font-size:12px;"> | |
| <tr><td style="padding:3px 0;color:#64748b;">Model type</td><td style="font-weight:600;">Hybrid KAN (spline + linear + gate)</td></tr> | |
| <tr><td style="padding:3px 0;color:#64748b;">Parameters</td><td style="font-weight:600;">32,080</td></tr> | |
| <tr><td style="padding:3px 0;color:#64748b;">Input features</td><td style="font-weight:600;">9 (cosine similarity)</td></tr> | |
| <tr><td style="padding:3px 0;color:#64748b;">KAN width</td><td style="font-weight:600;">[9 → 16 → 8 → 1]</td></tr> | |
| <tr><td style="padding:3px 0;color:#64748b;">Compression ratio</td><td style="font-weight:600;">8,012× vs. teacher ensemble</td></tr> | |
| <tr><td style="padding:3px 0;color:#64748b;">Distillation</td><td style="font-weight:600;">α = 0.7 | T = 3.0</td></tr> | |
| </table> | |
| </div> | |
| <div style="background:#f8fafc;border-radius:10px;padding:12px;border:1px solid #e2e8f0;"> | |
| <div style="font-size:10px;font-weight:700;color:#1e3a8a;margin-bottom:8px;letter-spacing:0.8px;">TEACHER ENSEMBLE</div> | |
| <table style="width:100%;font-size:12px;"> | |
| <tr><td style="padding:3px 0;color:#64748b;">Teacher 1</td><td style="font-weight:600;">all-MiniLM-L6-v2 (22M)</td></tr> | |
| <tr><td style="padding:3px 0;color:#64748b;">Teacher 2</td><td style="font-weight:600;">all-mpnet-base-v2 (110M)</td></tr> | |
| <tr><td style="padding:3px 0;color:#64748b;">Teacher 3</td><td style="font-weight:600;">stsb-roberta-base (125M)</td></tr> | |
| <tr><td style="padding:3px 0;color:#64748b;">Total capacity</td><td style="font-weight:600;">257M parameters</td></tr> | |
| <tr><td style="padding:3px 0;color:#64748b;">Weighting</td><td style="font-weight:600;">Inverse-variance</td></tr> | |
| <tr><td style="padding:3px 0;color:#64748b;">Ensemble Pearson r</td><td style="font-weight:600;color:#166534;">0.9146</td></tr> | |
| </table> | |
| </div> | |
| </div> | |
| </div>""" | |
| with gr.Blocks(title="English Learning Assistant — TinyKAN-Distill", | |
| theme=gr.themes.Soft()) as demo: | |
| gr.HTML(""" | |
| <div style="background:linear-gradient(135deg,#1e3a8a 0%,#1d4ed8 60%,#1e40af 100%); | |
| padding:22px 30px;border-radius:14px;margin-bottom:18px; | |
| box-shadow:0 4px 20px rgba(29,78,216,0.25);"> | |
| <div style="display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px;"> | |
| <div> | |
| <h1 style="margin:0;font-size:24px;font-weight:900;color:white;letter-spacing:-0.3px;"> | |
| 🎓 English Learning Assistant | |
| </h1> | |
| <p style="margin:5px 0 2px;font-size:12px;color:#bfdbfe;font-style:italic;"> | |
| Intelligent language assessment via Ensemble Knowledge Distillation | |
| into Kolmogorov–Arnold Networks (TinyKAN-Distill) | |
| </p> | |
| <p style="margin:0;font-size:11px;color:#93c5fd;"> | |
| Developed by <b>Mouna Khlifi</b> · University of Kairouan, Tunisia · 2026 | |
| </p> | |
| </div> | |
| <div style="display:flex;flex-direction:column;align-items:flex-end;gap:7px;"> | |
| <div style="display:flex;gap:5px;flex-wrap:wrap;justify-content:flex-end;"> | |
| <span style="background:rgba(255,255,255,0.15);color:white;padding:3px 9px;border-radius:99px;font-size:10px;font-weight:600;">🧠 TinyKAN-Distill</span> | |
| <span style="background:rgba(255,255,255,0.15);color:white;padding:3px 9px;border-radius:99px;font-size:10px;font-weight:600;">🤖 Gemini 1.5 Flash</span> | |
| <span style="background:rgba(255,255,255,0.15);color:white;padding:3px 9px;border-radius:99px;font-size:10px;font-weight:600;">📚 CEFR A1–C2</span> | |
| <span style="background:rgba(255,255,255,0.15);color:white;padding:3px 9px;border-radius:99px;font-size:10px;font-weight:600;">🌍 EN / FR</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=1, min_width=250): | |
| gr.HTML('<div style="font-size:9px;font-weight:700;color:#1e40af;letter-spacing:1.5px;margin-bottom:8px;text-transform:uppercase;">Learner Profile / Profil de l\'Apprenant</div>') | |
| learner_name = gr.Textbox(label="Full name / Nom complet", placeholder="e.g. Mouna Khlifi") | |
| level = gr.Dropdown( | |
| ["A1 — Beginner / Débutant","A2 — Elementary / Élémentaire", | |
| "B1 — Intermediate / Intermédiaire","B2 — Upper-Intermediate / Intermédiaire supérieur", | |
| "C1 — Advanced / Avancé","C2 — Proficient / Maîtrise"], | |
| value="B1 — Intermediate / Intermédiaire", | |
| label="CEFR Proficiency Level / Niveau de compétence CEFR") | |
| exercise_type = gr.Dropdown( | |
| choices=[ | |
| ("🔤 FR→EN Translation / Traduction français vers anglais", "translation"), | |
| ("✏️ Error Correction / Correction d'erreurs linguistiques", "error_correction"), | |
| ("📝 Fill in the Blank / Complétez la phrase", "fill_blank"), | |
| ("📖 Vocabulary — Term Identification / Identification de termes", "vocabulary"), | |
| ("📄 Reading Comprehension / Compréhension de l'écrit", "comprehension"), | |
| ], | |
| value="translation", | |
| label="Exercise Type / Type d'exercice") | |
| new_btn = gr.Button("▶ Start Exercise / Commencer l'exercice", variant="primary", size="lg") | |
| gr.HTML(""" | |
| <div style="margin-top:12px;padding:12px;background:#f8fafc;border-radius:10px; | |
| border:1px solid #e2e8f0;font-size:12px;color:#374151;line-height:1.8;"> | |
| <div style="font-size:9px;font-weight:700;color:#1e40af;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase;">Assessment Scale / Barème</div> | |
| 🏆 ≥ 85% — Excellent<br> | |
| ✅ ≥ 70% — Satisfactory / Satisfaisant<br> | |
| ⚠️ ≥ 50% — Partially correct / Partiellement correct<br> | |
| ❌ < 50% — Insufficient / Insuffisant<br><br> | |
| <div style="font-size:9px;font-weight:700;color:#1e40af;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase;">AI Scoring System</div> | |
| Similarity score: <b>TinyKAN-Distill</b><br> | |
| Pedagogical feedback: <b>Gemini 1.5 Flash</b> | |
| </div> | |
| """) | |
| with gr.Column(scale=2): | |
| gr.HTML('<div style="font-size:9px;font-weight:700;color:#1e40af;letter-spacing:1.5px;margin-bottom:8px;text-transform:uppercase;">Exercise / Exercice</div>') | |
| exercise_html = gr.HTML(""" | |
| <div style="background:#f8fafc;border:2px dashed #cbd5e1;border-radius:10px; | |
| padding:28px;text-align:center;color:#94a3b8;font-size:14px;line-height:1.9;"> | |
| Select your proficiency level and exercise type,<br>then click | |
| <b style="color:#1d4ed8;">Start Exercise</b> to begin.<br> | |
| <span style="font-size:12px;">Sélectionnez votre niveau et le type d'exercice,<br> | |
| puis cliquez sur <b style="color:#1d4ed8;">Commencer l'exercice</b>.</span> | |
| </div>""") | |
| gr.HTML('<div style="font-size:9px;font-weight:700;color:#1e40af;letter-spacing:1.5px;margin:10px 0 6px;text-transform:uppercase;">Your Answer / Votre Réponse</div>') | |
| answer_box = gr.Textbox( | |
| label="Write your answer below / Rédigez votre réponse ci-dessous", | |
| placeholder="Enter your answer here / Saisissez votre réponse ici...", lines=3) | |
| with gr.Row(): | |
| submit_btn = gr.Button("✅ Submit Answer / Soumettre la réponse", variant="primary", scale=2) | |
| next_btn = gr.Button("⏭ Next / Suivant", variant="secondary", scale=1, visible=False) | |
| result_col = gr.Column(visible=False) | |
| with result_col: | |
| gr.HTML('<div style="font-size:9px;font-weight:700;color:#1e40af;letter-spacing:1.5px;margin:14px 0 8px;text-transform:uppercase;">Assessment Results / Résultats d\'Évaluation</div>') | |
| result_html = gr.HTML("") | |
| gemini_col = gr.Column(visible=False) | |
| with gemini_col: | |
| gemini_html = gr.HTML("") | |
| with gr.Accordion("📊 Technical Details & Multi-Dataset Evaluation / Détails Techniques & Évaluation Multi-Dataset", open=False): | |
| gr.HTML(TECH_HTML) | |
| pipeline_html = gr.HTML("") | |
| gr.HTML(""" | |
| <div style="text-align:center;margin-top:14px;padding:12px; | |
| font-size:11px;color:#94a3b8;border-top:1px solid #e5e7eb;"> | |
| © 2026 <b>Mouna Khlifi</b> — University of Kairouan, Tunisia · | |
| TinyKAN-Distill: Ensemble Knowledge Distillation into Kolmogorov–Arnold Networks · | |
| <a href="https://github.com/Khelifimouna/Tiny-KAN-Distill" target="_blank" | |
| style="color:#1d4ed8;text-decoration:none;font-weight:600;">GitHub Repository →</a> | |
| </div> | |
| """) | |
| def parse_level(lv): | |
| return lv.split(" — ")[0].strip() if " — " in lv else lv.strip() | |
| new_btn.click( | |
| lambda lv, et, nm: new_exercise(parse_level(lv), et, nm), | |
| inputs=[level, exercise_type, learner_name], | |
| outputs=[exercise_html, answer_box, result_col, gemini_col]) | |
| submit_btn.click( | |
| evaluate, inputs=[answer_box], | |
| outputs=[result_col, gemini_col, result_html, gemini_html, pipeline_html]) | |
| next_btn.click( | |
| lambda lv, et, nm: new_exercise(parse_level(lv), et, nm), | |
| inputs=[level, exercise_type, learner_name], | |
| outputs=[exercise_html, answer_box, result_col, gemini_col]) | |
| demo.launch() |