Spaces:
Runtime error
Runtime error
Mohit Raj commited on
Sync from GitHub via hub-sync
Browse filesThis view is limited to 50 files because it contains too many changes. Β See raw diff
- .env.example +18 -0
- .gitattributes +33 -0
- .python-version +1 -0
- .railwayignore +13 -0
- COLAB_GUIDE.md +112 -0
- Dockerfile +24 -0
- Procfile +1 -0
- README.md +5 -7
- StudyOcean_RAG_Colab.ipynb +743 -0
- api.py +65 -0
- app.py +203 -0
- config.py +30 -0
- data/pdfs/StudyOceanPDFs/BIOLOGY_FLASHCARD_FOR_NEET.pdf +3 -0
- data/pdfs/StudyOceanPDFs/CBSE_2026_Class_12_Physics_Set_2_Question_Paper_With_Solutions_PDF.pdf +3 -0
- data/pdfs/StudyOceanPDFs/CENGAGE_IOC_CRUX_BY_studyoceanbooks.PDF +3 -0
- data/pdfs/StudyOceanPDFs/COM_COLLISION_Short_Notes_1_.pdf +3 -0
- data/pdfs/StudyOceanPDFs/Chemistry_Flash_Cards_Colourful_Complete.pdf +3 -0
- data/pdfs/StudyOceanPDFs/Chemistry_Flashcard_for_JEE_NEET.pdf +3 -0
- data/pdfs/StudyOceanPDFs/Chemistry_Formula_Booklet_Hashlearn.pdf +3 -0
- data/pdfs/StudyOceanPDFs/Circular_dyanamic_short_notes_1_.pdf +3 -0
- data/pdfs/StudyOceanPDFs/Circular_motion_in_kinematics_short_notes_1_.pdf +3 -0
- data/pdfs/StudyOceanPDFs/Rotational_motion_short_notes_1_.pdf +3 -0
- data/pdfs/StudyOceanPDFs/Thermal_properties_of_matter_short_notes_.pdf +3 -0
- data/pdfs/StudyOceanPDFs/Units_measurements_and_errors_short_notes_1_.pdf +3 -0
- data/pdfs/StudyOceanPDFs/Vertical_circular_motion_short_notes_1_.pdf +3 -0
- data/pdfs/StudyOceanPDFs/Wave_motion_short_notes_1_.pdf +3 -0
- data/pdfs/StudyOceanPDFs/Work_Power_Energy_Short_Notes_1_.pdf +3 -0
- data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_1_question_paper_2026_1771575627702.pdf +3 -0
- data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_2_question_paper_2026_1771575619105.pdf +3 -0
- data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_3_question_paper_2026_1771575610905.pdf +3 -0
- data/pdfs/StudyOceanPDFs/chapter_10.pdf +3 -0
- data/pdfs/StudyOceanPDFs/chapter_11.pdf +3 -0
- data/pdfs/StudyOceanPDFs/chapter_12.pdf +3 -0
- data/pdfs/StudyOceanPDFs/chapter_13.pdf +3 -0
- data/pdfs/StudyOceanPDFs/chapter_14.pdf +3 -0
- data/pdfs/StudyOceanPDFs/chapter_15.pdf +3 -0
- data/pdfs/StudyOceanPDFs/chapter_2.pdf +3 -0
- data/pdfs/StudyOceanPDFs/chapter_3_1_.pdf +3 -0
- data/pdfs/StudyOceanPDFs/chapter_4.pdf +3 -0
- data/pdfs/StudyOceanPDFs/chapter_5.pdf +3 -0
- data/pdfs/StudyOceanPDFs/chapter_6.pdf +3 -0
- data/pdfs/StudyOceanPDFs/chapter_7.pdf +3 -0
- data/pdfs/StudyOceanPDFs/vector_sheets_Cyan_notes_1_1_.pdf +3 -0
- data/vector_store/index.faiss +3 -0
- data/vector_store/index.pkl +3 -0
- frontend_demo.html +267 -0
- ingest.py +171 -0
- logo.png +3 -0
- main.py +107 -0
- 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
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: docker
|
| 7 |
-
|
| 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
|
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 |
+
]
|