diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 776850c..8089b9e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ jobs: app-tests: runs-on: ubuntu-latest env: - OPENAI_API_KEY: "sk-proj-OWUfGkcPjXgwaoVtRDSVT3BlbkFJHNUmsndNjL4JJDWRQjUL" + OPENAI_API_KEY: "sk-svcacct-XilnF3KG3SaQiqbSj3MlT3BlbkFJxgpB6yDM9CrjxfxhlpMz" steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 diff --git a/.gitignore b/.gitignore index e1a80df..f495d3a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ # Ignore .env files .env -src/utils/globals.py \ No newline at end of file +src/utils/globals.py +__pycache__/ +.pytest_cache \ No newline at end of file diff --git a/Home.py b/Home.py index eb351c1..f5699b5 100644 --- a/Home.py +++ b/Home.py @@ -1,6 +1,4 @@ import streamlit as st -import login as login -import profile from pages.Profile import profile_page from login import login_page diff --git a/login.py b/login.py index 7142645..be73d28 100644 --- a/login.py +++ b/login.py @@ -23,7 +23,7 @@ """, unsafe_allow_html=True) # Configuração da Conexão com o BD -DATABASE_URL = "postgresql://root:wRoNcAkjnwCvGRdxD2OKAeSevhOLwJ5b@dpg-cq6i442ju9rs73e8bleg-a.oregon-postgres.render.com/loginbd_fg6e" +DATABASE_URL = "postgresql://mygamehub:XnDyt3Xa8O66bmE7Jbc3ly6zZ3f4eiGH@dpg-cqlnivdumphs7397s8k0-a.oregon-postgres.render.com/loginbd_6tj3" engine = create_engine(DATABASE_URL) Session = sessionmaker(bind=engine) session = Session() @@ -34,8 +34,6 @@ Column('id', Integer, primary_key=True), Column('username', String, unique=True), Column('password', String), - Column('name', String), - Column('gender', String), autoload_with=engine) # Funções de hashing e verificação de senha diff --git a/pages/Profile.py b/pages/Profile.py index 581ee59..fc97bb4 100644 --- a/pages/Profile.py +++ b/pages/Profile.py @@ -1,36 +1,31 @@ import streamlit as st import pandas as pd +import plotly.graph_objects as go from utils import session, users, profiles from werkzeug.security import generate_password_hash, check_password_hash def plot_avg_game_ratings_plotly(reviews, jogos): if not reviews.empty: - # Ajusta as notas para a escala de 0 a 5 reviews['nota'] = reviews['nota'] / 2 - - # Calcula a média de notas por jogo avg_ratings = reviews.groupby('jogo_id')['nota'].mean() avg_ratings = avg_ratings.to_frame().join(jogos.set_index('id')['title']).rename(columns={'nota': 'avg_rating', 'title': 'game_title'}) avg_ratings = avg_ratings.sort_values('avg_rating', ascending=False) - # Criação do gráfico fig = go.Figure() - for index, row in avg_ratings.iterrows(): fig.add_trace(go.Bar( - x=[row['game_title']], + x=[row['game_title']], y=[row['avg_rating']], - text=[f"{'★' * int(round(row['avg_rating']))} ({row['avg_rating']:.1f})"], # Mostra estrelas e a nota + text=[f"{'★' * int(round(row['avg_rating']))} ({row['avg_rating']:.1f})"], textposition='auto', )) - # Personalizações adicionais fig.update_layout( title='Média de Notas por Jogo (em estrelas)', xaxis_title='Jogos', yaxis_title='Nota Média', - yaxis=dict(range=[0,5]), # Define o limite do eixo y para 5 - plot_bgcolor='rgba(0,0,0,0)' + yaxis=dict(range=[0, 5]), + plot_bgcolor='rgba(0, 0, 0, 0)' ) return fig @@ -38,11 +33,10 @@ def plot_avg_game_ratings_plotly(reviews, jogos): return None def load_data(): - jogos = pd.read_csv("src/data/processed_data.csv") # Ajuste o caminho do arquivo se necessário + jogos = pd.read_csv("src/data/processed_data.csv") jogos['id'] = range(1, len(jogos) + 1) return jogos -# Função para carregar avaliações (simulação de dados persistidos) def load_reviews(): try: return pd.read_csv('src/data/reviews.csv') @@ -50,107 +44,118 @@ def load_reviews(): return pd.DataFrame(columns=["jogo_id", "usuario", "nota", "comentario", "favorito", "sentimento"]) def delete_account(user_id): - # Deletar perfil session.query(profiles).filter(profiles.c.user_id == user_id).delete() - # Deletar usuário session.query(users).filter(users.c.id == user_id).delete() session.commit() -def profile_page(): - if 'username' not in st.session_state or not st.session_state.get('logged_in', False): - st.error('Por favor, faça login primeiro.') - return - - username = st.session_state.username - st.title('Perfil') - st.sidebar.markdown("# Profile 👤") - +def get_user_profile(username): user = session.query(users).filter(users.c.username == username).first() profile = session.query(profiles).filter(profiles.c.user_id == user.id).first() - if profile is None: profile = profiles.insert().values(user_id=user.id) session.execute(profile) session.commit() profile = session.query(profiles).filter(profiles.c.user_id == user.id).first() + return user, profile - st.sidebar.title("Minha Conta") - settings_mode = st.sidebar.radio("Escolha uma opção:", ["Perfil", "Configurações da Conta"]) +def display_profile(profile, user): + edit_mode = st.session_state.get('edit_mode', False) - if settings_mode == "Perfil": - edit_mode = st.session_state.get('edit_mode', False) - - if edit_mode: - with st.form("profile_info"): - full_name = st.text_input("Nome Completo", value=profile.full_name if profile.full_name else "") - bio = st.text_area("Bio", value=profile.bio if profile.bio else "") - - save_changes = st.form_submit_button("Salvar Alterações") - if save_changes: - session.query(profiles).filter(profiles.c.user_id == user.id).update({ - 'full_name': full_name, - 'bio': bio - }) - session.commit() - st.session_state.edit_mode = False # Turn off edit mode after saving - st.success("Perfil atualizado com sucesso!") - - st.form_submit_button("Cancelar", on_click=lambda: st.session_state.__setitem__('edit_mode', False)) - else: - st.write(f"**Nome Completo:** {profile.full_name if profile.full_name else 'Não definido'}") - st.write(f"**Bio:** {profile.bio if profile.bio else 'Não definida'}") - st.button("Editar", on_click=lambda: st.session_state.__setitem__('edit_mode', True)) + if edit_mode: + with st.form("profile_info"): + full_name = st.text_input("Nome Completo", value=profile.full_name if profile.full_name else "") + bio = st.text_area("Bio", value=profile.bio if profile.bio else "") - st.write("---") - st.write("**Avaliações**") + save_changes = st.form_submit_button("Salvar Alterações") + if save_changes: + update_profile(user.id, full_name, bio) + st.session_state.edit_mode = False + st.success("Perfil atualizado com sucesso!") + + st.form_submit_button("Cancelar", on_click=lambda: st.session_state.__setitem__('edit_mode', False)) + else: + st.write(f"**Nome Completo:** {profile.full_name if profile.full_name else 'Não definido'}") + st.write(f"**Bio:** {profile.bio if profile.bio else 'Não definida'}") + st.button("Editar", on_click=lambda: st.session_state.__setitem__('edit_mode', True)) + +def update_profile(user_id, full_name, bio): + session.query(profiles).filter(profiles.c.user_id == user_id).update({ + 'full_name': full_name, + 'bio': bio + }) + session.commit() - reviews = load_reviews() - user_reviews = reviews[reviews['usuario'] == username] +def display_reviews(username): + reviews = load_reviews() + user_reviews = reviews[reviews['usuario'] == username] + + if not user_reviews.empty: + jogos = load_data() + user_reviews = user_reviews.merge(jogos[['id', 'title']], left_on='jogo_id', right_on='id') + + for _, row in user_reviews.iterrows(): + st.write(f"**Jogo:** {row['title']}") + st.write(f"**Nota:** {row['nota']}") + st.write(f"**Comentário:** {row['comentario']}") + st.write(f"**Favorito:** {'Sim' if row['favorito'] else 'Não'}") + st.write("---") + + if st.button("Mostrar Gráfico de Avaliações"): + fig = plot_avg_game_ratings_plotly(user_reviews, jogos) + if fig: + st.plotly_chart(fig) + else: + st.write("Você ainda não fez nenhuma avaliação.") - if not user_reviews.empty: - jogos = load_data() - user_reviews = user_reviews.merge(jogos[['id', 'title']], left_on='jogo_id', right_on='id') +def update_password(user_id, new_password): + account_data = {'password': generate_password_hash(new_password)} + session.query(users).filter(users.c.id == user_id).update(account_data) + session.commit() - for _, row in user_reviews.iterrows(): - st.write(f"**Jogo:** {row['title']}") - st.write(f"**Nota:** {row['nota']}") - st.write(f"**Comentário:** {row['comentario']}") - st.write(f"**Favorito:** {'Sim' if row['favorito'] else 'Não'}") - st.write("---") +def account_settings(user): + with st.form("account_settings"): + new_password = st.text_input("Nova Senha", type="password") + confirm_password = st.text_input("Confirmar Nova Senha", type="password") + + save_changes = st.form_submit_button("Salvar Alterações") + if save_changes: + if new_password and new_password != confirm_password: + st.error("As senhas não coincidem.") + else: + if new_password: + update_password(user.id, new_password) + st.success("Configurações de conta atualizadas com sucesso!") + + st.write("---") + st.write("### Deletar Conta") + if st.button("Deletar Conta"): + confirm_delete = st.checkbox("Confirmar Deleção de Conta") + if confirm_delete: + delete_account(user.id) + st.session_state.clear() + st.success("Conta deletada com sucesso. Por favor, feche o aplicativo.") - if st.button("Mostrar Gráfico de Avaliações"): - fig = plot_avg_game_ratings_plotly(user_reviews, jogos) - if fig: - st.plotly_chart(fig) - else: - st.write("Você ainda não fez nenhuma avaliação.") +def profile_page(): + if 'username' not in st.session_state or not st.session_state.get('logged_in', False): + st.error('Por favor, faça login primeiro.') + return - else: - st.header("Configurações de Conta") + username = st.session_state.username + st.title('Perfil') + st.sidebar.markdown("# Profile 👤") - with st.form("account_settings"): - new_password = st.text_input("Nova Senha", type="password") - confirm_password = st.text_input("Confirmar Nova Senha", type="password") + user, profile = get_user_profile(username) - save_changes = st.form_submit_button("Salvar Alterações") - if save_changes: - if new_password and new_password != confirm_password: - st.error("As senhas não coincidem.") - else: - if new_password: - account_data = {'password': generate_password_hash(new_password)} - session.query(users).filter(users.c.id == user.id).update(account_data) - session.commit() - st.success("Configurações de conta atualizadas com sucesso!") + st.sidebar.title("Minha Conta") + settings_mode = st.sidebar.radio("Escolha uma opção:", ["Perfil", "Configurações da Conta"]) + if settings_mode == "Perfil": + display_profile(profile, user) st.write("---") - st.write("### Deletar Conta") - if st.button("Deletar Conta"): - confirm_delete = st.checkbox("Confirmar Deleção de Conta") - if confirm_delete: - delete_account(user.id) - st.session_state.clear() - st.success("Conta deletada com sucesso. Por favor, feche o aplicativo.") + st.write("**Avaliações**") + display_reviews(username) + else: + account_settings(user) if __name__ == "__main__": profile_page() \ No newline at end of file diff --git a/pages/tests/test_busca.py b/pages/tests/test_busca.py new file mode 100644 index 0000000..54698be --- /dev/null +++ b/pages/tests/test_busca.py @@ -0,0 +1,4 @@ +from streamlit.testing.v1 import AppTest +def test_busca(): + at = AppTest.from_file("../🔎 Busca.py").run(timeout=50) + assert not at.error, "Erro encontrado durante a execução inicial" \ No newline at end of file diff --git a/pages/tests/test_login.py b/pages/tests/test_login.py new file mode 100644 index 0000000..9f959e5 --- /dev/null +++ b/pages/tests/test_login.py @@ -0,0 +1,11 @@ +from streamlit.testing.v1 import AppTest + +def test_login(): + at = AppTest.from_file("../../login.py").run(timeout=50) + assert not at.error, "Erro encontrado durante a execução inicial" + + at.text_input[0].input("luishmq").run(timeout=50) + at.text_input[1].input("1234").run(timeout=50) + at.button[0].click().run(timeout=20) + + assert at.error[0].value == "Nome de usuário ou senha incorretos" \ No newline at end of file diff --git a/pages/tests/test_mario.py b/pages/tests/test_mario.py new file mode 100644 index 0000000..7e62588 --- /dev/null +++ b/pages/tests/test_mario.py @@ -0,0 +1,9 @@ +from streamlit.testing.v1 import AppTest + +def test_mario(): + at = AppTest.from_file("../🤖 Mario.py").run(timeout=50) + assert not at.error, "Erro encontrado durante a execução inicial" + + at.text_input[0].input("Qual jogo de corrida você recomenda?").run(timeout=50) + at.button[0].click().run(timeout=20) + assert at.markdown[2].value == "#### Resposta:" \ No newline at end of file diff --git a/pages/tests/test_reviews.py b/pages/tests/test_reviews.py new file mode 100644 index 0000000..752a3c5 --- /dev/null +++ b/pages/tests/test_reviews.py @@ -0,0 +1,21 @@ +from streamlit.testing.v1 import AppTest +import logging + +# Configuração de logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +def test_reviews(): + try: + at = AppTest.from_file("../🧠 Reviews.py").run(timeout=50) + assert not at.error, f"Erro encontrado durante a execução inicial: {at.error}" + logger.info("Teste inicial passado sem erros.") + + # Adicione qualquer verificação adicional que possa ser necessária aqui + + except Exception as e: + logger.error(f"Exceção durante o teste: {e}") + assert False, f"Exceção durante o teste: {e}" + +if __name__ == "__main__": + test_reviews() diff --git a/pages/tests/test_wishlist.py b/pages/tests/test_wishlist.py new file mode 100644 index 0000000..4b84233 --- /dev/null +++ b/pages/tests/test_wishlist.py @@ -0,0 +1,21 @@ +from streamlit.testing.v1 import AppTest +import logging + +# Configuração de logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +def test_wishlist(): + try: + at = AppTest.from_file("../✨ Wishlist.py").run(timeout=50) + assert not at.error, f"Erro encontrado durante a execução inicial: {at.error}" + logger.info("Teste inicial passado sem erros.") + +# Adicione qualquer verificação adicional que possa ser necessária aqui + + except Exception as e: + logger.error(f"Exceção durante o teste: {e}") + assert False, f"Exceção durante o teste: {e}" + +if __name__ == "__main__": + test_reviews() diff --git "a/pages/\342\234\250 Wishlist.py" "b/pages/\342\234\250 Wishlist.py" index ae71dd5..7b3493f 100644 --- "a/pages/\342\234\250 Wishlist.py" +++ "b/pages/\342\234\250 Wishlist.py" @@ -1,58 +1,93 @@ -import streamlit as st -from sqlalchemy.orm import sessionmaker -from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData - -# Configuração da Conexão com o BD -DATABASE_URL = "postgresql://root:wRoNcAkjnwCvGRdxD2OKAeSevhOLwJ5b@dpg-cq6i442ju9rs73e8bleg-a.oregon-postgres.render.com/loginbd_fg6e" -engine = create_engine(DATABASE_URL) -metadata = MetaData() - -# Tabela users -users = Table('users', metadata, - Column('id', Integer, primary_key=True), - Column('username', String, unique=True), - Column('password', String)) - -# Tabela wishlist -wishlist = Table('wishlist', metadata, - Column('id', Integer, primary_key=True), - Column('user_id', Integer), - Column('game_id', String)) - -metadata.create_all(engine) - -Session = sessionmaker(bind=engine) -session = Session() - -# Verificar se o usuário está logado -if 'logged_in' not in st.session_state or not st.session_state.logged_in: - st.warning("Por favor, faça login para acessar a lista de desejos.") - st.stop() - -# Obter informações do usuário logado -username = st.session_state.username -user = session.query(users).filter(users.c.username == username).first() - -# Página da Lista de Desejos -st.title('Minha Lista de Desejos') -st.sidebar.markdown("# Wishlist ✨") - -# Adicionar jogo à lista de desejos -game_id = st.text_input('Nome do Jogo para adicionar à lista de desejos') -if st.button('Adicionar à lista de desejos'): - if game_id: - new_wishlist_item = wishlist.insert().values(user_id=user.id, game_id=game_id) - session.execute(new_wishlist_item) - session.commit() - st.success('Jogo adicionado à lista de desejos com sucesso!') - else: - st.error('Por favor, insira o nome de um jogo.') - -# Exibir lista de desejos -st.header('Sua Lista de Desejos') -wishlist_items = session.query(wishlist).filter(wishlist.c.user_id == user.id).all() -if wishlist_items: - for item in wishlist_items: - st.write(f"Jogo: {item.game_id}") -else: - st.write('Sua lista de desejos está vazia.') +import streamlit as st +from sqlalchemy.orm import sessionmaker +from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData + +# Configuração da Conexão com o BD +DATABASE_URL = "postgresql://mygamehub:XnDyt3Xa8O66bmE7Jbc3ly6zZ3f4eiGH@dpg-cqlnivdumphs7397s8k0-a.oregon-postgres.render.com/loginbd_6tj3" + +# Configuração da Conexão com o Banco de Dados +class Database: + def __init__(self, url): + self.engine = create_engine(url) + self.metadata = MetaData() + self.metadata.create_all(self.engine) + self.Session = sessionmaker(bind=self.engine) + self.session = self.Session() + +# Facade para Gerenciamento de Usuários e Wishlist +class WishlistFacade: + def __init__(self, db): + self.db = db + self.users = Table('users', self.db.metadata, + Column('id', Integer, primary_key=True), + Column('username', String, unique=True), + Column('password', String)) + + self.wishlist = Table('wishlist', self.db.metadata, + Column('id', Integer, primary_key=True), + Column('user_id', Integer), + Column('game_id', String)) + + def get_logged_in_user(self, username): + return self.db.session.query(self.users).filter(self.users.c.username == username).first() + + def add_to_wishlist(self, user_id, game_id): + new_wishlist_item = self.wishlist.insert().values(user_id=user_id, game_id=game_id) + self.db.session.execute(new_wishlist_item) + self.db.session.commit() + + def remove_from_wishlist(self, user_id, game_id): + self.db.session.execute(self.wishlist.delete().where(self.wishlist.c.user_id == user_id).where(self.wishlist.c.game_id == game_id)) + self.db.session.commit() + + def get_wishlist_items(self, user_id): + return self.db.session.query(self.wishlist).filter(self.wishlist.c.user_id == user_id).all() + +# Funções de Interface com o Usuário +def check_user_logged_in(): + if 'logged_in' not in st.session_state or not st.session_state.logged_in: + st.warning("Por favor, faça login para acessar a lista de desejos.") + st.stop() + +def display_wishlist(facade, user_id): + st.header('Sua Lista de Desejos') + wishlist_items = facade.get_wishlist_items(user_id) + if wishlist_items: + for item in wishlist_items: + col1, col2 = st.columns([3, 1]) + col1.write(f"Jogo: {item.game_id}") + if col2.button('Remover', key=item.id): + facade.remove_from_wishlist(user_id, item.game_id) + st.success(f"Jogo {item.game_id} removido da lista de desejos com sucesso!") + st.experimental_rerun() # Atualizar a página para refletir a remoção + else: + st.write('Sua lista de desejos está vazia.') + +# Função Principal +def main(): + check_user_logged_in() + username = st.session_state.username + + # Instancia a conexão com o banco de dados e a facade + db = Database(DATABASE_URL) + facade = WishlistFacade(db) + + user = facade.get_logged_in_user(username) + if user: + st.title('Minha Lista de Desejos') + st.sidebar.markdown("# Wishlist ✨") + + game_id = st.text_input('Nome do Jogo para adicionar à lista de desejos') + if st.button('Adicionar à lista de desejos'): + if game_id: + facade.add_to_wishlist(user.id, game_id) + st.success('Jogo adicionado à lista de desejos com sucesso!') + else: + st.error('Por favor, insira o nome de um jogo.') + + display_wishlist(facade, user.id) + else: + st.error("Não foi possível recuperar o usuário logado.") + +if __name__ == "__main__": + main() diff --git "a/pages/\360\237\223\260 Journal.py" "b/pages/\360\237\223\260 Journal.py" index 01d4931..89ab492 100644 --- "a/pages/\360\237\223\260 Journal.py" +++ "b/pages/\360\237\223\260 Journal.py" @@ -129,19 +129,19 @@ unsafe_allow_html=True ) -# Função para formatar a data de publicação + def format_date(date_str): return datetime.datetime.strptime(date_str, "%Y-%m-%d").strftime("%d %b %Y") -# Barra de pesquisa + search_query = st.text_input("Pesquisar jogo:", "", key="search", placeholder="Digite o nome do jogo...") -# Obter o ID do artigo da URL + query_params = st.experimental_get_query_params() article_id = query_params.get("article_id", [None])[0] game_name = query_params.get("game", [None])[0] -# Filtrar notícias com base na pesquisa + if search_query: filtered_articles = [item for item in news_data if search_query.lower() in item["game"].lower()] else: diff --git "a/pages/\360\237\224\216 Busca.py" "b/pages/\360\237\224\216 Busca.py" index e15a7a4..b2b5df9 100644 --- "a/pages/\360\237\224\216 Busca.py" +++ "b/pages/\360\237\224\216 Busca.py" @@ -1,71 +1,116 @@ import pandas as pd import streamlit as st -def clean_price(price): - if price.lower() == 'free': - return 0.0 - return float(price.replace('$', '').replace(',', '')) - -@st.cache_data -def load_data(): - data = pd.read_csv('src/data/processed_data.csv') - data['original_price'] = data['original_price'].apply(clean_price) # Limpar preços - # Limpar as tags para remover chaves e garantir que não haja duplicatas - data['popular_tags'] = data['popular_tags'].fillna('').apply(lambda x: [tag.strip() for tag in x.strip('[]').replace('"', '').split(',')]) - return data - -data = load_data() - -# Extrair todas as tags únicas e remover duplicatas -all_tags = sorted(set(tag for tags in data['popular_tags'] for tag in tags if tag)) - -page_bg_img = ''' - -''' - -st.markdown(page_bg_img, unsafe_allow_html=True) -st.sidebar.markdown("# Busca 🔎") - -st.title('Sistema de Busca de Jogos') -query = st.text_input("Digite o nome do jogo que deseja buscar:") - -# Slider para filtrar por preço -min_price, max_price = st.slider( - 'Selecione o intervalo de preços:', - min_value=float(data['original_price'].min()), - max_value=float(data['original_price'].max()), - value=(float(data['original_price'].min()), float(data['original_price'].max())) -) - -# Campo de seleção para filtrar por tags populares -selected_tags = st.multiselect('Selecione as tags populares:', all_tags) - -if query or selected_tags: - results = data.copy() - - if query: - results = results[results['title'].str.contains(query, case=False, na=False)] - - if selected_tags: - results = results[results['popular_tags'].apply(lambda tags: all(tag in tags for tag in selected_tags))] - - results = results[results['original_price'].between(min_price, max_price)] - - st.write(f"Resultados para '{query}' com preço entre {min_price} e {max_price} e tags: {', '.join(selected_tags)}:") - st.dataframe(results) -else: - st.write("Digite o nome de um jogo para buscar e ajuste o filtro de preço e tags se desejar.") +class GameSearchFacade: + def __init__(self): + self.data = self.load_and_prepare_data() + self.all_tags = self.extract_all_tags() + + @staticmethod + def clean_price(price): + if price.lower() == 'free': + return 0.0 + return float(price.replace('$', '').replace(',', '')) + + @staticmethod + def load_and_prepare_data(): + data = GameSearchFacade.load_data() + data = GameSearchFacade.clean_data(data) + return data + + @staticmethod + @st.cache_data + def load_data(): + return pd.read_csv('src/data/processed_data.csv') + + @staticmethod + def clean_data(data): + data['original_price'] = data['original_price'].apply(GameSearchFacade.clean_price) + data['popular_tags'] = data['popular_tags'].fillna('').apply( + lambda x: [tag.strip() for tag in x.strip('[]').replace('"', '').split(',')] + ) + return data + + def extract_all_tags(self): + return sorted(set(tag for tags in self.data['popular_tags'] for tag in tags if tag)) + + def search_games(self, query='', selected_tags=None, min_price=None, max_price=None): + selected_tags = selected_tags or [] + results = self.filter_by_query(query) + results = self.filter_by_tags(results, selected_tags) + results = self.filter_by_price(results, min_price, max_price) + return results + + def filter_by_query(self, query): + if not query: + return self.data.copy() + return self.data[self.data['title'].str.contains(query, case=False, na=False)] + + def filter_by_tags(self, data, selected_tags): + if not selected_tags: + return data + return data[data['popular_tags'].apply(lambda tags: all(tag in tags for tag in selected_tags))] + + def filter_by_price(self, data, min_price, max_price): + if min_price is None or max_price is None: + return data + return data[data['original_price'].between(min_price, max_price)] + + def get_all_tags(self): + return self.all_tags + +# Função para configurar a interface do usuário +def configure_interface(game_search): + set_page_style() + st.sidebar.markdown("# Busca 🔎") + st.title('Sistema de Busca de Jogos') + + query = st.text_input("Digite o nome do jogo que deseja buscar:") + + min_price, max_price = get_price_range(game_search) + + selected_tags = st.multiselect('Selecione as tags populares:', game_search.get_all_tags()) + + results = game_search.search_games(query, selected_tags, min_price, max_price) + + display_results(query, min_price, max_price, selected_tags, results) + +def set_page_style(): + page_bg_img = ''' + + ''' + st.markdown(page_bg_img, unsafe_allow_html=True) + +def get_price_range(game_search): + return st.slider( + 'Selecione o intervalo de preços:', + min_value=float(game_search.data['original_price'].min()), + max_value=float(game_search.data['original_price'].max()), + value=(float(game_search.data['original_price'].min()), float(game_search.data['original_price'].max())) + ) + +def display_results(query, min_price, max_price, selected_tags, results): + if not results.empty: + st.write(f"Resultados para '{query}' com preço entre {min_price} e {max_price} e tags: {', '.join(selected_tags)}:") + st.dataframe(results) + else: + st.write("Digite o nome de um jogo para buscar e ajuste o filtro de preço e tags se desejar.") + +# Instanciar o facade +game_search = GameSearchFacade() + +# Configurar a interface do usuário +configure_interface(game_search) \ No newline at end of file diff --git "a/pages/\360\237\244\226 Mario.py" "b/pages/\360\237\244\226 Mario.py" index 320c032..a15e581 100644 --- "a/pages/\360\237\244\226 Mario.py" +++ "b/pages/\360\237\244\226 Mario.py" @@ -8,68 +8,66 @@ from langchain_openai import ChatOpenAI, OpenAIEmbeddings from langchain_core.messages import HumanMessage, AIMessage from langchain.chains import create_retrieval_chain -from src.utils.globals import KEY + +load_dotenv() + +ai_prompt = """ +Você é o Mario, o assistente virtual do MyGameHub. + +O seu papel é fornecer assistência informativa e amigável aos usuários com dúvidas sobre jogos e recomendações. + +Você deve possuir um conhecimento amplo sobre o mundo dos jogos da Steam para fornecer melhores recomendações e respostas aos usuários. + +Pergunta do usuário: {input} + +Você deve possuir memória e compreensão de todas as perguntas e respostas anteriores para fornecer respostas coerentes e úteis: {messages} + +Responda à pergunta para o usuário de forma agradável, sutil e precisa com base no seguinte contexto: {context} +""" @st.cache_resource def load_data(): + """Carregando os dados e criando o vectorstore.""" df = pd.read_csv("src/data/processed_data.csv") texts = df.apply(lambda x: x['title'] + " - " + x['game_description'], axis=1).tolist() - vectorstore = FAISS.from_texts(texts, embedding=OpenAIEmbeddings(api_key=KEY)) + vectorstore = FAISS.from_texts(texts, embedding=OpenAIEmbeddings()) return vectorstore.as_retriever() class Mario: + """ + Classe do assistente virtual Mario do MyGameHub. + + Atributos: + model (ChatOpenAI): O modelo de linguagem usado para gerar respostas. + chain (object): A cadeia de recuperação de respostas. + """ def __init__(self): - self.prompt_template = self._create_prompt_template() - self.model = self._initialize_model() - self.chain = self._create_chain() - self.retrieval_chain = self._create_retrieval_chain() + """Inicializando a instância do Mario, configurando o estado da sessão, o modelo e a cadeia de recuperação.""" self._initialize_session_state() - - def _create_prompt_template(self): - template = """Você é o Mario, o assistente virtual do MyGameHub. - - O seu papel é fornecer assistência informativa e amigável aos usuários com dúvidas sobre jogos e recomendações. - - Você deve possuir um conhecimento amplo sobre o mundo dos jogos da Steam para fornecer melhores recomendações e respostas aos usuários. - - Pergunta do usuário: - - {input} - - Você deve possuir memória e compreensão de todas as perguntas e respostas anteriores para fornecer respostas coerentes e úteis: - - {messages} - - Responda à pergunta para o usuário de forma agradável, sutil e precisa com base no seguinte contexto: - - {context} - """ - return ChatPromptTemplate.from_template(template) - - def _initialize_model(self): - return ChatOpenAI(temperature=0.2, model="gpt-4o", api_key=KEY) - - def _create_chain(self): - return ( - self.prompt_template - | self.model - | StrOutputParser() - ) - - def _create_retrieval_chain(self): - return create_retrieval_chain(load_data(), self.chain) + self.model = ChatOpenAI(temperature=0.2, model="gpt-4o") + self.chain = self._create_chain() def _initialize_session_state(self): + """Inicializando o estado da sessão para armazenar o histórico da conversa.""" if 'chat_history' not in st.session_state: st.session_state['chat_history'] = ChatMessageHistory() + def _create_chain(self): + """Criando a cadeia de processamento para o agente.""" + prompt = ChatPromptTemplate.from_template(ai_prompt) + chain = prompt | self.model | StrOutputParser() + retrieval_chain = create_retrieval_chain(load_data(), chain) + return retrieval_chain + def get_response(self, user_input): + """Obtendo resposta do chatbot.""" st.session_state.chat_history.add_user_message(user_input) - response = self.retrieval_chain.invoke({"input": user_input, "messages": st.session_state.chat_history.messages}) + response = self.chain.invoke({"input": user_input, "messages": st.session_state.chat_history.messages}) st.session_state.chat_history.add_ai_message(response["answer"]) return response["answer"] def display_chat_history(self): + """Exibindo o histórico da conversa para o usuário.""" download_str = [] with st.expander("Histórico da Conversa", expanded=True): for message in reversed(st.session_state.chat_history.messages): diff --git "a/pages/\360\237\247\240 Reviews.py" "b/pages/\360\237\247\240 Reviews.py" index f96898d..c50c6c9 100644 --- "a/pages/\360\237\247\240 Reviews.py" +++ "b/pages/\360\237\247\240 Reviews.py" @@ -2,109 +2,144 @@ import pandas as pd import plotly.graph_objects as go from transformers import pipeline - +from sqlalchemy.orm import sessionmaker +from sqlalchemy import create_engine, Table, Column, Integer, String, Float, MetaData + +# Configuração da Conexão com o BD +DATABASE_URL = "postgresql://mygamehub:XnDyt3Xa8O66bmE7Jbc3ly6zZ3f4eiGH@dpg-cqlnivdumphs7397s8k0-a.oregon-postgres.render.com/loginbd_6tj3" +engine = create_engine(DATABASE_URL) +metadata = MetaData() + +# Tabela reviews +reviews_table = Table('reviews', metadata, + Column('id', Integer, primary_key=True), + Column('jogo_id', Integer), + Column('usuario', String), + Column('nota', Float), + Column('comentario', String), + Column('favorito', String), + Column('sentimento', String)) + +metadata.create_all(engine) +Session = sessionmaker(bind=engine) +session = Session() + +# Carregamento de CSS personalizado def load_custom_css(): with open('src/data/styleReviews.css') as f: st.markdown(f'', unsafe_allow_html=True) -def plot_avg_game_ratings_plotly(reviews, jogos): - if not reviews.empty: - # Ajusta as notas para a escala de 0 a 5 - reviews['nota'] = reviews['nota'] / 2 - - # Calcula a média de notas por jogo - avg_ratings = reviews.groupby('jogo_id')['nota'].mean() - avg_ratings = avg_ratings.to_frame().join(jogos.set_index('id')['title']).rename(columns={'nota': 'avg_rating', 'title': 'game_title'}) - avg_ratings = avg_ratings.sort_values('avg_rating', ascending=False) - - # Criação do gráfico - fig = go.Figure() - - for index, row in avg_ratings.iterrows(): - fig.add_trace(go.Bar( - x=[row['game_title']], - y=[row['avg_rating']], - text=[f"{'★' * int(round(row['avg_rating']))} ({row['avg_rating']:.1f})"], # Mostra estrelas e a nota - textposition='auto', - )) - - # Personalizações adicionais - fig.update_layout( - title='Média de Notas por Jogo (em estrelas)', - xaxis_title='Jogos', - yaxis_title='Nota Média', - yaxis=dict(range=[0,5]), # Define o limite do eixo y para 5 - plot_bgcolor='rgba(0,0,0,0)' - ) - - return fig - else: - return None - -# Função para carregar os dados dos jogos -def load_data(): - jogos = pd.read_csv("src/data/processed_data.csv") # Ajuste o caminho do arquivo se necessário - jogos['id'] = range(1, len(jogos) + 1) - return jogos - -# Função para carregar avaliações (simulação de dados persistidos) -def load_reviews(): - try: - return pd.read_csv('src/data/reviews.csv') - except FileNotFoundError: - return pd.DataFrame(columns=["jogo_id", "usuario", "nota", "comentario", "favorito"]) - -# Função para salvar avaliações (persistência de dados) -def save_reviews(reviews): - reviews.to_csv('src/data/reviews.csv', index=False) - -def add_review(reviews, novo_review): - novo_review_df = pd.DataFrame([novo_review]) - reviews = pd.concat([reviews, novo_review_df], ignore_index=True) - save_reviews(reviews) - return reviews - -def analyze_sentiment(text): - sentiment_pipeline = pipeline("sentiment-analysis", model="nlptown/bert-base-multilingual-uncased-sentiment") - result = sentiment_pipeline(text)[0] - label = result['label'] - if label == '1 star' or label == '2 stars': - return 'Negative' - elif label == '5 stars' or label == '4 stars': - return 'Positive' - else: - return 'Neutral' +# Classe para carregar dados +class DataLoader: + @st.cache_resource + def load_games(): + jogos = pd.read_csv("src/data/processed_data.csv") # Usando o arquivo enviado + jogos['id'] = range(1, len(jogos) + 1) + return jogos + + @staticmethod + def load_reviews(): + return pd.read_sql(reviews_table.select(), con=engine) + +# Classe para análise de sentimento +class SentimentAnalyzer: + def __init__(self): + self.pipeline = pipeline("sentiment-analysis", model="nlptown/bert-base-multilingual-uncased-sentiment") + + def analyze_sentiment(self, text): + result = self.pipeline(text)[0] + label = result['label'] + if label in ['1 star', '2 stars']: + return 'Negative' + elif label in ['5 stars', '4 stars']: + return 'Positive' + else: + return 'Neutral' + +# Classe para visualização de dados +class DataVisualizer: + @staticmethod + def plot_avg_game_ratings_plotly(reviews, jogos): + if not reviews.empty: + reviews['nota'] = reviews['nota'] / 2 + avg_ratings = reviews.groupby('jogo_id')['nota'].mean() + avg_ratings = avg_ratings.to_frame().join(jogos.set_index('id')['title']).rename(columns={'nota': 'avg_rating', 'title': 'game_title'}) + avg_ratings = avg_ratings.sort_values('avg_rating', ascending=False) + + fig = go.Figure() + + for index, row in avg_ratings.iterrows(): + fig.add_trace(go.Bar( + x=[row['game_title']], + y=[row['avg_rating']], + text=[f"{'★' * int(round(row['avg_rating']))} ({row['avg_rating']:.1f})"], + textposition='auto', + )) + + fig.update_layout( + title='Média de Notas por Jogo (em estrelas)', + xaxis_title='Jogos', + yaxis_title='Nota Média', + yaxis=dict(range=[0,5]), + plot_bgcolor='rgba(0,0,0,0)' + ) + + return fig + else: + return None + +# Classe Facade +class GameReviewFacade: + def __init__(self): + self.jogos = DataLoader.load_games() + self.reviews = DataLoader.load_reviews() + self.sentiment_analyzer = SentimentAnalyzer() + + def add_review(self, novo_review): + session.execute(reviews_table.insert().values(novo_review)) + session.commit() + self.reviews = DataLoader.load_reviews() # Recarregar as reviews do banco de dados + return self.reviews + + def analyze_sentiment(self, comentario): + return self.sentiment_analyzer.analyze_sentiment(comentario) + + def plot_avg_ratings(self): + return DataVisualizer.plot_avg_game_ratings_plotly(self.reviews, self.jogos) + + def get_reviews_with_game_names(self): + reviews = self.reviews.copy() + reviews = reviews.merge(self.jogos[['id', 'title']], left_on='jogo_id', right_on='id', how='left') + reviews = reviews.drop(columns=['id_y']) + reviews = reviews.rename(columns={'id_x': 'id', 'title': 'jogo'}) + return reviews def main(): st.title("Avaliação de Jogos") st.sidebar.markdown("# Reviews 🧠") + + load_custom_css() + facade = GameReviewFacade() - jogos = load_data() - reviews = load_reviews() - - # Autenticação simples usuario = st.text_input("Nome do Usuário") if not usuario: st.warning("Por favor, insira um nome de usuário para continuar.") st.stop() - # Mapeamento de jogos - jogos_dict = jogos.set_index('id')['title'].to_dict() + jogos_dict = facade.jogos.set_index('id')['title'].to_dict() - # Seleção do jogo jogo_id = st.selectbox( "Selecione um jogo para avaliar:", - jogos['id'], + facade.jogos['id'], format_func=lambda x: jogos_dict[x] ) - # Entrada para avaliação nota = st.slider("Nota", min_value=0, max_value=10, value=5) comentario = st.text_area("Comentário") favorito = st.checkbox("Favoritar este jogo") if st.button("Analisar Sentimento"): - sentimento = analyze_sentiment(comentario) + sentimento = facade.analyze_sentiment(comentario) if sentimento == 'Positive': st.success("O sentimento do comentário é positivo!") elif sentimento == 'Negative': @@ -112,9 +147,8 @@ def main(): else: st.info("O sentimento do comentário é neutro.") - # Submissão de avaliação if st.button("Enviar Avaliação"): - sentimento = analyze_sentiment(comentario) + sentimento = facade.analyze_sentiment(comentario) novo_review = { "jogo_id": jogo_id, "usuario": usuario, @@ -123,23 +157,15 @@ def main(): "favorito": favorito, "sentimento": sentimento # Adiciona o sentimento ao review } - reviews = add_review(reviews, novo_review) + facade.add_review(novo_review) st.success("Avaliação enviada com sucesso!") - # Visualização das avaliações - if st.checkbox("Ver avaliações existentes"): - avaliacoes_filtradas = reviews[reviews['jogo_id'] == jogo_id] - if avaliacoes_filtradas.empty: - st.write("Ainda não há avaliações para este jogo.") - else: - # Adiciona a coluna de sentimento, se não existir - if 'sentimento' not in avaliacoes_filtradas.columns: - avaliacoes_filtradas['sentimento'] = avaliacoes_filtradas['comentario'].apply(analyze_sentiment) - save_reviews(reviews) # Salva as mudanças no arquivo - st.write(avaliacoes_filtradas) - + if st.button("Ver Reviews Existentes"): + reviews_with_names = facade.get_reviews_with_game_names() + st.write(reviews_with_names.head()) + if st.button("Mostrar Gráfico com Plotly"): - fig = plot_avg_game_ratings_plotly(reviews, jogos) + fig = facade.plot_avg_ratings() st.plotly_chart(fig) if __name__ == "__main__": diff --git a/requirements.txt b/requirements.txt index fa4e029..af343ff 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,4 +13,5 @@ psycopg2 openai pytest bcrypt -faiss-cpu \ No newline at end of file +faiss-cpu +torch \ No newline at end of file diff --git a/src/data/processed_data_test.csv b/src/data/processed_data_test.csv new file mode 100644 index 0000000..1510540 --- /dev/null +++ b/src/data/processed_data_test.csv @@ -0,0 +1,5 @@ +title,original_price,popular_tags +"Game A","$19.99","['Action', 'Adventure']" +"Game B","$29.99","['RPG', 'Fantasy']" +"Game C","Free","['Puzzle', 'Indie']" +"Game D","$14.99","['Strategy', 'Multiplayer']" \ No newline at end of file diff --git a/src/utils/__pycache__/globals.cpython-311.pyc b/src/utils/__pycache__/globals.cpython-311.pyc index b76f051..d79254d 100644 Binary files a/src/utils/__pycache__/globals.cpython-311.pyc and b/src/utils/__pycache__/globals.cpython-311.pyc differ diff --git a/utils.py b/utils.py index a27201e..13f7ebd 100644 --- a/utils.py +++ b/utils.py @@ -3,7 +3,7 @@ from sqlalchemy.orm import sessionmaker # Configuração da Conexão com o BD -DATABASE_URL = "postgresql://root:wRoNcAkjnwCvGRdxD2OKAeSevhOLwJ5b@dpg-cq6i442ju9rs73e8bleg-a.oregon-postgres.render.com/loginbd_fg6e" +DATABASE_URL = "postgresql://mygamehub:XnDyt3Xa8O66bmE7Jbc3ly6zZ3f4eiGH@dpg-cqlnivdumphs7397s8k0-a.oregon-postgres.render.com/loginbd_6tj3" engine = create_engine(DATABASE_URL) Session = sessionmaker(bind=engine) session = Session() @@ -13,9 +13,7 @@ users = Table('users', metadata, Column('id', Integer, primary_key=True), Column('username', String, unique=True), - Column('password', String), - Column('name', String), - Column('gender', String)) + Column('password', String),) # Tabela profiles profiles = Table('profiles', metadata,