notionhive-ai commited on
Commit
5b40291
·
verified ·
1 Parent(s): a431950

Upload 9 files

Browse files
Files changed (9) hide show
  1. Dockerfile +27 -0
  2. README.md +97 -7
  3. auth.py +59 -0
  4. chatbot_prompt.py +72 -0
  5. faq_routes.py +376 -0
  6. faq_services.py +101 -0
  7. faqs.csv +65 -0
  8. main.py +24 -0
  9. 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
- title: Chatbot
3
- emoji: 🐢
4
- colorFrom: blue
5
- colorTo: yellow
6
- sdk: docker
7
- pinned: false
 
 
 
 
 
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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