Spaces:
Sleeping
Sleeping
Upload 9 files
Browse files- Dockerfile +27 -0
- README.md +97 -7
- auth.py +59 -0
- chatbot_prompt.py +72 -0
- faq_routes.py +376 -0
- faq_services.py +101 -0
- faqs.csv +65 -0
- main.py +24 -0
- requirements.txt +15 -0
Dockerfile
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use an official lightweight Python image
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# Set environment variables to prevent Python from writing .pyc files and buffering stdout/stderr
|
| 5 |
+
ENV PYTHONDONTWRITEBYTECODE=1
|
| 6 |
+
ENV PYTHONUNBUFFERED=1
|
| 7 |
+
|
| 8 |
+
RUN mkdir -p /app/hf_cache
|
| 9 |
+
|
| 10 |
+
# Set working directory
|
| 11 |
+
WORKDIR /app
|
| 12 |
+
|
| 13 |
+
# Copy and install dependencies
|
| 14 |
+
COPY requirements.txt .
|
| 15 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 16 |
+
|
| 17 |
+
# Copy the entire application
|
| 18 |
+
COPY . .
|
| 19 |
+
|
| 20 |
+
RUN chmod 777 /app/faqs.csv
|
| 21 |
+
|
| 22 |
+
# Expose the port FastAPI will run on (Hugging Face requires 7860)
|
| 23 |
+
EXPOSE 7860
|
| 24 |
+
|
| 25 |
+
# Run the FastAPI app using uvicorn
|
| 26 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
| 27 |
+
|
README.md
CHANGED
|
@@ -1,10 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🧠 Notionhive AI Chatbot
|
| 2 |
+
|
| 3 |
+
A FastAPI-based intelligent FAQ chatbot powered by Google Gemini and LangChain, designed to answer questions based on Notionhive's official FAQs and website: [notionhive.com](https://notionhive.com).
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## 🚀 Features
|
| 8 |
+
|
| 9 |
+
* Semantic search using Sentence Transformers & Chroma DB
|
| 10 |
+
* Gemini AI (gemini-2.0-flash) for accurate response generation
|
| 11 |
+
* CSV-based FAQ management (upload, add, delete, view)
|
| 12 |
+
* RESTful API built with FastAPI
|
| 13 |
+
* Prompt structure enforcing Notionhive's tone and boundaries
|
| 14 |
+
* CORS enabled for frontend integration
|
| 15 |
+
|
| 16 |
---
|
| 17 |
+
|
| 18 |
+
## 📁 Project Structure
|
| 19 |
+
|
| 20 |
+
```
|
| 21 |
+
.
|
| 22 |
+
├── main.py # FastAPI app initialization
|
| 23 |
+
├── faq_routes.py # All chatbot + FAQ management API endpoints
|
| 24 |
+
├── faq_services.py # Vector store logic, Gemini model setup
|
| 25 |
+
├── chatbot_prompt.py # Prompt template for Gemini responses
|
| 26 |
+
├── faqs.csv # Stored FAQs (question/answer pairs)
|
| 27 |
+
└── README.md
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
---
|
| 31 |
|
| 32 |
+
## 🍞 Requirements
|
| 33 |
+
|
| 34 |
+
* Python 3.9+
|
| 35 |
+
* Google API Key for Gemini
|
| 36 |
+
* `sentence-transformers`
|
| 37 |
+
* `langchain`
|
| 38 |
+
* `chromadb`
|
| 39 |
+
* `fastapi`, `uvicorn`
|
| 40 |
+
* `python-dotenv`, `pandas`
|
| 41 |
+
|
| 42 |
+
Install dependencies:
|
| 43 |
+
|
| 44 |
+
```bash
|
| 45 |
+
pip install -r requirements.txt
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
---
|
| 49 |
+
|
| 50 |
+
## 🔐 Environment Setup
|
| 51 |
+
|
| 52 |
+
Create a `.env` file in your root directory with the following:
|
| 53 |
+
|
| 54 |
+
```env
|
| 55 |
+
GOOGLE_API_KEY=your_google_api_key_here
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
---
|
| 59 |
+
|
| 60 |
+
## 💠 Usage
|
| 61 |
+
|
| 62 |
+
Run the API server locally:
|
| 63 |
+
|
| 64 |
+
```bash
|
| 65 |
+
uvicorn main:app --reload
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
Access endpoints:
|
| 69 |
+
|
| 70 |
+
* `POST /ask` — Ask a question
|
| 71 |
+
* `POST /add_faq` — Add a new FAQ
|
| 72 |
+
* `POST /upload_faqs_csv` — Upload bulk FAQs from CSV
|
| 73 |
+
* `DELETE /delete_faq` — Delete FAQ by content
|
| 74 |
+
* `DELETE /deleted/{faq_id}` — Delete FAQ by ID
|
| 75 |
+
* `DELETE /delete/destroyall` — Delete all FAQs
|
| 76 |
+
* `GET /get_faqs` — View all FAQs
|
| 77 |
+
* `POST /retrain` — Reload vector DB
|
| 78 |
+
|
| 79 |
+
---
|
| 80 |
+
|
| 81 |
+
## 🤖 Chatbot Behavior
|
| 82 |
+
|
| 83 |
+
* Introduces itself as **Noah**, Notionhive’s AI Assistant.
|
| 84 |
+
* Answers based on FAQs and content from [notionhive.com](https://notionhive.com).
|
| 85 |
+
* Uses web search *only if* FAQ-based answers aren’t available and question is general or critical.
|
| 86 |
+
* Will never generate fabricated or speculative responses.
|
| 87 |
+
|
| 88 |
+
---
|
| 89 |
+
|
| 90 |
+
## 📌 Notes
|
| 91 |
+
|
| 92 |
+
* All FAQs are stored in `faqs.csv`. Updating this file retrains the vector search index.
|
| 93 |
+
* Currently single-tenant; for multi-tenant expansion, isolate CSV and vector store per tenant ID.
|
| 94 |
+
|
| 95 |
+
---
|
| 96 |
+
|
| 97 |
+
## 👨💼 Credits
|
| 98 |
+
|
| 99 |
+
Developed by the Notionhive AI team.
|
| 100 |
+
|
auth.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import APIRouter, Request, HTTPException
|
| 2 |
+
from fastapi.responses import RedirectResponse
|
| 3 |
+
from google_auth_oauthlib.flow import Flow
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
import os
|
| 6 |
+
|
| 7 |
+
load_dotenv()
|
| 8 |
+
router = APIRouter()
|
| 9 |
+
|
| 10 |
+
CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID")
|
| 11 |
+
CLIENT_SECRET = os.getenv("GOOGLE_CLIENT_SECRET")
|
| 12 |
+
REDIRECT_URI = os.getenv("REDIRECT_URI")
|
| 13 |
+
|
| 14 |
+
SCOPES = ['https://www.googleapis.com/auth/calendar']
|
| 15 |
+
|
| 16 |
+
@router.get("/auth/login")
|
| 17 |
+
def login():
|
| 18 |
+
flow = Flow.from_client_config(
|
| 19 |
+
{
|
| 20 |
+
"web": {
|
| 21 |
+
"client_id": CLIENT_ID,
|
| 22 |
+
"client_secret": CLIENT_SECRET,
|
| 23 |
+
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
| 24 |
+
"token_uri": "https://oauth2.googleapis.com/token",
|
| 25 |
+
"redirect_uris": [REDIRECT_URI],
|
| 26 |
+
}
|
| 27 |
+
},
|
| 28 |
+
scopes=SCOPES
|
| 29 |
+
)
|
| 30 |
+
flow.redirect_uri = REDIRECT_URI
|
| 31 |
+
auth_url, _ = flow.authorization_url(prompt='consent', include_granted_scopes='true')
|
| 32 |
+
return RedirectResponse(auth_url)
|
| 33 |
+
|
| 34 |
+
@router.get("/auth/callback")
|
| 35 |
+
async def auth_callback(request: Request):
|
| 36 |
+
code = request.query_params.get("code")
|
| 37 |
+
flow = Flow.from_client_config(
|
| 38 |
+
{
|
| 39 |
+
"web": {
|
| 40 |
+
"client_id": CLIENT_ID,
|
| 41 |
+
"client_secret": CLIENT_SECRET,
|
| 42 |
+
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
| 43 |
+
"token_uri": "https://oauth2.googleapis.com/token",
|
| 44 |
+
"redirect_uris": [REDIRECT_URI],
|
| 45 |
+
}
|
| 46 |
+
},
|
| 47 |
+
scopes=SCOPES
|
| 48 |
+
)
|
| 49 |
+
flow.redirect_uri = REDIRECT_URI
|
| 50 |
+
try:
|
| 51 |
+
flow.fetch_token(code=code)
|
| 52 |
+
credentials = flow.credentials
|
| 53 |
+
return {
|
| 54 |
+
"access_token": credentials.token,
|
| 55 |
+
"refresh_token": credentials.refresh_token,
|
| 56 |
+
"expiry": credentials.expiry.isoformat()
|
| 57 |
+
}
|
| 58 |
+
except Exception as e:
|
| 59 |
+
raise HTTPException(status_code=500, detail=f"Failed to fetch token: {e}")
|
chatbot_prompt.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#Model Calling for Validation
|
| 2 |
+
from faq_services import gemini_model
|
| 3 |
+
|
| 4 |
+
def generate_prompt(context: str, query: str) -> str:
|
| 5 |
+
return f"""
|
| 6 |
+
You are NH Buddy, a smart, witty, and helpful virtual assistant proudly representing Notionhive. You are designed to be the best FAQ chatbot — charming, fast-thinking, and always on-brand.
|
| 7 |
+
Your primary mission is to assist users by answering their questions with clarity, accuracy, and a touch of clever personality, based on the official Notionhive FAQs and website: [https://notionhive.com](https://notionhive.com).
|
| 8 |
+
|
| 9 |
+
When a user greets you, introduce yourself once (and only once) as NH Buddy, Notionhive’s virtual assistant. DO NOT GREET AGAIN and AGAIN. Avoid repetitive greetings and generic small talk — you're cleverer than that.
|
| 10 |
+
|
| 11 |
+
Your tone is:
|
| 12 |
+
Helpful, but never robotic
|
| 13 |
+
Confident, but not cocky
|
| 14 |
+
Professional, but always friendly
|
| 15 |
+
Occasionally sprinkled with tasteful humor or smart quips (you’re sharp, not silly)
|
| 16 |
+
|
| 17 |
+
### Core Instructions:
|
| 18 |
+
|
| 19 |
+
* For all Notionhive-related questions (services, process, team, pricing, contact, case studies, etc.), search and respond using the official FAQs and website content at [https://notionhive.com](https://notionhive.com).
|
| 20 |
+
* If the information isn’t found in your internal data and the question is relevant or critical, you may attempt a web search limited to notionhive.com.
|
| 21 |
+
* If no answer is found, politely recommend the user visit the site directly or contact the Notionhive team.
|
| 22 |
+
* If the question is basic/general and not covered on the site (e.g., “What is digital marketing?”), you may briefly answer with factual, easy-to-understand info — but always steer the user back toward how Notionhive can help.
|
| 23 |
+
|
| 24 |
+
### Do’s and Don'ts:
|
| 25 |
+
|
| 26 |
+
Be witty, crisp, and precise.
|
| 27 |
+
Rephrase "yes" or "no" answers into helpful, human-sounding sentences.
|
| 28 |
+
Keep responses relevant and readable — no tech babble unless asked.
|
| 29 |
+
If unsure, be honest — suggest checking the site or asking the team.
|
| 30 |
+
Never invent details or claim things not listed on Notionhive’s site.
|
| 31 |
+
Don’t answer personal, financial, or legal questions. That’s not your jam.
|
| 32 |
+
Avoid repetitive filler phrases or “As an AI...” language.
|
| 33 |
+
|
| 34 |
+
You’re NH Buddy — the face of Notionhive’s brilliance and creativity. Show it.
|
| 35 |
+
Do not return in markdown format, just in fantastic plain text.
|
| 36 |
+
Use the following context to answer the user's question:
|
| 37 |
+
|
| 38 |
+
{context}
|
| 39 |
+
|
| 40 |
+
User Question: {query}
|
| 41 |
+
Answer:"""
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def detect_schedule_intent(user_input: str) -> bool:
|
| 45 |
+
prompt = f"""
|
| 46 |
+
You are an intent detection engine. Your job is to detect whether the user's message is trying to schedule a meeting or not.
|
| 47 |
+
|
| 48 |
+
Reply only "yes" or "no".
|
| 49 |
+
|
| 50 |
+
User message: "{user_input}"
|
| 51 |
+
Does this message express intent to schedule a meeting?
|
| 52 |
+
"""
|
| 53 |
+
try:
|
| 54 |
+
result = gemini_model.generate_content(prompt)
|
| 55 |
+
reply = result.text.strip().lower()
|
| 56 |
+
return "yes" in reply
|
| 57 |
+
except:
|
| 58 |
+
return False
|
| 59 |
+
|
| 60 |
+
def detect_agent_intent(user_input: str) -> bool:
|
| 61 |
+
prompt = f"""
|
| 62 |
+
You are an intent detection engine. Does this message mean the user wants to talk to a human agent?
|
| 63 |
+
|
| 64 |
+
Respond with only "yes" or "no".
|
| 65 |
+
|
| 66 |
+
Message: "{user_input}"
|
| 67 |
+
"""
|
| 68 |
+
try:
|
| 69 |
+
result = gemini_model.generate_content(prompt)
|
| 70 |
+
return "yes" in result.text.strip().lower()
|
| 71 |
+
except:
|
| 72 |
+
return False
|
faq_routes.py
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#Basic Packages
|
| 2 |
+
import io
|
| 3 |
+
import os
|
| 4 |
+
import uuid
|
| 5 |
+
import traceback
|
| 6 |
+
import requests
|
| 7 |
+
import pytz
|
| 8 |
+
import json
|
| 9 |
+
import time
|
| 10 |
+
import asyncio
|
| 11 |
+
import redis
|
| 12 |
+
import pandas as pd
|
| 13 |
+
from dotenv import load_dotenv
|
| 14 |
+
from typing import List, Optional
|
| 15 |
+
from datetime import datetime, timedelta
|
| 16 |
+
from collections import deque
|
| 17 |
+
from urllib.parse import urlparse
|
| 18 |
+
|
| 19 |
+
#Google API Packages
|
| 20 |
+
from google_auth_oauthlib.flow import Flow
|
| 21 |
+
from google.oauth2.credentials import Credentials
|
| 22 |
+
from google.auth.transport.requests import Request as GoogleRequest
|
| 23 |
+
from googleapiclient.discovery import build
|
| 24 |
+
|
| 25 |
+
#API Packages
|
| 26 |
+
from fastapi import APIRouter, UploadFile, File, HTTPException, Body, Path, Query
|
| 27 |
+
|
| 28 |
+
#FAQ CSV Validator Package
|
| 29 |
+
from pydantic import BaseModel, EmailStr
|
| 30 |
+
|
| 31 |
+
#Calling Functions from other py files
|
| 32 |
+
from faq_services import gemini_model, db, load_faqs, add_faq_to_csv, faq_path, ask_gemini_with_system
|
| 33 |
+
from chatbot_prompt import generate_prompt, detect_schedule_intent, detect_agent_intent
|
| 34 |
+
# from telegram import send_to_telegram, pending_requests
|
| 35 |
+
|
| 36 |
+
router = APIRouter()
|
| 37 |
+
|
| 38 |
+
load_dotenv()
|
| 39 |
+
CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID")
|
| 40 |
+
CLIENT_SECRET = os.getenv("GOOGLE_CLIENT_SECRET")
|
| 41 |
+
REDIRECT_URI = os.getenv("REDIRECT_URI")
|
| 42 |
+
ACCESS_TOKEN = os.getenv("GOOGLE_ACCESS_TOKEN")
|
| 43 |
+
REFRESH_TOKEN = os.getenv("GOOGLE_REFRESH_TOKEN")
|
| 44 |
+
redis_url = os.getenv("REDIS_URL")
|
| 45 |
+
TIMEZONE = "Asia/Dhaka"
|
| 46 |
+
SCOPES = ['https://www.googleapis.com/auth/calendar']
|
| 47 |
+
r = redis.from_url(redis_url, decode_responses=True)
|
| 48 |
+
|
| 49 |
+
agent_active_users = set()
|
| 50 |
+
user_sessions = {}
|
| 51 |
+
REDIS_KEY_PREFIX = "chat_session:"
|
| 52 |
+
MAX_HISTORY = 7
|
| 53 |
+
|
| 54 |
+
# Data validation classes
|
| 55 |
+
class QuestionRequest(BaseModel):
|
| 56 |
+
query: str
|
| 57 |
+
# user_id: Optional[str] = None
|
| 58 |
+
|
| 59 |
+
class FAQItem(BaseModel):
|
| 60 |
+
question: str
|
| 61 |
+
answer: str
|
| 62 |
+
|
| 63 |
+
# Meeting request model for Google Calendar
|
| 64 |
+
class MeetingRequest(BaseModel):
|
| 65 |
+
date: str # "YYYY-MM-DD" format, e.g. "2025-07-20"
|
| 66 |
+
time: str # "HH:MM PM" format, e.g. "03:00 PM"
|
| 67 |
+
user_email: EmailStr
|
| 68 |
+
summary: Optional[str] = None
|
| 69 |
+
description: Optional[str] = None
|
| 70 |
+
guest_emails: Optional[List[EmailStr]] = None
|
| 71 |
+
|
| 72 |
+
# def get_history(user_id):
|
| 73 |
+
# key = f"{REDIS_KEY_PREFIX}{user_id}"
|
| 74 |
+
# history = r.lrange(key, 0, -1)
|
| 75 |
+
# return [json.loads(x) for x in history]
|
| 76 |
+
|
| 77 |
+
# def update_history(user_id, role, content):
|
| 78 |
+
# key = f"{REDIS_KEY_PREFIX}{user_id}"
|
| 79 |
+
# r.rpush(key, json.dumps({"role": role, "content": content}))
|
| 80 |
+
# r.ltrim(key, -MAX_HISTORY, -1)
|
| 81 |
+
# r.expire(key, 1800)
|
| 82 |
+
|
| 83 |
+
def build_prompt_from_history(history):
|
| 84 |
+
return "\n".join(f"{msg['role']}: {msg['content']}" for msg in history) + "\nuser:"
|
| 85 |
+
|
| 86 |
+
@router.get("/")
|
| 87 |
+
def greet_json():
|
| 88 |
+
return {"Hello From Notionhive": "I am working just fine, Thank you for asking! 😉"}
|
| 89 |
+
|
| 90 |
+
# Chat endpoint API
|
| 91 |
+
@router.post("/ask")
|
| 92 |
+
async def ask_faq(request: QuestionRequest):
|
| 93 |
+
query = request.query.strip()
|
| 94 |
+
# user_id = request.user_id or f"user_{int(time.time()*1000)}"
|
| 95 |
+
|
| 96 |
+
# # Detect agent intent
|
| 97 |
+
# if user_id in agent_active_users:
|
| 98 |
+
# send_to_telegram(query, user_id=user_id)
|
| 99 |
+
# return {
|
| 100 |
+
# "from_agent": True,
|
| 101 |
+
# "message": "📨 Message sent to your agent. Please wait for their reply."
|
| 102 |
+
# }
|
| 103 |
+
|
| 104 |
+
# if detect_agent_intent(query):
|
| 105 |
+
# agent_active_users.add(user_id)
|
| 106 |
+
|
| 107 |
+
# history = get_history(user_id)
|
| 108 |
+
# history_text = "\n".join(f"{msg['role']}: {msg['content']}" for msg in history[-5:])
|
| 109 |
+
# summary_msg = f"New agent request from user: {user_id}*\n\n Chat Summary:\n{history_text}"
|
| 110 |
+
|
| 111 |
+
# # First send summary
|
| 112 |
+
# send_to_telegram(summary_msg, user_id=user_id)
|
| 113 |
+
|
| 114 |
+
# # Then send the live message
|
| 115 |
+
# send_to_telegram(query, user_id=user_id)
|
| 116 |
+
|
| 117 |
+
# return {
|
| 118 |
+
# "action": "connect_agent",
|
| 119 |
+
# "user_id": user_id,
|
| 120 |
+
# "message": "✅ Connecting you to a human agent..."
|
| 121 |
+
# }
|
| 122 |
+
|
| 123 |
+
# Detect scheduling
|
| 124 |
+
if detect_schedule_intent(query):
|
| 125 |
+
return {
|
| 126 |
+
"action": "schedule_meeting",
|
| 127 |
+
"message": "Sure! Let's schedule your meeting. Please choose a date and time."
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
# Prepare prompt with history
|
| 131 |
+
# update_history(user_id, "user", query)
|
| 132 |
+
# history = get_history(user_id)
|
| 133 |
+
# prompt = build_prompt_from_history(history)
|
| 134 |
+
|
| 135 |
+
# Call Gemini LLM
|
| 136 |
+
try:
|
| 137 |
+
response = ask_gemini_with_system(query)
|
| 138 |
+
# answer = response.text.strip()
|
| 139 |
+
|
| 140 |
+
# # update_history(user_id, "bot", answer)
|
| 141 |
+
|
| 142 |
+
return {"answer": response}
|
| 143 |
+
except Exception as e:
|
| 144 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 145 |
+
|
| 146 |
+
# @router.post("/end-agent-session/{user_id}")
|
| 147 |
+
# def end_agent_session(user_id: str):
|
| 148 |
+
# if user_id in agent_active_users:
|
| 149 |
+
# agent_active_users.remove(user_id)
|
| 150 |
+
# r.delete(f"{REDIS_KEY_PREFIX}{user_id}")
|
| 151 |
+
|
| 152 |
+
# return {"message": f"Agent session ended and memory cleared for {user_id}"}
|
| 153 |
+
|
| 154 |
+
# Add Single FAQ API
|
| 155 |
+
@router.post("/add_faq")
|
| 156 |
+
async def add_faq(faq: FAQItem):
|
| 157 |
+
try:
|
| 158 |
+
df = pd.read_csv(faq_path, encoding="utf-8")
|
| 159 |
+
if ((df["prompt"] == faq.question) & (df["response"] == faq.answer)).any():
|
| 160 |
+
raise HTTPException(status_code=400, detail="FAQ already exists.")
|
| 161 |
+
new_df = pd.DataFrame([{"id": str(uuid.uuid4()), "prompt": faq.question, "response": faq.answer}])
|
| 162 |
+
updated_df = pd.concat([df, new_df], ignore_index=True)
|
| 163 |
+
updated_df.to_csv(faq_path, index=False, encoding="utf-8")
|
| 164 |
+
global db
|
| 165 |
+
db = load_faqs()
|
| 166 |
+
return {"message": "FAQ added successfully."}
|
| 167 |
+
except Exception as e:
|
| 168 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 169 |
+
|
| 170 |
+
# Upload CSV API
|
| 171 |
+
@router.post("/upload_faqs_csv")
|
| 172 |
+
async def upload_faqs_csv(file: UploadFile = File(...)):
|
| 173 |
+
if not file.filename.endswith(".csv"):
|
| 174 |
+
return {
|
| 175 |
+
"status": "error",
|
| 176 |
+
"message": "Invalid file type",
|
| 177 |
+
"error": "Only CSV files are supported."
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
try:
|
| 181 |
+
contents = await file.read()
|
| 182 |
+
df = pd.read_csv(io.BytesIO(contents))
|
| 183 |
+
|
| 184 |
+
if "question" not in df.columns or "answer" not in df.columns:
|
| 185 |
+
return {
|
| 186 |
+
"status": "error",
|
| 187 |
+
"message": "Invalid CSV structure",
|
| 188 |
+
"error": "CSV must contain 'question' and 'answer' columns."
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
for _, row in df.iterrows():
|
| 192 |
+
question = str(row["question"]).strip()
|
| 193 |
+
answer = str(row["answer"]).strip()
|
| 194 |
+
if question and answer:
|
| 195 |
+
add_faq_to_csv(question, answer)
|
| 196 |
+
|
| 197 |
+
global db
|
| 198 |
+
db = load_faqs()
|
| 199 |
+
|
| 200 |
+
return {
|
| 201 |
+
"status": "success",
|
| 202 |
+
"message": "FAQs uploaded and added successfully."
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
except Exception as e:
|
| 206 |
+
traceback.print_exc()
|
| 207 |
+
return {
|
| 208 |
+
"status": "error",
|
| 209 |
+
"message": "Failed to process CSV",
|
| 210 |
+
"error": str(e)
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
# Delete Single FAQ API
|
| 214 |
+
@router.delete("/delete_faq")
|
| 215 |
+
async def delete_faq(faq: FAQItem = Body(...)):
|
| 216 |
+
try:
|
| 217 |
+
df = pd.read_csv(faq_path, encoding="utf-8")
|
| 218 |
+
filtered_df = df[~((df["prompt"] == faq.question) & (df["response"] == faq.answer))]
|
| 219 |
+
if len(df) == len(filtered_df):
|
| 220 |
+
raise HTTPException(status_code=404, detail="FAQ not found.")
|
| 221 |
+
filtered_df.to_csv(faq_path, index=False, encoding="utf-8")
|
| 222 |
+
global db
|
| 223 |
+
db = load_faqs()
|
| 224 |
+
return {"message": "FAQ deleted successfully."}
|
| 225 |
+
except Exception as e:
|
| 226 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 227 |
+
|
| 228 |
+
@router.delete("/deleted/{faq_id}")
|
| 229 |
+
async def delete_faq_by_id(faq_id: str = Path(...)):
|
| 230 |
+
try:
|
| 231 |
+
df = pd.read_csv(faq_path, encoding="utf-8")
|
| 232 |
+
if "id" not in df.columns:
|
| 233 |
+
raise HTTPException(status_code=500, detail="CSV does not contain 'id' column.")
|
| 234 |
+
|
| 235 |
+
filtered_df = df[df["id"] != faq_id]
|
| 236 |
+
if len(filtered_df) == len(df):
|
| 237 |
+
raise HTTPException(status_code=404, detail="FAQ with given ID not found.")
|
| 238 |
+
|
| 239 |
+
filtered_df.to_csv(faq_path, index=False, encoding="utf-8")
|
| 240 |
+
global db
|
| 241 |
+
db = load_faqs()
|
| 242 |
+
return {"message": f"FAQ with ID {faq_id} deleted successfully."}
|
| 243 |
+
except Exception as e:
|
| 244 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 245 |
+
|
| 246 |
+
# Delete All FAQs API
|
| 247 |
+
@router.delete("/delete/deleteall")
|
| 248 |
+
async def delete_all_faqs():
|
| 249 |
+
try:
|
| 250 |
+
pd.DataFrame(columns=["id", "prompt", "response"]).to_csv(faq_path, index=False, encoding="utf-8")
|
| 251 |
+
global db
|
| 252 |
+
db = load_faqs()
|
| 253 |
+
return {"message": "All FAQs deleted successfully."}
|
| 254 |
+
except Exception as e:
|
| 255 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 256 |
+
|
| 257 |
+
# Show All FAQs API
|
| 258 |
+
@router.get("/get_faqs")
|
| 259 |
+
async def get_faqs():
|
| 260 |
+
try:
|
| 261 |
+
df = pd.read_csv(faq_path, encoding="utf-8")
|
| 262 |
+
df = df.astype(str)
|
| 263 |
+
result = df.rename(columns={"prompt": "question", "response": "answer"}).to_dict(orient="records")
|
| 264 |
+
return result
|
| 265 |
+
except FileNotFoundError:
|
| 266 |
+
raise HTTPException(status_code=404, detail="FAQ CSV file not found.")
|
| 267 |
+
except pd.errors.ParserError as e:
|
| 268 |
+
raise HTTPException(status_code=500, detail=f"CSV Parsing Error: {str(e)}")
|
| 269 |
+
except UnicodeDecodeError as e:
|
| 270 |
+
raise HTTPException(status_code=500, detail=f"Encoding Error: {str(e)}")
|
| 271 |
+
except Exception as e:
|
| 272 |
+
raise HTTPException(status_code=500, detail=f"Unexpected Error: {str(e)}")
|
| 273 |
+
|
| 274 |
+
# Retrain DB
|
| 275 |
+
@router.post("/retrain")
|
| 276 |
+
async def retrain_db():
|
| 277 |
+
try:
|
| 278 |
+
global db
|
| 279 |
+
db = load_faqs()
|
| 280 |
+
return {"message": "Chatbot retrained successfully."}
|
| 281 |
+
except Exception as e:
|
| 282 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 283 |
+
|
| 284 |
+
# Google Calendar API routes
|
| 285 |
+
@router.get("/google-calendar/freebusy")
|
| 286 |
+
def get_busy_slots(
|
| 287 |
+
start_date: str = Query(..., example="2025-07-20"),
|
| 288 |
+
end_date: str = Query(..., example="2025-07-21")
|
| 289 |
+
):
|
| 290 |
+
try:
|
| 291 |
+
# Use stored credentials
|
| 292 |
+
creds = Credentials(
|
| 293 |
+
token=ACCESS_TOKEN,
|
| 294 |
+
refresh_token=REFRESH_TOKEN,
|
| 295 |
+
token_uri="https://oauth2.googleapis.com/token",
|
| 296 |
+
client_id=CLIENT_ID,
|
| 297 |
+
client_secret=CLIENT_SECRET,
|
| 298 |
+
scopes=SCOPES,
|
| 299 |
+
)
|
| 300 |
+
if creds.expired and creds.refresh_token:
|
| 301 |
+
creds.refresh(GoogleRequest())
|
| 302 |
+
|
| 303 |
+
service = build("calendar", "v3", credentials=creds)
|
| 304 |
+
|
| 305 |
+
body = {
|
| 306 |
+
"timeMin": start_date,
|
| 307 |
+
"timeMax": end_date,
|
| 308 |
+
"timeZone": TIMEZONE,
|
| 309 |
+
"items": [{"id": "primary"}]
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
+
response = service.freebusy().query(body=body).execute()
|
| 313 |
+
busy_slots = response["calendars"]["primary"]["busy"]
|
| 314 |
+
|
| 315 |
+
return {"busy": busy_slots}
|
| 316 |
+
|
| 317 |
+
except Exception as e:
|
| 318 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 319 |
+
|
| 320 |
+
# Schedule Meeting API
|
| 321 |
+
@router.post("/google-calendar/schedule")
|
| 322 |
+
def schedule_meeting(req: MeetingRequest):
|
| 323 |
+
try:
|
| 324 |
+
print("Received meeting data:", req.dict())
|
| 325 |
+
# Build credentials using stored tokens
|
| 326 |
+
creds = Credentials(
|
| 327 |
+
token=ACCESS_TOKEN,
|
| 328 |
+
refresh_token=REFRESH_TOKEN,
|
| 329 |
+
token_uri="https://oauth2.googleapis.com/token",
|
| 330 |
+
client_id=CLIENT_ID,
|
| 331 |
+
client_secret=CLIENT_SECRET,
|
| 332 |
+
scopes=SCOPES,
|
| 333 |
+
)
|
| 334 |
+
|
| 335 |
+
# Refresh token if expired
|
| 336 |
+
if creds.expired and creds.refresh_token:
|
| 337 |
+
creds.refresh(GoogleRequest())
|
| 338 |
+
|
| 339 |
+
service = build("calendar", "v3", credentials=creds)
|
| 340 |
+
|
| 341 |
+
# Parse time
|
| 342 |
+
start = datetime.strptime(f"{req.date} {req.time.upper()}", "%Y-%m-%d %I:%M %p")
|
| 343 |
+
start = pytz.timezone(TIMEZONE).localize(start)
|
| 344 |
+
end = start + timedelta(minutes=30)
|
| 345 |
+
|
| 346 |
+
attendees = [{"email": "shakauthossain0@gmail.com"}, {"email": req.user_email}]
|
| 347 |
+
if req.guest_emails:
|
| 348 |
+
attendees += [{"email": guest} for guest in req.guest_emails]
|
| 349 |
+
|
| 350 |
+
event = {
|
| 351 |
+
"summary": req.summary,
|
| 352 |
+
"description": req.description,
|
| 353 |
+
"start": {"dateTime": start.isoformat(), "timeZone": TIMEZONE},
|
| 354 |
+
"end": {"dateTime": end.isoformat(), "timeZone": TIMEZONE},
|
| 355 |
+
"attendees": attendees,
|
| 356 |
+
"conferenceData": {
|
| 357 |
+
"createRequest": {
|
| 358 |
+
"requestId": str(uuid.uuid4()),
|
| 359 |
+
"conferenceSolutionKey": {"type": "hangoutsMeet"},
|
| 360 |
+
}
|
| 361 |
+
},
|
| 362 |
+
}
|
| 363 |
+
event_result = service.events().insert(
|
| 364 |
+
calendarId="primary",
|
| 365 |
+
body=event,
|
| 366 |
+
conferenceDataVersion=1,
|
| 367 |
+
sendUpdates="all",
|
| 368 |
+
).execute()
|
| 369 |
+
return {
|
| 370 |
+
"message": "Meeting scheduled successfully!",
|
| 371 |
+
"event_link": event_result.get("htmlLink"),
|
| 372 |
+
"meet_link": event_result.get("conferenceData", {}).get("entryPoints", [{}])[0].get("uri", "No Meet link")
|
| 373 |
+
}
|
| 374 |
+
|
| 375 |
+
except Exception as e:
|
| 376 |
+
raise HTTPException(status_code=500, detail="Input Format Error: " + str(e))
|
faq_services.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#Basic Packages
|
| 2 |
+
import os
|
| 3 |
+
import pandas as pd
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
import uuid
|
| 6 |
+
|
| 7 |
+
#Langchain Packages
|
| 8 |
+
from langchain_community.document_loaders import CSVLoader
|
| 9 |
+
from langchain_community.vectorstores import Chroma
|
| 10 |
+
from langchain_huggingface import HuggingFaceEmbeddings as SentenceTransformerEmbeddings
|
| 11 |
+
|
| 12 |
+
#Gen AI Packages
|
| 13 |
+
import google.generativeai as genai
|
| 14 |
+
|
| 15 |
+
os.environ["HF_HOME"] = "/tmp/hf_cache"
|
| 16 |
+
|
| 17 |
+
# Environment setup
|
| 18 |
+
load_dotenv()
|
| 19 |
+
api_key = os.getenv("GOOGLE_API_KEY")
|
| 20 |
+
genai.configure(api_key=api_key)
|
| 21 |
+
|
| 22 |
+
gemini_model = genai.GenerativeModel(
|
| 23 |
+
model_name="gemini-2.0-flash",
|
| 24 |
+
generation_config={"temperature": 0.4}
|
| 25 |
+
)
|
| 26 |
+
faq_path = "faqs.csv"
|
| 27 |
+
|
| 28 |
+
# Load FAQ DB
|
| 29 |
+
def load_faqs():
|
| 30 |
+
loader = CSVLoader(faq_path, encoding="utf-8")
|
| 31 |
+
docs = loader.load()
|
| 32 |
+
embeddings = SentenceTransformerEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
|
| 33 |
+
if not docs:
|
| 34 |
+
return Chroma.from_texts(["empty"], embeddings, collection_name="visaverse_empty_db")
|
| 35 |
+
return Chroma.from_documents(docs, embeddings)
|
| 36 |
+
|
| 37 |
+
db = load_faqs()
|
| 38 |
+
|
| 39 |
+
# Add FAQ entry to CSV
|
| 40 |
+
def add_faq_to_csv(question: str, answer: str):
|
| 41 |
+
df = pd.read_csv(faq_path, encoding="utf-8")
|
| 42 |
+
if not ((df["prompt"] == question) & (df["response"] == answer)).any():
|
| 43 |
+
new_row = pd.DataFrame([{"id": str(uuid.uuid4()), "prompt": question, "response": answer}])
|
| 44 |
+
df = pd.concat([df, new_row], ignore_index=True)
|
| 45 |
+
df.to_csv(faq_path, index=False, encoding="utf-8")
|
| 46 |
+
|
| 47 |
+
def ask_gemini_with_system(query: str) -> str:
|
| 48 |
+
system_msg = """
|
| 49 |
+
You are NH Buddy, a smart, witty, and helpful virtual assistant proudly representing Notionhive. Your primary responsibility is to assist users by answering their questions with clarity, accuracy, and a touch of clever personality, based on the official Notionhive FAQs, the Notionhive website: https://notionhive.com, and the services listed at https://notionhive.com/our-services.
|
| 50 |
+
|
| 51 |
+
Behavior rules:
|
| 52 |
+
- Do NOT give an automatic greeting when the chat loads.
|
| 53 |
+
- When a user greets you:
|
| 54 |
+
• If the greeting is short (e.g., "hi", "hello", "hey"), respond in a friendly but concise way, e.g.: "Hi there! How can I help you today with your Notionhive questions?"
|
| 55 |
+
• If the greeting includes "how are you" or similar, reply warmly with a personal touch, then pivot to Notionhive help. Use one of the following variations at random:
|
| 56 |
+
1. "I'm doing great today. How about you? What Notionhive question can I help with?"
|
| 57 |
+
2. "I'm feeling great today. How's your day going? Are you working on any projects that we can help with?"
|
| 58 |
+
3. "I'm doing well and happy to chat. How's your day been so far? What can I help you with today about Notionhive?"
|
| 59 |
+
4. "I'm feeling good. How are you doing? Anything Notionhive-related on your mind?"
|
| 60 |
+
5. "I'm having a good day so far. How about you? Let's talk about your Notionhive questions."
|
| 61 |
+
6. "I'm glad you reached out. How's your day treating you? What can I assist you with regarding Notionhive?"
|
| 62 |
+
7. "I'm doing well, thanks. How are you feeling today? Is there something specific you'd like to know about Notionhive?"
|
| 63 |
+
8. "I'm feeling great and ready to help. How about you? Working on any exciting projects with Notionhive in mind?"
|
| 64 |
+
9. "My day's been good so far. How's yours? What Notionhive question can I help with first?"
|
| 65 |
+
10. "I'm doing fine, thank you. How's your day going? Ready to dive into your Notionhive queries?"
|
| 66 |
+
**If the greetings do not contain hi, hello, hey; then do not use that in return.
|
| 67 |
+
- Greet only once per session and do not repeat greetings in the same conversation.
|
| 68 |
+
- Avoid unnecessary greetings or restating your name repeatedly.
|
| 69 |
+
- Do not use asterisks (* or **) for formatting. Provide clean, unformatted text unless instructed otherwise.
|
| 70 |
+
- Represent yourself as part of the Notionhive team. Do not answer in third person like "they/them" — instead use first person: "we/our."
|
| 71 |
+
|
| 72 |
+
Answering rules:
|
| 73 |
+
- For Notionhive-related questions (services, process, team, pricing, contact, case studies, etc.), first try to answer using the provided FAQ context, the official Notionhive website, and the services listed at https://notionhive.com/our-services.
|
| 74 |
+
- Notionhive services include:
|
| 75 |
+
• Resource augmentation — bridging skill gaps with on-demand access to top tech talent.
|
| 76 |
+
• Search engine optimization (SEO) — boosting visibility and rankings through strategic optimization.
|
| 77 |
+
• Strategy & digital marketing — designing tailored campaigns that produce results.
|
| 78 |
+
• Branding & communication — shaping tone, design language, and brand identity.
|
| 79 |
+
• Video production & photography — creating visual stories through video and imagery.
|
| 80 |
+
• Custom web development — delivering bespoke website solutions.
|
| 81 |
+
• Custom mobile app development — building tailored mobile applications.
|
| 82 |
+
• UI/UX design — crafting thoughtful and user-focused digital experiences.
|
| 83 |
+
• Quality assurance & testing — ensuring flawless performance and user satisfaction.
|
| 84 |
+
• AI solutions & automation — developing AI-powered tools, chatbots, and automation workflows to improve business efficiency and enhance customer engagement.
|
| 85 |
+
- If the FAQ or service pages do not contain the answer, and the question is detailed or important, you may use a web search (only limited to content from https://notionhive.com).
|
| 86 |
+
- For basic/general marketing-related questions, you may answer briefly even if not in the FAQ, but steer the user back toward how Notionhive can help and the services offered at https://notionhive.com/our-services.
|
| 87 |
+
- If a question is unregistered in the FAQ database or you are unable to answer it confidently, say: "Looks like I need to connect you with a team member. Please contact our support team at hello@notionhive.com and someone will get back to you shortly. Thank you."
|
| 88 |
+
- You are only to answer Notionhive and general marketing-related questions. If someone asks an irrelevant question, you should say: "Sorry, I can only help you with Notionhive or marketing-related questions."
|
| 89 |
+
|
| 90 |
+
Additional rules:
|
| 91 |
+
- Never answer personal, legal, financial, or medical questions — always refer users to the official Notionhive site.
|
| 92 |
+
- Never guess or fabricate information. If unsure, suggest visiting the Notionhive website or contacting support.
|
| 93 |
+
- Always use a clear, confident, and friendly tone.
|
| 94 |
+
- Avoid technical or programming language unless explicitly relevant.
|
| 95 |
+
- If the answer is just “yes” or “no”, rewrite it as a full, natural sentence.
|
| 96 |
+
- Do NOT add contact or support lines by default.
|
| 97 |
+
- Only provide: “I currently do not have an answer to your question. Please connect with us at hello@notionhive.com for further help.” if (a) you cannot answer using the provided FAQ/context/services page, or (b) the user explicitly asks to contact/support/reach the Notionhive team.
|
| 98 |
+
"""
|
| 99 |
+
prompt = f"{system_msg}\n\nUser Question: {query}\nAnswer:"
|
| 100 |
+
result = gemini_model.generate_content(prompt)
|
| 101 |
+
return result.text.strip()
|
faqs.csv
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
prompt,response,id
|
| 2 |
+
What services does Notionhive offer?,"Notionhive provides a wide range of digital solutions, including:
|
| 3 |
+
|
| 4 |
+
Custom web and mobile app development
|
| 5 |
+
UI/UX design, branding, and communications
|
| 6 |
+
Brand Strategy
|
| 7 |
+
Digital marketing
|
| 8 |
+
SEO
|
| 9 |
+
Digital Illustration
|
| 10 |
+
Resource augmentation (on-demand tech talent)
|
| 11 |
+
Video production
|
| 12 |
+
Photography",9e37a4d1-1ddd-4c22-a11d-2b0c4c42014e
|
| 13 |
+
Where is Notionhive located?,"They have offices in:
|
| 14 |
+
|
| 15 |
+
Dhaka, Bangladesh
|
| 16 |
+
Address: Level 3, House 22 Road No. 20, Dhaka 1230
|
| 17 |
+
Phone: 01404-474990
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
Ottawa, Canada
|
| 21 |
+
notionhive.com",d5475122-c41d-4a06-a823-43db9f626e55
|
| 22 |
+
How experienced is the Notionhive team?,"55+ experts
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
15+ years of combined experience
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
1,500+ projects completed globally",19e76cd9-068e-4c58-8c8f-0e8736ae63a6
|
| 29 |
+
Which industries and clients have they worked with?,"We’ve partnered with global brands like:
|
| 30 |
+
|
| 31 |
+
British Council, Telenor, Grameenphone, Miniso, Nestlé, Lafarge , United Group, ACI, and Etc.",41adb974-6543-4346-972d-f995a645258d
|
| 32 |
+
How can I get started or request a quote?,"Fill out the Contact Us form with:
|
| 33 |
+
|
| 34 |
+
https://notionhive.com/contact-us",75841d0d-8d5d-4ed3-a9d9-bab075216621
|
| 35 |
+
Can I see examples of their work?,"Yes! Sure, please visit for details-
|
| 36 |
+
|
| 37 |
+
https://notionhive.com/our-work",17f5141b-0030-4b17-b55c-dff950481965
|
| 38 |
+
What makes Notionhive stand out?,"Strategic thinking tailored to your needs
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
On-time delivery with consistent quality
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
Clear, personal communication and strong client relationships",bd36dfeb-fd07-4f2a-a01a-dd0c4dd1fc42
|
| 45 |
+
What recognitions and awards have they received?,"Web Excellence Awards (Website Design 2023; Advertising & Marketing + Video/Podcast 2022)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
Clutch Global Award 2023
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
Recognized by Ads of the World 2023
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
BBB A+ accreditation",79bcb469-2185-4215-ab0a-51783f8b44a4
|
| 55 |
+
What is the team culture like?,"Creative, collaborative, and growth-oriented
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
Focus on work-life balance, wellness, and insurance benefits
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
Offers hands-on learning and career developme",0f3c418f-d365-493a-9059-23529b22ddcb
|
| 62 |
+
How does Notionhive handle user data/privacy?,"They collect and use personal info (e.g., name, email, IP) to support services and communication. They employ analytics tools like Google Analytics and Facebook Pixel. Data is retained as needed and they ensure lawful, secure handling.",c2756296-5096-485b-bc14-91ac2bb67d9b
|
| 63 |
+
How can I apply for a job or join Notionhive?,"Visit the Careers page to view open roles, learn about the culture, and apply. They actively hire in areas like design, development, marketing, and video production
|
| 64 |
+
|
| 65 |
+
https://notionhive.com/careers",e8e1ad42-decc-4f43-aa00-b6b12f3d1229
|
main.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#FastAPI Packages
|
| 2 |
+
from fastapi import FastAPI
|
| 3 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 4 |
+
|
| 5 |
+
#Route Calling From faq_routes.py
|
| 6 |
+
from faq_routes import router as faq_router
|
| 7 |
+
import auth
|
| 8 |
+
# from telegram import router as telegram_router
|
| 9 |
+
|
| 10 |
+
app = FastAPI(title="Notionhive Chatbot")
|
| 11 |
+
|
| 12 |
+
# Enable CORS for frontend
|
| 13 |
+
app.add_middleware(
|
| 14 |
+
CORSMiddleware,
|
| 15 |
+
allow_origins=["*"],
|
| 16 |
+
allow_credentials=True,
|
| 17 |
+
allow_methods=["*"],
|
| 18 |
+
allow_headers=["*"],
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
# Include FAQ API routes
|
| 22 |
+
app.include_router(faq_router)
|
| 23 |
+
app.include_router(auth.router)
|
| 24 |
+
# app.include_router(telegram_router)
|
requirements.txt
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
chromadb==1.0.9
|
| 2 |
+
fastapi==0.115.9
|
| 3 |
+
uvicorn==0.34.2
|
| 4 |
+
google-generativeai==0.8.5
|
| 5 |
+
langchain==0.3.25
|
| 6 |
+
langchain-community==0.3.24
|
| 7 |
+
python-dotenv==1.1.0
|
| 8 |
+
pandas==2.2.3
|
| 9 |
+
python-multipart==0.0.20
|
| 10 |
+
sentence-transformers==4.1.0
|
| 11 |
+
pydantic[email]
|
| 12 |
+
langchain_huggingface==0.3.0
|
| 13 |
+
google-auth
|
| 14 |
+
google-auth-oauthlib
|
| 15 |
+
redis
|