Feat: Adds cognito and memory

This commit is contained in:
2025-10-22 11:24:38 -03:00
parent f71b054dca
commit d37d5132eb
45 changed files with 3983 additions and 0 deletions

37
front/Dockerfile Normal file
View File

@@ -0,0 +1,37 @@
# Use uma imagem base Python oficial.
# Escolha uma versão que seja compatível com suas dependências.
FROM python:3.12-slim
# Copia os arquivos de requisitos primeiro para aproveitar o cache do Docker
COPY requirements.txt ./requirements.txt
# Instala as dependências do backend
RUN pip install --no-cache-dir -r requirements.txt
# Copia o restante dos diretórios e arquivos da aplicação
COPY ./ ./
# Garante que o script de inicialização seja executável
RUN chmod +x ./entrypoint.sh
# Cria os diretórios que a API FastAPI pode precisar (se eles não existirem)
# Estes diretórios serão usados para persistência se volumes forem montados.
#RUN mkdir -p /app/faiss_index_store && \
# mkdir -p /app/uploaded_pdfs
# Expõe as portas que os aplicativos usarão
# Porta 8000 para a API FastAPI
EXPOSE 8000
# Porta 8501 para o aplicativo Streamlit
EXPOSE 8501
# Define a variável de ambiente GROQ_API_KEY.
# É ALTAMENTE RECOMENDADO passar esta variável em tempo de execução
# em vez de embuti-la aqui por questões de segurança.
# Exemplo: docker run -e GROQ_API_KEY="sua_chave_aqui" ...
# ENV GROQ_API_KEY="SUA_CHAVE_GROQ_AQUI_SE_NECESSARIO_MAS_NAO_RECOMENDADO_EMBUTIR"
# Comando para executar quando o contêiner iniciar
# Executa o script start.sh que gerencia os dois processos
CMD ["./entrypoint.sh"]

Binary file not shown.

69
front/app/front.py Normal file
View File

@@ -0,0 +1,69 @@
import streamlit as st
from typing import Set
import requests
import json
import yaml
import st_auth
import boto3
from botocore.exceptions import ClientError
import jwt
headers = st.context.headers
# The ID token contains user claims
id_token = headers.get('x-amzn-oidc-data')
decoded = jwt.decode(id_token, options={"verify_signature": False})
# Tenta diferentes campos onde o user_id pode estar
user_id = (
decoded.get("sub") or # Subject (padrão JWT)
decoded.get("cognito:username") or # Username do Cognito
decoded.get("username") or # Username alternativo
decoded.get("user_id") # Campo customizado
)
st.header("Assistente Produtos Servicos")
url="https://xexm2wsz07-vpce-05915540d0592b921.execute-api.us-east-1.amazonaws.com/dev"
payload=[]
message_history=[]
if "user_prompt_history" not in st.session_state:
st.session_state["user_prompt_history"]=[]
if "chat_answer_history" not in st.session_state:
st.session_state["chat_answer_history"]=[]
if "chat_history" not in st.session_state:
st.session_state["chat_history"] = []
prompt=st.chat_input(placeholder="Digite uma mensagem...",key="prompt")
for generated_response, user_query in zip(st.session_state["chat_answer_history"],st.session_state["user_prompt_history"]):
st.chat_message("user").write(user_query)
st.chat_message("assistant").write(generated_response)
def create_sources_string(source_urls: Set[str])->str:
if not source_urls:
return ""
source_list=list(source_urls)
source_list.sort()
sources_string="source:\n"
for i, source in enumerate(source_list):
sources_string+=f"{i+1}, {source}\n"
return sources_string
if prompt:
st.chat_message("user").write(prompt)
with st.spinner("Generating response.."):
payload=[{"role":"user","content":prompt}]
content={"message":payload,"chat_history":st.session_state["chat_history"],"username":user_id}
headers={"Content-type":"application/json","x-api-key":json.loads(st_auth.get_secret())['api-gateway-api-key']}
generated_response=json.loads(requests.post(url,json=content,headers=headers).text)
if 'chat_history' in generated_response:
if st.session_state["chat_history"] == []:
st.session_state["chat_history"] = generated_response['chat_history']
if 'json' in generated_response:
generated_response=generated_response['json']
if 'message' in generated_response and generated_response['message']=="Endpoint request timed out":
generated_response="Falta de dados: Por favor encaminhe a conversa para a equipe desenvolvedora"
#generated_response=[{"role":"user","content":prompt}]
# sources= set([doc.metadata["source"] for doc in generated_response['context']])
#formatted_response=f"{generated_response['answer']} \n\n {create_sources_string(sources)}"
formatted_response=generated_response
st.chat_message("assistant").write(formatted_response)
st.session_state["user_prompt_history"].append(prompt)
st.session_state["chat_answer_history"].append(formatted_response)
st.session_state["chat_history"]=st.session_state["chat_history"]+[{"role":"user","content":prompt}]+[{"role":"assistant","content":formatted_response}]
st.session_state.user_input=""

102
front/app/st_auth.py Normal file
View File

@@ -0,0 +1,102 @@
import streamlit_authenticator as stauth
import yaml
from yaml.loader import SafeLoader
import streamlit as st
import boto3
from botocore.exceptions import ClientError
def get_authenticator():
# with open('.streamlit/users.yaml') as file:
# cred_config = yaml.load(file, Loader=SafeLoader)
file_content = read_text_file_from_s3('chatbot-editais-auth', 'config.yaml')
# Parse the YAML content safely
cred_config = yaml.safe_load(file_content)
# Pre-hashing all plain text passwords once
# hash_credentials = stauth.Hasher.hash_passwords(config['credentials'])
authenticator = stauth.Authenticate(
cred_config['credentials'],
cred_config['cookie']['name'],
cred_config['cookie']['key'],
cred_config['cookie']['expiry_days']
)
return authenticator
def st_authenticate(authenticator: stauth.Authenticate):
# authenticator = get_authenticator()
try:
authenticator.login(
location='sidebar',
fields=dict(Username="Usuário",
Password="Senha")
)
except Exception as e:
st.error(e)
authenticator.cookie_controller.delete_cookie()
st.warning('Caso o erro persistir, tente recarregar a página.')
st.stop()
if st.session_state['authentication_status']:
with st.sidebar:
st.write(f'Usuário: {st.session_state["name"]}')
authenticator.logout()
elif st.session_state['authentication_status'] is False:
with st.sidebar:
st.error('Usuário/senha incorreto')
st.stop()
elif st.session_state['authentication_status'] is None:
st.warning('Por favor, informe o seu usuário e senha no painel lateral')
st.stop()
def read_text_file_from_s3(bucket, key):
"""
Read a YAML file from an S3 bucket using provided AWS credentials.
Args:
bucket (str): Name of the S3 bucket
key (str): Path to the YAML file in the bucket
aws_access_key_id (str): AWS Access Key ID
aws_secret_access_key (str): AWS Secret Access Key
Returns:
dict: Parsed YAML content
"""
try:
# Create an S3 client
s3_client = boto3.client('s3')
# Download the file from S3
response = s3_client.get_object(Bucket=bucket, Key=key)
# Read the file content
file_content = response['Body'].read().decode('utf-8')
return file_content
except Exception as e:
st.error(f"Error reading file from S3: {e}")
return None
def get_secret():
secret_name = "assistente-produtos-servicos"
region_name = "us-east-1"
# Create a Secrets Manager client
session = boto3.session.Session()
client = session.client(
service_name='secretsmanager',
region_name=region_name
)
try:
get_secret_value_response = client.get_secret_value(
SecretId=secret_name
)
except ClientError as e:
# For a list of exceptions thrown, see
# https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html
raise e
secret = get_secret_value_response['SecretString']
return secret

23
front/entrypoint.sh Normal file
View File

@@ -0,0 +1,23 @@
#!/bin/bash
# Define o diretório base da aplicação dentro do contêiner
APP_DIR="/app"
# Navega para o diretório do backend e inicia a API FastAPI em segundo plano
#echo "Iniciando API FastAPI na porta 8000..."
#python app/backend/main.py &
# Aguarda alguns segundos para garantir que a API tenha tempo de iniciar
# Isso é opcional, mas pode ajudar a evitar problemas de conexão imediata do frontend
#echo "Aguardando a API iniciar..."
#sleep 10 # Ajuste o tempo conforme necessário
# Navega para o diretório do frontend e inicia o aplicativo Streamlit em primeiro plano
echo "Iniciando aplicativo Streamlit na porta 8501..."
# --server.headless=true é importante para rodar Streamlit em ambientes sem GUI (como Docker)
# --server.address=0.0.0.0 permite que o Streamlit seja acessado de fora do contêiner
# --server.enableCORS=false pode ser necessário dependendo da configuração, mas geralmente não para localhost
streamlit run app/front.py --server.port 8501 --server.address 0.0.0.0 --server.headless true
# O comando 'streamlit run' manterá o contêiner em execução.
# Se o Streamlit parar por algum motivo, o script e, consequentemente, o contêiner, terminarão.

1633
front/poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

17
front/pyproject.toml Normal file
View File

@@ -0,0 +1,17 @@
[tool.poetry]
name = "front"
version = "0.1.0"
description = "Front para o ChatBot editais do IFSP"
authors = ["Lucas DNX"]
readme = "README.md"
package-mode = false
[tool.poetry.dependencies]
python = "^3.12"
streamlit = "^1.49.1"
streamlit-authenticator = "^0.4.2"
boto3 = "^1.40.37"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

6
front/requirements.txt Normal file
View File

@@ -0,0 +1,6 @@
streamlit
requests
pyyaml
boto3
streamlit-authenticator
jwt