import backend import gradio as gr import re, os, random, json, requests, math import pandas as pd from collections import defaultdict from utilit import skills as all_skills from groq import Groq OLLAMA_URL = "http://localhost:11434/api/chat" MODEL = "qwen2.5:1.5b" HIERARCHY = { "Arithmetic Operations": {1: ["whole numbers", "ordering", "number line"], 2: ["integers"], 3: ["decimals"], 4: ["order of operations"]}, "Fractions Decimals Percents": {1: ["equivalent fractions", "conversion"], 2: ["fraction", "decimal"], 3: ["mixed fractions"], 4: ["percent", "ratio", "rate"]}, "Algebra Basics": {1: ["expression", "terms"], 2: ["like terms", "distributive"], 3: ["simplifying"], 4: ["polynomial"]}, "Equations and Inequalities": {1: ["one step", "two step"], 2: ["solving"], 3: ["multi", "more than two"], 4: ["inequality"]}, "Linear Functions": {1: ["pattern", "table"], 2: ["slope"], 3: ["graph"], 4: ["write linear equation", "from situation"]}, "Systems of Equations": {3: ["graphing"], 4: ["substitution", "system"]}, "Geometry": {1: ["angles", "triangle", "polygon"], 2: ["perimeter", "area"], 3: ["volume"], 4: ["surface area", "3d"]}, "Statistics": {1: ["mean", "median", "mode"], 2: ["range"], 3: ["plot", "histogram"], 4: ["scatter", "sampling"]}, "Probability": {1: ["single"], 2: ["venn"], 3: ["two"], 4: ["combinatorics", "counting"]}, "Advanced Algebra": {2: ["factoring"], 3: ["quadratic"], 4: ["quadratic formula"]} } CLUSTERS = { "Arithmetic Operations": ["addition", "subtraction", "multiplication", "division", "order of operations", "computation", "estimation"], "Fractions Decimals Percents": ["fraction", "decimal", "percent", "ratio", "rate"], "Number Theory": ["prime", "factor", "multiple", "divisibility", "gcf", "lcm", "odd", "even"], "Algebra Basics": ["expression", "like terms", "distributive", "polynomial", "simplifying"], "Equations and Inequalities": ["equation", "inequality", "solving", "variable"], "Linear Functions": ["linear", "slope", "y-intercept", "graph", "table"], "Systems of Equations": ["system"], "Geometry": ["angle", "triangle", "polygon", "circle", "perimeter", "area", "volume", "surface area", "prism", "cone", "sphere"], "Statistics": ["mean", "median", "mode", "range", "plot", "histogram", "scatter"], "Probability": ["probability", "venn", "combinatorics", "counting"], "Transformations": ["rotation", "reflection", "translation", "symmetry"], "Coordinate Geometry": ["coordinate", "distance", "midpoint"], "Advanced Algebra": ["quadratic", "factoring", "trinomials"] } SKILL_VIDEOS = { "whole numbers": "https://www.youtube.com/watch?v=m94WTzp14SA", "ordering": "https://www.youtube.com/watch?v=0_fC9Lsn7-8", "number line": "https://www.youtube.com/watch?v=6u3pU9V7NoQ", "integers": "https://www.youtube.com/watch?v=vNguZreVv_g", "decimals": "https://www.youtube.com/watch?v=kgv_UnE_iXQ", "order of operations": "https://www.youtube.com/watch?v=dAgfnK528RA", "equivalent fractions": "https://www.youtube.com/watch?v=qcHHbaKo_7E", "conversion": "https://www.youtube.com/watch?v=do_IbHId2Os", "fraction": "https://www.youtube.com/watch?v=CA9Xp80ZfIs", "decimal": "https://www.youtube.com/watch?v=9VvU6Sow064", "mixed fractions": "https://www.youtube.com/watch?v=1xuf6Z40Y8U", "percent": "https://www.youtube.com/watch?v=JeVSmq1Nrpw", "ratio": "https://www.youtube.com/watch?v=HpdMJaKa9P0", "rate": "https://www.youtube.com/watch?v=LIYfbe7_EaA", "expression": "https://www.youtube.com/watch?v=LpUeZ8T_6mE", "terms": "https://www.youtube.com/watch?v=0AfS8vS4Zks", "like terms": "https://www.youtube.com/watch?v=H_N1_Z9_X7Y", "distributive": "https://www.youtube.com/watch?v=v-6MShC82ow", "simplifying": "https://www.youtube.com/watch?v=3S9S8p_UuU4", "polynomial": "https://www.youtube.com/watch?v=ffLLmV4mZwU", "one step": "https://www.youtube.com/watch?v=L0_K895874A", "two step": "https://www.youtube.com/watch?v=LDIiYKYvvdA", "solving": "https://www.youtube.com/watch?v=bAerID24QJ0", "multi": "https://www.youtube.com/watch?v=f15zA0PhSek", "more than two": "https://www.youtube.com/watch?v=13YFOnR_kio", "inequality": "https://www.youtube.com/watch?v=njpSAtIDZ30", "pattern": "https://www.youtube.com/watch?v=8mGeU_uIs9I", "table": "https://www.youtube.com/watch?v=7u_X6X8tU-A", "slope": "https://www.youtube.com/watch?v=v8VSDg_WQlA", "graph": "https://www.youtube.com/watch?v=f58_XvY9o30", "write linear equation": "https://www.youtube.com/watch?v=eHPTyYbNmxw", "from situation": "https://www.youtube.com/watch?v=m682S8xUuYg", "graphing": "https://www.youtube.com/watch?v=S0_qX4VJhMQ", "substitution": "https://www.youtube.com/watch?v=zsD965-X-Vw", "system": "https://www.youtube.com/watch?v=fNk_zzaMoBA", "angles": "https://www.youtube.com/watch?v=DGKwdHMiqCg", "triangle": "https://www.youtube.com/watch?v=mLeNaZcy-hE", "polygon": "https://www.youtube.com/watch?v=IaoZhhx_I9s", "perimeter": "https://www.youtube.com/watch?v=AAY1bsazcgM", "area": "https://www.youtube.com/watch?v=xCdxURXMdFY", "volume": "https://www.youtube.com/watch?v=qJwecTgce6c", "surface area": "https://www.youtube.com/watch?v=uK5E_T6fX7c", "3d": "https://www.youtube.com/watch?v=ZqzAOZasN9A", "mean": "https://www.youtube.com/watch?v=B1HEzQu68tY", "median": "https://www.youtube.com/watch?v=B1HEzQu68tY", "mode": "https://www.youtube.com/watch?v=B1HEzQu68tY", "range": "https://www.youtube.com/watch?v=B1HEzQu68tY", "plot": "https://www.youtube.com/watch?v=mH_S_zT3qH0", "histogram": "https://www.youtube.com/watch?v=gY-mreidV-4", "scatter": "https://www.youtube.com/watch?v=pGZ37A47nI0", "sampling": "https://www.youtube.com/watch?v=pTuj57uXWlk", "single": "https://www.youtube.com/watch?v=KzfWUEJjG18", "venn": "https://www.youtube.com/watch?v=XdfS2-W8WnU", "two": "https://www.youtube.com/watch?v=Y-XU9rL-pY0", "combinatorics": "https://www.youtube.com/watch?v=Tr6Yp3_78uY", "counting": "https://www.youtube.com/watch?v=O129Sscf6lE", "factoring": "https://www.youtube.com/watch?v=f_Vp_74p_G8", "quadratic": "https://www.youtube.com/watch?v=6QYatS7vV0o", "quadratic formula": "https://www.youtube.com/watch?v=EeVqtpuGgu8" } hierarchy_skills_list = [] for domain_data in HIERARCHY.values(): for skill_group in domain_data.values(): hierarchy_skills_list.extend(skill_group) css = """ body, .gradio-container { font-size: 16px; } """ groq_key = os.getenv("GROQ_KEY") backend.init_db() client = Groq(api_key=groq_key) model_id = "llama-3.3-70b-versatile" session_skills = [] def assign_cluster(skill): skill_lower = str(skill).lower() for cluster, keywords in CLUSTERS.items(): if any(keyword in skill_lower for keyword in keywords): return cluster return "Other" def assign_hierarchy(skill): skill_lower = str(skill).lower() for domain, levels in HIERARCHY.items(): for level, keywords in levels.items(): if any(keyword in skill_lower for keyword in keywords): return domain, level return "Other", -1 def find_most_similar_prediction(current_session_skills): all_logs = backend.get_all_logs() if not all_logs: return [], "Database is empty." student_histories = defaultdict(list) for user_id, skill in all_logs: student_histories[str(user_id)].append(str(skill)) if not student_histories: return [], "No student data available." random_student = random.choice(list(student_histories.keys())) selected_history = student_histories[random_student][-30:] formatted_history = [] for s in selected_history: domain, level = assign_hierarchy(s) formatted_history.append(f"(skill={s}, cluster={assign_cluster(s)}, level={level})") prompt = f"""You are a student knowledge model. Reference Student history: {", ".join(formatted_history)} The current user has expressed difficulty with: {", ".join(current_session_skills)} Available hierarchy skills: {", ".join(hierarchy_skills_list)} Predict the NEXT 3 most likely skills (ranked) for the current user. Reply ONLY with JSON: {{"skills": ["Skill_A", "Skill_B", "Skill_C"]}}""" try: response = requests.post( OLLAMA_URL, json={ "model": MODEL, "messages": [{"role": "user", "content": prompt}], "stream": False, "format": "json" }, timeout=15 ) response.raise_for_status() raw_data = response.json() text_content = raw_data.get("message", {}).get("content", "{}") parsed = json.loads(text_content) preds = parsed.get("skills", []) for i, skill in enumerate(preds, 1): print(f"Rank {i}: {skill}") return preds, f"Random match based on Student ID: {random_student}" except Exception as e: print(f"Detailed LLM Error: {e}") return [], f"LLM processing error: {str(e)}" def get_youtube_id(url): pattern = r'(?:https?://)?(?:www\.)?(?:youtube\.com/(?:watch\?v=|embed/)|youtu\.be/)([a-zA-Z0-9_-]{11})' match = re.search(pattern, url) return match.group(1) if match else None def display_video_and_predict(): preds, info_msg = find_most_similar_prediction(session_skills) print(f"Final Output: {info_msg} Recommendations: {preds}") if not preds: return "
No recommendation available.
", f"### Personalized Path\n{info_msg}" main_skill = preds[0] main_url = SKILL_VIDEOS.get(main_skill, "https://www.youtube.com/watch?v=WUvTyaaNkzM") video_id = get_youtube_id(main_url) video_html = f'''Video will appear here.
") prediction_output = gr.Markdown() change_video = gr.Button("Get Video Lesson & Recommendation", variant="primary") btn_l.click( fn=handle_login, inputs=[u, p], outputs=[auth_block, main_block, out_l, user_id_state] ) send_btn.click(respond, [msg, chatbot, user_id_state], [msg, chatbot]) msg.submit(respond, [msg, chatbot, user_id_state], [msg, chatbot]) clear_btn.click(lambda: ([], reset_skills()), None, chatbot) change_video.click(fn=display_video_and_predict, outputs=[video_output, prediction_output]) logout_btn.click(lambda: (gr.update(visible=True), gr.update(visible=False), "", None), None, [auth_block, main_block, out_l, user_id_state]) demo.launch()