Mohit Raj commited on
Commit
030f984
Β·
verified Β·
1 Parent(s): ea18ea5

Sync from GitHub via hub-sync

Browse files
This view is limited to 50 files because it contains too many changes. Β  See raw diff
Files changed (50) hide show
  1. .env.example +18 -0
  2. .gitattributes +33 -0
  3. .python-version +1 -0
  4. .railwayignore +13 -0
  5. COLAB_GUIDE.md +112 -0
  6. Dockerfile +24 -0
  7. Procfile +1 -0
  8. README.md +5 -7
  9. StudyOcean_RAG_Colab.ipynb +743 -0
  10. api.py +65 -0
  11. app.py +203 -0
  12. config.py +30 -0
  13. data/pdfs/StudyOceanPDFs/BIOLOGY_FLASHCARD_FOR_NEET.pdf +3 -0
  14. data/pdfs/StudyOceanPDFs/CBSE_2026_Class_12_Physics_Set_2_Question_Paper_With_Solutions_PDF.pdf +3 -0
  15. data/pdfs/StudyOceanPDFs/CENGAGE_IOC_CRUX_BY_studyoceanbooks.PDF +3 -0
  16. data/pdfs/StudyOceanPDFs/COM_COLLISION_Short_Notes_1_.pdf +3 -0
  17. data/pdfs/StudyOceanPDFs/Chemistry_Flash_Cards_Colourful_Complete.pdf +3 -0
  18. data/pdfs/StudyOceanPDFs/Chemistry_Flashcard_for_JEE_NEET.pdf +3 -0
  19. data/pdfs/StudyOceanPDFs/Chemistry_Formula_Booklet_Hashlearn.pdf +3 -0
  20. data/pdfs/StudyOceanPDFs/Circular_dyanamic_short_notes_1_.pdf +3 -0
  21. data/pdfs/StudyOceanPDFs/Circular_motion_in_kinematics_short_notes_1_.pdf +3 -0
  22. data/pdfs/StudyOceanPDFs/Rotational_motion_short_notes_1_.pdf +3 -0
  23. data/pdfs/StudyOceanPDFs/Thermal_properties_of_matter_short_notes_.pdf +3 -0
  24. data/pdfs/StudyOceanPDFs/Units_measurements_and_errors_short_notes_1_.pdf +3 -0
  25. data/pdfs/StudyOceanPDFs/Vertical_circular_motion_short_notes_1_.pdf +3 -0
  26. data/pdfs/StudyOceanPDFs/Wave_motion_short_notes_1_.pdf +3 -0
  27. data/pdfs/StudyOceanPDFs/Work_Power_Energy_Short_Notes_1_.pdf +3 -0
  28. data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_1_question_paper_2026_1771575627702.pdf +3 -0
  29. data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_2_question_paper_2026_1771575619105.pdf +3 -0
  30. data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_3_question_paper_2026_1771575610905.pdf +3 -0
  31. data/pdfs/StudyOceanPDFs/chapter_10.pdf +3 -0
  32. data/pdfs/StudyOceanPDFs/chapter_11.pdf +3 -0
  33. data/pdfs/StudyOceanPDFs/chapter_12.pdf +3 -0
  34. data/pdfs/StudyOceanPDFs/chapter_13.pdf +3 -0
  35. data/pdfs/StudyOceanPDFs/chapter_14.pdf +3 -0
  36. data/pdfs/StudyOceanPDFs/chapter_15.pdf +3 -0
  37. data/pdfs/StudyOceanPDFs/chapter_2.pdf +3 -0
  38. data/pdfs/StudyOceanPDFs/chapter_3_1_.pdf +3 -0
  39. data/pdfs/StudyOceanPDFs/chapter_4.pdf +3 -0
  40. data/pdfs/StudyOceanPDFs/chapter_5.pdf +3 -0
  41. data/pdfs/StudyOceanPDFs/chapter_6.pdf +3 -0
  42. data/pdfs/StudyOceanPDFs/chapter_7.pdf +3 -0
  43. data/pdfs/StudyOceanPDFs/vector_sheets_Cyan_notes_1_1_.pdf +3 -0
  44. data/vector_store/index.faiss +3 -0
  45. data/vector_store/index.pkl +3 -0
  46. frontend_demo.html +267 -0
  47. ingest.py +171 -0
  48. logo.png +3 -0
  49. main.py +107 -0
  50. pyproject.toml +23 -0
.env.example ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Google Gemini API Key (get from https://aistudio.google.com/apikey)
2
+ GOOGLE_API_KEY=your_google_api_key_here
3
+
4
+ # Embedding model (default: all-MiniLM-L6-v2 from sentence-transformers, runs locally)
5
+ EMBEDDING_MODEL=all-MiniLM-L6-v2
6
+
7
+ # LLM model (default: gemini-2.0-flash)
8
+ LLM_MODEL=gemini-2.0-flash
9
+
10
+ # Vector store path
11
+ VECTOR_STORE_PATH=data/vector_store
12
+
13
+ # PDF source directory
14
+ PDF_DIR=data/pdfs
15
+
16
+ # Chunk size and overlap for text splitting
17
+ CHUNK_SIZE=1000
18
+ CHUNK_OVERLAP=200
.gitattributes CHANGED
@@ -33,3 +33,36 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ data/pdfs/StudyOceanPDFs/BIOLOGY_FLASHCARD_FOR_NEET.pdf filter=lfs diff=lfs merge=lfs -text
37
+ data/pdfs/StudyOceanPDFs/CBSE_2026_Class_12_Physics_Set_2_Question_Paper_With_Solutions_PDF.pdf filter=lfs diff=lfs merge=lfs -text
38
+ data/pdfs/StudyOceanPDFs/CENGAGE_IOC_CRUX_BY_studyoceanbooks.PDF filter=lfs diff=lfs merge=lfs -text
39
+ data/pdfs/StudyOceanPDFs/COM_COLLISION_Short_Notes_1_.pdf filter=lfs diff=lfs merge=lfs -text
40
+ data/pdfs/StudyOceanPDFs/Chemistry_Flash_Cards_Colourful_Complete.pdf filter=lfs diff=lfs merge=lfs -text
41
+ data/pdfs/StudyOceanPDFs/Chemistry_Flashcard_for_JEE_NEET.pdf filter=lfs diff=lfs merge=lfs -text
42
+ data/pdfs/StudyOceanPDFs/Chemistry_Formula_Booklet_Hashlearn.pdf filter=lfs diff=lfs merge=lfs -text
43
+ data/pdfs/StudyOceanPDFs/Circular_dyanamic_short_notes_1_.pdf filter=lfs diff=lfs merge=lfs -text
44
+ data/pdfs/StudyOceanPDFs/Circular_motion_in_kinematics_short_notes_1_.pdf filter=lfs diff=lfs merge=lfs -text
45
+ data/pdfs/StudyOceanPDFs/Rotational_motion_short_notes_1_.pdf filter=lfs diff=lfs merge=lfs -text
46
+ data/pdfs/StudyOceanPDFs/Thermal_properties_of_matter_short_notes_.pdf filter=lfs diff=lfs merge=lfs -text
47
+ data/pdfs/StudyOceanPDFs/Units_measurements_and_errors_short_notes_1_.pdf filter=lfs diff=lfs merge=lfs -text
48
+ data/pdfs/StudyOceanPDFs/Vertical_circular_motion_short_notes_1_.pdf filter=lfs diff=lfs merge=lfs -text
49
+ data/pdfs/StudyOceanPDFs/Wave_motion_short_notes_1_.pdf filter=lfs diff=lfs merge=lfs -text
50
+ data/pdfs/StudyOceanPDFs/Work_Power_Energy_Short_Notes_1_.pdf filter=lfs diff=lfs merge=lfs -text
51
+ data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_1_question_paper_2026_1771575627702.pdf filter=lfs diff=lfs merge=lfs -text
52
+ data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_2_question_paper_2026_1771575619105.pdf filter=lfs diff=lfs merge=lfs -text
53
+ data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_3_question_paper_2026_1771575610905.pdf filter=lfs diff=lfs merge=lfs -text
54
+ data/pdfs/StudyOceanPDFs/chapter_10.pdf filter=lfs diff=lfs merge=lfs -text
55
+ data/pdfs/StudyOceanPDFs/chapter_11.pdf filter=lfs diff=lfs merge=lfs -text
56
+ data/pdfs/StudyOceanPDFs/chapter_12.pdf filter=lfs diff=lfs merge=lfs -text
57
+ data/pdfs/StudyOceanPDFs/chapter_13.pdf filter=lfs diff=lfs merge=lfs -text
58
+ data/pdfs/StudyOceanPDFs/chapter_14.pdf filter=lfs diff=lfs merge=lfs -text
59
+ data/pdfs/StudyOceanPDFs/chapter_15.pdf filter=lfs diff=lfs merge=lfs -text
60
+ data/pdfs/StudyOceanPDFs/chapter_2.pdf filter=lfs diff=lfs merge=lfs -text
61
+ data/pdfs/StudyOceanPDFs/chapter_3_1_.pdf filter=lfs diff=lfs merge=lfs -text
62
+ data/pdfs/StudyOceanPDFs/chapter_4.pdf filter=lfs diff=lfs merge=lfs -text
63
+ data/pdfs/StudyOceanPDFs/chapter_5.pdf filter=lfs diff=lfs merge=lfs -text
64
+ data/pdfs/StudyOceanPDFs/chapter_6.pdf filter=lfs diff=lfs merge=lfs -text
65
+ data/pdfs/StudyOceanPDFs/chapter_7.pdf filter=lfs diff=lfs merge=lfs -text
66
+ data/pdfs/StudyOceanPDFs/vector_sheets_Cyan_notes_1_1_.pdf filter=lfs diff=lfs merge=lfs -text
67
+ data/vector_store/index.faiss filter=lfs diff=lfs merge=lfs -text
68
+ logo.png filter=lfs diff=lfs merge=lfs -text
.python-version ADDED
@@ -0,0 +1 @@
 
 
1
+ 3.10
.railwayignore ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .venv/
2
+ venv/
3
+ .env
4
+ data/pdfs/
5
+ __pycache__/
6
+ *.pyc
7
+ *.pyo
8
+ .vscode/
9
+ .idea/
10
+ .DS_Store
11
+ Thumbs.db
12
+ .ipynb_checkpoints/
13
+ *.zip
COLAB_GUIDE.md ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # πŸŽ“ Study Ocean RAG β€” Google Colab Guide
2
+
3
+ > Your laptop stays cool. Colab's **T4 GPU** does all the heavy lifting.
4
+
5
+ ---
6
+
7
+ ## πŸ“‹ One-Time Setup (First Time Only)
8
+
9
+ ### 1. Upload ZIP to Google Drive
10
+ 1. Go to [drive.google.com](https://drive.google.com)
11
+ 2. Create a folder: `MyDrive/StudyOcean/`
12
+ 3. Upload `StudyOceanPDFs.zip` into that folder
13
+ 4. ⏳ Wait for upload to complete (6 GB β€” may take 15–30 min on slow wifi)
14
+
15
+ ### 2. Get a Gemini API Key (Free)
16
+ 1. Go to [aistudio.google.com/apikey](https://aistudio.google.com/apikey)
17
+ 2. Click **Create API Key**
18
+ 3. Copy the key β€” you'll paste it into the notebook
19
+
20
+ ### 3. Open the Notebook in Colab
21
+ 1. Go to [colab.research.google.com](https://colab.research.google.com)
22
+ 2. Click **File β†’ Upload notebook**
23
+ 3. Upload `StudyOcean_RAG_Colab.ipynb` from this folder
24
+
25
+ ### 4. Set T4 GPU Runtime
26
+ > **This is critical β€” without it, ingestion will be very slow!**
27
+ 1. In Colab: **Runtime β†’ Change runtime type**
28
+ 2. Set Hardware accelerator = **T4 GPU**
29
+ 3. Click **Save**
30
+
31
+ ---
32
+
33
+ ## πŸš€ Running the Pipeline
34
+
35
+ ### First Time (Fresh Ingestion)
36
+ Run cells in order: **Steps 1 β†’ 2 β†’ 3 β†’ 4 β†’ 5 β†’ 6 β†’ 7 β†’ 8 β†’ 9 β†’ 10**
37
+
38
+ | Step | What it does | Time |
39
+ |------|-------------|------|
40
+ | 1 | Verify T4 GPU + mount Drive | 30s |
41
+ | 2 | Install packages | 2–3 min |
42
+ | 3 | Configure settings (paste API key here!) | instant |
43
+ | 4 | Extract PDFs from ZIP | 5–10 min |
44
+ | 5 | Ingest PDFs & build vector store | **30–60 min** |
45
+ | 6 | Save vector store to Drive | 2–5 min |
46
+ | 7 | Test retrieval | instant |
47
+ | 8 | Build RAG chain | instant |
48
+ | 9 | Test full pipeline | 5s |
49
+ | 10 | Launch Gradio chat UI | instant |
50
+
51
+ ### Future Sessions (Vector Store Already Built)
52
+ Run cells: **Steps 1 β†’ 2 β†’ 3 β†’ 11 β†’ 8 β†’ 10**
53
+
54
+ | Step | What it does | Time |
55
+ |------|-------------|------|
56
+ | 11 | Load saved vector store from Drive | 1–2 min |
57
+ | 8 | Build RAG chain | instant |
58
+ | 10 | Launch Gradio chat UI | instant |
59
+
60
+ ---
61
+
62
+ ## πŸ’» Running Locally (After Ingestion)
63
+
64
+ Once the vector store is built, you can run the chat locally:
65
+
66
+ ### Option A β€” Download via Colab (Step 12)
67
+ - Run **Step 12** in the notebook
68
+ - It downloads `vector_store_export.zip` to your computer
69
+ - Extract it to: `Study_Ocean Rag/data/vector_store/`
70
+
71
+ ### Option B β€” Download from Google Drive
72
+ - Go to Drive β†’ `StudyOcean/vector_store/`
73
+ - Download the folder
74
+ - Place it at: `Study_Ocean Rag/data/vector_store/`
75
+
76
+ ### Then run locally:
77
+ ```bash
78
+ # Make sure .env has your GOOGLE_API_KEY
79
+ python main.py chat # Web UI at http://localhost:7860
80
+ # OR
81
+ python main.py ask # Terminal Q&A
82
+ ```
83
+
84
+ ---
85
+
86
+ ## ⚑ Performance on T4 GPU
87
+
88
+ | Task | CPU (your laptop) | T4 GPU (Colab) |
89
+ |------|------------------|----------------|
90
+ | Embedding batch size | 64 | 256 |
91
+ | ~10,000 chunks | ~45 min | ~8 min |
92
+ | ~50,000 chunks | ~4 hours | ~35 min |
93
+ | ~100,000 chunks | ~8 hours | ~70 min |
94
+
95
+ ---
96
+
97
+ ## ❓ Troubleshooting
98
+
99
+ **"ZIP not found" error**
100
+ β†’ Update `ZIP_DRIVE_PATH` in Step 3 to match your actual Drive path
101
+
102
+ **"CUDA not available"**
103
+ β†’ You forgot to set T4 GPU runtime! Runtime β†’ Change runtime type β†’ T4 GPU
104
+
105
+ **Colab session disconnects during ingestion**
106
+ β†’ The vector store saves in batches. Re-run from Step 5 β€” FAISS will rebuild from scratch. Save to Drive often.
107
+
108
+ **"Invalid API key"**
109
+ β†’ Make sure you pasted the full Gemini API key in Step 3
110
+
111
+ **Gradio link doesn't work**
112
+ β†’ Gradio public links expire after 72 hours. Re-run Step 10 to get a new link.
Dockerfile ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image
2
+ FROM python:3.11-slim
3
+
4
+ # Hugging Face Spaces require running as a non-root user
5
+ RUN useradd -m -u 1000 user
6
+ USER user
7
+ ENV PATH="/home/user/.local/bin:$PATH"
8
+
9
+ # Set the working directory
10
+ WORKDIR /app
11
+
12
+ # Copy requirements and install dependencies
13
+ COPY --chown=user ./requirements.txt requirements.txt
14
+ RUN pip install --no-cache-dir --upgrade pip && \
15
+ pip install --no-cache-dir -r requirements.txt
16
+
17
+ # Copy the rest of the application
18
+ COPY --chown=user . /app
19
+
20
+ # Expose the port that Hugging Face Spaces expects (7860)
21
+ EXPOSE 7860
22
+
23
+ # Command to run the FastAPI app
24
+ CMD ["uvicorn", "api:app", "--host", "0.0.0.0", "--port", "7860"]
Procfile ADDED
@@ -0,0 +1 @@
 
 
1
+ web: uvicorn api:app --host 0.0.0.0 --port $PORT
README.md CHANGED
@@ -1,10 +1,8 @@
1
  ---
2
- title: Study-Ocean-AI Mentor
3
- emoji: πŸ“š
4
- colorFrom: green
5
- colorTo: blue
6
  sdk: docker
7
- pinned: false
8
  ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: Study Ocean RAG
3
+ emoji: 🌊
4
+ colorFrom: blue
5
+ colorTo: indigo
6
  sdk: docker
7
+ app_port: 7860
8
  ---
 
 
StudyOcean_RAG_Colab.ipynb ADDED
@@ -0,0 +1,743 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "nbformat": 4,
3
+ "nbformat_minor": 0,
4
+ "metadata": {
5
+ "colab": {
6
+ "provenance": [],
7
+ "gpuType": "T4",
8
+ "name": "StudyOcean_RAG_Colab.ipynb"
9
+ },
10
+ "kernelspec": {
11
+ "name": "python3",
12
+ "display_name": "Python 3"
13
+ },
14
+ "language_info": {
15
+ "name": "python"
16
+ },
17
+ "accelerator": "GPU"
18
+ },
19
+ "cells": [
20
+ {
21
+ "cell_type": "markdown",
22
+ "metadata": {},
23
+ "source": [
24
+ "# πŸŽ“ Study Ocean AI Mentor β€” Google Colab Pipeline\n",
25
+ "\n",
26
+ "> **Run this notebook on Colab with T4 GPU** \n",
27
+ "> Runtime β†’ Change runtime type β†’ **T4 GPU**\n",
28
+ "\n",
29
+ "### What this notebook does:\n",
30
+ "1. βœ… Mounts your Google Drive\n",
31
+ "2. βœ… Installs all dependencies (with `faiss-gpu` for T4 acceleration)\n",
32
+ "3. βœ… Extracts PDFs from `StudyOceanPDFs.zip`\n",
33
+ "4. βœ… Runs the ingestion pipeline (GPU-accelerated embeddings)\n",
34
+ "5. βœ… Saves the FAISS vector store back to Google Drive\n",
35
+ "6. βœ… Launches the Gradio chat UI with a public shareable link\n",
36
+ "\n",
37
+ "---\n",
38
+ "### πŸ“‹ Before you start:\n",
39
+ "- Upload `StudyOceanPDFs.zip` to your **Google Drive** (e.g., `MyDrive/StudyOcean/StudyOceanPDFs.zip`)\n",
40
+ "- Get your **Google Gemini API key** from https://aistudio.google.com/apikey\n",
41
+ "- Set runtime to **T4 GPU**: Runtime β†’ Change runtime type β†’ T4 GPU\n"
42
+ ]
43
+ },
44
+ {
45
+ "cell_type": "markdown",
46
+ "metadata": {},
47
+ "source": [
48
+ "## πŸ”§ Step 1 β€” Verify GPU & Mount Google Drive"
49
+ ]
50
+ },
51
+ {
52
+ "cell_type": "code",
53
+ "execution_count": null,
54
+ "metadata": {},
55
+ "outputs": [],
56
+ "source": [
57
+ "# ── Verify T4 GPU is available ───────────────────────────────\n",
58
+ "import subprocess\n",
59
+ "result = subprocess.run(['nvidia-smi'], capture_output=True, text=True)\n",
60
+ "print(result.stdout if result.returncode == 0 else \"⚠️ No GPU found! Go to Runtime β†’ Change runtime type β†’ T4 GPU\")\n",
61
+ "\n",
62
+ "import torch\n",
63
+ "print(f\"\\nπŸ”₯ PyTorch CUDA available: {torch.cuda.is_available()}\")\n",
64
+ "if torch.cuda.is_available():\n",
65
+ " print(f\" GPU: {torch.cuda.get_device_name(0)}\")\n",
66
+ " print(f\" VRAM: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB\")"
67
+ ]
68
+ },
69
+ {
70
+ "cell_type": "code",
71
+ "execution_count": null,
72
+ "metadata": {},
73
+ "outputs": [],
74
+ "source": [
75
+ "# ── Mount Google Drive ────────────────────────────────────────\n",
76
+ "from google.colab import drive\n",
77
+ "drive.mount('/content/drive')\n",
78
+ "print(\"βœ… Google Drive mounted at /content/drive\")"
79
+ ]
80
+ },
81
+ {
82
+ "cell_type": "markdown",
83
+ "metadata": {},
84
+ "source": [
85
+ "## πŸ“¦ Step 2 β€” Install Dependencies"
86
+ ]
87
+ },
88
+ {
89
+ "cell_type": "code",
90
+ "execution_count": null,
91
+ "metadata": {},
92
+ "outputs": [],
93
+ "source": [
94
+ "# ── Install all packages ──────────────────────────────────────\n",
95
+ "# Using faiss-gpu for T4 GPU acceleration during similarity search\n",
96
+ "!pip install -q \\\n",
97
+ " langchain \\\n",
98
+ " langchain-community \\\n",
99
+ " langchain-core \\\n",
100
+ " langchain-google-genai \\\n",
101
+ " langchain-huggingface \\\n",
102
+ " pymupdf \\\n",
103
+ " faiss-gpu \\\n",
104
+ " sentence-transformers \\\n",
105
+ " google-generativeai \\\n",
106
+ " python-dotenv \\\n",
107
+ " gradio \\\n",
108
+ " tiktoken \\\n",
109
+ " accelerate\n",
110
+ "\n",
111
+ "print(\"\\nβœ… All packages installed!\")"
112
+ ]
113
+ },
114
+ {
115
+ "cell_type": "markdown",
116
+ "metadata": {},
117
+ "source": [
118
+ "## βš™οΈ Step 3 β€” Configure Settings\n",
119
+ "\n",
120
+ "> **Edit the variables in this cell before running!**"
121
+ ]
122
+ },
123
+ {
124
+ "cell_type": "code",
125
+ "execution_count": null,
126
+ "metadata": {},
127
+ "outputs": [],
128
+ "source": [
129
+ "import os\n",
130
+ "\n",
131
+ "# ════════════════════════════════════════════════════════\n",
132
+ "# ✏️ EDIT THESE SETTINGS\n",
133
+ "# ════════════════════════════════════════════════════════\n",
134
+ "\n",
135
+ "# Your Google Gemini API key β€” get it from https://aistudio.google.com/apikey\n",
136
+ "GOOGLE_API_KEY = \"\" # ← PASTE YOUR KEY HERE\n",
137
+ "\n",
138
+ "# Path to StudyOceanPDFs.zip in your Google Drive\n",
139
+ "ZIP_DRIVE_PATH = \"/content/drive/MyDrive/StudyOcean/StudyOceanPDFs.zip\"\n",
140
+ "\n",
141
+ "# Where to save the vector store in Google Drive (so it persists!)\n",
142
+ "VECTOR_STORE_DRIVE_PATH = \"/content/drive/MyDrive/StudyOcean/vector_store\"\n",
143
+ "\n",
144
+ "# Embedding model β€” 'cuda' will use the T4 GPU!\n",
145
+ "EMBEDDING_MODEL = \"all-MiniLM-L6-v2\" # Fast & accurate\n",
146
+ "EMBEDDING_DEVICE = \"cuda\" # ← T4 GPU acceleration\n",
147
+ "\n",
148
+ "# Gemini LLM model\n",
149
+ "LLM_MODEL = \"gemini-2.0-flash\"\n",
150
+ "\n",
151
+ "# Text splitting config\n",
152
+ "CHUNK_SIZE = 1000\n",
153
+ "CHUNK_OVERLAP = 200\n",
154
+ "\n",
155
+ "# RAG retrieval\n",
156
+ "TOP_K = 5\n",
157
+ "\n",
158
+ "# ════════════════════════════════════════════════════════\n",
159
+ "\n",
160
+ "# Local working paths (inside Colab VM)\n",
161
+ "PDF_DIR = \"/content/studyocean_pdfs\"\n",
162
+ "VECTOR_STORE_LOCAL = \"/content/vector_store\"\n",
163
+ "\n",
164
+ "os.makedirs(PDF_DIR, exist_ok=True)\n",
165
+ "os.makedirs(VECTOR_STORE_LOCAL, exist_ok=True)\n",
166
+ "\n",
167
+ "# Set the API key as env var for LangChain\n",
168
+ "os.environ[\"GOOGLE_API_KEY\"] = GOOGLE_API_KEY\n",
169
+ "\n",
170
+ "print(\"βœ… Configuration set!\")\n",
171
+ "print(f\" ZIP source: {ZIP_DRIVE_PATH}\")\n",
172
+ "print(f\" PDF extract: {PDF_DIR}\")\n",
173
+ "print(f\" Vector store: {VECTOR_STORE_LOCAL}\")\n",
174
+ "print(f\" Drive backup: {VECTOR_STORE_DRIVE_PATH}\")\n",
175
+ "print(f\" Embedding GPU: {EMBEDDING_DEVICE}\")\n",
176
+ "print(f\" LLM: {LLM_MODEL}\")"
177
+ ]
178
+ },
179
+ {
180
+ "cell_type": "markdown",
181
+ "metadata": {},
182
+ "source": [
183
+ "## πŸ“‚ Step 4 β€” Extract PDFs from ZIP"
184
+ ]
185
+ },
186
+ {
187
+ "cell_type": "code",
188
+ "execution_count": null,
189
+ "metadata": {},
190
+ "outputs": [],
191
+ "source": [
192
+ "import zipfile\n",
193
+ "import time\n",
194
+ "\n",
195
+ "# Check if zip exists\n",
196
+ "if not os.path.exists(ZIP_DRIVE_PATH):\n",
197
+ " print(f\"❌ ZIP not found at: {ZIP_DRIVE_PATH}\")\n",
198
+ " print(\" Please upload StudyOceanPDFs.zip to your Google Drive\")\n",
199
+ " print(\" Then update ZIP_DRIVE_PATH in Step 3.\")\n",
200
+ " raise FileNotFoundError(f\"ZIP not found: {ZIP_DRIVE_PATH}\")\n",
201
+ "\n",
202
+ "zip_size_gb = os.path.getsize(ZIP_DRIVE_PATH) / 1e9\n",
203
+ "print(f\"πŸ“¦ Found ZIP: {zip_size_gb:.2f} GB\")\n",
204
+ "print(f\" Extracting to {PDF_DIR} ...\")\n",
205
+ "print(\" (This may take a few minutes for large archives)\")\n",
206
+ "\n",
207
+ "start = time.time()\n",
208
+ "with zipfile.ZipFile(ZIP_DRIVE_PATH, 'r') as zf:\n",
209
+ " zf.extractall(PDF_DIR)\n",
210
+ "elapsed = time.time() - start\n",
211
+ "\n",
212
+ "# Count extracted PDFs\n",
213
+ "pdf_count = sum(\n",
214
+ " 1 for root, _, files in os.walk(PDF_DIR)\n",
215
+ " for f in files if f.lower().endswith('.pdf')\n",
216
+ ")\n",
217
+ "\n",
218
+ "print(f\"\\nβœ… Extraction complete in {elapsed:.1f}s\")\n",
219
+ "print(f\" Found {pdf_count} PDF files\")"
220
+ ]
221
+ },
222
+ {
223
+ "cell_type": "markdown",
224
+ "metadata": {},
225
+ "source": [
226
+ "## πŸ” Step 5 β€” PDF Ingestion Pipeline (GPU-Accelerated)"
227
+ ]
228
+ },
229
+ {
230
+ "cell_type": "code",
231
+ "execution_count": null,
232
+ "metadata": {},
233
+ "outputs": [],
234
+ "source": [
235
+ "import fitz # PyMuPDF\n",
236
+ "from langchain.text_splitter import RecursiveCharacterTextSplitter\n",
237
+ "from langchain_community.vectorstores import FAISS\n",
238
+ "from langchain_huggingface import HuggingFaceEmbeddings\n",
239
+ "from langchain.docstore.document import Document\n",
240
+ "\n",
241
+ "\n",
242
+ "def collect_pdf_paths(pdf_dir):\n",
243
+ " \"\"\"Recursively collect all PDF paths.\"\"\"\n",
244
+ " paths = []\n",
245
+ " for root, _, files in os.walk(pdf_dir):\n",
246
+ " for f in files:\n",
247
+ " if f.lower().endswith('.pdf'):\n",
248
+ " paths.append(os.path.join(root, f))\n",
249
+ " return sorted(paths)\n",
250
+ "\n",
251
+ "\n",
252
+ "def extract_text(pdf_path):\n",
253
+ " \"\"\"Extract text from a PDF using PyMuPDF.\"\"\"\n",
254
+ " text = \"\"\n",
255
+ " try:\n",
256
+ " doc = fitz.open(pdf_path)\n",
257
+ " for page in doc:\n",
258
+ " text += page.get_text()\n",
259
+ " doc.close()\n",
260
+ " except Exception as e:\n",
261
+ " print(f\" ⚠ Error reading {os.path.basename(pdf_path)}: {e}\")\n",
262
+ " return text\n",
263
+ "\n",
264
+ "\n",
265
+ "def build_documents(pdf_paths):\n",
266
+ " \"\"\"Extract and chunk all PDFs into LangChain Documents.\"\"\"\n",
267
+ " splitter = RecursiveCharacterTextSplitter(\n",
268
+ " chunk_size=CHUNK_SIZE,\n",
269
+ " chunk_overlap=CHUNK_OVERLAP,\n",
270
+ " separators=[\"\\n\\n\", \"\\n\", \". \", \" \", \"\"],\n",
271
+ " )\n",
272
+ " all_docs = []\n",
273
+ " skipped = 0\n",
274
+ "\n",
275
+ " for i, path in enumerate(pdf_paths, 1):\n",
276
+ " rel = os.path.relpath(path, PDF_DIR)\n",
277
+ " parts = rel.replace(\"\\\\\", \"/\").split(\"/\")\n",
278
+ " subject = parts[0] if len(parts) > 1 else \"General\"\n",
279
+ "\n",
280
+ " if i % 50 == 0 or i == 1:\n",
281
+ " print(f\" [{i}/{len(pdf_paths)}] {rel}\")\n",
282
+ "\n",
283
+ " text = extract_text(path)\n",
284
+ " if not text.strip():\n",
285
+ " skipped += 1\n",
286
+ " continue\n",
287
+ "\n",
288
+ " chunks = splitter.create_documents(\n",
289
+ " texts=[text],\n",
290
+ " metadatas=[{\"source\": rel, \"filename\": os.path.basename(path), \"subject\": subject}]\n",
291
+ " )\n",
292
+ " all_docs.extend(chunks)\n",
293
+ "\n",
294
+ " print(f\"\\n Skipped (image/scanned PDFs): {skipped}\")\n",
295
+ " return all_docs\n",
296
+ "\n",
297
+ "\n",
298
+ "# ── Step 5a: Collect PDFs ────────────────────────────────────\n",
299
+ "print(\"=\" * 60)\n",
300
+ "print(\"πŸ“„ PHASE 1: Collecting & chunking PDFs\")\n",
301
+ "print(\"=\" * 60)\n",
302
+ "pdf_paths = collect_pdf_paths(PDF_DIR)\n",
303
+ "print(f\" {len(pdf_paths)} PDFs found\")\n",
304
+ "\n",
305
+ "t0 = time.time()\n",
306
+ "documents = build_documents(pdf_paths)\n",
307
+ "t_extract = time.time() - t0\n",
308
+ "\n",
309
+ "print(f\"\\nβœ… Created {len(documents)} chunks in {t_extract:.1f}s\")"
310
+ ]
311
+ },
312
+ {
313
+ "cell_type": "code",
314
+ "execution_count": null,
315
+ "metadata": {},
316
+ "outputs": [],
317
+ "source": [
318
+ "# ── Step 5b: Load Embedding Model onto T4 GPU ────────────────\n",
319
+ "print(\"=\" * 60)\n",
320
+ "print(f\"🧠 PHASE 2: Loading embedding model on {EMBEDDING_DEVICE.upper()}\")\n",
321
+ "print(\"=\" * 60)\n",
322
+ "\n",
323
+ "import torch\n",
324
+ "device = EMBEDDING_DEVICE if torch.cuda.is_available() else \"cpu\"\n",
325
+ "if device == \"cpu\" and EMBEDDING_DEVICE == \"cuda\":\n",
326
+ " print(\"⚠️ CUDA not available, falling back to CPU\")\n",
327
+ "\n",
328
+ "embeddings = HuggingFaceEmbeddings(\n",
329
+ " model_name=EMBEDDING_MODEL,\n",
330
+ " model_kwargs={\"device\": device},\n",
331
+ " encode_kwargs={\"normalize_embeddings\": True, \"batch_size\": 256}, # Larger batch on GPU!\n",
332
+ ")\n",
333
+ "\n",
334
+ "print(f\"βœ… Embedding model loaded on {device.upper()}\")\n",
335
+ "print(f\" Model: {EMBEDDING_MODEL}\")\n",
336
+ "print(f\" Batch size: 256 (optimized for T4)\")"
337
+ ]
338
+ },
339
+ {
340
+ "cell_type": "code",
341
+ "execution_count": null,
342
+ "metadata": {},
343
+ "outputs": [],
344
+ "source": [
345
+ "# ── Step 5c: Build FAISS Vector Store ───────────────────────\n",
346
+ "print(\"=\" * 60)\n",
347
+ "print(f\"πŸ“Š PHASE 3: Building FAISS vector store ({len(documents)} chunks)\")\n",
348
+ "print(\"=\" * 60)\n",
349
+ "\n",
350
+ "BATCH_SIZE = 1000 # Larger batches since we have GPU\n",
351
+ "vector_store = None\n",
352
+ "t0 = time.time()\n",
353
+ "\n",
354
+ "total_batches = (len(documents) + BATCH_SIZE - 1) // BATCH_SIZE\n",
355
+ "for i in range(0, len(documents), BATCH_SIZE):\n",
356
+ " batch = documents[i : i + BATCH_SIZE]\n",
357
+ " batch_num = (i // BATCH_SIZE) + 1\n",
358
+ "\n",
359
+ " elapsed = time.time() - t0\n",
360
+ " rate = (i + 1) / elapsed if elapsed > 0 else 0\n",
361
+ " eta = (len(documents) - i) / rate if rate > 0 else 0\n",
362
+ " print(f\" Batch {batch_num:3d}/{total_batches} | {len(batch)} chunks | \"\n",
363
+ " f\"ETA: {eta/60:.1f} min\")\n",
364
+ "\n",
365
+ " if vector_store is None:\n",
366
+ " vector_store = FAISS.from_documents(batch, embeddings)\n",
367
+ " else:\n",
368
+ " batch_store = FAISS.from_documents(batch, embeddings)\n",
369
+ " vector_store.merge_from(batch_store)\n",
370
+ "\n",
371
+ "t_embed = time.time() - t0\n",
372
+ "print(f\"\\nβœ… Vector store built in {t_embed/60:.1f} minutes\")\n",
373
+ "print(f\" Total vectors: {vector_store.index.ntotal}\")"
374
+ ]
375
+ },
376
+ {
377
+ "cell_type": "markdown",
378
+ "metadata": {},
379
+ "source": [
380
+ "## πŸ’Ύ Step 6 β€” Save Vector Store to Google Drive"
381
+ ]
382
+ },
383
+ {
384
+ "cell_type": "code",
385
+ "execution_count": null,
386
+ "metadata": {},
387
+ "outputs": [],
388
+ "source": [
389
+ "import shutil\n",
390
+ "\n",
391
+ "# Save locally first\n",
392
+ "print(\"πŸ’Ύ Saving vector store locally...\")\n",
393
+ "vector_store.save_local(VECTOR_STORE_LOCAL)\n",
394
+ "print(f\" βœ… Saved to {VECTOR_STORE_LOCAL}\")\n",
395
+ "\n",
396
+ "# Copy to Google Drive for persistence\n",
397
+ "print(\"\\n☁️ Copying to Google Drive...\")\n",
398
+ "os.makedirs(VECTOR_STORE_DRIVE_PATH, exist_ok=True)\n",
399
+ "\n",
400
+ "for fname in os.listdir(VECTOR_STORE_LOCAL):\n",
401
+ " src = os.path.join(VECTOR_STORE_LOCAL, fname)\n",
402
+ " dst = os.path.join(VECTOR_STORE_DRIVE_PATH, fname)\n",
403
+ " shutil.copy2(src, dst)\n",
404
+ " size = os.path.getsize(dst) / 1e6\n",
405
+ " print(f\" βœ… {fname} ({size:.1f} MB)\")\n",
406
+ "\n",
407
+ "print(f\"\\nπŸŽ‰ Vector store saved to Google Drive!\")\n",
408
+ "print(f\" Path: {VECTOR_STORE_DRIVE_PATH}\")\n",
409
+ "print(f\" Total vectors: {vector_store.index.ntotal}\")\n",
410
+ "print(\"\\nπŸ“Œ You can now download this folder to your laptop\")\n",
411
+ "print(\" and place it at: Study_Ocean Rag/data/vector_store/\")"
412
+ ]
413
+ },
414
+ {
415
+ "cell_type": "markdown",
416
+ "metadata": {},
417
+ "source": [
418
+ "## πŸ§ͺ Step 7 β€” Test the Vector Store (Quick Sanity Check)"
419
+ ]
420
+ },
421
+ {
422
+ "cell_type": "code",
423
+ "execution_count": null,
424
+ "metadata": {},
425
+ "outputs": [],
426
+ "source": [
427
+ "# ── Quick retrieval test ─────────────────────────────────────\n",
428
+ "test_question = \"What is Newton's second law of motion?\"\n",
429
+ "print(f\"πŸ” Test query: '{test_question}'\\n\")\n",
430
+ "\n",
431
+ "results = vector_store.similarity_search(test_question, k=3)\n",
432
+ "for i, doc in enumerate(results, 1):\n",
433
+ " print(f\"--- Result {i} ---\")\n",
434
+ " print(f\"Subject : {doc.metadata.get('subject', 'N/A')}\")\n",
435
+ " print(f\"File : {doc.metadata.get('filename', 'N/A')}\")\n",
436
+ " print(f\"Preview : {doc.page_content[:200]}...\")\n",
437
+ " print()"
438
+ ]
439
+ },
440
+ {
441
+ "cell_type": "markdown",
442
+ "metadata": {},
443
+ "source": [
444
+ "## πŸ€– Step 8 β€” Build the RAG Chain (Gemini + FAISS)"
445
+ ]
446
+ },
447
+ {
448
+ "cell_type": "code",
449
+ "execution_count": null,
450
+ "metadata": {},
451
+ "outputs": [],
452
+ "source": [
453
+ "from langchain_google_genai import ChatGoogleGenerativeAI\n",
454
+ "from langchain.chains import RetrievalQA\n",
455
+ "from langchain.prompts import PromptTemplate\n",
456
+ "\n",
457
+ "# Validate API key\n",
458
+ "if not GOOGLE_API_KEY or GOOGLE_API_KEY == \"\":\n",
459
+ " print(\"❌ GOOGLE_API_KEY is not set! Go back to Step 3 and paste your key.\")\n",
460
+ " raise ValueError(\"Missing GOOGLE_API_KEY\")\n",
461
+ "\n",
462
+ "# ── Student Mentor Prompt ────────────────────────────────────\n",
463
+ "MENTOR_PROMPT = PromptTemplate(\n",
464
+ " input_variables=[\"context\", \"question\"],\n",
465
+ " template=\"\"\"You are **Study Ocean AI Mentor** β€” a friendly, patient, and knowledgeable tutor who helps students understand their study material deeply.\n",
466
+ "\n",
467
+ "Your role:\n",
468
+ "- Solve student doubts with clear, step-by-step explanations\n",
469
+ "- Use simple language that students can easily understand\n",
470
+ "- Provide examples and analogies when helpful\n",
471
+ "- If the answer is in the context, use it; if not, say so honestly\n",
472
+ "- Encourage students and make learning enjoyable\n",
473
+ "\n",
474
+ "**Guidelines:**\n",
475
+ "1. Start with a brief, direct answer, then explain in detail\n",
476
+ "2. Use bullet points, numbered steps, or tables for clarity\n",
477
+ "3. For math/science problems, show the solution step by step\n",
478
+ "4. Mention which topic/chapter the information relates to when possible\n",
479
+ "5. If the topic isn't covered, say: \"This isn't in your study materials, but here's what I know...\"\n",
480
+ "\n",
481
+ "---\n",
482
+ "πŸ“š **Relevant Study Material:**\n",
483
+ "{context}\n",
484
+ "\n",
485
+ "---\n",
486
+ "πŸ™‹ **Student's Question:** {question}\n",
487
+ "\n",
488
+ "---\n",
489
+ "πŸŽ“ **Mentor's Response:**\n",
490
+ "\"\"\"\n",
491
+ ")\n",
492
+ "\n",
493
+ "# ── Initialize LLM ───────────────────────────────────────────\n",
494
+ "llm = ChatGoogleGenerativeAI(\n",
495
+ " model=LLM_MODEL,\n",
496
+ " google_api_key=GOOGLE_API_KEY,\n",
497
+ " temperature=0.3,\n",
498
+ " max_output_tokens=2048,\n",
499
+ " convert_system_message_to_human=True,\n",
500
+ ")\n",
501
+ "\n",
502
+ "# ── Build RAG Chain ──────────────────────────────────────────\n",
503
+ "retriever = vector_store.as_retriever(\n",
504
+ " search_type=\"similarity\",\n",
505
+ " search_kwargs={\"k\": TOP_K},\n",
506
+ ")\n",
507
+ "\n",
508
+ "rag_chain = RetrievalQA.from_chain_type(\n",
509
+ " llm=llm,\n",
510
+ " chain_type=\"stuff\",\n",
511
+ " retriever=retriever,\n",
512
+ " return_source_documents=True,\n",
513
+ " chain_type_kwargs={\"prompt\": MENTOR_PROMPT},\n",
514
+ ")\n",
515
+ "\n",
516
+ "print(\"βœ… RAG Chain ready!\")\n",
517
+ "print(f\" LLM: {LLM_MODEL}\")\n",
518
+ "print(f\" Retriever: top-{TOP_K} chunks\")"
519
+ ]
520
+ },
521
+ {
522
+ "cell_type": "markdown",
523
+ "metadata": {},
524
+ "source": [
525
+ "## πŸ§ͺ Step 9 β€” Test the Full RAG Pipeline"
526
+ ]
527
+ },
528
+ {
529
+ "cell_type": "code",
530
+ "execution_count": null,
531
+ "metadata": {},
532
+ "outputs": [],
533
+ "source": [
534
+ "def ask(question):\n",
535
+ " \"\"\"Ask the mentor a question.\"\"\"\n",
536
+ " print(f\"\\nπŸ™‹ Question: {question}\")\n",
537
+ " print(\"πŸ” Searching study material...\\n\")\n",
538
+ " result = rag_chain.invoke({\"query\": question})\n",
539
+ " print(\"πŸŽ“ Mentor's Answer:\")\n",
540
+ " print(\"─\" * 60)\n",
541
+ " print(result[\"result\"])\n",
542
+ " print(\"─\" * 60)\n",
543
+ " print(\"\\nπŸ“– Sources:\")\n",
544
+ " seen = set()\n",
545
+ " for doc in result.get(\"source_documents\", []):\n",
546
+ " key = doc.metadata.get(\"source\", \"?\")\n",
547
+ " if key not in seen:\n",
548
+ " seen.add(key)\n",
549
+ " print(f\" [{doc.metadata.get('subject','?')}] {doc.metadata.get('filename','?')}\")\n",
550
+ "\n",
551
+ "# Test it!\n",
552
+ "ask(\"Explain Newton's laws of motion with examples\")"
553
+ ]
554
+ },
555
+ {
556
+ "cell_type": "markdown",
557
+ "metadata": {},
558
+ "source": [
559
+ "## 🌐 Step 10 β€” Launch Gradio Chat UI (Public Link)"
560
+ ]
561
+ },
562
+ {
563
+ "cell_type": "code",
564
+ "execution_count": null,
565
+ "metadata": {},
566
+ "outputs": [],
567
+ "source": [
568
+ "import gradio as gr\n",
569
+ "\n",
570
+ "CUSTOM_CSS = \"\"\"\n",
571
+ ".gradio-container {\n",
572
+ " max-width: 900px !important;\n",
573
+ " margin: auto !important;\n",
574
+ " font-family: 'Inter', 'Segoe UI', sans-serif !important;\n",
575
+ "}\n",
576
+ ".header-section {\n",
577
+ " text-align: center;\n",
578
+ " padding: 20px 0 10px;\n",
579
+ " background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n",
580
+ " border-radius: 16px;\n",
581
+ " margin-bottom: 20px;\n",
582
+ " color: white;\n",
583
+ "}\n",
584
+ ".header-section h1 { font-size: 2em; margin: 0; color: white !important; }\n",
585
+ ".header-section p { opacity: 0.9; margin: 5px 0 0; font-size: 1.05em; color: #e8e8ff !important; }\n",
586
+ ".chatbot { border-radius: 12px !important; min-height: 450px !important; }\n",
587
+ ".primary-btn {\n",
588
+ " background: linear-gradient(135deg, #667eea, #764ba2) !important;\n",
589
+ " border: none !important; color: white !important;\n",
590
+ " border-radius: 10px !important; font-weight: 600 !important;\n",
591
+ "}\n",
592
+ ".primary-btn:hover { opacity: 0.9 !important; transform: translateY(-1px); }\n",
593
+ "\"\"\"\n",
594
+ "\n",
595
+ "def format_sources(docs):\n",
596
+ " if not docs: return \"\"\n",
597
+ " seen, lines = set(), [\"\\n\\n---\\nπŸ“– **Sources Referenced:**\\n\"]\n",
598
+ " for doc in docs:\n",
599
+ " key = f\"{doc.metadata.get('subject','?')}/{doc.metadata.get('source','?')}\"\n",
600
+ " if key not in seen:\n",
601
+ " seen.add(key)\n",
602
+ " lines.append(f\"**β€’** `{doc.metadata.get('subject','?')}` β†’ _{doc.metadata.get('filename','?')}_\")\n",
603
+ " return \"\\n\".join(lines)\n",
604
+ "\n",
605
+ "def respond(message, history):\n",
606
+ " if not message.strip():\n",
607
+ " return \"\", history\n",
608
+ " result = rag_chain.invoke({\"query\": message})\n",
609
+ " answer = result[\"result\"] + format_sources(result.get(\"source_documents\", []))\n",
610
+ " history.append((message, answer))\n",
611
+ " return \"\", history\n",
612
+ "\n",
613
+ "def clear_chat():\n",
614
+ " return [], \"\"\n",
615
+ "\n",
616
+ "with gr.Blocks(css=CUSTOM_CSS, title=\"Study Ocean AI Mentor\", theme=gr.themes.Soft()) as demo:\n",
617
+ " gr.HTML(\"\"\"\n",
618
+ " <div class=\"header-section\">\n",
619
+ " <h1>πŸŽ“ Study Ocean AI Mentor</h1>\n",
620
+ " <p>Ask any doubt from your study material β€” I'll explain it step by step!</p>\n",
621
+ " </div>\n",
622
+ " \"\"\")\n",
623
+ " chatbot = gr.Chatbot(\n",
624
+ " label=\"πŸ’¬ Chat with Your Mentor\",\n",
625
+ " elem_classes=[\"chatbot\"],\n",
626
+ " height=480,\n",
627
+ " show_copy_button=True,\n",
628
+ " bubble_full_width=False,\n",
629
+ " avatar_images=(None, \"https://api.dicebear.com/7.x/bottts/svg?seed=mentor&backgroundColor=6366f1\"),\n",
630
+ " )\n",
631
+ " with gr.Row():\n",
632
+ " msg = gr.Textbox(\n",
633
+ " placeholder=\"Type your doubt here... e.g., 'Explain Newton's third law'\",\n",
634
+ " scale=5, lines=1, max_lines=3, show_label=False, container=False,\n",
635
+ " )\n",
636
+ " submit_btn = gr.Button(\"Ask πŸš€\", elem_classes=[\"primary-btn\"], scale=1)\n",
637
+ " with gr.Row():\n",
638
+ " clear_btn = gr.Button(\"πŸ—‘οΈ Clear Chat\", size=\"sm\")\n",
639
+ "\n",
640
+ " gr.Examples(\n",
641
+ " examples=[\n",
642
+ " \"What are Newton's laws of motion? Explain with examples.\",\n",
643
+ " \"Explain the difference between mitosis and meiosis.\",\n",
644
+ " \"What is the Pythagorean theorem and how do I apply it?\",\n",
645
+ " \"Explain the concept of supply and demand in economics.\",\n",
646
+ " \"What are the key features of the Indian Constitution?\",\n",
647
+ " ],\n",
648
+ " inputs=msg, label=\"πŸ’‘ Try these example questions:\",\n",
649
+ " )\n",
650
+ "\n",
651
+ " msg.submit(respond, [msg, chatbot], [msg, chatbot])\n",
652
+ " submit_btn.click(respond, [msg, chatbot], [msg, chatbot])\n",
653
+ " clear_btn.click(clear_chat, outputs=[chatbot, msg])\n",
654
+ "\n",
655
+ "# Launch with share=True to get a public Gradio URL!\n",
656
+ "demo.launch(share=True, debug=False)"
657
+ ]
658
+ },
659
+ {
660
+ "cell_type": "markdown",
661
+ "metadata": {},
662
+ "source": [
663
+ "---\n",
664
+ "## πŸ’» Step 11 β€” Use Pre-built Vector Store (Skip Re-ingestion)\n",
665
+ "\n",
666
+ "> **Run this cell INSTEAD of Steps 4–6 if you already have a saved vector store on Drive**\n",
667
+ "> This lets you skip the ~1 hour ingestion on future sessions!"
668
+ ]
669
+ },
670
+ {
671
+ "cell_type": "code",
672
+ "execution_count": null,
673
+ "metadata": {},
674
+ "outputs": [],
675
+ "source": [
676
+ "# ── Load existing vector store from Drive ────────────────────\n",
677
+ "# ⚠️ Only run this cell if you've already done ingestion before!\n",
678
+ "# Skip this if running ingestion fresh (Steps 4-6).\n",
679
+ "\n",
680
+ "from langchain_community.vectorstores import FAISS\n",
681
+ "from langchain_huggingface import HuggingFaceEmbeddings\n",
682
+ "import torch, os\n",
683
+ "\n",
684
+ "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
685
+ "print(f\"πŸ”§ Loading embeddings on {device.upper()}...\")\n",
686
+ "\n",
687
+ "embeddings = HuggingFaceEmbeddings(\n",
688
+ " model_name=EMBEDDING_MODEL,\n",
689
+ " model_kwargs={\"device\": device},\n",
690
+ " encode_kwargs={\"normalize_embeddings\": True},\n",
691
+ ")\n",
692
+ "\n",
693
+ "# Try Drive path first, fall back to local\n",
694
+ "store_path = VECTOR_STORE_DRIVE_PATH if os.path.exists(VECTOR_STORE_DRIVE_PATH) else VECTOR_STORE_LOCAL\n",
695
+ "print(f\"πŸ“‚ Loading vector store from: {store_path}\")\n",
696
+ "\n",
697
+ "vector_store = FAISS.load_local(\n",
698
+ " store_path,\n",
699
+ " embeddings,\n",
700
+ " allow_dangerous_deserialization=True,\n",
701
+ ")\n",
702
+ "\n",
703
+ "print(f\"βœ… Loaded! Total vectors: {vector_store.index.ntotal}\")\n",
704
+ "print(\" Now run Steps 8 β†’ 10 to start chatting!\")"
705
+ ]
706
+ },
707
+ {
708
+ "cell_type": "markdown",
709
+ "metadata": {},
710
+ "source": [
711
+ "---\n",
712
+ "## πŸ“₯ Step 12 β€” Download Vector Store to Your Laptop\n",
713
+ "\n",
714
+ "> Run this to zip and download the vector store directly to your PC (no Drive needed)"
715
+ ]
716
+ },
717
+ {
718
+ "cell_type": "code",
719
+ "execution_count": null,
720
+ "metadata": {},
721
+ "outputs": [],
722
+ "source": [
723
+ "import shutil\n",
724
+ "from google.colab import files\n",
725
+ "\n",
726
+ "# Zip the vector store\n",
727
+ "print(\"πŸ“¦ Zipping vector store...\")\n",
728
+ "zip_path = \"/content/vector_store_export.zip\"\n",
729
+ "shutil.make_archive(\"/content/vector_store_export\", 'zip', VECTOR_STORE_LOCAL)\n",
730
+ "\n",
731
+ "size_mb = os.path.getsize(zip_path) / 1e6\n",
732
+ "print(f\" ZIP size: {size_mb:.1f} MB\")\n",
733
+ "\n",
734
+ "# Download to local machine\n",
735
+ "print(\"⬇️ Downloading to your computer...\")\n",
736
+ "files.download(zip_path)\n",
737
+ "print(\"\\nβœ… Download started!\")\n",
738
+ "print(\" Extract to: Study_Ocean Rag/data/vector_store/\")\n",
739
+ "print(\" Then run locally: python main.py chat\")"
740
+ ]
741
+ }
742
+ ]
743
+ }
api.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ # memory saving trick for Render 512MB limit!
4
+ os.environ["OMP_NUM_THREADS"] = "1"
5
+ os.environ["MKL_NUM_THREADS"] = "1"
6
+ os.environ["OPENBLAS_NUM_THREADS"] = "1"
7
+ os.environ["VECLIB_MAXIMUM_THREADS"] = "1"
8
+ os.environ["NUMEXPR_NUM_THREADS"] = "1"
9
+
10
+ from fastapi import FastAPI
11
+ from fastapi.middleware.cors import CORSMiddleware
12
+ from fastapi.responses import FileResponse
13
+ from pydantic import BaseModel
14
+ from rag_chain import get_chain, ask_question
15
+ import uvicorn
16
+
17
+
18
+ app = FastAPI(title="Study Ocean RAG API")
19
+
20
+ # Allow requests from your frontend (CORS)
21
+ app.add_middleware(
22
+ CORSMiddleware,
23
+ allow_origins=["*"], # In production, replace with your frontend URL
24
+ allow_credentials=True,
25
+ allow_methods=["*"],
26
+ allow_headers=["*"],
27
+ )
28
+
29
+ @app.get("/")
30
+ def serve_frontend():
31
+ return FileResponse("frontend_demo.html")
32
+
33
+ @app.get("/logo.png")
34
+ def serve_logo():
35
+ if os.path.exists("logo.png"):
36
+ return FileResponse("logo.png")
37
+ return {"error": "Logo not found"}
38
+
39
+ print("πŸ”„ Initializing Study Ocean AI Mentor...")
40
+ chain = get_chain()
41
+ print("βœ… API Ready to answer student doubts!")
42
+
43
+ class Query(BaseModel):
44
+ question: str
45
+
46
+ @app.post("/api/ask")
47
+ def ask(query: Query):
48
+ try:
49
+ # Pass the question to your LangChain RAG
50
+ result = ask_question(chain, query.question)
51
+ return {
52
+ "answer": result["answer"],
53
+ "sources": result["sources"]
54
+ }
55
+ except Exception as e:
56
+ import traceback
57
+ traceback.print_exc()
58
+ return {
59
+ "answer": f"Backend Error: {str(e)}",
60
+ "sources": []
61
+ }
62
+
63
+ if __name__ == "__main__":
64
+ # Run the API on port 8000
65
+ uvicorn.run(app, host="0.0.0.0", port=8000)
app.py ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Study Ocean AI Mentor β€” Gradio Web Interface
3
+
4
+ A beautiful, student-friendly chat interface for the RAG-based
5
+ doubt-solving mentor. Built with Gradio for easy deployment.
6
+ """
7
+
8
+ import gradio as gr
9
+ from rag_chain import get_chain, ask_question
10
+
11
+ # ─── Custom CSS for Premium Look ────────────────────────────
12
+ CUSTOM_CSS = """
13
+ /* Global */
14
+ .gradio-container {
15
+ max-width: 900px !important;
16
+ margin: auto !important;
17
+ font-family: 'Inter', 'Segoe UI', sans-serif !important;
18
+ }
19
+
20
+ /* Header */
21
+ .header-section {
22
+ text-align: center;
23
+ padding: 20px 0 10px;
24
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
25
+ border-radius: 16px;
26
+ margin-bottom: 20px;
27
+ color: white;
28
+ }
29
+
30
+ .header-section h1 {
31
+ font-size: 2em;
32
+ margin: 0;
33
+ color: white !important;
34
+ }
35
+
36
+ .header-section p {
37
+ opacity: 0.9;
38
+ margin: 5px 0 0;
39
+ font-size: 1.05em;
40
+ color: #e8e8ff !important;
41
+ }
42
+
43
+ /* Chat area */
44
+ .chatbot {
45
+ border-radius: 12px !important;
46
+ border: 1px solid #e2e8f0 !important;
47
+ min-height: 450px !important;
48
+ }
49
+
50
+ /* Source cards */
51
+ .source-card {
52
+ background: #f8fafc;
53
+ border: 1px solid #e2e8f0;
54
+ border-radius: 8px;
55
+ padding: 10px 14px;
56
+ margin: 4px 0;
57
+ font-size: 0.9em;
58
+ }
59
+
60
+ .source-card strong {
61
+ color: #6366f1;
62
+ }
63
+
64
+ /* Override dark mode */
65
+ .dark .source-card {
66
+ background: #1e293b;
67
+ border-color: #334155;
68
+ }
69
+
70
+ /* Buttons */
71
+ .primary-btn {
72
+ background: linear-gradient(135deg, #667eea, #764ba2) !important;
73
+ border: none !important;
74
+ color: white !important;
75
+ border-radius: 10px !important;
76
+ font-weight: 600 !important;
77
+ padding: 10px 24px !important;
78
+ }
79
+
80
+ .primary-btn:hover {
81
+ opacity: 0.9 !important;
82
+ transform: translateY(-1px);
83
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4) !important;
84
+ }
85
+ """
86
+
87
+ # ─── Initialize chain on startup ────────────────────────────
88
+ print("πŸ”„ Initializing Study Ocean AI Mentor...")
89
+ chain = get_chain()
90
+ print("βœ… Ready to answer student doubts!\n")
91
+
92
+
93
+ def format_sources(sources: list) -> str:
94
+ """Format source documents into a readable string."""
95
+ if not sources:
96
+ return ""
97
+
98
+ lines = ["\n\n---\nπŸ“– **Sources Referenced:**\n"]
99
+ for i, src in enumerate(sources, 1):
100
+ lines.append(f"**{i}.** `{src['subject']}` β†’ _{src['file']}_")
101
+ return "\n".join(lines)
102
+
103
+
104
+ def respond(message: str, chat_history: list) -> tuple:
105
+ """Handle a student's question and return the mentor's response."""
106
+ if not message.strip():
107
+ return "", chat_history
108
+
109
+ # Get answer from RAG chain
110
+ result = ask_question(chain, message)
111
+ answer = result["answer"]
112
+ sources = result["sources"]
113
+
114
+ # Append source references to the answer
115
+ answer_with_sources = answer + format_sources(sources)
116
+
117
+ # Update chat history
118
+ chat_history.append((message, answer_with_sources))
119
+
120
+ return "", chat_history
121
+
122
+
123
+ def clear_chat():
124
+ """Clear the chat history."""
125
+ return [], ""
126
+
127
+
128
+ # ─── Build Gradio Interface ─────────────────────────────────
129
+ with gr.Blocks(title="Study Ocean AI Mentor") as app:
130
+
131
+ # Header
132
+ gr.HTML("""
133
+ <div class="header-section">
134
+ <h1>πŸŽ“ Study Ocean AI Mentor</h1>
135
+ <p>Ask any doubt from your study material β€” I'll explain it step by step!</p>
136
+ </div>
137
+ """)
138
+
139
+ # Chatbot
140
+ chatbot = gr.Chatbot(
141
+ label="πŸ’¬ Chat with Your Mentor",
142
+ elem_classes=["chatbot"],
143
+ height=480,
144
+ avatar_images=(None, "https://api.dicebear.com/7.x/bottts/svg?seed=mentor&backgroundColor=6366f1"),
145
+ )
146
+
147
+ # Input area
148
+ with gr.Row():
149
+ msg = gr.Textbox(
150
+ placeholder="Type your doubt here... e.g., 'Explain Newton's third law with examples'",
151
+ label="Your Question",
152
+ scale=5,
153
+ lines=1,
154
+ max_lines=3,
155
+ show_label=False,
156
+ container=False,
157
+ )
158
+ submit_btn = gr.Button("Ask πŸš€", elem_classes=["primary-btn"], scale=1)
159
+
160
+ # Action buttons
161
+ with gr.Row():
162
+ clear_btn = gr.Button("πŸ—‘οΈ Clear Chat", size="sm")
163
+
164
+ # Example questions
165
+ gr.Examples(
166
+ examples=[
167
+ "What are Newton's laws of motion? Explain with examples.",
168
+ "Explain the difference between mitosis and meiosis.",
169
+ "What is the Pythagorean theorem and how do I use it?",
170
+ "Explain the concept of supply and demand in economics.",
171
+ "What are the key features of the Indian Constitution?",
172
+ "Solve: Find the derivative of f(x) = 3xΒ² + 2x - 5",
173
+ ],
174
+ inputs=msg,
175
+ label="πŸ’‘ Try these example questions:",
176
+ )
177
+
178
+ # Tips section
179
+ with gr.Accordion("πŸ“Œ Tips for Better Answers", open=False):
180
+ gr.Markdown("""
181
+ - **Be specific**: Instead of "explain physics", ask "explain projectile motion with examples"
182
+ - **Mention the topic**: "In organic chemistry, what is an SN2 reaction?"
183
+ - **Ask for steps**: "Solve step by step: integrate sin(x)cos(x) dx"
184
+ - **Request comparisons**: "Compare TCP and UDP protocols"
185
+ - **Ask for formulas**: "List all the trigonometric identities"
186
+ """)
187
+
188
+ # Event handlers
189
+ msg.submit(respond, [msg, chatbot], [msg, chatbot])
190
+ submit_btn.click(respond, [msg, chatbot], [msg, chatbot])
191
+ clear_btn.click(clear_chat, outputs=[chatbot, msg])
192
+
193
+
194
+ # ─── Launch ─────────────────────────────────────────────────
195
+ if __name__ == "__main__":
196
+ app.launch(
197
+ server_name="0.0.0.0",
198
+ server_port=7860,
199
+ share=False,
200
+ show_error=True,
201
+ css=CUSTOM_CSS,
202
+ theme=gr.themes.Soft(),
203
+ )
config.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Configuration module for Study Ocean RAG Student Mentor.
3
+ Loads environment variables and provides default settings.
4
+ """
5
+
6
+ import os
7
+ from dotenv import load_dotenv
8
+
9
+ load_dotenv()
10
+
11
+ # ─── API Keys ────────────────────────────────────────────────
12
+ GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY", "")
13
+
14
+ # ─── Model Configuration ────────────────────────────────────
15
+ EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "all-MiniLM-L6-v2")
16
+ LLM_MODEL = os.getenv("LLM_MODEL", "gemini-2.0-flash")
17
+
18
+ # ─── Paths ───────────────────────────────────────────────────
19
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
20
+ PDF_DIR = os.path.join(BASE_DIR, os.getenv("PDF_DIR", "data/pdfs"))
21
+ VECTOR_STORE_PATH = os.path.join(BASE_DIR, os.getenv("VECTOR_STORE_PATH", "data/vector_store"))
22
+
23
+ # ─── Text Splitting ─────────────────────────────────────────
24
+ CHUNK_SIZE = int(os.getenv("CHUNK_SIZE", "1000"))
25
+ CHUNK_OVERLAP = int(os.getenv("CHUNK_OVERLAP", "200"))
26
+
27
+ # ─── RAG Parameters ─────────────────────────────────────────
28
+ TOP_K_RESULTS = 2 # Number of relevant chunks to retrieve (lowered for faster responses)
29
+ TEMPERATURE = 0.3 # LLM temperature (lower = more focused)
30
+ MAX_OUTPUT_TOKENS = 2048 # Max tokens in LLM response
data/pdfs/StudyOceanPDFs/BIOLOGY_FLASHCARD_FOR_NEET.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:dbfe25b3be671a226d8d27b3eb34fb60911846b5918ac78e72aed009b7137092
3
+ size 36021223
data/pdfs/StudyOceanPDFs/CBSE_2026_Class_12_Physics_Set_2_Question_Paper_With_Solutions_PDF.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2ee3feeb5e2c6b2fb6eb414e9d4cc11c008a4e7ff53564f60f0c88bdfe66c705
3
+ size 561121
data/pdfs/StudyOceanPDFs/CENGAGE_IOC_CRUX_BY_studyoceanbooks.PDF ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1fe208f6066c088482bfb970f53d969c67045459cd7fde203f2cdc1bd421aeda
3
+ size 6955370
data/pdfs/StudyOceanPDFs/COM_COLLISION_Short_Notes_1_.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ac84e71f21e1293fbb3463f89d43b6cfb6f21d02995eca69f8febb80170f8031
3
+ size 1507057
data/pdfs/StudyOceanPDFs/Chemistry_Flash_Cards_Colourful_Complete.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:660f51ef507ec000388389a1660387d35d3a2f897807fa496a93a89394eeb187
3
+ size 9544339
data/pdfs/StudyOceanPDFs/Chemistry_Flashcard_for_JEE_NEET.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:467be87f0dc909fc49f89786b890fc99e1c0b858c22ebe46b548fdebea80a77e
3
+ size 10429728
data/pdfs/StudyOceanPDFs/Chemistry_Formula_Booklet_Hashlearn.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:40214c132da21dffb625a043e616ac13b829a789c8f74ba3d1e12bfc0145554b
3
+ size 6484359
data/pdfs/StudyOceanPDFs/Circular_dyanamic_short_notes_1_.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:87714aff5c3b6135f26fb58c59ef472fd1f4f7ff0985477513fd20b2cc0d3c11
3
+ size 456696
data/pdfs/StudyOceanPDFs/Circular_motion_in_kinematics_short_notes_1_.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:14e351fa21a2cdfc7bad9177967118c7d153f32e7637cff0381d2c67c6c96604
3
+ size 1128082
data/pdfs/StudyOceanPDFs/Rotational_motion_short_notes_1_.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e6b5ff18923f0413d3470b1c28186194938b9e2d74d336c5e42252ad22d38628
3
+ size 2888938
data/pdfs/StudyOceanPDFs/Thermal_properties_of_matter_short_notes_.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4d4beba58867ed59e25b8a15193f991fb34fedbec21e20797cec6ac82450bc44
3
+ size 2325417
data/pdfs/StudyOceanPDFs/Units_measurements_and_errors_short_notes_1_.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2ed834b2629337279ff4793e8d771311ae2c110d3b6a4fd36db8a1a9ea0cf58c
3
+ size 2533427
data/pdfs/StudyOceanPDFs/Vertical_circular_motion_short_notes_1_.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e5710614b9d68770e10f4792a0f8fbf3cf99a4af569cab6a094d9d555abbd508
3
+ size 716852
data/pdfs/StudyOceanPDFs/Wave_motion_short_notes_1_.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3200903717291d721e29e4aba296c2493351c083b0b9675ff2dfc0e34e82a2a2
3
+ size 2288200
data/pdfs/StudyOceanPDFs/Work_Power_Energy_Short_Notes_1_.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5f6f14e982aff842c61a2f159aaf9a044742cd0aa3d07a7f721e3d08a7d7e20f
3
+ size 2026851
data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_1_question_paper_2026_1771575627702.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ec9e62a8630a7a359f36927f3b3fd8ec0b933a665b4d1c9935a81bbcadb2f753
3
+ size 2405782
data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_2_question_paper_2026_1771575619105.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:862f05a505453e84746b31e88fd4d42dad7cbf518a985ae974f103c4f7dba24e
3
+ size 2364093
data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_3_question_paper_2026_1771575610905.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2f3a677b0c4457ff58985f9dc6a56c65f294fe4bacfc420872774c81b71b47b6
3
+ size 2475777
data/pdfs/StudyOceanPDFs/chapter_10.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:60502d7ef8f74d65cdef3b847d6610f4562e60cae90717a982a800785c6510f0
3
+ size 1852257
data/pdfs/StudyOceanPDFs/chapter_11.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f79bb617255b2a183aabf9ff4a34fc27afe91797ed80f098a1e6b4c767bf879f
3
+ size 1842601
data/pdfs/StudyOceanPDFs/chapter_12.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4d33faddf847f9c6f05f32143f84ed7038fe3d2bf4f9462a07c7083b18b30258
3
+ size 4467748
data/pdfs/StudyOceanPDFs/chapter_13.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8198c5c589362a88b1dbf5d39b37c1faa0eebbec02d14a039d8146563a4cadf5
3
+ size 3530630
data/pdfs/StudyOceanPDFs/chapter_14.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:856a0e87419c68485bd1b852dedfdb19fdef9017ad278086b20f1485addb6950
3
+ size 3381393
data/pdfs/StudyOceanPDFs/chapter_15.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:40fa8f886030c1fa244779661159cacd06763d252e0f13d2ffd39000b856ee64
3
+ size 2954844
data/pdfs/StudyOceanPDFs/chapter_2.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:563cf1492644cf09e14573744e176230e1157a75bcbeeabe118d9ac9acc5e959
3
+ size 1577447
data/pdfs/StudyOceanPDFs/chapter_3_1_.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:93f99211db8fa7628a1cf092bc3b19f98124ae34208c30d0a99bb359788fbe2e
3
+ size 2022459
data/pdfs/StudyOceanPDFs/chapter_4.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b26907e1916bf831705c9cfcfb32c1d8c92faee0180b107947be830f1116cdde
3
+ size 1700543
data/pdfs/StudyOceanPDFs/chapter_5.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8c5c402f9d758771f4e22dc3f01cd6fa61c1c5b56a83c1210d2929bba739486f
3
+ size 987219
data/pdfs/StudyOceanPDFs/chapter_6.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:592a39941a139568374430fbaac2a277932baecc79b742937662a3087b7961df
3
+ size 3612728
data/pdfs/StudyOceanPDFs/chapter_7.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:dbc4c8dbcbbf29a76295581a9d4f1e9084a6b7cfb5726af5559d8d3f34802ac8
3
+ size 1028339
data/pdfs/StudyOceanPDFs/vector_sheets_Cyan_notes_1_1_.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3c2ab1f57ee66147454fcae197dadbfb8e4d718bb17eece72eb5d257ec97abce
3
+ size 903709
data/vector_store/index.faiss ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:eabc69fd7afeee324561ec01698a1fbe0464ab7cade2790647f67b504074664a
3
+ size 18837549
data/vector_store/index.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e0a5e3e437a7b95670e59ebd02a2d16d648b1f0cd463c61369ff0bc910059b03
3
+ size 10367491
frontend_demo.html ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Study Ocean | AI Mentor</title>
7
+ <!-- GSAP for Gen-Z Animations -->
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
9
+ <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;500;700&display=swap" rel="stylesheet">
10
+
11
+ <!-- MathJax for rendering Math Equations ($ and $$) -->
12
+ <script>
13
+ MathJax = {
14
+ tex: { inlineMath: [['$', '$'], ['\\(', '\\)']] }
15
+ };
16
+ </script>
17
+ <script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"></script>
18
+
19
+ <style>
20
+ :root {
21
+ --bg-color: #050505;
22
+ --glass-bg: rgba(255, 255, 255, 0.03);
23
+ --glass-border: rgba(255, 255, 255, 0.05);
24
+ --accent: #8b5cf6;
25
+ --accent-glow: rgba(139, 92, 246, 0.5);
26
+ --text-main: #f8fafc;
27
+ --text-muted: #94a3b8;
28
+ }
29
+
30
+ body, html {
31
+ margin: 0; padding: 0;
32
+ background-color: var(--bg-color);
33
+ font-family: 'Outfit', sans-serif;
34
+ color: var(--text-main);
35
+ height: 100vh;
36
+ overflow: hidden;
37
+ display: flex;
38
+ justify-content: center;
39
+ align-items: center;
40
+ background-image:
41
+ radial-gradient(circle at 15% 50%, rgba(139, 92, 246, 0.15), transparent 25%),
42
+ radial-gradient(circle at 85% 30%, rgba(56, 189, 248, 0.15), transparent 25%);
43
+ }
44
+
45
+ .chat-container {
46
+ width: 100%;
47
+ max-width: 900px;
48
+ height: 85vh;
49
+ background: var(--glass-bg);
50
+ backdrop-filter: blur(20px);
51
+ border: 1px solid var(--glass-border);
52
+ border-radius: 24px;
53
+ display: flex;
54
+ flex-direction: column;
55
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
56
+ overflow: hidden;
57
+ position: relative;
58
+ }
59
+
60
+ /* Animated top bar */
61
+ .header {
62
+ padding: 24px;
63
+ border-bottom: 1px solid var(--glass-border);
64
+ display: flex;
65
+ align-items: center;
66
+ gap: 15px;
67
+ }
68
+
69
+ .ai-avatar {
70
+ width: 55px;
71
+ height: 55px;
72
+ border-radius: 50%;
73
+ object-fit: cover;
74
+ border: 2px solid var(--accent);
75
+ box-shadow: 0 0 15px var(--accent-glow);
76
+ animation: pulse 2s infinite alternate;
77
+ }
78
+
79
+ .header-text h1 { margin: 0; font-size: 1.5rem; font-weight: 700; }
80
+ .header-text p { margin: 0; font-size: 0.9rem; color: var(--text-muted); }
81
+
82
+ .chat-history {
83
+ flex-grow: 1;
84
+ padding: 24px;
85
+ overflow-y: auto;
86
+ display: flex;
87
+ flex-direction: column;
88
+ gap: 20px;
89
+ }
90
+
91
+ .message {
92
+ max-width: 80%;
93
+ padding: 16px 20px;
94
+ border-radius: 20px;
95
+ font-size: 1.05rem;
96
+ line-height: 1.5;
97
+ opacity: 0;
98
+ transform: translateY(20px);
99
+ }
100
+
101
+ .user-msg {
102
+ align-self: flex-end;
103
+ background: linear-gradient(135deg, #3b82f6, var(--accent));
104
+ border-bottom-right-radius: 4px;
105
+ }
106
+
107
+ .ai-msg {
108
+ align-self: flex-start;
109
+ background: rgba(255, 255, 255, 0.05);
110
+ border: 1px solid var(--glass-border);
111
+ border-bottom-left-radius: 4px;
112
+ }
113
+
114
+ .input-area {
115
+ padding: 20px 24px;
116
+ border-top: 1px solid var(--glass-border);
117
+ display: flex;
118
+ gap: 15px;
119
+ }
120
+
121
+ input {
122
+ flex-grow: 1;
123
+ background: rgba(0,0,0,0.3);
124
+ border: 1px solid var(--glass-border);
125
+ padding: 18px 24px;
126
+ border-radius: 100px;
127
+ color: white;
128
+ font-size: 1rem;
129
+ font-family: 'Outfit';
130
+ outline: none;
131
+ transition: border-color 0.3s ease;
132
+ }
133
+
134
+ input:focus {
135
+ border-color: var(--accent);
136
+ box-shadow: 0 0 15px rgba(139, 92, 246, 0.2);
137
+ }
138
+
139
+ button {
140
+ background: var(--text-main);
141
+ color: var(--bg-color);
142
+ border: none;
143
+ border-radius: 100px;
144
+ padding: 0 30px;
145
+ font-weight: 700;
146
+ font-family: 'Outfit';
147
+ font-size: 1rem;
148
+ cursor: pointer;
149
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
150
+ }
151
+
152
+ button:hover {
153
+ transform: scale(1.05);
154
+ box-shadow: 0 0 20px rgba(255,255,255,0.3);
155
+ }
156
+
157
+ .typing-indicator { display: none; color: var(--text-muted); font-style: italic; }
158
+
159
+ @keyframes pulse {
160
+ 0% { box-shadow: 0 0 10px var(--accent-glow); }
161
+ 100% { box-shadow: 0 0 25px var(--accent-glow); }
162
+ }
163
+
164
+ /* Custom Scrollbar */
165
+ ::-webkit-scrollbar { width: 8px; }
166
+ ::-webkit-scrollbar-track { background: transparent; }
167
+ ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 10px; }
168
+ </style>
169
+ </head>
170
+ <body>
171
+
172
+ <div class="chat-container">
173
+ <div class="header">
174
+ <img src="logo.png" class="ai-avatar" alt="Study Ocean Logo">
175
+ <div class="header-text">
176
+ <h1>Study Ocean Mentor</h1>
177
+ <p>AI strictly trained on your PDFs</p>
178
+ </div>
179
+ </div>
180
+
181
+ <div class="chat-history" id="chat-history">
182
+ <div class="message ai-msg">
183
+ Hey! I'm your Study Ocean Mentor. Ask me any doubt from your materials, and I'll explain it clearly. 🌊
184
+ </div>
185
+ </div>
186
+
187
+ <div class="input-area">
188
+ <input type="text" id="user-input" placeholder="e.g. Explain Newton's laws of motion...">
189
+ <button id="send-btn">Ask</button>
190
+ </div>
191
+ </div>
192
+
193
+ <script>
194
+ // Initial entrance animation using GSAP
195
+ gsap.from(".chat-container", { y: 50, opacity: 0, duration: 1, ease: "power3.out" });
196
+ gsap.to(".ai-msg", { y: 0, opacity: 1, duration: 0.5, delay: 0.5 });
197
+
198
+ const history = document.getElementById("chat-history");
199
+ const input = document.getElementById("user-input");
200
+ const sendBtn = document.getElementById("send-btn");
201
+
202
+ function appendMessage(text, isUser) {
203
+ const msgDiv = document.createElement("div");
204
+ msgDiv.className = `message ${isUser ? 'user-msg' : 'ai-msg'}`;
205
+ // Simple markdown handling for bold text
206
+ msgDiv.innerHTML = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>').replace(/\n/g, '<br>');
207
+ history.appendChild(msgDiv);
208
+
209
+ // Animate message appearing
210
+ gsap.to(msgDiv, { y: 0, opacity: 1, duration: 0.4, ease: "back.out(1.7)" });
211
+ history.scrollTop = history.scrollHeight;
212
+
213
+ // Trigger MathJax to render the equations
214
+ if (window.MathJax) {
215
+ MathJax.typesetPromise([msgDiv]).catch((err) => console.log(err.message));
216
+ }
217
+
218
+ return msgDiv;
219
+ }
220
+
221
+ async function sendMessage() {
222
+ const text = input.value.trim();
223
+ if(!text) return;
224
+
225
+ input.value = "";
226
+ appendMessage(text, true);
227
+
228
+ // Add loading state
229
+ const loadingMsg = appendMessage("Thinking...", false);
230
+ gsap.to(loadingMsg, { opacity: 0.5, repeat: -1, yoyo: true, duration: 0.5 });
231
+
232
+ try {
233
+ // Call the FastAPI Backend
234
+ const response = await fetch("/api/ask", {
235
+ method: "POST",
236
+ headers: { "Content-Type": "application/json" },
237
+ body: JSON.stringify({ question: text })
238
+ });
239
+
240
+ const data = await response.json();
241
+
242
+ // Format response
243
+ let finalAnswer = data.answer;
244
+
245
+ // Replace loading message
246
+ gsap.killTweensOf(loadingMsg);
247
+ loadingMsg.innerHTML = finalAnswer.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>').replace(/\n/g, '<br>');
248
+ gsap.to(loadingMsg, { opacity: 1, duration: 0.2 });
249
+
250
+ // Render MathJax for the loaded answer
251
+ if (window.MathJax) {
252
+ MathJax.typesetPromise([loadingMsg]).catch((err) => console.log(err.message));
253
+ }
254
+
255
+ } catch(e) {
256
+ gsap.killTweensOf(loadingMsg);
257
+ loadingMsg.innerHTML = "❌ Failed to connect to backend. Is the FastAPI server running on port 8000?";
258
+ gsap.to(loadingMsg, { opacity: 1, duration: 0.2 });
259
+ }
260
+ history.scrollTop = history.scrollHeight;
261
+ }
262
+
263
+ sendBtn.addEventListener("click", sendMessage);
264
+ input.addEventListener("keypress", (e) => { if(e.key === "Enter") sendMessage(); });
265
+ </script>
266
+ </body>
267
+ </html>
ingest.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ PDF Ingestion & Vector Store Builder for Study Ocean RAG.
3
+
4
+ This module:
5
+ 1. Recursively scans the PDF directory for all .pdf files
6
+ 2. Extracts text from each PDF using PyMuPDF (faster & more reliable)
7
+ 3. Splits text into overlapping chunks
8
+ 4. Generates embeddings using a local sentence-transformer model
9
+ 5. Stores embeddings in a FAISS vector store for fast retrieval
10
+ """
11
+
12
+ import os
13
+ import sys
14
+ import time
15
+ import fitz # PyMuPDF
16
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
17
+ from langchain_community.vectorstores import FAISS
18
+ from langchain_huggingface import HuggingFaceEmbeddings
19
+
20
+ import config
21
+
22
+
23
+ def collect_pdf_paths(pdf_dir: str) -> list[str]:
24
+ """Recursively collect all PDF file paths from the given directory."""
25
+ pdf_paths = []
26
+ for root, _, files in os.walk(pdf_dir):
27
+ for file in files:
28
+ if file.lower().endswith(".pdf"):
29
+ pdf_paths.append(os.path.join(root, file))
30
+ return sorted(pdf_paths)
31
+
32
+
33
+ def extract_text_from_pdf(pdf_path: str) -> str:
34
+ """Extract all text from a PDF using PyMuPDF."""
35
+ text = ""
36
+ try:
37
+ doc = fitz.open(pdf_path)
38
+ for page_num in range(len(doc)):
39
+ page = doc[page_num]
40
+ text += page.get_text()
41
+ doc.close()
42
+ except Exception as e:
43
+ print(f" ⚠ Error reading {os.path.basename(pdf_path)}: {e}")
44
+ return text
45
+
46
+
47
+ def build_documents(pdf_paths: list[str]) -> list:
48
+ """
49
+ Extract text from all PDFs and split into chunks.
50
+ Each chunk carries metadata about its source PDF and page info.
51
+ """
52
+ text_splitter = RecursiveCharacterTextSplitter(
53
+ chunk_size=config.CHUNK_SIZE,
54
+ chunk_overlap=config.CHUNK_OVERLAP,
55
+ separators=["\n\n", "\n", ". ", " ", ""],
56
+ length_function=len,
57
+ )
58
+
59
+ all_documents = []
60
+ for i, pdf_path in enumerate(pdf_paths, 1):
61
+ filename = os.path.basename(pdf_path)
62
+ relative_path = os.path.relpath(pdf_path, config.PDF_DIR)
63
+ # Derive subject from folder structure
64
+ parts = relative_path.replace("\\", "/").split("/")
65
+ subject = parts[0] if len(parts) > 1 else "General"
66
+
67
+ print(f" [{i}/{len(pdf_paths)}] Processing: {relative_path}")
68
+
69
+ text = extract_text_from_pdf(pdf_path)
70
+ if not text.strip():
71
+ print(f" ⚠ No text extracted (might be scanned/image PDF)")
72
+ continue
73
+
74
+ # Create chunks with rich metadata
75
+ chunks = text_splitter.create_documents(
76
+ texts=[text],
77
+ metadatas=[{
78
+ "source": relative_path,
79
+ "filename": filename,
80
+ "subject": subject,
81
+ }]
82
+ )
83
+ all_documents.extend(chunks)
84
+
85
+ return all_documents
86
+
87
+
88
+ def create_vector_store(documents: list, embeddings) -> FAISS:
89
+ """Create a FAISS vector store from documents."""
90
+ print(f"\nπŸ“Š Creating vector store with {len(documents)} chunks...")
91
+
92
+ # Process in batches to avoid memory issues
93
+ batch_size = 500
94
+ vector_store = None
95
+
96
+ for i in range(0, len(documents), batch_size):
97
+ batch = documents[i:i + batch_size]
98
+ batch_num = (i // batch_size) + 1
99
+ total_batches = (len(documents) + batch_size - 1) // batch_size
100
+ print(f" Embedding batch {batch_num}/{total_batches} ({len(batch)} chunks)...")
101
+
102
+ if vector_store is None:
103
+ vector_store = FAISS.from_documents(batch, embeddings)
104
+ else:
105
+ batch_store = FAISS.from_documents(batch, embeddings)
106
+ vector_store.merge_from(batch_store)
107
+
108
+ return vector_store
109
+
110
+
111
+ def run_ingestion():
112
+ """Main ingestion pipeline."""
113
+ print("=" * 60)
114
+ print("πŸŽ“ Study Ocean RAG β€” PDF Ingestion Pipeline")
115
+ print("=" * 60)
116
+
117
+ # 1. Collect PDFs
118
+ print(f"\nπŸ“ Scanning for PDFs in: {config.PDF_DIR}")
119
+ pdf_paths = collect_pdf_paths(config.PDF_DIR)
120
+
121
+ if not pdf_paths:
122
+ print("❌ No PDF files found! Make sure your PDFs are in the data/pdfs directory.")
123
+ print(" Extract StudyOceanPDFs.zip into data/pdfs/ first.")
124
+ sys.exit(1)
125
+
126
+ print(f" Found {len(pdf_paths)} PDF files\n")
127
+
128
+ # 2. Extract and chunk
129
+ print("πŸ“„ Extracting text and creating chunks...")
130
+ start_time = time.time()
131
+ documents = build_documents(pdf_paths)
132
+ extract_time = time.time() - start_time
133
+ print(f"\n βœ… Created {len(documents)} text chunks in {extract_time:.1f}s")
134
+
135
+ if not documents:
136
+ print("❌ No text could be extracted from any PDF. Exiting.")
137
+ sys.exit(1)
138
+
139
+ # 3. Initialize embeddings
140
+ print(f"\n🧠 Loading embedding model: {config.EMBEDDING_MODEL}")
141
+ embeddings = HuggingFaceEmbeddings(
142
+ model_name=config.EMBEDDING_MODEL,
143
+ model_kwargs={"device": "cpu"},
144
+ encode_kwargs={"normalize_embeddings": True, "batch_size": 64},
145
+ )
146
+
147
+ # 4. Build vector store
148
+ start_time = time.time()
149
+ vector_store = create_vector_store(documents, embeddings)
150
+ embed_time = time.time() - start_time
151
+ print(f" βœ… Vector store created in {embed_time:.1f}s")
152
+
153
+ # 5. Save to disk
154
+ os.makedirs(config.VECTOR_STORE_PATH, exist_ok=True)
155
+ print(f"\nπŸ’Ύ Saving vector store to: {config.VECTOR_STORE_PATH}")
156
+ vector_store.save_local(config.VECTOR_STORE_PATH)
157
+ print(" βœ… Vector store saved successfully!")
158
+
159
+ # Summary
160
+ print("\n" + "=" * 60)
161
+ print("βœ… INGESTION COMPLETE")
162
+ print(f" PDFs processed: {len(pdf_paths)}")
163
+ print(f" Chunks created: {len(documents)}")
164
+ print(f" Extract time: {extract_time:.1f}s")
165
+ print(f" Embedding time: {embed_time:.1f}s")
166
+ print(f" Store location: {config.VECTOR_STORE_PATH}")
167
+ print("=" * 60)
168
+
169
+
170
+ if __name__ == "__main__":
171
+ run_ingestion()
logo.png ADDED

Git LFS Details

  • SHA256: 67928e220f35fffadb9532a9694b8d878bfdad54d68d57614ee8ca2291a2695a
  • Pointer size: 131 Bytes
  • Size of remote file: 259 kB
main.py ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Study Ocean RAG β€” Main Entry Point
3
+
4
+ Usage:
5
+ python main.py ingest # Ingest PDFs and build vector store
6
+ python main.py chat # Launch the Gradio web UI
7
+ python main.py ask # Ask a single question in the terminal
8
+ """
9
+
10
+ import sys
11
+
12
+
13
+ def print_banner():
14
+ print("""
15
+ ╔══════════════════════════════════════════════════════════╗
16
+ β•‘ πŸŽ“ Study Ocean AI Mentor β•‘
17
+ β•‘ RAG-based Student Doubt Solver β•‘
18
+ ╠══════════════════════════════════════════════════════════╣
19
+ β•‘ β•‘
20
+ β•‘ Commands: β•‘
21
+ β•‘ python main.py ingest β€” Process PDFs & build index β•‘
22
+ β•‘ python main.py chat β€” Launch web chat interface β•‘
23
+ β•‘ python main.py ask β€” Ask a question in terminal β•‘
24
+ β•‘ β•‘
25
+ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
26
+ """)
27
+
28
+
29
+ def run_ingest():
30
+ """Run the PDF ingestion pipeline."""
31
+ from ingest import run_ingestion
32
+ run_ingestion()
33
+
34
+
35
+ def run_chat():
36
+ """Launch the Gradio web interface."""
37
+ from app import app
38
+ print("\n🌐 Starting Study Ocean AI Mentor web interface...")
39
+ print(" Open http://localhost:7860 in your browser\n")
40
+ app.launch(
41
+ server_name="0.0.0.0",
42
+ server_port=7860,
43
+ share=False,
44
+ show_error=True,
45
+ )
46
+
47
+
48
+ def run_ask():
49
+ """Interactive terminal Q&A mode."""
50
+ from rag_chain import get_chain, ask_question
51
+
52
+ print("\nπŸŽ“ Study Ocean AI Mentor β€” Terminal Mode")
53
+ print(" Type your question and press Enter.")
54
+ print(" Type 'quit' or 'exit' to stop.\n")
55
+
56
+ chain = get_chain()
57
+
58
+ while True:
59
+ try:
60
+ question = input("\nπŸ™‹ Your doubt: ").strip()
61
+ except (KeyboardInterrupt, EOFError):
62
+ print("\n\nπŸ‘‹ Goodbye! Keep studying! πŸ“š")
63
+ break
64
+
65
+ if question.lower() in ("quit", "exit", "q"):
66
+ print("\nπŸ‘‹ Goodbye! Keep studying! πŸ“š")
67
+ break
68
+
69
+ if not question:
70
+ print(" ⚠ Please type a question.")
71
+ continue
72
+
73
+ print("\nπŸ” Searching study material...\n")
74
+ result = ask_question(chain, question)
75
+
76
+ print("─" * 50)
77
+ print(f"\nπŸŽ“ Mentor's Answer:\n")
78
+ print(result["answer"])
79
+
80
+ if result["sources"]:
81
+ print(f"\nπŸ“– Sources:")
82
+ for i, src in enumerate(result["sources"], 1):
83
+ print(f" {i}. [{src['subject']}] {src['file']}")
84
+ print("\n" + "─" * 50)
85
+
86
+
87
+ def main():
88
+ if len(sys.argv) < 2:
89
+ print_banner()
90
+ sys.exit(0)
91
+
92
+ command = sys.argv[1].lower()
93
+
94
+ if command == "ingest":
95
+ run_ingest()
96
+ elif command == "chat":
97
+ run_chat()
98
+ elif command == "ask":
99
+ run_ask()
100
+ else:
101
+ print(f"❌ Unknown command: {command}")
102
+ print_banner()
103
+ sys.exit(1)
104
+
105
+
106
+ if __name__ == "__main__":
107
+ main()
pyproject.toml ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "study-ocean-rag"
3
+ version = "0.1.0"
4
+ description = "AI Student Mentor - RAG-based doubt solver using study materials"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ dependencies = [
8
+ "ipykernel>=7.2.0",
9
+ "langchain>=0.3.0",
10
+ "langchain-community>=0.3.0",
11
+ "langchain-core>=0.3.0",
12
+ "langchain-google-genai>=2.1.0",
13
+ "langchain-huggingface>=0.1.0",
14
+ "pymupdf>=1.27.2.2",
15
+ "pypdf>=6.10.1",
16
+ "faiss-cpu>=1.9.0",
17
+ "chromadb>=0.6.0",
18
+ "sentence-transformers>=3.4.0",
19
+ "google-generativeai>=0.8.0",
20
+ "python-dotenv>=1.1.0",
21
+ "gradio>=5.0.0",
22
+ "tiktoken>=0.9.0",
23
+ ]