Spaces:
Sleeping
Sleeping
Synced repo using 'sync_with_huggingface' Github Action
Browse files- main.py +11 -69
- requirements.txt +7 -2
- src/bot/MongoChainGenerator.py +80 -0
- src/bot/MongoEmbeddingGenerator.py +35 -0
- src/bot/OtherFun.py +38 -0
- src/bot/main.py +66 -0
- src/config/appConfig.py +72 -0
- src/config/cloudinaryConfig.py +8 -0
- src/config/databaseConfig.py +14 -0
- src/controllers/auth/auth_controller.py +63 -0
- src/controllers/auth/otp_controller.py +26 -0
- src/controllers/auth/user_controller.py +49 -0
- src/controllers/chat/bot_controller.py +92 -0
- src/controllers/chat/chats_controller.py +191 -0
- src/controllers/chat/history_controller.py +85 -0
- src/helpers/json_convertor.py +15 -0
- src/helpers/pagination.py +19 -0
- src/helpers/response.py +27 -0
- src/helpers/send_request.py +12 -0
- src/helpers/upload_file_cloudinary.py +22 -0
- src/langs/en/messages.py +39 -0
- src/main.py +19 -0
- src/middleware/verifyToken.py +22 -0
- src/routes/auth_routes.py +10 -0
- src/routes/chat_routes.py +10 -0
- src/routes/main.py +8 -0
- src/services/message_services.py +30 -0
main.py
CHANGED
@@ -1,20 +1,15 @@
|
|
1 |
-
from
|
2 |
-
from
|
3 |
-
from fastapi.responses import JSONResponse
|
4 |
-
from starlette.middleware import Middleware
|
5 |
from starlette.middleware.gzip import GZipMiddleware
|
6 |
from starlette.middleware.cors import CORSMiddleware
|
7 |
-
from fastapi import FastAPI, File, UploadFile, Depends, Form, BackgroundTasks
|
8 |
|
9 |
-
from
|
10 |
-
|
11 |
-
from verifyToken import verify_token_and_role
|
12 |
# import os
|
13 |
-
origins = ["
|
14 |
|
15 |
# origins = os.getenv("ALLOWED_ORIGINS", "").split(",")
|
16 |
-
|
17 |
-
app = FastAPI(debug=True)
|
18 |
|
19 |
app.add_middleware(GZipMiddleware)
|
20 |
app.add_middleware(
|
@@ -22,65 +17,12 @@ app.add_middleware(
|
|
22 |
allow_origins=origins, # You can replace '*' with specific origins
|
23 |
allow_credentials=True,
|
24 |
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"], # or specific methods
|
25 |
-
allow_headers=["Authorization", "Content-Type", "Accept"] # or specific headers
|
26 |
)
|
27 |
|
28 |
-
|
29 |
-
|
30 |
-
class BodyModel(BaseModel):
|
31 |
-
query: str
|
32 |
-
chain_name: Optional[str] = None # Made chain_name optional
|
33 |
-
|
34 |
-
@app.get("/")
|
35 |
-
async def home():
|
36 |
-
return "chatbot api server is running..."
|
37 |
-
|
38 |
-
|
39 |
-
@app.post("/ask")
|
40 |
-
async def askQ(body: BodyModel, token: str = Depends(verify_token_and_role)):
|
41 |
-
try:
|
42 |
-
response = model.ask_question(body.query, token["username"] if body.chain_name is None else body.chain_name)
|
43 |
-
return JSONResponse(content={"success": True, "data": response})
|
44 |
-
except Exception as e: # Catch specific exceptions
|
45 |
-
return JSONResponse(content={"success": False, "error": str(e)})
|
46 |
-
|
47 |
-
|
48 |
-
@app.post("/create/embedding")
|
49 |
-
async def createEmbedding(collection_name: str = Form(...), files: List[UploadFile] = File(None), token: str = Depends(verify_token_and_role)):
|
50 |
-
try:
|
51 |
-
if not files:
|
52 |
-
return JSONResponse(content={"success": False, "error":"No files provided"})
|
53 |
-
|
54 |
-
responses = []
|
55 |
-
for file in files:
|
56 |
-
response = await process_file(model, collection_name, file)
|
57 |
-
responses.append(response)
|
58 |
-
|
59 |
-
return JSONResponse(content={"success": True,"responses": responses})
|
60 |
-
except Exception as e:
|
61 |
-
return JSONResponse(content={"success": False, "error": str(e)})
|
62 |
-
|
63 |
-
|
64 |
-
@app.post("/create/tmp/chain")
|
65 |
-
async def createTmpChain(background_tasks: BackgroundTasks, files: List[UploadFile] = File(...), token: str = Depends(verify_token_and_role)):
|
66 |
-
try:
|
67 |
-
if not files:
|
68 |
-
return JSONResponse(content={"success": False, "error":"No files provided"})
|
69 |
-
|
70 |
-
all_contents = b""
|
71 |
-
for file in files:
|
72 |
-
contents = await file.read()
|
73 |
-
all_contents += contents
|
74 |
|
75 |
-
|
76 |
-
if file_extension == "pdf":
|
77 |
-
chain_name = token["username"]
|
78 |
-
model.generate_tmp_embedding_and_chain(all_contents, chain_name)
|
79 |
-
background_tasks.add_task(delete_chain_after_delay(model, chain_name))
|
80 |
-
return JSONResponse(content={"success": True, "message": "Chain created. Will be deleted after 2 hours."})
|
81 |
-
elif file_extension == "txt":
|
82 |
-
all_contents.decode("utf-8")
|
83 |
-
return JSONResponse(content={"success": False, "error": "Unsupported file format"})
|
84 |
-
except Exception as e:
|
85 |
-
return JSONResponse(content={"success": False, "error": str(e)})
|
86 |
|
|
|
|
|
|
1 |
+
from fastapi import FastAPI
|
2 |
+
from src.config.appConfig import ENV_VAR
|
|
|
|
|
3 |
from starlette.middleware.gzip import GZipMiddleware
|
4 |
from starlette.middleware.cors import CORSMiddleware
|
|
|
5 |
|
6 |
+
from src.main import main
|
7 |
+
|
|
|
8 |
# import os
|
9 |
+
origins = ["http://localhost:3000"]
|
10 |
|
11 |
# origins = os.getenv("ALLOWED_ORIGINS", "").split(",")
|
12 |
+
app = FastAPI(debug=ENV_VAR.DEBUG)
|
|
|
13 |
|
14 |
app.add_middleware(GZipMiddleware)
|
15 |
app.add_middleware(
|
|
|
17 |
allow_origins=origins, # You can replace '*' with specific origins
|
18 |
allow_credentials=True,
|
19 |
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"], # or specific methods
|
20 |
+
allow_headers=["Authorization", "Content-Type", "Accept"], # or specific headers
|
21 |
)
|
22 |
|
23 |
+
app.include_router(main)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
|
25 |
+
import uvicorn
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
|
27 |
+
if __name__ == "__main__":
|
28 |
+
uvicorn.run("main:app", host="0.0.0.0", port=5000, reload=ENV_VAR.DEBUG)
|
requirements.txt
CHANGED
@@ -1,5 +1,7 @@
|
|
1 |
langchain==0.1.8
|
2 |
python-dotenv==1.0.0
|
|
|
|
|
3 |
|
4 |
# pdf
|
5 |
PyPDF2==3.0.1
|
@@ -15,13 +17,16 @@ faiss-cpu
|
|
15 |
# mongodb
|
16 |
pymongo==4.6.1
|
17 |
|
|
|
|
|
18 |
# API-END point
|
19 |
fastapi==0.109.2
|
20 |
fastapi-cors
|
21 |
uvicorn[standard]==0.17.*
|
22 |
python-multipart==0.0.9
|
23 |
PyJWT==2.8.0
|
|
|
24 |
|
25 |
|
26 |
-
|
27 |
-
|
|
|
1 |
langchain==0.1.8
|
2 |
python-dotenv==1.0.0
|
3 |
+
huggingface_hub
|
4 |
+
typing
|
5 |
|
6 |
# pdf
|
7 |
PyPDF2==3.0.1
|
|
|
17 |
# mongodb
|
18 |
pymongo==4.6.1
|
19 |
|
20 |
+
cloudinary
|
21 |
+
|
22 |
# API-END point
|
23 |
fastapi==0.109.2
|
24 |
fastapi-cors
|
25 |
uvicorn[standard]==0.17.*
|
26 |
python-multipart==0.0.9
|
27 |
PyJWT==2.8.0
|
28 |
+
pydantic
|
29 |
|
30 |
|
31 |
+
|
32 |
+
# requests-toolbelt
|
src/bot/MongoChainGenerator.py
ADDED
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from src.config.appConfig import ENV_VAR, CONST_VAR, LOG
|
2 |
+
from langchain.chains import RetrievalQA
|
3 |
+
from langchain.prompts import PromptTemplate
|
4 |
+
from langchain.llms.huggingface_endpoint import HuggingFaceEndpoint
|
5 |
+
from langchain.vectorstores.mongodb_atlas import MongoDBAtlasVectorSearch
|
6 |
+
from langchain.vectorstores.faiss import FAISS
|
7 |
+
from huggingface_hub import login
|
8 |
+
|
9 |
+
login(
|
10 |
+
token=ENV_VAR.HUGGINGFACEHUB_API_TOKEN,
|
11 |
+
write_permission=True,
|
12 |
+
add_to_git_credential=True,
|
13 |
+
)
|
14 |
+
|
15 |
+
|
16 |
+
class MongoChainGenerator:
|
17 |
+
LLM = None
|
18 |
+
|
19 |
+
def __init__(
|
20 |
+
self,
|
21 |
+
embedding_model,
|
22 |
+
template_context,
|
23 |
+
db_collection_name=None,
|
24 |
+
tmp_vector_embedding=None,
|
25 |
+
):
|
26 |
+
if db_collection_name:
|
27 |
+
self._load_vectors(embedding_model, db_collection_name)
|
28 |
+
else:
|
29 |
+
self._create_tmp_retriever(tmp_vector_embedding)
|
30 |
+
|
31 |
+
self._initialize_prompt(template_context)
|
32 |
+
|
33 |
+
if MongoChainGenerator.LLM is None:
|
34 |
+
self._initialize_llm()
|
35 |
+
|
36 |
+
def _create_tmp_retriever(self, tmp_vector_embedding: FAISS):
|
37 |
+
self.qa_retriever = tmp_vector_embedding.as_retriever(
|
38 |
+
search_type="similarity", search_kwargs={"k": 7}
|
39 |
+
)
|
40 |
+
LOG.debug("Temporary retriever created")
|
41 |
+
|
42 |
+
def _load_vectors(self, embedding_model, db_collection_name):
|
43 |
+
self.qa_retriever = MongoDBAtlasVectorSearch.from_connection_string(
|
44 |
+
connection_string=ENV_VAR.MONGO_DB_URL,
|
45 |
+
namespace=ENV_VAR.MONGO_DB_NAME + "." + db_collection_name,
|
46 |
+
embedding=embedding_model,
|
47 |
+
).as_retriever(search_type="similarity", search_kwargs={"k": 7})
|
48 |
+
LOG.debug("Retriever loaded from MongoDB Atlas")
|
49 |
+
|
50 |
+
def _initialize_prompt(self, template_context):
|
51 |
+
template = (
|
52 |
+
template_context
|
53 |
+
+ """
|
54 |
+
{context}
|
55 |
+
|
56 |
+
Question: {question} all related details.
|
57 |
+
Answer:"""
|
58 |
+
)
|
59 |
+
self.prompt = PromptTemplate(
|
60 |
+
template=template, input_variables=["context", "question"]
|
61 |
+
)
|
62 |
+
LOG.debug("Prompt template initialized")
|
63 |
+
|
64 |
+
def _initialize_llm(self):
|
65 |
+
MongoChainGenerator.LLM = HuggingFaceEndpoint(
|
66 |
+
repo_id=CONST_VAR.TEXT_GENERATOR_MODEL_REPO_ID,
|
67 |
+
temperature=0.8,
|
68 |
+
max_new_tokens=4096,
|
69 |
+
)
|
70 |
+
# MongoChainGenerator.LLM = HuggingFaceHub(repo_id=CONST_VAR.TEXT_GENERATOR_MODEL_REPO_ID, model_kwargs={"temperature": 0.85, "return_full_text": False, "max_length": 4096, "max_new_tokens": 4096})
|
71 |
+
LOG.info("LLM initialized")
|
72 |
+
|
73 |
+
def generate_retrieval_qa_chain(self):
|
74 |
+
chain = RetrievalQA.from_chain_type(
|
75 |
+
llm=MongoChainGenerator.LLM,
|
76 |
+
retriever=self.qa_retriever,
|
77 |
+
chain_type_kwargs={"prompt": self.prompt},
|
78 |
+
)
|
79 |
+
LOG.debug("Retrieval QA chain generated")
|
80 |
+
return chain
|
src/bot/MongoEmbeddingGenerator.py
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from io import BytesIO
|
2 |
+
import PyPDF2
|
3 |
+
from src.config.appConfig import *
|
4 |
+
from src.config.databaseConfig import DATABASE
|
5 |
+
|
6 |
+
from langchain.vectorstores.faiss import FAISS
|
7 |
+
from langchain.vectorstores.mongodb_atlas import MongoDBAtlasVectorSearch
|
8 |
+
from langchain.embeddings.huggingface_hub import HuggingFaceHubEmbeddings
|
9 |
+
|
10 |
+
class MongoEmbeddingGenerator:
|
11 |
+
|
12 |
+
def __init__(self, repo_id):
|
13 |
+
self.embedding_model = HuggingFaceHubEmbeddings(repo_id=repo_id, huggingfacehub_api_token=ENV_VAR.HUGGINGFACEHUB_API_TOKEN)
|
14 |
+
LOG.info("Embedding model initialised")
|
15 |
+
|
16 |
+
def _extract_text_from_pdf(self, pdf_bytes):
|
17 |
+
pdf_file = BytesIO(pdf_bytes)
|
18 |
+
pdf_reader = PyPDF2.PdfReader(pdf_file)
|
19 |
+
return [pdf_reader.pages[page_num].extract_text() for page_num in range(len(pdf_reader.pages))]
|
20 |
+
|
21 |
+
def generate_tmp_embeddings(self, pdf_bytes):
|
22 |
+
texts = self._extract_text_from_pdf(pdf_bytes)
|
23 |
+
return FAISS.from_texts(texts=texts, embedding=self.embedding_model)
|
24 |
+
|
25 |
+
def generate_embeddings(self, pdf_bytes, file_name: str, collection_name: str):
|
26 |
+
client = DATABASE.client
|
27 |
+
if client[ENV_VAR.MONGO_DB_NAME_CACHE][collection_name].find_one({"src_file_name": file_name}):
|
28 |
+
LOG.debug(f"Vectors already exist in MongoDB for file {file_name}")
|
29 |
+
return f"Vectors already exist in MongoDB for file {file_name}"
|
30 |
+
else:
|
31 |
+
texts = self._extract_text_from_pdf(pdf_bytes)
|
32 |
+
client[ENV_VAR.MONGO_DB_NAME_CACHE][collection_name].insert_one({"src_file_name": file_name})
|
33 |
+
MongoDBAtlasVectorSearch.from_texts(texts=texts, embedding=self.embedding_model, collection=client[ENV_VAR.MONGO_DB_NAME][collection_name])
|
34 |
+
LOG.debug(f"Vectors stored in MongoDB for file {file_name}")
|
35 |
+
return f"Vectors stored in MongoDB for file {file_name}"
|
src/bot/OtherFun.py
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import UploadFile
|
2 |
+
import asyncio
|
3 |
+
from src.bot.main import Main
|
4 |
+
from src.config.appConfig import LOG
|
5 |
+
|
6 |
+
|
7 |
+
def delete_chain_after_delay(model: Main, chain_name: str):
|
8 |
+
async def delete_chain():
|
9 |
+
try:
|
10 |
+
await asyncio.sleep(7200) # Sleep for 2 hours
|
11 |
+
if chain_name in model.qa_chains:
|
12 |
+
del model.qa_chains[chain_name]
|
13 |
+
# Log deletion
|
14 |
+
LOG.info(f"Chain '{chain_name}' deleted after 2 hours")
|
15 |
+
except Exception as e:
|
16 |
+
LOG.error(f"An error occurred while deleting chain '{chain_name}': {e}")
|
17 |
+
|
18 |
+
return delete_chain
|
19 |
+
|
20 |
+
|
21 |
+
async def process_file(model: Main, collection_name: str, file: UploadFile):
|
22 |
+
try:
|
23 |
+
contents = await file.read()
|
24 |
+
|
25 |
+
file_extension = file.filename.split(".")[-1]
|
26 |
+
|
27 |
+
if file_extension == "pdf":
|
28 |
+
response = model.generate_embedding(
|
29 |
+
contents, file.filename, collection_name)
|
30 |
+
elif file_extension == "txt":
|
31 |
+
response = contents.decode("utf-8")
|
32 |
+
else:
|
33 |
+
raise ValueError(f"Unsupported file format for {file.filename}")
|
34 |
+
|
35 |
+
return response
|
36 |
+
except Exception as e:
|
37 |
+
LOG.error(f"An error occurred while processing file '{file.filename}': {e}")
|
38 |
+
return f"Error processing file '{file.filename}': {e}"
|
src/bot/main.py
ADDED
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from src.bot.MongoChainGenerator import *
|
2 |
+
from src.bot.MongoEmbeddingGenerator import *
|
3 |
+
|
4 |
+
from src.config.appConfig import LOG
|
5 |
+
from src.config.databaseConfig import DATABASE
|
6 |
+
|
7 |
+
|
8 |
+
class Main:
|
9 |
+
qa_chains = {}
|
10 |
+
embedding_generator = None
|
11 |
+
|
12 |
+
def __init__(self) -> None:
|
13 |
+
DATABASE()
|
14 |
+
self._initialize_embedding_generator()
|
15 |
+
self._load_existing_qa_chains()
|
16 |
+
|
17 |
+
def _initialize_embedding_generator(self):
|
18 |
+
if Main.embedding_generator is None:
|
19 |
+
Main.embedding_generator = MongoEmbeddingGenerator(repo_id=CONST_VAR.EMBEDDING_MODEL_REPO_ID)
|
20 |
+
LOG.debug("Embedding generator initialized")
|
21 |
+
|
22 |
+
def _load_existing_qa_chains(self):
|
23 |
+
chats = DATABASE.client["chatData"]["chats"].find()
|
24 |
+
for chat in chats:
|
25 |
+
if chat["collectionName"] not in Main.qa_chains:
|
26 |
+
self.create_exist_chains(chat)
|
27 |
+
|
28 |
+
def create_exist_chains(self, chat):
|
29 |
+
if chat["collectionName"] not in Main.qa_chains:
|
30 |
+
qa_generator = MongoChainGenerator(
|
31 |
+
embedding_model=Main.embedding_generator.embedding_model,
|
32 |
+
db_collection_name=chat["collectionName"],
|
33 |
+
template_context=chat["templateContext"]
|
34 |
+
)
|
35 |
+
Main.qa_chains[chat["collectionName"]] = qa_generator.generate_retrieval_qa_chain()
|
36 |
+
LOG.debug("Chain created for collection " + chat["collectionName"])
|
37 |
+
else:
|
38 |
+
LOG.debug("Chain already exists for collection " + chat["collectionName"])
|
39 |
+
|
40 |
+
def generate_embedding(self, content: str, file_name: str, collection_name: str):
|
41 |
+
return Main.embedding_generator.generate_embeddings(content, file_name, collection_name)
|
42 |
+
|
43 |
+
def generate_tmp_embedding_and_chain(self, contents: str, tmp_collection_name):
|
44 |
+
qa_generator = MongoChainGenerator(
|
45 |
+
embedding_model=Main.embedding_generator.embedding_model,
|
46 |
+
template_context=CONST_VAR.TEMPLATE_CONTEXT,
|
47 |
+
tmp_vector_embedding=Main.embedding_generator.generate_tmp_embeddings(pdf_bytes=contents)
|
48 |
+
)
|
49 |
+
Main.qa_chains[tmp_collection_name] = qa_generator.generate_retrieval_qa_chain()
|
50 |
+
LOG.debug(tmp_collection_name + ' chain created')
|
51 |
+
|
52 |
+
def ask_question(self, question: str, collection_name):
|
53 |
+
if collection_name in Main.qa_chains:
|
54 |
+
try:
|
55 |
+
LOG.debug(collection_name + " answering")
|
56 |
+
response = Main.qa_chains[collection_name]({"query": question, "early_stopping": True, "min_length": 2000, "max_tokens": 5000})
|
57 |
+
return response["result"]
|
58 |
+
except Exception as e:
|
59 |
+
LOG.error("An error occurred while answering question: {}".format(str(e)))
|
60 |
+
return "Retry to ask question! An error occurred: {}".format(str(e))
|
61 |
+
else:
|
62 |
+
LOG.warning("Chain for collection '{}' not found.".format(collection_name))
|
63 |
+
return "Chain for collection '{}' not found.".format(collection_name)
|
64 |
+
|
65 |
+
def check_collection_name(self, collection_name):
|
66 |
+
return collection_name in self.qa_chains
|
src/config/appConfig.py
ADDED
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import logging
|
3 |
+
from dotenv import load_dotenv
|
4 |
+
|
5 |
+
load_dotenv()
|
6 |
+
|
7 |
+
class CLOUDINARY():
|
8 |
+
CLOUDINARY_CLOUD_NAME = os.environ.get("CLOUDINARY_CLOUD_NAME")
|
9 |
+
CLOUDINARY_API_KEY = os.environ.get("CLOUDINARY_API_KEY")
|
10 |
+
CLOUDINARY_API_SECRET = os.environ.get("CLOUDINARY_API_SECRET")
|
11 |
+
|
12 |
+
class ENV_VAR():
|
13 |
+
MONGO_DB_URL = os.environ.get("MONGO_DB_URL")
|
14 |
+
MONGO_DB_NAME = os.environ.get("MONGO_DB_NAME")
|
15 |
+
MONGO_DB_NAME_CHATS = os.environ.get("MONGO_DB_NAME_CHATS")
|
16 |
+
HUGGINGFACEHUB_API_TOKEN = os.environ.get("HUGGINGFACEHUB_API_TOKEN")
|
17 |
+
MONGO_DB_NAME_CACHE = os.environ.get("MONGO_DB_NAME_CACHE")
|
18 |
+
JWT_SECRET = os.environ.get("JWT_SECRET")
|
19 |
+
AUTH_API_END = os.environ.get("AUTH_API_END")
|
20 |
+
DEBUG = (
|
21 |
+
os.environ.get("DEBUG").lower() == "true" if os.environ.get("DEBUG") else False
|
22 |
+
)
|
23 |
+
|
24 |
+
class CONST_VAR():
|
25 |
+
TEXT_GENERATOR_MODEL_REPO_ID = "mistralai/Mixtral-8x7B-Instruct-v0.1"
|
26 |
+
EMBEDDING_MODEL_REPO_ID = "sentence-transformers/all-MiniLM-L6-v2"
|
27 |
+
TEMPLATE_CONTEXT = """
|
28 |
+
Use the following pieces of context to answer the question at the end.
|
29 |
+
You should prefer information which are more related to asked question.
|
30 |
+
Make sure to rely on information from text only and not on questions to provide accurate responses.
|
31 |
+
When you find particular answer in given text, display its context useful, make sure to cite it in the your answer.
|
32 |
+
If you don't know the answer, just say that you don't know, don't try to make up an answer.
|
33 |
+
You can only use the given to you to answer the question.
|
34 |
+
Generate concise answers and relevant data related to the asked question.
|
35 |
+
You must represent the answer in proper format such as make points highlight some major information.
|
36 |
+
don't attach your created quetions. if you don't get answer from the given text just say i don't know and terminate answering.
|
37 |
+
if you get answer from the text than write all about the asked quetion and relevant data related to it.
|
38 |
+
don't use your own knowledge just use the provided text to answer the question.
|
39 |
+
"""
|
40 |
+
|
41 |
+
|
42 |
+
class LOG:
|
43 |
+
def __init__(self) -> None:
|
44 |
+
pass
|
45 |
+
|
46 |
+
@staticmethod
|
47 |
+
def configure_logging(level=logging.INFO):
|
48 |
+
logging.basicConfig(level=level) # Set the logging level
|
49 |
+
|
50 |
+
@staticmethod
|
51 |
+
def debug(msg):
|
52 |
+
logging.debug(msg)
|
53 |
+
|
54 |
+
@staticmethod
|
55 |
+
def info(msg):
|
56 |
+
logging.info(msg)
|
57 |
+
|
58 |
+
@staticmethod
|
59 |
+
def warning(msg):
|
60 |
+
logging.warning(msg)
|
61 |
+
|
62 |
+
@staticmethod
|
63 |
+
def error(msg):
|
64 |
+
logging.error(msg)
|
65 |
+
|
66 |
+
@staticmethod
|
67 |
+
def critical(msg):
|
68 |
+
logging.critical(msg)
|
69 |
+
|
70 |
+
|
71 |
+
if ENV_VAR.DEBUG:
|
72 |
+
LOG.configure_logging(logging.DEBUG)
|
src/config/cloudinaryConfig.py
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cloudinary
|
2 |
+
from src.config.appConfig import CLOUDINARY
|
3 |
+
|
4 |
+
cloudinary.config(
|
5 |
+
cloud_name=CLOUDINARY.CLOUDINARY_CLOUD_NAME,
|
6 |
+
api_key=CLOUDINARY.CLOUDINARY_API_KEY,
|
7 |
+
api_secret=CLOUDINARY.CLOUDINARY_API_SECRET,
|
8 |
+
)
|
src/config/databaseConfig.py
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from src.config.appConfig import *
|
2 |
+
from pymongo import MongoClient
|
3 |
+
|
4 |
+
|
5 |
+
class DATABASE:
|
6 |
+
client = None
|
7 |
+
collections=None
|
8 |
+
def __init__(self):
|
9 |
+
self._initialize_mongodb_client()
|
10 |
+
|
11 |
+
def _initialize_mongodb_client(self):
|
12 |
+
if DATABASE.client is None:
|
13 |
+
DATABASE.client = MongoClient(ENV_VAR.MONGO_DB_URL)
|
14 |
+
print("mongodb connected")
|
src/controllers/auth/auth_controller.py
ADDED
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, Response, Request, UploadFile
|
2 |
+
from src.config.appConfig import ENV_VAR
|
3 |
+
from src.helpers.response import ResponseHandler
|
4 |
+
from src.helpers.send_request import sendRequest
|
5 |
+
from typing import Optional
|
6 |
+
|
7 |
+
router = APIRouter()
|
8 |
+
AUTH_API_END = ENV_VAR.AUTH_API_END
|
9 |
+
|
10 |
+
|
11 |
+
@router.post("/register/")
|
12 |
+
async def register(req: Request, picPath: UploadFile = None):
|
13 |
+
try:
|
14 |
+
form_data = await req.form()
|
15 |
+
|
16 |
+
payload = {key: form_data[key] for key in form_data if key != "picPath"}
|
17 |
+
|
18 |
+
files = None
|
19 |
+
|
20 |
+
if picPath:
|
21 |
+
files = {"picPath": (picPath.filename, picPath.file, picPath.content_type)}
|
22 |
+
|
23 |
+
# else f"{AUTH_API_END}/api/v1/auth/register/", json=payload}
|
24 |
+
response = sendRequest(
|
25 |
+
f"{AUTH_API_END}/api/v1/auth/register/", "post", payload, files
|
26 |
+
)
|
27 |
+
|
28 |
+
return ResponseHandler.success_mediator(response)
|
29 |
+
except Exception as e:
|
30 |
+
return ResponseHandler.error(9999, e)
|
31 |
+
|
32 |
+
|
33 |
+
@router.get("/get/usernames")
|
34 |
+
async def get_user_names():
|
35 |
+
try:
|
36 |
+
response = sendRequest(f"{AUTH_API_END}/api/v1/auth/get/usernames")
|
37 |
+
return ResponseHandler.success_mediator(response)
|
38 |
+
except Exception as e:
|
39 |
+
return ResponseHandler.error(9999, e)
|
40 |
+
|
41 |
+
|
42 |
+
@router.post("/login/")
|
43 |
+
async def login_control(req: dict):
|
44 |
+
try:
|
45 |
+
response = sendRequest(f"{AUTH_API_END}/api/v1/auth/login/", "post", req)
|
46 |
+
return ResponseHandler.success_mediator(response)
|
47 |
+
except Exception as e:
|
48 |
+
return ResponseHandler.error(9999, e)
|
49 |
+
|
50 |
+
|
51 |
+
@router.post("/change/password/")
|
52 |
+
async def change_pass_control(req: dict, authorization: str = None):
|
53 |
+
try:
|
54 |
+
headers = {"Content-Type": "application/json"}
|
55 |
+
if authorization:
|
56 |
+
headers["Authorization"] = authorization
|
57 |
+
|
58 |
+
response = sendRequest(
|
59 |
+
f"{AUTH_API_END}/api/v1/auth/change/password", "post", req, None, headers
|
60 |
+
)
|
61 |
+
return ResponseHandler.success_mediator(response)
|
62 |
+
except Exception as e:
|
63 |
+
return ResponseHandler.error(9999, e)
|
src/controllers/auth/otp_controller.py
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, Request
|
2 |
+
from src.config.appConfig import ENV_VAR
|
3 |
+
from src.helpers.response import ResponseHandler
|
4 |
+
from src.helpers.send_request import sendRequest
|
5 |
+
|
6 |
+
router = APIRouter()
|
7 |
+
AUTH_API_END = ENV_VAR.AUTH_API_END
|
8 |
+
|
9 |
+
|
10 |
+
@router.post("/send-otp")
|
11 |
+
async def send_otp_controller(req: Request):
|
12 |
+
try:
|
13 |
+
# Make the request to the authentication API endpoint
|
14 |
+
response = sendRequest(
|
15 |
+
f"{AUTH_API_END}/api/v1/mail/send-otp",
|
16 |
+
"post",
|
17 |
+
req,
|
18 |
+
None,
|
19 |
+
{"Content-Type": "application/json"},
|
20 |
+
)
|
21 |
+
# Handle the response using the ResponseHandler
|
22 |
+
return ResponseHandler.success_mediator(response)
|
23 |
+
except Exception as e:
|
24 |
+
# Log and handle any exceptions
|
25 |
+
print(e)
|
26 |
+
return ResponseHandler.error(9999, e)
|
src/controllers/auth/user_controller.py
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, Request, UploadFile, Header
|
2 |
+
from src.config.appConfig import ENV_VAR
|
3 |
+
from src.helpers.response import ResponseHandler
|
4 |
+
from src.helpers.send_request import sendRequest
|
5 |
+
from typing import Optional
|
6 |
+
|
7 |
+
router = APIRouter()
|
8 |
+
AUTH_API_END = ENV_VAR.AUTH_API_END
|
9 |
+
|
10 |
+
|
11 |
+
@router.get("/userid/{uid}")
|
12 |
+
async def get_users(uid: str):
|
13 |
+
try:
|
14 |
+
response = get_user_date(uid)
|
15 |
+
return ResponseHandler.success_mediator(response)
|
16 |
+
except Exception as error:
|
17 |
+
return ResponseHandler.error(error)
|
18 |
+
|
19 |
+
def get_user_date(uid):
|
20 |
+
return sendRequest(
|
21 |
+
url=f"{AUTH_API_END}/api/v1/user/get/userid/{uid}",
|
22 |
+
headers={"Content-Type": "application/json"},
|
23 |
+
)
|
24 |
+
|
25 |
+
@router.put("/update/")
|
26 |
+
async def update_user_data(
|
27 |
+
req: Request, picPath: UploadFile = None, authorization: str = Header(None)
|
28 |
+
):
|
29 |
+
try:
|
30 |
+
form_data = await req.form()
|
31 |
+
|
32 |
+
payload = {key: form_data[key] for key in form_data if key != "picPath"}
|
33 |
+
|
34 |
+
files = None
|
35 |
+
|
36 |
+
if picPath:
|
37 |
+
files = {"picPath": (picPath.filename, picPath.file, picPath.content_type)}
|
38 |
+
|
39 |
+
headers = {"Authorization": authorization}
|
40 |
+
response = sendRequest(
|
41 |
+
f"{AUTH_API_END}/api/v1/user/update/",
|
42 |
+
"put",
|
43 |
+
payload,
|
44 |
+
files,
|
45 |
+
headers,
|
46 |
+
)
|
47 |
+
return ResponseHandler.success_mediator(response)
|
48 |
+
except Exception as e:
|
49 |
+
return ResponseHandler.error(9999, e)
|
src/controllers/chat/bot_controller.py
ADDED
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import BackgroundTasks, APIRouter, Request, UploadFile, Depends, File
|
2 |
+
from typing import List
|
3 |
+
|
4 |
+
from src.helpers.response import ResponseHandler
|
5 |
+
from src.helpers.json_convertor import convert_to_json
|
6 |
+
|
7 |
+
from src.middleware.verifyToken import verify_token
|
8 |
+
from src.services.message_services import save_question_and_answer_to_chat_history
|
9 |
+
|
10 |
+
from src.bot.main import Main
|
11 |
+
from src.bot.OtherFun import delete_chain_after_delay, process_file
|
12 |
+
|
13 |
+
router = APIRouter()
|
14 |
+
|
15 |
+
model = Main()
|
16 |
+
|
17 |
+
|
18 |
+
@router.post("/ask-question")
|
19 |
+
async def askQ(req: Request, token: str = Depends(verify_token)):
|
20 |
+
try:
|
21 |
+
form_data = await req.json()
|
22 |
+
answer = model.ask_question(
|
23 |
+
form_data["query"],
|
24 |
+
(
|
25 |
+
token["username"]
|
26 |
+
if form_data["collectionName"] is None
|
27 |
+
else form_data["collectionName"]
|
28 |
+
),
|
29 |
+
)
|
30 |
+
await save_question_and_answer_to_chat_history(
|
31 |
+
token["username"],
|
32 |
+
{
|
33 |
+
"question": form_data["query"],
|
34 |
+
"answer": answer,
|
35 |
+
"collectionName": (
|
36 |
+
form_data["query"] if form_data["query"] else "CHAT WITH YOUR PDF"
|
37 |
+
),
|
38 |
+
},
|
39 |
+
)
|
40 |
+
return ResponseHandler.success(2001, answer)
|
41 |
+
except Exception as error:
|
42 |
+
print(error)
|
43 |
+
return ResponseHandler.error(9999, error, 500)
|
44 |
+
|
45 |
+
|
46 |
+
@router.post("/create/embedding/{collection_name}")
|
47 |
+
async def createEmbedding(
|
48 |
+
collection_name: str,
|
49 |
+
files: List[UploadFile] = File(None),
|
50 |
+
tokenData: str = Depends(verify_token),
|
51 |
+
):
|
52 |
+
try:
|
53 |
+
if not files:
|
54 |
+
return ResponseHandler.error(2003)
|
55 |
+
|
56 |
+
responses = []
|
57 |
+
for file in files:
|
58 |
+
response = process_file(model, collection_name, file)
|
59 |
+
responses.append(response)
|
60 |
+
|
61 |
+
return ResponseHandler.success(2002, response)
|
62 |
+
except Exception as error:
|
63 |
+
return ResponseHandler.error(9999, error, 500)
|
64 |
+
|
65 |
+
|
66 |
+
@router.post("/create/tmp/chain")
|
67 |
+
async def createTmpChain(
|
68 |
+
background_tasks: BackgroundTasks,
|
69 |
+
files: List[UploadFile] = File(...),
|
70 |
+
tokenData: str = Depends(verify_token),
|
71 |
+
):
|
72 |
+
try:
|
73 |
+
if not files:
|
74 |
+
return ResponseHandler.error(2003, error, 500)
|
75 |
+
|
76 |
+
all_contents = b""
|
77 |
+
for file in files:
|
78 |
+
contents = await file.read()
|
79 |
+
all_contents += contents
|
80 |
+
|
81 |
+
file_extension = files[0].filename.split(".")[-1]
|
82 |
+
if file_extension == "pdf":
|
83 |
+
chain_name = tokenData["username"]
|
84 |
+
model.generate_tmp_embedding_and_chain(all_contents, chain_name)
|
85 |
+
background_tasks.add_task(delete_chain_after_delay(model, chain_name))
|
86 |
+
return ResponseHandler.success(2001)
|
87 |
+
elif file_extension == "txt":
|
88 |
+
all_contents.decode("utf-8")
|
89 |
+
return ResponseHandler.error(2004, error, 500)
|
90 |
+
|
91 |
+
except Exception as error:
|
92 |
+
return ResponseHandler.error(9999, error, 500)
|
src/controllers/chat/chats_controller.py
ADDED
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter, Depends, Request, UploadFile
|
2 |
+
|
3 |
+
from src.config.databaseConfig import DATABASE
|
4 |
+
from src.config.appConfig import ENV_VAR
|
5 |
+
|
6 |
+
from src.helpers.pagination import *
|
7 |
+
from src.helpers.response import ResponseHandler
|
8 |
+
from src.helpers.upload_file_cloudinary import upload_file
|
9 |
+
|
10 |
+
from src.middleware.verifyToken import verify_token
|
11 |
+
|
12 |
+
router = APIRouter()
|
13 |
+
|
14 |
+
# Your route definitions
|
15 |
+
|
16 |
+
|
17 |
+
from typing import Dict, List
|
18 |
+
|
19 |
+
|
20 |
+
def convert_form_data_to_dict(form_data) -> Dict[str, List[str]]:
|
21 |
+
data_dict = {}
|
22 |
+
|
23 |
+
for key, value in form_data.items():
|
24 |
+
if isinstance(value, str):
|
25 |
+
if key in data_dict:
|
26 |
+
if isinstance(data_dict[key], list):
|
27 |
+
data_dict[key].append(value)
|
28 |
+
else:
|
29 |
+
data_dict[key] = [data_dict[key], value]
|
30 |
+
else:
|
31 |
+
data_dict[key] = value
|
32 |
+
elif isinstance(value, UploadFile):
|
33 |
+
# Handle UploadFile separately if needed
|
34 |
+
pass
|
35 |
+
else:
|
36 |
+
# Handle other types of values if needed
|
37 |
+
pass
|
38 |
+
|
39 |
+
# Convert keys with multiple values to lists
|
40 |
+
for key in data_dict:
|
41 |
+
if isinstance(data_dict[key], list) and len(data_dict[key]) > 1:
|
42 |
+
continue # Skip if it's already a list with multiple values
|
43 |
+
elif key in form_data.getlist():
|
44 |
+
data_dict[key] = form_data.getlist(key)
|
45 |
+
|
46 |
+
return data_dict
|
47 |
+
|
48 |
+
|
49 |
+
@router.post("/create")
|
50 |
+
async def create_chat(
|
51 |
+
req: Request, icon: UploadFile = None, tokenData=Depends(verify_token)
|
52 |
+
):
|
53 |
+
try:
|
54 |
+
username = tokenData["username"]
|
55 |
+
form_data = await req.form()
|
56 |
+
# payload = {key: form_data[key] for key in form_data if key != "picPath"}
|
57 |
+
# dt = {}
|
58 |
+
# print(convert_form_data_to_dict(form_data))
|
59 |
+
|
60 |
+
if icon is None:
|
61 |
+
return ResponseHandler.error(3007, None, 400)
|
62 |
+
|
63 |
+
file_data = await upload_file(
|
64 |
+
icon, form_data["collectionName"] + "_icon", "ChatIcons/"
|
65 |
+
)
|
66 |
+
|
67 |
+
icon_public_id = file_data["public_id"]
|
68 |
+
# print(icon_public_id)
|
69 |
+
# print(form_data["title"])
|
70 |
+
# print(form_data["templateContext"])
|
71 |
+
# print(form_data["collectionName"])
|
72 |
+
# print(form_data["sampleQuetions"])
|
73 |
+
# print(form_data)
|
74 |
+
# print(ENV_VAR.MONGO_DB_NAME_CHATS)
|
75 |
+
|
76 |
+
chat = DATABASE.client[ENV_VAR.MONGO_DB_NAME_CHATS]["chats"].insert_one(
|
77 |
+
{
|
78 |
+
"username": username,
|
79 |
+
"title": form_data["title"],
|
80 |
+
"templateContext": form_data["templateContext"],
|
81 |
+
"collectionName": form_data["collectionName"],
|
82 |
+
"sampleQuetions": (
|
83 |
+
(form_data["sampleQuetions"])
|
84 |
+
if form_data["sampleQuetions"] is not None
|
85 |
+
else []
|
86 |
+
),
|
87 |
+
"buttonIcon": icon_public_id,
|
88 |
+
}
|
89 |
+
)
|
90 |
+
|
91 |
+
return ResponseHandler.success(3000, chat.acknowledged)
|
92 |
+
except Exception as error:
|
93 |
+
print(error)
|
94 |
+
return ResponseHandler.error(9999, error, 500)
|
95 |
+
|
96 |
+
|
97 |
+
@router.get("/get")
|
98 |
+
async def get_paginated_chats(req: Request):
|
99 |
+
try:
|
100 |
+
page = int(req.query_params.get("page", 1))
|
101 |
+
limit = int(req.query_params.get("limit", 10))
|
102 |
+
|
103 |
+
total_count = DATABASE.client[ENV_VAR.MONGO_DB_NAME_CHATS][
|
104 |
+
"chats"
|
105 |
+
].estimated_document_count()
|
106 |
+
|
107 |
+
chats = (
|
108 |
+
DATABASE.client[ENV_VAR.MONGO_DB_NAME_CHATS]["chats"]
|
109 |
+
.find()
|
110 |
+
.skip((page - 1) * limit)
|
111 |
+
.limit(limit)
|
112 |
+
)
|
113 |
+
|
114 |
+
paginated_response = get_paginated_response(
|
115 |
+
list(chats), page, limit, total_count
|
116 |
+
)
|
117 |
+
# print(paginated_response)
|
118 |
+
return ResponseHandler.success(3001, paginated_response)
|
119 |
+
except Exception as error:
|
120 |
+
return ResponseHandler.error(9000, 500, error)
|
121 |
+
|
122 |
+
|
123 |
+
@router.get("/get/{collection_name}")
|
124 |
+
async def get_chat_by_collection_name(req: Request, collection_name: str):
|
125 |
+
try:
|
126 |
+
chat = DATABASE.client[ENV_VAR.MONGO_DB_NAME_CHATS]["chats"].find_one(
|
127 |
+
{"collectionName": collection_name}
|
128 |
+
)
|
129 |
+
if not chat:
|
130 |
+
return ResponseHandler.error(3003, 404)
|
131 |
+
return ResponseHandler.success(3002, chat)
|
132 |
+
except Exception as error:
|
133 |
+
return ResponseHandler.error(9000, 500, error)
|
134 |
+
|
135 |
+
|
136 |
+
@router.put("/edit/{collection_name}")
|
137 |
+
async def update_chat(
|
138 |
+
req: Request,
|
139 |
+
collection_name: str,
|
140 |
+
icon: UploadFile = None,
|
141 |
+
tokenData=Depends(verify_token),
|
142 |
+
):
|
143 |
+
try:
|
144 |
+
username = tokenData["username"]
|
145 |
+
form_data = await req.form()
|
146 |
+
chat = DATABASE.client[ENV_VAR.MONGO_DB_NAME_CHATS]["chats"].find_one(
|
147 |
+
{"collectionName": collection_name, "username": username}
|
148 |
+
)
|
149 |
+
|
150 |
+
if not chat:
|
151 |
+
return ResponseHandler.error(3003, 404)
|
152 |
+
|
153 |
+
icon_public_id = None
|
154 |
+
if icon is not None:
|
155 |
+
file_data = await upload_file(
|
156 |
+
icon, form_data["collectionName"] + "_icon", "ChatIcons/"
|
157 |
+
)
|
158 |
+
icon_public_id = file_data["public_id"]
|
159 |
+
|
160 |
+
updated_chat = DATABASE.client[ENV_VAR.MONGO_DB_NAME_CHATS][
|
161 |
+
"chats"
|
162 |
+
].find_one_and_update(
|
163 |
+
{"collectionName": collection_name},
|
164 |
+
{
|
165 |
+
"$set": {
|
166 |
+
"title": form_data["title"],
|
167 |
+
"templateContext": form_data["templateContext"],
|
168 |
+
"buttonIcon": icon_public_id or chat.get("buttonIcon"),
|
169 |
+
}
|
170 |
+
},
|
171 |
+
)
|
172 |
+
|
173 |
+
if not updated_chat:
|
174 |
+
return ResponseHandler.error(3003, 404)
|
175 |
+
return ResponseHandler.success(3004)
|
176 |
+
except Exception as error:
|
177 |
+
return ResponseHandler.error(9000, 500, error)
|
178 |
+
|
179 |
+
|
180 |
+
@router.delete("/delete/{collection_name}")
|
181 |
+
async def delete_chat(collection_name: str, tokenData=Depends(verify_token)):
|
182 |
+
try:
|
183 |
+
username = tokenData["username"]
|
184 |
+
deleted_chat = DATABASE.client[ENV_VAR.MONGO_DB_NAME_CHATS][
|
185 |
+
"chats"
|
186 |
+
].find_one_and_delete({"collectionName": collection_name, "username": username})
|
187 |
+
if not deleted_chat:
|
188 |
+
return ResponseHandler.error(3003, 404)
|
189 |
+
return ResponseHandler.success(3005)
|
190 |
+
except Exception as error:
|
191 |
+
return ResponseHandler.error(9000, 500, error)
|
src/controllers/chat/history_controller.py
ADDED
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Optional
|
2 |
+
from fastapi import APIRouter, Depends
|
3 |
+
from bson import ObjectId
|
4 |
+
|
5 |
+
from src.config.appConfig import ENV_VAR
|
6 |
+
from src.config.databaseConfig import DATABASE
|
7 |
+
|
8 |
+
from src.helpers.response import ResponseHandler
|
9 |
+
from src.helpers.pagination import get_paginated_response
|
10 |
+
|
11 |
+
from src.middleware.verifyToken import verify_token
|
12 |
+
|
13 |
+
router = APIRouter()
|
14 |
+
|
15 |
+
|
16 |
+
@router.delete("/delete")
|
17 |
+
async def delete_chat_history(tokenData: str = Depends(verify_token)):
|
18 |
+
try:
|
19 |
+
username = tokenData["username"]
|
20 |
+
deleted_chat_history = DATABASE.client[ENV_VAR.MONGO_DB_NAME_CHATS][
|
21 |
+
"chathistories"
|
22 |
+
].delete_one({"username": username})
|
23 |
+
|
24 |
+
if deleted_chat_history.deleted_count == 0:
|
25 |
+
return ResponseHandler.error(4002, 404)
|
26 |
+
else:
|
27 |
+
return ResponseHandler.success(4004)
|
28 |
+
except Exception as error:
|
29 |
+
print("Error deleting chat history:", error)
|
30 |
+
return ResponseHandler.error(9000, 500, error)
|
31 |
+
|
32 |
+
|
33 |
+
@router.delete("/delete/question/{questionId}")
|
34 |
+
async def delete_question_from_history(
|
35 |
+
questionId: str, tokenData: str = Depends(verify_token)
|
36 |
+
):
|
37 |
+
try:
|
38 |
+
username = tokenData["username"]
|
39 |
+
update_query = {
|
40 |
+
"$pull": {"history": {"_id": ObjectId(questionId)}},
|
41 |
+
"$inc": {"historyCount": -1},
|
42 |
+
}
|
43 |
+
|
44 |
+
result = DATABASE.client[ENV_VAR.MONGO_DB_NAME_CHATS][
|
45 |
+
"chathistories"
|
46 |
+
].update_one(
|
47 |
+
{"username": username, "history._id": ObjectId(questionId)}, update_query
|
48 |
+
)
|
49 |
+
print(result)
|
50 |
+
if result.matched_count != 0:
|
51 |
+
return ResponseHandler.success(4005)
|
52 |
+
else:
|
53 |
+
return ResponseHandler.error(4006, 404)
|
54 |
+
except Exception as error:
|
55 |
+
print("error:", error)
|
56 |
+
return ResponseHandler.error(9000, 500, error)
|
57 |
+
|
58 |
+
|
59 |
+
@router.get("/get")
|
60 |
+
async def get_chat_history_by_user_id(
|
61 |
+
page: Optional[int] = 1,
|
62 |
+
limit: Optional[int] = 10,
|
63 |
+
tokenData: str = Depends(verify_token),
|
64 |
+
):
|
65 |
+
try:
|
66 |
+
username = tokenData["username"]
|
67 |
+
|
68 |
+
start_index = (page - 1) * limit
|
69 |
+
|
70 |
+
chat_history = DATABASE.client[ENV_VAR.MONGO_DB_NAME_CHATS][
|
71 |
+
"chathistories"
|
72 |
+
].find_one(
|
73 |
+
{"username": username}, {"history": {"$slice": [start_index, limit]}}
|
74 |
+
)
|
75 |
+
|
76 |
+
if chat_history:
|
77 |
+
paginated_response = get_paginated_response(
|
78 |
+
chat_history["history"], page, limit, chat_history["historyCount"]
|
79 |
+
)
|
80 |
+
return ResponseHandler.success(4001, paginated_response)
|
81 |
+
else:
|
82 |
+
return ResponseHandler.error(4002, 404)
|
83 |
+
except Exception as error:
|
84 |
+
print("Error getting chat history:", error)
|
85 |
+
return ResponseHandler.error(9000)
|
src/helpers/json_convertor.py
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
from bson import ObjectId
|
3 |
+
from datetime import datetime
|
4 |
+
|
5 |
+
|
6 |
+
def custom_serializer(obj):
|
7 |
+
if isinstance(obj, ObjectId):
|
8 |
+
return str(obj)
|
9 |
+
if isinstance(obj, datetime):
|
10 |
+
return obj.isoformat() # Convert datetime to ISO 8601 format
|
11 |
+
raise TypeError(f"Object of type {type(obj)} is not JSON serializable")
|
12 |
+
|
13 |
+
def convert_to_json(data):
|
14 |
+
return json.dumps(data, default=custom_serializer, indent=4)
|
15 |
+
|
src/helpers/pagination.py
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import math
|
2 |
+
|
3 |
+
|
4 |
+
def get_paginated_response(data, current_page, limit, total):
|
5 |
+
total_pages = math.ceil(total / limit)
|
6 |
+
current_page = int(current_page)
|
7 |
+
previous_page = current_page - 1 if current_page > 1 else None
|
8 |
+
next_page = current_page + 1 if current_page < total_pages else None
|
9 |
+
|
10 |
+
return {
|
11 |
+
"page_data": data,
|
12 |
+
"page_information": {
|
13 |
+
"total_data": total,
|
14 |
+
"last_page": total_pages,
|
15 |
+
"current_page": current_page,
|
16 |
+
"previous_page": previous_page,
|
17 |
+
"next_page": next_page,
|
18 |
+
},
|
19 |
+
}
|
src/helpers/response.py
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import Response
|
2 |
+
from src.langs.en.messages import get_message
|
3 |
+
from src.helpers.json_convertor import convert_to_json
|
4 |
+
|
5 |
+
class ResponseHandler:
|
6 |
+
@staticmethod
|
7 |
+
def success(message_code=None, data=None, status_code=200):
|
8 |
+
response = {
|
9 |
+
"success": True,
|
10 |
+
"message": get_message(message_code),
|
11 |
+
"data": data
|
12 |
+
}
|
13 |
+
return Response(content=convert_to_json(response), status_code=status_code, media_type="application/json")
|
14 |
+
|
15 |
+
@staticmethod
|
16 |
+
def error(message_code=9999, error=None, status_code=422, data=None):
|
17 |
+
response = {
|
18 |
+
"success": False,
|
19 |
+
"message": get_message(message_code),
|
20 |
+
"error": str(error) if error else None,
|
21 |
+
"data": data
|
22 |
+
}
|
23 |
+
return Response(content=convert_to_json(response), status_code=(500 if message_code == 9999 else status_code), media_type="application/json")
|
24 |
+
|
25 |
+
@staticmethod
|
26 |
+
def success_mediator(response):
|
27 |
+
return Response(content=(response.content), status_code=response.status_code, media_type="application/json")
|
src/helpers/send_request.py
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from requests import request
|
2 |
+
|
3 |
+
|
4 |
+
def sendRequest(url, method="get", data=None, files=None, headers=None):
|
5 |
+
try:
|
6 |
+
if files:
|
7 |
+
return request(method, url, data=data, files=files, headers=headers)
|
8 |
+
return request(method, url, json=data, headers=headers)
|
9 |
+
except Exception as e:
|
10 |
+
# Handle exceptions and return None
|
11 |
+
print(f"Error occurred during request: {e}")
|
12 |
+
return None
|
src/helpers/upload_file_cloudinary.py
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cloudinary
|
2 |
+
from cloudinary.uploader import upload
|
3 |
+
import io
|
4 |
+
from fastapi import UploadFile
|
5 |
+
|
6 |
+
|
7 |
+
# Define the upload_file function as an async function
|
8 |
+
async def upload_file(file: UploadFile, new_img_file_name: str, dir_address: str):
|
9 |
+
try:
|
10 |
+
|
11 |
+
# Upload the file to Cloudinary
|
12 |
+
result = upload(
|
13 |
+
file=file.file,
|
14 |
+
resource_type="auto",
|
15 |
+
public_id=new_img_file_name,
|
16 |
+
folder=dir_address,
|
17 |
+
)
|
18 |
+
|
19 |
+
return result
|
20 |
+
except Exception as error:
|
21 |
+
print(error)
|
22 |
+
raise error
|
src/langs/en/messages.py
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
MESSAGES = {
|
2 |
+
# Bot messages
|
3 |
+
2000: "Answered successfully.",
|
4 |
+
2001: "Temporary chain created successfully.Will be deleted after 2 hours.",
|
5 |
+
2002: "Embedding added successfully.",
|
6 |
+
2003: "No files provided",
|
7 |
+
2004: "Unsupported file format",
|
8 |
+
# Chat messages
|
9 |
+
3000: "Chat created successfully.",
|
10 |
+
3001: "Chats fetched successfully.",
|
11 |
+
3002: "Chat fetched successfully.",
|
12 |
+
3003: "Chat not found.",
|
13 |
+
3004: "Chat updated successfully.",
|
14 |
+
3005: "Chat deleted successfully.",
|
15 |
+
3006: "Chat with this collection name already exists.",
|
16 |
+
3007: "Icon file must be uploaded.",
|
17 |
+
# Chat history messages
|
18 |
+
4000: "Chat history created successfully.",
|
19 |
+
4001: "Chat history retrieved successfully.",
|
20 |
+
4002: "Chat history not found.",
|
21 |
+
4003: "Chat history updated successfully.",
|
22 |
+
4004: "Chat history deleted successfully.",
|
23 |
+
4005: "Question deleted successfully.",
|
24 |
+
4006: "Question not found.",
|
25 |
+
# Authorization messages
|
26 |
+
5001: "Unauthorized - Admin access required.",
|
27 |
+
5002: "Access denied - Unauthorized.",
|
28 |
+
5003: "Your session expired! Please log in again.",
|
29 |
+
# General messages
|
30 |
+
9000: "Chatbot API server is running...",
|
31 |
+
9999: "Internal Server Error.",
|
32 |
+
}
|
33 |
+
|
34 |
+
|
35 |
+
def get_message(message_code):
|
36 |
+
if isinstance(message_code, int) and message_code in MESSAGES:
|
37 |
+
return MESSAGES[message_code]
|
38 |
+
|
39 |
+
return message_code
|
src/main.py
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter
|
2 |
+
from src.routes.main import main_router
|
3 |
+
import src.config.cloudinaryConfig
|
4 |
+
from src.config.databaseConfig import DATABASE
|
5 |
+
|
6 |
+
origins = ["http://localhost:3000"]
|
7 |
+
|
8 |
+
main = APIRouter()
|
9 |
+
|
10 |
+
DATABASE()
|
11 |
+
|
12 |
+
main.include_router(main_router, prefix="/api", tags=["main"])
|
13 |
+
|
14 |
+
from src.helpers.response import ResponseHandler
|
15 |
+
|
16 |
+
|
17 |
+
@main.get("/")
|
18 |
+
async def home():
|
19 |
+
return ResponseHandler.success(message_code=9000)
|
src/middleware/verifyToken.py
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import HTTPException, Header, status
|
2 |
+
from src.config.appConfig import ENV_VAR, LOG
|
3 |
+
import jwt
|
4 |
+
|
5 |
+
async def verify_token(authorization: str = Header(None)):
|
6 |
+
try:
|
7 |
+
if not authorization or not authorization.startswith("Bearer "):
|
8 |
+
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Token not provided or invalid")
|
9 |
+
|
10 |
+
token = authorization.split("Bearer ")[1]
|
11 |
+
|
12 |
+
try:
|
13 |
+
verified = jwt.decode(token, ENV_VAR.JWT_SECRET, algorithms=["HS256"])
|
14 |
+
LOG.debug("Token verified successfully")
|
15 |
+
except jwt.ExpiredSignatureError:
|
16 |
+
LOG.debug("Token expired")
|
17 |
+
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Token expired")
|
18 |
+
|
19 |
+
return verified
|
20 |
+
except Exception as e:
|
21 |
+
LOG.error(f"An error occurred: {e}")
|
22 |
+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
|
src/routes/auth_routes.py
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter
|
2 |
+
from src.controllers.auth.auth_controller import router as auth_routes
|
3 |
+
from src.controllers.auth.user_controller import router as user_routes
|
4 |
+
from src.controllers.auth.otp_controller import router as otp_routes
|
5 |
+
|
6 |
+
main_auth_router = APIRouter()
|
7 |
+
|
8 |
+
main_auth_router.include_router(auth_routes, prefix="/auth", tags=["Authentication"])
|
9 |
+
main_auth_router.include_router(user_routes, prefix="/user", tags=["Authentication"])
|
10 |
+
main_auth_router.include_router(otp_routes, prefix="/mail", tags=["Authentication"])
|
src/routes/chat_routes.py
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter
|
2 |
+
from src.controllers.chat.chats_controller import router as chat_routes
|
3 |
+
from src.controllers.chat.bot_controller import router as bot_routes
|
4 |
+
from src.controllers.chat.history_controller import router as history_routes
|
5 |
+
|
6 |
+
main_chat_router = APIRouter()
|
7 |
+
|
8 |
+
main_chat_router.include_router(chat_routes, prefix="", tags=["chatbot"])
|
9 |
+
main_chat_router.include_router(bot_routes, prefix="/bot", tags=["chatbot"])
|
10 |
+
main_chat_router.include_router(history_routes, prefix="/history", tags=["chatbot"])
|
src/routes/main.py
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter
|
2 |
+
from src.routes.auth_routes import main_auth_router
|
3 |
+
from src.routes.chat_routes import main_chat_router
|
4 |
+
|
5 |
+
main_router = APIRouter()
|
6 |
+
|
7 |
+
main_router.include_router(main_auth_router)
|
8 |
+
main_router.include_router(main_chat_router, prefix="/chat")
|
src/services/message_services.py
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from src.config.databaseConfig import DATABASE
|
2 |
+
from src.config.appConfig import ENV_VAR
|
3 |
+
from bson import ObjectId
|
4 |
+
|
5 |
+
|
6 |
+
async def save_question_and_answer_to_chat_history(username, history_obj):
|
7 |
+
try:
|
8 |
+
existing_chat_history = DATABASE.client[ENV_VAR.MONGO_DB_NAME_CHATS][
|
9 |
+
"chathistories"
|
10 |
+
].find_one({"username": username})
|
11 |
+
history_obj["_id"] = ObjectId()
|
12 |
+
if not existing_chat_history:
|
13 |
+
new_chat_history = {
|
14 |
+
"username": username,
|
15 |
+
"history": [history_obj],
|
16 |
+
"historyCount": 1,
|
17 |
+
}
|
18 |
+
DATABASE.client[ENV_VAR.MONGO_DB_NAME_CHATS]["chathistories"].insert_one(
|
19 |
+
new_chat_history
|
20 |
+
)
|
21 |
+
else:
|
22 |
+
DATABASE.client[ENV_VAR.MONGO_DB_NAME_CHATS]["chathistories"].update_one(
|
23 |
+
{"username": username},
|
24 |
+
{
|
25 |
+
"$push": {"history": {"$each": [history_obj], "$position": 0}},
|
26 |
+
"$inc": {"historyCount": 1},
|
27 |
+
},
|
28 |
+
)
|
29 |
+
except Exception as error:
|
30 |
+
raise ValueError(f"Error saving question and answer to chat history: {error}")
|