Upload 40 files
Browse files- .env +11 -0
- .gitattributes +1 -0
- Dockerfile +21 -0
- chunks_javea.csv +0 -0
- faiss_javea.index +3 -0
- main.py +3 -0
- project/__init__.py +30 -0
- project/__pycache__/__init__.cpython-310.pyc +0 -0
- project/__pycache__/admin.cpython-310.pyc +0 -0
- project/__pycache__/config.cpython-310.pyc +0 -0
- project/__pycache__/database.cpython-310.pyc +0 -0
- project/admin.py +51 -0
- project/asgi.py +4 -0
- project/bot/__init__.py +7 -0
- project/bot/__pycache__/__init__.cpython-310.pyc +0 -0
- project/bot/__pycache__/auth.cpython-310.pyc +0 -0
- project/bot/__pycache__/models.cpython-310.pyc +0 -0
- project/bot/__pycache__/openai_backend.cpython-310.pyc +0 -0
- project/bot/__pycache__/views.cpython-310.pyc +0 -0
- project/bot/auth.py +28 -0
- project/bot/documents.py +56 -0
- project/bot/models.py +16 -0
- project/bot/openai_backend.py +132 -0
- project/bot/schemas.py +5 -0
- project/bot/templates/home.html +82 -0
- project/bot/views.py +11 -0
- project/config.py +77 -0
- project/database.py +27 -0
- project/ws/__init__.py +5 -0
- project/ws/__pycache__/__init__.cpython-310.pyc +0 -0
- project/ws/__pycache__/views.cpython-310.pyc +0 -0
- project/ws/views.py +19 -0
- requirements.txt +72 -0
- static/css/style.css +81 -0
- static/images/alcolm_logo.png +0 -0
- static/images/bg.png +0 -0
- static/images/icons8-ai-chatting-500.svg +1 -0
- static/images/send.png +0 -0
- static/js/main.js +64 -0
- static/js/utils.js +48 -0
- static/js/ws.js +0 -0
.env
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FASTAPI_CONFIG=production
|
2 |
+
SECRET=zu=*nck@&r26rsa$2qv8a7g-p$slw79ym81mylj6s**w(da6&#
|
3 |
+
OPENAI_API_KEY=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
|
4 |
+
|
5 |
+
DATABASE_USER=hectool_ai_filter
|
6 |
+
DATABASE_PASSWORD=nixtz3orhepndjw4m1D4
|
7 |
+
DATABASE_HOST=localhost
|
8 |
+
DATABASE_PORT=5432
|
9 |
+
DATABASE_NAME=javea
|
10 |
+
|
11 |
+
CONFIDENCE_PARAMETER=50
|
.gitattributes
CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
faiss_javea.index filter=lfs diff=lfs merge=lfs -text
|
Dockerfile
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.10
|
2 |
+
|
3 |
+
ENV PYTHONDONTWRITEBYCODE 1
|
4 |
+
ENV PYTHONBUFFERED 1
|
5 |
+
ENV TRANSFORMERS_CACHE=/istvanai/.cache/huggingface/transformers
|
6 |
+
|
7 |
+
RUN pip install --upgrade pip
|
8 |
+
|
9 |
+
WORKDIR /code
|
10 |
+
|
11 |
+
ADD . /code
|
12 |
+
|
13 |
+
RUN chmod -R 777 /code
|
14 |
+
|
15 |
+
RUN pip install -r requirements.txt
|
16 |
+
|
17 |
+
COPY . .
|
18 |
+
|
19 |
+
EXPOSE 7860
|
20 |
+
|
21 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
chunks_javea.csv
ADDED
The diff for this file is too large to render.
See raw diff
|
|
faiss_javea.index
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:983785b39b3e03719744c884620b11e0b64975ae4388157865abe0b205c02993
|
3 |
+
size 3947565
|
main.py
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
from project import create_app
|
2 |
+
|
3 |
+
app = create_app()
|
project/__init__.py
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import FastAPI
|
2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
3 |
+
from fastapi.staticfiles import StaticFiles
|
4 |
+
|
5 |
+
from project.admin import AdminCustom, MessagePairAdmin
|
6 |
+
from project.bot.auth import authentication_backend_admin
|
7 |
+
from project.database import engine
|
8 |
+
|
9 |
+
|
10 |
+
def create_app() -> FastAPI:
|
11 |
+
app = FastAPI()
|
12 |
+
|
13 |
+
from project.bot import bot_router
|
14 |
+
app.include_router(bot_router, tags=['bot'])
|
15 |
+
|
16 |
+
from project.ws import ws_router
|
17 |
+
app.include_router(ws_router, tags=['ws'])
|
18 |
+
|
19 |
+
app.add_middleware(
|
20 |
+
CORSMiddleware,
|
21 |
+
allow_origins=["*"],
|
22 |
+
allow_methods=["*"],
|
23 |
+
allow_headers=["*"],
|
24 |
+
)
|
25 |
+
|
26 |
+
app.mount('/static', StaticFiles(directory="static"), name="static")
|
27 |
+
|
28 |
+
admin = AdminCustom(app, engine, authentication_backend=authentication_backend_admin)
|
29 |
+
admin.add_view(MessagePairAdmin)
|
30 |
+
return app
|
project/__pycache__/__init__.cpython-310.pyc
ADDED
Binary file (1.08 kB). View file
|
|
project/__pycache__/admin.cpython-310.pyc
ADDED
Binary file (1.94 kB). View file
|
|
project/__pycache__/config.cpython-310.pyc
ADDED
Binary file (3.46 kB). View file
|
|
project/__pycache__/database.cpython-310.pyc
ADDED
Binary file (1.07 kB). View file
|
|
project/admin.py
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from sqladmin import ModelView, action
|
2 |
+
from sqladmin import Admin
|
3 |
+
|
4 |
+
from fastapi import Request, Response
|
5 |
+
from fastapi.responses import RedirectResponse
|
6 |
+
from fastapi.templating import Jinja2Templates
|
7 |
+
|
8 |
+
import logging
|
9 |
+
|
10 |
+
from project.bot.models import MessagePair
|
11 |
+
|
12 |
+
template = Jinja2Templates(directory='templates')
|
13 |
+
|
14 |
+
logger = logging.getLogger(__name__)
|
15 |
+
|
16 |
+
|
17 |
+
class MessagePairAdmin(ModelView, model=MessagePair):
|
18 |
+
category = 'Message History'
|
19 |
+
column_list = [MessagePair.user_message, MessagePair.bot_response, MessagePair.country]
|
20 |
+
is_async = True
|
21 |
+
icon = "fa-solid fa-message"
|
22 |
+
name = "Message Pair"
|
23 |
+
name_plural = "Message pairs"
|
24 |
+
column_searchable_list = [MessagePair.user_message, MessagePair.bot_response, MessagePair.country]
|
25 |
+
column_export_list = ['user_message', 'bot_response', 'country']
|
26 |
+
can_edit = False
|
27 |
+
can_create = False
|
28 |
+
column_default_sort = ('id', True)
|
29 |
+
column_formatters = {
|
30 |
+
MessagePair.bot_response: lambda m, a: m.bot_response[:50] + '...' if len(m.bot_response) > 50
|
31 |
+
else m.bot_response}
|
32 |
+
|
33 |
+
|
34 |
+
class AdminCustom(Admin):
|
35 |
+
|
36 |
+
async def login(self, request: Request) -> Response:
|
37 |
+
assert self.authentication_backend is not None
|
38 |
+
|
39 |
+
context = {"request": request, "error": ""}
|
40 |
+
|
41 |
+
if request.method == "GET":
|
42 |
+
return template.TemplateResponse("login_admin.html", context)
|
43 |
+
|
44 |
+
ok = await self.authentication_backend.login(request)
|
45 |
+
if not ok:
|
46 |
+
context["error"] = "Invalid credentials."
|
47 |
+
return template.TemplateResponse(
|
48 |
+
"login_admin.html", context, status_code=400
|
49 |
+
)
|
50 |
+
|
51 |
+
return RedirectResponse(request.url_for("admin:index"), status_code=302)
|
project/asgi.py
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from project import create_app
|
2 |
+
|
3 |
+
app = create_app()
|
4 |
+
|
project/bot/__init__.py
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter
|
2 |
+
|
3 |
+
bot_router = APIRouter(
|
4 |
+
prefix=''
|
5 |
+
)
|
6 |
+
|
7 |
+
from project.bot import views, models
|
project/bot/__pycache__/__init__.cpython-310.pyc
ADDED
Binary file (299 Bytes). View file
|
|
project/bot/__pycache__/auth.cpython-310.pyc
ADDED
Binary file (1.26 kB). View file
|
|
project/bot/__pycache__/models.cpython-310.pyc
ADDED
Binary file (814 Bytes). View file
|
|
project/bot/__pycache__/openai_backend.cpython-310.pyc
ADDED
Binary file (4.79 kB). View file
|
|
project/bot/__pycache__/views.cpython-310.pyc
ADDED
Binary file (567 Bytes). View file
|
|
project/bot/auth.py
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import uuid
|
3 |
+
|
4 |
+
from fastapi import Request
|
5 |
+
from sqladmin.authentication import AuthenticationBackend as AuthBackendAdmin
|
6 |
+
|
7 |
+
|
8 |
+
class AdminAuth(AuthBackendAdmin):
|
9 |
+
async def login(self, request: Request) -> bool:
|
10 |
+
form = await request.form()
|
11 |
+
username, password = form["username"], form["password"]
|
12 |
+
if username == 'hectool24' and password == 'hectoolshopify2024@':
|
13 |
+
request.session.update({"session": str(uuid.uuid4())})
|
14 |
+
return True
|
15 |
+
return False
|
16 |
+
|
17 |
+
async def logout(self, request: Request) -> bool:
|
18 |
+
request.session.clear()
|
19 |
+
return True
|
20 |
+
|
21 |
+
async def authenticate(self, request: Request) -> bool:
|
22 |
+
token = request.session.get("session")
|
23 |
+
if not token:
|
24 |
+
return False
|
25 |
+
return True
|
26 |
+
|
27 |
+
|
28 |
+
authentication_backend_admin = AdminAuth(secret_key=os.getenv('SECRET'))
|
project/bot/documents.py
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
import pandas as pd
|
3 |
+
|
4 |
+
with open('../../data.json', 'r') as f:
|
5 |
+
data = json.load(f)
|
6 |
+
|
7 |
+
chunks = []
|
8 |
+
for post in data:
|
9 |
+
post_text = post['text']
|
10 |
+
comments: list[dict] = post['comments']
|
11 |
+
comments_str = ''
|
12 |
+
for i, comment in enumerate(comments):
|
13 |
+
comment_text = list(comment.keys())[0]
|
14 |
+
replies = comment[comment_text]
|
15 |
+
reply_str = 'Replies:\n'
|
16 |
+
for j, reply in enumerate(replies):
|
17 |
+
if j + 1 == len(replies):
|
18 |
+
reply_str += f' • {reply}'
|
19 |
+
else:
|
20 |
+
reply_str += f' • {reply}\n'
|
21 |
+
comments_str += f'{i + 1}. {comment_text}\n'
|
22 |
+
if replies:
|
23 |
+
comments_str += f'{reply_str}\n'
|
24 |
+
|
25 |
+
chunk = f"Post: {post_text}\n"
|
26 |
+
if comments:
|
27 |
+
chunk += f'Comments:\n{comments_str}'
|
28 |
+
chunks.append(chunk)
|
29 |
+
#
|
30 |
+
df = pd.DataFrame({"chunks": chunks})
|
31 |
+
df.to_csv('chunks_javea.csv', index=False)
|
32 |
+
|
33 |
+
# for post in data:
|
34 |
+
# post_text = post['text']
|
35 |
+
# comments: list[dict] = post['comments']
|
36 |
+
# comments_str = ''
|
37 |
+
# for i, comment in enumerate(comments):
|
38 |
+
# comment_text = list(comment.keys())[0]
|
39 |
+
# replies = comment[comment_text]
|
40 |
+
# reply_str = '\n'
|
41 |
+
# for j, reply in enumerate(replies):
|
42 |
+
# if j + 1 == len(replies):
|
43 |
+
# reply_str += f'{reply}'
|
44 |
+
# else:
|
45 |
+
# reply_str += f'{reply}\n'
|
46 |
+
# comments_str += f'{comment_text}\n'
|
47 |
+
# if replies:
|
48 |
+
# comments_str += f'{reply_str}\n'
|
49 |
+
#
|
50 |
+
# chunk = f"{post_text}\n"
|
51 |
+
# if comments:
|
52 |
+
# chunk += f'\n{comments_str}'
|
53 |
+
# chunks.append(chunk)
|
54 |
+
|
55 |
+
# df = pd.DataFrame({"chunks": chunks})
|
56 |
+
# df.to_csv('chunks_javea_raw.csv', index=False)
|
project/bot/models.py
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from sqlalchemy import Column, String, Integer
|
2 |
+
|
3 |
+
from project.database import Base
|
4 |
+
|
5 |
+
|
6 |
+
class MessagePair(Base):
|
7 |
+
__tablename__ = 'message_pair'
|
8 |
+
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
|
9 |
+
user_message = Column(String)
|
10 |
+
bot_response = Column(String)
|
11 |
+
country = Column(String, default='Undefined')
|
12 |
+
|
13 |
+
def __init__(self, user_message, bot_response, country):
|
14 |
+
self.user_message = user_message
|
15 |
+
self.bot_response = bot_response
|
16 |
+
self.country = country
|
project/bot/openai_backend.py
ADDED
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import asyncio
|
2 |
+
from typing import List, Dict
|
3 |
+
import faiss
|
4 |
+
import numpy as np
|
5 |
+
import pandas as pd
|
6 |
+
from sqlalchemy.ext.asyncio import AsyncSession
|
7 |
+
from starlette.websockets import WebSocket
|
8 |
+
|
9 |
+
from project.bot.models import MessagePair
|
10 |
+
from project.config import settings
|
11 |
+
|
12 |
+
|
13 |
+
class SearchBot:
|
14 |
+
chat_history = []
|
15 |
+
# is_unknown = False
|
16 |
+
# unknown_counter = 0
|
17 |
+
|
18 |
+
def __init__(self, memory=None):
|
19 |
+
if memory is None:
|
20 |
+
memory = []
|
21 |
+
self.chat_history = memory
|
22 |
+
|
23 |
+
async def _summarize_user_intent(self, user_query: str) -> str:
|
24 |
+
chat_history_str = ''
|
25 |
+
chat_history = self.chat_history[-self.unknown_counter * 2:]
|
26 |
+
for i in chat_history:
|
27 |
+
if i['role'] == 'user':
|
28 |
+
chat_history_str += f"{i['role']}: {i['content']}\n"
|
29 |
+
messages = [
|
30 |
+
{
|
31 |
+
'role': 'system',
|
32 |
+
'content': f"{settings.SUMMARIZE_PROMPT}\n"
|
33 |
+
f"Chat history: ```{chat_history_str}```\n"
|
34 |
+
f"User query: ```{user_query}```"
|
35 |
+
}
|
36 |
+
]
|
37 |
+
response = await settings.OPENAI_CLIENT.chat.completions.create(
|
38 |
+
messages=messages,
|
39 |
+
temperature=0.1,
|
40 |
+
n=1,
|
41 |
+
model="gpt-3.5-turbo-0125"
|
42 |
+
)
|
43 |
+
user_intent = response.choices[0].message.content
|
44 |
+
return user_intent
|
45 |
+
|
46 |
+
@staticmethod
|
47 |
+
def _cls_pooling(model_output):
|
48 |
+
return model_output.last_hidden_state[:, 0]
|
49 |
+
|
50 |
+
async def _convert_to_embeddings(self, text_list):
|
51 |
+
encoded_input = settings.INFO_TOKENIZER(
|
52 |
+
text_list, padding=True, truncation=True, return_tensors="pt"
|
53 |
+
)
|
54 |
+
encoded_input = {k: v.to(settings.device) for k, v in encoded_input.items()}
|
55 |
+
model_output = settings.INFO_MODEL(**encoded_input)
|
56 |
+
return self._cls_pooling(model_output).cpu().detach().numpy().astype('float32')
|
57 |
+
|
58 |
+
@staticmethod
|
59 |
+
async def _get_context_data(user_query: list[float]) -> list[dict]:
|
60 |
+
radius = 30
|
61 |
+
_, distances, indices = settings.FAISS_INDEX.range_search(user_query, radius)
|
62 |
+
indices_distances_df = pd.DataFrame({'index': indices, 'distance': distances})
|
63 |
+
filtered_data_df = settings.products_dataset.iloc[indices].copy()
|
64 |
+
filtered_data_df.loc[:, 'distance'] = indices_distances_df['distance'].values
|
65 |
+
sorted_data_df: pd.DataFrame = filtered_data_df.sort_values(by='distance').reset_index(drop=True)
|
66 |
+
sorted_data_df = sorted_data_df.drop('distance', axis=1)
|
67 |
+
data = sorted_data_df.head(3).to_dict(orient='records')
|
68 |
+
return data
|
69 |
+
|
70 |
+
@staticmethod
|
71 |
+
async def create_context_str(context: List[Dict]) -> str:
|
72 |
+
context_str = ''
|
73 |
+
for i, chunk in enumerate(context):
|
74 |
+
context_str += f'{i + 1}) {chunk["chunks"]}'
|
75 |
+
return context_str
|
76 |
+
|
77 |
+
async def _rag(self, context: List[Dict], query: str, session: AsyncSession, country: str):
|
78 |
+
if context:
|
79 |
+
context_str = await self.create_context_str(context)
|
80 |
+
assistant_message = {"role": 'assistant', "content": context_str}
|
81 |
+
self.chat_history.append(assistant_message)
|
82 |
+
content = settings.PROMPT
|
83 |
+
else:
|
84 |
+
content = settings.EMPTY_PROMPT
|
85 |
+
user_message = {"role": 'user', "content": query}
|
86 |
+
|
87 |
+
self.chat_history.append(user_message)
|
88 |
+
messages = [
|
89 |
+
{
|
90 |
+
'role': 'system',
|
91 |
+
'content': content
|
92 |
+
},
|
93 |
+
]
|
94 |
+
messages = messages + self.chat_history
|
95 |
+
|
96 |
+
stream = await settings.OPENAI_CLIENT.chat.completions.create(
|
97 |
+
messages=messages,
|
98 |
+
temperature=0.1,
|
99 |
+
n=1,
|
100 |
+
model="gpt-3.5-turbo",
|
101 |
+
stream=True
|
102 |
+
)
|
103 |
+
response = ''
|
104 |
+
async for chunk in stream:
|
105 |
+
if chunk.choices[0].delta.content is not None:
|
106 |
+
chunk_content = chunk.choices[0].delta.content
|
107 |
+
response += chunk_content
|
108 |
+
yield response
|
109 |
+
await asyncio.sleep(0.02)
|
110 |
+
assistant_message = {"role": 'assistant', "content": response}
|
111 |
+
self.chat_history.append(assistant_message)
|
112 |
+
try:
|
113 |
+
session.add(MessagePair(user_message=query, bot_response=response, country=country))
|
114 |
+
except Exception as e:
|
115 |
+
print(e)
|
116 |
+
|
117 |
+
async def ask_and_send(self, data: Dict, websocket: WebSocket, session: AsyncSession):
|
118 |
+
query = data['query']
|
119 |
+
country = data['country']
|
120 |
+
transformed_query = await self._convert_to_embeddings(query)
|
121 |
+
context = await self._get_context_data(transformed_query)
|
122 |
+
try:
|
123 |
+
async for chunk in self._rag(context, query, session, country):
|
124 |
+
await websocket.send_text(chunk)
|
125 |
+
# await websocket.send_text('finish')
|
126 |
+
except Exception:
|
127 |
+
await self.emergency_db_saving(session)
|
128 |
+
|
129 |
+
@staticmethod
|
130 |
+
async def emergency_db_saving(session: AsyncSession):
|
131 |
+
await session.commit()
|
132 |
+
await session.close()
|
project/bot/schemas.py
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# from pydantic import BaseModel
|
2 |
+
#
|
3 |
+
#
|
4 |
+
# class UserQuery(BaseModel):
|
5 |
+
# query: str
|
project/bot/templates/home.html
ADDED
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
6 |
+
<title>Hector AI</title>
|
7 |
+
<!-- Fonts -->
|
8 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
9 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
10 |
+
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500&display=swap" rel="stylesheet">
|
11 |
+
<!-- Bootstrap css -->
|
12 |
+
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"
|
13 |
+
integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
|
14 |
+
<!-- Connect style.css -->
|
15 |
+
<link rel="stylesheet" href="{{ url_for('static', path='/css/style.css') }}">
|
16 |
+
<!-- <link rel="stylesheet" href="../../../static/css/style.css">-->
|
17 |
+
</head>
|
18 |
+
|
19 |
+
<div class="container-fluid px-0">
|
20 |
+
<div class="row mx-0">
|
21 |
+
<div class="app px-0">
|
22 |
+
<div id="toggleButton" class="rounded-4">
|
23 |
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="60px" height="60px">
|
24 |
+
<path d="M 25 4.5 C 15.204 4.5 5.9439688 11.985969 3.9179688 21.542969 C 3.9119687 21.571969 3.9200156 21.599906 3.9160156 21.628906 C 1.5620156 23.233906 -0.04296875 26.383 -0.04296875 30 C -0.04296875 35.238 3.3210312 39.5 7.4570312 39.5 C 7.7850313 39.5 8.0913438 39.339313 8.2773438 39.070312 C 8.4643437 38.800312 8.5065781 38.456438 8.3925781 38.148438 C 8.3775781 38.110438 6.9550781 34.244 6.9550781 29.5 C 6.9550781 24.506 8.3091719 22.022187 8.3261719 21.992188 C 8.5011719 21.683187 8.4983125 21.305047 8.3203125 20.998047 C 8.1433125 20.689047 7.8130313 20.5 7.4570312 20.5 C 7.0350313 20.5 6.62275 20.554625 6.21875 20.640625 C 8.58675 12.613625 16.57 6.5 25 6.5 C 32.992 6.5 40.688641 12.044172 43.431641 19.576172 C 43.133641 19.530172 42.831438 19.5 42.523438 19.5 C 42.169438 19.5 41.841109 19.689094 41.662109 19.996094 C 41.482109 20.302094 41.481297 20.683187 41.654297 20.992188 C 41.668297 21.016188 43.023437 23.5 43.023438 28.5 C 43.023438 32.44 42.045078 35.767641 41.705078 36.806641 C 40.558078 37.740641 38.815344 39.034297 36.777344 40.154297 C 36.016344 39.305297 34.839391 38.873437 33.650391 39.148438 L 31.867188 39.558594 C 31.024188 39.751594 30.308609 40.262094 29.849609 40.996094 C 29.391609 41.728094 29.245453 42.5965 29.439453 43.4375 C 29.783453 44.9335 31.11975 45.949219 32.59375 45.949219 C 32.83275 45.949219 33.074359 45.923187 33.318359 45.867188 L 35.103516 45.455078 C 35.945516 45.262078 36.661141 44.752531 37.119141 44.019531 C 37.503141 43.406531 37.653984 42.698234 37.583984 41.990234 C 39.728984 40.828234 41.570453 39.481469 42.814453 38.480469 C 46.814453 38.285469 50.023438 34.114 50.023438 29 C 50.023438 25.237 48.284437 21.989172 45.773438 20.451172 C 45.769438 20.376172 45.777859 20.301563 45.755859 20.226562 C 43.152859 11.113563 34.423 4.5 25 4.5 z M 12 19 C 11.447 19 11 19.447 11 20 L 11 32 C 11 32.553 11.447 33 12 33 L 28.044922 33 C 27.540922 34.057 26.743578 35.482375 26.142578 36.484375 C 25.941578 36.819375 25.954828 37.2405 26.173828 37.5625 C 26.360828 37.8395 26.673 38 27 38 C 27.055 38 27.109063 37.995328 27.164062 37.986328 C 33.351062 36.955328 38.412 32.95125 38.625 32.78125 C 38.862 32.59125 39 32.304 39 32 L 39 20 C 39 19.447 38.553 19 38 19 L 12 19 z M 13 21 L 37 21 L 37 31.501953 C 35.952 32.266953 32.821953 34.393672 29.001953 35.513672 C 29.643953 34.334672 30.328469 32.955266 30.480469 32.197266 C 30.539469 31.903266 30.462438 31.598187 30.273438 31.367188 C 30.082438 31.135188 29.8 31 29.5 31 L 13 31 L 13 21 z M 44.121094 21.822266 C 46.378094 22.758266 48.023437 25.622 48.023438 29 C 48.023438 32.456 46.299891 35.373281 43.962891 36.238281 C 44.420891 34.565281 45.023438 31.747 45.023438 28.5 C 45.023438 25.445 44.556094 23.226266 44.121094 21.822266 z M 5.859375 22.822266 C 5.423375 24.225266 4.9570313 26.445 4.9570312 29.5 C 4.9570312 32.747 5.5595781 35.565281 6.0175781 37.238281 C 3.6805781 36.373281 1.9570312 33.456 1.9570312 30 C 1.9570312 26.622 3.602375 23.758266 5.859375 22.822266 z M 18.5 23 C 17.098 23 16 24.317 16 26 C 16 27.683 17.098 29 18.5 29 C 19.902 29 21 27.683 21 26 C 21 24.317 19.902 23 18.5 23 z M 31.5 23 C 30.098 23 29 24.317 29 26 C 29 27.683 30.098 29 31.5 29 C 32.902 29 34 27.683 34 26 C 34 24.317 32.902 23 31.5 23 z M 18.5 25 C 18.677 25 19 25.38 19 26 C 19 26.62 18.677 27 18.5 27 C 18.323 27 18 26.62 18 26 C 18 25.38 18.323 25 18.5 25 z M 31.5 25 C 31.677 25 32 25.38 32 26 C 32 26.62 31.677 27 31.5 27 C 31.323 27 31 26.62 31 26 C 31 25.38 31.323 25 31.5 25 z M 34.376953 41.064453 C 34.605953 41.064453 34.83225 41.128906 35.03125 41.253906 C 35.31025 41.428906 35.504125 41.702391 35.578125 42.025391 C 35.652125 42.348391 35.598828 42.678984 35.423828 42.958984 C 35.248828 43.237984 34.976297 43.433812 34.654297 43.507812 L 34.652344 43.507812 L 32.869141 43.917969 C 32.208141 44.071969 31.540672 43.654234 31.388672 42.990234 C 31.314672 42.668234 31.369922 42.337641 31.544922 42.056641 C 31.719922 41.777641 31.992453 41.581813 32.314453 41.507812 L 34.097656 41.097656 C 34.190656 41.076656 34.284953 41.064453 34.376953 41.064453 z"
|
25 |
+
fill="#008dff"/>
|
26 |
+
</svg>
|
27 |
+
</div>
|
28 |
+
<div class="chatbot-window" id="chatbotWindow" style="visibility: hidden;">
|
29 |
+
<div class="chat-header d-flex align-items-center justify-content-between py-2" id="chatHeader">
|
30 |
+
<div class="d-flex align-items-center">
|
31 |
+
<div style="color: #008dff; font-size: 30px; letter-spacing: 7px" class="fw-bold me-3 ms-3">
|
32 |
+
Hector AI
|
33 |
+
</div>
|
34 |
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="50px" height="50px"
|
35 |
+
class="">
|
36 |
+
<path d="M 25 4.5 C 15.204 4.5 5.9439688 11.985969 3.9179688 21.542969 C 3.9119687 21.571969 3.9200156 21.599906 3.9160156 21.628906 C 1.5620156 23.233906 -0.04296875 26.383 -0.04296875 30 C -0.04296875 35.238 3.3210312 39.5 7.4570312 39.5 C 7.7850313 39.5 8.0913438 39.339313 8.2773438 39.070312 C 8.4643437 38.800312 8.5065781 38.456438 8.3925781 38.148438 C 8.3775781 38.110438 6.9550781 34.244 6.9550781 29.5 C 6.9550781 24.506 8.3091719 22.022187 8.3261719 21.992188 C 8.5011719 21.683187 8.4983125 21.305047 8.3203125 20.998047 C 8.1433125 20.689047 7.8130313 20.5 7.4570312 20.5 C 7.0350313 20.5 6.62275 20.554625 6.21875 20.640625 C 8.58675 12.613625 16.57 6.5 25 6.5 C 32.992 6.5 40.688641 12.044172 43.431641 19.576172 C 43.133641 19.530172 42.831438 19.5 42.523438 19.5 C 42.169438 19.5 41.841109 19.689094 41.662109 19.996094 C 41.482109 20.302094 41.481297 20.683187 41.654297 20.992188 C 41.668297 21.016188 43.023437 23.5 43.023438 28.5 C 43.023438 32.44 42.045078 35.767641 41.705078 36.806641 C 40.558078 37.740641 38.815344 39.034297 36.777344 40.154297 C 36.016344 39.305297 34.839391 38.873437 33.650391 39.148438 L 31.867188 39.558594 C 31.024188 39.751594 30.308609 40.262094 29.849609 40.996094 C 29.391609 41.728094 29.245453 42.5965 29.439453 43.4375 C 29.783453 44.9335 31.11975 45.949219 32.59375 45.949219 C 32.83275 45.949219 33.074359 45.923187 33.318359 45.867188 L 35.103516 45.455078 C 35.945516 45.262078 36.661141 44.752531 37.119141 44.019531 C 37.503141 43.406531 37.653984 42.698234 37.583984 41.990234 C 39.728984 40.828234 41.570453 39.481469 42.814453 38.480469 C 46.814453 38.285469 50.023438 34.114 50.023438 29 C 50.023438 25.237 48.284437 21.989172 45.773438 20.451172 C 45.769438 20.376172 45.777859 20.301563 45.755859 20.226562 C 43.152859 11.113563 34.423 4.5 25 4.5 z M 12 19 C 11.447 19 11 19.447 11 20 L 11 32 C 11 32.553 11.447 33 12 33 L 28.044922 33 C 27.540922 34.057 26.743578 35.482375 26.142578 36.484375 C 25.941578 36.819375 25.954828 37.2405 26.173828 37.5625 C 26.360828 37.8395 26.673 38 27 38 C 27.055 38 27.109063 37.995328 27.164062 37.986328 C 33.351062 36.955328 38.412 32.95125 38.625 32.78125 C 38.862 32.59125 39 32.304 39 32 L 39 20 C 39 19.447 38.553 19 38 19 L 12 19 z M 13 21 L 37 21 L 37 31.501953 C 35.952 32.266953 32.821953 34.393672 29.001953 35.513672 C 29.643953 34.334672 30.328469 32.955266 30.480469 32.197266 C 30.539469 31.903266 30.462438 31.598187 30.273438 31.367188 C 30.082438 31.135188 29.8 31 29.5 31 L 13 31 L 13 21 z M 44.121094 21.822266 C 46.378094 22.758266 48.023437 25.622 48.023438 29 C 48.023438 32.456 46.299891 35.373281 43.962891 36.238281 C 44.420891 34.565281 45.023438 31.747 45.023438 28.5 C 45.023438 25.445 44.556094 23.226266 44.121094 21.822266 z M 5.859375 22.822266 C 5.423375 24.225266 4.9570313 26.445 4.9570312 29.5 C 4.9570312 32.747 5.5595781 35.565281 6.0175781 37.238281 C 3.6805781 36.373281 1.9570312 33.456 1.9570312 30 C 1.9570312 26.622 3.602375 23.758266 5.859375 22.822266 z M 18.5 23 C 17.098 23 16 24.317 16 26 C 16 27.683 17.098 29 18.5 29 C 19.902 29 21 27.683 21 26 C 21 24.317 19.902 23 18.5 23 z M 31.5 23 C 30.098 23 29 24.317 29 26 C 29 27.683 30.098 29 31.5 29 C 32.902 29 34 27.683 34 26 C 34 24.317 32.902 23 31.5 23 z M 18.5 25 C 18.677 25 19 25.38 19 26 C 19 26.62 18.677 27 18.5 27 C 18.323 27 18 26.62 18 26 C 18 25.38 18.323 25 18.5 25 z M 31.5 25 C 31.677 25 32 25.38 32 26 C 32 26.62 31.677 27 31.5 27 C 31.323 27 31 26.62 31 26 C 31 25.38 31.323 25 31.5 25 z M 34.376953 41.064453 C 34.605953 41.064453 34.83225 41.128906 35.03125 41.253906 C 35.31025 41.428906 35.504125 41.702391 35.578125 42.025391 C 35.652125 42.348391 35.598828 42.678984 35.423828 42.958984 C 35.248828 43.237984 34.976297 43.433812 34.654297 43.507812 L 34.652344 43.507812 L 32.869141 43.917969 C 32.208141 44.071969 31.540672 43.654234 31.388672 42.990234 C 31.314672 42.668234 31.369922 42.337641 31.544922 42.056641 C 31.719922 41.777641 31.992453 41.581813 32.314453 41.507812 L 34.097656 41.097656 C 34.190656 41.076656 34.284953 41.064453 34.376953 41.064453 z"
|
37 |
+
fill="#008dff"/>
|
38 |
+
</svg>
|
39 |
+
</div>
|
40 |
+
<div style="color: #008dff; cursor: pointer" onclick="closeChatBotWindow()">
|
41 |
+
<i class="fa-solid fa-xmark fs-1 me-3"></i>
|
42 |
+
</div>
|
43 |
+
</div>
|
44 |
+
<div class="chat-body" id="chatBody">
|
45 |
+
<div class="message">
|
46 |
+
<div class="bot_message mt-4 py-3 px-4 rounded-4 w-75 ms-3">
|
47 |
+
Hallo! Ik ben een virtuele assistent, een helper voor Nederlanders die naar Javea zijn
|
48 |
+
gemigreerd. Ik weet alles over deze regio en zal je helpen om je aan te passen in je nieuwe
|
49 |
+
thuis. Waarmee kan ik je helpen?
|
50 |
+
</div>
|
51 |
+
</div>
|
52 |
+
</div>
|
53 |
+
<div class="chat-footer px-3 mb-2 bg-white pt-3" id="chatFooter">
|
54 |
+
<input type="text" class="form-control rounded-5" id="textInput"
|
55 |
+
placeholder="Stellen Sie eine Frage" style="height: 50px">
|
56 |
+
<div style="cursor: pointer; background-color: #105ead; height: 50px"
|
57 |
+
class="rounded-5 mt-3 d-flex text-white justify-content-center align-items-center fs-4"
|
58 |
+
id="sendButton">
|
59 |
+
<span>Send <img src="../../../static/images/send.png" alt="" height="35px"></span>
|
60 |
+
</div>
|
61 |
+
</div>
|
62 |
+
</div>
|
63 |
+
</div>
|
64 |
+
</div>
|
65 |
+
</div>
|
66 |
+
|
67 |
+
</html>
|
68 |
+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"
|
69 |
+
integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4"
|
70 |
+
crossorigin="anonymous"></script>
|
71 |
+
<script src="https://code.jquery.com/jquery-3.6.3.min.js"
|
72 |
+
integrity="sha256-pvPw+upLPUjgMXY0G+8O0xUf+/Im1MZjXxxgOcBQBXU=" crossorigin="anonymous"></script>
|
73 |
+
<link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
|
74 |
+
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
|
75 |
+
<script src="https://kit.fontawesome.com/d4ffd37f75.js" crossorigin="anonymous"></script>
|
76 |
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
77 |
+
<!--<script type="text/javascript" src="../../../static/js/main.js"></script>-->
|
78 |
+
<!--<script type="text/javascript" src="../../../static/js/utils.js"></script>-->
|
79 |
+
<!--<script type="text/javascript" src="../../../static/js/ws.js"></script>-->
|
80 |
+
<script type="text/javascript" src="{{ url_for('static', path='/js/main.js') }}"></script>
|
81 |
+
<script type="text/javascript" src="{{ url_for('static', path='/js/utils.js') }}"></script>
|
82 |
+
<script type="text/javascript" src="{{ url_for('static', path='/js/ws.js') }}"></script>
|
project/bot/views.py
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi.templating import Jinja2Templates
|
2 |
+
from fastapi.requests import Request
|
3 |
+
|
4 |
+
from project.bot import bot_router
|
5 |
+
|
6 |
+
template = Jinja2Templates(directory='project/bot/templates')
|
7 |
+
|
8 |
+
|
9 |
+
@bot_router.get('/', name='main')
|
10 |
+
async def main(request: Request):
|
11 |
+
return template.TemplateResponse("home.html", {'request': request})
|
project/config.py
ADDED
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
|
3 |
+
import faiss
|
4 |
+
import pandas as pd
|
5 |
+
from openai import AsyncOpenAI
|
6 |
+
import pathlib
|
7 |
+
from functools import lru_cache
|
8 |
+
from environs import Env
|
9 |
+
from transformers import AutoModel, AutoTokenizer
|
10 |
+
import torch
|
11 |
+
|
12 |
+
env = Env()
|
13 |
+
env.read_env()
|
14 |
+
|
15 |
+
|
16 |
+
class BaseConfig:
|
17 |
+
BASE_DIR: pathlib.Path = pathlib.Path(__file__).parent.parent
|
18 |
+
DATA_DIR: pathlib.Path = BASE_DIR / 'project' / 'data'
|
19 |
+
MODEL_NAME = 'sentence-transformers/paraphrase-multilingual-mpnet-base-v2'
|
20 |
+
INFO_MODEL = AutoModel.from_pretrained(MODEL_NAME)
|
21 |
+
INFO_TOKENIZER = AutoTokenizer.from_pretrained(MODEL_NAME)
|
22 |
+
OPENAI_CLIENT = AsyncOpenAI(api_key=os.getenv('OPENAI_API_KEY'))
|
23 |
+
FAISS_INDEX = faiss.read_index(str(BASE_DIR / 'faiss_javea.index'))
|
24 |
+
|
25 |
+
|
26 |
+
class DevelopmentConfig(BaseConfig):
|
27 |
+
pass
|
28 |
+
|
29 |
+
|
30 |
+
class ProductionConfig(BaseConfig):
|
31 |
+
DATABASE_URL = f"postgresql+asyncpg://{env('DATABASE_USER')}:" \
|
32 |
+
f"{env('DATABASE_PASSWORD')}@" \
|
33 |
+
f"{env('DATABASE_HOST')}:" \
|
34 |
+
f"{env('DATABASE_PORT')}/" \
|
35 |
+
f"{env('DATABASE_NAME')}"
|
36 |
+
PROMPT = "Je bent een expert in de regio Javea in Italië, die alles weet om mensen te helpen die migreren van " \
|
37 |
+
"Nederland naar Spanje. Je taak is om mensen te helpen zich te vestigen in de nieuwe stad. Gebruik " \
|
38 |
+
"kennis uit je vorige antwoord (voornamelijk uit opmerkingen) om een informatief antwoord " \
|
39 |
+
"te geven op de gebruikersvraag. Vermeld nooit dat je kennis haalt uit posts of opmerkingen. Spreek " \
|
40 |
+
"vanuit jezelf."
|
41 |
+
EMPTY_PROMPT = "Je bent een expert in Javea aan de Costa Blanca in Spanje, met uitgebreide kennis om Nederlanders " \
|
42 |
+
"te helpen die naar deze regio verhuizen. Je taak is om mensen te helpen zich thuis te voelen in " \
|
43 |
+
"hun nieuwe stad. Gebruik je kennis over deze regio maar informatieve antwoorden te " \
|
44 |
+
"geven op de vragen van gebruikers."
|
45 |
+
SUMMARIZE_PROMPT = "Study the user's requests, paying special attention to the specific mentioned wishes when " \
|
46 |
+
"choosing a house. Combine these details into a single query that reflects all the user's " \
|
47 |
+
"needs. Formulate your answer as if you were a user, clearly and concisely stating the " \
|
48 |
+
"requirements. Make sure that all relevant user wishes are indicated in your response. "
|
49 |
+
|
50 |
+
def __init__(self):
|
51 |
+
if torch.cuda.is_available():
|
52 |
+
device = torch.device("cuda")
|
53 |
+
|
54 |
+
else:
|
55 |
+
device = torch.device("cpu")
|
56 |
+
self.device = device
|
57 |
+
self.INFO_MODEL.to(device)
|
58 |
+
self.products_dataset = pd.read_csv(self.BASE_DIR / 'chunks_javea.csv')
|
59 |
+
|
60 |
+
|
61 |
+
class TestConfig(BaseConfig):
|
62 |
+
pass
|
63 |
+
|
64 |
+
|
65 |
+
@lru_cache()
|
66 |
+
def get_settings() -> DevelopmentConfig | ProductionConfig | TestConfig:
|
67 |
+
config_cls_dict = {
|
68 |
+
'development': DevelopmentConfig,
|
69 |
+
'production': ProductionConfig,
|
70 |
+
'testing': TestConfig
|
71 |
+
}
|
72 |
+
config_name = env('FASTAPI_CONFIG', default='development')
|
73 |
+
config_cls = config_cls_dict[config_name]
|
74 |
+
return config_cls()
|
75 |
+
|
76 |
+
|
77 |
+
settings = get_settings()
|
project/database.py
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import AsyncGenerator
|
2 |
+
|
3 |
+
from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine, async_sessionmaker, AsyncSession
|
4 |
+
from sqlalchemy.orm import declarative_base
|
5 |
+
|
6 |
+
from project.config import settings
|
7 |
+
|
8 |
+
Base = declarative_base()
|
9 |
+
|
10 |
+
|
11 |
+
def get_async_engine(url: str) -> AsyncEngine:
|
12 |
+
return create_async_engine(url=url, echo=True, pool_pre_ping=True)
|
13 |
+
|
14 |
+
|
15 |
+
def get_async_sessionmaker(engine: AsyncEngine) -> async_sessionmaker:
|
16 |
+
return async_sessionmaker(bind=engine, class_=AsyncSession)
|
17 |
+
|
18 |
+
|
19 |
+
engine = get_async_engine(
|
20 |
+
settings.DATABASE_URL
|
21 |
+
)
|
22 |
+
async_session_maker = get_async_sessionmaker(engine)
|
23 |
+
|
24 |
+
|
25 |
+
async def get_async_session() -> AsyncSession:
|
26 |
+
async with async_session_maker() as session:
|
27 |
+
return session
|
project/ws/__init__.py
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import APIRouter
|
2 |
+
|
3 |
+
ws_router = APIRouter()
|
4 |
+
|
5 |
+
from . import views
|
project/ws/__pycache__/__init__.cpython-310.pyc
ADDED
Binary file (254 Bytes). View file
|
|
project/ws/__pycache__/views.cpython-310.pyc
ADDED
Binary file (793 Bytes). View file
|
|
project/ws/views.py
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import WebSocket, WebSocketDisconnect
|
2 |
+
|
3 |
+
from . import ws_router
|
4 |
+
from ..bot.openai_backend import SearchBot
|
5 |
+
from ..database import get_async_session
|
6 |
+
|
7 |
+
|
8 |
+
@ws_router.websocket("/ws/{client_id}")
|
9 |
+
async def websocket_endpoint(websocket: WebSocket, client_id: str):
|
10 |
+
await websocket.accept()
|
11 |
+
chatbot = SearchBot()
|
12 |
+
session = await get_async_session()
|
13 |
+
try:
|
14 |
+
while True:
|
15 |
+
data = await websocket.receive_json()
|
16 |
+
await chatbot.ask_and_send(data, websocket, session)
|
17 |
+
except WebSocketDisconnect:
|
18 |
+
await session.commit()
|
19 |
+
await session.close()
|
requirements.txt
ADDED
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
annotated-types==0.6.0
|
2 |
+
anyio==4.3.0
|
3 |
+
async-timeout==4.0.3
|
4 |
+
asyncpg==0.29.0
|
5 |
+
certifi==2024.2.2
|
6 |
+
charset-normalizer==3.3.2
|
7 |
+
click==8.1.7
|
8 |
+
distro==1.9.0
|
9 |
+
environs==11.0.0
|
10 |
+
exceptiongroup==1.2.0
|
11 |
+
faiss-gpu==1.7.2
|
12 |
+
fastapi==0.110.0
|
13 |
+
filelock==3.13.4
|
14 |
+
fsspec==2024.3.1
|
15 |
+
greenlet==3.0.3
|
16 |
+
h11==0.14.0
|
17 |
+
httpcore==1.0.5
|
18 |
+
httptools==0.6.1
|
19 |
+
httpx==0.27.0
|
20 |
+
huggingface-hub==0.22.2
|
21 |
+
idna==3.6
|
22 |
+
itsdangerous==2.1.2
|
23 |
+
Jinja2==3.1.3
|
24 |
+
MarkupSafe==2.1.5
|
25 |
+
marshmallow==3.21.1
|
26 |
+
mpmath==1.3.0
|
27 |
+
networkx==3.3
|
28 |
+
numpy==1.26.4
|
29 |
+
nvidia-cublas-cu12==12.1.3.1
|
30 |
+
nvidia-cuda-cupti-cu12==12.1.105
|
31 |
+
nvidia-cuda-nvrtc-cu12==12.1.105
|
32 |
+
nvidia-cuda-runtime-cu12==12.1.105
|
33 |
+
nvidia-cudnn-cu12==8.9.2.26
|
34 |
+
nvidia-cufft-cu12==11.0.2.54
|
35 |
+
nvidia-curand-cu12==10.3.2.106
|
36 |
+
nvidia-cusolver-cu12==11.4.5.107
|
37 |
+
nvidia-cusparse-cu12==12.1.0.106
|
38 |
+
nvidia-nccl-cu12==2.19.3
|
39 |
+
nvidia-nvjitlink-cu12==12.4.127
|
40 |
+
nvidia-nvtx-cu12==12.1.105
|
41 |
+
openai==1.17.1
|
42 |
+
packaging==24.0
|
43 |
+
pandas==2.2.2
|
44 |
+
pydantic==2.6.4
|
45 |
+
pydantic_core==2.16.3
|
46 |
+
python-dateutil==2.9.0.post0
|
47 |
+
python-dotenv==1.0.1
|
48 |
+
python-multipart==0.0.9
|
49 |
+
pytz==2024.1
|
50 |
+
PyYAML==6.0.1
|
51 |
+
regex==2023.12.25
|
52 |
+
requests==2.31.0
|
53 |
+
safetensors==0.4.2
|
54 |
+
six==1.16.0
|
55 |
+
sniffio==1.3.1
|
56 |
+
sqladmin==0.16.1
|
57 |
+
SQLAlchemy==2.0.29
|
58 |
+
starlette==0.36.3
|
59 |
+
sympy==1.12
|
60 |
+
tokenizers==0.15.2
|
61 |
+
torch==2.2.2
|
62 |
+
tqdm==4.66.2
|
63 |
+
transformers==4.39.3
|
64 |
+
triton==2.2.0
|
65 |
+
typing_extensions==4.10.0
|
66 |
+
tzdata==2024.1
|
67 |
+
urllib3==2.2.1
|
68 |
+
uvicorn==0.29.0
|
69 |
+
uvloop==0.19.0
|
70 |
+
watchfiles==0.21.0
|
71 |
+
websockets==12.0
|
72 |
+
WTForms==3.1.2
|
static/css/style.css
ADDED
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
:root {
|
2 |
+
--main-blue-color: #0e88df;
|
3 |
+
--main-blue-color-hover: #f51958;
|
4 |
+
--main-bg-color: #e8edf3;
|
5 |
+
}
|
6 |
+
|
7 |
+
body {
|
8 |
+
font-family: 'Montserrat', sans-serif;
|
9 |
+
}
|
10 |
+
|
11 |
+
.chat-header {
|
12 |
+
background-color: #f1f1f1;
|
13 |
+
border-bottom: 1px solid #bdbdbd;
|
14 |
+
}
|
15 |
+
|
16 |
+
.chat-body {
|
17 |
+
background-image: url('../../static/images/bg.png');
|
18 |
+
background-size: cover;
|
19 |
+
padding-top: 1px;
|
20 |
+
border-top: 1px solid transparent;
|
21 |
+
display: flex;
|
22 |
+
flex-direction: column;
|
23 |
+
overflow-y: auto;
|
24 |
+
}
|
25 |
+
|
26 |
+
.chatbot-window {
|
27 |
+
border: 1px solid #bdbdbd;
|
28 |
+
}
|
29 |
+
|
30 |
+
/*::placeholder {*/
|
31 |
+
/* font-size: 30px;*/
|
32 |
+
/*}*/
|
33 |
+
|
34 |
+
.message {
|
35 |
+
max-width: 100%;
|
36 |
+
display: flex;
|
37 |
+
justify-content: flex-start;
|
38 |
+
margin-top: 5px;
|
39 |
+
font-size: 14px;
|
40 |
+
}
|
41 |
+
|
42 |
+
.user_message {
|
43 |
+
background-color: #f3f2f2;
|
44 |
+
margin-left: auto;
|
45 |
+
border: 1px solid #d7d7d7;
|
46 |
+
/*font-size: 20px;*/
|
47 |
+
}
|
48 |
+
|
49 |
+
.bot_message {
|
50 |
+
margin-right: auto;
|
51 |
+
background-color: #d1e8ff;
|
52 |
+
border: 1px solid #71beff;
|
53 |
+
}
|
54 |
+
|
55 |
+
.bot_message p {
|
56 |
+
margin-bottom: 0;
|
57 |
+
}
|
58 |
+
|
59 |
+
.bot_message ol {
|
60 |
+
margin-bottom: 0;
|
61 |
+
}
|
62 |
+
|
63 |
+
.bot_message ul {
|
64 |
+
margin-bottom: 0;
|
65 |
+
}
|
66 |
+
|
67 |
+
.bot_message h3 {
|
68 |
+
margin-top: 0.5rem;
|
69 |
+
font-size: 20px;
|
70 |
+
}
|
71 |
+
|
72 |
+
#toggleButton {
|
73 |
+
cursor: pointer;
|
74 |
+
position: fixed;
|
75 |
+
right: 15px;
|
76 |
+
bottom: 15px;
|
77 |
+
background-color: #f7f9fc;
|
78 |
+
border: 1px solid #ddd;
|
79 |
+
padding: 20px;
|
80 |
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
81 |
+
}
|
static/images/alcolm_logo.png
ADDED
static/images/bg.png
ADDED
static/images/icons8-ai-chatting-500.svg
ADDED
static/images/send.png
ADDED
static/js/main.js
ADDED
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const chatBody = document.getElementById('chatBody')
|
2 |
+
const openButton = document.getElementById('toggleButton')
|
3 |
+
const chatbotWindow = document.getElementById('chatbotWindow');
|
4 |
+
|
5 |
+
let socket
|
6 |
+
let isFirstWord = false
|
7 |
+
let botMessage
|
8 |
+
|
9 |
+
window.addEventListener('load', adjustChatBodyHeight);
|
10 |
+
window.addEventListener('resize', adjustChatBodyHeight);
|
11 |
+
|
12 |
+
function getTotalHeight(element) {
|
13 |
+
const styles = window.getComputedStyle(element);
|
14 |
+
const margins = ['marginTop', 'marginBottom', 'borderTopWidth', 'borderBottomWidth']
|
15 |
+
.reduce((acc, style) => acc + parseFloat(styles[style]), 0);
|
16 |
+
return element.offsetHeight + margins;
|
17 |
+
}
|
18 |
+
|
19 |
+
function adjustChatBodyHeight() {
|
20 |
+
const chatFooter = document.getElementById('chatFooter')
|
21 |
+
const chatHeader = document.getElementById('chatHeader')
|
22 |
+
const chatFooterHeight = getTotalHeight(chatFooter);
|
23 |
+
const chatHeaderHeight = getTotalHeight(chatHeader);
|
24 |
+
const openButtonWindow = getTotalHeight(openButton)
|
25 |
+
const viewportHeight = window.innerHeight - chatHeaderHeight - chatFooterHeight;
|
26 |
+
chatBody.style.height = viewportHeight + 'px';
|
27 |
+
}
|
28 |
+
|
29 |
+
function openChatBotWindow() {
|
30 |
+
let lastScrollHeight = chatBody.scrollHeight;
|
31 |
+
const uuid = generateUUID()
|
32 |
+
socket = new WebSocket(`ws://127.0.0.1:8000/ws/${uuid}`);
|
33 |
+
socket.onclose = (event) => console.log('WebSocket disconnected', event);
|
34 |
+
socket.onerror = (error) => {
|
35 |
+
alert('Something was wrong. Try again later.')
|
36 |
+
window.location.reload()
|
37 |
+
};
|
38 |
+
socket.onmessage = (event) => {
|
39 |
+
if (chatBody.scrollHeight > lastScrollHeight) {
|
40 |
+
chatBody.scrollTop = chatBody.scrollHeight;
|
41 |
+
lastScrollHeight = chatBody.scrollHeight;
|
42 |
+
}
|
43 |
+
if (!isFirstWord) {
|
44 |
+
isFirstWord = true
|
45 |
+
createNewMessage(event.data, 'bot')
|
46 |
+
const botMessages = document.querySelectorAll('.bot_message');
|
47 |
+
botMessage = botMessages[botMessages.length - 1];
|
48 |
+
} else {
|
49 |
+
botMessage.innerHTML = marked.parse(event.data)
|
50 |
+
}
|
51 |
+
}
|
52 |
+
}
|
53 |
+
|
54 |
+
function closeChatBotWindow() {
|
55 |
+
socket.close()
|
56 |
+
chatbotWindow.style.visibility = 'hidden'
|
57 |
+
openButton.style.display = 'block'
|
58 |
+
}
|
59 |
+
|
60 |
+
openButton.addEventListener('click', function () {
|
61 |
+
chatbotWindow.style.visibility = "visible";
|
62 |
+
openButton.style.display = 'none'
|
63 |
+
openChatBotWindow();
|
64 |
+
});
|
static/js/utils.js
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const sendButton = document.getElementById('sendButton')
|
2 |
+
const textInput = document.getElementById('textInput')
|
3 |
+
|
4 |
+
function createNewMessage(text, type) {
|
5 |
+
const message = document.createElement('div')
|
6 |
+
message.className = 'message'
|
7 |
+
let content = document.createElement('div')
|
8 |
+
if (type === 'bot') {
|
9 |
+
content.className = 'bot_message mt-4 py-3 px-4 rounded-4 w-75 ms-3'
|
10 |
+
} else {
|
11 |
+
content.className = 'user_message mt-4 py-3 px-4 rounded-4 w-75 me-3'
|
12 |
+
}
|
13 |
+
content.innerText = text
|
14 |
+
message.appendChild(content)
|
15 |
+
chatBody.appendChild(message)
|
16 |
+
}
|
17 |
+
|
18 |
+
function sendMessageToServer() {
|
19 |
+
const userQuery = textInput.value;
|
20 |
+
if (userQuery.length === 0) {
|
21 |
+
return
|
22 |
+
}
|
23 |
+
createNewMessage(userQuery, 'user')
|
24 |
+
socket.send(JSON.stringify({'query': userQuery, 'country': "Undefined"}));
|
25 |
+
textInput.value = ''
|
26 |
+
isFirstWord = false
|
27 |
+
chatBody.scrollTop = chatBody.scrollHeight;
|
28 |
+
}
|
29 |
+
|
30 |
+
function generateUUID() {
|
31 |
+
const arr = new Uint8Array(16);
|
32 |
+
window.crypto.getRandomValues(arr);
|
33 |
+
|
34 |
+
arr[6] = (arr[6] & 0x0f) | 0x40;
|
35 |
+
arr[8] = (arr[8] & 0x3f) | 0x80;
|
36 |
+
|
37 |
+
return ([...arr].map((b, i) =>
|
38 |
+
(i === 4 || i === 6 || i === 8 || i === 10 ? "-" : "") + b.toString(16).padStart(2, "0")
|
39 |
+
).join(""));
|
40 |
+
}
|
41 |
+
|
42 |
+
textInput.addEventListener("keydown", function (event) {
|
43 |
+
if (event.key === "Enter") {
|
44 |
+
sendMessageToServer();
|
45 |
+
}
|
46 |
+
});
|
47 |
+
|
48 |
+
sendButton.addEventListener("click", sendMessageToServer);
|
static/js/ws.js
ADDED
File without changes
|