From e3797dfa01bb45623bb1d1972e7094604b471681 Mon Sep 17 00:00:00 2001 From: Ishi Jain Date: Tue, 15 Sep 2026 18:23:38 +0530 Subject: [PATCH 1/2] feat: add adaptive AI mentor system --- client/src/main.jsx | 926 ++++++++++++++++++---- server/package-lock.json | 461 +++++++++++ server/package.json | 1 + server/src/index.js | 4 +- server/src/routes/sessions.js | 230 +++++- server/src/services/adaptiveDifficulty.js | 57 ++ server/src/services/adaptiveStrategy.js | 48 ++ server/src/services/aiMentor.js | 316 ++++++++ server/src/services/learnerContext.js | 87 ++ server/src/services/learnerProfile.js | 74 ++ server/src/services/learningEngine.js | 653 ++++++++++++++- server/src/services/mentorContext.js | 43 + server/src/services/mentorValidator.js | 26 + 13 files changed, 2723 insertions(+), 203 deletions(-) create mode 100644 server/src/services/adaptiveDifficulty.js create mode 100644 server/src/services/adaptiveStrategy.js create mode 100644 server/src/services/aiMentor.js create mode 100644 server/src/services/learnerContext.js create mode 100644 server/src/services/learnerProfile.js create mode 100644 server/src/services/mentorContext.js create mode 100644 server/src/services/mentorValidator.js diff --git a/client/src/main.jsx b/client/src/main.jsx index ef3da3a9..8e8cf92a 100644 --- a/client/src/main.jsx +++ b/client/src/main.jsx @@ -1,5 +1,13 @@ -import React, { useEffect, useMemo, useState } from 'react'; -import { createRoot } from 'react-dom/client'; +import React, { + useEffect, + useMemo, + useState +} from 'react'; + +import { + createRoot +} from 'react-dom/client'; + import { Brain, ChartNoAxesCombined, @@ -13,132 +21,387 @@ import { Send, Sparkles } from 'lucide-react'; + import './styles.css'; -const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:5000/api'; +const API_URL = + import.meta.env.VITE_API_URL || + 'http://localhost:5000/api'; async function api(path, options) { - const response = await fetch(`${API_URL}${path}`, { - headers: { 'Content-Type': 'application/json' }, - ...options - }); - if (!response.ok) throw new Error(await response.text()); + const response = await fetch( + `${API_URL}${path}`, + { + headers: { + 'Content-Type': 'application/json' + }, + ...options + } + ); + + if (!response.ok) { + throw new Error( + await response.text() + ); + } + return response.json(); } function App() { - const [scenarios, setScenarios] = useState([]); - const [selected, setSelected] = useState(null); - const [sessions, setSessions] = useState([]); - const [analytics, setAnalytics] = useState(null); - const [roadmap, setRoadmap] = useState([]); - const [filters, setFilters] = useState({ q: '', difficulty: '', concept: '' }); - const [form, setForm] = useState({ learnerName: 'Guest learner', reasoning: '', promptText: '', reflection: '' }); - const [activeResult, setActiveResult] = useState(null); - const [loading, setLoading] = useState(true); - const [submitting, setSubmitting] = useState(false); - - const concepts = useMemo(() => [...new Set(scenarios.flatMap((scenario) => scenario.concepts || []))].sort(), [scenarios]); + const [scenarios, setScenarios] = + useState([]); + + const [selected, setSelected] = + useState(null); + + const [sessions, setSessions] = + useState([]); + + const [analytics, setAnalytics] = + useState(null); + + const [roadmap, setRoadmap] = + useState([]); + + const [filters, setFilters] = + useState({ + q: '', + difficulty: '', + concept: '' + }); + + const [form, setForm] = + useState({ + learnerName: 'Guest learner', + reasoning: '', + promptText: '', + reflection: '' + }); + + const [activeResult, setActiveResult] = + useState(null); + + const [loading, setLoading] = + useState(true); + + const [loadError, setLoadError] = + useState(null); + + const [submitting, setSubmitting] = + useState(false); + + const concepts = useMemo( + () => + [ + ...new Set( + scenarios.flatMap( + (scenario) => + scenario.concepts || [] + ) + ) + ].sort(), + [scenarios] + ); async function refresh() { - const params = new URLSearchParams(Object.entries(filters).filter(([, value]) => value)); - const [scenarioData, sessionData, analyticsData, roadmapData] = await Promise.all([ - api(`/scenarios?${params}`), - api('/sessions'), - api('/analytics'), - api('/roadmap') - ]); - setScenarios(scenarioData); - setSessions(sessionData); - setAnalytics(analyticsData); - setRoadmap(roadmapData); - setSelected((current) => current || scenarioData[0] || null); - setLoading(false); + const params = + new URLSearchParams( + Object.entries(filters).filter( + ([, value]) => value + ) + ); + + try { + const [ + scenarioData, + sessionData, + analyticsData, + roadmapData + ] = await Promise.all([ + api(`/scenarios?${params}`), + api('/sessions'), + api('/analytics'), + api('/roadmap') + ]); + + setScenarios(scenarioData); + setSessions(sessionData); + setAnalytics(analyticsData); + setRoadmap(roadmapData); + + setSelected( + (current) => + current || + scenarioData[0] || + null + ); + + setLoadError(null); + } catch (error) { + console.error( + 'PyBe API loading failed:', + error + ); + + setLoadError( + error.message || + 'Unable to connect to PyBe API' + ); + } finally { + setLoading(false); + } } useEffect(() => { - refresh().catch(console.error); - }, [filters.q, filters.difficulty, filters.concept]); + setLoading(true); + refresh(); + }, [ + filters.q, + filters.difficulty, + filters.concept + ]); async function submitSession(event) { event.preventDefault(); - if (!selected || !form.reasoning.trim()) return; + + if ( + !selected || + !form.reasoning.trim() + ) { + return; + } + setSubmitting(true); + setLoadError(null); + try { - const result = await api('/sessions', { - method: 'POST', - body: JSON.stringify({ ...form, scenarioId: selected._id }) - }); + const result = + await api('/sessions', { + method: 'POST', + body: JSON.stringify({ + ...form, + scenarioId: + selected._id + }) + }); + setActiveResult(result); - setForm({ ...form, reasoning: '', promptText: '', reflection: '' }); + + setForm({ + ...form, + reasoning: '', + promptText: '', + reflection: '' + }); + await refresh(); + } catch (error) { + console.error( + 'Session submission failed:', + error + ); + + setLoadError( + error.message || + 'Unable to submit session' + ); } finally { setSubmitting(false); } } - if (loading) return
Loading PyBe...
; + if (loading) { + return ( +
+ Loading PyBe... +
+ ); + } + + if (loadError) { + return ( +
+

+ Unable to load PyBe +

+ +

{loadError}

+ +

+ Make sure the backend is + running on port 5000. +

+ + +
+ ); + } return (
-

AI-native learning journey

-

Learn Python by reasoning through real situations first.

+

+ AI-native learning journey +

+ +

+ Learn Python by reasoning + through real situations first. +

+
- {analytics?.scenarioCount || 0}Scenarios - {analytics?.sessionCount || 0}Sessions - {analytics?.averagePromptScore || 0}Prompt score + + {analytics?.scenarioCount || 0} + + Scenarios + + + + + {analytics?.sessionCount || 0} + + Sessions + + + + + {analytics?.averagePromptScore || 0} + + Prompt score + +
@@ -146,40 +409,90 @@ function App() {
-

{selected?.title}

+ +

+ {selected?.title} +

-

{selected?.context}

+ +

+ {selected?.context} +

+
- {selected?.objectives.map((item) => {item})} + {selected?.objectives.map( + (item) => ( + + {item} + + ) + )}
-
+ +