upd
This commit is contained in:
parent
d1e5ec019d
commit
7e35793ca1
70
.gitignore
vendored
70
.gitignore
vendored
@ -1,36 +1,36 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
.vs/
|
.vs/
|
||||||
|
|
||||||
|
|
||||||
*.log
|
*.log
|
||||||
*.tmp
|
*.tmp
|
||||||
*.swp
|
*.swp
|
||||||
|
|
||||||
|
|
||||||
frontend/node_modules/
|
frontend/node_modules/
|
||||||
frontend/dist/
|
frontend/dist/
|
||||||
|
|
||||||
|
|
||||||
*.venv/
|
*.venv/
|
||||||
Backend/venv/
|
Backend/venv/
|
||||||
Backend/__pycache__/
|
Backend/__pycache__/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
*.dockerignore
|
*.dockerignore
|
||||||
*.env
|
*.env
|
||||||
docker-compose.override.yml
|
docker-compose.override.yml
|
||||||
|
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
|
|
||||||
*.lock
|
*.lock
|
||||||
package-lock.json
|
package-lock.json
|
||||||
yarn.lock
|
yarn.lock
|
244
Backend/arch.py
244
Backend/arch.py
@ -1,122 +1,122 @@
|
|||||||
from pptx import Presentation
|
from pptx import Presentation
|
||||||
from pptx.util import Inches
|
from pptx.util import Inches
|
||||||
from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE, MSO_CONNECTOR
|
from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE, MSO_CONNECTOR
|
||||||
|
|
||||||
# Vytvorenie novej prezentácie
|
# Vytvorenie novej prezentácie
|
||||||
prs = Presentation()
|
prs = Presentation()
|
||||||
slide_layout = prs.slide_layouts[5] # Prázdny slide
|
slide_layout = prs.slide_layouts[5] # Prázdny slide
|
||||||
slide = prs.slides.add_slide(slide_layout)
|
slide = prs.slides.add_slide(slide_layout)
|
||||||
|
|
||||||
# Definícia základných rozmerov a pozícií
|
# Definícia základných rozmerov a pozícií
|
||||||
left_margin = Inches(0.5)
|
left_margin = Inches(0.5)
|
||||||
top_margin = Inches(0.5)
|
top_margin = Inches(0.5)
|
||||||
block_width = Inches(3)
|
block_width = Inches(3)
|
||||||
block_height = Inches(0.7)
|
block_height = Inches(0.7)
|
||||||
vertical_gap = Inches(0.3)
|
vertical_gap = Inches(0.3)
|
||||||
horizontal_gap = Inches(0.5)
|
horizontal_gap = Inches(0.5)
|
||||||
|
|
||||||
# Blok 1: Používateľský dotaz & Chat history
|
# Blok 1: Používateľský dotaz & Chat history
|
||||||
box1 = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, left_margin, top_margin, block_width, block_height)
|
box1 = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, left_margin, top_margin, block_width, block_height)
|
||||||
box1.text = "Používateľský dotaz\n& Chat history"
|
box1.text = "Používateľský dotaz\n& Chat history"
|
||||||
|
|
||||||
# Blok 2: ConversationalAgent (pod box1)
|
# Blok 2: ConversationalAgent (pod box1)
|
||||||
box2_top = top_margin + block_height + vertical_gap
|
box2_top = top_margin + block_height + vertical_gap
|
||||||
box2 = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, left_margin, box2_top, block_width, block_height)
|
box2 = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, left_margin, box2_top, block_width, block_height)
|
||||||
box2.text = "ConversationalAgent"
|
box2.text = "ConversationalAgent"
|
||||||
|
|
||||||
# Blok 3: Klasifikácia dotazu (pod box2)
|
# Blok 3: Klasifikácia dotazu (pod box2)
|
||||||
box3_top = box2_top + block_height + vertical_gap
|
box3_top = box2_top + block_height + vertical_gap
|
||||||
box3 = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, left_margin, box3_top, block_width, block_height)
|
box3 = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, left_margin, box3_top, block_width, block_height)
|
||||||
box3.text = "Klasifikácia dotazu"
|
box3.text = "Klasifikácia dotazu"
|
||||||
|
|
||||||
# Vetvenie: Pozície pre dve vetvy
|
# Vetvenie: Pozície pre dve vetvy
|
||||||
branch_top = box3_top + block_height + vertical_gap
|
branch_top = box3_top + block_height + vertical_gap
|
||||||
|
|
||||||
# Ľavá vetva ("Vyhladavanie")
|
# Ľavá vetva ("Vyhladavanie")
|
||||||
left_branch_left = left_margin - Inches(0.2)
|
left_branch_left = left_margin - Inches(0.2)
|
||||||
box4A = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, left_branch_left, branch_top, block_width, block_height)
|
box4A = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, left_branch_left, branch_top, block_width, block_height)
|
||||||
box4A.text = "ElasticsearchStore\nvyhľadávanie"
|
box4A.text = "ElasticsearchStore\nvyhľadávanie"
|
||||||
|
|
||||||
box5A_top = branch_top + block_height + vertical_gap
|
box5A_top = branch_top + block_height + vertical_gap
|
||||||
box5A = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, left_branch_left, box5A_top, block_width, block_height)
|
box5A = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, left_branch_left, box5A_top, block_width, block_height)
|
||||||
box5A.text = "Generovanie\ndynamického promptu"
|
box5A.text = "Generovanie\ndynamického promptu"
|
||||||
|
|
||||||
box6A_top = box5A_top + block_height + vertical_gap
|
box6A_top = box5A_top + block_height + vertical_gap
|
||||||
box6A = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, left_branch_left, box6A_top, block_width, block_height)
|
box6A = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, left_branch_left, box6A_top, block_width, block_height)
|
||||||
box6A.text = "Generovanie\nodpovede"
|
box6A.text = "Generovanie\nodpovede"
|
||||||
|
|
||||||
box7A_top = box6A_top + block_height + vertical_gap
|
box7A_top = box6A_top + block_height + vertical_gap
|
||||||
box7A = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, left_branch_left, box7A_top, block_width, block_height)
|
box7A = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, left_branch_left, box7A_top, block_width, block_height)
|
||||||
box7A.text = "Finalizácia\nodpovede"
|
box7A.text = "Finalizácia\nodpovede"
|
||||||
|
|
||||||
# Pravá vetva ("Upresnenie")
|
# Pravá vetva ("Upresnenie")
|
||||||
right_branch_left = left_margin + block_width + horizontal_gap
|
right_branch_left = left_margin + block_width + horizontal_gap
|
||||||
box4B = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, right_branch_left, branch_top, block_width, block_height)
|
box4B = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, right_branch_left, branch_top, block_width, block_height)
|
||||||
box4B.text = "Kombinovanie\ndotazov"
|
box4B.text = "Kombinovanie\ndotazov"
|
||||||
|
|
||||||
box5B_top = branch_top + block_height + vertical_gap
|
box5B_top = branch_top + block_height + vertical_gap
|
||||||
box5B = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, right_branch_left, box5B_top, block_width, block_height)
|
box5B = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, right_branch_left, box5B_top, block_width, block_height)
|
||||||
box5B.text = "ElasticsearchStore\nvyhľadávanie"
|
box5B.text = "ElasticsearchStore\nvyhľadávanie"
|
||||||
|
|
||||||
box6B_top = box5B_top + block_height + vertical_gap
|
box6B_top = box5B_top + block_height + vertical_gap
|
||||||
box6B = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, right_branch_left, box6B_top, block_width, block_height)
|
box6B = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, right_branch_left, box6B_top, block_width, block_height)
|
||||||
box6B.text = "Generovanie\ndynamického promptu"
|
box6B.text = "Generovanie\ndynamického promptu"
|
||||||
|
|
||||||
box7B_top = box6B_top + block_height + vertical_gap
|
box7B_top = box6B_top + block_height + vertical_gap
|
||||||
box7B = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, right_branch_left, box7B_top, block_width, block_height)
|
box7B = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, right_branch_left, box7B_top, block_width, block_height)
|
||||||
box7B.text = "Generovanie\nodpovedí (2 modely)"
|
box7B.text = "Generovanie\nodpovedí (2 modely)"
|
||||||
|
|
||||||
box8B_top = box7B_top + block_height + vertical_gap
|
box8B_top = box7B_top + block_height + vertical_gap
|
||||||
box8B = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, right_branch_left, box8B_top, block_width, block_height)
|
box8B = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, right_branch_left, box8B_top, block_width, block_height)
|
||||||
box8B.text = "Validácia a\nhodnotenie odpovedí"
|
box8B.text = "Validácia a\nhodnotenie odpovedí"
|
||||||
|
|
||||||
box9B_top = box8B_top + block_height + vertical_gap
|
box9B_top = box8B_top + block_height + vertical_gap
|
||||||
box9B = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, right_branch_left, box9B_top, block_width, block_height)
|
box9B = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, right_branch_left, box9B_top, block_width, block_height)
|
||||||
box9B.text = "Finalizácia\nodpovede"
|
box9B.text = "Finalizácia\nodpovede"
|
||||||
|
|
||||||
# Finálny blok: Výstup (zlúčenie vetiev)
|
# Finálny blok: Výstup (zlúčenie vetiev)
|
||||||
final_box_top = max(box7A_top, box9B_top) + block_height + vertical_gap
|
final_box_top = max(box7A_top, box9B_top) + block_height + vertical_gap
|
||||||
final_box = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, left_margin, final_box_top, block_width, block_height)
|
final_box = slide.shapes.add_shape(MSO_AUTO_SHAPE_TYPE.RECTANGLE, left_margin, final_box_top, block_width, block_height)
|
||||||
final_box.text = "Výstup"
|
final_box.text = "Výstup"
|
||||||
|
|
||||||
# Funkcia na pridanie šípok medzi blokmi
|
# Funkcia na pridanie šípok medzi blokmi
|
||||||
def add_connector(slide, start_shape, end_shape):
|
def add_connector(slide, start_shape, end_shape):
|
||||||
start_x = start_shape.left + start_shape.width / 2
|
start_x = start_shape.left + start_shape.width / 2
|
||||||
start_y = start_shape.top + start_shape.height
|
start_y = start_shape.top + start_shape.height
|
||||||
end_x = end_shape.left + end_shape.width / 2
|
end_x = end_shape.left + end_shape.width / 2
|
||||||
end_y = end_shape.top
|
end_y = end_shape.top
|
||||||
connector = slide.shapes.add_connector(MSO_CONNECTOR.STRAIGHT, start_x, start_y, end_x, end_y)
|
connector = slide.shapes.add_connector(MSO_CONNECTOR.STRAIGHT, start_x, start_y, end_x, end_y)
|
||||||
# Aktuálna verzia python-pptx nepodporuje nastavenie šípky, preto tento riadok odstraňte alebo zakomentujte:
|
# Aktuálna verzia python-pptx nepodporuje nastavenie šípky, preto tento riadok odstraňte alebo zakomentujte:
|
||||||
# connector.line.end_arrowhead.style = 1
|
# connector.line.end_arrowhead.style = 1
|
||||||
return connector
|
return connector
|
||||||
|
|
||||||
# Prepojenie blokov
|
# Prepojenie blokov
|
||||||
add_connector(slide, box1, box2)
|
add_connector(slide, box1, box2)
|
||||||
add_connector(slide, box2, box3)
|
add_connector(slide, box2, box3)
|
||||||
|
|
||||||
# Vetvenie z Box3 do oboch vetiev
|
# Vetvenie z Box3 do oboch vetiev
|
||||||
mid_point = box3.left + box3.width / 2
|
mid_point = box3.left + box3.width / 2
|
||||||
branch_mid_y = box3.top + box3.height + vertical_gap/2
|
branch_mid_y = box3.top + box3.height + vertical_gap/2
|
||||||
# Do ľavej vetvy:
|
# Do ľavej vetvy:
|
||||||
connector_left = slide.shapes.add_connector(MSO_CONNECTOR.STRAIGHT, mid_point, box3.top + box3.height, left_branch_left + block_width/2, branch_mid_y)
|
connector_left = slide.shapes.add_connector(MSO_CONNECTOR.STRAIGHT, mid_point, box3.top + box3.height, left_branch_left + block_width/2, branch_mid_y)
|
||||||
# Do pravej vetvy:
|
# Do pravej vetvy:
|
||||||
connector_right = slide.shapes.add_connector(MSO_CONNECTOR.STRAIGHT, mid_point, box3.top + box3.height, right_branch_left + block_width/2, branch_mid_y)
|
connector_right = slide.shapes.add_connector(MSO_CONNECTOR.STRAIGHT, mid_point, box3.top + box3.height, right_branch_left + block_width/2, branch_mid_y)
|
||||||
|
|
||||||
# Prepojenie blokov v ľavej vetve
|
# Prepojenie blokov v ľavej vetve
|
||||||
add_connector(slide, box4A, box5A)
|
add_connector(slide, box4A, box5A)
|
||||||
add_connector(slide, box5A, box6A)
|
add_connector(slide, box5A, box6A)
|
||||||
add_connector(slide, box6A, box7A)
|
add_connector(slide, box6A, box7A)
|
||||||
|
|
||||||
# Prepojenie blokov v pravej vetve
|
# Prepojenie blokov v pravej vetve
|
||||||
add_connector(slide, box4B, box5B)
|
add_connector(slide, box4B, box5B)
|
||||||
add_connector(slide, box5B, box6B)
|
add_connector(slide, box5B, box6B)
|
||||||
add_connector(slide, box6B, box7B)
|
add_connector(slide, box6B, box7B)
|
||||||
add_connector(slide, box7B, box8B)
|
add_connector(slide, box7B, box8B)
|
||||||
add_connector(slide, box8B, box9B)
|
add_connector(slide, box8B, box9B)
|
||||||
|
|
||||||
# Spojenie oboch vetiev s finálnym blokom "Výstup"
|
# Spojenie oboch vetiev s finálnym blokom "Výstup"
|
||||||
add_connector(slide, box7A, final_box)
|
add_connector(slide, box7A, final_box)
|
||||||
add_connector(slide, box9B, final_box)
|
add_connector(slide, box9B, final_box)
|
||||||
|
|
||||||
# Uloženie prezentácie
|
# Uloženie prezentácie
|
||||||
prs.save("architecture_diagram.pptx")
|
prs.save("architecture_diagram.pptx")
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"useCloud" : false
|
"useCloud" : false
|
||||||
}
|
}
|
@ -1,80 +1,80 @@
|
|||||||
from elasticsearch import Elasticsearch
|
from elasticsearch import Elasticsearch
|
||||||
from langchain.embeddings import HuggingFaceEmbeddings
|
from langchain.embeddings import HuggingFaceEmbeddings
|
||||||
from elasticsearch.helpers import bulk
|
from elasticsearch.helpers import bulk
|
||||||
import json
|
import json
|
||||||
|
|
||||||
# Настройка подключения к Elasticsearch с аутентификацией и HTTPS
|
# Настройка подключения к Elasticsearch с аутентификацией и HTTPS
|
||||||
es = Elasticsearch(
|
es = Elasticsearch(
|
||||||
[{'host': 'localhost', 'port': 9200, 'scheme': 'https'}],
|
[{'host': 'localhost', 'port': 9200, 'scheme': 'https'}],
|
||||||
http_auth=('elastic', '3lvFhvVYrazLsj=M-R_g'), # замените на ваш пароль
|
http_auth=('elastic', '3lvFhvVYrazLsj=M-R_g'), # замените на ваш пароль
|
||||||
verify_certs=False # Отключить проверку SSL-сертификата, если используется самоподписанный сертификат
|
verify_certs=False # Отключить проверку SSL-сертификата, если используется самоподписанный сертификат
|
||||||
)
|
)
|
||||||
|
|
||||||
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
|
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
|
||||||
|
|
||||||
def create_index():
|
def create_index():
|
||||||
# Определяем маппинг для индекса
|
# Определяем маппинг для индекса
|
||||||
mapping = {
|
mapping = {
|
||||||
"mappings": {
|
"mappings": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"text": {
|
"text": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"analyzer": "standard"
|
"analyzer": "standard"
|
||||||
},
|
},
|
||||||
"vector": {
|
"vector": {
|
||||||
"type": "dense_vector",
|
"type": "dense_vector",
|
||||||
"dims": 384 # Размерность векторного представления
|
"dims": 384 # Размерность векторного представления
|
||||||
},
|
},
|
||||||
"full_data": {
|
"full_data": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"enabled": False # Отключаем индексацию вложенных данных
|
"enabled": False # Отключаем индексацию вложенных данных
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
es.indices.create(index='drug_docs', body=mapping, ignore=400)
|
es.indices.create(index='drug_docs', body=mapping, ignore=400)
|
||||||
|
|
||||||
def load_drug_data(json_path):
|
def load_drug_data(json_path):
|
||||||
with open(json_path, 'r', encoding='utf-8') as f:
|
with open(json_path, 'r', encoding='utf-8') as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def index_documents(data):
|
def index_documents(data):
|
||||||
actions = []
|
actions = []
|
||||||
total_docs = len(data)
|
total_docs = len(data)
|
||||||
for i, item in enumerate(data, start=1):
|
for i, item in enumerate(data, start=1):
|
||||||
doc_text = f"{item['link']} {item.get('pribalovy_letak', '')} {item.get('spc', '')}"
|
doc_text = f"{item['link']} {item.get('pribalovy_letak', '')} {item.get('spc', '')}"
|
||||||
|
|
||||||
vector = embeddings.embed_query(doc_text)
|
vector = embeddings.embed_query(doc_text)
|
||||||
|
|
||||||
action = {
|
action = {
|
||||||
"_index": "drug_docs",
|
"_index": "drug_docs",
|
||||||
"_id": i,
|
"_id": i,
|
||||||
"_source": {
|
"_source": {
|
||||||
'text': doc_text,
|
'text': doc_text,
|
||||||
'vector': vector,
|
'vector': vector,
|
||||||
'full_data': item
|
'full_data': item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
actions.append(action)
|
actions.append(action)
|
||||||
|
|
||||||
# Отображение прогресса
|
# Отображение прогресса
|
||||||
print(f"Индексируется документ {i}/{total_docs}", end='\r')
|
print(f"Индексируется документ {i}/{total_docs}", end='\r')
|
||||||
|
|
||||||
# Опционально: индексируем пакетами по N документов
|
# Опционально: индексируем пакетами по N документов
|
||||||
if i % 100 == 0 or i == total_docs:
|
if i % 100 == 0 or i == total_docs:
|
||||||
bulk(es, actions)
|
bulk(es, actions)
|
||||||
actions = []
|
actions = []
|
||||||
|
|
||||||
# Если остались неиндексированные документы
|
# Если остались неиндексированные документы
|
||||||
if actions:
|
if actions:
|
||||||
bulk(es, actions)
|
bulk(es, actions)
|
||||||
|
|
||||||
print("\nИндексирование завершено.")
|
print("\nИндексирование завершено.")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
create_index()
|
create_index()
|
||||||
data_path = "../../esDB/cleaned_general_info_additional.json"
|
data_path = "../../esDB/cleaned_general_info_additional.json"
|
||||||
drug_data = load_drug_data(data_path)
|
drug_data = load_drug_data(data_path)
|
||||||
index_documents(drug_data)
|
index_documents(drug_data)
|
||||||
|
|
||||||
|
@ -1,80 +1,80 @@
|
|||||||
from elasticsearch import Elasticsearch
|
from elasticsearch import Elasticsearch
|
||||||
from langchain.embeddings import HuggingFaceEmbeddings
|
from langchain.embeddings import HuggingFaceEmbeddings
|
||||||
from elasticsearch.helpers import bulk
|
from elasticsearch.helpers import bulk
|
||||||
import json
|
import json
|
||||||
|
|
||||||
# Настройка подключения к Elasticsearch с аутентификацией и HTTPS
|
# Настройка подключения к Elasticsearch с аутентификацией и HTTPS
|
||||||
es = Elasticsearch(
|
es = Elasticsearch(
|
||||||
[{'host': 'localhost', 'port': 9200, 'scheme': 'https'}],
|
[{'host': 'localhost', 'port': 9200, 'scheme': 'https'}],
|
||||||
http_auth=('elastic', 'S7DoO3ma=G=9USBPbqq3'), # замените на ваш пароль
|
http_auth=('elastic', 'S7DoO3ma=G=9USBPbqq3'), # замените на ваш пароль
|
||||||
verify_certs=False # Отключить проверку SSL-сертификата, если используется самоподписанный сертификат
|
verify_certs=False # Отключить проверку SSL-сертификата, если используется самоподписанный сертификат
|
||||||
)
|
)
|
||||||
|
|
||||||
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
|
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
|
||||||
|
|
||||||
def create_index():
|
def create_index():
|
||||||
# Определяем маппинг для индекса
|
# Определяем маппинг для индекса
|
||||||
mapping = {
|
mapping = {
|
||||||
"mappings": {
|
"mappings": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"text": {
|
"text": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"analyzer": "standard"
|
"analyzer": "standard"
|
||||||
},
|
},
|
||||||
"vector": {
|
"vector": {
|
||||||
"type": "dense_vector",
|
"type": "dense_vector",
|
||||||
"dims": 384 # Размерность векторного представления
|
"dims": 384 # Размерность векторного представления
|
||||||
},
|
},
|
||||||
"full_data": {
|
"full_data": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"enabled": False # Отключаем индексацию вложенных данных
|
"enabled": False # Отключаем индексацию вложенных данных
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
es.indices.create(index='drug_docs', body=mapping, ignore=400)
|
es.indices.create(index='drug_docs', body=mapping, ignore=400)
|
||||||
|
|
||||||
def load_drug_data(json_path):
|
def load_drug_data(json_path):
|
||||||
with open(json_path, 'r', encoding='utf-8') as f:
|
with open(json_path, 'r', encoding='utf-8') as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def index_documents(data):
|
def index_documents(data):
|
||||||
actions = []
|
actions = []
|
||||||
total_docs = len(data)
|
total_docs = len(data)
|
||||||
for i, item in enumerate(data, start=1):
|
for i, item in enumerate(data, start=1):
|
||||||
doc_text = f"{item['link']} {item.get('pribalovy_letak', '')} {item.get('spc', '')}"
|
doc_text = f"{item['link']} {item.get('pribalovy_letak', '')} {item.get('spc', '')}"
|
||||||
|
|
||||||
vector = embeddings.embed_query(doc_text)
|
vector = embeddings.embed_query(doc_text)
|
||||||
|
|
||||||
action = {
|
action = {
|
||||||
"_index": "drug_docs",
|
"_index": "drug_docs",
|
||||||
"_id": i,
|
"_id": i,
|
||||||
"_source": {
|
"_source": {
|
||||||
'text': doc_text,
|
'text': doc_text,
|
||||||
'vector': vector,
|
'vector': vector,
|
||||||
'full_data': item
|
'full_data': item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
actions.append(action)
|
actions.append(action)
|
||||||
|
|
||||||
# Отображение прогресса
|
# Отображение прогресса
|
||||||
print(f"Индексируется документ {i}/{total_docs}", end='\r')
|
print(f"Индексируется документ {i}/{total_docs}", end='\r')
|
||||||
|
|
||||||
# Опционально: индексируем пакетами по N документов
|
# Опционально: индексируем пакетами по N документов
|
||||||
if i % 100 == 0 or i == total_docs:
|
if i % 100 == 0 or i == total_docs:
|
||||||
bulk(es, actions)
|
bulk(es, actions)
|
||||||
actions = []
|
actions = []
|
||||||
|
|
||||||
# Если остались неиндексированные документы
|
# Если остались неиндексированные документы
|
||||||
if actions:
|
if actions:
|
||||||
bulk(es, actions)
|
bulk(es, actions)
|
||||||
|
|
||||||
print("\nИндексирование завершено.")
|
print("\nИндексирование завершено.")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
create_index()
|
create_index()
|
||||||
data_path = "/home/poiasnik/esDB/cleaned_general_info_additional.json"
|
data_path = "/home/poiasnik/esDB/cleaned_general_info_additional.json"
|
||||||
drug_data = load_drug_data(data_path)
|
drug_data = load_drug_data(data_path)
|
||||||
index_documents(drug_data)
|
index_documents(drug_data)
|
||||||
|
|
||||||
|
@ -1,41 +1,41 @@
|
|||||||
from elasticsearch import Elasticsearch
|
from elasticsearch import Elasticsearch
|
||||||
from langchain_huggingface import HuggingFaceEmbeddings
|
from langchain_huggingface import HuggingFaceEmbeddings
|
||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
es = Elasticsearch(
|
es = Elasticsearch(
|
||||||
cloud_id="tt:dXMtZWFzdC0yLmF3cy5lbGFzdGljLWNsb3VkLmNvbTo0NDMkOGM3ODQ0ZWVhZTEyNGY3NmFjNjQyNDFhNjI4NmVhYzMkZTI3YjlkNTQ0ODdhNGViNmEyMTcxMjMxNmJhMWI0ZGU=",
|
cloud_id="tt:dXMtZWFzdC0yLmF3cy5lbGFzdGljLWNsb3VkLmNvbTo0NDMkOGM3ODQ0ZWVhZTEyNGY3NmFjNjQyNDFhNjI4NmVhYzMkZTI3YjlkNTQ0ODdhNGViNmEyMTcxMjMxNmJhMWI0ZGU=",
|
||||||
basic_auth=("elastic", "sSz2BEGv56JRNjGFwoQ191RJ")
|
basic_auth=("elastic", "sSz2BEGv56JRNjGFwoQ191RJ")
|
||||||
)
|
)
|
||||||
|
|
||||||
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
|
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
|
||||||
|
|
||||||
|
|
||||||
def load_drug_data(json_path):
|
def load_drug_data(json_path):
|
||||||
with open(json_path, 'r', encoding='utf-8') as f:
|
with open(json_path, 'r', encoding='utf-8') as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def index_documents(data):
|
def index_documents(data):
|
||||||
total_documents = len(data)
|
total_documents = len(data)
|
||||||
for i, item in enumerate(data, start=1):
|
for i, item in enumerate(data, start=1):
|
||||||
doc_text = f"{item['link']} {item.get('pribalovy_letak', '')} {item.get('spc', '')}"
|
doc_text = f"{item['link']} {item.get('pribalovy_letak', '')} {item.get('spc', '')}"
|
||||||
|
|
||||||
vector = embeddings.embed_query(doc_text)
|
vector = embeddings.embed_query(doc_text)
|
||||||
|
|
||||||
es.index(index='drug_docs', id=i, body={
|
es.index(index='drug_docs', id=i, body={
|
||||||
'text': doc_text,
|
'text': doc_text,
|
||||||
'vector': vector,
|
'vector': vector,
|
||||||
'full_data': item
|
'full_data': item
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
data_path = "../../data_adc_databaza/cleaned_general_info_additional.json"
|
data_path = "../../data_adc_databaza/cleaned_general_info_additional.json"
|
||||||
drug_data = load_drug_data(data_path)
|
drug_data = load_drug_data(data_path)
|
||||||
index_documents(drug_data)
|
index_documents(drug_data)
|
||||||
|
@ -1,70 +1,70 @@
|
|||||||
from elasticsearch import Elasticsearch
|
from elasticsearch import Elasticsearch
|
||||||
from langchain_huggingface import HuggingFaceEmbeddings
|
from langchain_huggingface import HuggingFaceEmbeddings
|
||||||
from elasticsearch.helpers import bulk
|
from elasticsearch.helpers import bulk
|
||||||
import json
|
import json
|
||||||
|
|
||||||
es = Elasticsearch([{'host': 'localhost', 'port': 9200, 'scheme': 'http'}])
|
es = Elasticsearch([{'host': 'localhost', 'port': 9200, 'scheme': 'http'}])
|
||||||
|
|
||||||
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
|
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
|
||||||
|
|
||||||
def create_index():
|
def create_index():
|
||||||
|
|
||||||
mapping = {
|
mapping = {
|
||||||
"mappings": {
|
"mappings": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"text": {
|
"text": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"analyzer": "standard"
|
"analyzer": "standard"
|
||||||
},
|
},
|
||||||
"vector": {
|
"vector": {
|
||||||
"type": "dense_vector",
|
"type": "dense_vector",
|
||||||
"dims": 384
|
"dims": 384
|
||||||
},
|
},
|
||||||
"full_data": {
|
"full_data": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"enabled": False
|
"enabled": False
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
es.indices.create(index='drug_docs', body=mapping, ignore=400)
|
es.indices.create(index='drug_docs', body=mapping, ignore=400)
|
||||||
|
|
||||||
def load_drug_data(json_path):
|
def load_drug_data(json_path):
|
||||||
with open(json_path, 'r', encoding='utf-8') as f:
|
with open(json_path, 'r', encoding='utf-8') as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def index_documents(data):
|
def index_documents(data):
|
||||||
actions = []
|
actions = []
|
||||||
total_docs = len(data)
|
total_docs = len(data)
|
||||||
for i, item in enumerate(data, start=1):
|
for i, item in enumerate(data, start=1):
|
||||||
doc_text = f"{item['link']} {item.get('pribalovy_letak', '')} {item.get('spc', '')}"
|
doc_text = f"{item['link']} {item.get('pribalovy_letak', '')} {item.get('spc', '')}"
|
||||||
|
|
||||||
vector = embeddings.embed_query(doc_text)
|
vector = embeddings.embed_query(doc_text)
|
||||||
|
|
||||||
action = {
|
action = {
|
||||||
"_index": "drug_docs",
|
"_index": "drug_docs",
|
||||||
"_id": i,
|
"_id": i,
|
||||||
"_source": {
|
"_source": {
|
||||||
'text': doc_text,
|
'text': doc_text,
|
||||||
'vector': vector,
|
'vector': vector,
|
||||||
'full_data': item
|
'full_data': item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
actions.append(action)
|
actions.append(action)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if i % 100 == 0 or i == total_docs:
|
if i % 100 == 0 or i == total_docs:
|
||||||
bulk(es, actions)
|
bulk(es, actions)
|
||||||
actions = []
|
actions = []
|
||||||
|
|
||||||
if actions:
|
if actions:
|
||||||
bulk(es, actions)
|
bulk(es, actions)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
create_index()
|
create_index()
|
||||||
data_path = "../../data_adc_databaza/cleaned_general_info_additional.json"
|
data_path = "../../data_adc_databaza/cleaned_general_info_additional.json"
|
||||||
drug_data = load_drug_data(data_path)
|
drug_data = load_drug_data(data_path)
|
||||||
index_documents(drug_data)
|
index_documents(drug_data)
|
||||||
|
1082
Backend/model.py
1082
Backend/model.py
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,12 @@
|
|||||||
Flask
|
Flask
|
||||||
flask-cors
|
flask-cors
|
||||||
requests
|
requests
|
||||||
elasticsearch
|
elasticsearch
|
||||||
langchain
|
langchain
|
||||||
langchain-huggingface
|
langchain-huggingface
|
||||||
langchain-elasticsearch
|
langchain-elasticsearch
|
||||||
psycopg2-binary
|
psycopg2-binary
|
||||||
google-auth
|
google-auth
|
||||||
transformers
|
transformers
|
||||||
sentence-transformers>=2.2.2
|
sentence-transformers>=2.2.2
|
||||||
accelerate
|
accelerate
|
||||||
|
@ -1,361 +1,366 @@
|
|||||||
import time
|
import time
|
||||||
import re
|
import re
|
||||||
from flask import Flask, request, jsonify
|
from flask import Flask, request, jsonify
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
from google.oauth2 import id_token
|
from google.oauth2 import id_token
|
||||||
from google.auth.transport import requests as google_requests
|
from google.auth.transport import requests as google_requests
|
||||||
import logging
|
import logging
|
||||||
import psycopg2
|
import psycopg2
|
||||||
from psycopg2.extras import RealDictCursor
|
from psycopg2.extras import RealDictCursor
|
||||||
from model import process_query_with_mistral
|
from model import process_query_with_mistral
|
||||||
|
|
||||||
_real_time = time.time
|
_real_time = time.time
|
||||||
time.time = lambda: _real_time() - 1
|
time.time = lambda: _real_time() - 1
|
||||||
|
|
||||||
# Database connection parameters
|
# Database connection parameters
|
||||||
DATABASE_CONFIG = {
|
DATABASE_CONFIG = {
|
||||||
"dbname": "HealthAIDB",
|
"dbname": "HealthAIDB",
|
||||||
"user": "postgres",
|
"user": "postgres",
|
||||||
"password": "Oleg2005",
|
"password": "Oleg2005",
|
||||||
"host": "postgres",
|
"host": "postgres",
|
||||||
"port": 5432,
|
"port": 5432,
|
||||||
}
|
}
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = psycopg2.connect(**DATABASE_CONFIG)
|
conn = psycopg2.connect(**DATABASE_CONFIG)
|
||||||
logger.info("Database connection established successfully")
|
logger.info("Database connection established successfully")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error connecting to database: {e}", exc_info=True)
|
logger.error(f"Error connecting to database: {e}", exc_info=True)
|
||||||
conn = None
|
conn = None
|
||||||
|
|
||||||
def init_db():
|
def init_db():
|
||||||
create_users_query = """
|
create_users_query = """
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
email TEXT UNIQUE NOT NULL,
|
email TEXT UNIQUE NOT NULL,
|
||||||
google_id TEXT,
|
google_id TEXT,
|
||||||
password TEXT
|
password TEXT,
|
||||||
);
|
phone TEXT,
|
||||||
"""
|
role TEXT,
|
||||||
create_chat_history_query = """
|
bio TEXT,
|
||||||
CREATE TABLE IF NOT EXISTS chat_history (
|
picture TEXT
|
||||||
id SERIAL PRIMARY KEY,
|
);
|
||||||
user_email TEXT NOT NULL,
|
|
||||||
chat TEXT NOT NULL,
|
"""
|
||||||
user_data TEXT,
|
create_chat_history_query = """
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
CREATE TABLE IF NOT EXISTS chat_history (
|
||||||
);
|
id SERIAL PRIMARY KEY,
|
||||||
"""
|
user_email TEXT NOT NULL,
|
||||||
try:
|
chat TEXT NOT NULL,
|
||||||
with conn.cursor() as cur:
|
user_data TEXT,
|
||||||
cur.execute(create_users_query)
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
cur.execute(create_chat_history_query)
|
);
|
||||||
conn.commit()
|
"""
|
||||||
logger.info("Database tables initialized successfully")
|
try:
|
||||||
except Exception as e:
|
with conn.cursor() as cur:
|
||||||
logger.error(f"Error initializing database tables: {e}", exc_info=True)
|
cur.execute(create_users_query)
|
||||||
conn.rollback()
|
cur.execute(create_chat_history_query)
|
||||||
|
conn.commit()
|
||||||
if conn:
|
logger.info("Database tables initialized successfully")
|
||||||
init_db()
|
except Exception as e:
|
||||||
|
logger.error(f"Error initializing database tables: {e}", exc_info=True)
|
||||||
app = Flask(__name__)
|
conn.rollback()
|
||||||
CORS(app, resources={r"/api/*": {"origins": "*"}})
|
|
||||||
|
if conn:
|
||||||
CLIENT_ID = "532143017111-4eqtlp0oejqaovj6rf5l1ergvhrp4vao.apps.googleusercontent.com"
|
init_db()
|
||||||
|
|
||||||
def save_user_to_db(name, email, google_id=None, password=None):
|
app = Flask(__name__)
|
||||||
logger.info(f"Saving user {name} with email: {email} to the database")
|
CORS(app, resources={r"/api/*": {"origins": "*"}})
|
||||||
try:
|
|
||||||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
CLIENT_ID = "532143017111-4eqtlp0oejqaovj6rf5l1ergvhrp4vao.apps.googleusercontent.com"
|
||||||
cur.execute(
|
|
||||||
"""
|
def save_user_to_db(name, email, google_id=None, password=None):
|
||||||
INSERT INTO users (name, email, google_id, password)
|
logger.info(f"Saving user {name} with email: {email} to the database")
|
||||||
VALUES (%s, %s, %s, %s)
|
try:
|
||||||
ON CONFLICT (email) DO NOTHING
|
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||||
""",
|
cur.execute(
|
||||||
(name, email, google_id, password)
|
"""
|
||||||
)
|
INSERT INTO users (name, email, google_id, password)
|
||||||
conn.commit()
|
VALUES (%s, %s, %s, %s)
|
||||||
logger.info(f"User {name} ({email}) saved successfully")
|
ON CONFLICT (email) DO NOTHING
|
||||||
except Exception as e:
|
""",
|
||||||
logger.error(f"Error saving user {name} ({email}) to database: {e}", exc_info=True)
|
(name, email, google_id, password)
|
||||||
|
)
|
||||||
@app.route('/api/verify', methods=['POST'])
|
conn.commit()
|
||||||
def verify_token():
|
logger.info(f"User {name} ({email}) saved successfully")
|
||||||
logger.info("Received token verification request")
|
except Exception as e:
|
||||||
data = request.get_json()
|
logger.error(f"Error saving user {name} ({email}) to database: {e}", exc_info=True)
|
||||||
token = data.get('token')
|
|
||||||
if not token:
|
@app.route('/api/verify', methods=['POST'])
|
||||||
logger.warning("Token not provided in request")
|
def verify_token():
|
||||||
return jsonify({'error': 'No token provided'}), 400
|
logger.info("Received token verification request")
|
||||||
try:
|
data = request.get_json()
|
||||||
id_info = id_token.verify_oauth2_token(token, google_requests.Request(), CLIENT_ID)
|
token = data.get('token')
|
||||||
user_email = id_info.get('email')
|
if not token:
|
||||||
user_name = id_info.get('name')
|
logger.warning("Token not provided in request")
|
||||||
google_id = id_info.get('sub')
|
return jsonify({'error': 'No token provided'}), 400
|
||||||
logger.info(f"Token verified for user: {user_name} ({user_email})")
|
try:
|
||||||
save_user_to_db(name=user_name, email=user_email, google_id=google_id)
|
id_info = id_token.verify_oauth2_token(token, google_requests.Request(), CLIENT_ID)
|
||||||
return jsonify({'message': 'Authentication successful', 'user': {'email': user_email, 'name': user_name}}), 200
|
user_email = id_info.get('email')
|
||||||
except ValueError as e:
|
user_name = id_info.get('name')
|
||||||
logger.error(f"Token verification error: {e}", exc_info=True)
|
google_id = id_info.get('sub')
|
||||||
return jsonify({'error': 'Invalid token'}), 400
|
logger.info(f"Token verified for user: {user_name} ({user_email})")
|
||||||
|
save_user_to_db(name=user_name, email=user_email, google_id=google_id)
|
||||||
@app.route('/api/register', methods=['POST'])
|
return jsonify({'message': 'Authentication successful', 'user': {'email': user_email, 'name': user_name}}), 200
|
||||||
def register():
|
except ValueError as e:
|
||||||
logger.info("Received new user registration request")
|
logger.error(f"Token verification error: {e}", exc_info=True)
|
||||||
data = request.get_json()
|
return jsonify({'error': 'Invalid token'}), 400
|
||||||
name = data.get('name')
|
|
||||||
email = data.get('email')
|
@app.route('/api/register', methods=['POST'])
|
||||||
password = data.get('password')
|
def register():
|
||||||
if not all([name, email, password]):
|
logger.info("Received new user registration request")
|
||||||
logger.warning("Not all required fields provided for registration")
|
data = request.get_json()
|
||||||
return jsonify({'error': 'All fields are required'}), 400
|
name = data.get('name')
|
||||||
try:
|
email = data.get('email')
|
||||||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
password = data.get('password')
|
||||||
cur.execute("SELECT * FROM users WHERE email = %s", (email,))
|
if not all([name, email, password]):
|
||||||
existing_user = cur.fetchone()
|
logger.warning("Not all required fields provided for registration")
|
||||||
if existing_user:
|
return jsonify({'error': 'All fields are required'}), 400
|
||||||
logger.warning(f"User with email {email} already exists")
|
try:
|
||||||
return jsonify({'error': 'User already exists'}), 409
|
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||||
save_user_to_db(name=name, email=email, password=password)
|
cur.execute("SELECT * FROM users WHERE email = %s", (email,))
|
||||||
logger.info(f"User {name} ({email}) registered successfully")
|
existing_user = cur.fetchone()
|
||||||
return jsonify({'message': 'User registered successfully'}), 201
|
if existing_user:
|
||||||
except Exception as e:
|
logger.warning(f"User with email {email} already exists")
|
||||||
logger.error(f"Error during user registration: {e}", exc_info=True)
|
return jsonify({'error': 'User already exists'}), 409
|
||||||
return jsonify({'error': str(e)}), 500
|
save_user_to_db(name=name, email=email, password=password)
|
||||||
|
logger.info(f"User {name} ({email}) registered successfully")
|
||||||
@app.route('/api/login', methods=['POST'])
|
return jsonify({'message': 'User registered successfully'}), 201
|
||||||
def login():
|
except Exception as e:
|
||||||
logger.info("Received login request")
|
logger.error(f"Error during user registration: {e}", exc_info=True)
|
||||||
data = request.get_json()
|
return jsonify({'error': str(e)}), 500
|
||||||
email = data.get('email')
|
|
||||||
password = data.get('password')
|
@app.route('/api/login', methods=['POST'])
|
||||||
if not all([email, password]):
|
def login():
|
||||||
logger.warning("Email or password not provided")
|
logger.info("Received login request")
|
||||||
return jsonify({'error': 'Email and password are required'}), 400
|
data = request.get_json()
|
||||||
try:
|
email = data.get('email')
|
||||||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
password = data.get('password')
|
||||||
cur.execute("SELECT * FROM users WHERE email = %s", (email,))
|
if not all([email, password]):
|
||||||
user = cur.fetchone()
|
logger.warning("Email or password not provided")
|
||||||
if not user or user.get('password') != password:
|
return jsonify({'error': 'Email and password are required'}), 400
|
||||||
logger.warning(f"Invalid credentials for email: {email}")
|
try:
|
||||||
return jsonify({'error': 'Invalid credentials'}), 401
|
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||||
logger.info(f"User {user.get('name')} ({email}) logged in successfully")
|
cur.execute("SELECT * FROM users WHERE email = %s", (email,))
|
||||||
return jsonify({'message': 'Login successful', 'user': {'name': user.get('name'), 'email': user.get('email')}}), 200
|
user = cur.fetchone()
|
||||||
except Exception as e:
|
if not user or user.get('password') != password:
|
||||||
logger.error(f"Error during user login: {e}", exc_info=True)
|
logger.warning(f"Invalid credentials for email: {email}")
|
||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': 'Invalid credentials'}), 401
|
||||||
|
logger.info(f"User {user.get('name')} ({email}) logged in successfully")
|
||||||
|
return jsonify({'message': 'Login successful', 'user': {'name': user.get('name'), 'email': user.get('email')}}), 200
|
||||||
@app.route('/api/update_profile', methods=['PUT'])
|
except Exception as e:
|
||||||
def update_profile():
|
logger.error(f"Error during user login: {e}", exc_info=True)
|
||||||
data = request.get_json()
|
return jsonify({'error': str(e)}), 500
|
||||||
email = data.get('email')
|
|
||||||
if not email:
|
|
||||||
return jsonify({'error': 'Email is required'}), 400
|
@app.route('/api/update_profile', methods=['PUT'])
|
||||||
|
def update_profile():
|
||||||
# Fields to update; if not provided, the current value remains.
|
data = request.get_json()
|
||||||
name = data.get('name')
|
email = data.get('email')
|
||||||
phone = data.get('phone')
|
if not email:
|
||||||
role = data.get('role')
|
return jsonify({'error': 'Email is required'}), 400
|
||||||
bio = data.get('bio')
|
|
||||||
picture = data.get('picture')
|
# Fields to update; if not provided, the current value remains.
|
||||||
|
name = data.get('name')
|
||||||
try:
|
phone = data.get('phone')
|
||||||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
role = data.get('role')
|
||||||
cur.execute(
|
bio = data.get('bio')
|
||||||
"""
|
picture = data.get('picture')
|
||||||
UPDATE users
|
|
||||||
SET name = COALESCE(%s, name),
|
try:
|
||||||
phone = COALESCE(%s, phone),
|
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||||
role = COALESCE(%s, role),
|
cur.execute(
|
||||||
bio = COALESCE(%s, bio),
|
"""
|
||||||
picture = COALESCE(%s, picture)
|
UPDATE users
|
||||||
WHERE email = %s
|
SET name = COALESCE(%s, name),
|
||||||
""",
|
phone = COALESCE(%s, phone),
|
||||||
(name, phone, role, bio, picture, email)
|
role = COALESCE(%s, role),
|
||||||
)
|
bio = COALESCE(%s, bio),
|
||||||
conn.commit()
|
picture = COALESCE(%s, picture)
|
||||||
logger.info(f"Profile updated for {email}")
|
WHERE email = %s
|
||||||
return jsonify({'message': 'Profile updated successfully'}), 200
|
""",
|
||||||
except Exception as e:
|
(name, phone, role, bio, picture, email)
|
||||||
logger.error(f"Error updating profile: {e}")
|
)
|
||||||
return jsonify({'error': str(e)}), 500
|
conn.commit()
|
||||||
|
logger.info(f"Profile updated for {email}")
|
||||||
@app.route('/api/chat', methods=['POST'])
|
return jsonify({'message': 'Profile updated successfully'}), 200
|
||||||
def chat():
|
except Exception as e:
|
||||||
logger.info("Received chat request")
|
logger.error(f"Error updating profile: {e}")
|
||||||
data = request.get_json()
|
return jsonify({'error': str(e)}), 500
|
||||||
query = data.get('query', '')
|
|
||||||
user_email = data.get('email')
|
@app.route('/api/chat', methods=['POST'])
|
||||||
chat_id = data.get('chatId')
|
def chat():
|
||||||
|
logger.info("Received chat request")
|
||||||
if not query:
|
data = request.get_json()
|
||||||
logger.warning("No query provided")
|
query = data.get('query', '')
|
||||||
return jsonify({'error': 'No query provided'}), 400
|
user_email = data.get('email')
|
||||||
|
chat_id = data.get('chatId')
|
||||||
logger.info(f"Processing request for chatId: {chat_id if chat_id else 'new chat'} | Query: {query}")
|
|
||||||
|
if not query:
|
||||||
# Retrieve chat context from the database
|
logger.warning("No query provided")
|
||||||
chat_context = ""
|
return jsonify({'error': 'No query provided'}), 400
|
||||||
if chat_id:
|
|
||||||
try:
|
logger.info(f"Processing request for chatId: {chat_id if chat_id else 'new chat'} | Query: {query}")
|
||||||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
|
||||||
cur.execute("SELECT chat, user_data FROM chat_history WHERE id = %s", (chat_id,))
|
# Retrieve chat context from the database
|
||||||
result = cur.fetchone()
|
chat_context = ""
|
||||||
if result:
|
if chat_id:
|
||||||
chat_context = result.get("chat", "")
|
try:
|
||||||
logger.info(f"Loaded chat context for chatId {chat_id}: {chat_context}")
|
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||||
else:
|
cur.execute("SELECT chat, user_data FROM chat_history WHERE id = %s", (chat_id,))
|
||||||
logger.info(f"No chat context found for chatId {chat_id}")
|
result = cur.fetchone()
|
||||||
except Exception as e:
|
if result:
|
||||||
logger.error(f"Error loading chat context from DB: {e}", exc_info=True)
|
chat_context = result.get("chat", "")
|
||||||
|
logger.info(f"Loaded chat context for chatId {chat_id}: {chat_context}")
|
||||||
logger.info("Calling process_query_with_mistral function")
|
else:
|
||||||
response_obj = process_query_with_mistral(query, chat_id=chat_id, chat_context=chat_context)
|
logger.info(f"No chat context found for chatId {chat_id}")
|
||||||
best_answer = response_obj.get("best_answer", "") if isinstance(response_obj, dict) else str(response_obj)
|
except Exception as e:
|
||||||
logger.info(f"Response from process_query_with_mistral: {best_answer}")
|
logger.error(f"Error loading chat context from DB: {e}", exc_info=True)
|
||||||
|
|
||||||
best_answer = re.sub(r'[*#]', '', best_answer)
|
logger.info("Calling process_query_with_mistral function")
|
||||||
best_answer = re.sub(r'(\d\.\s)', r'\n\n\1', best_answer)
|
response_obj = process_query_with_mistral(query, chat_id=chat_id, chat_context=chat_context)
|
||||||
best_answer = re.sub(r':\s-', r':\n-', best_answer)
|
best_answer = response_obj.get("best_answer", "") if isinstance(response_obj, dict) else str(response_obj)
|
||||||
|
logger.info(f"Response from process_query_with_mistral: {best_answer}")
|
||||||
# Update or create chat_history record including user_data if available
|
|
||||||
if chat_id:
|
best_answer = re.sub(r'[*#]', '', best_answer)
|
||||||
try:
|
best_answer = re.sub(r'(\d\.\s)', r'\n\n\1', best_answer)
|
||||||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
best_answer = re.sub(r':\s-', r':\n-', best_answer)
|
||||||
cur.execute("SELECT chat FROM chat_history WHERE id = %s", (chat_id,))
|
|
||||||
existing_chat = cur.fetchone()
|
# Update or create chat_history record including user_data if available
|
||||||
if existing_chat:
|
if chat_id:
|
||||||
updated_chat = existing_chat['chat'] + f"\nUser: {query}\nBot: {best_answer}"
|
try:
|
||||||
if "patient_data" in response_obj:
|
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||||
cur.execute("UPDATE chat_history SET chat = %s, user_data = %s WHERE id = %s",
|
cur.execute("SELECT chat FROM chat_history WHERE id = %s", (chat_id,))
|
||||||
(updated_chat, response_obj["patient_data"], chat_id))
|
existing_chat = cur.fetchone()
|
||||||
else:
|
if existing_chat:
|
||||||
cur.execute("UPDATE chat_history SET chat = %s WHERE id = %s", (updated_chat, chat_id))
|
updated_chat = existing_chat['chat'] + f"\nUser: {query}\nBot: {best_answer}"
|
||||||
conn.commit()
|
if "patient_data" in response_obj:
|
||||||
logger.info(f"Chat history for chatId {chat_id} updated successfully")
|
cur.execute("UPDATE chat_history SET chat = %s, user_data = %s WHERE id = %s",
|
||||||
else:
|
(updated_chat, response_obj["patient_data"], chat_id))
|
||||||
with conn.cursor(cursor_factory=RealDictCursor) as cur2:
|
else:
|
||||||
cur2.execute(
|
cur.execute("UPDATE chat_history SET chat = %s WHERE id = %s", (updated_chat, chat_id))
|
||||||
"INSERT INTO chat_history (user_email, chat) VALUES (%s, %s) RETURNING id",
|
conn.commit()
|
||||||
(user_email, f"User: {query}\nBot: {best_answer}")
|
logger.info(f"Chat history for chatId {chat_id} updated successfully")
|
||||||
)
|
else:
|
||||||
new_chat_id = cur2.fetchone()['id']
|
with conn.cursor(cursor_factory=RealDictCursor) as cur2:
|
||||||
conn.commit()
|
cur2.execute(
|
||||||
chat_id = new_chat_id
|
"INSERT INTO chat_history (user_email, chat) VALUES (%s, %s) RETURNING id",
|
||||||
logger.info(f"New chat created with chatId: {chat_id}")
|
(user_email, f"User: {query}\nBot: {best_answer}")
|
||||||
except Exception as e:
|
)
|
||||||
logger.error(f"Error updating/creating chat history: {e}", exc_info=True)
|
new_chat_id = cur2.fetchone()['id']
|
||||||
return jsonify({'error': str(e)}), 500
|
conn.commit()
|
||||||
else:
|
chat_id = new_chat_id
|
||||||
try:
|
logger.info(f"New chat created with chatId: {chat_id}")
|
||||||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
except Exception as e:
|
||||||
cur.execute(
|
logger.error(f"Error updating/creating chat history: {e}", exc_info=True)
|
||||||
"INSERT INTO chat_history (user_email, chat) VALUES (%s, %s) RETURNING id",
|
return jsonify({'error': str(e)}), 500
|
||||||
(user_email, f"User: {query}\nBot: {best_answer}")
|
else:
|
||||||
)
|
try:
|
||||||
new_chat_id = cur.fetchone()['id']
|
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||||
conn.commit()
|
cur.execute(
|
||||||
chat_id = new_chat_id
|
"INSERT INTO chat_history (user_email, chat) VALUES (%s, %s) RETURNING id",
|
||||||
logger.info(f"New chat created with chatId: {chat_id}")
|
(user_email, f"User: {query}\nBot: {best_answer}")
|
||||||
except Exception as e:
|
)
|
||||||
logger.error(f"Error creating new chat: {e}", exc_info=True)
|
new_chat_id = cur.fetchone()['id']
|
||||||
return jsonify({'error': str(e)}), 500
|
conn.commit()
|
||||||
|
chat_id = new_chat_id
|
||||||
return jsonify({'response': {'best_answer': best_answer, 'model': response_obj.get("model", ""), 'chatId': chat_id}}), 200
|
logger.info(f"New chat created with chatId: {chat_id}")
|
||||||
|
except Exception as e:
|
||||||
@app.route('/api/save_user_data', methods=['POST'])
|
logger.error(f"Error creating new chat: {e}", exc_info=True)
|
||||||
def save_user_data():
|
return jsonify({'error': str(e)}), 500
|
||||||
logger.info("Received request to save user data")
|
|
||||||
data = request.get_json()
|
return jsonify({'response': {'best_answer': best_answer, 'model': response_obj.get("model", ""), 'chatId': chat_id}}), 200
|
||||||
chat_id = data.get('chatId')
|
|
||||||
user_data = data.get('userData')
|
@app.route('/api/save_user_data', methods=['POST'])
|
||||||
if not chat_id or not user_data:
|
def save_user_data():
|
||||||
return jsonify({'error': 'chatId and userData are required'}), 400
|
logger.info("Received request to save user data")
|
||||||
try:
|
data = request.get_json()
|
||||||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
chat_id = data.get('chatId')
|
||||||
cur.execute("UPDATE chat_history SET user_data = %s WHERE id = %s", (user_data, chat_id))
|
user_data = data.get('userData')
|
||||||
conn.commit()
|
if not chat_id or not user_data:
|
||||||
logger.info(f"User data for chatId {chat_id} updated successfully")
|
return jsonify({'error': 'chatId and userData are required'}), 400
|
||||||
return jsonify({'message': 'User data updated successfully'}), 200
|
try:
|
||||||
except Exception as e:
|
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||||
logger.error(f"Error updating user data: {e}", exc_info=True)
|
cur.execute("UPDATE chat_history SET user_data = %s WHERE id = %s", (user_data, chat_id))
|
||||||
return jsonify({'error': str(e)}), 500
|
conn.commit()
|
||||||
|
logger.info(f"User data for chatId {chat_id} updated successfully")
|
||||||
@app.route('/api/chat_history', methods=['GET'])
|
return jsonify({'message': 'User data updated successfully'}), 200
|
||||||
def get_chat_history():
|
except Exception as e:
|
||||||
logger.info("Received request to get chat history")
|
logger.error(f"Error updating user data: {e}", exc_info=True)
|
||||||
user_email = request.args.get('email')
|
return jsonify({'error': str(e)}), 500
|
||||||
if not user_email:
|
|
||||||
return jsonify({'error': 'User email is required'}), 400
|
@app.route('/api/chat_history', methods=['GET'])
|
||||||
try:
|
def get_chat_history():
|
||||||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
logger.info("Received request to get chat history")
|
||||||
cur.execute(
|
user_email = request.args.get('email')
|
||||||
"SELECT id, chat, user_data, created_at FROM chat_history WHERE user_email = %s ORDER BY created_at DESC",
|
if not user_email:
|
||||||
(user_email,)
|
return jsonify({'error': 'User email is required'}), 400
|
||||||
)
|
try:
|
||||||
history = cur.fetchall()
|
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||||
return jsonify({'history': history}), 200
|
cur.execute(
|
||||||
except Exception as e:
|
"SELECT id, chat, user_data, created_at FROM chat_history WHERE user_email = %s ORDER BY created_at DESC",
|
||||||
logger.error(f"Error getting chat history for {user_email}: {e}", exc_info=True)
|
(user_email,)
|
||||||
return jsonify({'error': str(e)}), 500
|
)
|
||||||
|
history = cur.fetchall()
|
||||||
@app.route('/api/chat_history', methods=['DELETE'])
|
return jsonify({'history': history}), 200
|
||||||
def delete_chat():
|
except Exception as e:
|
||||||
logger.info("Received request to delete chat")
|
logger.error(f"Error getting chat history for {user_email}: {e}", exc_info=True)
|
||||||
chat_id = request.args.get('id')
|
return jsonify({'error': str(e)}), 500
|
||||||
if not chat_id:
|
|
||||||
return jsonify({'error': 'Chat id is required'}), 400
|
@app.route('/api/chat_history', methods=['DELETE'])
|
||||||
try:
|
def delete_chat():
|
||||||
with conn.cursor() as cur:
|
logger.info("Received request to delete chat")
|
||||||
cur.execute("DELETE FROM chat_history WHERE id = %s", (chat_id,))
|
chat_id = request.args.get('id')
|
||||||
conn.commit()
|
if not chat_id:
|
||||||
return jsonify({'message': 'Chat deleted successfully'}), 200
|
return jsonify({'error': 'Chat id is required'}), 400
|
||||||
except Exception as e:
|
try:
|
||||||
logger.error(f"Error deleting chat with chatId {chat_id}: {e}", exc_info=True)
|
with conn.cursor() as cur:
|
||||||
return jsonify({'error': str(e)}), 500
|
cur.execute("DELETE FROM chat_history WHERE id = %s", (chat_id,))
|
||||||
|
conn.commit()
|
||||||
@app.route('/api/get_user_data', methods=['GET'])
|
return jsonify({'message': 'Chat deleted successfully'}), 200
|
||||||
def get_user_data():
|
except Exception as e:
|
||||||
chat_id = request.args.get('chatId')
|
logger.error(f"Error deleting chat with chatId {chat_id}: {e}", exc_info=True)
|
||||||
if not chat_id:
|
return jsonify({'error': str(e)}), 500
|
||||||
return jsonify({'error': 'Chat id is required'}), 400
|
|
||||||
try:
|
@app.route('/api/get_user_data', methods=['GET'])
|
||||||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
def get_user_data():
|
||||||
cur.execute("SELECT user_data FROM chat_history WHERE id = %s", (chat_id,))
|
chat_id = request.args.get('chatId')
|
||||||
result = cur.fetchone()
|
if not chat_id:
|
||||||
if result and result.get("user_data"):
|
return jsonify({'error': 'Chat id is required'}), 400
|
||||||
return jsonify({'user_data': result.get("user_data")}), 200
|
try:
|
||||||
else:
|
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||||
return jsonify({'user_data': None}), 200
|
cur.execute("SELECT user_data FROM chat_history WHERE id = %s", (chat_id,))
|
||||||
except Exception as e:
|
result = cur.fetchone()
|
||||||
logger.error(f"Error retrieving user data: {e}", exc_info=True)
|
if result and result.get("user_data"):
|
||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'user_data': result.get("user_data")}), 200
|
||||||
|
else:
|
||||||
@app.route('/api/chat_history_detail', methods=['GET'])
|
return jsonify({'user_data': None}), 200
|
||||||
def chat_history_detail():
|
except Exception as e:
|
||||||
logger.info("Received request to get chat details")
|
logger.error(f"Error retrieving user data: {e}", exc_info=True)
|
||||||
chat_id = request.args.get('id')
|
return jsonify({'error': str(e)}), 500
|
||||||
if not chat_id:
|
|
||||||
return jsonify({'error': 'Chat id is required'}), 400
|
@app.route('/api/chat_history_detail', methods=['GET'])
|
||||||
try:
|
def chat_history_detail():
|
||||||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
logger.info("Received request to get chat details")
|
||||||
cur.execute("SELECT id, chat, user_data, created_at FROM chat_history WHERE id = %s", (chat_id,))
|
chat_id = request.args.get('id')
|
||||||
chat = cur.fetchone()
|
if not chat_id:
|
||||||
if not chat:
|
return jsonify({'error': 'Chat id is required'}), 400
|
||||||
return jsonify({'error': 'Chat not found'}), 404
|
try:
|
||||||
return jsonify({'chat': chat}), 200
|
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||||
except Exception as e:
|
cur.execute("SELECT id, chat, user_data, created_at FROM chat_history WHERE id = %s", (chat_id,))
|
||||||
logger.error(f"Error getting chat details for chatId {chat_id}: {e}", exc_info=True)
|
chat = cur.fetchone()
|
||||||
return jsonify({'error': str(e)}), 500
|
if not chat:
|
||||||
|
return jsonify({'error': 'Chat not found'}), 404
|
||||||
if __name__ == '__main__':
|
return jsonify({'chat': chat}), 200
|
||||||
logger.info("Starting Flask application")
|
except Exception as e:
|
||||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
logger.error(f"Error getting chat details for chatId {chat_id}: {e}", exc_info=True)
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
logger.info("Starting Flask application")
|
||||||
|
app.run(host='0.0.0.0', port=5000, debug=True)
|
||||||
|
@ -1,56 +1,56 @@
|
|||||||
from pptx import Presentation
|
from pptx import Presentation
|
||||||
from pptx.util import Inches
|
from pptx.util import Inches
|
||||||
|
|
||||||
# Создание новой презентации
|
# Создание новой презентации
|
||||||
prs = Presentation()
|
prs = Presentation()
|
||||||
|
|
||||||
# Добавляем пустой слайд (layout с индексом 5 обычно является пустым)
|
# Добавляем пустой слайд (layout с индексом 5 обычно является пустым)
|
||||||
slide_layout = prs.slide_layouts[5]
|
slide_layout = prs.slide_layouts[5]
|
||||||
slide = prs.slides.add_slide(slide_layout)
|
slide = prs.slides.add_slide(slide_layout)
|
||||||
|
|
||||||
# Определяем позицию и размер таблицы
|
# Определяем позицию и размер таблицы
|
||||||
left = Inches(0.5)
|
left = Inches(0.5)
|
||||||
top = Inches(1.5)
|
top = Inches(1.5)
|
||||||
width = Inches(9)
|
width = Inches(9)
|
||||||
height = Inches(3)
|
height = Inches(3)
|
||||||
|
|
||||||
# Количество строк: 1 заголовок + 2 строки с данными
|
# Количество строк: 1 заголовок + 2 строки с данными
|
||||||
rows = 3
|
rows = 3
|
||||||
cols = 4
|
cols = 4
|
||||||
|
|
||||||
# Добавляем таблицу на слайд
|
# Добавляем таблицу на слайд
|
||||||
table = slide.shapes.add_table(rows, cols, left, top, width, height).table
|
table = slide.shapes.add_table(rows, cols, left, top, width, height).table
|
||||||
|
|
||||||
# Устанавливаем ширину столбцов (при необходимости можно настроить отдельно)
|
# Устанавливаем ширину столбцов (при необходимости можно настроить отдельно)
|
||||||
table.columns[0].width = Inches(1.5) # Модель
|
table.columns[0].width = Inches(1.5) # Модель
|
||||||
table.columns[1].width = Inches(1) # Оценка
|
table.columns[1].width = Inches(1) # Оценка
|
||||||
table.columns[2].width = Inches(4) # Текст
|
table.columns[2].width = Inches(4) # Текст
|
||||||
table.columns[3].width = Inches(2.5) # Описание
|
table.columns[3].width = Inches(2.5) # Описание
|
||||||
|
|
||||||
# Заполняем заголовки
|
# Заполняем заголовки
|
||||||
table.cell(0, 0).text = "Модель"
|
table.cell(0, 0).text = "Модель"
|
||||||
table.cell(0, 1).text = "Оценка"
|
table.cell(0, 1).text = "Оценка"
|
||||||
table.cell(0, 2).text = "Текст"
|
table.cell(0, 2).text = "Текст"
|
||||||
table.cell(0, 3).text = "Описание"
|
table.cell(0, 3).text = "Описание"
|
||||||
|
|
||||||
# Данные для первого кандидата
|
# Данные для первого кандидата
|
||||||
table.cell(1, 0).text = "Mistral Small"
|
table.cell(1, 0).text = "Mistral Small"
|
||||||
table.cell(1, 1).text = "9.0"
|
table.cell(1, 1).text = "9.0"
|
||||||
table.cell(1, 2).text = (
|
table.cell(1, 2).text = (
|
||||||
"Nevolnosť môže byť spôsobená rôznymi príčinami, ako sú napríklad gastrointestinálne problémy, infekcie, alebo vedľajšie účinky liekov. "
|
"Nevolnosť môže byť spôsobená rôznymi príčinami, ako sú napríklad gastrointestinálne problémy, infekcie, alebo vedľajšie účinky liekov. "
|
||||||
"Pre ľudí, ktorí hľadajú voľnopredajný liek na nevolnosť, sú dostupné niekoľko možností: 1. Dimedrol (Dramin) – Antihistaminikum; "
|
"Pre ľudí, ktorí hľadajú voľnopredajný liek na nevolnosť, sú dostupné niekoľko možností: 1. Dimedrol (Dramin) – Antihistaminikum; "
|
||||||
"2. Bismut subsalicylát (Pepto-Bismol); 3. Ginger (Zázvor); 4. Meclizin (Bonine). Pred použitím lieku je dôležité konzultovať s lekárom."
|
"2. Bismut subsalicylát (Pepto-Bismol); 3. Ginger (Zázvor); 4. Meclizin (Bonine). Pred použitím lieku je dôležité konzultovať s lekárom."
|
||||||
)
|
)
|
||||||
table.cell(1, 3).text = "Evaluation based on required criteria."
|
table.cell(1, 3).text = "Evaluation based on required criteria."
|
||||||
|
|
||||||
# Данные для второго кандидата
|
# Данные для второго кандидата
|
||||||
table.cell(2, 0).text = "Mistral Large"
|
table.cell(2, 0).text = "Mistral Large"
|
||||||
table.cell(2, 1).text = "8.0"
|
table.cell(2, 1).text = "8.0"
|
||||||
table.cell(2, 2).text = (
|
table.cell(2, 2).text = (
|
||||||
"Pre nevolnosť sa dajú použiť niektoré voľne predávané lieky, ale dôležité je poradiť sa s lekárom, najmä ak má pacient 20 rokov. "
|
"Pre nevolnosť sa dajú použiť niektoré voľne predávané lieky, ale dôležité je poradiť sa s lekárom, najmä ak má pacient 20 rokov. "
|
||||||
"Medzi bežné voľne predávané lieky patria: 1. Dimenhydrinát; 2. Meclozín. Tieto lieky môžu spôsobiť spánkovosť a dávkovanie je nutné konzultovať."
|
"Medzi bežné voľne predávané lieky patria: 1. Dimenhydrinát; 2. Meclozín. Tieto lieky môžu spôsobiť spánkovosť a dávkovanie je nutné konzultovať."
|
||||||
)
|
)
|
||||||
table.cell(2, 3).text = "Evaluation based on required criteria."
|
table.cell(2, 3).text = "Evaluation based on required criteria."
|
||||||
|
|
||||||
# Сохраняем презентацию в файл
|
# Сохраняем презентацию в файл
|
||||||
prs.save("evaluation_table.pptx")
|
prs.save("evaluation_table.pptx")
|
||||||
|
@ -1,34 +1,34 @@
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
|
||||||
API_TOKEN = "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"
|
API_TOKEN = "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"
|
||||||
API_URL = "https://api-inference.huggingface.co/models/google/flan-t5-large"
|
API_URL = "https://api-inference.huggingface.co/models/google/flan-t5-large"
|
||||||
|
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": f"Bearer {API_TOKEN}",
|
"Authorization": f"Bearer {API_TOKEN}",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
}
|
}
|
||||||
|
|
||||||
def query_flan_t5(prompt):
|
def query_flan_t5(prompt):
|
||||||
payload = {
|
payload = {
|
||||||
"inputs": prompt,
|
"inputs": prompt,
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"max_length": 250,
|
"max_length": 250,
|
||||||
"do_sample": True,
|
"do_sample": True,
|
||||||
"temperature": 0.9,
|
"temperature": 0.9,
|
||||||
"top_p": 0.95,
|
"top_p": 0.95,
|
||||||
"top_k": 50
|
"top_k": 50
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
response = requests.post(API_URL, headers=headers, json=payload)
|
response = requests.post(API_URL, headers=headers, json=payload)
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
|
|
||||||
prompt = "Ako sa máš? Daj odpoved v slovencine"
|
prompt = "Ako sa máš? Daj odpoved v slovencine"
|
||||||
result = query_flan_t5(prompt)
|
result = query_flan_t5(prompt)
|
||||||
|
|
||||||
if isinstance(result, list) and len(result) > 0:
|
if isinstance(result, list) and len(result) > 0:
|
||||||
print("Ответ от Flan-T5:", result[0]['generated_text'])
|
print("Ответ от Flan-T5:", result[0]['generated_text'])
|
||||||
else:
|
else:
|
||||||
print("Ошибка при получении ответа:", result)
|
print("Ошибка при получении ответа:", result)
|
@ -1,47 +1,47 @@
|
|||||||
# import requests
|
# import requests
|
||||||
#
|
#
|
||||||
# API_TOKEN = "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"
|
# API_TOKEN = "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"
|
||||||
# API_URL = "https://api-inference.huggingface.co/models/google/mt5-base"
|
# API_URL = "https://api-inference.huggingface.co/models/google/mt5-base"
|
||||||
#
|
#
|
||||||
# headers = {
|
# headers = {
|
||||||
# "Authorization": f"Bearer {API_TOKEN}",
|
# "Authorization": f"Bearer {API_TOKEN}",
|
||||||
# "Content-Type": "application/json"
|
# "Content-Type": "application/json"
|
||||||
# }
|
# }
|
||||||
#
|
#
|
||||||
# def query_mT5(prompt):
|
# def query_mT5(prompt):
|
||||||
# payload = {
|
# payload = {
|
||||||
# "inputs": prompt,
|
# "inputs": prompt,
|
||||||
# "parameters": {
|
# "parameters": {
|
||||||
# "max_length": 100,
|
# "max_length": 100,
|
||||||
# "do_sample": True,
|
# "do_sample": True,
|
||||||
# "temperature": 0.7
|
# "temperature": 0.7
|
||||||
# }
|
# }
|
||||||
# }
|
# }
|
||||||
# response = requests.post(API_URL, headers=headers, json=payload)
|
# response = requests.post(API_URL, headers=headers, json=payload)
|
||||||
# return response.json()
|
# return response.json()
|
||||||
#
|
#
|
||||||
# # Пример использования
|
# # Пример использования
|
||||||
# result = query_mT5("Aké sú účinné lieky na horúčku?")
|
# result = query_mT5("Aké sú účinné lieky na horúčku?")
|
||||||
# print("Ответ от mT5:", result)
|
# print("Ответ от mT5:", result)
|
||||||
|
|
||||||
from transformers import AutoTokenizer, MT5ForConditionalGeneration
|
from transformers import AutoTokenizer, MT5ForConditionalGeneration
|
||||||
|
|
||||||
tokenizer = AutoTokenizer.from_pretrained("google/mt5-small")
|
tokenizer = AutoTokenizer.from_pretrained("google/mt5-small")
|
||||||
model = MT5ForConditionalGeneration.from_pretrained("google/mt5-small")
|
model = MT5ForConditionalGeneration.from_pretrained("google/mt5-small")
|
||||||
|
|
||||||
# training
|
# training
|
||||||
input_ids = tokenizer("The <extra_id_0> walks in <extra_id_1> park", return_tensors="pt").input_ids
|
input_ids = tokenizer("The <extra_id_0> walks in <extra_id_1> park", return_tensors="pt").input_ids
|
||||||
labels = tokenizer("<extra_id_0> cute dog <extra_id_1> the <extra_id_2>", return_tensors="pt").input_ids
|
labels = tokenizer("<extra_id_0> cute dog <extra_id_1> the <extra_id_2>", return_tensors="pt").input_ids
|
||||||
outputs = model(input_ids=input_ids, labels=labels)
|
outputs = model(input_ids=input_ids, labels=labels)
|
||||||
loss = outputs.loss
|
loss = outputs.loss
|
||||||
logits = outputs.logits
|
logits = outputs.logits
|
||||||
|
|
||||||
# inference
|
# inference
|
||||||
|
|
||||||
input_ids = tokenizer(
|
input_ids = tokenizer(
|
||||||
"summarize: studies have shown that owning a dog is good for you", return_tensors="pt"
|
"summarize: studies have shown that owning a dog is good for you", return_tensors="pt"
|
||||||
).input_ids # Batch size 1
|
).input_ids # Batch size 1
|
||||||
outputs = model.generate(input_ids, max_new_tokens=50)
|
outputs = model.generate(input_ids, max_new_tokens=50)
|
||||||
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
|
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
|
||||||
|
|
||||||
# studies have shown that owning a dog is good for you.
|
# studies have shown that owning a dog is good for you.
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
from sentence_transformers import SentenceTransformer, util
|
from sentence_transformers import SentenceTransformer, util
|
||||||
|
|
||||||
|
|
||||||
model = SentenceTransformer("TUKE-DeutscheTelekom/slovakbert-skquad-mnlr")
|
model = SentenceTransformer("TUKE-DeutscheTelekom/slovakbert-skquad-mnlr")
|
||||||
|
|
||||||
|
|
||||||
sentences = [
|
sentences = [
|
||||||
"Prvý most cez Zlatý roh nechal vybudovať cisár Justinián I. V roku 1502 vypísal sultán Bajezid II. súťaž na nový most.",
|
"Prvý most cez Zlatý roh nechal vybudovať cisár Justinián I. V roku 1502 vypísal sultán Bajezid II. súťaž na nový most.",
|
||||||
"V ktorom roku vznikol druhý drevený most cez záliv Zlatý roh?",
|
"V ktorom roku vznikol druhý drevený most cez záliv Zlatý roh?",
|
||||||
"Aká je priemerná dĺžka života v Eritrei?"
|
"Aká je priemerná dĺžka života v Eritrei?"
|
||||||
]
|
]
|
||||||
|
|
||||||
embeddings = model.encode(sentences)
|
embeddings = model.encode(sentences)
|
||||||
print("Shape of embeddings:", embeddings.shape)
|
print("Shape of embeddings:", embeddings.shape)
|
||||||
|
|
||||||
|
|
||||||
similarities = util.cos_sim(embeddings, embeddings)
|
similarities = util.cos_sim(embeddings, embeddings)
|
||||||
print("Similarity matrix:\n", similarities)
|
print("Similarity matrix:\n", similarities)
|
||||||
|
@ -1,73 +1,73 @@
|
|||||||
version: '3.8'
|
version: '3.8'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
elasticsearch:
|
elasticsearch:
|
||||||
build:
|
build:
|
||||||
context: ./elasticsearch
|
context: ./elasticsearch
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
container_name: elasticsearch
|
container_name: elasticsearch
|
||||||
environment:
|
environment:
|
||||||
- discovery.type=single-node
|
- discovery.type=single-node
|
||||||
- xpack.security.enabled=false
|
- xpack.security.enabled=false
|
||||||
ports:
|
ports:
|
||||||
- "9200:9200"
|
- "9200:9200"
|
||||||
- "9300:9300"
|
- "9300:9300"
|
||||||
networks:
|
networks:
|
||||||
- app-network
|
- app-network
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD", "curl", "-f", "http://localhost:9200/_cluster/health" ]
|
test: [ "CMD", "curl", "-f", "http://localhost:9200/_cluster/health" ]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
start_period: 40s
|
start_period: 40s
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:14
|
image: postgres:14
|
||||||
container_name: postgres_db
|
container_name: postgres_db
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_DB=HealthAIDB
|
- POSTGRES_DB=HealthAIDB
|
||||||
- POSTGRES_USER=postgres
|
- POSTGRES_USER=postgres
|
||||||
- POSTGRES_PASSWORD=Oleg2005
|
- POSTGRES_PASSWORD=Oleg2005
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- pgdata:/var/lib/postgresql/data
|
- pgdata:/var/lib/postgresql/data
|
||||||
networks:
|
networks:
|
||||||
- app-network
|
- app-network
|
||||||
|
|
||||||
backend:
|
backend:
|
||||||
container_name: backend_container
|
container_name: backend_container
|
||||||
build:
|
build:
|
||||||
context: ./Backend
|
context: ./Backend
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
ports:
|
ports:
|
||||||
- "5000:5000"
|
- "5000:5000"
|
||||||
environment:
|
environment:
|
||||||
- ELASTICSEARCH_HOST=http://elasticsearch:9200
|
- ELASTICSEARCH_HOST=http://elasticsearch:9200
|
||||||
- DATABASE_HOST=postgres
|
- DATABASE_HOST=postgres
|
||||||
depends_on:
|
depends_on:
|
||||||
elasticsearch:
|
elasticsearch:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
postgres:
|
postgres:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
networks:
|
networks:
|
||||||
- app-network
|
- app-network
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
container_name: frontend_container
|
container_name: frontend_container
|
||||||
build:
|
build:
|
||||||
context: ./frontend
|
context: ./frontend
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
ports:
|
ports:
|
||||||
- "5173:5173"
|
- "5173:5173"
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
- backend
|
||||||
networks:
|
networks:
|
||||||
- app-network
|
- app-network
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
app-network:
|
app-network:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
pgdata:
|
pgdata:
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
FROM docker.elastic.co/elasticsearch/elasticsearch:8.14.3
|
FROM docker.elastic.co/elasticsearch/elasticsearch:8.14.3
|
||||||
|
|
||||||
|
|
||||||
ENV discovery.type=single-node
|
ENV discovery.type=single-node
|
||||||
ENV xpack.security.enabled=false
|
ENV xpack.security.enabled=false
|
||||||
|
|
||||||
|
|
||||||
COPY --chown=elasticsearch:elasticsearch data/ /usr/share/elasticsearch/data
|
COPY --chown=elasticsearch:elasticsearch data/ /usr/share/elasticsearch/data
|
||||||
|
|
||||||
|
|
||||||
RUN chmod -R 0775 /usr/share/elasticsearch/data
|
RUN chmod -R 0775 /usr/share/elasticsearch/data
|
||||||
|
|
||||||
|
|
||||||
RUN find /usr/share/elasticsearch/data -type f -name "*.lock" -delete
|
RUN find /usr/share/elasticsearch/data -type f -name "*.lock" -delete
|
||||||
RUN rm -f /usr/share/elasticsearch/data/nodes/0/node.lock
|
RUN rm -f /usr/share/elasticsearch/data/nodes/0/node.lock
|
||||||
|
48
frontend/.gitignore
vendored
48
frontend/.gitignore
vendored
@ -1,24 +1,24 @@
|
|||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
pnpm-debug.log*
|
pnpm-debug.log*
|
||||||
lerna-debug.log*
|
lerna-debug.log*
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
.idea
|
.idea
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.suo
|
*.suo
|
||||||
*.ntvs*
|
*.ntvs*
|
||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"hash": "c4d2d06d",
|
"hash": "c4d2d06d",
|
||||||
"configHash": "9db50785",
|
"configHash": "9db50785",
|
||||||
"lockfileHash": "e3b0c442",
|
"lockfileHash": "e3b0c442",
|
||||||
"browserHash": "efbdb5f3",
|
"browserHash": "efbdb5f3",
|
||||||
"optimized": {},
|
"optimized": {},
|
||||||
"chunks": {}
|
"chunks": {}
|
||||||
}
|
}
|
@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"type": "module"
|
"type": "module"
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"type": "module"
|
"type": "module"
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
|
|
||||||
FROM node:18-alpine
|
FROM node:18-alpine
|
||||||
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
|
||||||
|
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
|
||||||
RUN npm install -g serve
|
RUN npm install -g serve
|
||||||
|
|
||||||
|
|
||||||
EXPOSE 5173
|
EXPOSE 5173
|
||||||
|
|
||||||
|
|
||||||
CMD ["serve", "-s", "dist", "-l", "5173"]
|
CMD ["serve", "-s", "dist", "-l", "5173"]
|
||||||
|
@ -1,28 +1,28 @@
|
|||||||
import js from '@eslint/js'
|
import js from '@eslint/js'
|
||||||
import globals from 'globals'
|
import globals from 'globals'
|
||||||
import reactHooks from 'eslint-plugin-react-hooks'
|
import reactHooks from 'eslint-plugin-react-hooks'
|
||||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||||
import tseslint from 'typescript-eslint'
|
import tseslint from 'typescript-eslint'
|
||||||
|
|
||||||
export default tseslint.config(
|
export default tseslint.config(
|
||||||
{ ignores: ['dist'] },
|
{ ignores: ['dist'] },
|
||||||
{
|
{
|
||||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||||
files: ['**/*.{ts,tsx}'],
|
files: ['**/*.{ts,tsx}'],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
ecmaVersion: 2020,
|
ecmaVersion: 2020,
|
||||||
globals: globals.browser,
|
globals: globals.browser,
|
||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
'react-hooks': reactHooks,
|
'react-hooks': reactHooks,
|
||||||
'react-refresh': reactRefresh,
|
'react-refresh': reactRefresh,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
...reactHooks.configs.recommended.rules,
|
...reactHooks.configs.recommended.rules,
|
||||||
'react-refresh/only-export-components': [
|
'react-refresh/only-export-components': [
|
||||||
'warn',
|
'warn',
|
||||||
{ allowConstantExport: true },
|
{ allowConstantExport: true },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Health AI</title>
|
<title>Health AI</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
9952
frontend/package-lock.json
generated
9952
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,46 +1,46 @@
|
|||||||
{
|
{
|
||||||
"name": "coconuts",
|
"name": "coconuts",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.13.3",
|
"@emotion/react": "^11.13.3",
|
||||||
"@emotion/styled": "^11.13.0",
|
"@emotion/styled": "^11.13.0",
|
||||||
"@gsap/react": "^2.1.1",
|
"@gsap/react": "^2.1.1",
|
||||||
"@mui/icons-material": "^6.4.3",
|
"@mui/icons-material": "^6.4.3",
|
||||||
"@mui/material": "^6.1.5",
|
"@mui/material": "^6.1.5",
|
||||||
"@react-oauth/google": "^0.12.1",
|
"@react-oauth/google": "^0.12.1",
|
||||||
"@reduxjs/toolkit": "^2.3.0",
|
"@reduxjs/toolkit": "^2.3.0",
|
||||||
"appwrite": "^16.0.2",
|
"appwrite": "^16.0.2",
|
||||||
"final-form": "^4.20.10",
|
"final-form": "^4.20.10",
|
||||||
"gsap": "^3.12.5",
|
"gsap": "^3.12.5",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-final-form": "^6.5.9",
|
"react-final-form": "^6.5.9",
|
||||||
"react-icons": "^5.3.0",
|
"react-icons": "^5.3.0",
|
||||||
"react-redux": "^9.1.2",
|
"react-redux": "^9.1.2",
|
||||||
"react-router-dom": "^6.27.0"
|
"react-router-dom": "^6.27.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.13.0",
|
"@eslint/js": "^9.13.0",
|
||||||
"@types/react": "^18.3.11",
|
"@types/react": "^18.3.11",
|
||||||
"@types/react-dom": "^18.3.1",
|
"@types/react-dom": "^18.3.1",
|
||||||
"@vitejs/plugin-react": "^4.3.3",
|
"@vitejs/plugin-react": "^4.3.3",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "^9.13.0",
|
"eslint": "^9.13.0",
|
||||||
"eslint-plugin-react-hooks": "^5.0.0",
|
"eslint-plugin-react-hooks": "^5.0.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.13",
|
"eslint-plugin-react-refresh": "^0.4.13",
|
||||||
"globals": "^15.11.0",
|
"globals": "^15.11.0",
|
||||||
"postcss": "^8.4.47",
|
"postcss": "^8.4.47",
|
||||||
"tailwindcss": "^3.4.14",
|
"tailwindcss": "^3.4.14",
|
||||||
"typescript": "~5.6.2",
|
"typescript": "~5.6.2",
|
||||||
"typescript-eslint": "^8.10.0",
|
"typescript-eslint": "^8.10.0",
|
||||||
"vite": "^5.4.9"
|
"vite": "^5.4.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
export default {
|
export default {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,45 +1,45 @@
|
|||||||
import { BrowserRouter as Router, Route, Routes, Outlet } from 'react-router-dom';
|
import { BrowserRouter as Router, Route, Routes, Outlet } from 'react-router-dom';
|
||||||
import Navigation from './Components/Navigation';
|
import Navigation from './Components/Navigation';
|
||||||
import {Home} from './pages/LandingPage';
|
import {Home} from './pages/LandingPage';
|
||||||
import RegistrationForm from "./Components/RegistrationForm";
|
import RegistrationForm from "./Components/RegistrationForm";
|
||||||
import LoginForm from "./Components/LoginForm";
|
import LoginForm from "./Components/LoginForm";
|
||||||
import ChatHistory from "./Components/ChatHistory";
|
import ChatHistory from "./Components/ChatHistory";
|
||||||
import HomePage from './pages/HomePage';
|
import HomePage from './pages/HomePage';
|
||||||
import NewChatPage from "./Components/NewChatPage";
|
import NewChatPage from "./Components/NewChatPage";
|
||||||
import About from "./Components/About.tsx";
|
import About from "./Components/About.tsx";
|
||||||
import Contact from "./Components/Contact.tsx";
|
import Contact from "./Components/Contact.tsx";
|
||||||
import Profile from "./Components/Profile.tsx";
|
import Profile from "./Components/Profile.tsx";
|
||||||
|
|
||||||
const Layout = () => (
|
const Layout = () => (
|
||||||
<div className="flex w-full h-screen dark:bg-slate-200">
|
<div className="flex w-full h-screen dark:bg-slate-200">
|
||||||
<Navigation isExpanded={false} />
|
<Navigation isExpanded={false} />
|
||||||
<div className="flex-grow p-3 h-full">
|
<div className="flex-grow p-3 h-full">
|
||||||
<main className="h-full w-full border rounded-xl dark:bg-slate-100 shadow-xl">
|
<main className="h-full w-full border rounded-xl dark:bg-slate-100 shadow-xl">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Home />} />
|
<Route path="/" element={<Home />} />
|
||||||
<Route path="/register" element={<RegistrationForm />} />
|
<Route path="/register" element={<RegistrationForm />} />
|
||||||
<Route path="/login" element={<LoginForm />} />
|
<Route path="/login" element={<LoginForm />} />
|
||||||
<Route path="/contact" element={<Contact />} />
|
<Route path="/contact" element={<Contact />} />
|
||||||
<Route path="/profile" element={<Profile />} />
|
<Route path="/profile" element={<Profile />} />
|
||||||
<Route path="/about" element={<About />} />
|
<Route path="/about" element={<About />} />
|
||||||
<Route path="/dashboard" element={<Layout />}>
|
<Route path="/dashboard" element={<Layout />}>
|
||||||
<Route path="new-chat" element={<NewChatPage />} />
|
<Route path="new-chat" element={<NewChatPage />} />
|
||||||
<Route path="chat/:id" element={<HomePage />} />
|
<Route path="chat/:id" element={<HomePage />} />
|
||||||
<Route path="history" element={<ChatHistory />} />
|
<Route path="history" element={<ChatHistory />} />
|
||||||
<Route index element={<HomePage />} />
|
<Route index element={<HomePage />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -1,109 +1,109 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Box, Typography, Grid, Paper } from '@mui/material';
|
import { Box, Typography, Grid, Paper } from '@mui/material';
|
||||||
import {Navbar} from '../pages/LandingPage';
|
import {Navbar} from '../pages/LandingPage';
|
||||||
import MedicalServicesIcon from '@mui/icons-material/MedicalServices';
|
import MedicalServicesIcon from '@mui/icons-material/MedicalServices';
|
||||||
import LocalHospitalIcon from '@mui/icons-material/LocalHospital';
|
import LocalHospitalIcon from '@mui/icons-material/LocalHospital';
|
||||||
import CodeIcon from '@mui/icons-material/Code';
|
import CodeIcon from '@mui/icons-material/Code';
|
||||||
|
|
||||||
const About: React.FC = () => {
|
const About: React.FC = () => {
|
||||||
const [user, setUser] = useState<any>(null);
|
const [user, setUser] = useState<any>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const storedUser = localStorage.getItem('user');
|
const storedUser = localStorage.getItem('user');
|
||||||
if (storedUser) {
|
if (storedUser) {
|
||||||
setUser(JSON.parse(storedUser));
|
setUser(JSON.parse(storedUser));
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ background: 'linear-gradient(to right, #d0e7ff, #f0f8ff)', minHeight: '100vh', p: 4 }}>
|
<Box sx={{ background: 'linear-gradient(to right, #d0e7ff, #f0f8ff)', minHeight: '100vh', p: 4 }}>
|
||||||
{/* Navigation bar */}
|
{/* Navigation bar */}
|
||||||
<Navbar user={user} setUser={setUser} />
|
<Navbar user={user} setUser={setUser} />
|
||||||
|
|
||||||
{/* Main content with top padding to account for fixed Navbar */}
|
{/* Main content with top padding to account for fixed Navbar */}
|
||||||
<Box sx={{ pt: '80px' }}>
|
<Box sx={{ pt: '80px' }}>
|
||||||
<Typography
|
<Typography
|
||||||
variant="h3"
|
variant="h3"
|
||||||
align="center"
|
align="center"
|
||||||
gutterBottom
|
gutterBottom
|
||||||
sx={{ fontWeight: 'bold', color: '#0d47a1' }}
|
sx={{ fontWeight: 'bold', color: '#0d47a1' }}
|
||||||
>
|
>
|
||||||
About Health AI
|
About Health AI
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
variant="h6"
|
variant="h6"
|
||||||
align="center"
|
align="center"
|
||||||
gutterBottom
|
gutterBottom
|
||||||
sx={{ color: '#0d47a1', mb: 4 }}
|
sx={{ color: '#0d47a1', mb: 4 }}
|
||||||
>
|
>
|
||||||
Your Personal AI Assistant for Tailored Drug Recommendations
|
Your Personal AI Assistant for Tailored Drug Recommendations
|
||||||
</Typography>
|
</Typography>
|
||||||
<Grid container spacing={4} justifyContent="center">
|
<Grid container spacing={4} justifyContent="center">
|
||||||
{/* Project Information Card */}
|
{/* Project Information Card */}
|
||||||
<Grid item xs={12} md={6}>
|
<Grid item xs={12} md={6}>
|
||||||
<Paper elevation={3} sx={{ p: 3, backgroundColor: '#ffffff', borderRadius: '12px' }}>
|
<Paper elevation={3} sx={{ p: 3, backgroundColor: '#ffffff', borderRadius: '12px' }}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||||
<MedicalServicesIcon sx={{ fontSize: 40, color: '#0d47a1', mr: 1 }} />
|
<MedicalServicesIcon sx={{ fontSize: 40, color: '#0d47a1', mr: 1 }} />
|
||||||
<Typography variant="h5" sx={{ fontWeight: 'bold', color: '#0d47a1' }}>
|
<Typography variant="h5" sx={{ fontWeight: 'bold', color: '#0d47a1' }}>
|
||||||
About the Project
|
About the Project
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Typography variant="body1" sx={{ color: '#424242', mb: 2 }}>
|
<Typography variant="body1" sx={{ color: '#424242', mb: 2 }}>
|
||||||
Health AI is a cutting-edge application specializing in providing personalized drug recommendations and medication advice.
|
Health AI is a cutting-edge application specializing in providing personalized drug recommendations and medication advice.
|
||||||
Leveraging advanced AI models like Mistral and powerful search technologies such as Elasticsearch, our platform delivers accurate,
|
Leveraging advanced AI models like Mistral and powerful search technologies such as Elasticsearch, our platform delivers accurate,
|
||||||
context-aware suggestions for both over-the-counter and prescription medications.
|
context-aware suggestions for both over-the-counter and prescription medications.
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" sx={{ color: '#424242' }}>
|
<Typography variant="body1" sx={{ color: '#424242' }}>
|
||||||
Our backend utilizes modern technologies including Flask, PostgreSQL, and Google OAuth, ensuring robust security and reliable performance.
|
Our backend utilizes modern technologies including Flask, PostgreSQL, and Google OAuth, ensuring robust security and reliable performance.
|
||||||
We also use long-term conversational memory to continuously enhance our responses.
|
We also use long-term conversational memory to continuously enhance our responses.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
{/* How It Works Card */}
|
{/* How It Works Card */}
|
||||||
<Grid item xs={12} md={6}>
|
<Grid item xs={12} md={6}>
|
||||||
<Paper elevation={3} sx={{ p: 3, backgroundColor: '#ffffff', borderRadius: '12px' }}>
|
<Paper elevation={3} sx={{ p: 3, backgroundColor: '#ffffff', borderRadius: '12px' }}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||||
<LocalHospitalIcon sx={{ fontSize: 40, color: '#0d47a1', mr: 1 }} />
|
<LocalHospitalIcon sx={{ fontSize: 40, color: '#0d47a1', mr: 1 }} />
|
||||||
<Typography variant="h5" sx={{ fontWeight: 'bold', color: '#0d47a1' }}>
|
<Typography variant="h5" sx={{ fontWeight: 'bold', color: '#0d47a1' }}>
|
||||||
How It Works
|
How It Works
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Typography variant="body1" sx={{ color: '#424242', mb: 2 }}>
|
<Typography variant="body1" sx={{ color: '#424242', mb: 2 }}>
|
||||||
Our system uses natural language processing to understand user queries and extract key details such as age, medical history,
|
Our system uses natural language processing to understand user queries and extract key details such as age, medical history,
|
||||||
and medication type. It then employs vector search techniques to fetch the most relevant information from a comprehensive drug database,
|
and medication type. It then employs vector search techniques to fetch the most relevant information from a comprehensive drug database,
|
||||||
ensuring precise recommendations.
|
ensuring precise recommendations.
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" sx={{ color: '#424242' }}>
|
<Typography variant="body1" sx={{ color: '#424242' }}>
|
||||||
Health AI validates its responses to guarantee consistency and reliability, making it an innovative solution for personalized healthcare guidance.
|
Health AI validates its responses to guarantee consistency and reliability, making it an innovative solution for personalized healthcare guidance.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
{/* Future Enhancements Card */}
|
{/* Future Enhancements Card */}
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Paper elevation={3} sx={{ p: 3, backgroundColor: '#ffffff', borderRadius: '12px' }}>
|
<Paper elevation={3} sx={{ p: 3, backgroundColor: '#ffffff', borderRadius: '12px' }}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||||
<CodeIcon sx={{ fontSize: 40, color: '#0d47a1', mr: 1 }} />
|
<CodeIcon sx={{ fontSize: 40, color: '#0d47a1', mr: 1 }} />
|
||||||
<Typography variant="h5" sx={{ fontWeight: 'bold', color: '#0d47a1' }}>
|
<Typography variant="h5" sx={{ fontWeight: 'bold', color: '#0d47a1' }}>
|
||||||
What's Next?
|
What's Next?
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Typography variant="body1" sx={{ color: '#424242' }}>
|
<Typography variant="body1" sx={{ color: '#424242' }}>
|
||||||
We are continuously improving Health AI by integrating additional data sources and refining our AI algorithms.
|
We are continuously improving Health AI by integrating additional data sources and refining our AI algorithms.
|
||||||
Future enhancements include real-time drug interaction checks, comprehensive patient monitoring,
|
Future enhancements include real-time drug interaction checks, comprehensive patient monitoring,
|
||||||
and seamless integration with healthcare providers. Stay tuned for more exciting updates and features as we strive to make healthcare more accessible and efficient.
|
and seamless integration with healthcare providers. Stay tuned for more exciting updates and features as we strive to make healthcare more accessible and efficient.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<Box sx={{ textAlign: 'center', mt: 6 }}>
|
<Box sx={{ textAlign: 'center', mt: 6 }}>
|
||||||
<Typography variant="body2" sx={{ color: '#424242' }}>
|
<Typography variant="body2" sx={{ color: '#424242' }}>
|
||||||
© {new Date().getFullYear()} Health AI. All rights reserved.
|
© {new Date().getFullYear()} Health AI. All rights reserved.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default About;
|
export default About;
|
||||||
|
@ -1,57 +1,57 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useParams, useLocation } from 'react-router-dom';
|
import { useParams, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
interface ChatHistoryItem {
|
interface ChatHistoryItem {
|
||||||
id: number;
|
id: number;
|
||||||
chat: string;
|
chat: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChatDetails: React.FC = () => {
|
const ChatDetails: React.FC = () => {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [chat, setChat] = useState<ChatHistoryItem | null>(location.state?.chat || null);
|
const [chat, setChat] = useState<ChatHistoryItem | null>(location.state?.chat || null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!chat && id) {
|
if (!chat && id) {
|
||||||
fetch(`http://localhost:5000/api/chat_history_detail?id=${encodeURIComponent(id)}`)
|
fetch(`http://localhost:5000/api/chat_history_detail?id=${encodeURIComponent(id)}`)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new Error('Chat not found');
|
throw new Error('Chat not found');
|
||||||
}
|
}
|
||||||
return res.json();
|
return res.json();
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
setError(data.error);
|
setError(data.error);
|
||||||
} else {
|
} else {
|
||||||
setChat(data.chat);
|
setChat(data.chat);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => setError(err.message));
|
.catch((err) => setError(err.message));
|
||||||
}
|
}
|
||||||
}, [id, chat]);
|
}, [id, chat]);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <div>Error: {error}</div>;
|
return <div>Error: {error}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
return <div>Loading chat details...</div>;
|
return <div>Loading chat details...</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: '20px' }}>
|
<div style={{ padding: '20px' }}>
|
||||||
<h1>Chat Details</h1>
|
<h1>Chat Details</h1>
|
||||||
<div style={{ border: '1px solid #ccc', padding: '10px' }}>
|
<div style={{ border: '1px solid #ccc', padding: '10px' }}>
|
||||||
{chat.chat.split('\n').map((line, index) => (
|
{chat.chat.split('\n').map((line, index) => (
|
||||||
<p key={index}>{line}</p>
|
<p key={index}>{line}</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<small>{new Date(chat.created_at).toLocaleString()}</small>
|
<small>{new Date(chat.created_at).toLocaleString()}</small>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ChatDetails;
|
export default ChatDetails;
|
||||||
|
@ -1,158 +1,158 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Box, Typography, Paper, IconButton } from '@mui/material';
|
import { Box, Typography, Paper, IconButton } from '@mui/material';
|
||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
|
|
||||||
interface ChatHistoryItem {
|
interface ChatHistoryItem {
|
||||||
id: number;
|
id: number;
|
||||||
chat: string;
|
chat: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChatHistory: React.FC = () => {
|
const ChatHistory: React.FC = () => {
|
||||||
const [history, setHistory] = useState<ChatHistoryItem[]>([]);
|
const [history, setHistory] = useState<ChatHistoryItem[]>([]);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const storedUser = localStorage.getItem('user');
|
const storedUser = localStorage.getItem('user');
|
||||||
if (storedUser) {
|
if (storedUser) {
|
||||||
const user = JSON.parse(storedUser);
|
const user = JSON.parse(storedUser);
|
||||||
const email = user.email;
|
const email = user.email;
|
||||||
fetch(`http://localhost:5000/api/chat_history?email=${encodeURIComponent(email)}`)
|
fetch(`http://localhost:5000/api/chat_history?email=${encodeURIComponent(email)}`)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
setError(data.error);
|
setError(data.error);
|
||||||
} else {
|
} else {
|
||||||
setHistory(data.history);
|
setHistory(data.history);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => setError('Error fetching chat history'));
|
.catch(() => setError('Error fetching chat history'));
|
||||||
} else {
|
} else {
|
||||||
setError('User not logged in');
|
setError('User not logged in');
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleClick = (item: ChatHistoryItem) => {
|
const handleClick = (item: ChatHistoryItem) => {
|
||||||
navigate(`/dashboard/chat/${item.id}`, { state: { selectedChat: item } });
|
navigate(`/dashboard/chat/${item.id}`, { state: { selectedChat: item } });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (chatId: number) => {
|
const handleDelete = (chatId: number) => {
|
||||||
if (window.confirm('Are you sure that you want to delete that chat?')) {
|
if (window.confirm('Are you sure that you want to delete that chat?')) {
|
||||||
fetch(`http://localhost:5000/api/chat_history?id=${chatId}`, {
|
fetch(`http://localhost:5000/api/chat_history?id=${chatId}`, {
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
})
|
})
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
setError(data.error);
|
setError(data.error);
|
||||||
} else {
|
} else {
|
||||||
setHistory(history.filter((chat) => chat.id !== chatId));
|
setHistory(history.filter((chat) => chat.id !== chatId));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => setError('Error deleting chat'));
|
.catch(() => setError('Error deleting chat'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100vh',
|
height: '100vh',
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
background: '#f5f5f5',
|
background: '#f5f5f5',
|
||||||
boxSizing: 'border-box',
|
boxSizing: 'border-box',
|
||||||
p: 3,
|
p: 3,
|
||||||
'&::-webkit-scrollbar': {
|
'&::-webkit-scrollbar': {
|
||||||
display: 'none',
|
display: 'none',
|
||||||
},
|
},
|
||||||
'-ms-overflow-style': 'none',
|
'-ms-overflow-style': 'none',
|
||||||
'scrollbarWidth': 'none',
|
'scrollbarWidth': 'none',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
<Typography
|
||||||
variant="h4"
|
variant="h4"
|
||||||
sx={{
|
sx={{
|
||||||
mb: 3,
|
mb: 3,
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
color: '#0d47a1',
|
color: '#0d47a1',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Chat History
|
Chat History
|
||||||
</Typography>
|
</Typography>
|
||||||
{error ? (
|
{error ? (
|
||||||
<Typography
|
<Typography
|
||||||
variant="body1"
|
variant="body1"
|
||||||
sx={{ color: 'error.main', textAlign: 'center' }}
|
sx={{ color: 'error.main', textAlign: 'center' }}
|
||||||
>
|
>
|
||||||
{error}
|
{error}
|
||||||
</Typography>
|
</Typography>
|
||||||
) : (
|
) : (
|
||||||
<Box sx={{ maxWidth: '800px', mx: 'auto' }}>
|
<Box sx={{ maxWidth: '800px', mx: 'auto' }}>
|
||||||
{history.length === 0 ? (
|
{history.length === 0 ? (
|
||||||
<Typography
|
<Typography
|
||||||
variant="body1"
|
variant="body1"
|
||||||
sx={{ textAlign: 'center', color: '#424242' }}
|
sx={{ textAlign: 'center', color: '#424242' }}
|
||||||
>
|
>
|
||||||
No chat history found.
|
No chat history found.
|
||||||
</Typography>
|
</Typography>
|
||||||
) : (
|
) : (
|
||||||
history.map((item) => {
|
history.map((item) => {
|
||||||
const lines = item.chat.split("\n");
|
const lines = item.chat.split("\n");
|
||||||
let firstUserMessage = lines[0];
|
let firstUserMessage = lines[0];
|
||||||
if (firstUserMessage.startsWith("User:")) {
|
if (firstUserMessage.startsWith("User:")) {
|
||||||
firstUserMessage = firstUserMessage.replace("User:", "").trim();
|
firstUserMessage = firstUserMessage.replace("User:", "").trim();
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Paper
|
<Paper
|
||||||
key={item.id}
|
key={item.id}
|
||||||
sx={{
|
sx={{
|
||||||
p: 2,
|
p: 2,
|
||||||
mb: 2,
|
mb: 2,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'box-shadow 0.3s ease',
|
transition: 'box-shadow 0.3s ease',
|
||||||
'&:hover': { boxShadow: 6 },
|
'&:hover': { boxShadow: 6 },
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{ flex: 1, display: 'flex', flexDirection: 'column' }}
|
sx={{ flex: 1, display: 'flex', flexDirection: 'column' }}
|
||||||
onClick={() => handleClick(item)}
|
onClick={() => handleClick(item)}
|
||||||
>
|
>
|
||||||
<Typography
|
<Typography
|
||||||
variant="subtitle1"
|
variant="subtitle1"
|
||||||
sx={{ fontWeight: 'bold', color: '#0d47a1' }}
|
sx={{ fontWeight: 'bold', color: '#0d47a1' }}
|
||||||
>
|
>
|
||||||
{firstUserMessage}
|
{firstUserMessage}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
variant="caption"
|
variant="caption"
|
||||||
sx={{ color: '#757575' }}
|
sx={{ color: '#757575' }}
|
||||||
>
|
>
|
||||||
{new Date(item.created_at).toLocaleString()}
|
{new Date(item.created_at).toLocaleString()}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleDelete(item.id);
|
handleDelete(item.id);
|
||||||
}}
|
}}
|
||||||
sx={{ color: '#d32f2f' }}
|
sx={{ color: '#d32f2f' }}
|
||||||
>
|
>
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ChatHistory;
|
export default ChatHistory;
|
||||||
|
@ -1,129 +1,129 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Box, Typography, Paper, Grid } from '@mui/material';
|
import { Box, Typography, Paper, Grid } from '@mui/material';
|
||||||
import {Navbar} from '../pages/LandingPage';
|
import {Navbar} from '../pages/LandingPage';
|
||||||
import SchoolIcon from '@mui/icons-material/School';
|
import SchoolIcon from '@mui/icons-material/School';
|
||||||
import DeveloperModeIcon from '@mui/icons-material/DeveloperMode';
|
import DeveloperModeIcon from '@mui/icons-material/DeveloperMode';
|
||||||
import EmailIcon from '@mui/icons-material/Email';
|
import EmailIcon from '@mui/icons-material/Email';
|
||||||
|
|
||||||
const Contact: React.FC = () => {
|
const Contact: React.FC = () => {
|
||||||
const [user, setUser] = useState<any>(null);
|
const [user, setUser] = useState<any>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const storedUser = localStorage.getItem('user');
|
const storedUser = localStorage.getItem('user');
|
||||||
if (storedUser) {
|
if (storedUser) {
|
||||||
setUser(JSON.parse(storedUser));
|
setUser(JSON.parse(storedUser));
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
background: 'linear-gradient(to right, #d0e7ff, #f0f8ff)',
|
background: 'linear-gradient(to right, #d0e7ff, #f0f8ff)',
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
p: 4,
|
p: 4,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Navbar with navigation links */}
|
{/* Navbar with navigation links */}
|
||||||
<Navbar user={user} setUser={setUser} />
|
<Navbar user={user} setUser={setUser} />
|
||||||
|
|
||||||
{/* Main content with spacing for fixed Navbar */}
|
{/* Main content with spacing for fixed Navbar */}
|
||||||
<Box sx={{ pt: '80px', maxWidth: '800px', mx: 'auto' }}>
|
<Box sx={{ pt: '80px', maxWidth: '800px', mx: 'auto' }}>
|
||||||
<Paper
|
<Paper
|
||||||
elevation={4}
|
elevation={4}
|
||||||
sx={{
|
sx={{
|
||||||
p: 4,
|
p: 4,
|
||||||
borderRadius: '16px',
|
borderRadius: '16px',
|
||||||
backgroundColor: '#ffffff',
|
backgroundColor: '#ffffff',
|
||||||
boxShadow: '0 4px 20px rgba(0,0,0,0.1)',
|
boxShadow: '0 4px 20px rgba(0,0,0,0.1)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
<Typography
|
||||||
variant="h4"
|
variant="h4"
|
||||||
align="center"
|
align="center"
|
||||||
sx={{ fontWeight: 'bold', color: '#0d47a1', mb: 4 }}
|
sx={{ fontWeight: 'bold', color: '#0d47a1', mb: 4 }}
|
||||||
>
|
>
|
||||||
Contact
|
Contact
|
||||||
</Typography>
|
</Typography>
|
||||||
<Grid container spacing={4}>
|
<Grid container spacing={4}>
|
||||||
{/* University Info */}
|
{/* University Info */}
|
||||||
<Grid item xs={12} sm={6}>
|
<Grid item xs={12} sm={6}>
|
||||||
<Paper
|
<Paper
|
||||||
elevation={2}
|
elevation={2}
|
||||||
sx={{
|
sx={{
|
||||||
p: 2,
|
p: 2,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderRadius: '12px',
|
borderRadius: '12px',
|
||||||
backgroundColor: '#e3f2fd',
|
backgroundColor: '#e3f2fd',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SchoolIcon sx={{ fontSize: 40, color: '#0d47a1', mr: 2 }} />
|
<SchoolIcon sx={{ fontSize: 40, color: '#0d47a1', mr: 2 }} />
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="h6" sx={{ fontWeight: 'bold', color: '#0d47a1' }}>
|
<Typography variant="h6" sx={{ fontWeight: 'bold', color: '#0d47a1' }}>
|
||||||
Technical University of Košice
|
Technical University of Košice
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" sx={{ color: '#424242' }}>
|
<Typography variant="body2" sx={{ color: '#424242' }}>
|
||||||
KEMT Department
|
KEMT Department
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
{/* Developer Info */}
|
{/* Developer Info */}
|
||||||
<Grid item xs={12} sm={6}>
|
<Grid item xs={12} sm={6}>
|
||||||
<Paper
|
<Paper
|
||||||
elevation={2}
|
elevation={2}
|
||||||
sx={{
|
sx={{
|
||||||
p: 2,
|
p: 2,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderRadius: '12px',
|
borderRadius: '12px',
|
||||||
backgroundColor: '#e8f5e9',
|
backgroundColor: '#e8f5e9',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DeveloperModeIcon sx={{ fontSize: 40, color: '#0d47a1', mr: 2 }} />
|
<DeveloperModeIcon sx={{ fontSize: 40, color: '#0d47a1', mr: 2 }} />
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="h6" sx={{ fontWeight: 'bold', color: '#0d47a1' }}>
|
<Typography variant="h6" sx={{ fontWeight: 'bold', color: '#0d47a1' }}>
|
||||||
Developer
|
Developer
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" sx={{ color: '#424242' }}>
|
<Typography variant="body2" sx={{ color: '#424242' }}>
|
||||||
oleh.poiasnik@student.tuke.sk
|
oleh.poiasnik@student.tuke.sk
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
{/* Additional Contact Option */}
|
{/* Additional Contact Option */}
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Paper
|
<Paper
|
||||||
elevation={2}
|
elevation={2}
|
||||||
sx={{
|
sx={{
|
||||||
p: 2,
|
p: 2,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
borderRadius: '12px',
|
borderRadius: '12px',
|
||||||
backgroundColor: '#fff3e0',
|
backgroundColor: '#fff3e0',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<EmailIcon sx={{ fontSize: 40, color: '#0d47a1', mr: 2 }} />
|
<EmailIcon sx={{ fontSize: 40, color: '#0d47a1', mr: 2 }} />
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="h6" sx={{ fontWeight: 'bold', color: '#0d47a1' }}>
|
<Typography variant="h6" sx={{ fontWeight: 'bold', color: '#0d47a1' }}>
|
||||||
Email Us
|
Email Us
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" sx={{ color: '#424242' }}>
|
<Typography variant="body2" sx={{ color: '#424242' }}>
|
||||||
For any inquiries or further information about Health AI, please get in touch!
|
For any inquiries or further information about Health AI, please get in touch!
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Box sx={{ textAlign: 'center', mt: 4 }}>
|
<Box sx={{ textAlign: 'center', mt: 4 }}>
|
||||||
<Typography variant="body2" sx={{ color: '#424242' }}>
|
<Typography variant="body2" sx={{ color: '#424242' }}>
|
||||||
© {new Date().getFullYear()} Health AI. All rights reserved.
|
© {new Date().getFullYear()} Health AI. All rights reserved.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Contact;
|
export default Contact;
|
||||||
|
@ -1,190 +1,190 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { GoogleLogin, GoogleOAuthProvider } from '@react-oauth/google';
|
import { GoogleLogin, GoogleOAuthProvider } from '@react-oauth/google';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
const CLIENT_ID = "532143017111-4eqtlp0oejqaovj6rf5l1ergvhrp4vao.apps.googleusercontent.com";
|
const CLIENT_ID = "532143017111-4eqtlp0oejqaovj6rf5l1ergvhrp4vao.apps.googleusercontent.com";
|
||||||
|
|
||||||
const LoginFormContent: React.FC = () => {
|
const LoginFormContent: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const emailElement = document.getElementById('email') as HTMLInputElement | null;
|
const emailElement = document.getElementById('email') as HTMLInputElement | null;
|
||||||
const passwordElement = document.getElementById('password') as HTMLInputElement | null;
|
const passwordElement = document.getElementById('password') as HTMLInputElement | null;
|
||||||
|
|
||||||
if (!emailElement || !passwordElement) {
|
if (!emailElement || !passwordElement) {
|
||||||
console.error('One or many inputs are missing');
|
console.error('One or many inputs are missing');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const email = emailElement.value;
|
const email = emailElement.value;
|
||||||
const password = passwordElement.value;
|
const password = passwordElement.value;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://localhost:5000/api/login', {
|
const response = await fetch('http://localhost:5000/api/login', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ email, password }),
|
body: JSON.stringify({ email, password }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
console.log('Login successful:', data.message);
|
console.log('Login successful:', data.message);
|
||||||
const loggedInUser = {
|
const loggedInUser = {
|
||||||
name: data.user.name,
|
name: data.user.name,
|
||||||
email: data.user.email,
|
email: data.user.email,
|
||||||
picture: data.user.picture || 'https://via.placeholder.com/150',
|
picture: data.user.picture || 'https://via.placeholder.com/150',
|
||||||
};
|
};
|
||||||
localStorage.setItem('user', JSON.stringify(loggedInUser));
|
localStorage.setItem('user', JSON.stringify(loggedInUser));
|
||||||
navigate('/dashboard');
|
navigate('/dashboard');
|
||||||
} else {
|
} else {
|
||||||
console.error('Error:', data.error);
|
console.error('Error:', data.error);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loginning:', error);
|
console.error('Error loginning:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGoogleLoginSuccess = async (response: any) => {
|
const handleGoogleLoginSuccess = async (response: any) => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('http://localhost:5000/api/verify', {
|
const res = await fetch('http://localhost:5000/api/verify', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ token: response.credential }),
|
body: JSON.stringify({ token: response.credential }),
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
const loggedInUser = {
|
const loggedInUser = {
|
||||||
name: data.user.name,
|
name: data.user.name,
|
||||||
email: data.user.email,
|
email: data.user.email,
|
||||||
picture: data.user.picture || 'https://via.placeholder.com/150',
|
picture: data.user.picture || 'https://via.placeholder.com/150',
|
||||||
};
|
};
|
||||||
localStorage.setItem('user', JSON.stringify(loggedInUser));
|
localStorage.setItem('user', JSON.stringify(loggedInUser));
|
||||||
navigate('/dashboard');
|
navigate('/dashboard');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error token verification:', error);
|
console.error('Error token verification:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGoogleLoginError = () => {
|
const handleGoogleLoginError = () => {
|
||||||
console.error('Error auth through Google');
|
console.error('Error auth through Google');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
background: 'rgba(0, 0, 0, 0.5)',
|
background: 'rgba(0, 0, 0, 0.5)',
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
padding: '16px',
|
padding: '16px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="login-card"
|
className="login-card"
|
||||||
style={{
|
style={{
|
||||||
maxWidth: '400px',
|
maxWidth: '400px',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
|
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
padding: '24px',
|
padding: '24px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h2 style={{ textAlign: 'center', fontWeight: 'bold', color: '#333', marginBottom: '16px' }}>
|
<h2 style={{ textAlign: 'center', fontWeight: 'bold', color: '#333', marginBottom: '16px' }}>
|
||||||
Sign In
|
Sign In
|
||||||
</h2>
|
</h2>
|
||||||
<form noValidate autoComplete="off" onSubmit={handleSubmit}>
|
<form noValidate autoComplete="off" onSubmit={handleSubmit}>
|
||||||
<div style={{ marginBottom: '16px' }}>
|
<div style={{ marginBottom: '16px' }}>
|
||||||
<label htmlFor="email" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
|
<label htmlFor="email" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
|
||||||
Email
|
Email
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
id="email"
|
id="email"
|
||||||
required
|
required
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
padding: '8px',
|
padding: '8px',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
border: '1px solid #ccc',
|
border: '1px solid #ccc',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginBottom: '16px' }}>
|
<div style={{ marginBottom: '16px' }}>
|
||||||
<label htmlFor="password" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
|
<label htmlFor="password" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
|
||||||
Password
|
Password
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
id="password"
|
id="password"
|
||||||
required
|
required
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
padding: '8px',
|
padding: '8px',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
border: '1px solid #ccc',
|
border: '1px solid #ccc',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
padding: '12px',
|
padding: '12px',
|
||||||
backgroundColor: '#007bff',
|
backgroundColor: '#007bff',
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
marginBottom: '16px',
|
marginBottom: '16px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Sign In
|
Sign In
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
marginBottom: '16px',
|
marginBottom: '16px',
|
||||||
color: '#555',
|
color: '#555',
|
||||||
fontSize: '14px',
|
fontSize: '14px',
|
||||||
borderBottom: '1px solid #ccc',
|
borderBottom: '1px solid #ccc',
|
||||||
lineHeight: '0.1em',
|
lineHeight: '0.1em',
|
||||||
margin: '10px 0 20px',
|
margin: '10px 0 20px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span style={{ background: '#fff', padding: '0 10px' }}>OR</span>
|
<span style={{ background: '#fff', padding: '0 10px' }}>OR</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ textAlign: 'center', marginBottom: '16px' }}>
|
<div style={{ textAlign: 'center', marginBottom: '16px' }}>
|
||||||
<GoogleLogin
|
<GoogleLogin
|
||||||
onSuccess={handleGoogleLoginSuccess}
|
onSuccess={handleGoogleLoginSuccess}
|
||||||
onError={handleGoogleLoginError}
|
onError={handleGoogleLoginError}
|
||||||
size="large"
|
size="large"
|
||||||
width="100%"
|
width="100%"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p style={{ textAlign: 'center', color: '#555', marginTop: '16px' }}>
|
<p style={{ textAlign: 'center', color: '#555', marginTop: '16px' }}>
|
||||||
Don't have an account?{' '}
|
Don't have an account?{' '}
|
||||||
<Link to="/register" style={{ color: '#007bff', textDecoration: 'none' }}>
|
<Link to="/register" style={{ color: '#007bff', textDecoration: 'none' }}>
|
||||||
Sign Up
|
Sign Up
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const LoginForm: React.FC = () => {
|
const LoginForm: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<GoogleOAuthProvider clientId={CLIENT_ID}>
|
<GoogleOAuthProvider clientId={CLIENT_ID}>
|
||||||
<LoginFormContent />
|
<LoginFormContent />
|
||||||
</GoogleOAuthProvider>
|
</GoogleOAuthProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LoginForm;
|
export default LoginForm;
|
||||||
|
@ -1,146 +1,146 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import Avatar from '@mui/material/Avatar';
|
import Avatar from '@mui/material/Avatar';
|
||||||
import { MdAddCircleOutline } from "react-icons/md";
|
import { MdAddCircleOutline } from "react-icons/md";
|
||||||
import { GoHistory } from "react-icons/go";
|
import { GoHistory } from "react-icons/go";
|
||||||
import { CgLogIn } from "react-icons/cg";
|
import { CgLogIn } from "react-icons/cg";
|
||||||
import BackImage from '../assets/smallheadicon.png';
|
import BackImage from '../assets/smallheadicon.png';
|
||||||
|
|
||||||
export interface NavigationItem {
|
export interface NavigationItem {
|
||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
title: string;
|
title: string;
|
||||||
link: string;
|
link: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NavigationItems: NavigationItem[] = [
|
const NavigationItems: NavigationItem[] = [
|
||||||
{
|
{
|
||||||
title: 'New Chat',
|
title: 'New Chat',
|
||||||
link: '/dashboard/new-chat',
|
link: '/dashboard/new-chat',
|
||||||
icon: <MdAddCircleOutline size={30} />
|
icon: <MdAddCircleOutline size={30} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'History',
|
title: 'History',
|
||||||
link: '/dashboard/history',
|
link: '/dashboard/history',
|
||||||
icon: <GoHistory size={25} />
|
icon: <GoHistory size={25} />
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
interface NavigationProps {
|
interface NavigationProps {
|
||||||
isExpanded: boolean;
|
isExpanded: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Navigation = ({ isExpanded = false }: NavigationProps) => {
|
const Navigation = ({ isExpanded = false }: NavigationProps) => {
|
||||||
const [theme, setTheme] = useState<'dark' | 'light'>('light');
|
const [theme, setTheme] = useState<'dark' | 'light'>('light');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (window.matchMedia('(prefers-color-scheme:dark)').matches) {
|
if (window.matchMedia('(prefers-color-scheme:dark)').matches) {
|
||||||
setTheme('dark');
|
setTheme('dark');
|
||||||
} else {
|
} else {
|
||||||
setTheme('light');
|
setTheme('light');
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (theme === "dark") {
|
if (theme === "dark") {
|
||||||
document.documentElement.classList.add("dark");
|
document.documentElement.classList.add("dark");
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.classList.remove("dark");
|
document.documentElement.classList.remove("dark");
|
||||||
}
|
}
|
||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const [user, setUser] = useState<any>(null);
|
const [user, setUser] = useState<any>(null);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const storedUser = localStorage.getItem('user');
|
const storedUser = localStorage.getItem('user');
|
||||||
if (storedUser) {
|
if (storedUser) {
|
||||||
setUser(JSON.parse(storedUser));
|
setUser(JSON.parse(storedUser));
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='h-full p-3 w-fit'>
|
<div className='h-full p-3 w-fit'>
|
||||||
<div className='h-full rounded-xl border flex flex-col px-1 justify-between py-2 items-center dark:bg-slate-300 shadow-xl'>
|
<div className='h-full rounded-xl border flex flex-col px-1 justify-between py-2 items-center dark:bg-slate-300 shadow-xl'>
|
||||||
<div className='flex flex-col items-start gap-12'>
|
<div className='flex flex-col items-start gap-12'>
|
||||||
<Link to='/' className='w-full flex items-center justify-center'>
|
<Link to='/' className='w-full flex items-center justify-center'>
|
||||||
<IconButton sx={{ width: 40, height: 40 }}>
|
<IconButton sx={{ width: 40, height: 40 }}>
|
||||||
<img src={BackImage} width={25} alt="Back" />
|
<img src={BackImage} width={25} alt="Back" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
{isExpanded && (
|
{isExpanded && (
|
||||||
<p className='text-2xl font-semibold text-dark-blue flex items-center'>
|
<p className='text-2xl font-semibold text-dark-blue flex items-center'>
|
||||||
Health AI
|
Health AI
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
<div className='flex flex-col p-1 gap-5 items-center'>
|
<div className='flex flex-col p-1 gap-5 items-center'>
|
||||||
{NavigationItems.map((item) => (
|
{NavigationItems.map((item) => (
|
||||||
<Link
|
<Link
|
||||||
key={item.link}
|
key={item.link}
|
||||||
to={item.link}
|
to={item.link}
|
||||||
className='flex gap-2 items-center w-full'
|
className='flex gap-2 items-center w-full'
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
sx={{
|
sx={{
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 40,
|
height: 40,
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
backgroundColor: '#FFFFFF',
|
backgroundColor: '#FFFFFF',
|
||||||
...(theme === 'dark' && {
|
...(theme === 'dark' && {
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
backgroundColor: '#eef3f4',
|
backgroundColor: '#eef3f4',
|
||||||
borderColor: '#0062cc',
|
borderColor: '#0062cc',
|
||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.icon}
|
{item.icon}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
{isExpanded && item.title}
|
{isExpanded && item.title}
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-center gap-2">
|
<div className="flex flex-col items-center gap-2">
|
||||||
<Link to={user ? '/profile' : '/login'} className="flex items-center">
|
<Link to={user ? '/profile' : '/login'} className="flex items-center">
|
||||||
<IconButton
|
<IconButton
|
||||||
sx={{
|
sx={{
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 40,
|
height: 40,
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
backgroundColor: '#FFFFFF',
|
backgroundColor: '#FFFFFF',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{user ? (
|
{user ? (
|
||||||
<Avatar alt={user.name} src={user.picture} />
|
<Avatar alt={user.name} src={user.picture} />
|
||||||
) : (
|
) : (
|
||||||
<CgLogIn size={24} />
|
<CgLogIn size={24} />
|
||||||
)}
|
)}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Link>
|
</Link>
|
||||||
{/*<button onClick={handleThemeSwitch} className='flex items-center gap-2'>*/}
|
{/*<button onClick={handleThemeSwitch} className='flex items-center gap-2'>*/}
|
||||||
{/* <IconButton*/}
|
{/* <IconButton*/}
|
||||||
{/* sx={{*/}
|
{/* sx={{*/}
|
||||||
{/* width: 40,*/}
|
{/* width: 40,*/}
|
||||||
{/* height: 40,*/}
|
{/* height: 40,*/}
|
||||||
{/* borderRadius: 2,*/}
|
{/* borderRadius: 2,*/}
|
||||||
{/* background: theme === 'dark' ? 'white' : 'initial',*/}
|
{/* background: theme === 'dark' ? 'white' : 'initial',*/}
|
||||||
{/* '&:focus-visible': {*/}
|
{/* '&:focus-visible': {*/}
|
||||||
{/* outline: '2px solid blue',*/}
|
{/* outline: '2px solid blue',*/}
|
||||||
{/* outlineOffset: '0px',*/}
|
{/* outlineOffset: '0px',*/}
|
||||||
{/* borderRadius: '4px',*/}
|
{/* borderRadius: '4px',*/}
|
||||||
{/* },*/}
|
{/* },*/}
|
||||||
{/* }}*/}
|
{/* }}*/}
|
||||||
{/* >*/}
|
{/* >*/}
|
||||||
{/* {theme === 'light' ? <CiLight size={30} /> : <MdOutlineDarkMode size={30} />}*/}
|
{/* {theme === 'light' ? <CiLight size={30} /> : <MdOutlineDarkMode size={30} />}*/}
|
||||||
{/* </IconButton>*/}
|
{/* </IconButton>*/}
|
||||||
{/* {isExpanded && (theme === 'light' ? 'Light mode' : 'Dark mode')}*/}
|
{/* {isExpanded && (theme === 'light' ? 'Light mode' : 'Dark mode')}*/}
|
||||||
{/*</button>*/}
|
{/*</button>*/}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Navigation;
|
export default Navigation;
|
||||||
|
@ -1,186 +1,186 @@
|
|||||||
import React, { useEffect, useState, useRef } from 'react';
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import gsap from 'gsap';
|
import gsap from 'gsap';
|
||||||
import { useGSAP } from '@gsap/react';
|
import { useGSAP } from '@gsap/react';
|
||||||
import { useLazySendChatQuestionQuery } from '../store/api/chatApi';
|
import { useLazySendChatQuestionQuery } from '../store/api/chatApi';
|
||||||
import callCenterIcon from '../assets/call-center.png';
|
import callCenterIcon from '../assets/call-center.png';
|
||||||
|
|
||||||
interface ChatMessage {
|
interface ChatMessage {
|
||||||
sender: string;
|
sender: string;
|
||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NewChatPage: React.FC = () => {
|
const NewChatPage: React.FC = () => {
|
||||||
const [sendChatQuestion, { isLoading, isFetching }] = useLazySendChatQuestionQuery();
|
const [sendChatQuestion, { isLoading, isFetching }] = useLazySendChatQuestionQuery();
|
||||||
const [message, setMessage] = useState<string>('');
|
const [message, setMessage] = useState<string>('');
|
||||||
const [chatHistory, setChatHistory] = useState<ChatMessage[]>([]);
|
const [chatHistory, setChatHistory] = useState<ChatMessage[]>([]);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||||
}, [chatHistory, isLoading, isFetching]);
|
}, [chatHistory, isLoading, isFetching]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setChatHistory([]);
|
setChatHistory([]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
async function onSubmit() {
|
async function onSubmit() {
|
||||||
if (!message.trim()) return;
|
if (!message.trim()) return;
|
||||||
|
|
||||||
const newUserMessage: ChatMessage = { sender: 'User', text: message };
|
const newUserMessage: ChatMessage = { sender: 'User', text: message };
|
||||||
const updatedHistory = [...chatHistory, newUserMessage];
|
const updatedHistory = [...chatHistory, newUserMessage];
|
||||||
setChatHistory(updatedHistory);
|
setChatHistory(updatedHistory);
|
||||||
const userMessage = message;
|
const userMessage = message;
|
||||||
setMessage('');
|
setMessage('');
|
||||||
|
|
||||||
const storedUser = localStorage.getItem('user');
|
const storedUser = localStorage.getItem('user');
|
||||||
const email = storedUser ? JSON.parse(storedUser).email : '';
|
const email = storedUser ? JSON.parse(storedUser).email : '';
|
||||||
|
|
||||||
const question = { query: userMessage, email };
|
const question = { query: userMessage, email };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await sendChatQuestion(question).unwrap();
|
const res = await sendChatQuestion(question).unwrap();
|
||||||
console.log('Response from server:', res);
|
console.log('Response from server:', res);
|
||||||
|
|
||||||
let bestAnswer = res.response.best_answer;
|
let bestAnswer = res.response.best_answer;
|
||||||
if (typeof bestAnswer !== 'string') {
|
if (typeof bestAnswer !== 'string') {
|
||||||
bestAnswer = String(bestAnswer);
|
bestAnswer = String(bestAnswer);
|
||||||
}
|
}
|
||||||
bestAnswer = bestAnswer.trim();
|
bestAnswer = bestAnswer.trim();
|
||||||
|
|
||||||
const newAssistantMessage: ChatMessage = { sender: 'Assistant', text: bestAnswer };
|
const newAssistantMessage: ChatMessage = { sender: 'Assistant', text: bestAnswer };
|
||||||
const newUpdatedHistory = [...updatedHistory, newAssistantMessage];
|
const newUpdatedHistory = [...updatedHistory, newAssistantMessage];
|
||||||
setChatHistory(newUpdatedHistory);
|
setChatHistory(newUpdatedHistory);
|
||||||
|
|
||||||
if (res.response.chatId) {
|
if (res.response.chatId) {
|
||||||
const chatString = newUpdatedHistory
|
const chatString = newUpdatedHistory
|
||||||
.map(msg => (msg.sender === 'User' ? 'User: ' : 'Bot: ') + msg.text)
|
.map(msg => (msg.sender === 'User' ? 'User: ' : 'Bot: ') + msg.text)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
navigate(`/dashboard/chat/${res.response.chatId}`, {
|
navigate(`/dashboard/chat/${res.response.chatId}`, {
|
||||||
replace: true,
|
replace: true,
|
||||||
state: {
|
state: {
|
||||||
selectedChat: {
|
selectedChat: {
|
||||||
id: res.response.chatId,
|
id: res.response.chatId,
|
||||||
chat: chatString,
|
chat: chatString,
|
||||||
created_at: new Date().toISOString()
|
created_at: new Date().toISOString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
setChatHistory(prev => [
|
setChatHistory(prev => [
|
||||||
...prev,
|
...prev,
|
||||||
{ sender: 'Assistant', text: 'Something went wrong' }
|
{ sender: 'Assistant', text: 'Something went wrong' }
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useGSAP(() => {
|
useGSAP(() => {
|
||||||
gsap.from('#input', { opacity: 0, y: 5, ease: 'power2.inOut', duration: 0.5 });
|
gsap.from('#input', { opacity: 0, y: 5, ease: 'power2.inOut', duration: 0.5 });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col justify-end items-center p-4 gap-8 h-full w-full">
|
<div className="flex flex-col justify-end items-center p-4 gap-8 h-full w-full">
|
||||||
<div className="w-full p-2 rounded overflow-y-auto h-full mb-4">
|
<div className="w-full p-2 rounded overflow-y-auto h-full mb-4">
|
||||||
{chatHistory.length > 0 ? (
|
{chatHistory.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
{chatHistory.map((msg, index) => (
|
{chatHistory.map((msg, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className={`flex mb-2 ${msg.sender === 'User' ? 'justify-end' : 'justify-start items-start'}`}
|
className={`flex mb-2 ${msg.sender === 'User' ? 'justify-end' : 'justify-start items-start'}`}
|
||||||
>
|
>
|
||||||
{msg.sender === 'Assistant' && (
|
{msg.sender === 'Assistant' && (
|
||||||
<img
|
<img
|
||||||
src={callCenterIcon}
|
src={callCenterIcon}
|
||||||
alt="Call Center Icon"
|
alt="Call Center Icon"
|
||||||
className="w-6 h-6 mr-2"
|
className="w-6 h-6 mr-2"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
className={`p-3 rounded-lg max-w-md flex ${
|
className={`p-3 rounded-lg max-w-md flex ${
|
||||||
msg.sender === 'User'
|
msg.sender === 'User'
|
||||||
? 'bg-blue-500 text-white'
|
? 'bg-blue-500 text-white'
|
||||||
: 'bg-gray-200 text-gray-800'
|
: 'bg-gray-200 text-gray-800'
|
||||||
}`}
|
}`}
|
||||||
style={{ whiteSpace: 'normal' }}
|
style={{ whiteSpace: 'normal' }}
|
||||||
>
|
>
|
||||||
{msg.text.split('\n').map((line, i) => (
|
{msg.text.split('\n').map((line, i) => (
|
||||||
<p key={i}>{line}</p>
|
<p key={i}>{line}</p>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{(isLoading || isFetching) && (
|
{(isLoading || isFetching) && (
|
||||||
<div className="flex mb-2 justify-start items-start">
|
<div className="flex mb-2 justify-start items-start">
|
||||||
<img
|
<img
|
||||||
src={callCenterIcon}
|
src={callCenterIcon}
|
||||||
alt="Call Center Icon"
|
alt="Call Center Icon"
|
||||||
className="w-6 h-6 mr-2"
|
className="w-6 h-6 mr-2"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className="p-3 rounded-lg max-w-md flex bg-gray-200 text-gray-800"
|
className="p-3 rounded-lg max-w-md flex bg-gray-200 text-gray-800"
|
||||||
style={{ whiteSpace: 'normal' }}
|
style={{ whiteSpace: 'normal' }}
|
||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<svg
|
<svg
|
||||||
className="animate-spin h-5 w-5 mr-3 text-gray-500"
|
className="animate-spin h-5 w-5 mr-3 text-gray-500"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<circle
|
<circle
|
||||||
className="opacity-25"
|
className="opacity-25"
|
||||||
cx="12"
|
cx="12"
|
||||||
cy="12"
|
cy="12"
|
||||||
r="10"
|
r="10"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeWidth="4"
|
strokeWidth="4"
|
||||||
></circle>
|
></circle>
|
||||||
<path
|
<path
|
||||||
className="opacity-75"
|
className="opacity-75"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"
|
d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"
|
||||||
></path>
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
<span>Assistant is typing...</span>
|
<span>Assistant is typing...</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full h-full flex flex-col gap-2 items-center justify-center">
|
<div className="w-full h-full flex flex-col gap-2 items-center justify-center">
|
||||||
<h1 className="text-xl" id="firstheading">
|
<h1 className="text-xl" id="firstheading">
|
||||||
Start a New Chat
|
Start a New Chat
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div ref={messagesEndRef} />
|
<div ref={messagesEndRef} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="input" className="w-2/3 mb-20">
|
<div id="input" className="w-2/3 mb-20">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Type your message..."
|
placeholder="Type your message..."
|
||||||
value={message}
|
value={message}
|
||||||
onChange={(e) => setMessage(e.target.value)}
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
className="w-full px-5 py-2 rounded-l-xl outline-none border border-gray-300"
|
className="w-full px-5 py-2 rounded-l-xl outline-none border border-gray-300"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
disabled={isLoading || isFetching}
|
disabled={isLoading || isFetching}
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
className="bg-black text-white font-semibold px-4 py-2 rounded-r-xl hover:bg-slate-700"
|
className="bg-black text-white font-semibold px-4 py-2 rounded-r-xl hover:bg-slate-700"
|
||||||
>
|
>
|
||||||
Send
|
Send
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default NewChatPage;
|
export default NewChatPage;
|
||||||
|
@ -1,233 +1,233 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
Avatar,
|
Avatar,
|
||||||
Paper,
|
Paper,
|
||||||
Button,
|
Button,
|
||||||
TextField,
|
TextField,
|
||||||
IconButton,
|
IconButton,
|
||||||
Divider
|
Divider
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Navbar } from '../pages/LandingPage';
|
import { Navbar } from '../pages/LandingPage';
|
||||||
import CheckIcon from '@mui/icons-material/Check';
|
import CheckIcon from '@mui/icons-material/Check';
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
|
|
||||||
const Profile: React.FC = () => {
|
const Profile: React.FC = () => {
|
||||||
const [user, setUser] = useState<any>(null);
|
const [user, setUser] = useState<any>(null);
|
||||||
const [editing, setEditing] = useState<boolean>(false);
|
const [editing, setEditing] = useState<boolean>(false);
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: '',
|
name: '',
|
||||||
email: '',
|
email: '',
|
||||||
phone: '',
|
phone: '',
|
||||||
role: '',
|
role: '',
|
||||||
bio: '',
|
bio: '',
|
||||||
picture: '',
|
picture: '',
|
||||||
});
|
});
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const storedUser = localStorage.getItem('user');
|
const storedUser = localStorage.getItem('user');
|
||||||
if (storedUser) {
|
if (storedUser) {
|
||||||
const parsedUser = JSON.parse(storedUser);
|
const parsedUser = JSON.parse(storedUser);
|
||||||
setUser(parsedUser);
|
setUser(parsedUser);
|
||||||
setFormData({
|
setFormData({
|
||||||
name: parsedUser.name || '',
|
name: parsedUser.name || '',
|
||||||
email: parsedUser.email || '',
|
email: parsedUser.email || '',
|
||||||
phone: parsedUser.phone || '',
|
phone: parsedUser.phone || '',
|
||||||
role: parsedUser.role || '',
|
role: parsedUser.role || '',
|
||||||
bio: parsedUser.bio || '',
|
bio: parsedUser.bio || '',
|
||||||
picture: parsedUser.picture || '',
|
picture: parsedUser.picture || '',
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
navigate('/login');
|
navigate('/login');
|
||||||
}
|
}
|
||||||
}, [navigate]);
|
}, [navigate]);
|
||||||
|
|
||||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setFormData(prev => ({
|
setFormData(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[e.target.name]: e.target.value,
|
[e.target.name]: e.target.value,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancelEdit = () => {
|
const handleCancelEdit = () => {
|
||||||
setFormData({
|
setFormData({
|
||||||
name: user.name || '',
|
name: user.name || '',
|
||||||
email: user.email || '',
|
email: user.email || '',
|
||||||
phone: user.phone || '',
|
phone: user.phone || '',
|
||||||
role: user.role || '',
|
role: user.role || '',
|
||||||
bio: user.bio || '',
|
bio: user.bio || '',
|
||||||
picture: user.picture || '',
|
picture: user.picture || '',
|
||||||
});
|
});
|
||||||
setEditing(false);
|
setEditing(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveEdit = async () => {
|
const handleSaveEdit = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://localhost:5000/api/update_profile', {
|
const response = await fetch('http://localhost:5000/api/update_profile', {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(formData),
|
body: JSON.stringify(formData),
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const updatedUser = { ...user, ...formData };
|
const updatedUser = { ...user, ...formData };
|
||||||
setUser(updatedUser);
|
setUser(updatedUser);
|
||||||
localStorage.setItem('user', JSON.stringify(updatedUser));
|
localStorage.setItem('user', JSON.stringify(updatedUser));
|
||||||
setEditing(false);
|
setEditing(false);
|
||||||
} else {
|
} else {
|
||||||
alert(data.error || 'Error updating profile');
|
alert(data.error || 'Error updating profile');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
alert('Error updating profile');
|
alert('Error updating profile');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
background: 'linear-gradient(to right, #d0e7ff, #f0f8ff)',
|
background: 'linear-gradient(to right, #d0e7ff, #f0f8ff)',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Navbar user={user} setUser={setUser} />
|
<Navbar user={user} setUser={setUser} />
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
p: 4,
|
p: 4,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Paper
|
<Paper
|
||||||
elevation={3}
|
elevation={3}
|
||||||
sx={{
|
sx={{
|
||||||
p: 4,
|
p: 4,
|
||||||
borderRadius: '12px',
|
borderRadius: '12px',
|
||||||
maxWidth: '500px',
|
maxWidth: '500px',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
src={user.picture}
|
src={user.picture}
|
||||||
alt={user.name}
|
alt={user.name}
|
||||||
sx={{ width: 100, height: 100, mb: 2 }}
|
sx={{ width: 100, height: 100, mb: 2 }}
|
||||||
/>
|
/>
|
||||||
{editing ? (
|
{editing ? (
|
||||||
<>
|
<>
|
||||||
<TextField
|
<TextField
|
||||||
label="Name"
|
label="Name"
|
||||||
name="name"
|
name="name"
|
||||||
value={formData.name}
|
value={formData.name}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
fullWidth
|
fullWidth
|
||||||
sx={{ mb: 2 }}
|
sx={{ mb: 2 }}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label="Email"
|
label="Email"
|
||||||
name="email"
|
name="email"
|
||||||
value={formData.email}
|
value={formData.email}
|
||||||
fullWidth
|
fullWidth
|
||||||
sx={{ mb: 2 }}
|
sx={{ mb: 2 }}
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label="Phone"
|
label="Phone"
|
||||||
name="phone"
|
name="phone"
|
||||||
value={formData.phone}
|
value={formData.phone}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
fullWidth
|
fullWidth
|
||||||
sx={{ mb: 2 }}
|
sx={{ mb: 2 }}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label="Role"
|
label="Role"
|
||||||
name="role"
|
name="role"
|
||||||
value={formData.role}
|
value={formData.role}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
fullWidth
|
fullWidth
|
||||||
sx={{ mb: 2 }}
|
sx={{ mb: 2 }}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label="Bio"
|
label="Bio"
|
||||||
name="bio"
|
name="bio"
|
||||||
value={formData.bio}
|
value={formData.bio}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
fullWidth
|
fullWidth
|
||||||
multiline
|
multiline
|
||||||
rows={3}
|
rows={3}
|
||||||
sx={{ mb: 2 }}
|
sx={{ mb: 2 }}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label="Picture URL"
|
label="Picture URL"
|
||||||
name="picture"
|
name="picture"
|
||||||
value={formData.picture}
|
value={formData.picture}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
fullWidth
|
fullWidth
|
||||||
sx={{ mb: 2 }}
|
sx={{ mb: 2 }}
|
||||||
/>
|
/>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'space-around',
|
justifyContent: 'space-around',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
mt: 2,
|
mt: 2,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconButton onClick={handleSaveEdit} color="primary">
|
<IconButton onClick={handleSaveEdit} color="primary">
|
||||||
<CheckIcon />
|
<CheckIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton onClick={handleCancelEdit} color="error">
|
<IconButton onClick={handleCancelEdit} color="error">
|
||||||
<CloseIcon />
|
<CloseIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Typography
|
<Typography
|
||||||
variant="h5"
|
variant="h5"
|
||||||
sx={{ fontWeight: 'bold', color: '#0d47a1', mb: 1 }}
|
sx={{ fontWeight: 'bold', color: '#0d47a1', mb: 1 }}
|
||||||
>
|
>
|
||||||
{user.name}
|
{user.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" sx={{ color: '#424242', mb: 2 }}>
|
<Typography variant="body1" sx={{ color: '#424242', mb: 2 }}>
|
||||||
{user.email}
|
{user.email}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Divider sx={{ width: '100%', mb: 2 }} />
|
<Divider sx={{ width: '100%', mb: 2 }} />
|
||||||
<Typography variant="body1" sx={{ color: '#424242', mb: 1 }}>
|
<Typography variant="body1" sx={{ color: '#424242', mb: 1 }}>
|
||||||
<strong>Phone:</strong> {user.phone || 'Not provided'}
|
<strong>Phone:</strong> {user.phone || 'Not provided'}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" sx={{ color: '#424242', mb: 1 }}>
|
<Typography variant="body1" sx={{ color: '#424242', mb: 1 }}>
|
||||||
<strong>Role:</strong> {user.role || 'User'}
|
<strong>Role:</strong> {user.role || 'User'}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" sx={{ color: '#424242', mb: 1 }}>
|
<Typography variant="body1" sx={{ color: '#424242', mb: 1 }}>
|
||||||
<strong>Bio:</strong> {user.bio || 'No bio available'}
|
<strong>Bio:</strong> {user.bio || 'No bio available'}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
sx={{ mt: 3, backgroundColor: '#0d47a1' }}
|
sx={{ mt: 3, backgroundColor: '#0d47a1' }}
|
||||||
onClick={() => setEditing(true)}
|
onClick={() => setEditing(true)}
|
||||||
>
|
>
|
||||||
Edit Profile
|
Edit Profile
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Profile;
|
export default Profile;
|
||||||
|
@ -1,240 +1,240 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { GoogleLogin, GoogleOAuthProvider } from '@react-oauth/google';
|
import { GoogleLogin, GoogleOAuthProvider } from '@react-oauth/google';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
const CLIENT_ID = "532143017111-4eqtlp0oejqaovj6rf5l1ergvhrp4vao.apps.googleusercontent.com";
|
const CLIENT_ID = "532143017111-4eqtlp0oejqaovj6rf5l1ergvhrp4vao.apps.googleusercontent.com";
|
||||||
|
|
||||||
const RegistrationFormContent: React.FC = () => {
|
const RegistrationFormContent: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const nameElement = document.getElementById('name') as HTMLInputElement | null;
|
const nameElement = document.getElementById('name') as HTMLInputElement | null;
|
||||||
const emailElement = document.getElementById('email') as HTMLInputElement | null;
|
const emailElement = document.getElementById('email') as HTMLInputElement | null;
|
||||||
const passwordElement = document.getElementById('password') as HTMLInputElement | null;
|
const passwordElement = document.getElementById('password') as HTMLInputElement | null;
|
||||||
const confirmPasswordElement = document.getElementById('confirm-password') as HTMLInputElement | null;
|
const confirmPasswordElement = document.getElementById('confirm-password') as HTMLInputElement | null;
|
||||||
|
|
||||||
if (!nameElement || !emailElement || !passwordElement || !confirmPasswordElement) {
|
if (!nameElement || !emailElement || !passwordElement || !confirmPasswordElement) {
|
||||||
console.error('One or more input fields are missing');
|
console.error('One or more input fields are missing');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = nameElement.value;
|
const name = nameElement.value;
|
||||||
const email = emailElement.value;
|
const email = emailElement.value;
|
||||||
const password = passwordElement.value;
|
const password = passwordElement.value;
|
||||||
const confirmPassword = confirmPasswordElement.value;
|
const confirmPassword = confirmPasswordElement.value;
|
||||||
|
|
||||||
if (password !== confirmPassword) {
|
if (password !== confirmPassword) {
|
||||||
console.error('Passwords do not match');
|
console.error('Passwords do not match');
|
||||||
alert('Passwords do not match');
|
alert('Passwords do not match');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://localhost:5000/api/register', {
|
const response = await fetch('http://localhost:5000/api/register', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ name, email, password }),
|
body: JSON.stringify({ name, email, password }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
console.log('User registered successfully:', data.message);
|
console.log('User registered successfully:', data.message);
|
||||||
const loggedInUser = {
|
const loggedInUser = {
|
||||||
name,
|
name,
|
||||||
email,
|
email,
|
||||||
picture: 'https://via.placeholder.com/150',
|
picture: 'https://via.placeholder.com/150',
|
||||||
};
|
};
|
||||||
localStorage.setItem('user', JSON.stringify(loggedInUser));
|
localStorage.setItem('user', JSON.stringify(loggedInUser));
|
||||||
navigate('/dashboard');
|
navigate('/dashboard');
|
||||||
} else {
|
} else {
|
||||||
console.error('Error:', data.error);
|
console.error('Error:', data.error);
|
||||||
alert(data.error);
|
alert(data.error);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error registering user:', error);
|
console.error('Error registering user:', error);
|
||||||
alert('Error registering user');
|
alert('Error registering user');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGoogleLoginSuccess = async (response: any) => {
|
const handleGoogleLoginSuccess = async (response: any) => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('http://localhost:5000/api/verify', {
|
const res = await fetch('http://localhost:5000/api/verify', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ token: response.credential }),
|
body: JSON.stringify({ token: response.credential }),
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
const loggedInUser = {
|
const loggedInUser = {
|
||||||
name: data.user.name,
|
name: data.user.name,
|
||||||
email: data.user.email,
|
email: data.user.email,
|
||||||
picture: data.user.picture || 'https://via.placeholder.com/150',
|
picture: data.user.picture || 'https://via.placeholder.com/150',
|
||||||
};
|
};
|
||||||
localStorage.setItem('user', JSON.stringify(loggedInUser));
|
localStorage.setItem('user', JSON.stringify(loggedInUser));
|
||||||
navigate('/dashboard');
|
navigate('/dashboard');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error tiken verification:', error);
|
console.error('Error tiken verification:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGoogleLoginError = () => {
|
const handleGoogleLoginError = () => {
|
||||||
console.error('Error auth: Google login failed');
|
console.error('Error auth: Google login failed');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
background: 'rgba(0, 0, 0, 0.5)',
|
background: 'rgba(0, 0, 0, 0.5)',
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
padding: '16px',
|
padding: '16px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="registration-card"
|
className="registration-card"
|
||||||
style={{
|
style={{
|
||||||
maxWidth: '400px',
|
maxWidth: '400px',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
|
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
padding: '24px',
|
padding: '24px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h2 style={{ textAlign: 'center', fontWeight: 'bold', color: '#333', marginBottom: '16px' }}>
|
<h2 style={{ textAlign: 'center', fontWeight: 'bold', color: '#333', marginBottom: '16px' }}>
|
||||||
Create Your Account
|
Create Your Account
|
||||||
</h2>
|
</h2>
|
||||||
<p style={{ textAlign: 'center', color: '#555', marginBottom: '24px' }}>
|
<p style={{ textAlign: 'center', color: '#555', marginBottom: '24px' }}>
|
||||||
Join us to explore personalized health solutions.
|
Join us to explore personalized health solutions.
|
||||||
</p>
|
</p>
|
||||||
<form noValidate autoComplete="off" onSubmit={handleSubmit}>
|
<form noValidate autoComplete="off" onSubmit={handleSubmit}>
|
||||||
<div style={{ marginBottom: '16px' }}>
|
<div style={{ marginBottom: '16px' }}>
|
||||||
<label htmlFor="name" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
|
<label htmlFor="name" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
|
||||||
Name
|
Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="name"
|
id="name"
|
||||||
required
|
required
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
padding: '8px',
|
padding: '8px',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
border: '1px solid #ccc',
|
border: '1px solid #ccc',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginBottom: '16px' }}>
|
<div style={{ marginBottom: '16px' }}>
|
||||||
<label htmlFor="email" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
|
<label htmlFor="email" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
|
||||||
Email
|
Email
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
id="email"
|
id="email"
|
||||||
required
|
required
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
padding: '8px',
|
padding: '8px',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
border: '1px solid #ccc',
|
border: '1px solid #ccc',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginBottom: '16px' }}>
|
<div style={{ marginBottom: '16px' }}>
|
||||||
<label htmlFor="password" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
|
<label htmlFor="password" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
|
||||||
Password
|
Password
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
id="password"
|
id="password"
|
||||||
required
|
required
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
padding: '8px',
|
padding: '8px',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
border: '1px solid #ccc',
|
border: '1px solid #ccc',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginBottom: '24px' }}>
|
<div style={{ marginBottom: '24px' }}>
|
||||||
<label
|
<label
|
||||||
htmlFor="confirm-password"
|
htmlFor="confirm-password"
|
||||||
style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}
|
style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}
|
||||||
>
|
>
|
||||||
Confirm Password
|
Confirm Password
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
id="confirm-password"
|
id="confirm-password"
|
||||||
required
|
required
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
padding: '8px',
|
padding: '8px',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
border: '1px solid #ccc',
|
border: '1px solid #ccc',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
padding: '12px',
|
padding: '12px',
|
||||||
backgroundColor: '#007bff',
|
backgroundColor: '#007bff',
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
marginBottom: '16px',
|
marginBottom: '16px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Register
|
Register
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
marginBottom: '16px',
|
marginBottom: '16px',
|
||||||
color: '#555',
|
color: '#555',
|
||||||
fontSize: '14px',
|
fontSize: '14px',
|
||||||
borderBottom: '1px solid #ccc',
|
borderBottom: '1px solid #ccc',
|
||||||
lineHeight: '0.1em',
|
lineHeight: '0.1em',
|
||||||
margin: '10px 0 20px',
|
margin: '10px 0 20px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span style={{ background: '#fff', padding: '0 10px' }}>OR</span>
|
<span style={{ background: '#fff', padding: '0 10px' }}>OR</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ textAlign: 'center', marginBottom: '16px' }}>
|
<div style={{ textAlign: 'center', marginBottom: '16px' }}>
|
||||||
<GoogleLogin
|
<GoogleLogin
|
||||||
onSuccess={handleGoogleLoginSuccess}
|
onSuccess={handleGoogleLoginSuccess}
|
||||||
onError={handleGoogleLoginError}
|
onError={handleGoogleLoginError}
|
||||||
size="large"
|
size="large"
|
||||||
width="100%"
|
width="100%"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p style={{ textAlign: 'center', color: '#555', marginTop: '16px' }}>
|
<p style={{ textAlign: 'center', color: '#555', marginTop: '16px' }}>
|
||||||
Already have an account?{' '}
|
Already have an account?{' '}
|
||||||
<Link to="/login" style={{ color: '#007bff', textDecoration: 'none' }}>
|
<Link to="/login" style={{ color: '#007bff', textDecoration: 'none' }}>
|
||||||
Sign In
|
Sign In
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const RegistrationForm: React.FC = () => {
|
const RegistrationForm: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<GoogleOAuthProvider clientId={CLIENT_ID}>
|
<GoogleOAuthProvider clientId={CLIENT_ID}>
|
||||||
<RegistrationFormContent />
|
<RegistrationFormContent />
|
||||||
</GoogleOAuthProvider>
|
</GoogleOAuthProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RegistrationForm;
|
export default RegistrationForm;
|
||||||
|
@ -1,37 +1,37 @@
|
|||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
/* Приховує скроллбар у браузерах на основі WebKit (Chrome, Safari) */
|
/* Приховує скроллбар у браузерах на основі WebKit (Chrome, Safari) */
|
||||||
.no-scrollbar::-webkit-scrollbar {
|
.no-scrollbar::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Приховує скроллбар у Firefox */
|
/* Приховує скроллбар у Firefox */
|
||||||
.no-scrollbar {
|
.no-scrollbar {
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Приховує скроллбар в Internet Explorer та Edge */
|
/* Приховує скроллбар в Internet Explorer та Edge */
|
||||||
.no-scrollbar {
|
.no-scrollbar {
|
||||||
-ms-overflow-style: none;
|
-ms-overflow-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* HTML: <div class="loader"></div> */
|
/* HTML: <div class="loader"></div> */
|
||||||
.loader {
|
.loader {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
aspect-ratio: 2;
|
aspect-ratio: 2;
|
||||||
--_g: no-repeat radial-gradient(circle closest-side,#000 90%,#0000);
|
--_g: no-repeat radial-gradient(circle closest-side,#000 90%,#0000);
|
||||||
background:
|
background:
|
||||||
var(--_g) 0% 50%,
|
var(--_g) 0% 50%,
|
||||||
var(--_g) 50% 50%,
|
var(--_g) 50% 50%,
|
||||||
var(--_g) 100% 50%;
|
var(--_g) 100% 50%;
|
||||||
background-size: calc(100%/3) 50%;
|
background-size: calc(100%/3) 50%;
|
||||||
animation: l3 1s infinite linear;
|
animation: l3 1s infinite linear;
|
||||||
}
|
}
|
||||||
@keyframes l3 {
|
@keyframes l3 {
|
||||||
20%{background-position:0% 0%, 50% 50%,100% 50%}
|
20%{background-position:0% 0%, 50% 50%,100% 50%}
|
||||||
40%{background-position:0% 100%, 50% 0%,100% 50%}
|
40%{background-position:0% 100%, 50% 0%,100% 50%}
|
||||||
60%{background-position:0% 50%, 50% 100%,100% 0%}
|
60%{background-position:0% 50%, 50% 100%,100% 0%}
|
||||||
80%{background-position:0% 50%, 50% 50%,100% 100%}
|
80%{background-position:0% 50%, 50% 50%,100% 100%}
|
||||||
}
|
}
|
@ -1,15 +1,15 @@
|
|||||||
import { StrictMode } from 'react'
|
import { StrictMode } from 'react'
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
import App from './App.tsx'
|
import App from './App.tsx'
|
||||||
import { Provider } from 'react-redux'
|
import { Provider } from 'react-redux'
|
||||||
import store from './store/index.ts'
|
import store from './store/index.ts'
|
||||||
|
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<App />
|
<App />
|
||||||
</Provider>
|
</Provider>
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
)
|
)
|
||||||
|
@ -1,227 +1,227 @@
|
|||||||
import React, { useEffect, useState, useRef } from 'react';
|
import React, { useEffect, useState, useRef } from 'react';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { useLazySendChatQuestionQuery } from '../store/api/chatApi';
|
import { useLazySendChatQuestionQuery } from '../store/api/chatApi';
|
||||||
import callCenterIcon from '../assets/call-center.png';
|
import callCenterIcon from '../assets/call-center.png';
|
||||||
|
|
||||||
interface ChatMessage {
|
interface ChatMessage {
|
||||||
sender: string;
|
sender: string;
|
||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const HomePage: React.FC = () => {
|
const HomePage: React.FC = () => {
|
||||||
const [sendChatQuestion, { isLoading, isFetching }] = useLazySendChatQuestionQuery();
|
const [sendChatQuestion, { isLoading, isFetching }] = useLazySendChatQuestionQuery();
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
const [chatHistory, setChatHistory] = useState<ChatMessage[]>([]);
|
const [chatHistory, setChatHistory] = useState<ChatMessage[]>([]);
|
||||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const state = (location.state as any) || {};
|
const state = (location.state as any) || {};
|
||||||
|
|
||||||
const isNewChat = state.newChat === true;
|
const isNewChat = state.newChat === true;
|
||||||
const selectedChat = state.selectedChat || null;
|
const selectedChat = state.selectedChat || null;
|
||||||
const selectedChatId = selectedChat ? selectedChat.id : null;
|
const selectedChatId = selectedChat ? selectedChat.id : null;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||||
}, [chatHistory, isLoading, isFetching]);
|
}, [chatHistory, isLoading, isFetching]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isNewChat && selectedChat && selectedChat.chat) {
|
if (!isNewChat && selectedChat && selectedChat.chat) {
|
||||||
const messages: ChatMessage[] = selectedChat.chat
|
const messages: ChatMessage[] = selectedChat.chat
|
||||||
.split(/(?=^(User:|Bot:))/m)
|
.split(/(?=^(User:|Bot:))/m)
|
||||||
.map((msg:any) => {
|
.map((msg:any) => {
|
||||||
const trimmed = msg.trim();
|
const trimmed = msg.trim();
|
||||||
const sender = trimmed.startsWith('User:') ? 'User' : 'Assistant';
|
const sender = trimmed.startsWith('User:') ? 'User' : 'Assistant';
|
||||||
return {
|
return {
|
||||||
sender,
|
sender,
|
||||||
text: trimmed.replace(/^User:|^Bot:/, '').trim(),
|
text: trimmed.replace(/^User:|^Bot:/, '').trim(),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
setChatHistory(messages);
|
setChatHistory(messages);
|
||||||
} else {
|
} else {
|
||||||
setChatHistory([]);
|
setChatHistory([]);
|
||||||
}
|
}
|
||||||
}, [isNewChat, selectedChat]);
|
}, [isNewChat, selectedChat]);
|
||||||
|
|
||||||
|
|
||||||
const formatMessage = (text: string) => {
|
const formatMessage = (text: string) => {
|
||||||
let lines: string[] = [];
|
let lines: string[] = [];
|
||||||
|
|
||||||
if (text.includes('\n')) {
|
if (text.includes('\n')) {
|
||||||
lines = text.split('\n');
|
lines = text.split('\n');
|
||||||
} else {
|
} else {
|
||||||
lines = text.split(/(?=\d+\.\s+)/);
|
lines = text.split(/(?=\d+\.\s+)/);
|
||||||
}
|
}
|
||||||
|
|
||||||
lines = lines.map((line) => line.trim()).filter((line) => line !== '');
|
lines = lines.map((line) => line.trim()).filter((line) => line !== '');
|
||||||
if (lines.length === 0) return null;
|
if (lines.length === 0) return null;
|
||||||
|
|
||||||
return lines.map((line, index) => {
|
return lines.map((line, index) => {
|
||||||
if (/^\d+\.\s*/.test(line)) {
|
if (/^\d+\.\s*/.test(line)) {
|
||||||
const colonIndex = line.indexOf(':');
|
const colonIndex = line.indexOf(':');
|
||||||
if (colonIndex !== -1) {
|
if (colonIndex !== -1) {
|
||||||
const firstPart = line.substring(0, colonIndex);
|
const firstPart = line.substring(0, colonIndex);
|
||||||
const rest = line.substring(colonIndex + 1);
|
const rest = line.substring(colonIndex + 1);
|
||||||
return (
|
return (
|
||||||
<div key={index} className="mb-1">
|
<div key={index} className="mb-1">
|
||||||
<strong>{firstPart.trim()}</strong>: {rest.trim()}
|
<strong>{firstPart.trim()}</strong>: {rest.trim()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div key={index} className="mb-1">
|
<div key={index} className="mb-1">
|
||||||
<strong>{line}</strong>
|
<strong>{line}</strong>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return <div key={index}>{line}</div>;
|
return <div key={index}>{line}</div>;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
if (!message.trim()) return;
|
if (!message.trim()) return;
|
||||||
const userMessage = message.trim();
|
const userMessage = message.trim();
|
||||||
setMessage('');
|
setMessage('');
|
||||||
setChatHistory((prev) => [...prev, { sender: 'User', text: userMessage }]);
|
setChatHistory((prev) => [...prev, { sender: 'User', text: userMessage }]);
|
||||||
|
|
||||||
const storedUser = localStorage.getItem('user');
|
const storedUser = localStorage.getItem('user');
|
||||||
const email = storedUser ? JSON.parse(storedUser).email : '';
|
const email = storedUser ? JSON.parse(storedUser).email : '';
|
||||||
const payload = selectedChatId
|
const payload = selectedChatId
|
||||||
? { query: userMessage, chatId: selectedChatId, email }
|
? { query: userMessage, chatId: selectedChatId, email }
|
||||||
: { query: userMessage, email };
|
: { query: userMessage, email };
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await sendChatQuestion(payload).unwrap();
|
const res = await sendChatQuestion(payload).unwrap();
|
||||||
let bestAnswer = res.response.best_answer;
|
let bestAnswer = res.response.best_answer;
|
||||||
if (typeof bestAnswer !== 'string') {
|
if (typeof bestAnswer !== 'string') {
|
||||||
bestAnswer = String(bestAnswer);
|
bestAnswer = String(bestAnswer);
|
||||||
}
|
}
|
||||||
bestAnswer = bestAnswer.trim();
|
bestAnswer = bestAnswer.trim();
|
||||||
|
|
||||||
if (bestAnswer) {
|
if (bestAnswer) {
|
||||||
setChatHistory((prev) => [...prev, { sender: 'Assistant', text: bestAnswer }]);
|
setChatHistory((prev) => [...prev, { sender: 'Assistant', text: bestAnswer }]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!selectedChatId && res.response.chatId) {
|
if (!selectedChatId && res.response.chatId) {
|
||||||
const updatedChatHistory = [...chatHistory, { sender: 'User', text: userMessage }];
|
const updatedChatHistory = [...chatHistory, { sender: 'User', text: userMessage }];
|
||||||
if (bestAnswer) {
|
if (bestAnswer) {
|
||||||
updatedChatHistory.push({ sender: 'Assistant', text: bestAnswer });
|
updatedChatHistory.push({ sender: 'Assistant', text: bestAnswer });
|
||||||
}
|
}
|
||||||
const chatString = updatedChatHistory
|
const chatString = updatedChatHistory
|
||||||
.map((msg) => (msg.sender === 'User' ? 'User: ' : 'Bot: ') + msg.text)
|
.map((msg) => (msg.sender === 'User' ? 'User: ' : 'Bot: ') + msg.text)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
navigate(`/dashboard/chat/${res.response.chatId}`, {
|
navigate(`/dashboard/chat/${res.response.chatId}`, {
|
||||||
replace: true,
|
replace: true,
|
||||||
state: {
|
state: {
|
||||||
selectedChat: {
|
selectedChat: {
|
||||||
id: res.response.chatId,
|
id: res.response.chatId,
|
||||||
chat: chatString,
|
chat: chatString,
|
||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
setChatHistory((prev) => [
|
setChatHistory((prev) => [
|
||||||
...prev,
|
...prev,
|
||||||
{ sender: 'Assistant', text: 'Something went wrong' },
|
{ sender: 'Assistant', text: 'Something went wrong' },
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col justify-end items-center p-4 gap-8 h-full w-full">
|
<div className="flex flex-col justify-end items-center p-4 gap-8 h-full w-full">
|
||||||
<div className="w-full p-2 rounded overflow-y-auto h-full mb-4">
|
<div className="w-full p-2 rounded overflow-y-auto h-full mb-4">
|
||||||
{chatHistory.length === 0 ? (
|
{chatHistory.length === 0 ? (
|
||||||
<div className="flex flex-col items-center justify-center h-full">
|
<div className="flex flex-col items-center justify-center h-full">
|
||||||
<h1 className="text-xl">Start a New Chat</h1>
|
<h1 className="text-xl">Start a New Chat</h1>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
chatHistory.map((msg, index) => {
|
chatHistory.map((msg, index) => {
|
||||||
const formattedMessage = formatMessage(msg.text);
|
const formattedMessage = formatMessage(msg.text);
|
||||||
if (!formattedMessage) return null;
|
if (!formattedMessage) return null;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className={`flex mb-2 ${msg.sender === 'User' ? 'justify-end' : 'justify-start items-start'}`}
|
className={`flex mb-2 ${msg.sender === 'User' ? 'justify-end' : 'justify-start items-start'}`}
|
||||||
>
|
>
|
||||||
{msg.sender === 'Assistant' && (
|
{msg.sender === 'Assistant' && (
|
||||||
<img
|
<img
|
||||||
src={callCenterIcon}
|
src={callCenterIcon}
|
||||||
alt="Call Center Icon"
|
alt="Call Center Icon"
|
||||||
className="w-6 h-6 mr-2"
|
className="w-6 h-6 mr-2"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
className={`p-3 rounded-lg max-w-md ${
|
className={`p-3 rounded-lg max-w-md ${
|
||||||
msg.sender === 'User'
|
msg.sender === 'User'
|
||||||
? 'bg-blue-500 text-white'
|
? 'bg-blue-500 text-white'
|
||||||
: 'bg-gray-200 text-gray-800'
|
: 'bg-gray-200 text-gray-800'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{formattedMessage}
|
{formattedMessage}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(isLoading || isFetching) && (
|
{(isLoading || isFetching) && (
|
||||||
<div className="flex mb-2 justify-start items-start">
|
<div className="flex mb-2 justify-start items-start">
|
||||||
<img src={callCenterIcon} alt="Call Center Icon" className="w-6 h-6 mr-2" />
|
<img src={callCenterIcon} alt="Call Center Icon" className="w-6 h-6 mr-2" />
|
||||||
<div className="p-3 rounded-lg max-w-md bg-gray-200 text-gray-800 flex items-center">
|
<div className="p-3 rounded-lg max-w-md bg-gray-200 text-gray-800 flex items-center">
|
||||||
<svg
|
<svg
|
||||||
className="animate-spin h-5 w-5 mr-3 text-gray-500"
|
className="animate-spin h-5 w-5 mr-3 text-gray-500"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<circle
|
<circle
|
||||||
className="opacity-25"
|
className="opacity-25"
|
||||||
cx="12"
|
cx="12"
|
||||||
cy="12"
|
cy="12"
|
||||||
r="10"
|
r="10"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeWidth="4"
|
strokeWidth="4"
|
||||||
></circle>
|
></circle>
|
||||||
<path
|
<path
|
||||||
className="opacity-75"
|
className="opacity-75"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"
|
d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"
|
||||||
></path>
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
<span>Assistant is typing...</span>
|
<span>Assistant is typing...</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div ref={messagesEndRef} />
|
<div ref={messagesEndRef} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-2/3 mb-20">
|
<div className="w-2/3 mb-20">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Waiting for your question..."
|
placeholder="Waiting for your question..."
|
||||||
value={message}
|
value={message}
|
||||||
onChange={(e) => setMessage(e.target.value)}
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
disabled={isLoading || isFetching}
|
disabled={isLoading || isFetching}
|
||||||
className="w-full px-5 py-2 rounded-l-xl outline-none border border-gray-300"
|
className="w-full px-5 py-2 rounded-l-xl outline-none border border-gray-300"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
disabled={isLoading || isFetching}
|
disabled={isLoading || isFetching}
|
||||||
className="bg-black text-white font-semibold px-4 py-2 rounded-r-xl hover:bg-gray-800 disabled:opacity-50"
|
className="bg-black text-white font-semibold px-4 py-2 rounded-r-xl hover:bg-gray-800 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
Send
|
Send
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default HomePage;
|
export default HomePage;
|
||||||
|
@ -1,186 +1,186 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
|
||||||
import BackImage from '../assets/smallheadicon.png';
|
import BackImage from '../assets/smallheadicon.png';
|
||||||
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
|
||||||
import { Box, Avatar } from '@mui/material';
|
import { Box, Avatar } from '@mui/material';
|
||||||
import gsap from 'gsap';
|
import gsap from 'gsap';
|
||||||
import { useGSAP } from '@gsap/react';
|
import { useGSAP } from '@gsap/react';
|
||||||
import LogoutIcon from '@mui/icons-material/Logout';
|
import LogoutIcon from '@mui/icons-material/Logout';
|
||||||
import LoginIcon from '@mui/icons-material/Login';
|
import LoginIcon from '@mui/icons-material/Login';
|
||||||
import { Link, useNavigate } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
const BouncingArrow = () => {
|
const BouncingArrow = () => {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
mt: 2,
|
mt: 2,
|
||||||
animation: 'bounce 1s infinite',
|
animation: 'bounce 1s infinite',
|
||||||
'@keyframes bounce': {
|
'@keyframes bounce': {
|
||||||
'0%, 100%': {
|
'0%, 100%': {
|
||||||
transform: 'translateY(0)',
|
transform: 'translateY(0)',
|
||||||
},
|
},
|
||||||
'50%': {
|
'50%': {
|
||||||
transform: 'translateY(-10px)',
|
transform: 'translateY(-10px)',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ArrowDownwardIcon fontSize="large" />
|
<ArrowDownwardIcon fontSize="large" />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface NavbarProps {
|
interface NavbarProps {
|
||||||
user: any;
|
user: any;
|
||||||
setUser: (user: any) => void;
|
setUser: (user: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Navbar: React.FC<NavbarProps> = ({ user, setUser }) => {
|
const Navbar: React.FC<NavbarProps> = ({ user, setUser }) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleSignOut = () => {
|
const handleSignOut = () => {
|
||||||
setUser(null);
|
setUser(null);
|
||||||
localStorage.removeItem('user');
|
localStorage.removeItem('user');
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="w-full bg-white shadow-md py-4 px-2 sm:px-8 flex justify-between items-center fixed top-0 left-0 right-0 z-50">
|
<nav className="w-full bg-white shadow-md py-4 px-2 sm:px-8 flex justify-between items-center fixed top-0 left-0 right-0 z-50">
|
||||||
<div className="text-2xl font-semibold text-dark-blue flex items-center">
|
<div className="text-2xl font-semibold text-dark-blue flex items-center">
|
||||||
Health AI
|
Health AI
|
||||||
<img src={BackImage} width={25} alt="Logo" />
|
<img src={BackImage} width={25} alt="Logo" />
|
||||||
</div>
|
</div>
|
||||||
<ul className="flex space-x-6 text-gray-600">
|
<ul className="flex space-x-6 text-gray-600">
|
||||||
<li>
|
<li>
|
||||||
<Link to="/" className="hover:text-bright-blue transition duration-300">
|
<Link to="/" className="hover:text-bright-blue transition duration-300">
|
||||||
Home
|
Home
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<Link to="/about" className="hover:text-bright-blue transition duration-300">
|
<Link to="/about" className="hover:text-bright-blue transition duration-300">
|
||||||
About
|
About
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<Link to="/contact" className="hover:text-bright-blue transition duration-300">
|
<Link to="/contact" className="hover:text-bright-blue transition duration-300">
|
||||||
Contact
|
Contact
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
{user ? (
|
{user ? (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Avatar alt={user.name} src={user.picture} onClick={() => navigate('/profile')} />
|
<Avatar alt={user.name} src={user.picture} onClick={() => navigate('/profile')} />
|
||||||
<LogoutIcon
|
<LogoutIcon
|
||||||
onClick={handleSignOut}
|
onClick={handleSignOut}
|
||||||
sx={{ cursor: 'pointer', color: '#0d47a1', fontSize: '30px' }}
|
sx={{ cursor: 'pointer', color: '#0d47a1', fontSize: '30px' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<LoginIcon
|
<LoginIcon
|
||||||
onClick={() => navigate('/register')}
|
onClick={() => navigate('/register')}
|
||||||
sx={{ cursor: 'pointer', color: '#0d47a1', fontSize: '30px' }}
|
sx={{ cursor: 'pointer', color: '#0d47a1', fontSize: '30px' }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Home: React.FC = () => {
|
const Home: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [user, setUser] = useState<any>(null);
|
const [user, setUser] = useState<any>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const storedUser = localStorage.getItem('user');
|
const storedUser = localStorage.getItem('user');
|
||||||
if (storedUser) {
|
if (storedUser) {
|
||||||
setUser(JSON.parse(storedUser));
|
setUser(JSON.parse(storedUser));
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useGSAP(() => {
|
useGSAP(() => {
|
||||||
gsap.from('#mainheading', { opacity: 0.3, ease: 'power2.inOut', duration: 0.5 });
|
gsap.from('#mainheading', { opacity: 0.3, ease: 'power2.inOut', duration: 0.5 });
|
||||||
gsap.from('#secondheading', { opacity: 0, y: 5, ease: 'power2.inOut', delay: 0.3, duration: 0.5 });
|
gsap.from('#secondheading', { opacity: 0, y: 5, ease: 'power2.inOut', delay: 0.3, duration: 0.5 });
|
||||||
gsap.from('#button', { opacity: 0, y: 5, ease: 'power2.inOut', delay: 0.5, duration: 0.5 });
|
gsap.from('#button', { opacity: 0, y: 5, ease: 'power2.inOut', delay: 0.5, duration: 0.5 });
|
||||||
gsap.from('#features', { opacity: 0, y: 5, ease: 'power2.inOut', delay: 0.7, duration: 0.5 });
|
gsap.from('#features', { opacity: 0, y: 5, ease: 'power2.inOut', delay: 0.7, duration: 0.5 });
|
||||||
gsap.from('#arrow', { opacity: 0, ease: 'power2.inOut', delay: 2, duration: 0.2 });
|
gsap.from('#arrow', { opacity: 0, ease: 'power2.inOut', delay: 2, duration: 0.2 });
|
||||||
gsap.to('#button', { opacity: 1, ease: 'power2.inOut', delay: 2.5, duration: 0.5 });
|
gsap.to('#button', { opacity: 1, ease: 'power2.inOut', delay: 2.5, duration: 0.5 });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleGetStartedClick = () => {
|
const handleGetStartedClick = () => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
navigate('/register');
|
navigate('/register');
|
||||||
} else {
|
} else {
|
||||||
navigate('/dashboard');
|
navigate('/dashboard');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ backgroundColor: '#d0e7ff' }} className="min-h-screen flex flex-col">
|
<div style={{ backgroundColor: '#d0e7ff' }} className="min-h-screen flex flex-col">
|
||||||
<div className="flex-grow flex flex-col items-center justify-center bg-gradient-to-b text-gray-800 p-4 pt-20">
|
<div className="flex-grow flex flex-col items-center justify-center bg-gradient-to-b text-gray-800 p-4 pt-20">
|
||||||
<Navbar user={user} setUser={setUser} />
|
<Navbar user={user} setUser={setUser} />
|
||||||
|
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<h1
|
<h1
|
||||||
id="mainheading"
|
id="mainheading"
|
||||||
className="text-4xl flex items-center sm:text-5xl md:text-6xl font-semibold mb-4 text-center text-dark-blue"
|
className="text-4xl flex items-center sm:text-5xl md:text-6xl font-semibold mb-4 text-center text-dark-blue"
|
||||||
>
|
>
|
||||||
AI Assistant for Your Health
|
AI Assistant for Your Health
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p
|
<p
|
||||||
id="secondheading"
|
id="secondheading"
|
||||||
className="text-base sm:text-lg md:text-xl text-center max-w-2xl mb-8 text-gray-700"
|
className="text-base sm:text-lg md:text-xl text-center max-w-2xl mb-8 text-gray-700"
|
||||||
>
|
>
|
||||||
A solution for personalized health support, including tailored medication guidance and wellness
|
A solution for personalized health support, including tailored medication guidance and wellness
|
||||||
insights. Take care of yourself with the power of modern technology.
|
insights. Take care of yourself with the power of modern technology.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row gap-6 mb-10" id="features">
|
<div className="flex flex-col sm:flex-row gap-6 mb-10" id="features">
|
||||||
<div className="bg-white p-6 rounded-lg max-w-xs text-center shadow-md">
|
<div className="bg-white p-6 rounded-lg max-w-xs text-center shadow-md">
|
||||||
<h3 className="text-xl font-medium mb-3 text-dark-blue">Personalized Prescription</h3>
|
<h3 className="text-xl font-medium mb-3 text-dark-blue">Personalized Prescription</h3>
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600">
|
||||||
Receive tailored medication recommendations specifically designed for your needs.
|
Receive tailored medication recommendations specifically designed for your needs.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white p-6 rounded-lg max-w-xs text-center shadow-md">
|
<div className="bg-white p-6 rounded-lg max-w-xs text-center shadow-md">
|
||||||
<h3 className="text-xl font-medium mb-3 text-dark-blue">Health Monitoring</h3>
|
<h3 className="text-xl font-medium mb-3 text-dark-blue">Health Monitoring</h3>
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600">
|
||||||
Stay informed about your health with real-time monitoring and AI-driven insights.
|
Stay informed about your health with real-time monitoring and AI-driven insights.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white p-6 rounded-lg max-w-xs text-center shadow-md">
|
<div className="bg-white p-6 rounded-lg max-w-xs text-center shadow-md">
|
||||||
<h3 className="text-xl font-medium mb-3 text-dark-blue">Advanced AI Support</h3>
|
<h3 className="text-xl font-medium mb-3 text-dark-blue">Advanced AI Support</h3>
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600">
|
||||||
Utilize AI support to ensure you're following the best routines for a healthier lifestyle.
|
Utilize AI support to ensure you're following the best routines for a healthier lifestyle.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="arrow" className="flex flex-col items-center mt-10 z-0">
|
<div id="arrow" className="flex flex-col items-center mt-10 z-0">
|
||||||
<p className="text-gray-600">Try it out</p>
|
<p className="text-gray-600">Try it out</p>
|
||||||
<BouncingArrow />
|
<BouncingArrow />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="button-wrapper" className="flex justify-center mt-6">
|
<div id="button-wrapper" className="flex justify-center mt-6">
|
||||||
<button
|
<button
|
||||||
id="button"
|
id="button"
|
||||||
onClick={handleGetStartedClick}
|
onClick={handleGetStartedClick}
|
||||||
className="bg-bright-blue text-white font-medium py-2 px-5 rounded hover:bg-deep-blue transition duration-300 shadow-md"
|
className="bg-bright-blue text-white font-medium py-2 px-5 rounded hover:bg-deep-blue transition duration-300 shadow-md"
|
||||||
>
|
>
|
||||||
Get started
|
Get started
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer className="text-center text-gray-500 p-4">
|
<footer className="text-center text-gray-500 p-4">
|
||||||
<p>© {new Date().getFullYear()} Health AI. All rights reserved.</p>
|
<p>© {new Date().getFullYear()} Health AI. All rights reserved.</p>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { Home, Navbar };
|
export { Home, Navbar };
|
||||||
|
@ -1,33 +1,33 @@
|
|||||||
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"
|
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"
|
||||||
|
|
||||||
|
|
||||||
// type chatQuestion = {
|
// type chatQuestion = {
|
||||||
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const chatApi = createApi({
|
const chatApi = createApi({
|
||||||
reducerPath: 'chat',
|
reducerPath: 'chat',
|
||||||
baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:5000' }),
|
baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:5000' }),
|
||||||
endpoints: (builder) => ({
|
endpoints: (builder) => ({
|
||||||
sendTestVersion: builder.query<string, any>({
|
sendTestVersion: builder.query<string, any>({
|
||||||
query: (body) => ({
|
query: (body) => ({
|
||||||
url: '/create-answer',
|
url: '/create-answer',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: body
|
body: body
|
||||||
}),
|
}),
|
||||||
transformResponse: ({ response }) => response
|
transformResponse: ({ response }) => response
|
||||||
}),
|
}),
|
||||||
sendChatQuestion: builder.query<any, any>({
|
sendChatQuestion: builder.query<any, any>({
|
||||||
query: (body) => ({
|
query: (body) => ({
|
||||||
url: '/api/chat',
|
url: '/api/chat',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: body
|
body: body
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default chatApi
|
export default chatApi
|
||||||
export const { useLazySendTestVersionQuery,useLazySendChatQuestionQuery } = chatApi
|
export const { useLazySendTestVersionQuery,useLazySendChatQuestionQuery } = chatApi
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import { configureStore } from "@reduxjs/toolkit";
|
import { configureStore } from "@reduxjs/toolkit";
|
||||||
import chatApi from "./api/chatApi";
|
import chatApi from "./api/chatApi";
|
||||||
import { setupListeners } from '@reduxjs/toolkit/query';
|
import { setupListeners } from '@reduxjs/toolkit/query';
|
||||||
|
|
||||||
|
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
[chatApi.reducerPath]: chatApi.reducer
|
[chatApi.reducerPath]: chatApi.reducer
|
||||||
},
|
},
|
||||||
middleware: (getDefaultMiddleware) =>
|
middleware: (getDefaultMiddleware) =>
|
||||||
getDefaultMiddleware().concat(chatApi.middleware),
|
getDefaultMiddleware().concat(chatApi.middleware),
|
||||||
})
|
})
|
||||||
|
|
||||||
setupListeners(store.dispatch);
|
setupListeners(store.dispatch);
|
||||||
|
|
||||||
export type RootState = ReturnType<typeof store.getState>;
|
export type RootState = ReturnType<typeof store.getState>;
|
||||||
export type AppDispatch = typeof store.dispatch;
|
export type AppDispatch = typeof store.dispatch;
|
||||||
|
|
||||||
export default store;
|
export default store;
|
2
frontend/src/vite-env.d.ts
vendored
2
frontend/src/vite-env.d.ts
vendored
@ -1 +1 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
export default {
|
export default {
|
||||||
content: [
|
content: [
|
||||||
"./index.html",
|
"./index.html",
|
||||||
"./src/**/*.{js,ts,jsx,tsx}",
|
"./src/**/*.{js,ts,jsx,tsx}",
|
||||||
],
|
],
|
||||||
darkMode: "class",
|
darkMode: "class",
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
'light-blue': '#dbeafe',
|
'light-blue': '#dbeafe',
|
||||||
'soft-blue': '#bfdbfe',
|
'soft-blue': '#bfdbfe',
|
||||||
'light-cyan': '#e0f7fa',
|
'light-cyan': '#e0f7fa',
|
||||||
'dark-blue': '#1e3a8a',
|
'dark-blue': '#1e3a8a',
|
||||||
'bright-blue': '#2563eb',
|
'bright-blue': '#2563eb',
|
||||||
'deep-blue': '#1d4ed8',
|
'deep-blue': '#1d4ed8',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,25 +1,25 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
|
||||||
/* Bundler mode */
|
/* Bundler mode */
|
||||||
"moduleResolution": "Bundler",
|
"moduleResolution": "Bundler",
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"noUncheckedSideEffectImports": true
|
"noUncheckedSideEffectImports": true
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"files": [],
|
"files": [],
|
||||||
"references": [
|
"references": [
|
||||||
{ "path": "./tsconfig.app.json" },
|
{ "path": "./tsconfig.app.json" },
|
||||||
{ "path": "./tsconfig.node.json" }
|
{ "path": "./tsconfig.node.json" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
"lib": ["ES2023"],
|
"lib": ["ES2023"],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
|
|
||||||
/* Bundler mode */
|
/* Bundler mode */
|
||||||
"moduleResolution": "Bundler",
|
"moduleResolution": "Bundler",
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"noUncheckedSideEffectImports": true
|
"noUncheckedSideEffectImports": true
|
||||||
},
|
},
|
||||||
"include": ["vite.config.ts"]
|
"include": ["vite.config.ts"]
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user