souzat19 commited on
Commit
dc224f1
·
verified ·
1 Parent(s): ab9bded

Upload 19 files

Browse files
.env ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ OPENAI_API_KEY=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
2
+ LANGSMITH_API_KEY=
3
+ PINECONE_API_KEY=pcsk_6MNEad_PUMZQykNTtjE6qmCYJicbJMFkqrRHa5UgtGZKNEjwrEjqcQ5uAmqDGb7fH8ThQu
4
+
5
+
6
+ SUPABASE_URL=https://pjfwwtllcdoapscfehoc.supabase.co
7
+ SUPABASE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBqZnd3dGxsY2RvYXBzY2ZlaG9jIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzkxMjEzMTUsImV4cCI6MjA1NDY5NzMxNX0.b1PsWNXHKusgVWHMzyEvTV-XuddUu2BEmuMmJ9BMPNE
8
+ SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBqZnd3dGxsY2RvYXBzY2ZlaG9jIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTczOTEyMTMxNSwiZXhwIjoyMDU0Njk3MzE1fQ.g1ie61hA73q1wjGdaqCZJzfkMYl8450SWI58umqjmhs
main.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from oauth.login import main as login_main
3
+ from oauth.cadastro import main as cadastro_main
4
+ from sollai.chat import main as chat_main
5
+
6
+ # Configuração inicial do Streamlit
7
+ st.set_page_config(
8
+ page_title="Sollai Assistant",
9
+ page_icon="🤖",
10
+ layout="wide"
11
+ )
12
+
13
+ # Inicializar variáveis de sessão
14
+ if 'is_authenticated' not in st.session_state:
15
+ st.session_state['is_authenticated'] = False
16
+
17
+ if 'user' not in st.session_state:
18
+ st.session_state['user'] = None
19
+
20
+ if 'assistant' not in st.session_state:
21
+ st.session_state['assistant'] = None
22
+
23
+ # Função principal
24
+ def main():
25
+ if not st.session_state['is_authenticated']:
26
+ login_main()
27
+ else:
28
+ user = st.session_state['user']
29
+ assistant = st.session_state.get('assistant')
30
+
31
+ if not assistant:
32
+ st.warning("⚠️ Você não possui um assistente configurado.")
33
+ if st.button("Sair"):
34
+ for key in st.session_state.keys():
35
+ del st.session_state[key]
36
+ st.rerun()
37
+ else:
38
+ chat_main()
39
+
40
+ # Execução principal
41
+ if __name__ == "__main__":
42
+ main()
oauth/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # Arquivo vazio para marcar o diretório como um pacote Python
oauth/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (145 Bytes). View file
 
oauth/__pycache__/cadastro.cpython-311.pyc ADDED
Binary file (13.3 kB). View file
 
oauth/__pycache__/login.cpython-311.pyc ADDED
Binary file (2.48 kB). View file
 
oauth/__pycache__/loguin.cpython-311.pyc ADDED
Binary file (3.96 kB). View file
 
oauth/__pycache__/supabase_conex.cpython-311.pyc ADDED
Binary file (4.21 kB). View file
 
oauth/__pycache__/user_manager.cpython-311.pyc ADDED
Binary file (5.01 kB). View file
 
oauth/cadastro.py ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from pathlib import Path
3
+
4
+ # Adicionar o diretório raiz ao PYTHONPATH
5
+ root_dir = Path(__file__).parent.parent
6
+ sys.path.append(str(root_dir))
7
+
8
+ import streamlit as st
9
+ import pandas as pd
10
+ from oauth.user_manager import UserManager
11
+ from oauth.supabase_conex import SupabaseConnection
12
+ from datetime import datetime
13
+ from sollai.assistentes import CriaAssistente, AssistenteConfig
14
+
15
+ def create_admin_user(email: str, password: str, nome: str, is_admin: bool = False):
16
+ """Cria um novo usuário diretamente"""
17
+ try:
18
+ # Usar admin client para ter permissões totais
19
+ admin_client = SupabaseConnection().get_admin_client()
20
+
21
+ # 1. Criar usuário na auth
22
+ auth_response = admin_client.auth.admin.create_user({
23
+ "email": email,
24
+ "password": password,
25
+ "email_confirm": True,
26
+ "user_metadata": {
27
+ "nome": nome,
28
+ "is_admin": is_admin
29
+ }
30
+ })
31
+
32
+ # Debug detalhado
33
+ print("\n=== Debug Auth Response ===")
34
+ print(f"Tipo: {type(auth_response)}")
35
+ print(f"Dir: {dir(auth_response)}")
36
+ print(f"Raw: {auth_response}")
37
+
38
+ if not auth_response:
39
+ raise Exception("Falha na criação da autenticação")
40
+
41
+ # Tentar diferentes formas de acessar o ID
42
+ user_id = None
43
+ if hasattr(auth_response, 'user'):
44
+ user_id = auth_response.user.id
45
+ elif hasattr(auth_response, 'id'):
46
+ user_id = auth_response.id
47
+ else:
48
+ # Último recurso: converter para dict
49
+ auth_dict = auth_response.model_dump() if hasattr(auth_response, 'model_dump') else vars(auth_response)
50
+ print(f"Auth Dict: {auth_dict}")
51
+ user_id = auth_dict.get('id') or auth_dict.get('user', {}).get('id')
52
+
53
+ if not user_id:
54
+ raise Exception("Não foi possível obter o ID do usuário")
55
+
56
+ # 2. Inserir dados na tabela public.users
57
+ user_data = {
58
+ "id": user_id,
59
+ "email": email,
60
+ "nome": nome,
61
+ "is_admin": is_admin,
62
+ "created_at": datetime.utcnow().isoformat(),
63
+ "updated_at": datetime.utcnow().isoformat(),
64
+ "is_active": True
65
+ }
66
+
67
+ response = admin_client.from_('users')\
68
+ .insert(user_data)\
69
+ .execute()
70
+
71
+ return response.data[0]
72
+
73
+ except Exception as e:
74
+ print(f"\n=== Erro Detalhado ===")
75
+ print(f"Tipo do erro: {type(e)}")
76
+ print(f"Mensagem: {str(e)}")
77
+ import traceback
78
+ print(f"Traceback:\n{traceback.format_exc()}")
79
+ raise Exception(f"Erro ao criar usuário: {str(e)}")
80
+
81
+ def delete_user(user_id: str):
82
+ """Deleta um usuário do sistema"""
83
+ try:
84
+ admin_client = SupabaseConnection().get_admin_client()
85
+
86
+ # 1. Deletar da tabela users primeiro
87
+ response = admin_client.from_('users')\
88
+ .delete()\
89
+ .eq('id', user_id)\
90
+ .execute()
91
+
92
+ # 2. Deletar da autenticação
93
+ auth_response = admin_client.auth.admin.delete_user(user_id)
94
+
95
+ return True
96
+
97
+ except Exception as e:
98
+ print(f"\n=== Erro ao deletar usuário ===")
99
+ print(f"Tipo do erro: {type(e)}")
100
+ print(f"Mensagem: {str(e)}")
101
+ import traceback
102
+ print(f"Traceback:\n{traceback.format_exc()}")
103
+ raise Exception(f"Erro ao deletar usuário: {str(e)}")
104
+
105
+ def create_user(email: str, password: str, nome: str):
106
+ """Cria um novo usuário e seu assistente"""
107
+ try:
108
+ # 1. Criar usuário no Supabase
109
+ user = create_admin_user(
110
+ email=email,
111
+ password=password,
112
+ nome=nome,
113
+ is_admin=False
114
+ )
115
+
116
+ # 2. Criar assistente no Pinecone
117
+ safe_name = "".join(c for c in nome if c.isalnum() or c.isspace()).strip()
118
+ safe_name = safe_name.lower().replace(" ", "-")
119
+ assistant_name = f"assistant-{safe_name}-{user['id'][:8]}"
120
+
121
+ config = AssistenteConfig(
122
+ nome=assistant_name,
123
+ instrucoes="Você é uma assistente amigável que responde perguntas do usuário. Responda sempre em português do Brasil."
124
+ )
125
+
126
+ try:
127
+ criador = CriaAssistente()
128
+ assistente = criador.criar(config)
129
+
130
+ # 3. Atualizar usuário com referências do assistente
131
+ admin_client = SupabaseConnection().get_admin_client()
132
+ response = admin_client.from_('users')\
133
+ .update({
134
+ "assistant_name": assistant_name # Salvando apenas o nome
135
+ })\
136
+ .eq('id', user['id'])\
137
+ .execute()
138
+
139
+ st.success(f"✅ Usuário {nome} criado com sucesso!")
140
+ st.success(f"✅ Assistente '{assistant_name}' criado com sucesso!")
141
+
142
+ return response.data[0]
143
+
144
+ except Exception as e:
145
+ st.error(f"❌ Erro ao criar assistente: {str(e)}")
146
+ delete_user(user['id'])
147
+ raise
148
+
149
+ except Exception as e:
150
+ st.error(f"❌ Erro ao criar usuário: {str(e)}")
151
+ raise
152
+
153
+ def main():
154
+ st.set_page_config(page_title="Cadastro de Usuários", page_icon="👤")
155
+
156
+ st.title("👤 Cadastro de Usuários")
157
+ st.markdown("### Área Administrativa")
158
+
159
+ # Formulário de cadastro
160
+ with st.form("admin_cadastro_form"):
161
+ nome = st.text_input("Nome completo", help="Digite o nome completo do usuário")
162
+ email = st.text_input("Email", help="Digite o email do usuário")
163
+ password = st.text_input("Senha", type="password", help="Mínimo 6 caracteres")
164
+ confirm_password = st.text_input("Confirmar senha", type="password")
165
+ is_admin = st.checkbox("👑 Usuário Administrador")
166
+
167
+ if st.form_submit_button("Criar Usuário"):
168
+ try:
169
+ # Validações
170
+ if not all([nome, email, password, confirm_password]):
171
+ st.error("❌ Todos os campos são obrigatórios!")
172
+ st.stop()
173
+
174
+ if password != confirm_password:
175
+ st.error("❌ As senhas não coincidem!")
176
+ st.stop()
177
+
178
+ if len(password) < 6:
179
+ st.error("❌ A senha deve ter no mínimo 6 caracteres!")
180
+ st.stop()
181
+
182
+ # Criar usuário
183
+ user = create_user(
184
+ email=email,
185
+ password=password,
186
+ nome=nome
187
+ )
188
+
189
+ st.json(user) # Mostra os dados do usuário criado
190
+
191
+ except Exception as e:
192
+ st.error(f"❌ Erro ao criar usuário: {str(e)}")
193
+
194
+ # Lista de usuários existentes
195
+ st.markdown("---")
196
+ st.subheader("📋 Usuários Cadastrados")
197
+
198
+ try:
199
+ admin_client = SupabaseConnection().get_admin_client()
200
+ response = admin_client.from_('users').select('*').execute()
201
+
202
+ if response.data:
203
+ # Criar DataFrame com os dados
204
+ df = pd.DataFrame(response.data)
205
+
206
+ # Mostrar tabela
207
+ st.dataframe(
208
+ df,
209
+ column_config={
210
+ "nome": "Nome",
211
+ "email": "Email",
212
+ "is_admin": "Admin",
213
+ "is_active": "Ativo",
214
+ "created_at": "Criado em"
215
+ },
216
+ hide_index=True
217
+ )
218
+
219
+ # Seleção de usuário para deletar
220
+ usuarios = [(f"{u['nome']} ({u['email']})", u['id']) for u in response.data]
221
+ selected_user = st.selectbox(
222
+ "Selecione um usuário para deletar:",
223
+ options=usuarios,
224
+ format_func=lambda x: x[0]
225
+ )
226
+
227
+ # Botão de deletar com confirmação
228
+ if st.button("🗑️ Deletar Usuário"):
229
+ if st.session_state.get('is_admin'): # Verifica se é admin
230
+ user_id = selected_user[1]
231
+
232
+ # Confirmação adicional
233
+ if st.warning(f"⚠️ Tem certeza que deseja deletar este usuário? Esta ação não pode ser desfeita."):
234
+ try:
235
+ delete_user(user_id)
236
+ st.success("✅ Usuário deletado com sucesso!")
237
+ st.rerun() # Atualiza a página
238
+ except Exception as e:
239
+ st.error(f"❌ Erro ao deletar usuário: {str(e)}")
240
+ else:
241
+ st.error("❌ Apenas administradores podem deletar usuários!")
242
+
243
+ else:
244
+ st.info("Nenhum usuário cadastrado.")
245
+
246
+ except Exception as e:
247
+ st.error(f"❌ Erro ao listar usuários: {str(e)}")
248
+
249
+ if __name__ == "__main__":
250
+ main()
oauth/login.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from oauth.user_manager import UserManager
3
+ from sollai.assistentes import Assistente
4
+
5
+ def main():
6
+ st.title("🔐 Login")
7
+
8
+ with st.form("login_form"):
9
+ email = st.text_input("Email")
10
+ password = st.text_input("Senha", type="password")
11
+
12
+ if st.form_submit_button("Entrar"):
13
+ try:
14
+ user_manager = UserManager()
15
+ user = user_manager.authenticate_user(email, password)
16
+
17
+ if user:
18
+ # Guardar dados do usuário
19
+ st.session_state['user'] = user
20
+ st.session_state['is_authenticated'] = True
21
+
22
+ # Carregar assistente usando o nome
23
+ assistant_name = user.get('assistant_name')
24
+ if assistant_name:
25
+ try:
26
+ assistente = Assistente(assistant_name)
27
+ st.session_state['assistant'] = assistente
28
+ st.success("✅ Login realizado com sucesso!")
29
+ st.rerun()
30
+ except Exception as e:
31
+ st.error(f"❌ Erro ao carregar assistente: {str(e)}")
32
+ else:
33
+ st.error("❌ Você não possui um assistente configurado.")
34
+ else:
35
+ st.error("❌ Email ou senha inválidos!")
36
+
37
+ except Exception as e:
38
+ st.error(f"❌ Erro ao fazer login: {str(e)}")
oauth/supabase_conex.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from supabase import create_client, Client
4
+ from typing import Optional
5
+
6
+ load_dotenv()
7
+
8
+ class SupabaseConnection:
9
+ """Gerenciador de conexão com Supabase"""
10
+
11
+ _instance: Optional['SupabaseConnection'] = None
12
+
13
+ def __new__(cls):
14
+ """Implementa Singleton para evitar múltiplas conexões"""
15
+ if cls._instance is None:
16
+ cls._instance = super().__new__(cls)
17
+ return cls._instance
18
+
19
+ def __init__(self):
20
+ """Inicializa a conexão com Supabase"""
21
+ if not hasattr(self, 'client'):
22
+ self.supabase_url = os.getenv("SUPABASE_URL")
23
+ self.supabase_key = os.getenv("SUPABASE_KEY")
24
+ self.service_role_key = os.getenv("SUPABASE_SERVICE_ROLE_KEY")
25
+
26
+ if not all([self.supabase_url, self.supabase_key, self.service_role_key]):
27
+ raise ValueError(
28
+ "SUPABASE_URL, SUPABASE_KEY e SUPABASE_SERVICE_ROLE_KEY devem estar definidos no arquivo .env"
29
+ )
30
+
31
+ # Cliente normal para operações regulares
32
+ self.client = create_client(
33
+ supabase_url=self.supabase_url,
34
+ supabase_key=self.supabase_key
35
+ )
36
+
37
+ # Cliente admin para operações administrativas
38
+ self.admin_client = create_client(
39
+ supabase_url=self.supabase_url,
40
+ supabase_key=self.service_role_key
41
+ )
42
+
43
+ def get_client(self) -> Client:
44
+ """Retorna o cliente Supabase regular"""
45
+ return self.client
46
+
47
+ def get_admin_client(self) -> Client:
48
+ """Retorna o cliente Supabase com privilégios administrativos"""
49
+ return self.admin_client
50
+
51
+ def test_connection(self) -> bool:
52
+ """Testa a conexão com Supabase"""
53
+ try:
54
+ # Tenta fazer uma query simples
55
+ print("🔍 Testando conexão com Supabase...")
56
+ response = self.client.table('users').select("*").limit(1).execute()
57
+ print("✅ Conexão Supabase bem sucedida!")
58
+ return True
59
+
60
+ except Exception as e:
61
+ print(f"❌ Erro na conexão Supabase: {str(e)}")
62
+ return False
63
+
64
+ def get_table(self, table_name: str):
65
+ """
66
+ Retorna uma referência para uma tabela específica
67
+
68
+ Args:
69
+ table_name: Nome da tabela
70
+
71
+ Returns:
72
+ PostgrestFilterBuilder: Referência para a tabela
73
+ """
74
+ return self.client.table(table_name)
oauth/user_manager.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime
2
+ from typing import Dict, Optional, List
3
+ from oauth.supabase_conex import SupabaseConnection
4
+
5
+ class UserManager:
6
+ """Gerenciador de usuários usando Supabase"""
7
+
8
+ def __init__(self):
9
+ self.supabase = SupabaseConnection().get_client()
10
+ self.users_table = 'users'
11
+
12
+ def authenticate_user(self, email: str, password: str) -> Optional[Dict]:
13
+ """Autentica um usuário"""
14
+ try:
15
+ # Autenticar usuário
16
+ auth_response = self.supabase.auth.sign_in_with_password({
17
+ "email": email,
18
+ "password": password
19
+ })
20
+
21
+ if auth_response:
22
+ # Usar admin_client para evitar recursão nas políticas
23
+ admin_client = SupabaseConnection().get_admin_client()
24
+
25
+ # Buscar dados completos do usuário
26
+ user_response = admin_client.from_('users')\
27
+ .select('*')\
28
+ .eq('email', email)\
29
+ .single()\
30
+ .execute()
31
+
32
+ if user_response.data:
33
+ print(f"=== Login realizado com sucesso ===")
34
+ print(f"Usuário: {user_response.data['nome']} ({user_response.data['email']})")
35
+
36
+ # Verificar se tem assistente
37
+ if not user_response.data.get('assistant_name'):
38
+ print("✗ Usuário não possui assistente configurado")
39
+
40
+ return user_response.data
41
+
42
+ return None
43
+
44
+ except Exception as e:
45
+ print(f"Erro na autenticação: {str(e)}")
46
+ raise
47
+
48
+ def get_user(self, user_id: str) -> Optional[Dict]:
49
+ """Busca um usuário pelo ID"""
50
+ try:
51
+ # Usar admin_client para bypass nas políticas
52
+ admin_client = SupabaseConnection().get_admin_client()
53
+
54
+ response = admin_client.from_(self.users_table)\
55
+ .select('*')\
56
+ .eq('id', user_id)\
57
+ .execute()
58
+
59
+ return response.data[0] if response.data else None
60
+
61
+ except Exception as e:
62
+ raise Exception(f"Erro ao buscar usuário: {str(e)}")
63
+
64
+ def get_current_user(self) -> Optional[Dict]:
65
+ """Retorna o usuário atual"""
66
+ try:
67
+ session = self.supabase.auth.get_session()
68
+ if not session:
69
+ return None
70
+
71
+ return self.get_user(session.user.id)
72
+ except Exception as e:
73
+ print(f"Erro ao buscar usuário atual: {str(e)}")
74
+ return None
75
+
76
+ def is_admin(self, user_id: str) -> bool:
77
+ """Verifica se um usuário é admin"""
78
+ try:
79
+ user = self.get_user(user_id)
80
+ return user.get('is_admin', False) if user else False
81
+ except Exception as e:
82
+ print(f"Erro ao verificar admin: {str(e)}")
83
+ return False
84
+
85
+ # Exemplo de uso
86
+ if __name__ == "__main__":
87
+ user_manager = UserManager()
88
+
89
+
sollai/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # Este arquivo pode ficar vazio
sollai/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (146 Bytes). View file
 
sollai/__pycache__/assistentes.cpython-311.pyc ADDED
Binary file (8.47 kB). View file
 
sollai/__pycache__/chat.cpython-311.pyc ADDED
Binary file (17 kB). View file
 
sollai/assistentes.py ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from pinecone import Pinecone
4
+ from pinecone_plugins.assistant.models.chat import Message
5
+ from rich.console import Console
6
+ from rich.panel import Panel
7
+ from dataclasses import dataclass
8
+ from typing import List, Optional, Dict, Generator
9
+ from pathlib import Path
10
+ from datetime import datetime
11
+
12
+
13
+ load_dotenv()
14
+
15
+ @dataclass
16
+ class AssistenteConfig:
17
+ """Configuração do assistente"""
18
+ nome: str
19
+ instrucoes: str
20
+
21
+ class CriaAssistente:
22
+ """Classe responsável por criar e configurar assistentes"""
23
+
24
+ def __init__(self):
25
+ self.pc = Pinecone(api_key=os.getenv('PINECONE_API_KEY'))
26
+ self.console = Console()
27
+
28
+ def criar(self, config: AssistenteConfig) -> Dict:
29
+ """Cria um novo assistente com as configurações especificadas"""
30
+ try:
31
+ assistant = self.pc.assistant.create_assistant(
32
+ assistant_name=config.nome,
33
+ instructions=config.instrucoes,
34
+ timeout=30
35
+ )
36
+
37
+ self.console.print(
38
+ f"[green]✓ Assistente '{config.nome}' criado com sucesso![/green]"
39
+ )
40
+ return assistant
41
+
42
+ except Exception as e:
43
+ self.console.print(f"[red]✗ Erro ao criar assistente: {str(e)}[/red]")
44
+ raise
45
+
46
+ class Assistente:
47
+ """Classe para interagir com assistentes existentes"""
48
+
49
+ def __init__(self, assistant_name: str):
50
+ self.pc = Pinecone(api_key=os.getenv('PINECONE_API_KEY'))
51
+ self.console = Console()
52
+ self.nome = assistant_name
53
+ self.assistente = self.pc.assistant.Assistant(assistant_name=assistant_name)
54
+
55
+ def get_context(self, query: str) -> List[Dict]:
56
+ """Busca snippets relevantes para uma query"""
57
+ try:
58
+ response = self.assistente.context(query=query)
59
+ if response is None:
60
+ return []
61
+
62
+ # Verificar se response é um dicionário
63
+ if isinstance(response, dict):
64
+ return response.get("snippets", [])
65
+ # Se for uma lista, retornar diretamente
66
+ elif isinstance(response, list):
67
+ return response
68
+ return []
69
+
70
+ except Exception as e:
71
+ self.console.print(f"[red]✗ Erro ao buscar contexto: {str(e)}[/red]")
72
+ return []
73
+
74
+ def fazer_pergunta(self, pergunta: str) -> Dict:
75
+ """Faz uma pergunta ao assistente com contexto"""
76
+ try:
77
+ # Primeiro buscar contexto relevante
78
+ snippets = self.get_context(pergunta)
79
+
80
+ # Fazer a pergunta
81
+ msg = Message(content=pergunta)
82
+ response = self.assistente.chat(messages=[msg])
83
+
84
+ # Extrair conteúdo e citações
85
+ content = response["message"]["content"]
86
+ citations = response.get("citations", [])
87
+
88
+ return {
89
+ "content": content,
90
+ "citations": citations,
91
+ "snippets": snippets # Incluir snippets na resposta
92
+ }
93
+
94
+ except Exception as e:
95
+ self.console.print(f"[red]✗ Erro ao fazer pergunta: {str(e)}[/red]")
96
+ raise
97
+
98
+ def _process_stream(self, chunks: Generator) -> Dict:
99
+ """Processa o stream de resposta"""
100
+ content = ""
101
+ citations = []
102
+
103
+ for chunk in chunks:
104
+ if not chunk:
105
+ continue
106
+
107
+ # Processar diferentes tipos de chunks
108
+ if isinstance(chunk, dict):
109
+ chunk_type = chunk.get("type")
110
+
111
+ if chunk_type == "message_start":
112
+ continue
113
+ elif chunk_type == "content_chunk":
114
+ if "delta" in chunk and "content" in chunk["delta"]:
115
+ content += chunk["delta"]["content"]
116
+ elif chunk_type == "citation":
117
+ citations.append(chunk.get("citation", {}))
118
+ elif chunk_type == "message_end":
119
+ if chunk.get("finish_reason") == "stop":
120
+ break
121
+ else:
122
+ content += str(chunk)
123
+
124
+ return {
125
+ "content": content,
126
+ "citations": citations
127
+ }
128
+
129
+ def upload_file(self, file_path: str, timeout: Optional[int] = None) -> Dict:
130
+ """Faz upload de um arquivo para o assistente"""
131
+ try:
132
+ # Adicionar metadados com nome original do arquivo
133
+ original_name = Path(file_path).name
134
+ metadata = {
135
+ "original_name": original_name,
136
+ "uploaded_on": datetime.now().isoformat()
137
+ }
138
+
139
+ response = self.assistente.upload_file(
140
+ file_path=file_path,
141
+ timeout=timeout,
142
+ metadata=metadata
143
+ )
144
+ return response
145
+ except Exception as e:
146
+ self.console.print(f"[red]✗ Erro ao fazer upload: {str(e)}[/red]")
147
+ raise
148
+
149
+ def get_files(self) -> List[Dict]:
150
+ """Retorna a lista de arquivos carregados no assistente"""
151
+ try:
152
+ response = self.assistente.list_files()
153
+ if isinstance(response, list):
154
+ return response
155
+ elif isinstance(response, dict):
156
+ return response.get("files", [])
157
+ return []
158
+ except Exception as e:
159
+ self.console.print(f"[red]✗ Erro ao listar arquivos: {str(e)}[/red]")
160
+ return []
161
+
sollai/chat.py ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import os
3
+ from pathlib import Path
4
+ from typing import List, Dict
5
+ import tempfile
6
+ from pinecone import Pinecone
7
+ from datetime import datetime
8
+
9
+ def save_uploaded_file(file) -> str:
10
+ """Salva um arquivo temporariamente e retorna o caminho"""
11
+ with tempfile.NamedTemporaryFile(delete=False, suffix=Path(file.name).suffix) as tmp:
12
+ tmp.write(file.getvalue())
13
+ return tmp.name
14
+
15
+ def format_citation(citation: dict, index: int) -> str:
16
+ """Formata uma citação com estilo de referência acadêmica"""
17
+ try:
18
+ references = citation.get("references", [])
19
+ position = citation.get("position", 0)
20
+ citation_text = []
21
+
22
+ for ref in references:
23
+ if not ref:
24
+ continue
25
+
26
+ file_data = ref.get("file", {}) or {}
27
+ metadata = file_data.get("metadata", {}) or {}
28
+
29
+ # Usar nome original do arquivo se disponível
30
+ file_name = (metadata.get("original_name")
31
+ or file_data.get("name", "Documento"))
32
+ pages = ref.get("pages", [])
33
+
34
+ # Criar link de referência
35
+ citation_text.append(f"[{index}] {file_name}")
36
+ if pages:
37
+ citation_text.append(f"Page{'s' if len(pages) > 1 else ''} {', '.join(map(str, pages))}")
38
+
39
+ return " | ".join(citation_text)
40
+ except Exception as e:
41
+ st.error(f"Erro ao formatar citação: {str(e)}")
42
+ return f"[{index}] Referência"
43
+
44
+ def show_references(citations: List[dict]):
45
+ """Mostra a seção de referências no estilo acadêmico com trechos relevantes"""
46
+ if not citations:
47
+ return
48
+
49
+ st.markdown("---")
50
+ st.markdown("### References:")
51
+
52
+ for i, citation in enumerate(citations, 1):
53
+ try:
54
+ references = citation.get("references", [])
55
+ position = citation.get("position", 0)
56
+
57
+ for ref in references:
58
+ if not ref:
59
+ continue
60
+
61
+ file_data = ref.get("file", {}) or {}
62
+ metadata = file_data.get("metadata", {}) or {}
63
+
64
+ # Usar nome original do arquivo
65
+ file_name = (metadata.get("original_name")
66
+ or file_data.get("name", "Documento"))
67
+ pages = ref.get("pages", [])
68
+
69
+ with st.container():
70
+ # Cabeçalho da referência
71
+ col1, col2 = st.columns([1, 4])
72
+ with col1:
73
+ st.markdown(f"**[{i}]**")
74
+ with col2:
75
+ st.markdown(f"**{file_name}**")
76
+ if pages:
77
+ st.caption(f"Page {', '.join(map(str, pages))}")
78
+
79
+ # Trecho relevante
80
+ if position is not None and citation.get("text"):
81
+ with st.expander("Ver trecho citado"):
82
+ st.caption("Trecho relevante:")
83
+ st.markdown("> " + citation["text"].strip())
84
+
85
+ st.divider()
86
+ except Exception as e:
87
+ st.error(f"Erro ao mostrar referência {i}: {str(e)}")
88
+
89
+ def format_snippet(snippet: dict) -> str:
90
+ """Formata um snippet de contexto"""
91
+ try:
92
+ content = snippet.get("content", "").strip()
93
+ score = snippet.get("score", 0)
94
+ reference = snippet.get("reference", {}) or {}
95
+ file_data = reference.get("file", {}) or {}
96
+ metadata = file_data.get("metadata", {}) or {}
97
+
98
+ # Pegar nome original do arquivo se disponível
99
+ file_name = (metadata.get("original_name")
100
+ or file_data.get("name", "Documento"))
101
+
102
+ # Pegar páginas
103
+ pages = reference.get("pages", [])
104
+ page_info = f"Page {', '.join(map(str, pages))}" if pages else ""
105
+
106
+ # Formatar texto
107
+ return f"""
108
+ 📄 **Fonte:** {file_name} {page_info}
109
+ 📊 **Relevância:** {score:.2%}
110
+
111
+ > {content}
112
+ """
113
+ except Exception as e:
114
+ return f"❌ Erro ao formatar snippet: {str(e)}"
115
+
116
+ def show_context(snippets: List[dict]):
117
+ """Mostra snippets de contexto relevantes"""
118
+ if not snippets:
119
+ return
120
+
121
+ with st.expander("🔍 **Contexto Relevante**"):
122
+ try:
123
+ st.info("ℹ️ Trechos relevantes encontrados nos documentos:")
124
+
125
+ for snippet in snippets:
126
+ if snippet: # Verificar se o snippet é válido
127
+ st.markdown(format_snippet(snippet))
128
+ st.divider()
129
+ except Exception as e:
130
+ st.error(f"❌ Erro ao mostrar contexto: {str(e)}")
131
+
132
+ def main():
133
+ st.title("💬 Chat")
134
+
135
+ if 'assistant' not in st.session_state:
136
+ st.error("❌ Nenhum assistente configurado!")
137
+ return
138
+
139
+ # Área de documentos
140
+ with st.sidebar:
141
+ st.subheader("📁 Documentos")
142
+
143
+ # Mostrar contagem de documentos
144
+ try:
145
+ files = st.session_state['assistant'].get_files()
146
+ total_files = len(files)
147
+
148
+ # Mostrar status dos documentos
149
+ if total_files > 0:
150
+ st.info(f"📊 {total_files} documento{'s' if total_files > 1 else ''} carregado{'s' if total_files > 1 else ''}")
151
+
152
+ # Mostrar detalhes em um expander
153
+ with st.expander("Ver detalhes dos documentos"):
154
+ for file in files:
155
+ with st.container():
156
+ st.text(f"📄 {file['name'] if 'name' in file else 'Documento'}")
157
+ if 'size' in file:
158
+ st.caption(f"📦 {file['size']/1024:.1f} KB")
159
+ if 'created_on' in file:
160
+ st.caption(f"📅 {file['created_on']}")
161
+ st.divider()
162
+ else:
163
+ st.warning("⚠️ Nenhum documento carregado")
164
+
165
+ except Exception as e:
166
+ st.error(f"❌ Erro ao carregar documentos: {str(e)}")
167
+ st.warning("⚠️ Nenhum documento carregado")
168
+
169
+ st.info("ℹ️ Carregue documentos para permitir que o assistente os utilize nas respostas.")
170
+
171
+ # Upload de arquivos
172
+ uploaded_files = st.file_uploader(
173
+ "Carregar documentos",
174
+ accept_multiple_files=True,
175
+ type=['pdf', 'txt', 'doc', 'docx']
176
+ )
177
+
178
+ if uploaded_files:
179
+ if st.button("📤 Enviar Documentos"):
180
+ with st.spinner("Carregando documentos..."):
181
+ try:
182
+ for uploaded_file in uploaded_files:
183
+ with st.status(f"Processando {uploaded_file.name}..."):
184
+ temp_path = save_uploaded_file(uploaded_file)
185
+ try:
186
+ response = st.session_state['assistant'].upload_file(temp_path)
187
+ st.success("✅ Arquivo processado!")
188
+ finally:
189
+ try:
190
+ os.unlink(temp_path)
191
+ except:
192
+ pass
193
+
194
+ st.success("✅ Todos os documentos foram carregados!")
195
+ st.rerun()
196
+
197
+ except Exception as e:
198
+ st.error(f"❌ Erro: {str(e)}")
199
+
200
+ # Interface do chat
201
+ if "messages" not in st.session_state:
202
+ st.session_state.messages = []
203
+
204
+ # Mostrar mensagens anteriores
205
+ for message in st.session_state.messages:
206
+ with st.chat_message(message["role"]):
207
+ st.markdown(message["content"])
208
+
209
+ # Mostrar citações se existirem
210
+ if message.get("citations"):
211
+ with st.expander("📚 **Fontes Consultadas**"):
212
+ st.info("ℹ️ O assistente baseou sua resposta nos seguintes documentos:")
213
+ for i, citation in enumerate(message["citations"], 1):
214
+ st.markdown(format_citation(citation, i))
215
+
216
+ # Input do usuário
217
+ if prompt := st.chat_input("Digite sua mensagem..."):
218
+ st.session_state.messages.append({"role": "user", "content": prompt})
219
+ with st.chat_message("user"):
220
+ st.markdown(prompt)
221
+
222
+ with st.chat_message("assistant"):
223
+ try:
224
+ response = st.session_state['assistant'].fazer_pergunta(prompt)
225
+
226
+ # Mostrar contexto relevante primeiro
227
+ if response.get("snippets"):
228
+ show_context(response["snippets"])
229
+
230
+ # Mostrar resposta com citações inline
231
+ content = response["content"]
232
+ citations = response.get("citations", [])
233
+
234
+ # Processar citações e extrair trechos
235
+ processed_citations = []
236
+ for citation in citations:
237
+ # Extrair o trecho do texto original
238
+ position = citation.get("position", 0)
239
+ text_before = content[max(0, position-100):position]
240
+ text_after = content[position:position+100]
241
+ citation["text"] = f"...{text_before}**{text_after}**..."
242
+ processed_citations.append(citation)
243
+
244
+ # Adicionar números de referência ao conteúdo
245
+ for i, citation in enumerate(processed_citations, 1):
246
+ position = citation.get("position", 0)
247
+ content = content[:position] + f" [{i}]" + content[position:]
248
+
249
+ # Mostrar conteúdo
250
+ st.markdown(content)
251
+
252
+ # Mostrar seção de referências
253
+ if processed_citations:
254
+ show_references(processed_citations)
255
+
256
+ # Salvar mensagem
257
+ st.session_state.messages.append({
258
+ "role": "assistant",
259
+ "content": content,
260
+ "citations": processed_citations
261
+ })
262
+
263
+ except Exception as e:
264
+ st.error(f"❌ Erro: {str(e)}")
265
+
266
+ if __name__ == "__main__":
267
+ main()