This commit is contained in:
oleh 2025-04-06 15:27:22 +02:00
parent d1e5ec019d
commit 7e35793ca1
48 changed files with 8815 additions and 8810 deletions

70
.gitignore vendored
View File

@ -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

View File

@ -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")

View File

@ -1,3 +1,3 @@
{ {
"useCloud" : false "useCloud" : false
} }

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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)

View File

@ -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")

View File

@ -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)

View File

@ -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.

View File

@ -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)

View File

@ -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:

View File

@ -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
View File

@ -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?

View File

@ -1,8 +1,8 @@
{ {
"hash": "c4d2d06d", "hash": "c4d2d06d",
"configHash": "9db50785", "configHash": "9db50785",
"lockfileHash": "e3b0c442", "lockfileHash": "e3b0c442",
"browserHash": "efbdb5f3", "browserHash": "efbdb5f3",
"optimized": {}, "optimized": {},
"chunks": {} "chunks": {}
} }

View File

@ -1,3 +1,3 @@
{ {
"type": "module" "type": "module"
} }

View File

@ -1,3 +1,3 @@
{ {
"type": "module" "type": "module"
} }

View File

@ -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"]

View File

@ -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 },
], ],
}, },
}, },
) )

View File

@ -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>

File diff suppressed because it is too large Load Diff

View File

@ -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"
} }
} }

View File

@ -1,6 +1,6 @@
export default { export default {
plugins: { plugins: {
tailwindcss: {}, tailwindcss: {},
autoprefixer: {}, autoprefixer: {},
}, },
} }

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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&apos;t have an account?{' '} Don&apos;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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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%}
} }

View File

@ -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>,
) )

View File

@ -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;

View File

@ -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>&copy; {new Date().getFullYear()} Health AI. All rights reserved.</p> <p>&copy; {new Date().getFullYear()} Health AI. All rights reserved.</p>
</footer> </footer>
</div> </div>
); );
}; };
export { Home, Navbar }; export { Home, Navbar };

View File

@ -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

View File

@ -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;

View File

@ -1 +1 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />

View File

@ -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: [],
} }

View File

@ -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"]
} }

View File

@ -1,7 +1,7 @@
{ {
"files": [], "files": [],
"references": [ "references": [
{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" } { "path": "./tsconfig.node.json" }
] ]
} }

View File

@ -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"]
} }

View File

@ -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()],
}) })