Upload 37 files
Browse files- .env +2 -2
- project/__pycache__/config.cpython-310.pyc +0 -0
- project/bot/__pycache__/openai_backend.cpython-310.pyc +0 -0
- project/bot/documents.py +52 -25
- project/bot/openai_backend.py +68 -26
- project/bot/templates/home.html +10 -11
- project/config.py +11 -11
- static/css/style.css +38 -0
- static/js/main.js +9 -4
- static/js/utils.js +40 -1
.env
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
FASTAPI_CONFIG=production
|
2 |
SECRET=zu=*nck@&r26rsa$2qv8a7g-p$slw79ym81mylj6s**w(da6&#
|
3 |
-
OPENAI_API_KEY=sk-
|
4 |
-
|
5 |
#DATABASE_USER=hectool_ai_filter
|
6 |
#DATABASE_PASSWORD=nixtz3orhepndjw4m1D4
|
7 |
#DATABASE_HOST=localhost
|
|
|
1 |
FASTAPI_CONFIG=production
|
2 |
SECRET=zu=*nck@&r26rsa$2qv8a7g-p$slw79ym81mylj6s**w(da6&#
|
3 |
+
OPENAI_API_KEY=sk-proj-GYY7hx5SM02oDz5lhrK0T3BlbkFJegeuu9bXdNONd3Ds1rqY
|
4 |
+
GOOGLE_PLACES_API_KEY=AIzaSyDvND-yu2b3p2FcO5aS7YhuyFI4dsXRBts
|
5 |
#DATABASE_USER=hectool_ai_filter
|
6 |
#DATABASE_PASSWORD=nixtz3orhepndjw4m1D4
|
7 |
#DATABASE_HOST=localhost
|
project/__pycache__/config.cpython-310.pyc
CHANGED
Binary files a/project/__pycache__/config.cpython-310.pyc and b/project/__pycache__/config.cpython-310.pyc differ
|
|
project/bot/__pycache__/openai_backend.cpython-310.pyc
CHANGED
Binary files a/project/bot/__pycache__/openai_backend.cpython-310.pyc and b/project/bot/__pycache__/openai_backend.cpython-310.pyc differ
|
|
project/bot/documents.py
CHANGED
@@ -3,33 +3,33 @@ 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 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
|
|
|
|
|
|
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']
|
@@ -54,3 +54,30 @@ df.to_csv('chunks_javea.csv', index=False)
|
|
54 |
|
55 |
# df = pd.DataFrame({"chunks": chunks})
|
56 |
# df.to_csv('chunks_javea_raw.csv', index=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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']
|
|
|
54 |
|
55 |
# df = pd.DataFrame({"chunks": chunks})
|
56 |
# df.to_csv('chunks_javea_raw.csv', index=False)
|
57 |
+
|
58 |
+
# import requests
|
59 |
+
# import json
|
60 |
+
#
|
61 |
+
# # URL для запроса к API
|
62 |
+
# url = "https://places.googleapis.com/v1/places:searchText"
|
63 |
+
#
|
64 |
+
# # API ключ (замените 'API_KEY' на ваш реальный API-ключ)
|
65 |
+
# api_key = "AIzaSyAcfgJAVTK1gwC9tmeKg-wcVWCy5CdieW4"
|
66 |
+
#
|
67 |
+
# # Заголовки запроса
|
68 |
+
# headers = {
|
69 |
+
# "Content-Type": "application/json",
|
70 |
+
# "X-Goog-Api-Key": api_key,
|
71 |
+
# "X-Goog-FieldMask": "places.displayName,places.formattedAddress,places.priceLevel"
|
72 |
+
# }
|
73 |
+
#
|
74 |
+
# # Тело запроса
|
75 |
+
# data = {
|
76 |
+
# "textQuery": "Spicy Vegetarian Food in Sydney, Australia"
|
77 |
+
# }
|
78 |
+
#
|
79 |
+
# # Отправка POST запроса
|
80 |
+
# response = requests.post(url, headers=headers, data=json.dumps(data))
|
81 |
+
#
|
82 |
+
# # Вывод ответа от сервера
|
83 |
+
# print(response.text)
|
project/bot/openai_backend.py
CHANGED
@@ -1,10 +1,14 @@
|
|
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
|
@@ -12,6 +16,7 @@ from project.config import settings
|
|
12 |
|
13 |
class SearchBot:
|
14 |
chat_history = []
|
|
|
15 |
# is_unknown = False
|
16 |
# unknown_counter = 0
|
17 |
|
@@ -20,33 +25,70 @@ class SearchBot:
|
|
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"
|
@@ -57,7 +99,7 @@ class SearchBot:
|
|
57 |
|
58 |
@staticmethod
|
59 |
async def _get_context_data(user_query: list[float]) -> list[dict]:
|
60 |
-
radius =
|
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()
|
@@ -83,7 +125,6 @@ class SearchBot:
|
|
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 |
{
|
@@ -122,7 +163,8 @@ class SearchBot:
|
|
122 |
try:
|
123 |
async for chunk in self._rag(context, query, session, country):
|
124 |
await websocket.send_text(chunk)
|
125 |
-
|
|
|
126 |
except Exception:
|
127 |
await self.emergency_db_saving(session)
|
128 |
|
|
|
1 |
import asyncio
|
2 |
+
import json
|
3 |
+
import re
|
4 |
from typing import List, Dict
|
5 |
import faiss
|
6 |
+
import httpx
|
7 |
import numpy as np
|
8 |
import pandas as pd
|
9 |
from sqlalchemy.ext.asyncio import AsyncSession
|
10 |
from starlette.websockets import WebSocket
|
11 |
+
from transformers import pipeline
|
12 |
|
13 |
from project.bot.models import MessagePair
|
14 |
from project.config import settings
|
|
|
16 |
|
17 |
class SearchBot:
|
18 |
chat_history = []
|
19 |
+
|
20 |
# is_unknown = False
|
21 |
# unknown_counter = 0
|
22 |
|
|
|
25 |
memory = []
|
26 |
self.chat_history = memory
|
27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
@staticmethod
|
29 |
def _cls_pooling(model_output):
|
30 |
return model_output.last_hidden_state[:, 0]
|
31 |
|
32 |
+
@staticmethod
|
33 |
+
async def enrich_information_from_google(search_word: str) -> str:
|
34 |
+
url = "https://places.googleapis.com/v1/places:searchText"
|
35 |
+
headers = {
|
36 |
+
"Content-Type": "application/json",
|
37 |
+
"X-Goog-Api-Key": settings.GOOGLE_PLACES_API_KEY,
|
38 |
+
"X-Goog-FieldMask": "places.shortFormattedAddress,places.websiteUri,places.internationalPhoneNumber,"
|
39 |
+
"places.googleMapsUri,places.photos"
|
40 |
+
}
|
41 |
+
data = {
|
42 |
+
"textQuery": f"{search_word} in Javea",
|
43 |
+
"languageCode": "nl",
|
44 |
+
"maxResultCount": 1,
|
45 |
+
|
46 |
+
}
|
47 |
+
async with httpx.AsyncClient() as client:
|
48 |
+
response = await client.post(url, headers=headers, content=json.dumps(data))
|
49 |
+
place_response = response.json()
|
50 |
+
place_response = place_response['places'][0]
|
51 |
+
photo_name = place_response.get('photos')
|
52 |
+
photo_uri = None
|
53 |
+
if photo_name:
|
54 |
+
async with httpx.AsyncClient() as client:
|
55 |
+
response = await client.get(
|
56 |
+
f'https://places.googleapis.com/v1/{photo_name[0]["name"]}/media?maxWidthPx=350&key={settings.GOOGLE_PLACES_API_KEY}')
|
57 |
+
photo_response = response.json()
|
58 |
+
photo_uri = photo_response.get('photoUri')
|
59 |
+
google_maps_uri = place_response.get('googleMapsUri')
|
60 |
+
phone_number = place_response.get('internationalPhoneNumber')
|
61 |
+
formatted_address = place_response.get('shortFormattedAddress')
|
62 |
+
website_uri = place_response.get('websiteUri')
|
63 |
+
if not google_maps_uri:
|
64 |
+
return search_word
|
65 |
+
enriched_word = f'<a class="extraDataLink" href="{google_maps_uri}" target="_blank">{search_word}</a><div class="tooltip-elem">'
|
66 |
+
if photo_uri:
|
67 |
+
enriched_word += f'<img src="{photo_uri}" alt="Image" class="tooltip-img">'
|
68 |
+
if formatted_address:
|
69 |
+
enriched_word += f'<p><a href="{google_maps_uri}" target="_blank">{formatted_address}</a></p>'
|
70 |
+
if website_uri:
|
71 |
+
enriched_word += f'<p><a href="{website_uri}">Google Maps URI</a></p>'
|
72 |
+
if phone_number:
|
73 |
+
phone_str = re.sub(r' ', '', phone_number)
|
74 |
+
enriched_word += f'<p><a href="tel:{phone_str}">Phone number</a></p>'
|
75 |
+
enriched_word += f"</div>"
|
76 |
+
return enriched_word
|
77 |
+
|
78 |
+
async def analyze_full_response(self) -> str:
|
79 |
+
assistant_message = self.chat_history.pop()['content']
|
80 |
+
nlp = pipeline("ner", model=settings.NLP_MODEL, tokenizer=settings.NLP_TOKENIZER, grouped_entities=True)
|
81 |
+
ner_result = nlp(assistant_message)
|
82 |
+
analyzed_assistant_message = assistant_message
|
83 |
+
for entity in ner_result:
|
84 |
+
if entity['entity_group'] in ("LOC", "ORG", "MISC") and entity['word'] != "Javea":
|
85 |
+
start = entity['start']
|
86 |
+
end = entity['end']
|
87 |
+
enriched_information = await self.enrich_information_from_google(entity['word'])
|
88 |
+
analyzed_assistant_message = analyzed_assistant_message[
|
89 |
+
:start] + f"{enriched_information}" + analyzed_assistant_message[end:]
|
90 |
+
return "ENRICHED:" + analyzed_assistant_message
|
91 |
+
|
92 |
async def _convert_to_embeddings(self, text_list):
|
93 |
encoded_input = settings.INFO_TOKENIZER(
|
94 |
text_list, padding=True, truncation=True, return_tensors="pt"
|
|
|
99 |
|
100 |
@staticmethod
|
101 |
async def _get_context_data(user_query: list[float]) -> list[dict]:
|
102 |
+
radius = 4
|
103 |
_, distances, indices = settings.FAISS_INDEX.range_search(user_query, radius)
|
104 |
indices_distances_df = pd.DataFrame({'index': indices, 'distance': distances})
|
105 |
filtered_data_df = settings.products_dataset.iloc[indices].copy()
|
|
|
125 |
else:
|
126 |
content = settings.EMPTY_PROMPT
|
127 |
user_message = {"role": 'user', "content": query}
|
|
|
128 |
self.chat_history.append(user_message)
|
129 |
messages = [
|
130 |
{
|
|
|
163 |
try:
|
164 |
async for chunk in self._rag(context, query, session, country):
|
165 |
await websocket.send_text(chunk)
|
166 |
+
analyzing = await self.analyze_full_response()
|
167 |
+
await websocket.send_text(analyzing)
|
168 |
except Exception:
|
169 |
await self.emergency_db_saving(session)
|
170 |
|
project/bot/templates/home.html
CHANGED
@@ -12,8 +12,8 @@
|
|
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 |
-
|
16 |
-
|
17 |
</head>
|
18 |
|
19 |
<div class="container-fluid px-0">
|
@@ -44,9 +44,7 @@
|
|
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!
|
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>
|
@@ -64,6 +62,7 @@
|
|
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"
|
@@ -74,9 +73,9 @@
|
|
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 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
|
|
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">
|
|
|
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! Ich bin der virtuelle Assistent Alcolm AI. Sie können sich jederzeit mit Fragen zum Alcolm-Software an mich wenden. Ich helfe Ihnen gerne weiter!
|
|
|
|
|
48 |
</div>
|
49 |
</div>
|
50 |
</div>
|
|
|
62 |
</div>
|
63 |
</div>
|
64 |
|
65 |
+
|
66 |
</html>
|
67 |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"
|
68 |
integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4"
|
|
|
73 |
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
|
74 |
<script src="https://kit.fontawesome.com/d4ffd37f75.js" crossorigin="anonymous"></script>
|
75 |
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
76 |
+
<!--<script type="text/javascript" src="../../../static/js/main.js"></script>-->
|
77 |
+
<!--<script type="text/javascript" src="../../../static/js/utils.js"></script>-->
|
78 |
+
<!--<script type="text/javascript" src="../../../static/js/ws.js"></script>-->
|
79 |
+
<script type="text/javascript" src="{{ url_for('static', path='/js/main.js') }}"></script>
|
80 |
+
<script type="text/javascript" src="{{ url_for('static', path='/js/utils.js') }}"></script>
|
81 |
+
<script type="text/javascript" src="{{ url_for('static', path='/js/ws.js') }}"></script>
|
project/config.py
CHANGED
@@ -6,7 +6,7 @@ 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()
|
@@ -21,6 +21,9 @@ class BaseConfig:
|
|
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):
|
@@ -33,24 +36,21 @@ class ProductionConfig(BaseConfig):
|
|
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
|
37 |
-
"Nederland naar Spanje.
|
38 |
-
"kennis
|
39 |
-
"te geven op de
|
40 |
-
"
|
|
|
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 |
-
|
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
|
|
|
6 |
import pathlib
|
7 |
from functools import lru_cache
|
8 |
from environs import Env
|
9 |
+
from transformers import AutoModel, AutoTokenizer, AutoModelForTokenClassification
|
10 |
import torch
|
11 |
|
12 |
env = Env()
|
|
|
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 |
+
NLP_MODEL_NAME = 'Babelscape/wikineural-multilingual-ner'
|
25 |
+
NLP_TOKENIZER = AutoTokenizer.from_pretrained("Babelscape/wikineural-multilingual-ner")
|
26 |
+
NLP_MODEL = AutoModelForTokenClassification.from_pretrained("Babelscape/wikineural-multilingual-ner")
|
27 |
|
28 |
|
29 |
class DevelopmentConfig(BaseConfig):
|
|
|
36 |
f"{env('DATABASE_HOST')}:" \
|
37 |
f"{env('DATABASE_PORT')}/" \
|
38 |
f"{env('DATABASE_NAME')}"
|
39 |
+
PROMPT = "Je bent een expert in de Spaanse regio Javea die alles weet over hoe je mensen kunt helpen die vanuit " \
|
40 |
+
"Nederland naar Spanje migreren. Jouw taak is om mensen te helpen zich te vestigen in een nieuwe stad. " \
|
41 |
+
"Gebruik de kennis die je in je vorige antwoord hebt opgedaan (meestal uit opmerkingen) om een " \
|
42 |
+
"informatief antwoord te geven op de vraag van de gebruiker. Concentreer je in je antwoorden op de namen " \
|
43 |
+
"van de locaties. Vermeld nooit dat je kennis afkomstig is van berichten of opmerkingen. Spreek namens " \
|
44 |
+
"jezelf. "
|
45 |
EMPTY_PROMPT = "Je bent een expert in Javea aan de Costa Blanca in Spanje, met uitgebreide kennis om Nederlanders " \
|
46 |
"te helpen die naar deze regio verhuizen. Je taak is om mensen te helpen zich thuis te voelen in " \
|
47 |
"hun nieuwe stad. Gebruik je kennis over deze regio maar informatieve antwoorden te " \
|
48 |
"geven op de vragen van gebruikers."
|
49 |
+
GOOGLE_PLACES_API_KEY = env('GOOGLE_PLACES_API_KEY')
|
|
|
|
|
|
|
50 |
|
51 |
def __init__(self):
|
52 |
if torch.cuda.is_available():
|
53 |
device = torch.device("cuda")
|
|
|
54 |
else:
|
55 |
device = torch.device("cpu")
|
56 |
self.device = device
|
static/css/style.css
CHANGED
@@ -11,6 +11,8 @@ body {
|
|
11 |
.chat-header {
|
12 |
background-color: #f1f1f1;
|
13 |
border-bottom: 1px solid #bdbdbd;
|
|
|
|
|
14 |
}
|
15 |
|
16 |
.chat-body {
|
@@ -79,3 +81,39 @@ body {
|
|
79 |
padding: 20px;
|
80 |
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
81 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
.chat-header {
|
12 |
background-color: #f1f1f1;
|
13 |
border-bottom: 1px solid #bdbdbd;
|
14 |
+
/*z-index: 10;*/
|
15 |
+
/*position: relative;*/
|
16 |
}
|
17 |
|
18 |
.chat-body {
|
|
|
81 |
padding: 20px;
|
82 |
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
83 |
}
|
84 |
+
|
85 |
+
.extraDataLink {
|
86 |
+
color: #105ead;
|
87 |
+
text-decoration: none;
|
88 |
+
position: relative;
|
89 |
+
}
|
90 |
+
|
91 |
+
.extraDataLink:hover {
|
92 |
+
color: #1164b7;
|
93 |
+
text-decoration: none;
|
94 |
+
z-index: 150;
|
95 |
+
}
|
96 |
+
|
97 |
+
.tooltip-elem {
|
98 |
+
position: absolute;
|
99 |
+
background-color: black;
|
100 |
+
color: white;
|
101 |
+
font-size: 12px;
|
102 |
+
padding: 8px;
|
103 |
+
border-radius: 4px;
|
104 |
+
z-index: 1000;
|
105 |
+
display: none;
|
106 |
+
}
|
107 |
+
.extraDataLink + .tooltip-elem {
|
108 |
+
position: absolute;
|
109 |
+
}
|
110 |
+
|
111 |
+
.tooltip-img{
|
112 |
+
border-radius: 4px;
|
113 |
+
margin-bottom: 5px;
|
114 |
+
}
|
115 |
+
|
116 |
+
.tooltip-elem a{
|
117 |
+
text-decoration: none;
|
118 |
+
color: #008dff;
|
119 |
+
}
|
static/js/main.js
CHANGED
@@ -21,7 +21,6 @@ function adjustChatBodyHeight() {
|
|
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 |
}
|
@@ -29,7 +28,7 @@ function adjustChatBodyHeight() {
|
|
29 |
function openChatBotWindow() {
|
30 |
let lastScrollHeight = chatBody.scrollHeight;
|
31 |
const uuid = generateUUID()
|
32 |
-
socket = new WebSocket(`
|
33 |
socket.onclose = (event) => console.log('WebSocket disconnected', event);
|
34 |
socket.onerror = (error) => {
|
35 |
alert('Something was wrong. Try again later.')
|
@@ -46,7 +45,13 @@ function openChatBotWindow() {
|
|
46 |
const botMessages = document.querySelectorAll('.bot_message');
|
47 |
botMessage = botMessages[botMessages.length - 1];
|
48 |
} else {
|
49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
50 |
}
|
51 |
}
|
52 |
}
|
@@ -61,4 +66,4 @@ openButton.addEventListener('click', function () {
|
|
61 |
chatbotWindow.style.visibility = "visible";
|
62 |
openButton.style.display = 'none'
|
63 |
openChatBotWindow();
|
64 |
-
});
|
|
|
21 |
const chatHeader = document.getElementById('chatHeader')
|
22 |
const chatFooterHeight = getTotalHeight(chatFooter);
|
23 |
const chatHeaderHeight = getTotalHeight(chatHeader);
|
|
|
24 |
const viewportHeight = window.innerHeight - chatHeaderHeight - chatFooterHeight;
|
25 |
chatBody.style.height = viewportHeight + 'px';
|
26 |
}
|
|
|
28 |
function openChatBotWindow() {
|
29 |
let lastScrollHeight = chatBody.scrollHeight;
|
30 |
const uuid = generateUUID()
|
31 |
+
socket = new WebSocket(`ws://127.0.0.1:8000/ws/${uuid}`);
|
32 |
socket.onclose = (event) => console.log('WebSocket disconnected', event);
|
33 |
socket.onerror = (error) => {
|
34 |
alert('Something was wrong. Try again later.')
|
|
|
45 |
const botMessages = document.querySelectorAll('.bot_message');
|
46 |
botMessage = botMessages[botMessages.length - 1];
|
47 |
} else {
|
48 |
+
const aiMessage = event.data
|
49 |
+
if (aiMessage.startsWith('ENRICHED:')) {
|
50 |
+
botMessage.innerHTML = aiMessage.substring(9)
|
51 |
+
enrichAIResponse(botMessage)
|
52 |
+
} else {
|
53 |
+
botMessage.innerHTML = marked.parse(aiMessage)
|
54 |
+
}
|
55 |
}
|
56 |
}
|
57 |
}
|
|
|
66 |
chatbotWindow.style.visibility = "visible";
|
67 |
openButton.style.display = 'none'
|
68 |
openChatBotWindow();
|
69 |
+
});
|
static/js/utils.js
CHANGED
@@ -5,6 +5,7 @@ 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 {
|
@@ -45,4 +46,42 @@ textInput.addEventListener("keydown", function (event) {
|
|
45 |
}
|
46 |
});
|
47 |
|
48 |
-
sendButton.addEventListener("click", sendMessageToServer);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5 |
const message = document.createElement('div')
|
6 |
message.className = 'message'
|
7 |
let content = document.createElement('div')
|
8 |
+
content.style.whiteSpace = "pre-line"
|
9 |
if (type === 'bot') {
|
10 |
content.className = 'bot_message mt-4 py-3 px-4 rounded-4 w-75 ms-3'
|
11 |
} else {
|
|
|
46 |
}
|
47 |
});
|
48 |
|
49 |
+
sendButton.addEventListener("click", sendMessageToServer);
|
50 |
+
|
51 |
+
|
52 |
+
function enrichAIResponse(botMessageElement) {
|
53 |
+
const links = botMessageElement.querySelectorAll('.extraDataLink');
|
54 |
+
|
55 |
+
links.forEach(link => {
|
56 |
+
const tooltip = link.nextElementSibling;
|
57 |
+
link.addEventListener('mouseenter', function () {
|
58 |
+
tooltip.style.display = 'block';
|
59 |
+
const tooltipWidth = parseInt(link.offsetWidth) * 2
|
60 |
+
tooltip.style.width = tooltipWidth + 'px'
|
61 |
+
|
62 |
+
const tooltipImg = tooltip.querySelector('.tooltip-img')
|
63 |
+
if (tooltipImg) {
|
64 |
+
tooltipImg.width = tooltipWidth - 16
|
65 |
+
}
|
66 |
+
const linkRect = this.getBoundingClientRect();
|
67 |
+
if (linkRect.top < tooltip.offsetHeight + 4) {
|
68 |
+
tooltip.style.top = (window.scrollY + linkRect.bottom + 4) + 'px';
|
69 |
+
} else {
|
70 |
+
tooltip.style.top = (window.scrollY + linkRect.top - tooltip.offsetHeight - 4) + 'px';
|
71 |
+
}
|
72 |
+
tooltip.style.left = (linkRect.left + (linkRect.width / 2) - (tooltipWidth / 2)) + 'px';
|
73 |
+
});
|
74 |
+
|
75 |
+
link.addEventListener('mouseleave', function () {
|
76 |
+
setTimeout(() => {
|
77 |
+
if (!tooltip.matches(':hover')) {
|
78 |
+
tooltip.style.display = 'none';
|
79 |
+
}
|
80 |
+
}, 300)
|
81 |
+
});
|
82 |
+
|
83 |
+
tooltip.addEventListener('mouseleave', function () {
|
84 |
+
this.style.display = 'none';
|
85 |
+
});
|
86 |
+
});
|
87 |
+
}
|