diff --git a/backend/ai.py b/backend/ai.py index 2d3c5c0..e097080 100644 --- a/backend/ai.py +++ b/backend/ai.py @@ -204,3 +204,24 @@ async def generate_flashcards(topic: str, num_cards: int = 8) -> list[dict]: {"front": str(it.get("front", "")), "back": str(it.get("back", ""))} for it in data if isinstance(it, dict) and "front" in it ] + + +async def generate_mindmap(topic: str) -> dict: + """Return a hierarchical JSON structure for a mindmap.""" + reply = await groq_chat( + [ + {"role": "system", "content": + "You are a mindmap generator. Respond ONLY with valid JSON, no prose."}, + {"role": "user", "content": + f"Create a hierarchical mindmap about '{topic}'. " + f"Return a JSON object with a 'name' (the topic) and 'children' (array of objects). " + f"Each child can also have 'children'. Go at least 3 levels deep. " + f"Keep node names short and concise. Output ONLY the JSON object."}, + ], + temperature=0.6, + max_tokens=2500, + ) + data = _extract_json(reply) + if not isinstance(data, dict): + return {"name": topic, "children": []} + return data diff --git a/backend/routes/chat.py b/backend/routes/chat.py index 2e89861..e2d274f 100644 --- a/backend/routes/chat.py +++ b/backend/routes/chat.py @@ -255,3 +255,9 @@ async def summarize(body: TextIn, user=Depends(auth.current_user)): async def homework(body: QuestionIn, user=Depends(auth.current_user)): content = await ai.homework_help(body.question) return {"answer": content} + + +@router.post("/tools/mindmap") +async def make_mindmap(body: TopicIn, user=Depends(auth.current_user)): + data = await ai.generate_mindmap(body.topic) + return {"topic": body.topic, "mindmap": data} diff --git a/frontend/css/dashboard.css b/frontend/css/dashboard.css index 113e9b1..4bab979 100644 --- a/frontend/css/dashboard.css +++ b/frontend/css/dashboard.css @@ -117,9 +117,31 @@ .settings-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.1rem; } .settings-card { padding: 1.6rem; } .settings-card h3 { margin-bottom: 1.1rem; } -.settings-card .field input { padding-left: 1rem; } +.settings-card .field { margin-bottom: 1.2rem; } +.settings-card .field label { display: block; font-size: 0.85rem; font-weight: 600; margin-bottom: 0.4rem; color: var(--text-dim); } +.settings-card .field input { + width: 100%; padding: 0.8rem 1rem; border-radius: 11px; + border: 1px solid var(--border); background: rgba(255,255,255,0.04); + color: var(--text); font-size: 0.96rem; font-family: inherit; + transition: border-color 0.2s, box-shadow 0.2s; +} +.settings-card .field input:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(109,123,255,0.2); } .settings-card .field input.with-icon { padding-left: 2.8rem; } +/* Account preferences */ +.pref-row { display: flex; align-items: center; justify-content: space-between; padding: 0.8rem 0; border-bottom: 1px solid var(--border); } +.pref-row:last-child { border-bottom: none; } +.pref-info b { display: block; font-size: 0.95rem; } +.pref-info span { font-size: 0.82rem; color: var(--text-dim); } + +/* Custom switch */ +.switch { position: relative; display: inline-block; width: 44px; height: 24px; } +.switch input { opacity: 0; width: 0; height: 0; } +.slider { position: absolute; cursor: pointer; inset: 0; background-color: var(--border); transition: .3s; border-radius: 24px; } +.slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: white; transition: .3s; border-radius: 50%; } +input:checked + .slider { background-color: var(--primary); } +input:checked + .slider:before { transform: translateX(20px); } + /* skeleton loader */ .skel { background: linear-gradient(90deg, rgba(255,255,255,0.04) 25%, rgba(255,255,255,0.1) 37%, rgba(255,255,255,0.04) 63%); background-size: 400% 100%; animation: shimmer 1.4s ease infinite; border-radius: 10px; } @keyframes shimmer { 0% { background-position: 100% 0; } 100% { background-position: -100% 0; } } diff --git a/frontend/css/tools.css b/frontend/css/tools.css index d5574c0..ecdb91f 100644 --- a/frontend/css/tools.css +++ b/frontend/css/tools.css @@ -93,3 +93,70 @@ .dropzone:hover, .dropzone.drag { border-color: var(--primary); background: rgba(109,123,255,0.06); color: var(--text); } .dropzone i { font-size: 2.2rem; margin-bottom: 0.7rem; display: block; color: var(--accent); } .file-chip { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.5rem 0.9rem; border-radius: 999px; background: var(--glass-strong); border: 1px solid var(--border); margin-top: 1rem; } + +/* Mindmap */ +.mindmap-container { + margin-top: 1.5rem; + padding: 2rem; + background: rgba(0,0,0,0.2); + border-radius: 12px; + overflow-x: auto; + display: flex; + flex-direction: column; + align-items: center; + min-height: 300px; + display: none; +} +.mindmap-container.show { display: flex; animation: fadeUp 0.35s ease; } + +.mm-node { + background: var(--grad); + color: white; + padding: 0.8rem 1.2rem; + border-radius: 50px; + font-weight: 600; + box-shadow: 0 4px 15px rgba(0,0,0,0.2); + position: relative; + z-index: 2; + white-space: nowrap; + transition: transform 0.2s; +} +.mm-root { + font-size: 1.2rem; + padding: 1rem 2rem; + margin-bottom: 3rem; +} +.mm-children { + display: flex; + gap: 2rem; + justify-content: center; + position: relative; +} +.mm-wrap { + display: flex; + flex-direction: column; + align-items: center; + position: relative; +} +.mm-wrap::before { + content: ''; + position: absolute; + top: -1.5rem; + left: 50%; + width: 2px; + height: 1.5rem; + background: var(--border); +} +.mm-children::before { + content: ''; + position: absolute; + top: -1.5rem; + left: 10%; + right: 10%; + height: 2px; + background: var(--border); +} +.mm-node:hover { + transform: translateY(-2px); + filter: brightness(1.1); +} diff --git a/frontend/js/sidebar.js b/frontend/js/sidebar.js index 66e2e29..704db42 100644 --- a/frontend/js/sidebar.js +++ b/frontend/js/sidebar.js @@ -22,6 +22,7 @@ { id: 'planner', href: '/tools#planner', icon: 'fa-calendar-days', label: 'Study Planner' }, { id: 'summarizer', href: '/tools#summarizer', icon: 'fa-file-pdf', label: 'PDF Summarizer' }, { id: 'homework', href: '/tools#homework', icon: 'fa-pen-to-square', label: 'Homework Helper' }, + { id: 'mindmap', href: '/tools#mindmap', icon: 'fa-sitemap', label: 'Mindmap' }, { section: 'Account' }, { id: 'profile', href: '/profile', icon: 'fa-user-gear', label: 'Profile & Settings' }, ]; diff --git a/frontend/js/tools.js b/frontend/js/tools.js index 17d3d7b..951be12 100644 --- a/frontend/js/tools.js +++ b/frontend/js/tools.js @@ -268,6 +268,60 @@ async function homework() { finally { busyBtn(btn, false); setLoading('hwLoading', false); } } +/* ========================================================= + MINDMAP + ========================================================= */ +async function genMindmap() { + const topic = document.getElementById('mmTopic').value.trim(); + if (!topic) return SS.toast('Enter a topic first.', 'error'); + const btn = document.getElementById('mmBtn'); + busyBtn(btn, true, 'Visualizing…'); + setLoading('mmLoading', true); + try { + const data = await SS.api('/api/tools/mindmap', { method: 'POST', body: { topic } }); + renderMindmap(data.mindmap); + SS.toast('Mindmap generated!'); + } catch (err) { SS.toast(err.message, 'error'); } + finally { busyBtn(btn, false); setLoading('mmLoading', false); } +} + +function renderMindmap(data) { + const container = document.getElementById('mmResult'); + container.innerHTML = ''; + container.classList.add('show'); + + const root = document.createElement('div'); + root.className = 'mm-node mm-root'; + root.innerHTML = `${esc(data.name)}`; + container.appendChild(root); + + if (data.children && data.children.length) { + const childrenContainer = document.createElement('div'); + childrenContainer.className = 'mm-children'; + data.children.forEach(child => childrenContainer.appendChild(createNode(child))); + container.appendChild(childrenContainer); + } +} + +function createNode(node) { + const wrap = document.createElement('div'); + wrap.className = 'mm-wrap'; + + const el = document.createElement('div'); + el.className = 'mm-node'; + el.innerHTML = `${esc(node.name)}`; + wrap.appendChild(el); + + if (node.children && node.children.length) { + const childrenContainer = document.createElement('div'); + childrenContainer.className = 'mm-children'; + node.children.forEach(child => childrenContainer.appendChild(createNode(child))); + wrap.appendChild(childrenContainer); + } + + return wrap; +} + /* ---------- Boot ---------- */ document.addEventListener('DOMContentLoaded', () => { initTabs(); @@ -280,6 +334,7 @@ document.addEventListener('DOMContentLoaded', () => { document.getElementById('planBtn').addEventListener('click', genPlan); document.getElementById('sumBtn').addEventListener('click', summarize); document.getElementById('hwBtn').addEventListener('click', homework); + document.getElementById('mmBtn').addEventListener('click', genMindmap); }); // expose for inline download buttons diff --git a/frontend/profile.html b/frontend/profile.html index f3e4865..2620e93 100644 --- a/frontend/profile.html +++ b/frontend/profile.html @@ -52,22 +52,55 @@

Edit profile

-
-
+
+
+ +
+

Preferences

+
+
+ Email Notifications + Receive weekly study summaries +
+ +
+
+
+ AI Insights + Allow AI to suggest study goals +
+ +
+
+
+ Public Profile + Let others see your achievements +
+ +
+
+

Change password

-
-
+
+
+ + +
+

Danger zone

+

Once you delete your account, there is no going back. Please be certain.

+ +
diff --git a/frontend/tools.html b/frontend/tools.html index 1c181cd..7e9dee9 100644 --- a/frontend/tools.html +++ b/frontend/tools.html @@ -44,6 +44,7 @@

Study Tools 🧰

+ @@ -146,6 +147,20 @@

Homework Helper

+ + +
+
+

Mindmap Generator

+

Visualize concepts with an interactive AI-generated mindmap.

+
+
+ +
+
Visualizing your topic…
+
+
+