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/
.vs/
*.log
*.tmp
*.swp
frontend/node_modules/
frontend/dist/
*.venv/
Backend/venv/
Backend/__pycache__/
*.dockerignore
*.env
docker-compose.override.yml
.DS_Store
Thumbs.db
*.lock
package-lock.json
.idea/
.vs/
*.log
*.tmp
*.swp
frontend/node_modules/
frontend/dist/
*.venv/
Backend/venv/
Backend/__pycache__/
*.dockerignore
*.env
docker-compose.override.yml
.DS_Store
Thumbs.db
*.lock
package-lock.json
yarn.lock

View File

@ -1,122 +1,122 @@
from pptx import Presentation
from pptx.util import Inches
from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE, MSO_CONNECTOR
# Vytvorenie novej prezentácie
prs = Presentation()
slide_layout = prs.slide_layouts[5] # Prázdny slide
slide = prs.slides.add_slide(slide_layout)
# Definícia základných rozmerov a pozícií
left_margin = Inches(0.5)
top_margin = Inches(0.5)
block_width = Inches(3)
block_height = Inches(0.7)
vertical_gap = Inches(0.3)
horizontal_gap = Inches(0.5)
# 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.text = "Používateľský dotaz\n& Chat history"
# Blok 2: ConversationalAgent (pod box1)
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.text = "ConversationalAgent"
# Blok 3: Klasifikácia dotazu (pod box2)
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.text = "Klasifikácia dotazu"
# Vetvenie: Pozície pre dve vetvy
branch_top = box3_top + block_height + vertical_gap
# Ľavá vetva ("Vyhladavanie")
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.text = "ElasticsearchStore\nvyhľadávanie"
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.text = "Generovanie\ndynamického promptu"
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.text = "Generovanie\nodpovede"
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.text = "Finalizácia\nodpovede"
# Pravá vetva ("Upresnenie")
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.text = "Kombinovanie\ndotazov"
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.text = "ElasticsearchStore\nvyhľadávanie"
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.text = "Generovanie\ndynamického promptu"
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.text = "Generovanie\nodpovedí (2 modely)"
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.text = "Validácia a\nhodnotenie odpovedí"
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.text = "Finalizácia\nodpovede"
# Finálny blok: Výstup (zlúčenie vetiev)
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.text = "Výstup"
# Funkcia na pridanie šípok medzi blokmi
def add_connector(slide, start_shape, end_shape):
start_x = start_shape.left + start_shape.width / 2
start_y = start_shape.top + start_shape.height
end_x = end_shape.left + end_shape.width / 2
end_y = end_shape.top
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:
# connector.line.end_arrowhead.style = 1
return connector
# Prepojenie blokov
add_connector(slide, box1, box2)
add_connector(slide, box2, box3)
# Vetvenie z Box3 do oboch vetiev
mid_point = box3.left + box3.width / 2
branch_mid_y = box3.top + box3.height + vertical_gap/2
# 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)
# 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)
# Prepojenie blokov v ľavej vetve
add_connector(slide, box4A, box5A)
add_connector(slide, box5A, box6A)
add_connector(slide, box6A, box7A)
# Prepojenie blokov v pravej vetve
add_connector(slide, box4B, box5B)
add_connector(slide, box5B, box6B)
add_connector(slide, box6B, box7B)
add_connector(slide, box7B, box8B)
add_connector(slide, box8B, box9B)
# Spojenie oboch vetiev s finálnym blokom "Výstup"
add_connector(slide, box7A, final_box)
add_connector(slide, box9B, final_box)
# Uloženie prezentácie
prs.save("architecture_diagram.pptx")
from pptx import Presentation
from pptx.util import Inches
from pptx.enum.shapes import MSO_AUTO_SHAPE_TYPE, MSO_CONNECTOR
# Vytvorenie novej prezentácie
prs = Presentation()
slide_layout = prs.slide_layouts[5] # Prázdny slide
slide = prs.slides.add_slide(slide_layout)
# Definícia základných rozmerov a pozícií
left_margin = Inches(0.5)
top_margin = Inches(0.5)
block_width = Inches(3)
block_height = Inches(0.7)
vertical_gap = Inches(0.3)
horizontal_gap = Inches(0.5)
# 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.text = "Používateľský dotaz\n& Chat history"
# Blok 2: ConversationalAgent (pod box1)
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.text = "ConversationalAgent"
# Blok 3: Klasifikácia dotazu (pod box2)
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.text = "Klasifikácia dotazu"
# Vetvenie: Pozície pre dve vetvy
branch_top = box3_top + block_height + vertical_gap
# Ľavá vetva ("Vyhladavanie")
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.text = "ElasticsearchStore\nvyhľadávanie"
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.text = "Generovanie\ndynamického promptu"
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.text = "Generovanie\nodpovede"
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.text = "Finalizácia\nodpovede"
# Pravá vetva ("Upresnenie")
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.text = "Kombinovanie\ndotazov"
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.text = "ElasticsearchStore\nvyhľadávanie"
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.text = "Generovanie\ndynamického promptu"
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.text = "Generovanie\nodpovedí (2 modely)"
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.text = "Validácia a\nhodnotenie odpovedí"
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.text = "Finalizácia\nodpovede"
# Finálny blok: Výstup (zlúčenie vetiev)
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.text = "Výstup"
# Funkcia na pridanie šípok medzi blokmi
def add_connector(slide, start_shape, end_shape):
start_x = start_shape.left + start_shape.width / 2
start_y = start_shape.top + start_shape.height
end_x = end_shape.left + end_shape.width / 2
end_y = end_shape.top
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:
# connector.line.end_arrowhead.style = 1
return connector
# Prepojenie blokov
add_connector(slide, box1, box2)
add_connector(slide, box2, box3)
# Vetvenie z Box3 do oboch vetiev
mid_point = box3.left + box3.width / 2
branch_mid_y = box3.top + box3.height + vertical_gap/2
# 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)
# 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)
# Prepojenie blokov v ľavej vetve
add_connector(slide, box4A, box5A)
add_connector(slide, box5A, box6A)
add_connector(slide, box6A, box7A)
# Prepojenie blokov v pravej vetve
add_connector(slide, box4B, box5B)
add_connector(slide, box5B, box6B)
add_connector(slide, box6B, box7B)
add_connector(slide, box7B, box8B)
add_connector(slide, box8B, box9B)
# Spojenie oboch vetiev s finálnym blokom "Výstup"
add_connector(slide, box7A, final_box)
add_connector(slide, box9B, final_box)
# Uloženie prezentácie
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 langchain.embeddings import HuggingFaceEmbeddings
from elasticsearch.helpers import bulk
import json
# Настройка подключения к Elasticsearch с аутентификацией и HTTPS
es = Elasticsearch(
[{'host': 'localhost', 'port': 9200, 'scheme': 'https'}],
http_auth=('elastic', '3lvFhvVYrazLsj=M-R_g'), # замените на ваш пароль
verify_certs=False # Отключить проверку SSL-сертификата, если используется самоподписанный сертификат
)
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
def create_index():
# Определяем маппинг для индекса
mapping = {
"mappings": {
"properties": {
"text": {
"type": "text",
"analyzer": "standard"
},
"vector": {
"type": "dense_vector",
"dims": 384 # Размерность векторного представления
},
"full_data": {
"type": "object",
"enabled": False # Отключаем индексацию вложенных данных
}
}
}
}
es.indices.create(index='drug_docs', body=mapping, ignore=400)
def load_drug_data(json_path):
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
return data
def index_documents(data):
actions = []
total_docs = len(data)
for i, item in enumerate(data, start=1):
doc_text = f"{item['link']} {item.get('pribalovy_letak', '')} {item.get('spc', '')}"
vector = embeddings.embed_query(doc_text)
action = {
"_index": "drug_docs",
"_id": i,
"_source": {
'text': doc_text,
'vector': vector,
'full_data': item
}
}
actions.append(action)
# Отображение прогресса
print(f"Индексируется документ {i}/{total_docs}", end='\r')
# Опционально: индексируем пакетами по N документов
if i % 100 == 0 or i == total_docs:
bulk(es, actions)
actions = []
# Если остались неиндексированные документы
if actions:
bulk(es, actions)
print("\nИндексирование завершено.")
if __name__ == "__main__":
create_index()
data_path = "../../esDB/cleaned_general_info_additional.json"
drug_data = load_drug_data(data_path)
index_documents(drug_data)
from elasticsearch import Elasticsearch
from langchain.embeddings import HuggingFaceEmbeddings
from elasticsearch.helpers import bulk
import json
# Настройка подключения к Elasticsearch с аутентификацией и HTTPS
es = Elasticsearch(
[{'host': 'localhost', 'port': 9200, 'scheme': 'https'}],
http_auth=('elastic', '3lvFhvVYrazLsj=M-R_g'), # замените на ваш пароль
verify_certs=False # Отключить проверку SSL-сертификата, если используется самоподписанный сертификат
)
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
def create_index():
# Определяем маппинг для индекса
mapping = {
"mappings": {
"properties": {
"text": {
"type": "text",
"analyzer": "standard"
},
"vector": {
"type": "dense_vector",
"dims": 384 # Размерность векторного представления
},
"full_data": {
"type": "object",
"enabled": False # Отключаем индексацию вложенных данных
}
}
}
}
es.indices.create(index='drug_docs', body=mapping, ignore=400)
def load_drug_data(json_path):
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
return data
def index_documents(data):
actions = []
total_docs = len(data)
for i, item in enumerate(data, start=1):
doc_text = f"{item['link']} {item.get('pribalovy_letak', '')} {item.get('spc', '')}"
vector = embeddings.embed_query(doc_text)
action = {
"_index": "drug_docs",
"_id": i,
"_source": {
'text': doc_text,
'vector': vector,
'full_data': item
}
}
actions.append(action)
# Отображение прогресса
print(f"Индексируется документ {i}/{total_docs}", end='\r')
# Опционально: индексируем пакетами по N документов
if i % 100 == 0 or i == total_docs:
bulk(es, actions)
actions = []
# Если остались неиндексированные документы
if actions:
bulk(es, actions)
print("\nИндексирование завершено.")
if __name__ == "__main__":
create_index()
data_path = "../../esDB/cleaned_general_info_additional.json"
drug_data = load_drug_data(data_path)
index_documents(drug_data)

View File

@ -1,80 +1,80 @@
from elasticsearch import Elasticsearch
from langchain.embeddings import HuggingFaceEmbeddings
from elasticsearch.helpers import bulk
import json
# Настройка подключения к Elasticsearch с аутентификацией и HTTPS
es = Elasticsearch(
[{'host': 'localhost', 'port': 9200, 'scheme': 'https'}],
http_auth=('elastic', 'S7DoO3ma=G=9USBPbqq3'), # замените на ваш пароль
verify_certs=False # Отключить проверку SSL-сертификата, если используется самоподписанный сертификат
)
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
def create_index():
# Определяем маппинг для индекса
mapping = {
"mappings": {
"properties": {
"text": {
"type": "text",
"analyzer": "standard"
},
"vector": {
"type": "dense_vector",
"dims": 384 # Размерность векторного представления
},
"full_data": {
"type": "object",
"enabled": False # Отключаем индексацию вложенных данных
}
}
}
}
es.indices.create(index='drug_docs', body=mapping, ignore=400)
def load_drug_data(json_path):
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
return data
def index_documents(data):
actions = []
total_docs = len(data)
for i, item in enumerate(data, start=1):
doc_text = f"{item['link']} {item.get('pribalovy_letak', '')} {item.get('spc', '')}"
vector = embeddings.embed_query(doc_text)
action = {
"_index": "drug_docs",
"_id": i,
"_source": {
'text': doc_text,
'vector': vector,
'full_data': item
}
}
actions.append(action)
# Отображение прогресса
print(f"Индексируется документ {i}/{total_docs}", end='\r')
# Опционально: индексируем пакетами по N документов
if i % 100 == 0 or i == total_docs:
bulk(es, actions)
actions = []
# Если остались неиндексированные документы
if actions:
bulk(es, actions)
print("\nИндексирование завершено.")
if __name__ == "__main__":
create_index()
data_path = "/home/poiasnik/esDB/cleaned_general_info_additional.json"
drug_data = load_drug_data(data_path)
index_documents(drug_data)
from elasticsearch import Elasticsearch
from langchain.embeddings import HuggingFaceEmbeddings
from elasticsearch.helpers import bulk
import json
# Настройка подключения к Elasticsearch с аутентификацией и HTTPS
es = Elasticsearch(
[{'host': 'localhost', 'port': 9200, 'scheme': 'https'}],
http_auth=('elastic', 'S7DoO3ma=G=9USBPbqq3'), # замените на ваш пароль
verify_certs=False # Отключить проверку SSL-сертификата, если используется самоподписанный сертификат
)
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
def create_index():
# Определяем маппинг для индекса
mapping = {
"mappings": {
"properties": {
"text": {
"type": "text",
"analyzer": "standard"
},
"vector": {
"type": "dense_vector",
"dims": 384 # Размерность векторного представления
},
"full_data": {
"type": "object",
"enabled": False # Отключаем индексацию вложенных данных
}
}
}
}
es.indices.create(index='drug_docs', body=mapping, ignore=400)
def load_drug_data(json_path):
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
return data
def index_documents(data):
actions = []
total_docs = len(data)
for i, item in enumerate(data, start=1):
doc_text = f"{item['link']} {item.get('pribalovy_letak', '')} {item.get('spc', '')}"
vector = embeddings.embed_query(doc_text)
action = {
"_index": "drug_docs",
"_id": i,
"_source": {
'text': doc_text,
'vector': vector,
'full_data': item
}
}
actions.append(action)
# Отображение прогресса
print(f"Индексируется документ {i}/{total_docs}", end='\r')
# Опционально: индексируем пакетами по N документов
if i % 100 == 0 or i == total_docs:
bulk(es, actions)
actions = []
# Если остались неиндексированные документы
if actions:
bulk(es, actions)
print("\nИндексирование завершено.")
if __name__ == "__main__":
create_index()
data_path = "/home/poiasnik/esDB/cleaned_general_info_additional.json"
drug_data = load_drug_data(data_path)
index_documents(drug_data)

View File

@ -1,41 +1,41 @@
from elasticsearch import Elasticsearch
from langchain_huggingface import HuggingFaceEmbeddings
import json
import sys
es = Elasticsearch(
cloud_id="tt:dXMtZWFzdC0yLmF3cy5lbGFzdGljLWNsb3VkLmNvbTo0NDMkOGM3ODQ0ZWVhZTEyNGY3NmFjNjQyNDFhNjI4NmVhYzMkZTI3YjlkNTQ0ODdhNGViNmEyMTcxMjMxNmJhMWI0ZGU=",
basic_auth=("elastic", "sSz2BEGv56JRNjGFwoQ191RJ")
)
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
def load_drug_data(json_path):
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
return data
def index_documents(data):
total_documents = len(data)
for i, item in enumerate(data, start=1):
doc_text = f"{item['link']} {item.get('pribalovy_letak', '')} {item.get('spc', '')}"
vector = embeddings.embed_query(doc_text)
es.index(index='drug_docs', id=i, body={
'text': doc_text,
'vector': vector,
'full_data': item
})
sys.stdout.flush()
data_path = "../../data_adc_databaza/cleaned_general_info_additional.json"
drug_data = load_drug_data(data_path)
index_documents(drug_data)
from elasticsearch import Elasticsearch
from langchain_huggingface import HuggingFaceEmbeddings
import json
import sys
es = Elasticsearch(
cloud_id="tt:dXMtZWFzdC0yLmF3cy5lbGFzdGljLWNsb3VkLmNvbTo0NDMkOGM3ODQ0ZWVhZTEyNGY3NmFjNjQyNDFhNjI4NmVhYzMkZTI3YjlkNTQ0ODdhNGViNmEyMTcxMjMxNmJhMWI0ZGU=",
basic_auth=("elastic", "sSz2BEGv56JRNjGFwoQ191RJ")
)
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
def load_drug_data(json_path):
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
return data
def index_documents(data):
total_documents = len(data)
for i, item in enumerate(data, start=1):
doc_text = f"{item['link']} {item.get('pribalovy_letak', '')} {item.get('spc', '')}"
vector = embeddings.embed_query(doc_text)
es.index(index='drug_docs', id=i, body={
'text': doc_text,
'vector': vector,
'full_data': item
})
sys.stdout.flush()
data_path = "../../data_adc_databaza/cleaned_general_info_additional.json"
drug_data = load_drug_data(data_path)
index_documents(drug_data)

View File

@ -1,70 +1,70 @@
from elasticsearch import Elasticsearch
from langchain_huggingface import HuggingFaceEmbeddings
from elasticsearch.helpers import bulk
import json
es = Elasticsearch([{'host': 'localhost', 'port': 9200, 'scheme': 'http'}])
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
def create_index():
mapping = {
"mappings": {
"properties": {
"text": {
"type": "text",
"analyzer": "standard"
},
"vector": {
"type": "dense_vector",
"dims": 384
},
"full_data": {
"type": "object",
"enabled": False
}
}
}
}
es.indices.create(index='drug_docs', body=mapping, ignore=400)
def load_drug_data(json_path):
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
return data
def index_documents(data):
actions = []
total_docs = len(data)
for i, item in enumerate(data, start=1):
doc_text = f"{item['link']} {item.get('pribalovy_letak', '')} {item.get('spc', '')}"
vector = embeddings.embed_query(doc_text)
action = {
"_index": "drug_docs",
"_id": i,
"_source": {
'text': doc_text,
'vector': vector,
'full_data': item
}
}
actions.append(action)
if i % 100 == 0 or i == total_docs:
bulk(es, actions)
actions = []
if actions:
bulk(es, actions)
if __name__ == "__main__":
create_index()
data_path = "../../data_adc_databaza/cleaned_general_info_additional.json"
drug_data = load_drug_data(data_path)
index_documents(drug_data)
from elasticsearch import Elasticsearch
from langchain_huggingface import HuggingFaceEmbeddings
from elasticsearch.helpers import bulk
import json
es = Elasticsearch([{'host': 'localhost', 'port': 9200, 'scheme': 'http'}])
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
def create_index():
mapping = {
"mappings": {
"properties": {
"text": {
"type": "text",
"analyzer": "standard"
},
"vector": {
"type": "dense_vector",
"dims": 384
},
"full_data": {
"type": "object",
"enabled": False
}
}
}
}
es.indices.create(index='drug_docs', body=mapping, ignore=400)
def load_drug_data(json_path):
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
return data
def index_documents(data):
actions = []
total_docs = len(data)
for i, item in enumerate(data, start=1):
doc_text = f"{item['link']} {item.get('pribalovy_letak', '')} {item.get('spc', '')}"
vector = embeddings.embed_query(doc_text)
action = {
"_index": "drug_docs",
"_id": i,
"_source": {
'text': doc_text,
'vector': vector,
'full_data': item
}
}
actions.append(action)
if i % 100 == 0 or i == total_docs:
bulk(es, actions)
actions = []
if actions:
bulk(es, actions)
if __name__ == "__main__":
create_index()
data_path = "../../data_adc_databaza/cleaned_general_info_additional.json"
drug_data = load_drug_data(data_path)
index_documents(drug_data)

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,12 @@
Flask
flask-cors
requests
elasticsearch
langchain
langchain-huggingface
langchain-elasticsearch
psycopg2-binary
google-auth
transformers
sentence-transformers>=2.2.2
accelerate
Flask
flask-cors
requests
elasticsearch
langchain
langchain-huggingface
langchain-elasticsearch
psycopg2-binary
google-auth
transformers
sentence-transformers>=2.2.2
accelerate

View File

@ -1,361 +1,366 @@
import time
import re
from flask import Flask, request, jsonify
from flask_cors import CORS
from google.oauth2 import id_token
from google.auth.transport import requests as google_requests
import logging
import psycopg2
from psycopg2.extras import RealDictCursor
from model import process_query_with_mistral
_real_time = time.time
time.time = lambda: _real_time() - 1
# Database connection parameters
DATABASE_CONFIG = {
"dbname": "HealthAIDB",
"user": "postgres",
"password": "Oleg2005",
"host": "postgres",
"port": 5432,
}
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
try:
conn = psycopg2.connect(**DATABASE_CONFIG)
logger.info("Database connection established successfully")
except Exception as e:
logger.error(f"Error connecting to database: {e}", exc_info=True)
conn = None
def init_db():
create_users_query = """
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
google_id TEXT,
password TEXT
);
"""
create_chat_history_query = """
CREATE TABLE IF NOT EXISTS chat_history (
id SERIAL PRIMARY KEY,
user_email TEXT NOT NULL,
chat TEXT NOT NULL,
user_data TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
"""
try:
with conn.cursor() as cur:
cur.execute(create_users_query)
cur.execute(create_chat_history_query)
conn.commit()
logger.info("Database tables initialized successfully")
except Exception as e:
logger.error(f"Error initializing database tables: {e}", exc_info=True)
conn.rollback()
if conn:
init_db()
app = Flask(__name__)
CORS(app, resources={r"/api/*": {"origins": "*"}})
CLIENT_ID = "532143017111-4eqtlp0oejqaovj6rf5l1ergvhrp4vao.apps.googleusercontent.com"
def save_user_to_db(name, email, google_id=None, password=None):
logger.info(f"Saving user {name} with email: {email} to the database")
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute(
"""
INSERT INTO users (name, email, google_id, password)
VALUES (%s, %s, %s, %s)
ON CONFLICT (email) DO NOTHING
""",
(name, email, google_id, password)
)
conn.commit()
logger.info(f"User {name} ({email}) saved successfully")
except Exception as e:
logger.error(f"Error saving user {name} ({email}) to database: {e}", exc_info=True)
@app.route('/api/verify', methods=['POST'])
def verify_token():
logger.info("Received token verification request")
data = request.get_json()
token = data.get('token')
if not token:
logger.warning("Token not provided in request")
return jsonify({'error': 'No token provided'}), 400
try:
id_info = id_token.verify_oauth2_token(token, google_requests.Request(), CLIENT_ID)
user_email = id_info.get('email')
user_name = id_info.get('name')
google_id = id_info.get('sub')
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)
return jsonify({'message': 'Authentication successful', 'user': {'email': user_email, 'name': user_name}}), 200
except ValueError as e:
logger.error(f"Token verification error: {e}", exc_info=True)
return jsonify({'error': 'Invalid token'}), 400
@app.route('/api/register', methods=['POST'])
def register():
logger.info("Received new user registration request")
data = request.get_json()
name = data.get('name')
email = data.get('email')
password = data.get('password')
if not all([name, email, password]):
logger.warning("Not all required fields provided for registration")
return jsonify({'error': 'All fields are required'}), 400
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute("SELECT * FROM users WHERE email = %s", (email,))
existing_user = cur.fetchone()
if existing_user:
logger.warning(f"User with email {email} already exists")
return jsonify({'error': 'User already exists'}), 409
save_user_to_db(name=name, email=email, password=password)
logger.info(f"User {name} ({email}) registered successfully")
return jsonify({'message': 'User registered successfully'}), 201
except Exception as e:
logger.error(f"Error during user registration: {e}", exc_info=True)
return jsonify({'error': str(e)}), 500
@app.route('/api/login', methods=['POST'])
def login():
logger.info("Received login request")
data = request.get_json()
email = data.get('email')
password = data.get('password')
if not all([email, password]):
logger.warning("Email or password not provided")
return jsonify({'error': 'Email and password are required'}), 400
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute("SELECT * FROM users WHERE email = %s", (email,))
user = cur.fetchone()
if not user or user.get('password') != password:
logger.warning(f"Invalid credentials for email: {email}")
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
except Exception as e:
logger.error(f"Error during user login: {e}", exc_info=True)
return jsonify({'error': str(e)}), 500
@app.route('/api/update_profile', methods=['PUT'])
def update_profile():
data = request.get_json()
email = data.get('email')
if not email:
return jsonify({'error': 'Email is required'}), 400
# Fields to update; if not provided, the current value remains.
name = data.get('name')
phone = data.get('phone')
role = data.get('role')
bio = data.get('bio')
picture = data.get('picture')
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute(
"""
UPDATE users
SET name = COALESCE(%s, name),
phone = COALESCE(%s, phone),
role = COALESCE(%s, role),
bio = COALESCE(%s, bio),
picture = COALESCE(%s, picture)
WHERE email = %s
""",
(name, phone, role, bio, picture, email)
)
conn.commit()
logger.info(f"Profile updated for {email}")
return jsonify({'message': 'Profile updated successfully'}), 200
except Exception as e:
logger.error(f"Error updating profile: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/api/chat', methods=['POST'])
def chat():
logger.info("Received chat request")
data = request.get_json()
query = data.get('query', '')
user_email = data.get('email')
chat_id = data.get('chatId')
if not query:
logger.warning("No query provided")
return jsonify({'error': 'No query provided'}), 400
logger.info(f"Processing request for chatId: {chat_id if chat_id else 'new chat'} | Query: {query}")
# Retrieve chat context from the database
chat_context = ""
if chat_id:
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute("SELECT chat, user_data FROM chat_history WHERE id = %s", (chat_id,))
result = cur.fetchone()
if result:
chat_context = result.get("chat", "")
logger.info(f"Loaded chat context for chatId {chat_id}: {chat_context}")
else:
logger.info(f"No chat context found for chatId {chat_id}")
except Exception as e:
logger.error(f"Error loading chat context from DB: {e}", exc_info=True)
logger.info("Calling process_query_with_mistral function")
response_obj = process_query_with_mistral(query, chat_id=chat_id, chat_context=chat_context)
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}")
best_answer = re.sub(r'[*#]', '', best_answer)
best_answer = re.sub(r'(\d\.\s)', r'\n\n\1', best_answer)
best_answer = re.sub(r':\s-', r':\n-', best_answer)
# Update or create chat_history record including user_data if available
if chat_id:
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute("SELECT chat FROM chat_history WHERE id = %s", (chat_id,))
existing_chat = cur.fetchone()
if existing_chat:
updated_chat = existing_chat['chat'] + f"\nUser: {query}\nBot: {best_answer}"
if "patient_data" in response_obj:
cur.execute("UPDATE chat_history SET chat = %s, user_data = %s WHERE id = %s",
(updated_chat, response_obj["patient_data"], chat_id))
else:
cur.execute("UPDATE chat_history SET chat = %s WHERE id = %s", (updated_chat, chat_id))
conn.commit()
logger.info(f"Chat history for chatId {chat_id} updated successfully")
else:
with conn.cursor(cursor_factory=RealDictCursor) as cur2:
cur2.execute(
"INSERT INTO chat_history (user_email, chat) VALUES (%s, %s) RETURNING id",
(user_email, f"User: {query}\nBot: {best_answer}")
)
new_chat_id = cur2.fetchone()['id']
conn.commit()
chat_id = new_chat_id
logger.info(f"New chat created with chatId: {chat_id}")
except Exception as e:
logger.error(f"Error updating/creating chat history: {e}", exc_info=True)
return jsonify({'error': str(e)}), 500
else:
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute(
"INSERT INTO chat_history (user_email, chat) VALUES (%s, %s) RETURNING id",
(user_email, f"User: {query}\nBot: {best_answer}")
)
new_chat_id = cur.fetchone()['id']
conn.commit()
chat_id = new_chat_id
logger.info(f"New chat created with chatId: {chat_id}")
except Exception as e:
logger.error(f"Error creating new chat: {e}", exc_info=True)
return jsonify({'error': str(e)}), 500
return jsonify({'response': {'best_answer': best_answer, 'model': response_obj.get("model", ""), 'chatId': chat_id}}), 200
@app.route('/api/save_user_data', methods=['POST'])
def save_user_data():
logger.info("Received request to save user data")
data = request.get_json()
chat_id = data.get('chatId')
user_data = data.get('userData')
if not chat_id or not user_data:
return jsonify({'error': 'chatId and userData are required'}), 400
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute("UPDATE chat_history SET user_data = %s WHERE id = %s", (user_data, chat_id))
conn.commit()
logger.info(f"User data for chatId {chat_id} updated successfully")
return jsonify({'message': 'User data updated successfully'}), 200
except Exception as e:
logger.error(f"Error updating user data: {e}", exc_info=True)
return jsonify({'error': str(e)}), 500
@app.route('/api/chat_history', methods=['GET'])
def get_chat_history():
logger.info("Received request to get chat history")
user_email = request.args.get('email')
if not user_email:
return jsonify({'error': 'User email is required'}), 400
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute(
"SELECT id, chat, user_data, created_at FROM chat_history WHERE user_email = %s ORDER BY created_at DESC",
(user_email,)
)
history = cur.fetchall()
return jsonify({'history': history}), 200
except Exception as e:
logger.error(f"Error getting chat history for {user_email}: {e}", exc_info=True)
return jsonify({'error': str(e)}), 500
@app.route('/api/chat_history', methods=['DELETE'])
def delete_chat():
logger.info("Received request to delete chat")
chat_id = request.args.get('id')
if not chat_id:
return jsonify({'error': 'Chat id is required'}), 400
try:
with conn.cursor() as cur:
cur.execute("DELETE FROM chat_history WHERE id = %s", (chat_id,))
conn.commit()
return jsonify({'message': 'Chat deleted successfully'}), 200
except Exception as e:
logger.error(f"Error deleting chat with chatId {chat_id}: {e}", exc_info=True)
return jsonify({'error': str(e)}), 500
@app.route('/api/get_user_data', methods=['GET'])
def get_user_data():
chat_id = request.args.get('chatId')
if not chat_id:
return jsonify({'error': 'Chat id is required'}), 400
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute("SELECT user_data FROM chat_history WHERE id = %s", (chat_id,))
result = cur.fetchone()
if result and result.get("user_data"):
return jsonify({'user_data': result.get("user_data")}), 200
else:
return jsonify({'user_data': None}), 200
except Exception as e:
logger.error(f"Error retrieving user data: {e}", exc_info=True)
return jsonify({'error': str(e)}), 500
@app.route('/api/chat_history_detail', methods=['GET'])
def chat_history_detail():
logger.info("Received request to get chat details")
chat_id = request.args.get('id')
if not chat_id:
return jsonify({'error': 'Chat id is required'}), 400
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute("SELECT id, chat, user_data, created_at FROM chat_history WHERE id = %s", (chat_id,))
chat = cur.fetchone()
if not chat:
return jsonify({'error': 'Chat not found'}), 404
return jsonify({'chat': chat}), 200
except Exception as e:
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)
import time
import re
from flask import Flask, request, jsonify
from flask_cors import CORS
from google.oauth2 import id_token
from google.auth.transport import requests as google_requests
import logging
import psycopg2
from psycopg2.extras import RealDictCursor
from model import process_query_with_mistral
_real_time = time.time
time.time = lambda: _real_time() - 1
# Database connection parameters
DATABASE_CONFIG = {
"dbname": "HealthAIDB",
"user": "postgres",
"password": "Oleg2005",
"host": "postgres",
"port": 5432,
}
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
try:
conn = psycopg2.connect(**DATABASE_CONFIG)
logger.info("Database connection established successfully")
except Exception as e:
logger.error(f"Error connecting to database: {e}", exc_info=True)
conn = None
def init_db():
create_users_query = """
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
google_id TEXT,
password TEXT,
phone TEXT,
role TEXT,
bio TEXT,
picture TEXT
);
"""
create_chat_history_query = """
CREATE TABLE IF NOT EXISTS chat_history (
id SERIAL PRIMARY KEY,
user_email TEXT NOT NULL,
chat TEXT NOT NULL,
user_data TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
"""
try:
with conn.cursor() as cur:
cur.execute(create_users_query)
cur.execute(create_chat_history_query)
conn.commit()
logger.info("Database tables initialized successfully")
except Exception as e:
logger.error(f"Error initializing database tables: {e}", exc_info=True)
conn.rollback()
if conn:
init_db()
app = Flask(__name__)
CORS(app, resources={r"/api/*": {"origins": "*"}})
CLIENT_ID = "532143017111-4eqtlp0oejqaovj6rf5l1ergvhrp4vao.apps.googleusercontent.com"
def save_user_to_db(name, email, google_id=None, password=None):
logger.info(f"Saving user {name} with email: {email} to the database")
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute(
"""
INSERT INTO users (name, email, google_id, password)
VALUES (%s, %s, %s, %s)
ON CONFLICT (email) DO NOTHING
""",
(name, email, google_id, password)
)
conn.commit()
logger.info(f"User {name} ({email}) saved successfully")
except Exception as e:
logger.error(f"Error saving user {name} ({email}) to database: {e}", exc_info=True)
@app.route('/api/verify', methods=['POST'])
def verify_token():
logger.info("Received token verification request")
data = request.get_json()
token = data.get('token')
if not token:
logger.warning("Token not provided in request")
return jsonify({'error': 'No token provided'}), 400
try:
id_info = id_token.verify_oauth2_token(token, google_requests.Request(), CLIENT_ID)
user_email = id_info.get('email')
user_name = id_info.get('name')
google_id = id_info.get('sub')
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)
return jsonify({'message': 'Authentication successful', 'user': {'email': user_email, 'name': user_name}}), 200
except ValueError as e:
logger.error(f"Token verification error: {e}", exc_info=True)
return jsonify({'error': 'Invalid token'}), 400
@app.route('/api/register', methods=['POST'])
def register():
logger.info("Received new user registration request")
data = request.get_json()
name = data.get('name')
email = data.get('email')
password = data.get('password')
if not all([name, email, password]):
logger.warning("Not all required fields provided for registration")
return jsonify({'error': 'All fields are required'}), 400
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute("SELECT * FROM users WHERE email = %s", (email,))
existing_user = cur.fetchone()
if existing_user:
logger.warning(f"User with email {email} already exists")
return jsonify({'error': 'User already exists'}), 409
save_user_to_db(name=name, email=email, password=password)
logger.info(f"User {name} ({email}) registered successfully")
return jsonify({'message': 'User registered successfully'}), 201
except Exception as e:
logger.error(f"Error during user registration: {e}", exc_info=True)
return jsonify({'error': str(e)}), 500
@app.route('/api/login', methods=['POST'])
def login():
logger.info("Received login request")
data = request.get_json()
email = data.get('email')
password = data.get('password')
if not all([email, password]):
logger.warning("Email or password not provided")
return jsonify({'error': 'Email and password are required'}), 400
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute("SELECT * FROM users WHERE email = %s", (email,))
user = cur.fetchone()
if not user or user.get('password') != password:
logger.warning(f"Invalid credentials for email: {email}")
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
except Exception as e:
logger.error(f"Error during user login: {e}", exc_info=True)
return jsonify({'error': str(e)}), 500
@app.route('/api/update_profile', methods=['PUT'])
def update_profile():
data = request.get_json()
email = data.get('email')
if not email:
return jsonify({'error': 'Email is required'}), 400
# Fields to update; if not provided, the current value remains.
name = data.get('name')
phone = data.get('phone')
role = data.get('role')
bio = data.get('bio')
picture = data.get('picture')
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute(
"""
UPDATE users
SET name = COALESCE(%s, name),
phone = COALESCE(%s, phone),
role = COALESCE(%s, role),
bio = COALESCE(%s, bio),
picture = COALESCE(%s, picture)
WHERE email = %s
""",
(name, phone, role, bio, picture, email)
)
conn.commit()
logger.info(f"Profile updated for {email}")
return jsonify({'message': 'Profile updated successfully'}), 200
except Exception as e:
logger.error(f"Error updating profile: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/api/chat', methods=['POST'])
def chat():
logger.info("Received chat request")
data = request.get_json()
query = data.get('query', '')
user_email = data.get('email')
chat_id = data.get('chatId')
if not query:
logger.warning("No query provided")
return jsonify({'error': 'No query provided'}), 400
logger.info(f"Processing request for chatId: {chat_id if chat_id else 'new chat'} | Query: {query}")
# Retrieve chat context from the database
chat_context = ""
if chat_id:
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute("SELECT chat, user_data FROM chat_history WHERE id = %s", (chat_id,))
result = cur.fetchone()
if result:
chat_context = result.get("chat", "")
logger.info(f"Loaded chat context for chatId {chat_id}: {chat_context}")
else:
logger.info(f"No chat context found for chatId {chat_id}")
except Exception as e:
logger.error(f"Error loading chat context from DB: {e}", exc_info=True)
logger.info("Calling process_query_with_mistral function")
response_obj = process_query_with_mistral(query, chat_id=chat_id, chat_context=chat_context)
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}")
best_answer = re.sub(r'[*#]', '', best_answer)
best_answer = re.sub(r'(\d\.\s)', r'\n\n\1', best_answer)
best_answer = re.sub(r':\s-', r':\n-', best_answer)
# Update or create chat_history record including user_data if available
if chat_id:
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute("SELECT chat FROM chat_history WHERE id = %s", (chat_id,))
existing_chat = cur.fetchone()
if existing_chat:
updated_chat = existing_chat['chat'] + f"\nUser: {query}\nBot: {best_answer}"
if "patient_data" in response_obj:
cur.execute("UPDATE chat_history SET chat = %s, user_data = %s WHERE id = %s",
(updated_chat, response_obj["patient_data"], chat_id))
else:
cur.execute("UPDATE chat_history SET chat = %s WHERE id = %s", (updated_chat, chat_id))
conn.commit()
logger.info(f"Chat history for chatId {chat_id} updated successfully")
else:
with conn.cursor(cursor_factory=RealDictCursor) as cur2:
cur2.execute(
"INSERT INTO chat_history (user_email, chat) VALUES (%s, %s) RETURNING id",
(user_email, f"User: {query}\nBot: {best_answer}")
)
new_chat_id = cur2.fetchone()['id']
conn.commit()
chat_id = new_chat_id
logger.info(f"New chat created with chatId: {chat_id}")
except Exception as e:
logger.error(f"Error updating/creating chat history: {e}", exc_info=True)
return jsonify({'error': str(e)}), 500
else:
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute(
"INSERT INTO chat_history (user_email, chat) VALUES (%s, %s) RETURNING id",
(user_email, f"User: {query}\nBot: {best_answer}")
)
new_chat_id = cur.fetchone()['id']
conn.commit()
chat_id = new_chat_id
logger.info(f"New chat created with chatId: {chat_id}")
except Exception as e:
logger.error(f"Error creating new chat: {e}", exc_info=True)
return jsonify({'error': str(e)}), 500
return jsonify({'response': {'best_answer': best_answer, 'model': response_obj.get("model", ""), 'chatId': chat_id}}), 200
@app.route('/api/save_user_data', methods=['POST'])
def save_user_data():
logger.info("Received request to save user data")
data = request.get_json()
chat_id = data.get('chatId')
user_data = data.get('userData')
if not chat_id or not user_data:
return jsonify({'error': 'chatId and userData are required'}), 400
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute("UPDATE chat_history SET user_data = %s WHERE id = %s", (user_data, chat_id))
conn.commit()
logger.info(f"User data for chatId {chat_id} updated successfully")
return jsonify({'message': 'User data updated successfully'}), 200
except Exception as e:
logger.error(f"Error updating user data: {e}", exc_info=True)
return jsonify({'error': str(e)}), 500
@app.route('/api/chat_history', methods=['GET'])
def get_chat_history():
logger.info("Received request to get chat history")
user_email = request.args.get('email')
if not user_email:
return jsonify({'error': 'User email is required'}), 400
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute(
"SELECT id, chat, user_data, created_at FROM chat_history WHERE user_email = %s ORDER BY created_at DESC",
(user_email,)
)
history = cur.fetchall()
return jsonify({'history': history}), 200
except Exception as e:
logger.error(f"Error getting chat history for {user_email}: {e}", exc_info=True)
return jsonify({'error': str(e)}), 500
@app.route('/api/chat_history', methods=['DELETE'])
def delete_chat():
logger.info("Received request to delete chat")
chat_id = request.args.get('id')
if not chat_id:
return jsonify({'error': 'Chat id is required'}), 400
try:
with conn.cursor() as cur:
cur.execute("DELETE FROM chat_history WHERE id = %s", (chat_id,))
conn.commit()
return jsonify({'message': 'Chat deleted successfully'}), 200
except Exception as e:
logger.error(f"Error deleting chat with chatId {chat_id}: {e}", exc_info=True)
return jsonify({'error': str(e)}), 500
@app.route('/api/get_user_data', methods=['GET'])
def get_user_data():
chat_id = request.args.get('chatId')
if not chat_id:
return jsonify({'error': 'Chat id is required'}), 400
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute("SELECT user_data FROM chat_history WHERE id = %s", (chat_id,))
result = cur.fetchone()
if result and result.get("user_data"):
return jsonify({'user_data': result.get("user_data")}), 200
else:
return jsonify({'user_data': None}), 200
except Exception as e:
logger.error(f"Error retrieving user data: {e}", exc_info=True)
return jsonify({'error': str(e)}), 500
@app.route('/api/chat_history_detail', methods=['GET'])
def chat_history_detail():
logger.info("Received request to get chat details")
chat_id = request.args.get('id')
if not chat_id:
return jsonify({'error': 'Chat id is required'}), 400
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute("SELECT id, chat, user_data, created_at FROM chat_history WHERE id = %s", (chat_id,))
chat = cur.fetchone()
if not chat:
return jsonify({'error': 'Chat not found'}), 404
return jsonify({'chat': chat}), 200
except Exception as e:
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.util import Inches
# Создание новой презентации
prs = Presentation()
# Добавляем пустой слайд (layout с индексом 5 обычно является пустым)
slide_layout = prs.slide_layouts[5]
slide = prs.slides.add_slide(slide_layout)
# Определяем позицию и размер таблицы
left = Inches(0.5)
top = Inches(1.5)
width = Inches(9)
height = Inches(3)
# Количество строк: 1 заголовок + 2 строки с данными
rows = 3
cols = 4
# Добавляем таблицу на слайд
table = slide.shapes.add_table(rows, cols, left, top, width, height).table
# Устанавливаем ширину столбцов (при необходимости можно настроить отдельно)
table.columns[0].width = Inches(1.5) # Модель
table.columns[1].width = Inches(1) # Оценка
table.columns[2].width = Inches(4) # Текст
table.columns[3].width = Inches(2.5) # Описание
# Заполняем заголовки
table.cell(0, 0).text = "Модель"
table.cell(0, 1).text = "Оценка"
table.cell(0, 2).text = "Текст"
table.cell(0, 3).text = "Описание"
# Данные для первого кандидата
table.cell(1, 0).text = "Mistral Small"
table.cell(1, 1).text = "9.0"
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. "
"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."
)
table.cell(1, 3).text = "Evaluation based on required criteria."
# Данные для второго кандидата
table.cell(2, 0).text = "Mistral Large"
table.cell(2, 1).text = "8.0"
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. "
"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."
# Сохраняем презентацию в файл
prs.save("evaluation_table.pptx")
from pptx import Presentation
from pptx.util import Inches
# Создание новой презентации
prs = Presentation()
# Добавляем пустой слайд (layout с индексом 5 обычно является пустым)
slide_layout = prs.slide_layouts[5]
slide = prs.slides.add_slide(slide_layout)
# Определяем позицию и размер таблицы
left = Inches(0.5)
top = Inches(1.5)
width = Inches(9)
height = Inches(3)
# Количество строк: 1 заголовок + 2 строки с данными
rows = 3
cols = 4
# Добавляем таблицу на слайд
table = slide.shapes.add_table(rows, cols, left, top, width, height).table
# Устанавливаем ширину столбцов (при необходимости можно настроить отдельно)
table.columns[0].width = Inches(1.5) # Модель
table.columns[1].width = Inches(1) # Оценка
table.columns[2].width = Inches(4) # Текст
table.columns[3].width = Inches(2.5) # Описание
# Заполняем заголовки
table.cell(0, 0).text = "Модель"
table.cell(0, 1).text = "Оценка"
table.cell(0, 2).text = "Текст"
table.cell(0, 3).text = "Описание"
# Данные для первого кандидата
table.cell(1, 0).text = "Mistral Small"
table.cell(1, 1).text = "9.0"
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. "
"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."
)
table.cell(1, 3).text = "Evaluation based on required criteria."
# Данные для второго кандидата
table.cell(2, 0).text = "Mistral Large"
table.cell(2, 1).text = "8.0"
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. "
"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."
# Сохраняем презентацию в файл
prs.save("evaluation_table.pptx")

View File

@ -1,34 +1,34 @@
import requests
API_TOKEN = "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"
API_URL = "https://api-inference.huggingface.co/models/google/flan-t5-large"
headers = {
"Authorization": f"Bearer {API_TOKEN}",
"Content-Type": "application/json"
}
def query_flan_t5(prompt):
payload = {
"inputs": prompt,
"parameters": {
"max_length": 250,
"do_sample": True,
"temperature": 0.9,
"top_p": 0.95,
"top_k": 50
}
}
response = requests.post(API_URL, headers=headers, json=payload)
return response.json()
prompt = "Ako sa máš? Daj odpoved v slovencine"
result = query_flan_t5(prompt)
if isinstance(result, list) and len(result) > 0:
print("Ответ от Flan-T5:", result[0]['generated_text'])
else:
import requests
API_TOKEN = "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"
API_URL = "https://api-inference.huggingface.co/models/google/flan-t5-large"
headers = {
"Authorization": f"Bearer {API_TOKEN}",
"Content-Type": "application/json"
}
def query_flan_t5(prompt):
payload = {
"inputs": prompt,
"parameters": {
"max_length": 250,
"do_sample": True,
"temperature": 0.9,
"top_p": 0.95,
"top_k": 50
}
}
response = requests.post(API_URL, headers=headers, json=payload)
return response.json()
prompt = "Ako sa máš? Daj odpoved v slovencine"
result = query_flan_t5(prompt)
if isinstance(result, list) and len(result) > 0:
print("Ответ от Flan-T5:", result[0]['generated_text'])
else:
print("Ошибка при получении ответа:", result)

View File

@ -1,47 +1,47 @@
# import requests
#
# API_TOKEN = "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"
# API_URL = "https://api-inference.huggingface.co/models/google/mt5-base"
#
# headers = {
# "Authorization": f"Bearer {API_TOKEN}",
# "Content-Type": "application/json"
# }
#
# def query_mT5(prompt):
# payload = {
# "inputs": prompt,
# "parameters": {
# "max_length": 100,
# "do_sample": True,
# "temperature": 0.7
# }
# }
# response = requests.post(API_URL, headers=headers, json=payload)
# return response.json()
#
# # Пример использования
# result = query_mT5("Aké sú účinné lieky na horúčku?")
# print("Ответ от mT5:", result)
from transformers import AutoTokenizer, MT5ForConditionalGeneration
tokenizer = AutoTokenizer.from_pretrained("google/mt5-small")
model = MT5ForConditionalGeneration.from_pretrained("google/mt5-small")
# training
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
outputs = model(input_ids=input_ids, labels=labels)
loss = outputs.loss
logits = outputs.logits
# inference
input_ids = tokenizer(
"summarize: studies have shown that owning a dog is good for you", return_tensors="pt"
).input_ids # Batch size 1
outputs = model.generate(input_ids, max_new_tokens=50)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
# studies have shown that owning a dog is good for you.
# import requests
#
# API_TOKEN = "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"
# API_URL = "https://api-inference.huggingface.co/models/google/mt5-base"
#
# headers = {
# "Authorization": f"Bearer {API_TOKEN}",
# "Content-Type": "application/json"
# }
#
# def query_mT5(prompt):
# payload = {
# "inputs": prompt,
# "parameters": {
# "max_length": 100,
# "do_sample": True,
# "temperature": 0.7
# }
# }
# response = requests.post(API_URL, headers=headers, json=payload)
# return response.json()
#
# # Пример использования
# result = query_mT5("Aké sú účinné lieky na horúčku?")
# print("Ответ от mT5:", result)
from transformers import AutoTokenizer, MT5ForConditionalGeneration
tokenizer = AutoTokenizer.from_pretrained("google/mt5-small")
model = MT5ForConditionalGeneration.from_pretrained("google/mt5-small")
# training
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
outputs = model(input_ids=input_ids, labels=labels)
loss = outputs.loss
logits = outputs.logits
# inference
input_ids = tokenizer(
"summarize: studies have shown that owning a dog is good for you", return_tensors="pt"
).input_ids # Batch size 1
outputs = model.generate(input_ids, max_new_tokens=50)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
# studies have shown that owning a dog is good for you.

View File

@ -1,18 +1,18 @@
from sentence_transformers import SentenceTransformer, util
model = SentenceTransformer("TUKE-DeutscheTelekom/slovakbert-skquad-mnlr")
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.",
"V ktorom roku vznikol druhý drevený most cez záliv Zlatý roh?",
"Aká je priemerná dĺžka života v Eritrei?"
]
embeddings = model.encode(sentences)
print("Shape of embeddings:", embeddings.shape)
similarities = util.cos_sim(embeddings, embeddings)
print("Similarity matrix:\n", similarities)
from sentence_transformers import SentenceTransformer, util
model = SentenceTransformer("TUKE-DeutscheTelekom/slovakbert-skquad-mnlr")
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.",
"V ktorom roku vznikol druhý drevený most cez záliv Zlatý roh?",
"Aká je priemerná dĺžka života v Eritrei?"
]
embeddings = model.encode(sentences)
print("Shape of embeddings:", embeddings.shape)
similarities = util.cos_sim(embeddings, embeddings)
print("Similarity matrix:\n", similarities)

View File

@ -1,73 +1,73 @@
version: '3.8'
services:
elasticsearch:
build:
context: ./elasticsearch
dockerfile: Dockerfile
container_name: elasticsearch
environment:
- discovery.type=single-node
- xpack.security.enabled=false
ports:
- "9200:9200"
- "9300:9300"
networks:
- app-network
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:9200/_cluster/health" ]
interval: 10s
timeout: 5s
retries: 5
start_period: 40s
postgres:
image: postgres:14
container_name: postgres_db
environment:
- POSTGRES_DB=HealthAIDB
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=Oleg2005
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- app-network
backend:
container_name: backend_container
build:
context: ./Backend
dockerfile: Dockerfile
ports:
- "5000:5000"
environment:
- ELASTICSEARCH_HOST=http://elasticsearch:9200
- DATABASE_HOST=postgres
depends_on:
elasticsearch:
condition: service_healthy
postgres:
condition: service_started
networks:
- app-network
frontend:
container_name: frontend_container
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "5173:5173"
depends_on:
- backend
networks:
- app-network
networks:
app-network:
driver: bridge
volumes:
pgdata:
version: '3.8'
services:
elasticsearch:
build:
context: ./elasticsearch
dockerfile: Dockerfile
container_name: elasticsearch
environment:
- discovery.type=single-node
- xpack.security.enabled=false
ports:
- "9200:9200"
- "9300:9300"
networks:
- app-network
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:9200/_cluster/health" ]
interval: 10s
timeout: 5s
retries: 5
start_period: 40s
postgres:
image: postgres:14
container_name: postgres_db
environment:
- POSTGRES_DB=HealthAIDB
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=Oleg2005
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- app-network
backend:
container_name: backend_container
build:
context: ./Backend
dockerfile: Dockerfile
ports:
- "5000:5000"
environment:
- ELASTICSEARCH_HOST=http://elasticsearch:9200
- DATABASE_HOST=postgres
depends_on:
elasticsearch:
condition: service_healthy
postgres:
condition: service_started
networks:
- app-network
frontend:
container_name: frontend_container
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "5173:5173"
depends_on:
- backend
networks:
- app-network
networks:
app-network:
driver: bridge
volumes:
pgdata:

View File

@ -1,15 +1,15 @@
FROM docker.elastic.co/elasticsearch/elasticsearch:8.14.3
ENV discovery.type=single-node
ENV xpack.security.enabled=false
COPY --chown=elasticsearch:elasticsearch data/ /usr/share/elasticsearch/data
RUN chmod -R 0775 /usr/share/elasticsearch/data
RUN find /usr/share/elasticsearch/data -type f -name "*.lock" -delete
RUN rm -f /usr/share/elasticsearch/data/nodes/0/node.lock
FROM docker.elastic.co/elasticsearch/elasticsearch:8.14.3
ENV discovery.type=single-node
ENV xpack.security.enabled=false
COPY --chown=elasticsearch:elasticsearch data/ /usr/share/elasticsearch/data
RUN chmod -R 0775 /usr/share/elasticsearch/data
RUN find /usr/share/elasticsearch/data -type f -name "*.lock" -delete
RUN rm -f /usr/share/elasticsearch/data/nodes/0/node.lock

48
frontend/.gitignore vendored
View File

@ -1,24 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -1,8 +1,8 @@
{
"hash": "c4d2d06d",
"configHash": "9db50785",
"lockfileHash": "e3b0c442",
"browserHash": "efbdb5f3",
"optimized": {},
"chunks": {}
{
"hash": "c4d2d06d",
"configHash": "9db50785",
"lockfileHash": "e3b0c442",
"browserHash": "efbdb5f3",
"optimized": {},
"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
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
RUN npm install -g serve
EXPOSE 5173
CMD ["serve", "-s", "dist", "-l", "5173"]
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
RUN npm install -g serve
EXPOSE 5173
CMD ["serve", "-s", "dist", "-l", "5173"]

View File

@ -1,28 +1,28 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
)
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
)

View File

@ -1,12 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Health AI</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Health AI</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -1,46 +1,46 @@
{
"name": "coconuts",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@gsap/react": "^2.1.1",
"@mui/icons-material": "^6.4.3",
"@mui/material": "^6.1.5",
"@react-oauth/google": "^0.12.1",
"@reduxjs/toolkit": "^2.3.0",
"appwrite": "^16.0.2",
"final-form": "^4.20.10",
"gsap": "^3.12.5",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-final-form": "^6.5.9",
"react-icons": "^5.3.0",
"react-redux": "^9.1.2",
"react-router-dom": "^6.27.0"
},
"devDependencies": {
"@eslint/js": "^9.13.0",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.3",
"autoprefixer": "^10.4.20",
"eslint": "^9.13.0",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.13",
"globals": "^15.11.0",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.14",
"typescript": "~5.6.2",
"typescript-eslint": "^8.10.0",
"vite": "^5.4.9"
}
}
{
"name": "coconuts",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@gsap/react": "^2.1.1",
"@mui/icons-material": "^6.4.3",
"@mui/material": "^6.1.5",
"@react-oauth/google": "^0.12.1",
"@reduxjs/toolkit": "^2.3.0",
"appwrite": "^16.0.2",
"final-form": "^4.20.10",
"gsap": "^3.12.5",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-final-form": "^6.5.9",
"react-icons": "^5.3.0",
"react-redux": "^9.1.2",
"react-router-dom": "^6.27.0"
},
"devDependencies": {
"@eslint/js": "^9.13.0",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.3",
"autoprefixer": "^10.4.20",
"eslint": "^9.13.0",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.13",
"globals": "^15.11.0",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.14",
"typescript": "~5.6.2",
"typescript-eslint": "^8.10.0",
"vite": "^5.4.9"
}
}

View File

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

View File

@ -1,45 +1,45 @@
import { BrowserRouter as Router, Route, Routes, Outlet } from 'react-router-dom';
import Navigation from './Components/Navigation';
import {Home} from './pages/LandingPage';
import RegistrationForm from "./Components/RegistrationForm";
import LoginForm from "./Components/LoginForm";
import ChatHistory from "./Components/ChatHistory";
import HomePage from './pages/HomePage';
import NewChatPage from "./Components/NewChatPage";
import About from "./Components/About.tsx";
import Contact from "./Components/Contact.tsx";
import Profile from "./Components/Profile.tsx";
const Layout = () => (
<div className="flex w-full h-screen dark:bg-slate-200">
<Navigation isExpanded={false} />
<div className="flex-grow p-3 h-full">
<main className="h-full w-full border rounded-xl dark:bg-slate-100 shadow-xl">
<Outlet />
</main>
</div>
</div>
);
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/register" element={<RegistrationForm />} />
<Route path="/login" element={<LoginForm />} />
<Route path="/contact" element={<Contact />} />
<Route path="/profile" element={<Profile />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Layout />}>
<Route path="new-chat" element={<NewChatPage />} />
<Route path="chat/:id" element={<HomePage />} />
<Route path="history" element={<ChatHistory />} />
<Route index element={<HomePage />} />
</Route>
</Routes>
</Router>
);
}
export default App;
import { BrowserRouter as Router, Route, Routes, Outlet } from 'react-router-dom';
import Navigation from './Components/Navigation';
import {Home} from './pages/LandingPage';
import RegistrationForm from "./Components/RegistrationForm";
import LoginForm from "./Components/LoginForm";
import ChatHistory from "./Components/ChatHistory";
import HomePage from './pages/HomePage';
import NewChatPage from "./Components/NewChatPage";
import About from "./Components/About.tsx";
import Contact from "./Components/Contact.tsx";
import Profile from "./Components/Profile.tsx";
const Layout = () => (
<div className="flex w-full h-screen dark:bg-slate-200">
<Navigation isExpanded={false} />
<div className="flex-grow p-3 h-full">
<main className="h-full w-full border rounded-xl dark:bg-slate-100 shadow-xl">
<Outlet />
</main>
</div>
</div>
);
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/register" element={<RegistrationForm />} />
<Route path="/login" element={<LoginForm />} />
<Route path="/contact" element={<Contact />} />
<Route path="/profile" element={<Profile />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Layout />}>
<Route path="new-chat" element={<NewChatPage />} />
<Route path="chat/:id" element={<HomePage />} />
<Route path="history" element={<ChatHistory />} />
<Route index element={<HomePage />} />
</Route>
</Routes>
</Router>
);
}
export default App;

View File

@ -1,109 +1,109 @@
import React, { useState, useEffect } from 'react';
import { Box, Typography, Grid, Paper } from '@mui/material';
import {Navbar} from '../pages/LandingPage';
import MedicalServicesIcon from '@mui/icons-material/MedicalServices';
import LocalHospitalIcon from '@mui/icons-material/LocalHospital';
import CodeIcon from '@mui/icons-material/Code';
const About: React.FC = () => {
const [user, setUser] = useState<any>(null);
useEffect(() => {
const storedUser = localStorage.getItem('user');
if (storedUser) {
setUser(JSON.parse(storedUser));
}
}, []);
return (
<Box sx={{ background: 'linear-gradient(to right, #d0e7ff, #f0f8ff)', minHeight: '100vh', p: 4 }}>
{/* Navigation bar */}
<Navbar user={user} setUser={setUser} />
{/* Main content with top padding to account for fixed Navbar */}
<Box sx={{ pt: '80px' }}>
<Typography
variant="h3"
align="center"
gutterBottom
sx={{ fontWeight: 'bold', color: '#0d47a1' }}
>
About Health AI
</Typography>
<Typography
variant="h6"
align="center"
gutterBottom
sx={{ color: '#0d47a1', mb: 4 }}
>
Your Personal AI Assistant for Tailored Drug Recommendations
</Typography>
<Grid container spacing={4} justifyContent="center">
{/* Project Information Card */}
<Grid item xs={12} md={6}>
<Paper elevation={3} sx={{ p: 3, backgroundColor: '#ffffff', borderRadius: '12px' }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<MedicalServicesIcon sx={{ fontSize: 40, color: '#0d47a1', mr: 1 }} />
<Typography variant="h5" sx={{ fontWeight: 'bold', color: '#0d47a1' }}>
About the Project
</Typography>
</Box>
<Typography variant="body1" sx={{ color: '#424242', mb: 2 }}>
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,
context-aware suggestions for both over-the-counter and prescription medications.
</Typography>
<Typography variant="body1" sx={{ color: '#424242' }}>
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.
</Typography>
</Paper>
</Grid>
{/* How It Works Card */}
<Grid item xs={12} md={6}>
<Paper elevation={3} sx={{ p: 3, backgroundColor: '#ffffff', borderRadius: '12px' }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<LocalHospitalIcon sx={{ fontSize: 40, color: '#0d47a1', mr: 1 }} />
<Typography variant="h5" sx={{ fontWeight: 'bold', color: '#0d47a1' }}>
How It Works
</Typography>
</Box>
<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,
and medication type. It then employs vector search techniques to fetch the most relevant information from a comprehensive drug database,
ensuring precise recommendations.
</Typography>
<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.
</Typography>
</Paper>
</Grid>
{/* Future Enhancements Card */}
<Grid item xs={12}>
<Paper elevation={3} sx={{ p: 3, backgroundColor: '#ffffff', borderRadius: '12px' }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<CodeIcon sx={{ fontSize: 40, color: '#0d47a1', mr: 1 }} />
<Typography variant="h5" sx={{ fontWeight: 'bold', color: '#0d47a1' }}>
What's Next?
</Typography>
</Box>
<Typography variant="body1" sx={{ color: '#424242' }}>
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,
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>
</Paper>
</Grid>
</Grid>
{/* Footer */}
<Box sx={{ textAlign: 'center', mt: 6 }}>
<Typography variant="body2" sx={{ color: '#424242' }}>
© {new Date().getFullYear()} Health AI. All rights reserved.
</Typography>
</Box>
</Box>
</Box>
);
};
export default About;
import React, { useState, useEffect } from 'react';
import { Box, Typography, Grid, Paper } from '@mui/material';
import {Navbar} from '../pages/LandingPage';
import MedicalServicesIcon from '@mui/icons-material/MedicalServices';
import LocalHospitalIcon from '@mui/icons-material/LocalHospital';
import CodeIcon from '@mui/icons-material/Code';
const About: React.FC = () => {
const [user, setUser] = useState<any>(null);
useEffect(() => {
const storedUser = localStorage.getItem('user');
if (storedUser) {
setUser(JSON.parse(storedUser));
}
}, []);
return (
<Box sx={{ background: 'linear-gradient(to right, #d0e7ff, #f0f8ff)', minHeight: '100vh', p: 4 }}>
{/* Navigation bar */}
<Navbar user={user} setUser={setUser} />
{/* Main content with top padding to account for fixed Navbar */}
<Box sx={{ pt: '80px' }}>
<Typography
variant="h3"
align="center"
gutterBottom
sx={{ fontWeight: 'bold', color: '#0d47a1' }}
>
About Health AI
</Typography>
<Typography
variant="h6"
align="center"
gutterBottom
sx={{ color: '#0d47a1', mb: 4 }}
>
Your Personal AI Assistant for Tailored Drug Recommendations
</Typography>
<Grid container spacing={4} justifyContent="center">
{/* Project Information Card */}
<Grid item xs={12} md={6}>
<Paper elevation={3} sx={{ p: 3, backgroundColor: '#ffffff', borderRadius: '12px' }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<MedicalServicesIcon sx={{ fontSize: 40, color: '#0d47a1', mr: 1 }} />
<Typography variant="h5" sx={{ fontWeight: 'bold', color: '#0d47a1' }}>
About the Project
</Typography>
</Box>
<Typography variant="body1" sx={{ color: '#424242', mb: 2 }}>
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,
context-aware suggestions for both over-the-counter and prescription medications.
</Typography>
<Typography variant="body1" sx={{ color: '#424242' }}>
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.
</Typography>
</Paper>
</Grid>
{/* How It Works Card */}
<Grid item xs={12} md={6}>
<Paper elevation={3} sx={{ p: 3, backgroundColor: '#ffffff', borderRadius: '12px' }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<LocalHospitalIcon sx={{ fontSize: 40, color: '#0d47a1', mr: 1 }} />
<Typography variant="h5" sx={{ fontWeight: 'bold', color: '#0d47a1' }}>
How It Works
</Typography>
</Box>
<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,
and medication type. It then employs vector search techniques to fetch the most relevant information from a comprehensive drug database,
ensuring precise recommendations.
</Typography>
<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.
</Typography>
</Paper>
</Grid>
{/* Future Enhancements Card */}
<Grid item xs={12}>
<Paper elevation={3} sx={{ p: 3, backgroundColor: '#ffffff', borderRadius: '12px' }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<CodeIcon sx={{ fontSize: 40, color: '#0d47a1', mr: 1 }} />
<Typography variant="h5" sx={{ fontWeight: 'bold', color: '#0d47a1' }}>
What's Next?
</Typography>
</Box>
<Typography variant="body1" sx={{ color: '#424242' }}>
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,
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>
</Paper>
</Grid>
</Grid>
{/* Footer */}
<Box sx={{ textAlign: 'center', mt: 6 }}>
<Typography variant="body2" sx={{ color: '#424242' }}>
© {new Date().getFullYear()} Health AI. All rights reserved.
</Typography>
</Box>
</Box>
</Box>
);
};
export default About;

View File

@ -1,57 +1,57 @@
import React, { useEffect, useState } from 'react';
import { useParams, useLocation } from 'react-router-dom';
interface ChatHistoryItem {
id: number;
chat: string;
created_at: string;
}
const ChatDetails: React.FC = () => {
const { id } = useParams<{ id: string }>();
const location = useLocation();
const [chat, setChat] = useState<ChatHistoryItem | null>(location.state?.chat || null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!chat && id) {
fetch(`http://localhost:5000/api/chat_history_detail?id=${encodeURIComponent(id)}`)
.then((res) => {
if (!res.ok) {
throw new Error('Chat not found');
}
return res.json();
})
.then((data) => {
if (data.error) {
setError(data.error);
} else {
setChat(data.chat);
}
})
.catch((err) => setError(err.message));
}
}, [id, chat]);
if (error) {
return <div>Error: {error}</div>;
}
if (!chat) {
return <div>Loading chat details...</div>;
}
return (
<div style={{ padding: '20px' }}>
<h1>Chat Details</h1>
<div style={{ border: '1px solid #ccc', padding: '10px' }}>
{chat.chat.split('\n').map((line, index) => (
<p key={index}>{line}</p>
))}
</div>
<small>{new Date(chat.created_at).toLocaleString()}</small>
</div>
);
};
export default ChatDetails;
import React, { useEffect, useState } from 'react';
import { useParams, useLocation } from 'react-router-dom';
interface ChatHistoryItem {
id: number;
chat: string;
created_at: string;
}
const ChatDetails: React.FC = () => {
const { id } = useParams<{ id: string }>();
const location = useLocation();
const [chat, setChat] = useState<ChatHistoryItem | null>(location.state?.chat || null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!chat && id) {
fetch(`http://localhost:5000/api/chat_history_detail?id=${encodeURIComponent(id)}`)
.then((res) => {
if (!res.ok) {
throw new Error('Chat not found');
}
return res.json();
})
.then((data) => {
if (data.error) {
setError(data.error);
} else {
setChat(data.chat);
}
})
.catch((err) => setError(err.message));
}
}, [id, chat]);
if (error) {
return <div>Error: {error}</div>;
}
if (!chat) {
return <div>Loading chat details...</div>;
}
return (
<div style={{ padding: '20px' }}>
<h1>Chat Details</h1>
<div style={{ border: '1px solid #ccc', padding: '10px' }}>
{chat.chat.split('\n').map((line, index) => (
<p key={index}>{line}</p>
))}
</div>
<small>{new Date(chat.created_at).toLocaleString()}</small>
</div>
);
};
export default ChatDetails;

View File

@ -1,158 +1,158 @@
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Box, Typography, Paper, IconButton } from '@mui/material';
import DeleteIcon from '@mui/icons-material/Delete';
interface ChatHistoryItem {
id: number;
chat: string;
created_at: string;
}
const ChatHistory: React.FC = () => {
const [history, setHistory] = useState<ChatHistoryItem[]>([]);
const [error, setError] = useState<string | null>(null);
const navigate = useNavigate();
useEffect(() => {
const storedUser = localStorage.getItem('user');
if (storedUser) {
const user = JSON.parse(storedUser);
const email = user.email;
fetch(`http://localhost:5000/api/chat_history?email=${encodeURIComponent(email)}`)
.then((res) => res.json())
.then((data) => {
if (data.error) {
setError(data.error);
} else {
setHistory(data.history);
}
})
.catch(() => setError('Error fetching chat history'));
} else {
setError('User not logged in');
}
}, []);
const handleClick = (item: ChatHistoryItem) => {
navigate(`/dashboard/chat/${item.id}`, { state: { selectedChat: item } });
};
const handleDelete = (chatId: number) => {
if (window.confirm('Are you sure that you want to delete that chat?')) {
fetch(`http://localhost:5000/api/chat_history?id=${chatId}`, {
method: 'DELETE'
})
.then((res) => res.json())
.then((data) => {
if (data.error) {
setError(data.error);
} else {
setHistory(history.filter((chat) => chat.id !== chatId));
}
})
.catch(() => setError('Error deleting chat'));
}
};
return (
<Box
sx={{
width: '100%',
height: '100vh',
overflowY: 'auto',
background: '#f5f5f5',
boxSizing: 'border-box',
p: 3,
'&::-webkit-scrollbar': {
display: 'none',
},
'-ms-overflow-style': 'none',
'scrollbarWidth': 'none',
}}
>
<Typography
variant="h4"
sx={{
mb: 3,
fontWeight: 'bold',
textAlign: 'center',
color: '#0d47a1',
}}
>
Chat History
</Typography>
{error ? (
<Typography
variant="body1"
sx={{ color: 'error.main', textAlign: 'center' }}
>
{error}
</Typography>
) : (
<Box sx={{ maxWidth: '800px', mx: 'auto' }}>
{history.length === 0 ? (
<Typography
variant="body1"
sx={{ textAlign: 'center', color: '#424242' }}
>
No chat history found.
</Typography>
) : (
history.map((item) => {
const lines = item.chat.split("\n");
let firstUserMessage = lines[0];
if (firstUserMessage.startsWith("User:")) {
firstUserMessage = firstUserMessage.replace("User:", "").trim();
}
return (
<Paper
key={item.id}
sx={{
p: 2,
mb: 2,
cursor: 'pointer',
transition: 'box-shadow 0.3s ease',
'&:hover': { boxShadow: 6 },
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<Box
sx={{ flex: 1, display: 'flex', flexDirection: 'column' }}
onClick={() => handleClick(item)}
>
<Typography
variant="subtitle1"
sx={{ fontWeight: 'bold', color: '#0d47a1' }}
>
{firstUserMessage}
</Typography>
<Typography
variant="caption"
sx={{ color: '#757575' }}
>
{new Date(item.created_at).toLocaleString()}
</Typography>
</Box>
<IconButton
onClick={(e) => {
e.stopPropagation();
handleDelete(item.id);
}}
sx={{ color: '#d32f2f' }}
>
<DeleteIcon />
</IconButton>
</Paper>
);
})
)}
</Box>
)}
</Box>
);
};
export default ChatHistory;
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Box, Typography, Paper, IconButton } from '@mui/material';
import DeleteIcon from '@mui/icons-material/Delete';
interface ChatHistoryItem {
id: number;
chat: string;
created_at: string;
}
const ChatHistory: React.FC = () => {
const [history, setHistory] = useState<ChatHistoryItem[]>([]);
const [error, setError] = useState<string | null>(null);
const navigate = useNavigate();
useEffect(() => {
const storedUser = localStorage.getItem('user');
if (storedUser) {
const user = JSON.parse(storedUser);
const email = user.email;
fetch(`http://localhost:5000/api/chat_history?email=${encodeURIComponent(email)}`)
.then((res) => res.json())
.then((data) => {
if (data.error) {
setError(data.error);
} else {
setHistory(data.history);
}
})
.catch(() => setError('Error fetching chat history'));
} else {
setError('User not logged in');
}
}, []);
const handleClick = (item: ChatHistoryItem) => {
navigate(`/dashboard/chat/${item.id}`, { state: { selectedChat: item } });
};
const handleDelete = (chatId: number) => {
if (window.confirm('Are you sure that you want to delete that chat?')) {
fetch(`http://localhost:5000/api/chat_history?id=${chatId}`, {
method: 'DELETE'
})
.then((res) => res.json())
.then((data) => {
if (data.error) {
setError(data.error);
} else {
setHistory(history.filter((chat) => chat.id !== chatId));
}
})
.catch(() => setError('Error deleting chat'));
}
};
return (
<Box
sx={{
width: '100%',
height: '100vh',
overflowY: 'auto',
background: '#f5f5f5',
boxSizing: 'border-box',
p: 3,
'&::-webkit-scrollbar': {
display: 'none',
},
'-ms-overflow-style': 'none',
'scrollbarWidth': 'none',
}}
>
<Typography
variant="h4"
sx={{
mb: 3,
fontWeight: 'bold',
textAlign: 'center',
color: '#0d47a1',
}}
>
Chat History
</Typography>
{error ? (
<Typography
variant="body1"
sx={{ color: 'error.main', textAlign: 'center' }}
>
{error}
</Typography>
) : (
<Box sx={{ maxWidth: '800px', mx: 'auto' }}>
{history.length === 0 ? (
<Typography
variant="body1"
sx={{ textAlign: 'center', color: '#424242' }}
>
No chat history found.
</Typography>
) : (
history.map((item) => {
const lines = item.chat.split("\n");
let firstUserMessage = lines[0];
if (firstUserMessage.startsWith("User:")) {
firstUserMessage = firstUserMessage.replace("User:", "").trim();
}
return (
<Paper
key={item.id}
sx={{
p: 2,
mb: 2,
cursor: 'pointer',
transition: 'box-shadow 0.3s ease',
'&:hover': { boxShadow: 6 },
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
<Box
sx={{ flex: 1, display: 'flex', flexDirection: 'column' }}
onClick={() => handleClick(item)}
>
<Typography
variant="subtitle1"
sx={{ fontWeight: 'bold', color: '#0d47a1' }}
>
{firstUserMessage}
</Typography>
<Typography
variant="caption"
sx={{ color: '#757575' }}
>
{new Date(item.created_at).toLocaleString()}
</Typography>
</Box>
<IconButton
onClick={(e) => {
e.stopPropagation();
handleDelete(item.id);
}}
sx={{ color: '#d32f2f' }}
>
<DeleteIcon />
</IconButton>
</Paper>
);
})
)}
</Box>
)}
</Box>
);
};
export default ChatHistory;

View File

@ -1,129 +1,129 @@
import React, { useState, useEffect } from 'react';
import { Box, Typography, Paper, Grid } from '@mui/material';
import {Navbar} from '../pages/LandingPage';
import SchoolIcon from '@mui/icons-material/School';
import DeveloperModeIcon from '@mui/icons-material/DeveloperMode';
import EmailIcon from '@mui/icons-material/Email';
const Contact: React.FC = () => {
const [user, setUser] = useState<any>(null);
useEffect(() => {
const storedUser = localStorage.getItem('user');
if (storedUser) {
setUser(JSON.parse(storedUser));
}
}, []);
return (
<Box
sx={{
background: 'linear-gradient(to right, #d0e7ff, #f0f8ff)',
minHeight: '100vh',
p: 4,
}}
>
{/* Navbar with navigation links */}
<Navbar user={user} setUser={setUser} />
{/* Main content with spacing for fixed Navbar */}
<Box sx={{ pt: '80px', maxWidth: '800px', mx: 'auto' }}>
<Paper
elevation={4}
sx={{
p: 4,
borderRadius: '16px',
backgroundColor: '#ffffff',
boxShadow: '0 4px 20px rgba(0,0,0,0.1)',
}}
>
<Typography
variant="h4"
align="center"
sx={{ fontWeight: 'bold', color: '#0d47a1', mb: 4 }}
>
Contact
</Typography>
<Grid container spacing={4}>
{/* University Info */}
<Grid item xs={12} sm={6}>
<Paper
elevation={2}
sx={{
p: 2,
display: 'flex',
alignItems: 'center',
borderRadius: '12px',
backgroundColor: '#e3f2fd',
}}
>
<SchoolIcon sx={{ fontSize: 40, color: '#0d47a1', mr: 2 }} />
<Box>
<Typography variant="h6" sx={{ fontWeight: 'bold', color: '#0d47a1' }}>
Technical University of Košice
</Typography>
<Typography variant="body2" sx={{ color: '#424242' }}>
KEMT Department
</Typography>
</Box>
</Paper>
</Grid>
{/* Developer Info */}
<Grid item xs={12} sm={6}>
<Paper
elevation={2}
sx={{
p: 2,
display: 'flex',
alignItems: 'center',
borderRadius: '12px',
backgroundColor: '#e8f5e9',
}}
>
<DeveloperModeIcon sx={{ fontSize: 40, color: '#0d47a1', mr: 2 }} />
<Box>
<Typography variant="h6" sx={{ fontWeight: 'bold', color: '#0d47a1' }}>
Developer
</Typography>
<Typography variant="body2" sx={{ color: '#424242' }}>
oleh.poiasnik@student.tuke.sk
</Typography>
</Box>
</Paper>
</Grid>
{/* Additional Contact Option */}
<Grid item xs={12}>
<Paper
elevation={2}
sx={{
p: 2,
display: 'flex',
alignItems: 'center',
borderRadius: '12px',
backgroundColor: '#fff3e0',
}}
>
<EmailIcon sx={{ fontSize: 40, color: '#0d47a1', mr: 2 }} />
<Box>
<Typography variant="h6" sx={{ fontWeight: 'bold', color: '#0d47a1' }}>
Email Us
</Typography>
<Typography variant="body2" sx={{ color: '#424242' }}>
For any inquiries or further information about Health AI, please get in touch!
</Typography>
</Box>
</Paper>
</Grid>
</Grid>
<Box sx={{ textAlign: 'center', mt: 4 }}>
<Typography variant="body2" sx={{ color: '#424242' }}>
© {new Date().getFullYear()} Health AI. All rights reserved.
</Typography>
</Box>
</Paper>
</Box>
</Box>
);
};
export default Contact;
import React, { useState, useEffect } from 'react';
import { Box, Typography, Paper, Grid } from '@mui/material';
import {Navbar} from '../pages/LandingPage';
import SchoolIcon from '@mui/icons-material/School';
import DeveloperModeIcon from '@mui/icons-material/DeveloperMode';
import EmailIcon from '@mui/icons-material/Email';
const Contact: React.FC = () => {
const [user, setUser] = useState<any>(null);
useEffect(() => {
const storedUser = localStorage.getItem('user');
if (storedUser) {
setUser(JSON.parse(storedUser));
}
}, []);
return (
<Box
sx={{
background: 'linear-gradient(to right, #d0e7ff, #f0f8ff)',
minHeight: '100vh',
p: 4,
}}
>
{/* Navbar with navigation links */}
<Navbar user={user} setUser={setUser} />
{/* Main content with spacing for fixed Navbar */}
<Box sx={{ pt: '80px', maxWidth: '800px', mx: 'auto' }}>
<Paper
elevation={4}
sx={{
p: 4,
borderRadius: '16px',
backgroundColor: '#ffffff',
boxShadow: '0 4px 20px rgba(0,0,0,0.1)',
}}
>
<Typography
variant="h4"
align="center"
sx={{ fontWeight: 'bold', color: '#0d47a1', mb: 4 }}
>
Contact
</Typography>
<Grid container spacing={4}>
{/* University Info */}
<Grid item xs={12} sm={6}>
<Paper
elevation={2}
sx={{
p: 2,
display: 'flex',
alignItems: 'center',
borderRadius: '12px',
backgroundColor: '#e3f2fd',
}}
>
<SchoolIcon sx={{ fontSize: 40, color: '#0d47a1', mr: 2 }} />
<Box>
<Typography variant="h6" sx={{ fontWeight: 'bold', color: '#0d47a1' }}>
Technical University of Košice
</Typography>
<Typography variant="body2" sx={{ color: '#424242' }}>
KEMT Department
</Typography>
</Box>
</Paper>
</Grid>
{/* Developer Info */}
<Grid item xs={12} sm={6}>
<Paper
elevation={2}
sx={{
p: 2,
display: 'flex',
alignItems: 'center',
borderRadius: '12px',
backgroundColor: '#e8f5e9',
}}
>
<DeveloperModeIcon sx={{ fontSize: 40, color: '#0d47a1', mr: 2 }} />
<Box>
<Typography variant="h6" sx={{ fontWeight: 'bold', color: '#0d47a1' }}>
Developer
</Typography>
<Typography variant="body2" sx={{ color: '#424242' }}>
oleh.poiasnik@student.tuke.sk
</Typography>
</Box>
</Paper>
</Grid>
{/* Additional Contact Option */}
<Grid item xs={12}>
<Paper
elevation={2}
sx={{
p: 2,
display: 'flex',
alignItems: 'center',
borderRadius: '12px',
backgroundColor: '#fff3e0',
}}
>
<EmailIcon sx={{ fontSize: 40, color: '#0d47a1', mr: 2 }} />
<Box>
<Typography variant="h6" sx={{ fontWeight: 'bold', color: '#0d47a1' }}>
Email Us
</Typography>
<Typography variant="body2" sx={{ color: '#424242' }}>
For any inquiries or further information about Health AI, please get in touch!
</Typography>
</Box>
</Paper>
</Grid>
</Grid>
<Box sx={{ textAlign: 'center', mt: 4 }}>
<Typography variant="body2" sx={{ color: '#424242' }}>
© {new Date().getFullYear()} Health AI. All rights reserved.
</Typography>
</Box>
</Paper>
</Box>
</Box>
);
};
export default Contact;

View File

@ -1,190 +1,190 @@
import React from 'react';
import { GoogleLogin, GoogleOAuthProvider } from '@react-oauth/google';
import { Link, useNavigate } from 'react-router-dom';
const CLIENT_ID = "532143017111-4eqtlp0oejqaovj6rf5l1ergvhrp4vao.apps.googleusercontent.com";
const LoginFormContent: React.FC = () => {
const navigate = useNavigate();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const emailElement = document.getElementById('email') as HTMLInputElement | null;
const passwordElement = document.getElementById('password') as HTMLInputElement | null;
if (!emailElement || !passwordElement) {
console.error('One or many inputs are missing');
return;
}
const email = emailElement.value;
const password = passwordElement.value;
try {
const response = await fetch('http://localhost:5000/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
const data = await response.json();
if (response.ok) {
console.log('Login successful:', data.message);
const loggedInUser = {
name: data.user.name,
email: data.user.email,
picture: data.user.picture || 'https://via.placeholder.com/150',
};
localStorage.setItem('user', JSON.stringify(loggedInUser));
navigate('/dashboard');
} else {
console.error('Error:', data.error);
}
} catch (error) {
console.error('Error loginning:', error);
}
};
const handleGoogleLoginSuccess = async (response: any) => {
try {
const res = await fetch('http://localhost:5000/api/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: response.credential }),
});
const data = await res.json();
const loggedInUser = {
name: data.user.name,
email: data.user.email,
picture: data.user.picture || 'https://via.placeholder.com/150',
};
localStorage.setItem('user', JSON.stringify(loggedInUser));
navigate('/dashboard');
} catch (error) {
console.error('Error token verification:', error);
}
};
const handleGoogleLoginError = () => {
console.error('Error auth through Google');
};
return (
<div
style={{
background: 'rgba(0, 0, 0, 0.5)',
minHeight: '100vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: '16px',
}}
>
<div
className="login-card"
style={{
maxWidth: '400px',
width: '100%',
borderRadius: '8px',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
backgroundColor: '#fff',
padding: '24px',
}}
>
<h2 style={{ textAlign: 'center', fontWeight: 'bold', color: '#333', marginBottom: '16px' }}>
Sign In
</h2>
<form noValidate autoComplete="off" onSubmit={handleSubmit}>
<div style={{ marginBottom: '16px' }}>
<label htmlFor="email" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
Email
</label>
<input
type="email"
id="email"
required
style={{
width: '100%',
padding: '8px',
borderRadius: '4px',
border: '1px solid #ccc',
}}
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label htmlFor="password" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
Password
</label>
<input
type="password"
id="password"
required
style={{
width: '100%',
padding: '8px',
borderRadius: '4px',
border: '1px solid #ccc',
}}
/>
</div>
<button
type="submit"
style={{
width: '100%',
padding: '12px',
backgroundColor: '#007bff',
color: '#fff',
fontWeight: 'bold',
borderRadius: '4px',
border: 'none',
cursor: 'pointer',
marginBottom: '16px',
}}
>
Sign In
</button>
</form>
<div
style={{
textAlign: 'center',
marginBottom: '16px',
color: '#555',
fontSize: '14px',
borderBottom: '1px solid #ccc',
lineHeight: '0.1em',
margin: '10px 0 20px',
}}
>
<span style={{ background: '#fff', padding: '0 10px' }}>OR</span>
</div>
<div style={{ textAlign: 'center', marginBottom: '16px' }}>
<GoogleLogin
onSuccess={handleGoogleLoginSuccess}
onError={handleGoogleLoginError}
size="large"
width="100%"
/>
</div>
<p style={{ textAlign: 'center', color: '#555', marginTop: '16px' }}>
Don&apos;t have an account?{' '}
<Link to="/register" style={{ color: '#007bff', textDecoration: 'none' }}>
Sign Up
</Link>
</p>
</div>
</div>
);
};
const LoginForm: React.FC = () => {
return (
<GoogleOAuthProvider clientId={CLIENT_ID}>
<LoginFormContent />
</GoogleOAuthProvider>
);
};
export default LoginForm;
import React from 'react';
import { GoogleLogin, GoogleOAuthProvider } from '@react-oauth/google';
import { Link, useNavigate } from 'react-router-dom';
const CLIENT_ID = "532143017111-4eqtlp0oejqaovj6rf5l1ergvhrp4vao.apps.googleusercontent.com";
const LoginFormContent: React.FC = () => {
const navigate = useNavigate();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const emailElement = document.getElementById('email') as HTMLInputElement | null;
const passwordElement = document.getElementById('password') as HTMLInputElement | null;
if (!emailElement || !passwordElement) {
console.error('One or many inputs are missing');
return;
}
const email = emailElement.value;
const password = passwordElement.value;
try {
const response = await fetch('http://localhost:5000/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
});
const data = await response.json();
if (response.ok) {
console.log('Login successful:', data.message);
const loggedInUser = {
name: data.user.name,
email: data.user.email,
picture: data.user.picture || 'https://via.placeholder.com/150',
};
localStorage.setItem('user', JSON.stringify(loggedInUser));
navigate('/dashboard');
} else {
console.error('Error:', data.error);
}
} catch (error) {
console.error('Error loginning:', error);
}
};
const handleGoogleLoginSuccess = async (response: any) => {
try {
const res = await fetch('http://localhost:5000/api/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: response.credential }),
});
const data = await res.json();
const loggedInUser = {
name: data.user.name,
email: data.user.email,
picture: data.user.picture || 'https://via.placeholder.com/150',
};
localStorage.setItem('user', JSON.stringify(loggedInUser));
navigate('/dashboard');
} catch (error) {
console.error('Error token verification:', error);
}
};
const handleGoogleLoginError = () => {
console.error('Error auth through Google');
};
return (
<div
style={{
background: 'rgba(0, 0, 0, 0.5)',
minHeight: '100vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: '16px',
}}
>
<div
className="login-card"
style={{
maxWidth: '400px',
width: '100%',
borderRadius: '8px',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
backgroundColor: '#fff',
padding: '24px',
}}
>
<h2 style={{ textAlign: 'center', fontWeight: 'bold', color: '#333', marginBottom: '16px' }}>
Sign In
</h2>
<form noValidate autoComplete="off" onSubmit={handleSubmit}>
<div style={{ marginBottom: '16px' }}>
<label htmlFor="email" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
Email
</label>
<input
type="email"
id="email"
required
style={{
width: '100%',
padding: '8px',
borderRadius: '4px',
border: '1px solid #ccc',
}}
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label htmlFor="password" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
Password
</label>
<input
type="password"
id="password"
required
style={{
width: '100%',
padding: '8px',
borderRadius: '4px',
border: '1px solid #ccc',
}}
/>
</div>
<button
type="submit"
style={{
width: '100%',
padding: '12px',
backgroundColor: '#007bff',
color: '#fff',
fontWeight: 'bold',
borderRadius: '4px',
border: 'none',
cursor: 'pointer',
marginBottom: '16px',
}}
>
Sign In
</button>
</form>
<div
style={{
textAlign: 'center',
marginBottom: '16px',
color: '#555',
fontSize: '14px',
borderBottom: '1px solid #ccc',
lineHeight: '0.1em',
margin: '10px 0 20px',
}}
>
<span style={{ background: '#fff', padding: '0 10px' }}>OR</span>
</div>
<div style={{ textAlign: 'center', marginBottom: '16px' }}>
<GoogleLogin
onSuccess={handleGoogleLoginSuccess}
onError={handleGoogleLoginError}
size="large"
width="100%"
/>
</div>
<p style={{ textAlign: 'center', color: '#555', marginTop: '16px' }}>
Don&apos;t have an account?{' '}
<Link to="/register" style={{ color: '#007bff', textDecoration: 'none' }}>
Sign Up
</Link>
</p>
</div>
</div>
);
};
const LoginForm: React.FC = () => {
return (
<GoogleOAuthProvider clientId={CLIENT_ID}>
<LoginFormContent />
</GoogleOAuthProvider>
);
};
export default LoginForm;

View File

@ -1,146 +1,146 @@
import React, { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import IconButton from '@mui/material/IconButton';
import Avatar from '@mui/material/Avatar';
import { MdAddCircleOutline } from "react-icons/md";
import { GoHistory } from "react-icons/go";
import { CgLogIn } from "react-icons/cg";
import BackImage from '../assets/smallheadicon.png';
export interface NavigationItem {
icon: React.ReactNode;
title: string;
link: string;
}
const NavigationItems: NavigationItem[] = [
{
title: 'New Chat',
link: '/dashboard/new-chat',
icon: <MdAddCircleOutline size={30} />
},
{
title: 'History',
link: '/dashboard/history',
icon: <GoHistory size={25} />
}
];
interface NavigationProps {
isExpanded: boolean;
}
const Navigation = ({ isExpanded = false }: NavigationProps) => {
const [theme, setTheme] = useState<'dark' | 'light'>('light');
useEffect(() => {
if (window.matchMedia('(prefers-color-scheme:dark)').matches) {
setTheme('dark');
} else {
setTheme('light');
}
}, []);
useEffect(() => {
if (theme === "dark") {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
}, [theme]);
const [user, setUser] = useState<any>(null);
useEffect(() => {
const storedUser = localStorage.getItem('user');
if (storedUser) {
setUser(JSON.parse(storedUser));
}
}, []);
return (
<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='flex flex-col items-start gap-12'>
<Link to='/' className='w-full flex items-center justify-center'>
<IconButton sx={{ width: 40, height: 40 }}>
<img src={BackImage} width={25} alt="Back" />
</IconButton>
{isExpanded && (
<p className='text-2xl font-semibold text-dark-blue flex items-center'>
Health AI
</p>
)}
</Link>
<div className='flex flex-col p-1 gap-5 items-center'>
{NavigationItems.map((item) => (
<Link
key={item.link}
to={item.link}
className='flex gap-2 items-center w-full'
>
<IconButton
sx={{
width: 40,
height: 40,
borderRadius: 2,
backgroundColor: '#FFFFFF',
...(theme === 'dark' && {
'&:hover': {
backgroundColor: '#eef3f4',
borderColor: '#0062cc',
boxShadow: 'none',
},
}),
}}
>
{item.icon}
</IconButton>
{isExpanded && item.title}
</Link>
))}
</div>
</div>
<div className="flex flex-col items-center gap-2">
<Link to={user ? '/profile' : '/login'} className="flex items-center">
<IconButton
sx={{
width: 40,
height: 40,
borderRadius: 2,
backgroundColor: '#FFFFFF',
}}
>
{user ? (
<Avatar alt={user.name} src={user.picture} />
) : (
<CgLogIn size={24} />
)}
</IconButton>
</Link>
{/*<button onClick={handleThemeSwitch} className='flex items-center gap-2'>*/}
{/* <IconButton*/}
{/* sx={{*/}
{/* width: 40,*/}
{/* height: 40,*/}
{/* borderRadius: 2,*/}
{/* background: theme === 'dark' ? 'white' : 'initial',*/}
{/* '&:focus-visible': {*/}
{/* outline: '2px solid blue',*/}
{/* outlineOffset: '0px',*/}
{/* borderRadius: '4px',*/}
{/* },*/}
{/* }}*/}
{/* >*/}
{/* {theme === 'light' ? <CiLight size={30} /> : <MdOutlineDarkMode size={30} />}*/}
{/* </IconButton>*/}
{/* {isExpanded && (theme === 'light' ? 'Light mode' : 'Dark mode')}*/}
{/*</button>*/}
</div>
</div>
</div>
);
};
export default Navigation;
import React, { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import IconButton from '@mui/material/IconButton';
import Avatar from '@mui/material/Avatar';
import { MdAddCircleOutline } from "react-icons/md";
import { GoHistory } from "react-icons/go";
import { CgLogIn } from "react-icons/cg";
import BackImage from '../assets/smallheadicon.png';
export interface NavigationItem {
icon: React.ReactNode;
title: string;
link: string;
}
const NavigationItems: NavigationItem[] = [
{
title: 'New Chat',
link: '/dashboard/new-chat',
icon: <MdAddCircleOutline size={30} />
},
{
title: 'History',
link: '/dashboard/history',
icon: <GoHistory size={25} />
}
];
interface NavigationProps {
isExpanded: boolean;
}
const Navigation = ({ isExpanded = false }: NavigationProps) => {
const [theme, setTheme] = useState<'dark' | 'light'>('light');
useEffect(() => {
if (window.matchMedia('(prefers-color-scheme:dark)').matches) {
setTheme('dark');
} else {
setTheme('light');
}
}, []);
useEffect(() => {
if (theme === "dark") {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
}, [theme]);
const [user, setUser] = useState<any>(null);
useEffect(() => {
const storedUser = localStorage.getItem('user');
if (storedUser) {
setUser(JSON.parse(storedUser));
}
}, []);
return (
<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='flex flex-col items-start gap-12'>
<Link to='/' className='w-full flex items-center justify-center'>
<IconButton sx={{ width: 40, height: 40 }}>
<img src={BackImage} width={25} alt="Back" />
</IconButton>
{isExpanded && (
<p className='text-2xl font-semibold text-dark-blue flex items-center'>
Health AI
</p>
)}
</Link>
<div className='flex flex-col p-1 gap-5 items-center'>
{NavigationItems.map((item) => (
<Link
key={item.link}
to={item.link}
className='flex gap-2 items-center w-full'
>
<IconButton
sx={{
width: 40,
height: 40,
borderRadius: 2,
backgroundColor: '#FFFFFF',
...(theme === 'dark' && {
'&:hover': {
backgroundColor: '#eef3f4',
borderColor: '#0062cc',
boxShadow: 'none',
},
}),
}}
>
{item.icon}
</IconButton>
{isExpanded && item.title}
</Link>
))}
</div>
</div>
<div className="flex flex-col items-center gap-2">
<Link to={user ? '/profile' : '/login'} className="flex items-center">
<IconButton
sx={{
width: 40,
height: 40,
borderRadius: 2,
backgroundColor: '#FFFFFF',
}}
>
{user ? (
<Avatar alt={user.name} src={user.picture} />
) : (
<CgLogIn size={24} />
)}
</IconButton>
</Link>
{/*<button onClick={handleThemeSwitch} className='flex items-center gap-2'>*/}
{/* <IconButton*/}
{/* sx={{*/}
{/* width: 40,*/}
{/* height: 40,*/}
{/* borderRadius: 2,*/}
{/* background: theme === 'dark' ? 'white' : 'initial',*/}
{/* '&:focus-visible': {*/}
{/* outline: '2px solid blue',*/}
{/* outlineOffset: '0px',*/}
{/* borderRadius: '4px',*/}
{/* },*/}
{/* }}*/}
{/* >*/}
{/* {theme === 'light' ? <CiLight size={30} /> : <MdOutlineDarkMode size={30} />}*/}
{/* </IconButton>*/}
{/* {isExpanded && (theme === 'light' ? 'Light mode' : 'Dark mode')}*/}
{/*</button>*/}
</div>
</div>
</div>
);
};
export default Navigation;

View File

@ -1,186 +1,186 @@
import React, { useEffect, useState, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import gsap from 'gsap';
import { useGSAP } from '@gsap/react';
import { useLazySendChatQuestionQuery } from '../store/api/chatApi';
import callCenterIcon from '../assets/call-center.png';
interface ChatMessage {
sender: string;
text: string;
}
const NewChatPage: React.FC = () => {
const [sendChatQuestion, { isLoading, isFetching }] = useLazySendChatQuestionQuery();
const [message, setMessage] = useState<string>('');
const [chatHistory, setChatHistory] = useState<ChatMessage[]>([]);
const navigate = useNavigate();
const messagesEndRef = useRef<HTMLDivElement>(null);
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [chatHistory, isLoading, isFetching]);
useEffect(() => {
setChatHistory([]);
}, []);
async function onSubmit() {
if (!message.trim()) return;
const newUserMessage: ChatMessage = { sender: 'User', text: message };
const updatedHistory = [...chatHistory, newUserMessage];
setChatHistory(updatedHistory);
const userMessage = message;
setMessage('');
const storedUser = localStorage.getItem('user');
const email = storedUser ? JSON.parse(storedUser).email : '';
const question = { query: userMessage, email };
try {
const res = await sendChatQuestion(question).unwrap();
console.log('Response from server:', res);
let bestAnswer = res.response.best_answer;
if (typeof bestAnswer !== 'string') {
bestAnswer = String(bestAnswer);
}
bestAnswer = bestAnswer.trim();
const newAssistantMessage: ChatMessage = { sender: 'Assistant', text: bestAnswer };
const newUpdatedHistory = [...updatedHistory, newAssistantMessage];
setChatHistory(newUpdatedHistory);
if (res.response.chatId) {
const chatString = newUpdatedHistory
.map(msg => (msg.sender === 'User' ? 'User: ' : 'Bot: ') + msg.text)
.join('\n');
navigate(`/dashboard/chat/${res.response.chatId}`, {
replace: true,
state: {
selectedChat: {
id: res.response.chatId,
chat: chatString,
created_at: new Date().toISOString()
}
}
});
}
} catch (error) {
console.error('Error:', error);
setChatHistory(prev => [
...prev,
{ sender: 'Assistant', text: 'Something went wrong' }
]);
}
}
useGSAP(() => {
gsap.from('#input', { opacity: 0, y: 5, ease: 'power2.inOut', duration: 0.5 });
}, []);
return (
<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">
{chatHistory.length > 0 ? (
<>
{chatHistory.map((msg, index) => (
<div
key={index}
className={`flex mb-2 ${msg.sender === 'User' ? 'justify-end' : 'justify-start items-start'}`}
>
{msg.sender === 'Assistant' && (
<img
src={callCenterIcon}
alt="Call Center Icon"
className="w-6 h-6 mr-2"
/>
)}
<div
className={`p-3 rounded-lg max-w-md flex ${
msg.sender === 'User'
? 'bg-blue-500 text-white'
: 'bg-gray-200 text-gray-800'
}`}
style={{ whiteSpace: 'normal' }}
>
{msg.text.split('\n').map((line, i) => (
<p key={i}>{line}</p>
))}
</div>
</div>
))}
{(isLoading || isFetching) && (
<div className="flex mb-2 justify-start items-start">
<img
src={callCenterIcon}
alt="Call Center Icon"
className="w-6 h-6 mr-2"
/>
<div
className="p-3 rounded-lg max-w-md flex bg-gray-200 text-gray-800"
style={{ whiteSpace: 'normal' }}
>
<div className="flex items-center">
<svg
className="animate-spin h-5 w-5 mr-3 text-gray-500"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"
></path>
</svg>
<span>Assistant is typing...</span>
</div>
</div>
</div>
)}
</>
) : (
<div className="w-full h-full flex flex-col gap-2 items-center justify-center">
<h1 className="text-xl" id="firstheading">
Start a New Chat
</h1>
</div>
)}
<div ref={messagesEndRef} />
</div>
<div id="input" className="w-2/3 mb-20">
<div className="flex">
<input
type="text"
placeholder="Type your message..."
value={message}
onChange={(e) => setMessage(e.target.value)}
className="w-full px-5 py-2 rounded-l-xl outline-none border border-gray-300"
/>
<button
disabled={isLoading || isFetching}
onClick={onSubmit}
className="bg-black text-white font-semibold px-4 py-2 rounded-r-xl hover:bg-slate-700"
>
Send
</button>
</div>
</div>
</div>
);
};
export default NewChatPage;
import React, { useEffect, useState, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import gsap from 'gsap';
import { useGSAP } from '@gsap/react';
import { useLazySendChatQuestionQuery } from '../store/api/chatApi';
import callCenterIcon from '../assets/call-center.png';
interface ChatMessage {
sender: string;
text: string;
}
const NewChatPage: React.FC = () => {
const [sendChatQuestion, { isLoading, isFetching }] = useLazySendChatQuestionQuery();
const [message, setMessage] = useState<string>('');
const [chatHistory, setChatHistory] = useState<ChatMessage[]>([]);
const navigate = useNavigate();
const messagesEndRef = useRef<HTMLDivElement>(null);
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [chatHistory, isLoading, isFetching]);
useEffect(() => {
setChatHistory([]);
}, []);
async function onSubmit() {
if (!message.trim()) return;
const newUserMessage: ChatMessage = { sender: 'User', text: message };
const updatedHistory = [...chatHistory, newUserMessage];
setChatHistory(updatedHistory);
const userMessage = message;
setMessage('');
const storedUser = localStorage.getItem('user');
const email = storedUser ? JSON.parse(storedUser).email : '';
const question = { query: userMessage, email };
try {
const res = await sendChatQuestion(question).unwrap();
console.log('Response from server:', res);
let bestAnswer = res.response.best_answer;
if (typeof bestAnswer !== 'string') {
bestAnswer = String(bestAnswer);
}
bestAnswer = bestAnswer.trim();
const newAssistantMessage: ChatMessage = { sender: 'Assistant', text: bestAnswer };
const newUpdatedHistory = [...updatedHistory, newAssistantMessage];
setChatHistory(newUpdatedHistory);
if (res.response.chatId) {
const chatString = newUpdatedHistory
.map(msg => (msg.sender === 'User' ? 'User: ' : 'Bot: ') + msg.text)
.join('\n');
navigate(`/dashboard/chat/${res.response.chatId}`, {
replace: true,
state: {
selectedChat: {
id: res.response.chatId,
chat: chatString,
created_at: new Date().toISOString()
}
}
});
}
} catch (error) {
console.error('Error:', error);
setChatHistory(prev => [
...prev,
{ sender: 'Assistant', text: 'Something went wrong' }
]);
}
}
useGSAP(() => {
gsap.from('#input', { opacity: 0, y: 5, ease: 'power2.inOut', duration: 0.5 });
}, []);
return (
<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">
{chatHistory.length > 0 ? (
<>
{chatHistory.map((msg, index) => (
<div
key={index}
className={`flex mb-2 ${msg.sender === 'User' ? 'justify-end' : 'justify-start items-start'}`}
>
{msg.sender === 'Assistant' && (
<img
src={callCenterIcon}
alt="Call Center Icon"
className="w-6 h-6 mr-2"
/>
)}
<div
className={`p-3 rounded-lg max-w-md flex ${
msg.sender === 'User'
? 'bg-blue-500 text-white'
: 'bg-gray-200 text-gray-800'
}`}
style={{ whiteSpace: 'normal' }}
>
{msg.text.split('\n').map((line, i) => (
<p key={i}>{line}</p>
))}
</div>
</div>
))}
{(isLoading || isFetching) && (
<div className="flex mb-2 justify-start items-start">
<img
src={callCenterIcon}
alt="Call Center Icon"
className="w-6 h-6 mr-2"
/>
<div
className="p-3 rounded-lg max-w-md flex bg-gray-200 text-gray-800"
style={{ whiteSpace: 'normal' }}
>
<div className="flex items-center">
<svg
className="animate-spin h-5 w-5 mr-3 text-gray-500"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"
></path>
</svg>
<span>Assistant is typing...</span>
</div>
</div>
</div>
)}
</>
) : (
<div className="w-full h-full flex flex-col gap-2 items-center justify-center">
<h1 className="text-xl" id="firstheading">
Start a New Chat
</h1>
</div>
)}
<div ref={messagesEndRef} />
</div>
<div id="input" className="w-2/3 mb-20">
<div className="flex">
<input
type="text"
placeholder="Type your message..."
value={message}
onChange={(e) => setMessage(e.target.value)}
className="w-full px-5 py-2 rounded-l-xl outline-none border border-gray-300"
/>
<button
disabled={isLoading || isFetching}
onClick={onSubmit}
className="bg-black text-white font-semibold px-4 py-2 rounded-r-xl hover:bg-slate-700"
>
Send
</button>
</div>
</div>
</div>
);
};
export default NewChatPage;

View File

@ -1,233 +1,233 @@
import React, { useEffect, useState } from 'react';
import {
Box,
Typography,
Avatar,
Paper,
Button,
TextField,
IconButton,
Divider
} from '@mui/material';
import { useNavigate } from 'react-router-dom';
import { Navbar } from '../pages/LandingPage';
import CheckIcon from '@mui/icons-material/Check';
import CloseIcon from '@mui/icons-material/Close';
const Profile: React.FC = () => {
const [user, setUser] = useState<any>(null);
const [editing, setEditing] = useState<boolean>(false);
const [formData, setFormData] = useState({
name: '',
email: '',
phone: '',
role: '',
bio: '',
picture: '',
});
const navigate = useNavigate();
useEffect(() => {
const storedUser = localStorage.getItem('user');
if (storedUser) {
const parsedUser = JSON.parse(storedUser);
setUser(parsedUser);
setFormData({
name: parsedUser.name || '',
email: parsedUser.email || '',
phone: parsedUser.phone || '',
role: parsedUser.role || '',
bio: parsedUser.bio || '',
picture: parsedUser.picture || '',
});
} else {
navigate('/login');
}
}, [navigate]);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData(prev => ({
...prev,
[e.target.name]: e.target.value,
}));
};
const handleCancelEdit = () => {
setFormData({
name: user.name || '',
email: user.email || '',
phone: user.phone || '',
role: user.role || '',
bio: user.bio || '',
picture: user.picture || '',
});
setEditing(false);
};
const handleSaveEdit = async () => {
try {
const response = await fetch('http://localhost:5000/api/update_profile', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData),
});
const data = await response.json();
if (response.ok) {
const updatedUser = { ...user, ...formData };
setUser(updatedUser);
localStorage.setItem('user', JSON.stringify(updatedUser));
setEditing(false);
} else {
alert(data.error || 'Error updating profile');
}
} catch (err) {
console.error(err);
alert('Error updating profile');
}
};
if (!user) {
return null;
}
return (
<Box
sx={{
minHeight: '100vh',
background: 'linear-gradient(to right, #d0e7ff, #f0f8ff)',
display: 'flex',
flexDirection: 'column',
}}
>
<Navbar user={user} setUser={setUser} />
<Box
sx={{
flexGrow: 1,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
p: 4,
}}
>
<Paper
elevation={3}
sx={{
p: 4,
borderRadius: '12px',
maxWidth: '500px',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Avatar
src={user.picture}
alt={user.name}
sx={{ width: 100, height: 100, mb: 2 }}
/>
{editing ? (
<>
<TextField
label="Name"
name="name"
value={formData.name}
onChange={handleChange}
fullWidth
sx={{ mb: 2 }}
/>
<TextField
label="Email"
name="email"
value={formData.email}
fullWidth
sx={{ mb: 2 }}
disabled
/>
<TextField
label="Phone"
name="phone"
value={formData.phone}
onChange={handleChange}
fullWidth
sx={{ mb: 2 }}
/>
<TextField
label="Role"
name="role"
value={formData.role}
onChange={handleChange}
fullWidth
sx={{ mb: 2 }}
/>
<TextField
label="Bio"
name="bio"
value={formData.bio}
onChange={handleChange}
fullWidth
multiline
rows={3}
sx={{ mb: 2 }}
/>
<TextField
label="Picture URL"
name="picture"
value={formData.picture}
onChange={handleChange}
fullWidth
sx={{ mb: 2 }}
/>
<Box
sx={{
display: 'flex',
justifyContent: 'space-around',
width: '100%',
mt: 2,
}}
>
<IconButton onClick={handleSaveEdit} color="primary">
<CheckIcon />
</IconButton>
<IconButton onClick={handleCancelEdit} color="error">
<CloseIcon />
</IconButton>
</Box>
</>
) : (
<>
<Typography
variant="h5"
sx={{ fontWeight: 'bold', color: '#0d47a1', mb: 1 }}
>
{user.name}
</Typography>
<Typography variant="body1" sx={{ color: '#424242', mb: 2 }}>
{user.email}
</Typography>
<Divider sx={{ width: '100%', mb: 2 }} />
<Typography variant="body1" sx={{ color: '#424242', mb: 1 }}>
<strong>Phone:</strong> {user.phone || 'Not provided'}
</Typography>
<Typography variant="body1" sx={{ color: '#424242', mb: 1 }}>
<strong>Role:</strong> {user.role || 'User'}
</Typography>
<Typography variant="body1" sx={{ color: '#424242', mb: 1 }}>
<strong>Bio:</strong> {user.bio || 'No bio available'}
</Typography>
<Button
variant="contained"
sx={{ mt: 3, backgroundColor: '#0d47a1' }}
onClick={() => setEditing(true)}
>
Edit Profile
</Button>
</>
)}
</Paper>
</Box>
</Box>
);
};
export default Profile;
import React, { useEffect, useState } from 'react';
import {
Box,
Typography,
Avatar,
Paper,
Button,
TextField,
IconButton,
Divider
} from '@mui/material';
import { useNavigate } from 'react-router-dom';
import { Navbar } from '../pages/LandingPage';
import CheckIcon from '@mui/icons-material/Check';
import CloseIcon from '@mui/icons-material/Close';
const Profile: React.FC = () => {
const [user, setUser] = useState<any>(null);
const [editing, setEditing] = useState<boolean>(false);
const [formData, setFormData] = useState({
name: '',
email: '',
phone: '',
role: '',
bio: '',
picture: '',
});
const navigate = useNavigate();
useEffect(() => {
const storedUser = localStorage.getItem('user');
if (storedUser) {
const parsedUser = JSON.parse(storedUser);
setUser(parsedUser);
setFormData({
name: parsedUser.name || '',
email: parsedUser.email || '',
phone: parsedUser.phone || '',
role: parsedUser.role || '',
bio: parsedUser.bio || '',
picture: parsedUser.picture || '',
});
} else {
navigate('/login');
}
}, [navigate]);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setFormData(prev => ({
...prev,
[e.target.name]: e.target.value,
}));
};
const handleCancelEdit = () => {
setFormData({
name: user.name || '',
email: user.email || '',
phone: user.phone || '',
role: user.role || '',
bio: user.bio || '',
picture: user.picture || '',
});
setEditing(false);
};
const handleSaveEdit = async () => {
try {
const response = await fetch('http://localhost:5000/api/update_profile', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData),
});
const data = await response.json();
if (response.ok) {
const updatedUser = { ...user, ...formData };
setUser(updatedUser);
localStorage.setItem('user', JSON.stringify(updatedUser));
setEditing(false);
} else {
alert(data.error || 'Error updating profile');
}
} catch (err) {
console.error(err);
alert('Error updating profile');
}
};
if (!user) {
return null;
}
return (
<Box
sx={{
minHeight: '100vh',
background: 'linear-gradient(to right, #d0e7ff, #f0f8ff)',
display: 'flex',
flexDirection: 'column',
}}
>
<Navbar user={user} setUser={setUser} />
<Box
sx={{
flexGrow: 1,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
p: 4,
}}
>
<Paper
elevation={3}
sx={{
p: 4,
borderRadius: '12px',
maxWidth: '500px',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Avatar
src={user.picture}
alt={user.name}
sx={{ width: 100, height: 100, mb: 2 }}
/>
{editing ? (
<>
<TextField
label="Name"
name="name"
value={formData.name}
onChange={handleChange}
fullWidth
sx={{ mb: 2 }}
/>
<TextField
label="Email"
name="email"
value={formData.email}
fullWidth
sx={{ mb: 2 }}
disabled
/>
<TextField
label="Phone"
name="phone"
value={formData.phone}
onChange={handleChange}
fullWidth
sx={{ mb: 2 }}
/>
<TextField
label="Role"
name="role"
value={formData.role}
onChange={handleChange}
fullWidth
sx={{ mb: 2 }}
/>
<TextField
label="Bio"
name="bio"
value={formData.bio}
onChange={handleChange}
fullWidth
multiline
rows={3}
sx={{ mb: 2 }}
/>
<TextField
label="Picture URL"
name="picture"
value={formData.picture}
onChange={handleChange}
fullWidth
sx={{ mb: 2 }}
/>
<Box
sx={{
display: 'flex',
justifyContent: 'space-around',
width: '100%',
mt: 2,
}}
>
<IconButton onClick={handleSaveEdit} color="primary">
<CheckIcon />
</IconButton>
<IconButton onClick={handleCancelEdit} color="error">
<CloseIcon />
</IconButton>
</Box>
</>
) : (
<>
<Typography
variant="h5"
sx={{ fontWeight: 'bold', color: '#0d47a1', mb: 1 }}
>
{user.name}
</Typography>
<Typography variant="body1" sx={{ color: '#424242', mb: 2 }}>
{user.email}
</Typography>
<Divider sx={{ width: '100%', mb: 2 }} />
<Typography variant="body1" sx={{ color: '#424242', mb: 1 }}>
<strong>Phone:</strong> {user.phone || 'Not provided'}
</Typography>
<Typography variant="body1" sx={{ color: '#424242', mb: 1 }}>
<strong>Role:</strong> {user.role || 'User'}
</Typography>
<Typography variant="body1" sx={{ color: '#424242', mb: 1 }}>
<strong>Bio:</strong> {user.bio || 'No bio available'}
</Typography>
<Button
variant="contained"
sx={{ mt: 3, backgroundColor: '#0d47a1' }}
onClick={() => setEditing(true)}
>
Edit Profile
</Button>
</>
)}
</Paper>
</Box>
</Box>
);
};
export default Profile;

View File

@ -1,240 +1,240 @@
import React from 'react';
import { GoogleLogin, GoogleOAuthProvider } from '@react-oauth/google';
import { Link, useNavigate } from 'react-router-dom';
const CLIENT_ID = "532143017111-4eqtlp0oejqaovj6rf5l1ergvhrp4vao.apps.googleusercontent.com";
const RegistrationFormContent: React.FC = () => {
const navigate = useNavigate();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const nameElement = document.getElementById('name') as HTMLInputElement | null;
const emailElement = document.getElementById('email') as HTMLInputElement | null;
const passwordElement = document.getElementById('password') as HTMLInputElement | null;
const confirmPasswordElement = document.getElementById('confirm-password') as HTMLInputElement | null;
if (!nameElement || !emailElement || !passwordElement || !confirmPasswordElement) {
console.error('One or more input fields are missing');
return;
}
const name = nameElement.value;
const email = emailElement.value;
const password = passwordElement.value;
const confirmPassword = confirmPasswordElement.value;
if (password !== confirmPassword) {
console.error('Passwords do not match');
alert('Passwords do not match');
return;
}
try {
const response = await fetch('http://localhost:5000/api/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, email, password }),
});
const data = await response.json();
if (response.ok) {
console.log('User registered successfully:', data.message);
const loggedInUser = {
name,
email,
picture: 'https://via.placeholder.com/150',
};
localStorage.setItem('user', JSON.stringify(loggedInUser));
navigate('/dashboard');
} else {
console.error('Error:', data.error);
alert(data.error);
}
} catch (error) {
console.error('Error registering user:', error);
alert('Error registering user');
}
};
const handleGoogleLoginSuccess = async (response: any) => {
try {
const res = await fetch('http://localhost:5000/api/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: response.credential }),
});
const data = await res.json();
const loggedInUser = {
name: data.user.name,
email: data.user.email,
picture: data.user.picture || 'https://via.placeholder.com/150',
};
localStorage.setItem('user', JSON.stringify(loggedInUser));
navigate('/dashboard');
} catch (error) {
console.error('Error tiken verification:', error);
}
};
const handleGoogleLoginError = () => {
console.error('Error auth: Google login failed');
};
return (
<div
style={{
background: 'rgba(0, 0, 0, 0.5)',
minHeight: '100vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: '16px',
}}
>
<div
className="registration-card"
style={{
maxWidth: '400px',
width: '100%',
borderRadius: '8px',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
backgroundColor: '#fff',
padding: '24px',
}}
>
<h2 style={{ textAlign: 'center', fontWeight: 'bold', color: '#333', marginBottom: '16px' }}>
Create Your Account
</h2>
<p style={{ textAlign: 'center', color: '#555', marginBottom: '24px' }}>
Join us to explore personalized health solutions.
</p>
<form noValidate autoComplete="off" onSubmit={handleSubmit}>
<div style={{ marginBottom: '16px' }}>
<label htmlFor="name" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
Name
</label>
<input
type="text"
id="name"
required
style={{
width: '100%',
padding: '8px',
borderRadius: '4px',
border: '1px solid #ccc',
}}
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label htmlFor="email" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
Email
</label>
<input
type="email"
id="email"
required
style={{
width: '100%',
padding: '8px',
borderRadius: '4px',
border: '1px solid #ccc',
}}
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label htmlFor="password" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
Password
</label>
<input
type="password"
id="password"
required
style={{
width: '100%',
padding: '8px',
borderRadius: '4px',
border: '1px solid #ccc',
}}
/>
</div>
<div style={{ marginBottom: '24px' }}>
<label
htmlFor="confirm-password"
style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}
>
Confirm Password
</label>
<input
type="password"
id="confirm-password"
required
style={{
width: '100%',
padding: '8px',
borderRadius: '4px',
border: '1px solid #ccc',
}}
/>
</div>
<button
type="submit"
style={{
width: '100%',
padding: '12px',
backgroundColor: '#007bff',
color: '#fff',
fontWeight: 'bold',
borderRadius: '4px',
border: 'none',
cursor: 'pointer',
marginBottom: '16px',
}}
>
Register
</button>
</form>
<div
style={{
textAlign: 'center',
marginBottom: '16px',
color: '#555',
fontSize: '14px',
borderBottom: '1px solid #ccc',
lineHeight: '0.1em',
margin: '10px 0 20px',
}}
>
<span style={{ background: '#fff', padding: '0 10px' }}>OR</span>
</div>
<div style={{ textAlign: 'center', marginBottom: '16px' }}>
<GoogleLogin
onSuccess={handleGoogleLoginSuccess}
onError={handleGoogleLoginError}
size="large"
width="100%"
/>
</div>
<p style={{ textAlign: 'center', color: '#555', marginTop: '16px' }}>
Already have an account?{' '}
<Link to="/login" style={{ color: '#007bff', textDecoration: 'none' }}>
Sign In
</Link>
</p>
</div>
</div>
);
};
const RegistrationForm: React.FC = () => {
return (
<GoogleOAuthProvider clientId={CLIENT_ID}>
<RegistrationFormContent />
</GoogleOAuthProvider>
);
};
export default RegistrationForm;
import React from 'react';
import { GoogleLogin, GoogleOAuthProvider } from '@react-oauth/google';
import { Link, useNavigate } from 'react-router-dom';
const CLIENT_ID = "532143017111-4eqtlp0oejqaovj6rf5l1ergvhrp4vao.apps.googleusercontent.com";
const RegistrationFormContent: React.FC = () => {
const navigate = useNavigate();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const nameElement = document.getElementById('name') as HTMLInputElement | null;
const emailElement = document.getElementById('email') as HTMLInputElement | null;
const passwordElement = document.getElementById('password') as HTMLInputElement | null;
const confirmPasswordElement = document.getElementById('confirm-password') as HTMLInputElement | null;
if (!nameElement || !emailElement || !passwordElement || !confirmPasswordElement) {
console.error('One or more input fields are missing');
return;
}
const name = nameElement.value;
const email = emailElement.value;
const password = passwordElement.value;
const confirmPassword = confirmPasswordElement.value;
if (password !== confirmPassword) {
console.error('Passwords do not match');
alert('Passwords do not match');
return;
}
try {
const response = await fetch('http://localhost:5000/api/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, email, password }),
});
const data = await response.json();
if (response.ok) {
console.log('User registered successfully:', data.message);
const loggedInUser = {
name,
email,
picture: 'https://via.placeholder.com/150',
};
localStorage.setItem('user', JSON.stringify(loggedInUser));
navigate('/dashboard');
} else {
console.error('Error:', data.error);
alert(data.error);
}
} catch (error) {
console.error('Error registering user:', error);
alert('Error registering user');
}
};
const handleGoogleLoginSuccess = async (response: any) => {
try {
const res = await fetch('http://localhost:5000/api/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: response.credential }),
});
const data = await res.json();
const loggedInUser = {
name: data.user.name,
email: data.user.email,
picture: data.user.picture || 'https://via.placeholder.com/150',
};
localStorage.setItem('user', JSON.stringify(loggedInUser));
navigate('/dashboard');
} catch (error) {
console.error('Error tiken verification:', error);
}
};
const handleGoogleLoginError = () => {
console.error('Error auth: Google login failed');
};
return (
<div
style={{
background: 'rgba(0, 0, 0, 0.5)',
minHeight: '100vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
padding: '16px',
}}
>
<div
className="registration-card"
style={{
maxWidth: '400px',
width: '100%',
borderRadius: '8px',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
backgroundColor: '#fff',
padding: '24px',
}}
>
<h2 style={{ textAlign: 'center', fontWeight: 'bold', color: '#333', marginBottom: '16px' }}>
Create Your Account
</h2>
<p style={{ textAlign: 'center', color: '#555', marginBottom: '24px' }}>
Join us to explore personalized health solutions.
</p>
<form noValidate autoComplete="off" onSubmit={handleSubmit}>
<div style={{ marginBottom: '16px' }}>
<label htmlFor="name" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
Name
</label>
<input
type="text"
id="name"
required
style={{
width: '100%',
padding: '8px',
borderRadius: '4px',
border: '1px solid #ccc',
}}
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label htmlFor="email" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
Email
</label>
<input
type="email"
id="email"
required
style={{
width: '100%',
padding: '8px',
borderRadius: '4px',
border: '1px solid #ccc',
}}
/>
</div>
<div style={{ marginBottom: '16px' }}>
<label htmlFor="password" style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}>
Password
</label>
<input
type="password"
id="password"
required
style={{
width: '100%',
padding: '8px',
borderRadius: '4px',
border: '1px solid #ccc',
}}
/>
</div>
<div style={{ marginBottom: '24px' }}>
<label
htmlFor="confirm-password"
style={{ display: 'block', marginBottom: '8px', fontWeight: '500' }}
>
Confirm Password
</label>
<input
type="password"
id="confirm-password"
required
style={{
width: '100%',
padding: '8px',
borderRadius: '4px',
border: '1px solid #ccc',
}}
/>
</div>
<button
type="submit"
style={{
width: '100%',
padding: '12px',
backgroundColor: '#007bff',
color: '#fff',
fontWeight: 'bold',
borderRadius: '4px',
border: 'none',
cursor: 'pointer',
marginBottom: '16px',
}}
>
Register
</button>
</form>
<div
style={{
textAlign: 'center',
marginBottom: '16px',
color: '#555',
fontSize: '14px',
borderBottom: '1px solid #ccc',
lineHeight: '0.1em',
margin: '10px 0 20px',
}}
>
<span style={{ background: '#fff', padding: '0 10px' }}>OR</span>
</div>
<div style={{ textAlign: 'center', marginBottom: '16px' }}>
<GoogleLogin
onSuccess={handleGoogleLoginSuccess}
onError={handleGoogleLoginError}
size="large"
width="100%"
/>
</div>
<p style={{ textAlign: 'center', color: '#555', marginTop: '16px' }}>
Already have an account?{' '}
<Link to="/login" style={{ color: '#007bff', textDecoration: 'none' }}>
Sign In
</Link>
</p>
</div>
</div>
);
};
const RegistrationForm: React.FC = () => {
return (
<GoogleOAuthProvider clientId={CLIENT_ID}>
<RegistrationFormContent />
</GoogleOAuthProvider>
);
};
export default RegistrationForm;

View File

@ -1,37 +1,37 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Приховує скроллбар у браузерах на основі WebKit (Chrome, Safari) */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
/* Приховує скроллбар у Firefox */
.no-scrollbar {
scrollbar-width: none;
}
/* Приховує скроллбар в Internet Explorer та Edge */
.no-scrollbar {
-ms-overflow-style: none;
}
/* HTML: <div class="loader"></div> */
.loader {
width: 20px;
aspect-ratio: 2;
--_g: no-repeat radial-gradient(circle closest-side,#000 90%,#0000);
background:
var(--_g) 0% 50%,
var(--_g) 50% 50%,
var(--_g) 100% 50%;
background-size: calc(100%/3) 50%;
animation: l3 1s infinite linear;
}
@keyframes l3 {
20%{background-position:0% 0%, 50% 50%,100% 50%}
40%{background-position:0% 100%, 50% 0%,100% 50%}
60%{background-position:0% 50%, 50% 100%,100% 0%}
80%{background-position:0% 50%, 50% 50%,100% 100%}
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Приховує скроллбар у браузерах на основі WebKit (Chrome, Safari) */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
/* Приховує скроллбар у Firefox */
.no-scrollbar {
scrollbar-width: none;
}
/* Приховує скроллбар в Internet Explorer та Edge */
.no-scrollbar {
-ms-overflow-style: none;
}
/* HTML: <div class="loader"></div> */
.loader {
width: 20px;
aspect-ratio: 2;
--_g: no-repeat radial-gradient(circle closest-side,#000 90%,#0000);
background:
var(--_g) 0% 50%,
var(--_g) 50% 50%,
var(--_g) 100% 50%;
background-size: calc(100%/3) 50%;
animation: l3 1s infinite linear;
}
@keyframes l3 {
20%{background-position:0% 0%, 50% 50%,100% 50%}
40%{background-position:0% 100%, 50% 0%,100% 50%}
60%{background-position:0% 50%, 50% 100%,100% 0%}
80%{background-position:0% 50%, 50% 50%,100% 100%}
}

View File

@ -1,15 +1,15 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
import { Provider } from 'react-redux'
import store from './store/index.ts'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>,
)
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
import { Provider } from 'react-redux'
import store from './store/index.ts'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>,
)

View File

@ -1,227 +1,227 @@
import React, { useEffect, useState, useRef } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { useLazySendChatQuestionQuery } from '../store/api/chatApi';
import callCenterIcon from '../assets/call-center.png';
interface ChatMessage {
sender: string;
text: string;
}
const HomePage: React.FC = () => {
const [sendChatQuestion, { isLoading, isFetching }] = useLazySendChatQuestionQuery();
const [message, setMessage] = useState('');
const [chatHistory, setChatHistory] = useState<ChatMessage[]>([]);
const messagesEndRef = useRef<HTMLDivElement>(null);
const location = useLocation();
const navigate = useNavigate();
const state = (location.state as any) || {};
const isNewChat = state.newChat === true;
const selectedChat = state.selectedChat || null;
const selectedChatId = selectedChat ? selectedChat.id : null;
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [chatHistory, isLoading, isFetching]);
useEffect(() => {
if (!isNewChat && selectedChat && selectedChat.chat) {
const messages: ChatMessage[] = selectedChat.chat
.split(/(?=^(User:|Bot:))/m)
.map((msg:any) => {
const trimmed = msg.trim();
const sender = trimmed.startsWith('User:') ? 'User' : 'Assistant';
return {
sender,
text: trimmed.replace(/^User:|^Bot:/, '').trim(),
};
});
setChatHistory(messages);
} else {
setChatHistory([]);
}
}, [isNewChat, selectedChat]);
const formatMessage = (text: string) => {
let lines: string[] = [];
if (text.includes('\n')) {
lines = text.split('\n');
} else {
lines = text.split(/(?=\d+\.\s+)/);
}
lines = lines.map((line) => line.trim()).filter((line) => line !== '');
if (lines.length === 0) return null;
return lines.map((line, index) => {
if (/^\d+\.\s*/.test(line)) {
const colonIndex = line.indexOf(':');
if (colonIndex !== -1) {
const firstPart = line.substring(0, colonIndex);
const rest = line.substring(colonIndex + 1);
return (
<div key={index} className="mb-1">
<strong>{firstPart.trim()}</strong>: {rest.trim()}
</div>
);
} else {
return (
<div key={index} className="mb-1">
<strong>{line}</strong>
</div>
);
}
}
return <div key={index}>{line}</div>;
});
};
const onSubmit = async () => {
if (!message.trim()) return;
const userMessage = message.trim();
setMessage('');
setChatHistory((prev) => [...prev, { sender: 'User', text: userMessage }]);
const storedUser = localStorage.getItem('user');
const email = storedUser ? JSON.parse(storedUser).email : '';
const payload = selectedChatId
? { query: userMessage, chatId: selectedChatId, email }
: { query: userMessage, email };
try {
const res = await sendChatQuestion(payload).unwrap();
let bestAnswer = res.response.best_answer;
if (typeof bestAnswer !== 'string') {
bestAnswer = String(bestAnswer);
}
bestAnswer = bestAnswer.trim();
if (bestAnswer) {
setChatHistory((prev) => [...prev, { sender: 'Assistant', text: bestAnswer }]);
}
if (!selectedChatId && res.response.chatId) {
const updatedChatHistory = [...chatHistory, { sender: 'User', text: userMessage }];
if (bestAnswer) {
updatedChatHistory.push({ sender: 'Assistant', text: bestAnswer });
}
const chatString = updatedChatHistory
.map((msg) => (msg.sender === 'User' ? 'User: ' : 'Bot: ') + msg.text)
.join('\n');
navigate(`/dashboard/chat/${res.response.chatId}`, {
replace: true,
state: {
selectedChat: {
id: res.response.chatId,
chat: chatString,
created_at: new Date().toISOString(),
},
},
});
}
} catch (error) {
console.error('Error:', error);
setChatHistory((prev) => [
...prev,
{ sender: 'Assistant', text: 'Something went wrong' },
]);
}
};
return (
<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">
{chatHistory.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full">
<h1 className="text-xl">Start a New Chat</h1>
</div>
) : (
chatHistory.map((msg, index) => {
const formattedMessage = formatMessage(msg.text);
if (!formattedMessage) return null;
return (
<div
key={index}
className={`flex mb-2 ${msg.sender === 'User' ? 'justify-end' : 'justify-start items-start'}`}
>
{msg.sender === 'Assistant' && (
<img
src={callCenterIcon}
alt="Call Center Icon"
className="w-6 h-6 mr-2"
/>
)}
<div
className={`p-3 rounded-lg max-w-md ${
msg.sender === 'User'
? 'bg-blue-500 text-white'
: 'bg-gray-200 text-gray-800'
}`}
>
{formattedMessage}
</div>
</div>
);
})
)}
{(isLoading || isFetching) && (
<div className="flex mb-2 justify-start items-start">
<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">
<svg
className="animate-spin h-5 w-5 mr-3 text-gray-500"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"
></path>
</svg>
<span>Assistant is typing...</span>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
<div className="w-2/3 mb-20">
<div className="flex">
<input
type="text"
placeholder="Waiting for your question..."
value={message}
onChange={(e) => setMessage(e.target.value)}
disabled={isLoading || isFetching}
className="w-full px-5 py-2 rounded-l-xl outline-none border border-gray-300"
/>
<button
onClick={onSubmit}
disabled={isLoading || isFetching}
className="bg-black text-white font-semibold px-4 py-2 rounded-r-xl hover:bg-gray-800 disabled:opacity-50"
>
Send
</button>
</div>
</div>
</div>
);
};
export default HomePage;
import React, { useEffect, useState, useRef } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { useLazySendChatQuestionQuery } from '../store/api/chatApi';
import callCenterIcon from '../assets/call-center.png';
interface ChatMessage {
sender: string;
text: string;
}
const HomePage: React.FC = () => {
const [sendChatQuestion, { isLoading, isFetching }] = useLazySendChatQuestionQuery();
const [message, setMessage] = useState('');
const [chatHistory, setChatHistory] = useState<ChatMessage[]>([]);
const messagesEndRef = useRef<HTMLDivElement>(null);
const location = useLocation();
const navigate = useNavigate();
const state = (location.state as any) || {};
const isNewChat = state.newChat === true;
const selectedChat = state.selectedChat || null;
const selectedChatId = selectedChat ? selectedChat.id : null;
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [chatHistory, isLoading, isFetching]);
useEffect(() => {
if (!isNewChat && selectedChat && selectedChat.chat) {
const messages: ChatMessage[] = selectedChat.chat
.split(/(?=^(User:|Bot:))/m)
.map((msg:any) => {
const trimmed = msg.trim();
const sender = trimmed.startsWith('User:') ? 'User' : 'Assistant';
return {
sender,
text: trimmed.replace(/^User:|^Bot:/, '').trim(),
};
});
setChatHistory(messages);
} else {
setChatHistory([]);
}
}, [isNewChat, selectedChat]);
const formatMessage = (text: string) => {
let lines: string[] = [];
if (text.includes('\n')) {
lines = text.split('\n');
} else {
lines = text.split(/(?=\d+\.\s+)/);
}
lines = lines.map((line) => line.trim()).filter((line) => line !== '');
if (lines.length === 0) return null;
return lines.map((line, index) => {
if (/^\d+\.\s*/.test(line)) {
const colonIndex = line.indexOf(':');
if (colonIndex !== -1) {
const firstPart = line.substring(0, colonIndex);
const rest = line.substring(colonIndex + 1);
return (
<div key={index} className="mb-1">
<strong>{firstPart.trim()}</strong>: {rest.trim()}
</div>
);
} else {
return (
<div key={index} className="mb-1">
<strong>{line}</strong>
</div>
);
}
}
return <div key={index}>{line}</div>;
});
};
const onSubmit = async () => {
if (!message.trim()) return;
const userMessage = message.trim();
setMessage('');
setChatHistory((prev) => [...prev, { sender: 'User', text: userMessage }]);
const storedUser = localStorage.getItem('user');
const email = storedUser ? JSON.parse(storedUser).email : '';
const payload = selectedChatId
? { query: userMessage, chatId: selectedChatId, email }
: { query: userMessage, email };
try {
const res = await sendChatQuestion(payload).unwrap();
let bestAnswer = res.response.best_answer;
if (typeof bestAnswer !== 'string') {
bestAnswer = String(bestAnswer);
}
bestAnswer = bestAnswer.trim();
if (bestAnswer) {
setChatHistory((prev) => [...prev, { sender: 'Assistant', text: bestAnswer }]);
}
if (!selectedChatId && res.response.chatId) {
const updatedChatHistory = [...chatHistory, { sender: 'User', text: userMessage }];
if (bestAnswer) {
updatedChatHistory.push({ sender: 'Assistant', text: bestAnswer });
}
const chatString = updatedChatHistory
.map((msg) => (msg.sender === 'User' ? 'User: ' : 'Bot: ') + msg.text)
.join('\n');
navigate(`/dashboard/chat/${res.response.chatId}`, {
replace: true,
state: {
selectedChat: {
id: res.response.chatId,
chat: chatString,
created_at: new Date().toISOString(),
},
},
});
}
} catch (error) {
console.error('Error:', error);
setChatHistory((prev) => [
...prev,
{ sender: 'Assistant', text: 'Something went wrong' },
]);
}
};
return (
<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">
{chatHistory.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full">
<h1 className="text-xl">Start a New Chat</h1>
</div>
) : (
chatHistory.map((msg, index) => {
const formattedMessage = formatMessage(msg.text);
if (!formattedMessage) return null;
return (
<div
key={index}
className={`flex mb-2 ${msg.sender === 'User' ? 'justify-end' : 'justify-start items-start'}`}
>
{msg.sender === 'Assistant' && (
<img
src={callCenterIcon}
alt="Call Center Icon"
className="w-6 h-6 mr-2"
/>
)}
<div
className={`p-3 rounded-lg max-w-md ${
msg.sender === 'User'
? 'bg-blue-500 text-white'
: 'bg-gray-200 text-gray-800'
}`}
>
{formattedMessage}
</div>
</div>
);
})
)}
{(isLoading || isFetching) && (
<div className="flex mb-2 justify-start items-start">
<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">
<svg
className="animate-spin h-5 w-5 mr-3 text-gray-500"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"
></path>
</svg>
<span>Assistant is typing...</span>
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
<div className="w-2/3 mb-20">
<div className="flex">
<input
type="text"
placeholder="Waiting for your question..."
value={message}
onChange={(e) => setMessage(e.target.value)}
disabled={isLoading || isFetching}
className="w-full px-5 py-2 rounded-l-xl outline-none border border-gray-300"
/>
<button
onClick={onSubmit}
disabled={isLoading || isFetching}
className="bg-black text-white font-semibold px-4 py-2 rounded-r-xl hover:bg-gray-800 disabled:opacity-50"
>
Send
</button>
</div>
</div>
</div>
);
};
export default HomePage;

View File

@ -1,186 +1,186 @@
import React, { useState, useEffect } from 'react';
import BackImage from '../assets/smallheadicon.png';
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
import { Box, Avatar } from '@mui/material';
import gsap from 'gsap';
import { useGSAP } from '@gsap/react';
import LogoutIcon from '@mui/icons-material/Logout';
import LoginIcon from '@mui/icons-material/Login';
import { Link, useNavigate } from 'react-router-dom';
const BouncingArrow = () => {
return (
<Box
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
mt: 2,
animation: 'bounce 1s infinite',
'@keyframes bounce': {
'0%, 100%': {
transform: 'translateY(0)',
},
'50%': {
transform: 'translateY(-10px)',
},
}
}}
>
<ArrowDownwardIcon fontSize="large" />
</Box>
);
};
interface NavbarProps {
user: any;
setUser: (user: any) => void;
}
const Navbar: React.FC<NavbarProps> = ({ user, setUser }) => {
const navigate = useNavigate();
const handleSignOut = () => {
setUser(null);
localStorage.removeItem('user');
};
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">
<div className="text-2xl font-semibold text-dark-blue flex items-center">
Health AI
<img src={BackImage} width={25} alt="Logo" />
</div>
<ul className="flex space-x-6 text-gray-600">
<li>
<Link to="/" className="hover:text-bright-blue transition duration-300">
Home
</Link>
</li>
<li>
<Link to="/about" className="hover:text-bright-blue transition duration-300">
About
</Link>
</li>
<li>
<Link to="/contact" className="hover:text-bright-blue transition duration-300">
Contact
</Link>
</li>
</ul>
<div className="flex items-center">
{user ? (
<div className="flex items-center gap-2">
<Avatar alt={user.name} src={user.picture} onClick={() => navigate('/profile')} />
<LogoutIcon
onClick={handleSignOut}
sx={{ cursor: 'pointer', color: '#0d47a1', fontSize: '30px' }}
/>
</div>
) : (
<LoginIcon
onClick={() => navigate('/register')}
sx={{ cursor: 'pointer', color: '#0d47a1', fontSize: '30px' }}
/>
)}
</div>
</nav>
);
};
const Home: React.FC = () => {
const navigate = useNavigate();
const [user, setUser] = useState<any>(null);
useEffect(() => {
const storedUser = localStorage.getItem('user');
if (storedUser) {
setUser(JSON.parse(storedUser));
}
}, []);
useGSAP(() => {
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('#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('#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 });
}, []);
const handleGetStartedClick = () => {
if (!user) {
navigate('/register');
} else {
navigate('/dashboard');
}
};
return (
<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">
<Navbar user={user} setUser={setUser} />
<div className="flex flex-col items-center">
<h1
id="mainheading"
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
</h1>
<p
id="secondheading"
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
insights. Take care of yourself with the power of modern technology.
</p>
<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">
<h3 className="text-xl font-medium mb-3 text-dark-blue">Personalized Prescription</h3>
<p className="text-gray-600">
Receive tailored medication recommendations specifically designed for your needs.
</p>
</div>
<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>
<p className="text-gray-600">
Stay informed about your health with real-time monitoring and AI-driven insights.
</p>
</div>
<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>
<p className="text-gray-600">
Utilize AI support to ensure you're following the best routines for a healthier lifestyle.
</p>
</div>
</div>
<div id="arrow" className="flex flex-col items-center mt-10 z-0">
<p className="text-gray-600">Try it out</p>
<BouncingArrow />
</div>
<div id="button-wrapper" className="flex justify-center mt-6">
<button
id="button"
onClick={handleGetStartedClick}
className="bg-bright-blue text-white font-medium py-2 px-5 rounded hover:bg-deep-blue transition duration-300 shadow-md"
>
Get started
</button>
</div>
</div>
</div>
<footer className="text-center text-gray-500 p-4">
<p>&copy; {new Date().getFullYear()} Health AI. All rights reserved.</p>
</footer>
</div>
);
};
export { Home, Navbar };
import React, { useState, useEffect } from 'react';
import BackImage from '../assets/smallheadicon.png';
import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
import { Box, Avatar } from '@mui/material';
import gsap from 'gsap';
import { useGSAP } from '@gsap/react';
import LogoutIcon from '@mui/icons-material/Logout';
import LoginIcon from '@mui/icons-material/Login';
import { Link, useNavigate } from 'react-router-dom';
const BouncingArrow = () => {
return (
<Box
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
mt: 2,
animation: 'bounce 1s infinite',
'@keyframes bounce': {
'0%, 100%': {
transform: 'translateY(0)',
},
'50%': {
transform: 'translateY(-10px)',
},
}
}}
>
<ArrowDownwardIcon fontSize="large" />
</Box>
);
};
interface NavbarProps {
user: any;
setUser: (user: any) => void;
}
const Navbar: React.FC<NavbarProps> = ({ user, setUser }) => {
const navigate = useNavigate();
const handleSignOut = () => {
setUser(null);
localStorage.removeItem('user');
};
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">
<div className="text-2xl font-semibold text-dark-blue flex items-center">
Health AI
<img src={BackImage} width={25} alt="Logo" />
</div>
<ul className="flex space-x-6 text-gray-600">
<li>
<Link to="/" className="hover:text-bright-blue transition duration-300">
Home
</Link>
</li>
<li>
<Link to="/about" className="hover:text-bright-blue transition duration-300">
About
</Link>
</li>
<li>
<Link to="/contact" className="hover:text-bright-blue transition duration-300">
Contact
</Link>
</li>
</ul>
<div className="flex items-center">
{user ? (
<div className="flex items-center gap-2">
<Avatar alt={user.name} src={user.picture} onClick={() => navigate('/profile')} />
<LogoutIcon
onClick={handleSignOut}
sx={{ cursor: 'pointer', color: '#0d47a1', fontSize: '30px' }}
/>
</div>
) : (
<LoginIcon
onClick={() => navigate('/register')}
sx={{ cursor: 'pointer', color: '#0d47a1', fontSize: '30px' }}
/>
)}
</div>
</nav>
);
};
const Home: React.FC = () => {
const navigate = useNavigate();
const [user, setUser] = useState<any>(null);
useEffect(() => {
const storedUser = localStorage.getItem('user');
if (storedUser) {
setUser(JSON.parse(storedUser));
}
}, []);
useGSAP(() => {
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('#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('#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 });
}, []);
const handleGetStartedClick = () => {
if (!user) {
navigate('/register');
} else {
navigate('/dashboard');
}
};
return (
<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">
<Navbar user={user} setUser={setUser} />
<div className="flex flex-col items-center">
<h1
id="mainheading"
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
</h1>
<p
id="secondheading"
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
insights. Take care of yourself with the power of modern technology.
</p>
<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">
<h3 className="text-xl font-medium mb-3 text-dark-blue">Personalized Prescription</h3>
<p className="text-gray-600">
Receive tailored medication recommendations specifically designed for your needs.
</p>
</div>
<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>
<p className="text-gray-600">
Stay informed about your health with real-time monitoring and AI-driven insights.
</p>
</div>
<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>
<p className="text-gray-600">
Utilize AI support to ensure you're following the best routines for a healthier lifestyle.
</p>
</div>
</div>
<div id="arrow" className="flex flex-col items-center mt-10 z-0">
<p className="text-gray-600">Try it out</p>
<BouncingArrow />
</div>
<div id="button-wrapper" className="flex justify-center mt-6">
<button
id="button"
onClick={handleGetStartedClick}
className="bg-bright-blue text-white font-medium py-2 px-5 rounded hover:bg-deep-blue transition duration-300 shadow-md"
>
Get started
</button>
</div>
</div>
</div>
<footer className="text-center text-gray-500 p-4">
<p>&copy; {new Date().getFullYear()} Health AI. All rights reserved.</p>
</footer>
</div>
);
};
export { Home, Navbar };

View File

@ -1,33 +1,33 @@
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"
// type chatQuestion = {
// }
const chatApi = createApi({
reducerPath: 'chat',
baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:5000' }),
endpoints: (builder) => ({
sendTestVersion: builder.query<string, any>({
query: (body) => ({
url: '/create-answer',
method: 'POST',
body: body
}),
transformResponse: ({ response }) => response
}),
sendChatQuestion: builder.query<any, any>({
query: (body) => ({
url: '/api/chat',
method: 'POST',
body: body
})
})
})
})
export default chatApi
export const { useLazySendTestVersionQuery,useLazySendChatQuestionQuery } = chatApi
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"
// type chatQuestion = {
// }
const chatApi = createApi({
reducerPath: 'chat',
baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:5000' }),
endpoints: (builder) => ({
sendTestVersion: builder.query<string, any>({
query: (body) => ({
url: '/create-answer',
method: 'POST',
body: body
}),
transformResponse: ({ response }) => response
}),
sendChatQuestion: builder.query<any, any>({
query: (body) => ({
url: '/api/chat',
method: 'POST',
body: body
})
})
})
})
export default chatApi
export const { useLazySendTestVersionQuery,useLazySendChatQuestionQuery } = chatApi

View File

@ -1,19 +1,19 @@
import { configureStore } from "@reduxjs/toolkit";
import chatApi from "./api/chatApi";
import { setupListeners } from '@reduxjs/toolkit/query';
const store = configureStore({
reducer: {
[chatApi.reducerPath]: chatApi.reducer
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(chatApi.middleware),
})
setupListeners(store.dispatch);
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
import { configureStore } from "@reduxjs/toolkit";
import chatApi from "./api/chatApi";
import { setupListeners } from '@reduxjs/toolkit/query';
const store = configureStore({
reducer: {
[chatApi.reducerPath]: chatApi.reducer
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(chatApi.middleware),
})
setupListeners(store.dispatch);
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
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} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
darkMode: "class",
theme: {
extend: {
colors: {
'light-blue': '#dbeafe',
'soft-blue': '#bfdbfe',
'light-cyan': '#e0f7fa',
'dark-blue': '#1e3a8a',
'bright-blue': '#2563eb',
'deep-blue': '#1d4ed8',
},
},
},
plugins: [],
}
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
darkMode: "class",
theme: {
extend: {
colors: {
'light-blue': '#dbeafe',
'soft-blue': '#bfdbfe',
'light-cyan': '#e0f7fa',
'dark-blue': '#1e3a8a',
'bright-blue': '#2563eb',
'deep-blue': '#1d4ed8',
},
},
},
plugins: [],
}

View File

@ -1,25 +1,25 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}

View File

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

View File

@ -1,23 +1,23 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}

View File

@ -1,7 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})