diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000000000000000000000000000000000000..8ffe8d898ebf07aefcad96ceef9f463f93f37556
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,18 @@
+# Google Gemini API Key (get from https://aistudio.google.com/apikey)
+GOOGLE_API_KEY=your_google_api_key_here
+
+# Embedding model (default: all-MiniLM-L6-v2 from sentence-transformers, runs locally)
+EMBEDDING_MODEL=all-MiniLM-L6-v2
+
+# LLM model (default: gemini-2.0-flash)
+LLM_MODEL=gemini-2.0-flash
+
+# Vector store path
+VECTOR_STORE_PATH=data/vector_store
+
+# PDF source directory
+PDF_DIR=data/pdfs
+
+# Chunk size and overlap for text splitting
+CHUNK_SIZE=1000
+CHUNK_OVERLAP=200
diff --git a/.gitattributes b/.gitattributes
index a6344aac8c09253b3b630fb776ae94478aa0275b..1a458815e58d41ad45b9e2f6349b608fbd22f8ac 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -33,3 +33,36 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
*.zip filter=lfs diff=lfs merge=lfs -text
*.zst filter=lfs diff=lfs merge=lfs -text
*tfevents* filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/BIOLOGY_FLASHCARD_FOR_NEET.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/CBSE_2026_Class_12_Physics_Set_2_Question_Paper_With_Solutions_PDF.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/CENGAGE_IOC_CRUX_BY_studyoceanbooks.PDF filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/COM_COLLISION_Short_Notes_1_.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/Chemistry_Flash_Cards_Colourful_Complete.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/Chemistry_Flashcard_for_JEE_NEET.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/Chemistry_Formula_Booklet_Hashlearn.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/Circular_dyanamic_short_notes_1_.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/Circular_motion_in_kinematics_short_notes_1_.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/Rotational_motion_short_notes_1_.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/Thermal_properties_of_matter_short_notes_.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/Units_measurements_and_errors_short_notes_1_.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/Vertical_circular_motion_short_notes_1_.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/Wave_motion_short_notes_1_.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/Work_Power_Energy_Short_Notes_1_.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_1_question_paper_2026_1771575627702.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_2_question_paper_2026_1771575619105.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_3_question_paper_2026_1771575610905.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/chapter_10.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/chapter_11.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/chapter_12.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/chapter_13.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/chapter_14.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/chapter_15.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/chapter_2.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/chapter_3_1_.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/chapter_4.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/chapter_5.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/chapter_6.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/chapter_7.pdf filter=lfs diff=lfs merge=lfs -text
+data/pdfs/StudyOceanPDFs/vector_sheets_Cyan_notes_1_1_.pdf filter=lfs diff=lfs merge=lfs -text
+data/vector_store/index.faiss filter=lfs diff=lfs merge=lfs -text
+logo.png filter=lfs diff=lfs merge=lfs -text
diff --git a/.python-version b/.python-version
new file mode 100644
index 0000000000000000000000000000000000000000..c8cfe3959183f8e9a50f83f54cd723f2dc9c252d
--- /dev/null
+++ b/.python-version
@@ -0,0 +1 @@
+3.10
diff --git a/.railwayignore b/.railwayignore
new file mode 100644
index 0000000000000000000000000000000000000000..6ebde1c167874359f49bf86419c53bae39f6df99
--- /dev/null
+++ b/.railwayignore
@@ -0,0 +1,13 @@
+.venv/
+venv/
+.env
+data/pdfs/
+__pycache__/
+*.pyc
+*.pyo
+.vscode/
+.idea/
+.DS_Store
+Thumbs.db
+.ipynb_checkpoints/
+*.zip
diff --git a/COLAB_GUIDE.md b/COLAB_GUIDE.md
new file mode 100644
index 0000000000000000000000000000000000000000..fd67764a481c87e288eeb89ab3fb88f8c7c4c298
--- /dev/null
+++ b/COLAB_GUIDE.md
@@ -0,0 +1,112 @@
+# ๐ Study Ocean RAG โ Google Colab Guide
+
+> Your laptop stays cool. Colab's **T4 GPU** does all the heavy lifting.
+
+---
+
+## ๐ One-Time Setup (First Time Only)
+
+### 1. Upload ZIP to Google Drive
+1. Go to [drive.google.com](https://drive.google.com)
+2. Create a folder: `MyDrive/StudyOcean/`
+3. Upload `StudyOceanPDFs.zip` into that folder
+4. โณ Wait for upload to complete (6 GB โ may take 15โ30 min on slow wifi)
+
+### 2. Get a Gemini API Key (Free)
+1. Go to [aistudio.google.com/apikey](https://aistudio.google.com/apikey)
+2. Click **Create API Key**
+3. Copy the key โ you'll paste it into the notebook
+
+### 3. Open the Notebook in Colab
+1. Go to [colab.research.google.com](https://colab.research.google.com)
+2. Click **File โ Upload notebook**
+3. Upload `StudyOcean_RAG_Colab.ipynb` from this folder
+
+### 4. Set T4 GPU Runtime
+> **This is critical โ without it, ingestion will be very slow!**
+1. In Colab: **Runtime โ Change runtime type**
+2. Set Hardware accelerator = **T4 GPU**
+3. Click **Save**
+
+---
+
+## ๐ Running the Pipeline
+
+### First Time (Fresh Ingestion)
+Run cells in order: **Steps 1 โ 2 โ 3 โ 4 โ 5 โ 6 โ 7 โ 8 โ 9 โ 10**
+
+| Step | What it does | Time |
+|------|-------------|------|
+| 1 | Verify T4 GPU + mount Drive | 30s |
+| 2 | Install packages | 2โ3 min |
+| 3 | Configure settings (paste API key here!) | instant |
+| 4 | Extract PDFs from ZIP | 5โ10 min |
+| 5 | Ingest PDFs & build vector store | **30โ60 min** |
+| 6 | Save vector store to Drive | 2โ5 min |
+| 7 | Test retrieval | instant |
+| 8 | Build RAG chain | instant |
+| 9 | Test full pipeline | 5s |
+| 10 | Launch Gradio chat UI | instant |
+
+### Future Sessions (Vector Store Already Built)
+Run cells: **Steps 1 โ 2 โ 3 โ 11 โ 8 โ 10**
+
+| Step | What it does | Time |
+|------|-------------|------|
+| 11 | Load saved vector store from Drive | 1โ2 min |
+| 8 | Build RAG chain | instant |
+| 10 | Launch Gradio chat UI | instant |
+
+---
+
+## ๐ป Running Locally (After Ingestion)
+
+Once the vector store is built, you can run the chat locally:
+
+### Option A โ Download via Colab (Step 12)
+- Run **Step 12** in the notebook
+- It downloads `vector_store_export.zip` to your computer
+- Extract it to: `Study_Ocean Rag/data/vector_store/`
+
+### Option B โ Download from Google Drive
+- Go to Drive โ `StudyOcean/vector_store/`
+- Download the folder
+- Place it at: `Study_Ocean Rag/data/vector_store/`
+
+### Then run locally:
+```bash
+# Make sure .env has your GOOGLE_API_KEY
+python main.py chat # Web UI at http://localhost:7860
+# OR
+python main.py ask # Terminal Q&A
+```
+
+---
+
+## โก Performance on T4 GPU
+
+| Task | CPU (your laptop) | T4 GPU (Colab) |
+|------|------------------|----------------|
+| Embedding batch size | 64 | 256 |
+| ~10,000 chunks | ~45 min | ~8 min |
+| ~50,000 chunks | ~4 hours | ~35 min |
+| ~100,000 chunks | ~8 hours | ~70 min |
+
+---
+
+## โ Troubleshooting
+
+**"ZIP not found" error**
+โ Update `ZIP_DRIVE_PATH` in Step 3 to match your actual Drive path
+
+**"CUDA not available"**
+โ You forgot to set T4 GPU runtime! Runtime โ Change runtime type โ T4 GPU
+
+**Colab session disconnects during ingestion**
+โ The vector store saves in batches. Re-run from Step 5 โ FAISS will rebuild from scratch. Save to Drive often.
+
+**"Invalid API key"**
+โ Make sure you pasted the full Gemini API key in Step 3
+
+**Gradio link doesn't work**
+โ Gradio public links expire after 72 hours. Re-run Step 10 to get a new link.
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..7e21c3f0ae9a8dfc18eae0978f275cfcdb69fcd2
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,24 @@
+# Use an official Python runtime as a parent image
+FROM python:3.11-slim
+
+# Hugging Face Spaces require running as a non-root user
+RUN useradd -m -u 1000 user
+USER user
+ENV PATH="/home/user/.local/bin:$PATH"
+
+# Set the working directory
+WORKDIR /app
+
+# Copy requirements and install dependencies
+COPY --chown=user ./requirements.txt requirements.txt
+RUN pip install --no-cache-dir --upgrade pip && \
+ pip install --no-cache-dir -r requirements.txt
+
+# Copy the rest of the application
+COPY --chown=user . /app
+
+# Expose the port that Hugging Face Spaces expects (7860)
+EXPOSE 7860
+
+# Command to run the FastAPI app
+CMD ["uvicorn", "api:app", "--host", "0.0.0.0", "--port", "7860"]
diff --git a/Procfile b/Procfile
new file mode 100644
index 0000000000000000000000000000000000000000..351a3f0dfa54201e99e93622aa27e6c056ea709d
--- /dev/null
+++ b/Procfile
@@ -0,0 +1 @@
+web: uvicorn api:app --host 0.0.0.0 --port $PORT
diff --git a/README.md b/README.md
index 9f4ef1175b50301d033fb1464ea46758c1be752b..2c1ec74d8644dc82fb85c84faeb041c254e062da 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,8 @@
---
-title: Study-Ocean-AI Mentor
-emoji: ๐
-colorFrom: green
-colorTo: blue
+title: Study Ocean RAG
+emoji: ๐
+colorFrom: blue
+colorTo: indigo
sdk: docker
-pinned: false
+app_port: 7860
---
-
-Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
diff --git a/StudyOcean_RAG_Colab.ipynb b/StudyOcean_RAG_Colab.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..5cd3163a6d43c31b04d898089f6112c6f9d66907
--- /dev/null
+++ b/StudyOcean_RAG_Colab.ipynb
@@ -0,0 +1,743 @@
+{
+ "nbformat": 4,
+ "nbformat_minor": 0,
+ "metadata": {
+ "colab": {
+ "provenance": [],
+ "gpuType": "T4",
+ "name": "StudyOcean_RAG_Colab.ipynb"
+ },
+ "kernelspec": {
+ "name": "python3",
+ "display_name": "Python 3"
+ },
+ "language_info": {
+ "name": "python"
+ },
+ "accelerator": "GPU"
+ },
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# ๐ Study Ocean AI Mentor โ Google Colab Pipeline\n",
+ "\n",
+ "> **Run this notebook on Colab with T4 GPU** \n",
+ "> Runtime โ Change runtime type โ **T4 GPU**\n",
+ "\n",
+ "### What this notebook does:\n",
+ "1. โ
Mounts your Google Drive\n",
+ "2. โ
Installs all dependencies (with `faiss-gpu` for T4 acceleration)\n",
+ "3. โ
Extracts PDFs from `StudyOceanPDFs.zip`\n",
+ "4. โ
Runs the ingestion pipeline (GPU-accelerated embeddings)\n",
+ "5. โ
Saves the FAISS vector store back to Google Drive\n",
+ "6. โ
Launches the Gradio chat UI with a public shareable link\n",
+ "\n",
+ "---\n",
+ "### ๐ Before you start:\n",
+ "- Upload `StudyOceanPDFs.zip` to your **Google Drive** (e.g., `MyDrive/StudyOcean/StudyOceanPDFs.zip`)\n",
+ "- Get your **Google Gemini API key** from https://aistudio.google.com/apikey\n",
+ "- Set runtime to **T4 GPU**: Runtime โ Change runtime type โ T4 GPU\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## ๐ง Step 1 โ Verify GPU & Mount Google Drive"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# โโ Verify T4 GPU is available โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n",
+ "import subprocess\n",
+ "result = subprocess.run(['nvidia-smi'], capture_output=True, text=True)\n",
+ "print(result.stdout if result.returncode == 0 else \"โ ๏ธ No GPU found! Go to Runtime โ Change runtime type โ T4 GPU\")\n",
+ "\n",
+ "import torch\n",
+ "print(f\"\\n๐ฅ PyTorch CUDA available: {torch.cuda.is_available()}\")\n",
+ "if torch.cuda.is_available():\n",
+ " print(f\" GPU: {torch.cuda.get_device_name(0)}\")\n",
+ " print(f\" VRAM: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# โโ Mount Google Drive โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n",
+ "from google.colab import drive\n",
+ "drive.mount('/content/drive')\n",
+ "print(\"โ
Google Drive mounted at /content/drive\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## ๐ฆ Step 2 โ Install Dependencies"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# โโ Install all packages โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n",
+ "# Using faiss-gpu for T4 GPU acceleration during similarity search\n",
+ "!pip install -q \\\n",
+ " langchain \\\n",
+ " langchain-community \\\n",
+ " langchain-core \\\n",
+ " langchain-google-genai \\\n",
+ " langchain-huggingface \\\n",
+ " pymupdf \\\n",
+ " faiss-gpu \\\n",
+ " sentence-transformers \\\n",
+ " google-generativeai \\\n",
+ " python-dotenv \\\n",
+ " gradio \\\n",
+ " tiktoken \\\n",
+ " accelerate\n",
+ "\n",
+ "print(\"\\nโ
All packages installed!\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## โ๏ธ Step 3 โ Configure Settings\n",
+ "\n",
+ "> **Edit the variables in this cell before running!**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import os\n",
+ "\n",
+ "# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n",
+ "# โ๏ธ EDIT THESE SETTINGS\n",
+ "# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n",
+ "\n",
+ "# Your Google Gemini API key โ get it from https://aistudio.google.com/apikey\n",
+ "GOOGLE_API_KEY = \"\" # โ PASTE YOUR KEY HERE\n",
+ "\n",
+ "# Path to StudyOceanPDFs.zip in your Google Drive\n",
+ "ZIP_DRIVE_PATH = \"/content/drive/MyDrive/StudyOcean/StudyOceanPDFs.zip\"\n",
+ "\n",
+ "# Where to save the vector store in Google Drive (so it persists!)\n",
+ "VECTOR_STORE_DRIVE_PATH = \"/content/drive/MyDrive/StudyOcean/vector_store\"\n",
+ "\n",
+ "# Embedding model โ 'cuda' will use the T4 GPU!\n",
+ "EMBEDDING_MODEL = \"all-MiniLM-L6-v2\" # Fast & accurate\n",
+ "EMBEDDING_DEVICE = \"cuda\" # โ T4 GPU acceleration\n",
+ "\n",
+ "# Gemini LLM model\n",
+ "LLM_MODEL = \"gemini-2.0-flash\"\n",
+ "\n",
+ "# Text splitting config\n",
+ "CHUNK_SIZE = 1000\n",
+ "CHUNK_OVERLAP = 200\n",
+ "\n",
+ "# RAG retrieval\n",
+ "TOP_K = 5\n",
+ "\n",
+ "# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n",
+ "\n",
+ "# Local working paths (inside Colab VM)\n",
+ "PDF_DIR = \"/content/studyocean_pdfs\"\n",
+ "VECTOR_STORE_LOCAL = \"/content/vector_store\"\n",
+ "\n",
+ "os.makedirs(PDF_DIR, exist_ok=True)\n",
+ "os.makedirs(VECTOR_STORE_LOCAL, exist_ok=True)\n",
+ "\n",
+ "# Set the API key as env var for LangChain\n",
+ "os.environ[\"GOOGLE_API_KEY\"] = GOOGLE_API_KEY\n",
+ "\n",
+ "print(\"โ
Configuration set!\")\n",
+ "print(f\" ZIP source: {ZIP_DRIVE_PATH}\")\n",
+ "print(f\" PDF extract: {PDF_DIR}\")\n",
+ "print(f\" Vector store: {VECTOR_STORE_LOCAL}\")\n",
+ "print(f\" Drive backup: {VECTOR_STORE_DRIVE_PATH}\")\n",
+ "print(f\" Embedding GPU: {EMBEDDING_DEVICE}\")\n",
+ "print(f\" LLM: {LLM_MODEL}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## ๐ Step 4 โ Extract PDFs from ZIP"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import zipfile\n",
+ "import time\n",
+ "\n",
+ "# Check if zip exists\n",
+ "if not os.path.exists(ZIP_DRIVE_PATH):\n",
+ " print(f\"โ ZIP not found at: {ZIP_DRIVE_PATH}\")\n",
+ " print(\" Please upload StudyOceanPDFs.zip to your Google Drive\")\n",
+ " print(\" Then update ZIP_DRIVE_PATH in Step 3.\")\n",
+ " raise FileNotFoundError(f\"ZIP not found: {ZIP_DRIVE_PATH}\")\n",
+ "\n",
+ "zip_size_gb = os.path.getsize(ZIP_DRIVE_PATH) / 1e9\n",
+ "print(f\"๐ฆ Found ZIP: {zip_size_gb:.2f} GB\")\n",
+ "print(f\" Extracting to {PDF_DIR} ...\")\n",
+ "print(\" (This may take a few minutes for large archives)\")\n",
+ "\n",
+ "start = time.time()\n",
+ "with zipfile.ZipFile(ZIP_DRIVE_PATH, 'r') as zf:\n",
+ " zf.extractall(PDF_DIR)\n",
+ "elapsed = time.time() - start\n",
+ "\n",
+ "# Count extracted PDFs\n",
+ "pdf_count = sum(\n",
+ " 1 for root, _, files in os.walk(PDF_DIR)\n",
+ " for f in files if f.lower().endswith('.pdf')\n",
+ ")\n",
+ "\n",
+ "print(f\"\\nโ
Extraction complete in {elapsed:.1f}s\")\n",
+ "print(f\" Found {pdf_count} PDF files\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## ๐ Step 5 โ PDF Ingestion Pipeline (GPU-Accelerated)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import fitz # PyMuPDF\n",
+ "from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
+ "from langchain_community.vectorstores import FAISS\n",
+ "from langchain_huggingface import HuggingFaceEmbeddings\n",
+ "from langchain.docstore.document import Document\n",
+ "\n",
+ "\n",
+ "def collect_pdf_paths(pdf_dir):\n",
+ " \"\"\"Recursively collect all PDF paths.\"\"\"\n",
+ " paths = []\n",
+ " for root, _, files in os.walk(pdf_dir):\n",
+ " for f in files:\n",
+ " if f.lower().endswith('.pdf'):\n",
+ " paths.append(os.path.join(root, f))\n",
+ " return sorted(paths)\n",
+ "\n",
+ "\n",
+ "def extract_text(pdf_path):\n",
+ " \"\"\"Extract text from a PDF using PyMuPDF.\"\"\"\n",
+ " text = \"\"\n",
+ " try:\n",
+ " doc = fitz.open(pdf_path)\n",
+ " for page in doc:\n",
+ " text += page.get_text()\n",
+ " doc.close()\n",
+ " except Exception as e:\n",
+ " print(f\" โ Error reading {os.path.basename(pdf_path)}: {e}\")\n",
+ " return text\n",
+ "\n",
+ "\n",
+ "def build_documents(pdf_paths):\n",
+ " \"\"\"Extract and chunk all PDFs into LangChain Documents.\"\"\"\n",
+ " splitter = RecursiveCharacterTextSplitter(\n",
+ " chunk_size=CHUNK_SIZE,\n",
+ " chunk_overlap=CHUNK_OVERLAP,\n",
+ " separators=[\"\\n\\n\", \"\\n\", \". \", \" \", \"\"],\n",
+ " )\n",
+ " all_docs = []\n",
+ " skipped = 0\n",
+ "\n",
+ " for i, path in enumerate(pdf_paths, 1):\n",
+ " rel = os.path.relpath(path, PDF_DIR)\n",
+ " parts = rel.replace(\"\\\\\", \"/\").split(\"/\")\n",
+ " subject = parts[0] if len(parts) > 1 else \"General\"\n",
+ "\n",
+ " if i % 50 == 0 or i == 1:\n",
+ " print(f\" [{i}/{len(pdf_paths)}] {rel}\")\n",
+ "\n",
+ " text = extract_text(path)\n",
+ " if not text.strip():\n",
+ " skipped += 1\n",
+ " continue\n",
+ "\n",
+ " chunks = splitter.create_documents(\n",
+ " texts=[text],\n",
+ " metadatas=[{\"source\": rel, \"filename\": os.path.basename(path), \"subject\": subject}]\n",
+ " )\n",
+ " all_docs.extend(chunks)\n",
+ "\n",
+ " print(f\"\\n Skipped (image/scanned PDFs): {skipped}\")\n",
+ " return all_docs\n",
+ "\n",
+ "\n",
+ "# โโ Step 5a: Collect PDFs โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n",
+ "print(\"=\" * 60)\n",
+ "print(\"๐ PHASE 1: Collecting & chunking PDFs\")\n",
+ "print(\"=\" * 60)\n",
+ "pdf_paths = collect_pdf_paths(PDF_DIR)\n",
+ "print(f\" {len(pdf_paths)} PDFs found\")\n",
+ "\n",
+ "t0 = time.time()\n",
+ "documents = build_documents(pdf_paths)\n",
+ "t_extract = time.time() - t0\n",
+ "\n",
+ "print(f\"\\nโ
Created {len(documents)} chunks in {t_extract:.1f}s\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# โโ Step 5b: Load Embedding Model onto T4 GPU โโโโโโโโโโโโโโโโ\n",
+ "print(\"=\" * 60)\n",
+ "print(f\"๐ง PHASE 2: Loading embedding model on {EMBEDDING_DEVICE.upper()}\")\n",
+ "print(\"=\" * 60)\n",
+ "\n",
+ "import torch\n",
+ "device = EMBEDDING_DEVICE if torch.cuda.is_available() else \"cpu\"\n",
+ "if device == \"cpu\" and EMBEDDING_DEVICE == \"cuda\":\n",
+ " print(\"โ ๏ธ CUDA not available, falling back to CPU\")\n",
+ "\n",
+ "embeddings = HuggingFaceEmbeddings(\n",
+ " model_name=EMBEDDING_MODEL,\n",
+ " model_kwargs={\"device\": device},\n",
+ " encode_kwargs={\"normalize_embeddings\": True, \"batch_size\": 256}, # Larger batch on GPU!\n",
+ ")\n",
+ "\n",
+ "print(f\"โ
Embedding model loaded on {device.upper()}\")\n",
+ "print(f\" Model: {EMBEDDING_MODEL}\")\n",
+ "print(f\" Batch size: 256 (optimized for T4)\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# โโ Step 5c: Build FAISS Vector Store โโโโโโโโโโโโโโโโโโโโโโโ\n",
+ "print(\"=\" * 60)\n",
+ "print(f\"๐ PHASE 3: Building FAISS vector store ({len(documents)} chunks)\")\n",
+ "print(\"=\" * 60)\n",
+ "\n",
+ "BATCH_SIZE = 1000 # Larger batches since we have GPU\n",
+ "vector_store = None\n",
+ "t0 = time.time()\n",
+ "\n",
+ "total_batches = (len(documents) + BATCH_SIZE - 1) // BATCH_SIZE\n",
+ "for i in range(0, len(documents), BATCH_SIZE):\n",
+ " batch = documents[i : i + BATCH_SIZE]\n",
+ " batch_num = (i // BATCH_SIZE) + 1\n",
+ "\n",
+ " elapsed = time.time() - t0\n",
+ " rate = (i + 1) / elapsed if elapsed > 0 else 0\n",
+ " eta = (len(documents) - i) / rate if rate > 0 else 0\n",
+ " print(f\" Batch {batch_num:3d}/{total_batches} | {len(batch)} chunks | \"\n",
+ " f\"ETA: {eta/60:.1f} min\")\n",
+ "\n",
+ " if vector_store is None:\n",
+ " vector_store = FAISS.from_documents(batch, embeddings)\n",
+ " else:\n",
+ " batch_store = FAISS.from_documents(batch, embeddings)\n",
+ " vector_store.merge_from(batch_store)\n",
+ "\n",
+ "t_embed = time.time() - t0\n",
+ "print(f\"\\nโ
Vector store built in {t_embed/60:.1f} minutes\")\n",
+ "print(f\" Total vectors: {vector_store.index.ntotal}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## ๐พ Step 6 โ Save Vector Store to Google Drive"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import shutil\n",
+ "\n",
+ "# Save locally first\n",
+ "print(\"๐พ Saving vector store locally...\")\n",
+ "vector_store.save_local(VECTOR_STORE_LOCAL)\n",
+ "print(f\" โ
Saved to {VECTOR_STORE_LOCAL}\")\n",
+ "\n",
+ "# Copy to Google Drive for persistence\n",
+ "print(\"\\nโ๏ธ Copying to Google Drive...\")\n",
+ "os.makedirs(VECTOR_STORE_DRIVE_PATH, exist_ok=True)\n",
+ "\n",
+ "for fname in os.listdir(VECTOR_STORE_LOCAL):\n",
+ " src = os.path.join(VECTOR_STORE_LOCAL, fname)\n",
+ " dst = os.path.join(VECTOR_STORE_DRIVE_PATH, fname)\n",
+ " shutil.copy2(src, dst)\n",
+ " size = os.path.getsize(dst) / 1e6\n",
+ " print(f\" โ
{fname} ({size:.1f} MB)\")\n",
+ "\n",
+ "print(f\"\\n๐ Vector store saved to Google Drive!\")\n",
+ "print(f\" Path: {VECTOR_STORE_DRIVE_PATH}\")\n",
+ "print(f\" Total vectors: {vector_store.index.ntotal}\")\n",
+ "print(\"\\n๐ You can now download this folder to your laptop\")\n",
+ "print(\" and place it at: Study_Ocean Rag/data/vector_store/\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## ๐งช Step 7 โ Test the Vector Store (Quick Sanity Check)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# โโ Quick retrieval test โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n",
+ "test_question = \"What is Newton's second law of motion?\"\n",
+ "print(f\"๐ Test query: '{test_question}'\\n\")\n",
+ "\n",
+ "results = vector_store.similarity_search(test_question, k=3)\n",
+ "for i, doc in enumerate(results, 1):\n",
+ " print(f\"--- Result {i} ---\")\n",
+ " print(f\"Subject : {doc.metadata.get('subject', 'N/A')}\")\n",
+ " print(f\"File : {doc.metadata.get('filename', 'N/A')}\")\n",
+ " print(f\"Preview : {doc.page_content[:200]}...\")\n",
+ " print()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## ๐ค Step 8 โ Build the RAG Chain (Gemini + FAISS)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from langchain_google_genai import ChatGoogleGenerativeAI\n",
+ "from langchain.chains import RetrievalQA\n",
+ "from langchain.prompts import PromptTemplate\n",
+ "\n",
+ "# Validate API key\n",
+ "if not GOOGLE_API_KEY or GOOGLE_API_KEY == \"\":\n",
+ " print(\"โ GOOGLE_API_KEY is not set! Go back to Step 3 and paste your key.\")\n",
+ " raise ValueError(\"Missing GOOGLE_API_KEY\")\n",
+ "\n",
+ "# โโ Student Mentor Prompt โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n",
+ "MENTOR_PROMPT = PromptTemplate(\n",
+ " input_variables=[\"context\", \"question\"],\n",
+ " template=\"\"\"You are **Study Ocean AI Mentor** โ a friendly, patient, and knowledgeable tutor who helps students understand their study material deeply.\n",
+ "\n",
+ "Your role:\n",
+ "- Solve student doubts with clear, step-by-step explanations\n",
+ "- Use simple language that students can easily understand\n",
+ "- Provide examples and analogies when helpful\n",
+ "- If the answer is in the context, use it; if not, say so honestly\n",
+ "- Encourage students and make learning enjoyable\n",
+ "\n",
+ "**Guidelines:**\n",
+ "1. Start with a brief, direct answer, then explain in detail\n",
+ "2. Use bullet points, numbered steps, or tables for clarity\n",
+ "3. For math/science problems, show the solution step by step\n",
+ "4. Mention which topic/chapter the information relates to when possible\n",
+ "5. If the topic isn't covered, say: \"This isn't in your study materials, but here's what I know...\"\n",
+ "\n",
+ "---\n",
+ "๐ **Relevant Study Material:**\n",
+ "{context}\n",
+ "\n",
+ "---\n",
+ "๐ **Student's Question:** {question}\n",
+ "\n",
+ "---\n",
+ "๐ **Mentor's Response:**\n",
+ "\"\"\"\n",
+ ")\n",
+ "\n",
+ "# โโ Initialize LLM โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n",
+ "llm = ChatGoogleGenerativeAI(\n",
+ " model=LLM_MODEL,\n",
+ " google_api_key=GOOGLE_API_KEY,\n",
+ " temperature=0.3,\n",
+ " max_output_tokens=2048,\n",
+ " convert_system_message_to_human=True,\n",
+ ")\n",
+ "\n",
+ "# โโ Build RAG Chain โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n",
+ "retriever = vector_store.as_retriever(\n",
+ " search_type=\"similarity\",\n",
+ " search_kwargs={\"k\": TOP_K},\n",
+ ")\n",
+ "\n",
+ "rag_chain = RetrievalQA.from_chain_type(\n",
+ " llm=llm,\n",
+ " chain_type=\"stuff\",\n",
+ " retriever=retriever,\n",
+ " return_source_documents=True,\n",
+ " chain_type_kwargs={\"prompt\": MENTOR_PROMPT},\n",
+ ")\n",
+ "\n",
+ "print(\"โ
RAG Chain ready!\")\n",
+ "print(f\" LLM: {LLM_MODEL}\")\n",
+ "print(f\" Retriever: top-{TOP_K} chunks\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## ๐งช Step 9 โ Test the Full RAG Pipeline"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def ask(question):\n",
+ " \"\"\"Ask the mentor a question.\"\"\"\n",
+ " print(f\"\\n๐ Question: {question}\")\n",
+ " print(\"๐ Searching study material...\\n\")\n",
+ " result = rag_chain.invoke({\"query\": question})\n",
+ " print(\"๐ Mentor's Answer:\")\n",
+ " print(\"โ\" * 60)\n",
+ " print(result[\"result\"])\n",
+ " print(\"โ\" * 60)\n",
+ " print(\"\\n๐ Sources:\")\n",
+ " seen = set()\n",
+ " for doc in result.get(\"source_documents\", []):\n",
+ " key = doc.metadata.get(\"source\", \"?\")\n",
+ " if key not in seen:\n",
+ " seen.add(key)\n",
+ " print(f\" [{doc.metadata.get('subject','?')}] {doc.metadata.get('filename','?')}\")\n",
+ "\n",
+ "# Test it!\n",
+ "ask(\"Explain Newton's laws of motion with examples\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## ๐ Step 10 โ Launch Gradio Chat UI (Public Link)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import gradio as gr\n",
+ "\n",
+ "CUSTOM_CSS = \"\"\"\n",
+ ".gradio-container {\n",
+ " max-width: 900px !important;\n",
+ " margin: auto !important;\n",
+ " font-family: 'Inter', 'Segoe UI', sans-serif !important;\n",
+ "}\n",
+ ".header-section {\n",
+ " text-align: center;\n",
+ " padding: 20px 0 10px;\n",
+ " background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n",
+ " border-radius: 16px;\n",
+ " margin-bottom: 20px;\n",
+ " color: white;\n",
+ "}\n",
+ ".header-section h1 { font-size: 2em; margin: 0; color: white !important; }\n",
+ ".header-section p { opacity: 0.9; margin: 5px 0 0; font-size: 1.05em; color: #e8e8ff !important; }\n",
+ ".chatbot { border-radius: 12px !important; min-height: 450px !important; }\n",
+ ".primary-btn {\n",
+ " background: linear-gradient(135deg, #667eea, #764ba2) !important;\n",
+ " border: none !important; color: white !important;\n",
+ " border-radius: 10px !important; font-weight: 600 !important;\n",
+ "}\n",
+ ".primary-btn:hover { opacity: 0.9 !important; transform: translateY(-1px); }\n",
+ "\"\"\"\n",
+ "\n",
+ "def format_sources(docs):\n",
+ " if not docs: return \"\"\n",
+ " seen, lines = set(), [\"\\n\\n---\\n๐ **Sources Referenced:**\\n\"]\n",
+ " for doc in docs:\n",
+ " key = f\"{doc.metadata.get('subject','?')}/{doc.metadata.get('source','?')}\"\n",
+ " if key not in seen:\n",
+ " seen.add(key)\n",
+ " lines.append(f\"**โข** `{doc.metadata.get('subject','?')}` โ _{doc.metadata.get('filename','?')}_\")\n",
+ " return \"\\n\".join(lines)\n",
+ "\n",
+ "def respond(message, history):\n",
+ " if not message.strip():\n",
+ " return \"\", history\n",
+ " result = rag_chain.invoke({\"query\": message})\n",
+ " answer = result[\"result\"] + format_sources(result.get(\"source_documents\", []))\n",
+ " history.append((message, answer))\n",
+ " return \"\", history\n",
+ "\n",
+ "def clear_chat():\n",
+ " return [], \"\"\n",
+ "\n",
+ "with gr.Blocks(css=CUSTOM_CSS, title=\"Study Ocean AI Mentor\", theme=gr.themes.Soft()) as demo:\n",
+ " gr.HTML(\"\"\"\n",
+ "
\n",
+ " \"\"\")\n",
+ " chatbot = gr.Chatbot(\n",
+ " label=\"๐ฌ Chat with Your Mentor\",\n",
+ " elem_classes=[\"chatbot\"],\n",
+ " height=480,\n",
+ " show_copy_button=True,\n",
+ " bubble_full_width=False,\n",
+ " avatar_images=(None, \"https://api.dicebear.com/7.x/bottts/svg?seed=mentor&backgroundColor=6366f1\"),\n",
+ " )\n",
+ " with gr.Row():\n",
+ " msg = gr.Textbox(\n",
+ " placeholder=\"Type your doubt here... e.g., 'Explain Newton's third law'\",\n",
+ " scale=5, lines=1, max_lines=3, show_label=False, container=False,\n",
+ " )\n",
+ " submit_btn = gr.Button(\"Ask ๐\", elem_classes=[\"primary-btn\"], scale=1)\n",
+ " with gr.Row():\n",
+ " clear_btn = gr.Button(\"๐๏ธ Clear Chat\", size=\"sm\")\n",
+ "\n",
+ " gr.Examples(\n",
+ " examples=[\n",
+ " \"What are Newton's laws of motion? Explain with examples.\",\n",
+ " \"Explain the difference between mitosis and meiosis.\",\n",
+ " \"What is the Pythagorean theorem and how do I apply it?\",\n",
+ " \"Explain the concept of supply and demand in economics.\",\n",
+ " \"What are the key features of the Indian Constitution?\",\n",
+ " ],\n",
+ " inputs=msg, label=\"๐ก Try these example questions:\",\n",
+ " )\n",
+ "\n",
+ " msg.submit(respond, [msg, chatbot], [msg, chatbot])\n",
+ " submit_btn.click(respond, [msg, chatbot], [msg, chatbot])\n",
+ " clear_btn.click(clear_chat, outputs=[chatbot, msg])\n",
+ "\n",
+ "# Launch with share=True to get a public Gradio URL!\n",
+ "demo.launch(share=True, debug=False)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "## ๐ป Step 11 โ Use Pre-built Vector Store (Skip Re-ingestion)\n",
+ "\n",
+ "> **Run this cell INSTEAD of Steps 4โ6 if you already have a saved vector store on Drive**\n",
+ "> This lets you skip the ~1 hour ingestion on future sessions!"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# โโ Load existing vector store from Drive โโโโโโโโโโโโโโโโโโโโ\n",
+ "# โ ๏ธ Only run this cell if you've already done ingestion before!\n",
+ "# Skip this if running ingestion fresh (Steps 4-6).\n",
+ "\n",
+ "from langchain_community.vectorstores import FAISS\n",
+ "from langchain_huggingface import HuggingFaceEmbeddings\n",
+ "import torch, os\n",
+ "\n",
+ "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
+ "print(f\"๐ง Loading embeddings on {device.upper()}...\")\n",
+ "\n",
+ "embeddings = HuggingFaceEmbeddings(\n",
+ " model_name=EMBEDDING_MODEL,\n",
+ " model_kwargs={\"device\": device},\n",
+ " encode_kwargs={\"normalize_embeddings\": True},\n",
+ ")\n",
+ "\n",
+ "# Try Drive path first, fall back to local\n",
+ "store_path = VECTOR_STORE_DRIVE_PATH if os.path.exists(VECTOR_STORE_DRIVE_PATH) else VECTOR_STORE_LOCAL\n",
+ "print(f\"๐ Loading vector store from: {store_path}\")\n",
+ "\n",
+ "vector_store = FAISS.load_local(\n",
+ " store_path,\n",
+ " embeddings,\n",
+ " allow_dangerous_deserialization=True,\n",
+ ")\n",
+ "\n",
+ "print(f\"โ
Loaded! Total vectors: {vector_store.index.ntotal}\")\n",
+ "print(\" Now run Steps 8 โ 10 to start chatting!\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "## ๐ฅ Step 12 โ Download Vector Store to Your Laptop\n",
+ "\n",
+ "> Run this to zip and download the vector store directly to your PC (no Drive needed)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import shutil\n",
+ "from google.colab import files\n",
+ "\n",
+ "# Zip the vector store\n",
+ "print(\"๐ฆ Zipping vector store...\")\n",
+ "zip_path = \"/content/vector_store_export.zip\"\n",
+ "shutil.make_archive(\"/content/vector_store_export\", 'zip', VECTOR_STORE_LOCAL)\n",
+ "\n",
+ "size_mb = os.path.getsize(zip_path) / 1e6\n",
+ "print(f\" ZIP size: {size_mb:.1f} MB\")\n",
+ "\n",
+ "# Download to local machine\n",
+ "print(\"โฌ๏ธ Downloading to your computer...\")\n",
+ "files.download(zip_path)\n",
+ "print(\"\\nโ
Download started!\")\n",
+ "print(\" Extract to: Study_Ocean Rag/data/vector_store/\")\n",
+ "print(\" Then run locally: python main.py chat\")"
+ ]
+ }
+ ]
+}
diff --git a/api.py b/api.py
new file mode 100644
index 0000000000000000000000000000000000000000..ca5b84551bd279d4d503d02b298cfa5e90a2da03
--- /dev/null
+++ b/api.py
@@ -0,0 +1,65 @@
+import os
+
+# memory saving trick for Render 512MB limit!
+os.environ["OMP_NUM_THREADS"] = "1"
+os.environ["MKL_NUM_THREADS"] = "1"
+os.environ["OPENBLAS_NUM_THREADS"] = "1"
+os.environ["VECLIB_MAXIMUM_THREADS"] = "1"
+os.environ["NUMEXPR_NUM_THREADS"] = "1"
+
+from fastapi import FastAPI
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.responses import FileResponse
+from pydantic import BaseModel
+from rag_chain import get_chain, ask_question
+import uvicorn
+
+
+app = FastAPI(title="Study Ocean RAG API")
+
+# Allow requests from your frontend (CORS)
+app.add_middleware(
+ CORSMiddleware,
+ allow_origins=["*"], # In production, replace with your frontend URL
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+)
+
+@app.get("/")
+def serve_frontend():
+ return FileResponse("frontend_demo.html")
+
+@app.get("/logo.png")
+def serve_logo():
+ if os.path.exists("logo.png"):
+ return FileResponse("logo.png")
+ return {"error": "Logo not found"}
+
+print("๐ Initializing Study Ocean AI Mentor...")
+chain = get_chain()
+print("โ
API Ready to answer student doubts!")
+
+class Query(BaseModel):
+ question: str
+
+@app.post("/api/ask")
+def ask(query: Query):
+ try:
+ # Pass the question to your LangChain RAG
+ result = ask_question(chain, query.question)
+ return {
+ "answer": result["answer"],
+ "sources": result["sources"]
+ }
+ except Exception as e:
+ import traceback
+ traceback.print_exc()
+ return {
+ "answer": f"Backend Error: {str(e)}",
+ "sources": []
+ }
+
+if __name__ == "__main__":
+ # Run the API on port 8000
+ uvicorn.run(app, host="0.0.0.0", port=8000)
diff --git a/app.py b/app.py
new file mode 100644
index 0000000000000000000000000000000000000000..da4c4b1cd852d2dc533aa93c85dc69dc3911e99f
--- /dev/null
+++ b/app.py
@@ -0,0 +1,203 @@
+"""
+Study Ocean AI Mentor โ Gradio Web Interface
+
+A beautiful, student-friendly chat interface for the RAG-based
+doubt-solving mentor. Built with Gradio for easy deployment.
+"""
+
+import gradio as gr
+from rag_chain import get_chain, ask_question
+
+# โโโ Custom CSS for Premium Look โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+CUSTOM_CSS = """
+/* Global */
+.gradio-container {
+ max-width: 900px !important;
+ margin: auto !important;
+ font-family: 'Inter', 'Segoe UI', sans-serif !important;
+}
+
+/* Header */
+.header-section {
+ text-align: center;
+ padding: 20px 0 10px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border-radius: 16px;
+ margin-bottom: 20px;
+ color: white;
+}
+
+.header-section h1 {
+ font-size: 2em;
+ margin: 0;
+ color: white !important;
+}
+
+.header-section p {
+ opacity: 0.9;
+ margin: 5px 0 0;
+ font-size: 1.05em;
+ color: #e8e8ff !important;
+}
+
+/* Chat area */
+.chatbot {
+ border-radius: 12px !important;
+ border: 1px solid #e2e8f0 !important;
+ min-height: 450px !important;
+}
+
+/* Source cards */
+.source-card {
+ background: #f8fafc;
+ border: 1px solid #e2e8f0;
+ border-radius: 8px;
+ padding: 10px 14px;
+ margin: 4px 0;
+ font-size: 0.9em;
+}
+
+.source-card strong {
+ color: #6366f1;
+}
+
+/* Override dark mode */
+.dark .source-card {
+ background: #1e293b;
+ border-color: #334155;
+}
+
+/* Buttons */
+.primary-btn {
+ background: linear-gradient(135deg, #667eea, #764ba2) !important;
+ border: none !important;
+ color: white !important;
+ border-radius: 10px !important;
+ font-weight: 600 !important;
+ padding: 10px 24px !important;
+}
+
+.primary-btn:hover {
+ opacity: 0.9 !important;
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4) !important;
+}
+"""
+
+# โโโ Initialize chain on startup โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+print("๐ Initializing Study Ocean AI Mentor...")
+chain = get_chain()
+print("โ
Ready to answer student doubts!\n")
+
+
+def format_sources(sources: list) -> str:
+ """Format source documents into a readable string."""
+ if not sources:
+ return ""
+
+ lines = ["\n\n---\n๐ **Sources Referenced:**\n"]
+ for i, src in enumerate(sources, 1):
+ lines.append(f"**{i}.** `{src['subject']}` โ _{src['file']}_")
+ return "\n".join(lines)
+
+
+def respond(message: str, chat_history: list) -> tuple:
+ """Handle a student's question and return the mentor's response."""
+ if not message.strip():
+ return "", chat_history
+
+ # Get answer from RAG chain
+ result = ask_question(chain, message)
+ answer = result["answer"]
+ sources = result["sources"]
+
+ # Append source references to the answer
+ answer_with_sources = answer + format_sources(sources)
+
+ # Update chat history
+ chat_history.append((message, answer_with_sources))
+
+ return "", chat_history
+
+
+def clear_chat():
+ """Clear the chat history."""
+ return [], ""
+
+
+# โโโ Build Gradio Interface โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+with gr.Blocks(title="Study Ocean AI Mentor") as app:
+
+ # Header
+ gr.HTML("""
+
+ """)
+
+ # Chatbot
+ chatbot = gr.Chatbot(
+ label="๐ฌ Chat with Your Mentor",
+ elem_classes=["chatbot"],
+ height=480,
+ avatar_images=(None, "https://api.dicebear.com/7.x/bottts/svg?seed=mentor&backgroundColor=6366f1"),
+ )
+
+ # Input area
+ with gr.Row():
+ msg = gr.Textbox(
+ placeholder="Type your doubt here... e.g., 'Explain Newton's third law with examples'",
+ label="Your Question",
+ scale=5,
+ lines=1,
+ max_lines=3,
+ show_label=False,
+ container=False,
+ )
+ submit_btn = gr.Button("Ask ๐", elem_classes=["primary-btn"], scale=1)
+
+ # Action buttons
+ with gr.Row():
+ clear_btn = gr.Button("๐๏ธ Clear Chat", size="sm")
+
+ # Example questions
+ gr.Examples(
+ examples=[
+ "What are Newton's laws of motion? Explain with examples.",
+ "Explain the difference between mitosis and meiosis.",
+ "What is the Pythagorean theorem and how do I use it?",
+ "Explain the concept of supply and demand in economics.",
+ "What are the key features of the Indian Constitution?",
+ "Solve: Find the derivative of f(x) = 3xยฒ + 2x - 5",
+ ],
+ inputs=msg,
+ label="๐ก Try these example questions:",
+ )
+
+ # Tips section
+ with gr.Accordion("๐ Tips for Better Answers", open=False):
+ gr.Markdown("""
+ - **Be specific**: Instead of "explain physics", ask "explain projectile motion with examples"
+ - **Mention the topic**: "In organic chemistry, what is an SN2 reaction?"
+ - **Ask for steps**: "Solve step by step: integrate sin(x)cos(x) dx"
+ - **Request comparisons**: "Compare TCP and UDP protocols"
+ - **Ask for formulas**: "List all the trigonometric identities"
+ """)
+
+ # Event handlers
+ msg.submit(respond, [msg, chatbot], [msg, chatbot])
+ submit_btn.click(respond, [msg, chatbot], [msg, chatbot])
+ clear_btn.click(clear_chat, outputs=[chatbot, msg])
+
+
+# โโโ Launch โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+if __name__ == "__main__":
+ app.launch(
+ server_name="0.0.0.0",
+ server_port=7860,
+ share=False,
+ show_error=True,
+ css=CUSTOM_CSS,
+ theme=gr.themes.Soft(),
+ )
diff --git a/config.py b/config.py
new file mode 100644
index 0000000000000000000000000000000000000000..cfd229319f0d971a476887cf73ceab951bc92a82
--- /dev/null
+++ b/config.py
@@ -0,0 +1,30 @@
+"""
+Configuration module for Study Ocean RAG Student Mentor.
+Loads environment variables and provides default settings.
+"""
+
+import os
+from dotenv import load_dotenv
+
+load_dotenv()
+
+# โโโ API Keys โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY", "")
+
+# โโโ Model Configuration โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "all-MiniLM-L6-v2")
+LLM_MODEL = os.getenv("LLM_MODEL", "gemini-2.0-flash")
+
+# โโโ Paths โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+BASE_DIR = os.path.dirname(os.path.abspath(__file__))
+PDF_DIR = os.path.join(BASE_DIR, os.getenv("PDF_DIR", "data/pdfs"))
+VECTOR_STORE_PATH = os.path.join(BASE_DIR, os.getenv("VECTOR_STORE_PATH", "data/vector_store"))
+
+# โโโ Text Splitting โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+CHUNK_SIZE = int(os.getenv("CHUNK_SIZE", "1000"))
+CHUNK_OVERLAP = int(os.getenv("CHUNK_OVERLAP", "200"))
+
+# โโโ RAG Parameters โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+TOP_K_RESULTS = 2 # Number of relevant chunks to retrieve (lowered for faster responses)
+TEMPERATURE = 0.3 # LLM temperature (lower = more focused)
+MAX_OUTPUT_TOKENS = 2048 # Max tokens in LLM response
diff --git a/data/pdfs/StudyOceanPDFs/BIOLOGY_FLASHCARD_FOR_NEET.pdf b/data/pdfs/StudyOceanPDFs/BIOLOGY_FLASHCARD_FOR_NEET.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..b3cb23e88bb293c7997a47761ad4f2591e2c57fc
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/BIOLOGY_FLASHCARD_FOR_NEET.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:dbfe25b3be671a226d8d27b3eb34fb60911846b5918ac78e72aed009b7137092
+size 36021223
diff --git a/data/pdfs/StudyOceanPDFs/CBSE_2026_Class_12_Physics_Set_2_Question_Paper_With_Solutions_PDF.pdf b/data/pdfs/StudyOceanPDFs/CBSE_2026_Class_12_Physics_Set_2_Question_Paper_With_Solutions_PDF.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..e85b05121d4f854192e01250cca93f1c557def67
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/CBSE_2026_Class_12_Physics_Set_2_Question_Paper_With_Solutions_PDF.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2ee3feeb5e2c6b2fb6eb414e9d4cc11c008a4e7ff53564f60f0c88bdfe66c705
+size 561121
diff --git a/data/pdfs/StudyOceanPDFs/CENGAGE_IOC_CRUX_BY_studyoceanbooks.PDF b/data/pdfs/StudyOceanPDFs/CENGAGE_IOC_CRUX_BY_studyoceanbooks.PDF
new file mode 100644
index 0000000000000000000000000000000000000000..c1c3a272bb1acc2e8211096f7d0265a606e41a1e
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/CENGAGE_IOC_CRUX_BY_studyoceanbooks.PDF
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1fe208f6066c088482bfb970f53d969c67045459cd7fde203f2cdc1bd421aeda
+size 6955370
diff --git a/data/pdfs/StudyOceanPDFs/COM_COLLISION_Short_Notes_1_.pdf b/data/pdfs/StudyOceanPDFs/COM_COLLISION_Short_Notes_1_.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..7dbbc7dfec4a29b3ec41c0dc136f8d4de02fe476
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/COM_COLLISION_Short_Notes_1_.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ac84e71f21e1293fbb3463f89d43b6cfb6f21d02995eca69f8febb80170f8031
+size 1507057
diff --git a/data/pdfs/StudyOceanPDFs/Chemistry_Flash_Cards_Colourful_Complete.pdf b/data/pdfs/StudyOceanPDFs/Chemistry_Flash_Cards_Colourful_Complete.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..44e888d28b13161ec02cc458639b50170120398b
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/Chemistry_Flash_Cards_Colourful_Complete.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:660f51ef507ec000388389a1660387d35d3a2f897807fa496a93a89394eeb187
+size 9544339
diff --git a/data/pdfs/StudyOceanPDFs/Chemistry_Flashcard_for_JEE_NEET.pdf b/data/pdfs/StudyOceanPDFs/Chemistry_Flashcard_for_JEE_NEET.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..424e1ce31c316796edcfac39fdcec2a79b5b6f9a
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/Chemistry_Flashcard_for_JEE_NEET.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:467be87f0dc909fc49f89786b890fc99e1c0b858c22ebe46b548fdebea80a77e
+size 10429728
diff --git a/data/pdfs/StudyOceanPDFs/Chemistry_Formula_Booklet_Hashlearn.pdf b/data/pdfs/StudyOceanPDFs/Chemistry_Formula_Booklet_Hashlearn.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..3f5a551c49e84fcab7b35b43b6d57857e8066d5b
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/Chemistry_Formula_Booklet_Hashlearn.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:40214c132da21dffb625a043e616ac13b829a789c8f74ba3d1e12bfc0145554b
+size 6484359
diff --git a/data/pdfs/StudyOceanPDFs/Circular_dyanamic_short_notes_1_.pdf b/data/pdfs/StudyOceanPDFs/Circular_dyanamic_short_notes_1_.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..5fae94e5edc85f7dda0c2ce5a4613de18d608cc8
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/Circular_dyanamic_short_notes_1_.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:87714aff5c3b6135f26fb58c59ef472fd1f4f7ff0985477513fd20b2cc0d3c11
+size 456696
diff --git a/data/pdfs/StudyOceanPDFs/Circular_motion_in_kinematics_short_notes_1_.pdf b/data/pdfs/StudyOceanPDFs/Circular_motion_in_kinematics_short_notes_1_.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..07561a7612c863d320a63524c0df8157c1152755
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/Circular_motion_in_kinematics_short_notes_1_.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:14e351fa21a2cdfc7bad9177967118c7d153f32e7637cff0381d2c67c6c96604
+size 1128082
diff --git a/data/pdfs/StudyOceanPDFs/Rotational_motion_short_notes_1_.pdf b/data/pdfs/StudyOceanPDFs/Rotational_motion_short_notes_1_.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..10b7998185ba4d6fee64062fbf2564caa5a21843
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/Rotational_motion_short_notes_1_.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e6b5ff18923f0413d3470b1c28186194938b9e2d74d336c5e42252ad22d38628
+size 2888938
diff --git a/data/pdfs/StudyOceanPDFs/Thermal_properties_of_matter_short_notes_.pdf b/data/pdfs/StudyOceanPDFs/Thermal_properties_of_matter_short_notes_.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..3f668295a0f6db2c6bbb3ab505b8959016f8b4f2
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/Thermal_properties_of_matter_short_notes_.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4d4beba58867ed59e25b8a15193f991fb34fedbec21e20797cec6ac82450bc44
+size 2325417
diff --git a/data/pdfs/StudyOceanPDFs/Units_measurements_and_errors_short_notes_1_.pdf b/data/pdfs/StudyOceanPDFs/Units_measurements_and_errors_short_notes_1_.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..081f59f4f6452f7a70adee290b5f9911ed39ff3b
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/Units_measurements_and_errors_short_notes_1_.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2ed834b2629337279ff4793e8d771311ae2c110d3b6a4fd36db8a1a9ea0cf58c
+size 2533427
diff --git a/data/pdfs/StudyOceanPDFs/Vertical_circular_motion_short_notes_1_.pdf b/data/pdfs/StudyOceanPDFs/Vertical_circular_motion_short_notes_1_.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..98ddb4912038d1ec9e6f64307515a23629075d2f
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/Vertical_circular_motion_short_notes_1_.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e5710614b9d68770e10f4792a0f8fbf3cf99a4af569cab6a094d9d555abbd508
+size 716852
diff --git a/data/pdfs/StudyOceanPDFs/Wave_motion_short_notes_1_.pdf b/data/pdfs/StudyOceanPDFs/Wave_motion_short_notes_1_.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..44ebc3aa188913a940cbfeaedeec745637ed2773
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/Wave_motion_short_notes_1_.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3200903717291d721e29e4aba296c2493351c083b0b9675ff2dfc0e34e82a2a2
+size 2288200
diff --git a/data/pdfs/StudyOceanPDFs/Work_Power_Energy_Short_Notes_1_.pdf b/data/pdfs/StudyOceanPDFs/Work_Power_Energy_Short_Notes_1_.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..359651dadeb9f497b0d30980e795799f95db829f
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/Work_Power_Energy_Short_Notes_1_.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5f6f14e982aff842c61a2f159aaf9a044742cd0aa3d07a7f721e3d08a7d7e20f
+size 2026851
diff --git a/data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_1_question_paper_2026_1771575627702.pdf b/data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_1_question_paper_2026_1771575627702.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..a0b2519ef00ee4ddf32b6a3e226c3bd04e6e87e1
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_1_question_paper_2026_1771575627702.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ec9e62a8630a7a359f36927f3b3fd8ec0b933a665b4d1c9935a81bbcadb2f753
+size 2405782
diff --git a/data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_2_question_paper_2026_1771575619105.pdf b/data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_2_question_paper_2026_1771575619105.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..2b3d893fd14c2a32d633bc7c0a464789cbe5aa7a
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_2_question_paper_2026_1771575619105.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:862f05a505453e84746b31e88fd4d42dad7cbf518a985ae974f103c4f7dba24e
+size 2364093
diff --git a/data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_3_question_paper_2026_1771575610905.pdf b/data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_3_question_paper_2026_1771575610905.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..cce63f35968b390b927fb98716ab9b57ced23fe8
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_3_question_paper_2026_1771575610905.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2f3a677b0c4457ff58985f9dc6a56c65f294fe4bacfc420872774c81b71b47b6
+size 2475777
diff --git a/data/pdfs/StudyOceanPDFs/chapter_10.pdf b/data/pdfs/StudyOceanPDFs/chapter_10.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..34b776cb9adcba3409cacdb6678e60fbb7298df0
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/chapter_10.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:60502d7ef8f74d65cdef3b847d6610f4562e60cae90717a982a800785c6510f0
+size 1852257
diff --git a/data/pdfs/StudyOceanPDFs/chapter_11.pdf b/data/pdfs/StudyOceanPDFs/chapter_11.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..9393807bf555d918fbd26391eb83f37ce05ac4f4
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/chapter_11.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f79bb617255b2a183aabf9ff4a34fc27afe91797ed80f098a1e6b4c767bf879f
+size 1842601
diff --git a/data/pdfs/StudyOceanPDFs/chapter_12.pdf b/data/pdfs/StudyOceanPDFs/chapter_12.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..bd669f2628bec97f88401c6e83d5942e767e5a99
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/chapter_12.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4d33faddf847f9c6f05f32143f84ed7038fe3d2bf4f9462a07c7083b18b30258
+size 4467748
diff --git a/data/pdfs/StudyOceanPDFs/chapter_13.pdf b/data/pdfs/StudyOceanPDFs/chapter_13.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..17eef160f11cb8ec6c2f71ef56caa93f808c2c13
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/chapter_13.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8198c5c589362a88b1dbf5d39b37c1faa0eebbec02d14a039d8146563a4cadf5
+size 3530630
diff --git a/data/pdfs/StudyOceanPDFs/chapter_14.pdf b/data/pdfs/StudyOceanPDFs/chapter_14.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..692b61e49c5b26c12715069421cb5514fa4b0b5b
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/chapter_14.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:856a0e87419c68485bd1b852dedfdb19fdef9017ad278086b20f1485addb6950
+size 3381393
diff --git a/data/pdfs/StudyOceanPDFs/chapter_15.pdf b/data/pdfs/StudyOceanPDFs/chapter_15.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..3826aab35e5c00eaad8e0feec07b959d9ddf08e4
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/chapter_15.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:40fa8f886030c1fa244779661159cacd06763d252e0f13d2ffd39000b856ee64
+size 2954844
diff --git a/data/pdfs/StudyOceanPDFs/chapter_2.pdf b/data/pdfs/StudyOceanPDFs/chapter_2.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..0cd396dec4c14bbd209fc7857b1c4fea254a0fcf
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/chapter_2.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:563cf1492644cf09e14573744e176230e1157a75bcbeeabe118d9ac9acc5e959
+size 1577447
diff --git a/data/pdfs/StudyOceanPDFs/chapter_3_1_.pdf b/data/pdfs/StudyOceanPDFs/chapter_3_1_.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..3c20167bd069b3dde3d5206b385bcd3305084ebd
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/chapter_3_1_.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:93f99211db8fa7628a1cf092bc3b19f98124ae34208c30d0a99bb359788fbe2e
+size 2022459
diff --git a/data/pdfs/StudyOceanPDFs/chapter_4.pdf b/data/pdfs/StudyOceanPDFs/chapter_4.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..7c583a636025f590007b78cdec2e008e650cba3a
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/chapter_4.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b26907e1916bf831705c9cfcfb32c1d8c92faee0180b107947be830f1116cdde
+size 1700543
diff --git a/data/pdfs/StudyOceanPDFs/chapter_5.pdf b/data/pdfs/StudyOceanPDFs/chapter_5.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..3c56f2e76407db6f30f826d33254e83d857af88c
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/chapter_5.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8c5c402f9d758771f4e22dc3f01cd6fa61c1c5b56a83c1210d2929bba739486f
+size 987219
diff --git a/data/pdfs/StudyOceanPDFs/chapter_6.pdf b/data/pdfs/StudyOceanPDFs/chapter_6.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..9c92c4394c3177ea9ce2ed85ee66e4aa0474e4c2
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/chapter_6.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:592a39941a139568374430fbaac2a277932baecc79b742937662a3087b7961df
+size 3612728
diff --git a/data/pdfs/StudyOceanPDFs/chapter_7.pdf b/data/pdfs/StudyOceanPDFs/chapter_7.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..cb177193e1361703539d54fb923203dda95bae84
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/chapter_7.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:dbc4c8dbcbbf29a76295581a9d4f1e9084a6b7cfb5726af5559d8d3f34802ac8
+size 1028339
diff --git a/data/pdfs/StudyOceanPDFs/vector_sheets_Cyan_notes_1_1_.pdf b/data/pdfs/StudyOceanPDFs/vector_sheets_Cyan_notes_1_1_.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..c203f88d49a50a3ac409bc2166cc3c77b6ab0d6c
--- /dev/null
+++ b/data/pdfs/StudyOceanPDFs/vector_sheets_Cyan_notes_1_1_.pdf
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3c2ab1f57ee66147454fcae197dadbfb8e4d718bb17eece72eb5d257ec97abce
+size 903709
diff --git a/data/vector_store/index.faiss b/data/vector_store/index.faiss
new file mode 100644
index 0000000000000000000000000000000000000000..87fe646fd61ef5a765c644d90462d6d88cf5f933
--- /dev/null
+++ b/data/vector_store/index.faiss
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:eabc69fd7afeee324561ec01698a1fbe0464ab7cade2790647f67b504074664a
+size 18837549
diff --git a/data/vector_store/index.pkl b/data/vector_store/index.pkl
new file mode 100644
index 0000000000000000000000000000000000000000..42ae991c1a0baf10d0274693b14b062fa9d5b80b
--- /dev/null
+++ b/data/vector_store/index.pkl
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e0a5e3e437a7b95670e59ebd02a2d16d648b1f0cd463c61369ff0bc910059b03
+size 10367491
diff --git a/frontend_demo.html b/frontend_demo.html
new file mode 100644
index 0000000000000000000000000000000000000000..08da11fdfbede2da5b2f00f011d776e54c0ce597
--- /dev/null
+++ b/frontend_demo.html
@@ -0,0 +1,267 @@
+
+
+
+
+
+ Study Ocean | AI Mentor
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Hey! I'm your Study Ocean Mentor. Ask me any doubt from your materials, and I'll explain it clearly. ๐
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ingest.py b/ingest.py
new file mode 100644
index 0000000000000000000000000000000000000000..d14806aa0f92b7f16ce1cc97f757fd845e16df43
--- /dev/null
+++ b/ingest.py
@@ -0,0 +1,171 @@
+"""
+PDF Ingestion & Vector Store Builder for Study Ocean RAG.
+
+This module:
+1. Recursively scans the PDF directory for all .pdf files
+2. Extracts text from each PDF using PyMuPDF (faster & more reliable)
+3. Splits text into overlapping chunks
+4. Generates embeddings using a local sentence-transformer model
+5. Stores embeddings in a FAISS vector store for fast retrieval
+"""
+
+import os
+import sys
+import time
+import fitz # PyMuPDF
+from langchain.text_splitter import RecursiveCharacterTextSplitter
+from langchain_community.vectorstores import FAISS
+from langchain_huggingface import HuggingFaceEmbeddings
+
+import config
+
+
+def collect_pdf_paths(pdf_dir: str) -> list[str]:
+ """Recursively collect all PDF file paths from the given directory."""
+ pdf_paths = []
+ for root, _, files in os.walk(pdf_dir):
+ for file in files:
+ if file.lower().endswith(".pdf"):
+ pdf_paths.append(os.path.join(root, file))
+ return sorted(pdf_paths)
+
+
+def extract_text_from_pdf(pdf_path: str) -> str:
+ """Extract all text from a PDF using PyMuPDF."""
+ text = ""
+ try:
+ doc = fitz.open(pdf_path)
+ for page_num in range(len(doc)):
+ page = doc[page_num]
+ text += page.get_text()
+ doc.close()
+ except Exception as e:
+ print(f" โ Error reading {os.path.basename(pdf_path)}: {e}")
+ return text
+
+
+def build_documents(pdf_paths: list[str]) -> list:
+ """
+ Extract text from all PDFs and split into chunks.
+ Each chunk carries metadata about its source PDF and page info.
+ """
+ text_splitter = RecursiveCharacterTextSplitter(
+ chunk_size=config.CHUNK_SIZE,
+ chunk_overlap=config.CHUNK_OVERLAP,
+ separators=["\n\n", "\n", ". ", " ", ""],
+ length_function=len,
+ )
+
+ all_documents = []
+ for i, pdf_path in enumerate(pdf_paths, 1):
+ filename = os.path.basename(pdf_path)
+ relative_path = os.path.relpath(pdf_path, config.PDF_DIR)
+ # Derive subject from folder structure
+ parts = relative_path.replace("\\", "/").split("/")
+ subject = parts[0] if len(parts) > 1 else "General"
+
+ print(f" [{i}/{len(pdf_paths)}] Processing: {relative_path}")
+
+ text = extract_text_from_pdf(pdf_path)
+ if not text.strip():
+ print(f" โ No text extracted (might be scanned/image PDF)")
+ continue
+
+ # Create chunks with rich metadata
+ chunks = text_splitter.create_documents(
+ texts=[text],
+ metadatas=[{
+ "source": relative_path,
+ "filename": filename,
+ "subject": subject,
+ }]
+ )
+ all_documents.extend(chunks)
+
+ return all_documents
+
+
+def create_vector_store(documents: list, embeddings) -> FAISS:
+ """Create a FAISS vector store from documents."""
+ print(f"\n๐ Creating vector store with {len(documents)} chunks...")
+
+ # Process in batches to avoid memory issues
+ batch_size = 500
+ vector_store = None
+
+ for i in range(0, len(documents), batch_size):
+ batch = documents[i:i + batch_size]
+ batch_num = (i // batch_size) + 1
+ total_batches = (len(documents) + batch_size - 1) // batch_size
+ print(f" Embedding batch {batch_num}/{total_batches} ({len(batch)} chunks)...")
+
+ if vector_store is None:
+ vector_store = FAISS.from_documents(batch, embeddings)
+ else:
+ batch_store = FAISS.from_documents(batch, embeddings)
+ vector_store.merge_from(batch_store)
+
+ return vector_store
+
+
+def run_ingestion():
+ """Main ingestion pipeline."""
+ print("=" * 60)
+ print("๐ Study Ocean RAG โ PDF Ingestion Pipeline")
+ print("=" * 60)
+
+ # 1. Collect PDFs
+ print(f"\n๐ Scanning for PDFs in: {config.PDF_DIR}")
+ pdf_paths = collect_pdf_paths(config.PDF_DIR)
+
+ if not pdf_paths:
+ print("โ No PDF files found! Make sure your PDFs are in the data/pdfs directory.")
+ print(" Extract StudyOceanPDFs.zip into data/pdfs/ first.")
+ sys.exit(1)
+
+ print(f" Found {len(pdf_paths)} PDF files\n")
+
+ # 2. Extract and chunk
+ print("๐ Extracting text and creating chunks...")
+ start_time = time.time()
+ documents = build_documents(pdf_paths)
+ extract_time = time.time() - start_time
+ print(f"\n โ
Created {len(documents)} text chunks in {extract_time:.1f}s")
+
+ if not documents:
+ print("โ No text could be extracted from any PDF. Exiting.")
+ sys.exit(1)
+
+ # 3. Initialize embeddings
+ print(f"\n๐ง Loading embedding model: {config.EMBEDDING_MODEL}")
+ embeddings = HuggingFaceEmbeddings(
+ model_name=config.EMBEDDING_MODEL,
+ model_kwargs={"device": "cpu"},
+ encode_kwargs={"normalize_embeddings": True, "batch_size": 64},
+ )
+
+ # 4. Build vector store
+ start_time = time.time()
+ vector_store = create_vector_store(documents, embeddings)
+ embed_time = time.time() - start_time
+ print(f" โ
Vector store created in {embed_time:.1f}s")
+
+ # 5. Save to disk
+ os.makedirs(config.VECTOR_STORE_PATH, exist_ok=True)
+ print(f"\n๐พ Saving vector store to: {config.VECTOR_STORE_PATH}")
+ vector_store.save_local(config.VECTOR_STORE_PATH)
+ print(" โ
Vector store saved successfully!")
+
+ # Summary
+ print("\n" + "=" * 60)
+ print("โ
INGESTION COMPLETE")
+ print(f" PDFs processed: {len(pdf_paths)}")
+ print(f" Chunks created: {len(documents)}")
+ print(f" Extract time: {extract_time:.1f}s")
+ print(f" Embedding time: {embed_time:.1f}s")
+ print(f" Store location: {config.VECTOR_STORE_PATH}")
+ print("=" * 60)
+
+
+if __name__ == "__main__":
+ run_ingestion()
diff --git a/logo.png b/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..efb99a504e064452cb4ab89d927b002123589f98
--- /dev/null
+++ b/logo.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:67928e220f35fffadb9532a9694b8d878bfdad54d68d57614ee8ca2291a2695a
+size 258677
diff --git a/main.py b/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..749acdb0e2b02626100423d77ca898be3eba2dd8
--- /dev/null
+++ b/main.py
@@ -0,0 +1,107 @@
+"""
+Study Ocean RAG โ Main Entry Point
+
+Usage:
+ python main.py ingest # Ingest PDFs and build vector store
+ python main.py chat # Launch the Gradio web UI
+ python main.py ask # Ask a single question in the terminal
+"""
+
+import sys
+
+
+def print_banner():
+ print("""
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+โ ๐ Study Ocean AI Mentor โ
+โ RAG-based Student Doubt Solver โ
+โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฃ
+โ โ
+โ Commands: โ
+โ python main.py ingest โ Process PDFs & build index โ
+โ python main.py chat โ Launch web chat interface โ
+โ python main.py ask โ Ask a question in terminal โ
+โ โ
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ """)
+
+
+def run_ingest():
+ """Run the PDF ingestion pipeline."""
+ from ingest import run_ingestion
+ run_ingestion()
+
+
+def run_chat():
+ """Launch the Gradio web interface."""
+ from app import app
+ print("\n๐ Starting Study Ocean AI Mentor web interface...")
+ print(" Open http://localhost:7860 in your browser\n")
+ app.launch(
+ server_name="0.0.0.0",
+ server_port=7860,
+ share=False,
+ show_error=True,
+ )
+
+
+def run_ask():
+ """Interactive terminal Q&A mode."""
+ from rag_chain import get_chain, ask_question
+
+ print("\n๐ Study Ocean AI Mentor โ Terminal Mode")
+ print(" Type your question and press Enter.")
+ print(" Type 'quit' or 'exit' to stop.\n")
+
+ chain = get_chain()
+
+ while True:
+ try:
+ question = input("\n๐ Your doubt: ").strip()
+ except (KeyboardInterrupt, EOFError):
+ print("\n\n๐ Goodbye! Keep studying! ๐")
+ break
+
+ if question.lower() in ("quit", "exit", "q"):
+ print("\n๐ Goodbye! Keep studying! ๐")
+ break
+
+ if not question:
+ print(" โ Please type a question.")
+ continue
+
+ print("\n๐ Searching study material...\n")
+ result = ask_question(chain, question)
+
+ print("โ" * 50)
+ print(f"\n๐ Mentor's Answer:\n")
+ print(result["answer"])
+
+ if result["sources"]:
+ print(f"\n๐ Sources:")
+ for i, src in enumerate(result["sources"], 1):
+ print(f" {i}. [{src['subject']}] {src['file']}")
+ print("\n" + "โ" * 50)
+
+
+def main():
+ if len(sys.argv) < 2:
+ print_banner()
+ sys.exit(0)
+
+ command = sys.argv[1].lower()
+
+ if command == "ingest":
+ run_ingest()
+ elif command == "chat":
+ run_chat()
+ elif command == "ask":
+ run_ask()
+ else:
+ print(f"โ Unknown command: {command}")
+ print_banner()
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000000000000000000000000000000000000..d178bb26003aa281f231c26c16c58caeee9078cc
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,23 @@
+[project]
+name = "study-ocean-rag"
+version = "0.1.0"
+description = "AI Student Mentor - RAG-based doubt solver using study materials"
+readme = "README.md"
+requires-python = ">=3.13"
+dependencies = [
+ "ipykernel>=7.2.0",
+ "langchain>=0.3.0",
+ "langchain-community>=0.3.0",
+ "langchain-core>=0.3.0",
+ "langchain-google-genai>=2.1.0",
+ "langchain-huggingface>=0.1.0",
+ "pymupdf>=1.27.2.2",
+ "pypdf>=6.10.1",
+ "faiss-cpu>=1.9.0",
+ "chromadb>=0.6.0",
+ "sentence-transformers>=3.4.0",
+ "google-generativeai>=0.8.0",
+ "python-dotenv>=1.1.0",
+ "gradio>=5.0.0",
+ "tiktoken>=0.9.0",
+]
diff --git a/rag_chain.py b/rag_chain.py
new file mode 100644
index 0000000000000000000000000000000000000000..9bf23ac8d7d364d7c4635138a135662646f1daeb
--- /dev/null
+++ b/rag_chain.py
@@ -0,0 +1,154 @@
+"""
+RAG Chain module for Study Ocean Student Mentor.
+Reverted to stable RetrievalQA for compatibility with LangChain 0.2.x.
+"""
+
+from langchain_community.vectorstores import FAISS
+from langchain_huggingface import HuggingFaceEmbeddings
+from langchain_google_genai import ChatGoogleGenerativeAI
+from langchain.chains import RetrievalQA
+from langchain.prompts import PromptTemplate
+
+import config
+
+# โโโ System Prompt for Student Mentor โโโโโโโโโโโโโโโโโโโโโโโโ
+STUDENT_MENTOR_PROMPT = PromptTemplate(
+ input_variables=["context", "question"],
+ template="""You are **Study Ocean AI Mentor** โ a friendly, patient, and knowledgeable tutor who helps students understand their study material deeply.
+
+Your role:
+- You solve student doubts with clear, step-by-step explanations
+- You use simple language that students can easily understand
+- You provide examples and analogies when helpful
+- You reference the study material when answering
+- If the answer is in the provided context, use it; if not, say so honestly
+- You encourage students and make learning enjoyable
+
+**Important Guidelines:**
+1. Start with a brief, direct answer, then explain in detail
+2. Use bullet points, numbered steps, or tables for clarity
+3. If it's a math/science problem, show the solution step by step
+4. If it's a theory question, break it into key points
+5. Mention which topic/chapter the information relates to when possible
+6. If the user is just saying "hi", "how are you", or making casual conversation, respond warmly and naturally without mentioning study materials.
+7. If it's a genuine study question not covered in the context, say: "This isn't explicitly in your study materials, but here is the answer..."
+8. ๐ Use relevant emojis generously in every response to make learning fun and visually appealing for Gen-Z students!
+
+---
+
+๐ **Relevant Study Material:**
+{context}
+
+---
+
+๐ **Student's Question:**
+{question}
+
+---
+
+๐ **Mentor's Response:**
+"""
+)
+
+
+def load_vector_store() -> FAISS:
+ """Load the pre-built FAISS vector store from disk. Fallback to ingestion if it fails."""
+ embeddings = HuggingFaceEmbeddings(
+ model_name=config.EMBEDDING_MODEL,
+ model_kwargs={"device": "cpu"},
+ encode_kwargs={"normalize_embeddings": True},
+ )
+
+ try:
+ print("๐ Loading vector store...")
+ vector_store = FAISS.load_local(
+ config.VECTOR_STORE_PATH,
+ embeddings,
+ allow_dangerous_deserialization=True,
+ )
+ print(" โ
Vector store loaded!")
+ except Exception as e:
+ print(f" โ Failed to load vector store: {e}")
+ print(" ๐ Automatically rebuilding index from PDFs... (This may take a minute)")
+ from ingest import run_ingestion
+ run_ingestion()
+
+ # Try loading again after ingestion
+ vector_store = FAISS.load_local(
+ config.VECTOR_STORE_PATH,
+ embeddings,
+ allow_dangerous_deserialization=True,
+ )
+ print(" โ
Vector store rebuilt and loaded!")
+
+ return vector_store
+
+
+def build_rag_chain(vector_store: FAISS) -> RetrievalQA:
+ """Build the RAG chain with retriever + LLM."""
+ # Configure retriever
+ retriever = vector_store.as_retriever(
+ search_type="similarity",
+ search_kwargs={"k": config.TOP_K_RESULTS},
+ )
+
+ # Initialize Gemini LLM
+ llm = ChatGoogleGenerativeAI(
+ model=config.LLM_MODEL,
+ google_api_key=config.GOOGLE_API_KEY,
+ temperature=config.TEMPERATURE,
+ max_output_tokens=config.MAX_OUTPUT_TOKENS,
+ convert_system_message_to_human=True,
+ )
+
+ # Build the RAG chain
+ chain = RetrievalQA.from_chain_type(
+ llm=llm,
+ chain_type="stuff",
+ retriever=retriever,
+ return_source_documents=True,
+ chain_type_kwargs={
+ "prompt": STUDENT_MENTOR_PROMPT,
+ },
+ )
+
+ return chain
+
+
+def ask_question(chain: RetrievalQA, question: str) -> dict:
+ """
+ Ask a question and get the mentor's response along with source references.
+ """
+ result = chain.invoke({"query": question})
+
+ # Extract source info
+ sources = []
+ seen = set()
+ for doc in result.get("source_documents", []):
+ source_name = doc.metadata.get("source", "Unknown")
+ subject = doc.metadata.get("subject", "General")
+ key = f"{subject}/{source_name}"
+ if key not in seen:
+ seen.add(key)
+ sources.append({
+ "file": source_name,
+ "subject": subject,
+ "preview": doc.page_content[:150] + "..." if len(doc.page_content) > 150 else doc.page_content,
+ })
+
+ return {
+ "answer": result["result"],
+ "sources": sources,
+ }
+
+
+# โโโ Singleton pattern for reuse โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+_chain_instance = None
+
+def get_chain():
+ """Get or create the RAG chain (singleton)."""
+ global _chain_instance
+ if _chain_instance is None:
+ vector_store = load_vector_store()
+ _chain_instance = build_rag_chain(vector_store)
+ return _chain_instance
diff --git a/render.yaml b/render.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..014b46529f7bc0a1ae41aaf58f7f5a12a5d09d46
--- /dev/null
+++ b/render.yaml
@@ -0,0 +1,9 @@
+services:
+ - type: web
+ name: study-ocean-rag-api
+ env: python
+ buildCommand: pip install -r requirements.txt
+ startCommand: uvicorn api:app --host 0.0.0.0 --port $PORT
+ envVars:
+ - key: PYTHON_VERSION
+ value: 3.11.0
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..64e389b411466e6ca513787cffcd6d52a013f68b
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,16 @@
+langchain==0.2.16
+langchain-community==0.2.12
+langchain-core==0.2.38
+langchain-google-genai==1.0.10
+langchain-huggingface==0.0.3
+faiss-cpu>=1.8.0
+sentence-transformers==3.0.1
+google-generativeai==0.7.2
+python-dotenv==1.0.1
+fastapi==0.115.0
+uvicorn==0.30.6
+pydantic==2.9.2
+pypdf==5.0.1
+pymupdf==1.24.10
+gunicorn==23.0.0
+gradio
\ No newline at end of file