"""
app.py - Smart Resource Finder Agent + Study Planner — Streamlit Interface
"""
import os
import re
import json
from datetime import date, timedelta
import streamlit as st
from agent import ResourceFinderAgent
# ─── Page Config ──────────────────────────────────────────────────────────────
st.set_page_config(
page_title="Smart Resource Finder",
page_icon="🎓",
layout="centered",
initial_sidebar_state="collapsed",
)
# ─── Session State Init ───────────────────────────────────────────────────────
if "plan" not in st.session_state:
st.session_state.plan = []
if "plan_generated" not in st.session_state:
st.session_state.plan_generated = False
# ─── Custom CSS ───────────────────────────────────────────────────────────────
st.markdown("""
""", unsafe_allow_html=True)
# ─── Hero ─────────────────────────────────────────────────────────────────────
st.markdown("""
🎓
Smart Resource Finder
Agentic AI study assistant · Powered by Groq + LLaMA 3.3
""", unsafe_allow_html=True)
st.markdown("
", unsafe_allow_html=True)
# ─── Sidebar ──────────────────────────────────────────────────────────────────
with st.sidebar:
st.markdown("### ⚙️ Configuration")
st.markdown("Get your **free** Groq API key at [console.groq.com](https://console.groq.com/keys)")
sidebar_key = st.text_input("Groq API Key", type="password", placeholder="gsk_...", help="Never stored.")
st.markdown("---")
st.markdown("**Agent Loop**\n\n```\nObserve → Think → Act\n```\n\nLLaMA 3.3-70B with tool-use via Groq.")
api_key = sidebar_key or os.getenv("GROQ_API_KEY", "")
# $env:GROQ_API_KEY="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"
if not api_key:
st.markdown(
'⚠️ No API key found. '
'Add your Groq key in the sidebar or set GROQ_API_KEY env variable.
',
unsafe_allow_html=True,
)
# ─── TABS ─────────────────────────────────────────────────────────────────────
tab1, tab2 = st.tabs(["🔍 Resource Finder", "📅 Study Planner"])
# ══════════════════════════════════════════════════════════════════════════════
# TAB 1 — RESOURCE FINDER
# ══════════════════════════════════════════════════════════════════════════════
with tab1:
st.markdown('', unsafe_allow_html=True)
topic = st.text_input(
"📖 Enter an academic topic",
placeholder="e.g. Machine Learning, Fourier Transform, Operating Systems…",
key="topic_input",
)
find_clicked = st.button("🔍 Find Study Resources", disabled=not api_key, key="find_btn")
st.markdown("
", unsafe_allow_html=True)
st.markdown(
""
"Try: Convolutional Neural Networks · "
"Digital Image Processing · "
"Data Structures · Quantum Computing
",
unsafe_allow_html=True,
)
if find_clicked and topic.strip():
with st.spinner("Agent is thinking…"):
try:
agent = ResourceFinderAgent(api_key=api_key)
output = agent.run(topic.strip())
st.markdown("#### 🤖 Agent Loop")
for step in output["steps"]:
html_step = re.sub(r'\*\*(.*?)\*\*', r'\1', step)
st.markdown(f'{html_step}
', unsafe_allow_html=True)
st.markdown("
", unsafe_allow_html=True)
st.markdown("#### 📚 Study Resources")
st.markdown('', unsafe_allow_html=True)
st.markdown(output["result"])
st.markdown('
', unsafe_allow_html=True)
except Exception as exc:
st.error(f"❌ Error: {exc}")
st.info("Check your Groq API key and internet connection.")
elif find_clicked and not topic.strip():
st.warning("Please enter a topic before searching.")
# ══════════════════════════════════════════════════════════════════════════════
# TAB 2 — STUDY PLANNER
# ══════════════════════════════════════════════════════════════════════════════
with tab2:
st.markdown("### 📅 AI Study Planner")
st.markdown(
""
"Enter your exam date and topics — AI will auto-build a day-by-day "
"study schedule counting backwards from your exam. "
"Tick off sessions as you complete them!
",
unsafe_allow_html=True,
)
# ── Inputs ────────────────────────────────────────────────────────────────
st.markdown('', unsafe_allow_html=True)
col1, col2 = st.columns(2)
with col1:
exam_date = st.date_input(
"🗓️ Exam Date",
value=date.today() + timedelta(days=14),
min_value=date.today() + timedelta(days=1),
key="exam_date",
)
with col2:
hours_per_day = st.number_input(
"⏰ Study Hours / Day",
min_value=1, max_value=12, value=3, step=1,
key="hours_day",
)
topics_input = st.text_area(
"📚 Topics to Study (one per line)",
placeholder="Machine Learning\nDeep Learning\nConvolutional Neural Networks\nNLP",
height=130,
key="topics_area",
)
generate_clicked = st.button(
"🗺️ Generate My Study Plan",
disabled=not api_key,
key="gen_plan_btn",
)
st.markdown("
", unsafe_allow_html=True)
# ── Generate Plan ─────────────────────────────────────────────────────────
if generate_clicked:
topics_list = [t.strip() for t in topics_input.strip().splitlines() if t.strip()]
if not topics_list:
st.warning("Please enter at least one topic.")
else:
days_left = (exam_date - date.today()).days
with st.spinner("AI is building your personalised study plan…"):
try:
from groq import Groq
client = Groq(api_key=api_key)
prompt = f"""You are an expert academic study planner.
Student details:
- Days until exam: {days_left} (exam on {exam_date})
- Study hours per day: {hours_per_day}
- Topics to cover: {json.dumps(topics_list)}
Create a day-by-day study plan starting from TODAY ({date.today()}) up to the day BEFORE the exam.
The final day must be: topic="Exam Preparation", task="Full Revision + Mock Test + Rest"
Rules:
1. Distribute topics evenly. Big topics can span multiple days.
2. Tasks must be specific (e.g. "Study Backpropagation & Gradient Descent" not "Study ML").
3. Return ONLY a valid JSON array. No explanation, no markdown, no backticks.
Each object must have exactly these keys:
- "day": integer starting at 1
- "date": "YYYY-MM-DD" string
- "topic": topic name
- "task": specific actionable task
- "hours": integer (equal to {hours_per_day})"""
response = client.chat.completions.create(
model="llama-3.3-70b-versatile",
messages=[{"role": "user", "content": prompt}],
max_tokens=2048,
temperature=0.3,
)
raw = response.choices[0].message.content.strip()
raw = re.sub(r"```json|```", "", raw).strip()
plan_data = json.loads(raw)
st.session_state.plan = [{**item, "done": False} for item in plan_data]
st.session_state.plan_generated = True
st.rerun()
except json.JSONDecodeError:
st.error("❌ AI returned invalid format. Please try again.")
except Exception as e:
st.error(f"❌ Error: {e}")
# ── Show Plan ─────────────────────────────────────────────────────────────
if st.session_state.plan_generated and st.session_state.plan:
plan = st.session_state.plan
total = len(plan)
done_count = sum(1 for p in plan if p["done"])
progress_pct = done_count / total if total > 0 else 0
# Exam badge + progress
st.markdown(
f'🎯 Exam on {exam_date.strftime("%d %B %Y")}'
f' · {(exam_date - date.today()).days} days left
',
unsafe_allow_html=True,
)
st.markdown(
f'📊 Progress — {done_count}/{total} sessions done '
f'({int(progress_pct*100)}%)
',
unsafe_allow_html=True,
)
st.progress(progress_pct)
st.markdown("
", unsafe_allow_html=True)
# Clear button
if st.button("🗑️ Clear Plan & Start Over", key="clear_btn"):
st.session_state.plan = []
st.session_state.plan_generated = False
st.rerun()
st.markdown("---")
st.markdown(
"#### 📋 Your Study Schedule "
""
"— tick ✅ each session when done",
unsafe_allow_html=True,
)
# Day cards
for i, item in enumerate(plan):
is_done = item["done"]
card_class = "day-card done" if is_done else "day-card"
done_icon = "✅" if is_done else "⬜"
is_revision = any(k in item["task"].lower() for k in ["revision", "mock", "rest", "exam prep"])
topic_color = "#f59e0b" if is_revision else "#818cf8"
col_check, col_content = st.columns([0.07, 0.93])
with col_check:
checked = st.checkbox(
"", value=is_done, key=f"task_{i}",
label_visibility="collapsed",
)
if checked != is_done:
st.session_state.plan[i]["done"] = checked
st.rerun()
with col_content:
st.markdown(
f"""
Day {item['day']} · {item['date']} · ⏱ {item.get('hours','—')}h
{done_icon} {item['task']}
📌 {item['topic']}
""",
unsafe_allow_html=True,
)
# All done 🎉
if done_count == total:
st.markdown(
""
"
🎉
"
"
All sessions completed! Go ace that exam!
"
"
",
unsafe_allow_html=True,
)
# ─── Footer ───────────────────────────────────────────────────────────────────
st.markdown("
", unsafe_allow_html=True)
st.markdown(
""
"Smart Resource Finder Agent · Built with Groq API + LLaMA 3.3-70B + Streamlit"
"
",
unsafe_allow_html=True,
)