Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions backend/ai.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 6 additions & 0 deletions backend/routes/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}
24 changes: 23 additions & 1 deletion frontend/css/dashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -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; } }
Expand Down
67 changes: 67 additions & 0 deletions frontend/css/tools.css
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
1 change: 1 addition & 0 deletions frontend/js/sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
];
Expand Down
55 changes: 55 additions & 0 deletions frontend/js/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = `<span>${esc(data.name)}</span>`;
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 = `<span>${esc(node.name)}</span>`;
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();
Expand All @@ -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
Expand Down
41 changes: 37 additions & 4 deletions frontend/profile.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,22 +52,55 @@ <h2 id="pName" style="margin:0">—</h2>
<h3><i class="fas fa-user-pen" style="color:var(--accent)"></i> Edit profile</h3>
<div class="form-msg" id="profileMsg"></div>
<form id="profileForm">
<div class="field"><label>Full name</label><input id="editName" type="text" class="with-icon" style="padding-left:1rem" /></div>
<div class="field"><label>Email (read-only)</label><input id="editEmail" type="email" disabled style="padding-left:1rem;opacity:.7" /></div>
<div class="field"><label>Full name</label><input id="editName" type="text" placeholder="Your name" /></div>
<div class="field"><label>Email (read-only)</label><input id="editEmail" type="email" disabled style="opacity:.7" /></div>
<button class="btn block" id="profileBtn"><i class="fas fa-floppy-disk"></i> Save changes</button>
</form>
</div>

<!-- Preferences -->
<div class="settings-card glass">
<h3><i class="fas fa-sliders" style="color:var(--accent)"></i> Preferences</h3>
<div class="pref-row">
<div class="pref-info">
<b>Email Notifications</b>
<span>Receive weekly study summaries</span>
</div>
<label class="switch"><input type="checkbox" checked><span class="slider"></span></label>
</div>
<div class="pref-row">
<div class="pref-info">
<b>AI Insights</b>
<span>Allow AI to suggest study goals</span>
</div>
<label class="switch"><input type="checkbox" checked><span class="slider"></span></label>
</div>
<div class="pref-row">
<div class="pref-info">
<b>Public Profile</b>
<span>Let others see your achievements</span>
</div>
<label class="switch"><input type="checkbox"><span class="slider"></span></label>
</div>
</div>

<!-- Change password -->
<div class="settings-card glass">
<h3><i class="fas fa-lock" style="color:var(--accent)"></i> Change password</h3>
<div class="form-msg" id="pwMsg"></div>
<form id="pwForm">
<div class="field"><label>Current password</label><input id="curPw" type="password" style="padding-left:1rem" /></div>
<div class="field"><label>New password</label><input id="newPw" type="password" style="padding-left:1rem" /></div>
<div class="field"><label>Current password</label><input id="curPw" type="password" placeholder="••••••••" /></div>
<div class="field"><label>New password</label><input id="newPw" type="password" placeholder="At least 6 characters" /></div>
<button class="btn block" id="pwBtn"><i class="fas fa-key"></i> Update password</button>
</form>
</div>

<!-- Danger zone -->
<div class="settings-card glass" style="border-color:rgba(251,113,133,0.3)">
<h3 style="color:var(--danger)"><i class="fas fa-triangle-exclamation"></i> Danger zone</h3>
<p class="sub" style="margin-bottom:1.2rem">Once you delete your account, there is no going back. Please be certain.</p>
<button class="btn block ghost" style="color:var(--danger);border-color:var(--danger)"><i class="fas fa-trash-can"></i> Delete account</button>
</div>
</div>

<!-- Telegram bot info -->
Expand Down
15 changes: 15 additions & 0 deletions frontend/tools.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ <h1>Study Tools 🧰</h1>
<button data-tab="planner"><i class="fas fa-calendar-days"></i> Planner</button>
<button data-tab="summarizer"><i class="fas fa-file-pdf"></i> Summarizer</button>
<button data-tab="homework"><i class="fas fa-pen-to-square"></i> Homework</button>
<button data-tab="mindmap"><i class="fas fa-sitemap"></i> Mindmap</button>
</div>

<!-- Notes -->
Expand Down Expand Up @@ -146,6 +147,20 @@ <h2><i class="fas fa-pen-to-square"></i> Homework Helper</h2>
<div class="result" id="hwResult"></div>
</div>
</section>

<!-- Mindmap -->
<section class="tool-panel" id="panel-mindmap">
<div class="tool-card glass">
<h2><i class="fas fa-sitemap"></i> Mindmap Generator</h2>
<p class="desc">Visualize concepts with an interactive AI-generated mindmap.</p>
<div class="tool-form">
<div class="grow"><label>Topic</label><input id="mmTopic" placeholder="e.g. Ancient Rome" /></div>
<button class="btn" id="mmBtn"><i class="fas fa-wand-magic-sparkles"></i> Generate</button>
</div>
<div class="tool-loading" id="mmLoading"><span class="spinner"></span> Visualizing your topic…</div>
<div id="mmResult" class="mindmap-container"></div>
</div>
</section>
</main>
</div>

Expand Down