File size: 8,243 Bytes
d368963
 
2d1ddb2
d368963
2d1ddb2
d368963
 
2d1ddb2
0da7a7f
2d1ddb2
0da7a7f
 
d368963
 
 
2d1ddb2
 
d368963
2d1ddb2
0da7a7f
 
2d1ddb2
 
0da7a7f
2d1ddb2
 
 
 
0da7a7f
 
2d1ddb2
 
 
 
 
 
 
 
 
 
 
 
0da7a7f
 
 
 
 
 
 
 
2d1ddb2
 
 
 
0da7a7f
2d1ddb2
 
 
 
d368963
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2d1ddb2
 
 
0da7a7f
d368963
2d1ddb2
 
7ecec38
0da7a7f
2d1ddb2
 
 
 
 
0da7a7f
2d1ddb2
0da7a7f
 
 
 
 
 
 
d368963
 
 
0da7a7f
2d1ddb2
 
d368963
 
 
 
 
 
 
 
 
 
 
 
0da7a7f
d368963
 
 
 
 
 
 
 
2d1ddb2
0da7a7f
2d1ddb2
d368963
 
 
 
 
 
 
 
0da7a7f
d368963
 
dbbe9b1
d368963
 
 
 
 
 
 
 
 
0da7a7f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d368963
0da7a7f
d368963
0da7a7f
 
d368963
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
import streamlit as st
import requests
import re
import streamlit_authenticator as stauth
import os
import yaml
from yaml.loader import SafeLoader
from pathlib import Path
from drive_search import search_file_in_drive
from datetime import datetime
from feedback_manager import FeedbackManager


class ChatbotApp:
    def __init__(self):
        # Configura título, ícone e layout da página
        st.set_page_config(page_title="Sicoob Chatbot 🤖", page_icon="logos/sicoob-ico.ico", layout="wide")
        self.backend_url = "http://localhost:5001/chat"
        self.title = "Sicoob Chatbot"
        self.description = "Este assistente virtual pode te ajudar com informações sobre documentos da Sicoob."
        self.caption = "Confira as atualizações no botão 'Atualizações'."
        self.style_dir = Path("./logos/styles")
        self.load_styles()
        self.feedback_manager = FeedbackManager()
        st.session_state.first = False
        if "theme" not in st.session_state:
            st.session_state.theme = "light"
        self.configyml = os.path.join(os.getcwd(), "./files/config.yaml")
        if "feedback_submitted" not in st.session_state:
            st.session_state.feedback_submitted = set()

    def load_styles(self):
        try:
            self.base_style = (self.style_dir / "base.css").read_text()
            self.light_style = (self.style_dir / "light.css").read_text()
            self.dark_style = (self.style_dir / "dark.css").read_text()
        except FileNotFoundError as e:
            st.error(f"Error loading styles: {e}")
            self.base_style = ""
            self.light_style = ""
            self.dark_style = ""

    def change_style(self):
        with st.sidebar:
            if st.session_state.theme == "light":
                st.logo("./logos/sicoob-logo-horizontal-light.png", icon_image="./logos/sicoob-logo-vertical-sm.png")
            else:
                st.logo("./logos/sicoob-logo-horizontal-dark.png", icon_image="./logos/sicoob-logo-vertical-sm.png")
        st.session_state.theme = "dark" if st.session_state.theme == "light" else "light"

    @st.dialog("📄 Atualizações Sicoob Chatbot", width="small")
    def changelog_user(self):
        with open("./logos/ChangelogUser.md", encoding="utf-8") as f:
            st.markdown(f"{f.read()}", unsafe_allow_html=True)

    def get_current_style(self):
        theme_style = self.dark_style if st.session_state.theme == "dark" else self.light_style
        return f"<style>{self.base_style}{theme_style}</style>"

    def stream_chat(self, user_input):
        """
        Faz a comunicação com o backend e retorna a resposta como streaming de tokens.
        """
        try:
            response = requests.post(
                self.backend_url,
                json={"message": user_input},
                stream=True  # Ativa o streaming
            )
            response.raise_for_status()

            # Gera os tokens conforme chegam no streaming
            for chunk in response.iter_content(chunk_size=512):
                if chunk:
                    yield chunk.decode("utf-8")
        except Exception as e:
            yield f"Erro ao conectar ao servidor: {e}"

    def clear_chat_history(self):
        st.session_state.chat_history = []
        st.toast("Histórico limpo com sucesso!", icon="✅")

    def render_sidebar(self):
        """Siderbar"""
        with st.sidebar:
            # st.button("Light/Dark Mode", on_click=self.change_style)
            if st.session_state.theme == "light":
                st.logo("./logos/sicoob-logo-horizontal-light.png", icon_image="./logos/sicoob-logo-vertical-sm.png")
            else:
                st.logo("./logos/sicoob-logo-horizontal-dark.png", icon_image="./logos/sicoob-logo-vertical-sm.png")
            if st.button("Limpar Histórico", icon=":material/delete:"):
                self.clear_chat_history()
            st.button("Atualizações", icon=":material/info:", on_click=self.changelog_user)
            st.divider()

    def add_link_to_text(self, text):
        """
        Adiciona links ao texto com base no padrão ||texto||.
        """
        return re.sub(r'\|\|(.*?)\|\|', lambda match: f' <br>Fonte: <a href="{search_file_in_drive(match.group(1))}" target="_blank">{match.group(1)}</a>', text)

    def render(self):
        """
        Renderiza a interface do chatbot.
        """
        st.markdown(self.get_current_style(), unsafe_allow_html=True)
        with open(self.configyml) as file:
            config = yaml.load(file, Loader=SafeLoader)

        authenticator = stauth.Authenticate(
            config['credentials'],
            config['cookie']['name'],
            config['cookie']['key'],
            config['cookie']['expiry_days']
        )

        with open('./config.yaml', 'w', encoding='utf-8') as file:
            yaml.dump(config, file, default_flow_style=False)

        authentication_status = authenticator.login(fields={'Form name': 'Autenticação', 'Username': 'Nome de Usuário', 'Password': 'Senha', 'Login': 'Entrar'})

        if st.session_state["authentication_status"]:
            # Renderiza a barra lateral
            self.render_sidebar()

            # Título e descrição
            st.title(self.title)
            st.write(self.description)
            with st.sidebar:
                st.caption(self.caption)
                authenticator.logout('Sair', 'main')
            # Inicializa o histórico na sessão
            if "chat_history" not in st.session_state:
                st.session_state.chat_history = []

            # Renderiza as mensagens do histórico
            for message in st.session_state.chat_history:
                role, text = message.split(":", 1)
                with st.chat_message(role.strip().lower()):
                    st.markdown(text.strip(), unsafe_allow_html=True)

            # Captura o input do usuário
            user_input = st.chat_input(placeholder="Digite sua pergunta...")
            if user_input:
                # Exibe a mensagem do usuário
                with st.chat_message("user"):
                    st.write(user_input)
                st.session_state.chat_history.append(f"user: {user_input}")
                # Placeholder para a resposta do assistente
                with st.chat_message("assistant"):
                    message_placeholder = st.empty()
                    assistant_message = ""
                    try:
                        # Gerando ID único para a mensagem
                        message_id = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
                        print(f"Message ID gerado: {message_id}")
                        # Executa o streaming de tokens enquanto o backend responde
                        for token in self.stream_chat(user_input):
                            assistant_message += token
                            message_placeholder.markdown(assistant_message + "▌", unsafe_allow_html=True)
                    except Exception as e:
                        st.error(f"Erro durante o chat: {str(e)}")
                        print(f"Erro durante o chat: {str(e)}")
                    finally:
                        assistant_message_with_link = self.add_link_to_text(assistant_message)
                        message_placeholder.markdown(assistant_message_with_link, unsafe_allow_html=True)
                        # Feedback
                        self.feedback_manager.render_feedback_buttons(
                            message_id=message_id,
                            user_input=user_input,
                            assistant_response=assistant_message_with_link
                        )
                        # Adicionando histórico Streamlit
                        st.session_state.chat_history.append(f"assistant: {assistant_message_with_link}")
        elif st.session_state["authentication_status"] == False:
            st.error('Nome de Usuário/Senha incorreta.')
        elif st.session_state["authentication_status"] == None:
            st.warning('Por favor entre com seu nome de usuário e senha.')


if __name__ == "__main__":
    chatbot_app = ChatbotApp()
    chatbot_app.render()