HenriqueBraz commited on
Commit
02d376a
·
verified ·
1 Parent(s): 0316581

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +832 -0
app.py ADDED
@@ -0,0 +1,832 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import streamlit_authenticator as stauth
3
+ import sqlite3
4
+ import os
5
+ import jwt
6
+ from datetime import datetime, timedelta
7
+ from transformers import pipeline, AutoModelForCausalLM, AutoTokenizer
8
+ import torch
9
+ from PIL import Image
10
+ import io
11
+ import librosa
12
+ import numpy as np
13
+ import logging
14
+ import tempfile
15
+ from streamlit.runtime.uploaded_file_manager import UploadedFile
16
+ from diffusers import StableDiffusionPipeline
17
+ import sentry_sdk
18
+ import streamlit.components.v1 as components
19
+ import mimetypes
20
+
21
+ mimetypes.add_type('application/javascript', '.js')
22
+ mimetypes.add_type('text/css', '.css')
23
+
24
+ # Configurar página
25
+ st.set_page_config(
26
+ page_title="Aplicação de IA Multi-Modal",
27
+ page_icon="🤖",
28
+ layout="wide"
29
+ )
30
+
31
+ # Configurar logging e Sentry
32
+ sentry_sdk.init(os.getenv('SENTRY_DSN', ''), traces_sample_rate=1.0)
33
+ logging.basicConfig(
34
+ filename='private/app_errors.log',
35
+ level=logging.ERROR,
36
+ format='%(asctime)s - %(levelname)s - %(message)s'
37
+ )
38
+
39
+ # Configurar banco de dados
40
+ def init_db():
41
+ """Inicializa o banco de dados SQLite com tabela de usuários"""
42
+ db_path = os.getenv('DB_PATH', 'private/users.db')
43
+ conn = sqlite3.connect(db_path)
44
+ cursor = conn.cursor()
45
+
46
+ cursor.execute('''
47
+ CREATE TABLE IF NOT EXISTS users (
48
+ username TEXT PRIMARY KEY,
49
+ name TEXT NOT NULL,
50
+ password TEXT NOT NULL,
51
+ role TEXT NOT NULL DEFAULT 'user'
52
+ )
53
+ ''')
54
+
55
+ # Adicionar admin padrão (remover em produção)
56
+ try:
57
+ hashed_password = stauth.Hasher(['admin123']).generate()[0]
58
+ cursor.execute('''
59
+ INSERT OR IGNORE INTO users (username, name, password, role)
60
+ VALUES (?, ?, ?, ?)
61
+ ''', ('admin', 'Administrador', hashed_password, 'admin'))
62
+ conn.commit()
63
+ except Exception as e:
64
+ logging.error(f"Erro ao inicializar banco de dados: {e}")
65
+ sentry_sdk.capture_exception(e)
66
+ st.error(f"Erro ao inicializar banco de dados: {str(e)}")
67
+
68
+ conn.close()
69
+
70
+ def load_users_from_db():
71
+ """Carrega usuários do banco de dados SQLite"""
72
+ try:
73
+ db_path = os.getenv('DB_PATH', 'private/users.db')
74
+ conn = sqlite3.connect(db_path)
75
+ cursor = conn.cursor()
76
+ cursor.execute("SELECT username, name, password, role FROM users")
77
+ users = {row[0]: {'name': row[1], 'password': row[2], 'role': row[3]} for row in cursor.fetchall()}
78
+ conn.close()
79
+
80
+ config = {
81
+ 'credentials': {'usernames': users},
82
+ 'cookie': {'name': 'ai_app_cookie', 'key': 'random_key_123', 'expiry_days': 30},
83
+ 'preauthorized': {'emails': []}
84
+ }
85
+ return config
86
+ except Exception as e:
87
+ st.error(f"Erro ao carregar usuários: {str(e)}")
88
+ logging.error(f"Erro ao carregar usuários: {e}")
89
+ sentry_sdk.capture_exception(e)
90
+ return None
91
+
92
+ # Cache para modelos
93
+ @st.cache_resource(show_spinner=False)
94
+ def load_model(model_key):
95
+ """Carrega modelo específico com cache persistente"""
96
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
97
+ cache_dir = "model_cache"
98
+ os.makedirs(cache_dir, exist_ok=True)
99
+ logging.info(f"Carregando modelo {model_key} em {device} com cache em {cache_dir}")
100
+
101
+ try:
102
+ if model_key == 'sentiment_analysis':
103
+ return pipeline("sentiment-analysis", model="cardiffnlp/twitter-roberta-base-sentiment-latest", device=device, cache_dir=cache_dir)
104
+ elif model_key == 'text_classification':
105
+ return pipeline("text-classification", model="distilbert-base-uncased-finetuned-sst-2-english", device=device, cache_dir=cache_dir)
106
+ elif model_key == 'summarization':
107
+ return pipeline("summarization", model="facebook/bart-large-cnn", device=device, max_length=150, min_length=30, cache_dir=cache_dir)
108
+ elif model_key == 'question_answering':
109
+ return pipeline("question-answering", model="deepset/roberta-base-squad2", device=device, cache_dir=cache_dir)
110
+ elif model_key == 'translation':
111
+ return pipeline("translation", model="Helsinki-NLP/opus-mt-tc-big-en-pt", device=device, cache_dir=cache_dir)
112
+ elif model_key == 'text_generation':
113
+ tokenizer = AutoTokenizer.from_pretrained("gpt2", cache_dir=cache_dir)
114
+ model = AutoModelForCausalLM.from_pretrained("gpt2", cache_dir=cache_dir)
115
+ model.config.pad_token_id = model.config.eos_token_id
116
+ return pipeline("text-generation", model=model, tokenizer=tokenizer, device=device)
117
+ elif model_key == 'ner':
118
+ return pipeline("ner", model="dbmdz/bert-large-cased-finetuned-conll03-english", device=device, aggregation_strategy="simple", cache_dir=cache_dir)
119
+ elif model_key == 'image_classification':
120
+ return pipeline("image-classification", model="google/vit-base-patch16-224", device=device, cache_dir=cache_dir)
121
+ elif model_key == 'object_detection':
122
+ return pipeline("object-detection", model="facebook/detr-resnet-50", device=device, cache_dir=cache_dir)
123
+ elif model_key == 'image_segmentation':
124
+ return pipeline("image-segmentation", model="facebook/detr-resnet-50-panoptic", device=device, cache_dir=cache_dir)
125
+ elif model_key == 'facial_recognition':
126
+ return pipeline("image-classification", model="mo-thecreator/vit-Facial-Expression-Recognition", device=device, cache_dir=cache_dir)
127
+ elif model_key == 'speech_to_text':
128
+ return pipeline("automatic-speech-recognition", model="openai/whisper-base", device=device, cache_dir=cache_dir)
129
+ elif model_key == 'audio_classification':
130
+ return pipeline("audio-classification", model="superb/hubert-base-superb-er", device=device, cache_dir=cache_dir)
131
+ elif model_key == 'text_to_image':
132
+ return StableDiffusionPipeline.from_pretrained(
133
+ "runwayml/stable-diffusion-v1-5", torch_dtype=torch.float16, use_safetensors=True, safety_checker=None, variant="fp16", cache_dir=cache_dir
134
+ )
135
+ except Exception as e:
136
+ st.error(f"Erro ao carregar modelo {model_key}: {str(e)}")
137
+ logging.error(f"Erro ao carregar modelo {model_key}: {e}")
138
+ sentry_sdk.capture_exception(e)
139
+ return None
140
+
141
+ def validate_audio_file(file: UploadedFile) -> bool:
142
+ """Valida o arquivo de áudio"""
143
+ valid_extensions = ['.wav', '.mp3', '.flac', '.m4a']
144
+ return any(file.name.lower().endswith(ext) for ext in valid_extensions)
145
+
146
+ def validate_image_file(file: UploadedFile) -> bool:
147
+ """Valida o arquivo de imagem"""
148
+ valid_extensions = ['.jpg', '.jpeg', '.png', '.bmp']
149
+ if not any(file.name.lower().endswith(ext) for ext in valid_extensions):
150
+ return False
151
+ try:
152
+ Image.open(file).verify()
153
+ return True
154
+ except Exception:
155
+ return False
156
+
157
+ def process_audio_file(audio_file):
158
+ """Processa arquivo de áudio para o formato correto"""
159
+ try:
160
+ with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(audio_file.name)[1]) as tmp_file:
161
+ tmp_file.write(audio_file.read())
162
+ tmp_file_path = tmp_file.name
163
+
164
+ audio_array, sample_rate = librosa.load(tmp_file_path, sr=16000)
165
+ os.unlink(tmp_file_path)
166
+ return audio_array
167
+ except Exception as e:
168
+ st.error(f"Erro ao processar áudio: {str(e)}")
169
+ logging.error(f"Erro no processamento de áudio: {e}")
170
+ sentry_sdk.capture_exception(e)
171
+ return None
172
+
173
+ def process_image_file(image_file):
174
+ """Processa arquivo de imagem"""
175
+ try:
176
+ image = Image.open(image_file)
177
+ if image.mode != 'RGB':
178
+ image = image.convert('RGB')
179
+ return image
180
+ except Exception as e:
181
+ st.error(f"Erro ao processar imagem: {str(e)}")
182
+ logging.error(f"Erro no processamento de imagem: {e}")
183
+ sentry_sdk.capture_exception(e)
184
+ return None
185
+
186
+ def display_results(result, model_key, input_text=None):
187
+ """Exibe resultados formatados de acordo com o tipo de modelo"""
188
+ if model_key == 'summarization':
189
+ st.subheader("📝 Resumo Gerado")
190
+ if input_text:
191
+ st.markdown("**Texto Original:**")
192
+ st.write(input_text)
193
+ st.markdown("**Resumo:**")
194
+ st.info(result[0]['summary_text'])
195
+
196
+ elif model_key == 'translation':
197
+ st.subheader("🌍 Tradução")
198
+ st.success(result[0]['translation_text'])
199
+
200
+ elif model_key in ['sentiment_analysis', 'text_classification']:
201
+ st.subheader("📊 Resultados")
202
+ for res in result:
203
+ label = res['label']
204
+ score = res['score']
205
+ st.progress(float(score), text=f"{label} ({score:.2%})")
206
+
207
+ elif model_key == 'ner':
208
+ st.subheader("🔍 Entidades Reconhecidas")
209
+ for entity in result:
210
+ st.write(f"- **{entity['word']}**: {entity['entity_group']} (confiança: {entity['score']:.2%})")
211
+
212
+ elif model_key == 'text_generation':
213
+ st.subheader("🧠 Texto Gerado")
214
+ st.write(result[0]['generated_text'])
215
+
216
+ elif model_key == 'image_classification':
217
+ st.subheader("🏷️ Classificação")
218
+ for res in result[:5]:
219
+ st.write(f"- **{res['label']}**: {res['score']:.2%}")
220
+
221
+ elif model_key == 'object_detection':
222
+ st.subheader("📦 Objetos Detectados")
223
+ for obj in result:
224
+ st.write(f"- {obj['label']} (confiança: {obj['score']:.2%})")
225
+
226
+ elif model_key == 'image_segmentation':
227
+ st.subheader("🧩 Segmentação")
228
+ st.image(result[0]['mask'], caption="Máscara de segmentação")
229
+
230
+ elif model_key == 'facial_recognition':
231
+ st.subheader("😊 Reconhecimento Facial")
232
+ top_result = result[0]
233
+ st.write(f"**Emoção predominante**: {top_result['label']} (confiança: {top_result['score']:.2%})")
234
+
235
+ elif model_key == 'speech_to_text':
236
+ st.subheader("🔈 Transcrição")
237
+ st.success(result['text'])
238
+
239
+ elif model_key == 'audio_classification':
240
+ st.subheader("🎧 Classificação de Áudio")
241
+ top_emotion = result[0]
242
+ st.write(f"**Emoção detectada**: {top_emotion['label']} (confiança: {top_emotion['score']:.2%})")
243
+
244
+ elif model_key == 'text_to_image':
245
+ st.subheader("🎨 Imagem Gerada")
246
+ st.image(result[0], caption="Imagem gerada a partir do texto")
247
+
248
+ def load_branding(username):
249
+ """Carrega branding personalizado por usuário"""
250
+ branding = {
251
+ 'admin': {'logo': 'logos/admin_logo.png', 'title': 'Bem-vindo, Administrador!'},
252
+ 'cliente': {'logo': 'logos/cliente_logo.png', 'title': 'Bem-vindo, Cliente!'},
253
+ 'empresa1': {'logo': 'logos/empresa1_logo.png', 'title': 'Bem-vindo, Empresa Um!'}
254
+ }
255
+ return branding.get(username, {'logo': None, 'title': 'Bem-vindo!'})
256
+
257
+ def admin_panel(authenticator):
258
+ """Painel administrativo para gerenciar usuários"""
259
+ if st.session_state.get('role') == 'admin':
260
+ st.sidebar.subheader("Painel Admin")
261
+ with st.sidebar.form("add_user_form"):
262
+ username = st.text_input("Username")
263
+ name = st.text_input("Nome")
264
+ password = st.text_input("Senha", type="password")
265
+ role = st.selectbox("Role", ["user", "admin"])
266
+ if st.form_submit_button("Adicionar Usuário"):
267
+ hashed_password = stauth.Hasher([password]).generate()[0]
268
+ db_path = os.getenv('DB_PATH', 'private/users.db')
269
+ conn = sqlite3.connect(db_path)
270
+ cursor = conn.cursor()
271
+ try:
272
+ cursor.execute('INSERT INTO users (username, name, password, role) VALUES (?, ?, ?, ?)',
273
+ (username, name, hashed_password, role))
274
+ conn.commit()
275
+ st.success(f"Usuário {username} adicionado!")
276
+ except sqlite3.IntegrityError:
277
+ st.error("Usuário já existe.")
278
+ except Exception as e:
279
+ st.error(f"Erro ao adicionar usuário: {str(e)}")
280
+ logging.error(f"Erro ao adicionar usuário: {e}")
281
+ sentry_sdk.capture_exception(e)
282
+ conn.close()
283
+
284
+ with st.sidebar.form("remove_user_form"):
285
+ username_to_remove = st.text_input("Username para Remover")
286
+ if st.form_submit_button("Remover Usuário"):
287
+ db_path = os.getenv('DB_PATH', 'private/users.db')
288
+ conn = sqlite3.connect(db_path)
289
+ cursor = conn.cursor()
290
+ try:
291
+ cursor.execute('DELETE FROM users WHERE username = ?', (username_to_remove,))
292
+ conn.commit()
293
+ if cursor.rowcount > 0:
294
+ st.success(f"Usuário {username_to_remove} removido!")
295
+ else:
296
+ st.error("Usuário não encontrado.")
297
+ except Exception as e:
298
+ st.error(f"Erro ao remover usuário: {str(e)}")
299
+ logging.error(f"Erro ao remover usuário: {e}")
300
+ sentry_sdk.capture_exception(e)
301
+ conn.close()
302
+
303
+ def get_use_cases():
304
+ """Retorna os casos de uso para cada modelo"""
305
+ return {
306
+ 'sentiment_analysis': {
307
+ 'title': "Análise de Sentimento",
308
+ 'description': "Analisa o sentimento (positivo, negativo, neutro) em comentários, avaliações ou postagens de clientes em redes sociais.",
309
+ 'example': "Uma empresa de varejo monitora menções da marca no Twitter/X, identificando feedback negativo para responder proativamente ou destacando comentários positivos em campanhas de marketing.",
310
+ 'benefit': "Melhoria na gestão de reputação online e resposta rápida a crises de imagem.",
311
+ 'demo_input': "A entrega foi super rápida, adorei!",
312
+ 'demo_type': 'text'
313
+ },
314
+ # Outros casos de uso mantidos idênticos ao original
315
+ 'text_classification': {
316
+ 'title': "Classificação de Texto",
317
+ 'description': "Classifica e-mails recebidos como positivos ou negativos para priorizar respostas ou identificar reclamações.",
318
+ 'example': "Um call center categoriza e-mails de clientes, direcionando mensagens negativas para equipes de suporte prioritário.",
319
+ 'benefit': "Otimização do tempo da equipe de atendimento e melhoria na experiência do cliente.",
320
+ 'demo_input': "Estou insatisfeito com o produto",
321
+ 'demo_type': 'text'
322
+ },
323
+ 'summarization': {
324
+ 'title': "Resumo de Texto",
325
+ 'description': "Gera resumos concisos de documentos longos, como relatórios financeiros ou atas de reuniões.",
326
+ 'example': "Uma consultoria financeira resume relatórios anuais de empresas em poucos parágrafos para facilitar a análise de investidores.",
327
+ 'benefit': "Economia de tempo na leitura de documentos extensos e tomada de decisão mais rápida.",
328
+ 'demo_input': "A empresa XYZ reportou um crescimento de 15% no último trimestre, impulsionado por novas parcerias estratégicas e expansão no mercado asiático. No entanto, desafios logísticos aumentaram os custos operacionais em 5%. A diretoria planeja investir em automação para mitigar esses custos no próximo ano.",
329
+ 'demo_type': 'text'
330
+ },
331
+ 'question_answering': {
332
+ 'title': "Perguntas e Respostas",
333
+ 'description': "Responde perguntas específicas com base em manuais, FAQs ou documentos internos.",
334
+ 'example': "Um chatbot de suporte técnico responde perguntas como 'Como configurar o produto X?' extraindo respostas diretamente do manual do produto.",
335
+ 'benefit': "Redução do tempo de suporte e maior autonomia para os usuários finais.",
336
+ 'demo_input': {
337
+ 'context': "O produto X tem garantia de 2 anos e pode ser configurado via aplicativo móvel em 5 minutos.",
338
+ 'question': "Qual é o tempo de garantia do produto X?"
339
+ },
340
+ 'demo_type': 'qa'
341
+ },
342
+ 'translation': {
343
+ 'title': "Tradução (EN→PT)",
344
+ 'description': "Traduz conteúdo de marketing, manuais ou comunicações de inglês para português.",
345
+ 'example': "Uma empresa de software traduz descrições de produtos para lançar no mercado brasileiro.",
346
+ 'benefit': "Expansão de mercado com conteúdo adaptado e redução de custos com tradutores humanos.",
347
+ 'demo_input': "Our product ensures high performance",
348
+ 'demo_type': 'text'
349
+ },
350
+ 'ner': {
351
+ 'title': "Reconhecimento de Entidades",
352
+ 'description': "Identifica entidades como nomes de pessoas, organizações e locais em contratos ou documentos legais.",
353
+ 'example': "Um escritório de advocacia extrai automaticamente nomes de partes envolvidas em contratos, agilizando revisões.",
354
+ 'benefit': "Redução de erros manuais e maior eficiência na análise de documentos.",
355
+ 'demo_input': "Microsoft assinou um contrato com a empresa XYZ em Nova York.",
356
+ 'demo_type': 'text'
357
+ },
358
+ 'text_generation': {
359
+ 'title': "Geração de Texto",
360
+ 'description': "Gera textos criativos para campanhas de marketing, postagens em redes sociais ou roteiros.",
361
+ 'example': "Uma agência de publicidade cria slogans ou descrições de produtos a partir de prompts iniciais.",
362
+ 'benefit': "Aceleração do processo criativo e geração de ideias inovadoras.",
363
+ 'demo_input': "Um futuro onde a tecnologia conecta todos",
364
+ 'demo_type': 'text'
365
+ },
366
+ 'image_classification': {
367
+ 'title': "Classificação de Imagem",
368
+ 'description': "Identifica defeitos ou classifica produtos em linhas de produção com base em imagens.",
369
+ 'example': "Uma fábrica de eletrônicos classifica imagens de circuitos como 'Defeituoso' ou 'Aprovado' para controle de qualidade.",
370
+ 'benefit': "Redução de erros humanos e aumento da eficiência na inspeção.",
371
+ 'demo_input': None,
372
+ 'demo_type': 'image'
373
+ },
374
+ 'object_detection': {
375
+ 'title': "Detecção de Objetos",
376
+ 'description': "Detecta objetos como pessoas, veículos ou itens em imagens de câmeras de segurança.",
377
+ 'example': "Um sistema de segurança identifica veículos em um estacionamento para monitoramento automático.",
378
+ 'benefit': "Maior segurança e automação de processos de monitoramento.",
379
+ 'demo_input': None,
380
+ 'demo_type': 'image'
381
+ },
382
+ 'image_segmentation': {
383
+ 'title': "Segmentação de Imagem",
384
+ 'description': "Segmenta diferentes partes de uma imagem, como órgãos em exames médicos.",
385
+ 'example': "Um hospital segmenta tumores em imagens de ressonância magnética, facilitando diagnósticos.",
386
+ 'benefit': "Apoio a diagnósticos médicos com maior precisão e rapidez.",
387
+ 'demo_input': None,
388
+ 'demo_type': 'image'
389
+ },
390
+ 'facial_recognition': {
391
+ 'title': "Reconhecimento Facial",
392
+ 'description': "Identifica emoções faciais em vídeos ou fotos de clientes em lojas ou eventos.",
393
+ 'example': "Uma loja de varejo analisa expressões faciais de clientes para avaliar a satisfação durante interações com produtos.",
394
+ 'benefit': "Melhoria na experiência do cliente com base em dados emocionais.",
395
+ 'demo_input': None,
396
+ 'demo_type': 'image'
397
+ },
398
+ 'speech_to_text': {
399
+ 'title': "Transcrição de Áudio",
400
+ 'description': "Converte gravações de reuniões ou entrevistas em texto para documentação.",
401
+ 'example': "Uma empresa transcreve automaticamente reuniões para criar atas ou resumos.",
402
+ 'benefit': "Economia de tempo na documentação e maior acessibilidade de conteúdo.",
403
+ 'demo_input': None,
404
+ 'demo_type': 'audio'
405
+ },
406
+ 'audio_classification': {
407
+ 'title': "Classificação de Áudio",
408
+ 'description': "Classifica emoções em chamadas de suporte para avaliar a qualidade do atendimento.",
409
+ 'example': "Um call center analisa chamadas para identificar emoções como 'Frustração' ou 'Satisfação' dos clientes.",
410
+ 'benefit': "Melhoria na formação de equipes e na experiência do cliente.",
411
+ 'demo_input': None,
412
+ 'demo_type': 'audio'
413
+ },
414
+ 'text_to_image': {
415
+ 'title': "Texto para Imagem",
416
+ 'description': "Gera imagens personalizadas a partir de descrições textuais para campanhas publicitárias ou design de produtos.",
417
+ 'example': "Uma agência de design cria mockups de produtos com base em prompts como 'Um smartphone futurista em um fundo azul neon'.",
418
+ 'benefit': "Redução de custos com designers gráficos e maior agilidade na criação de conteúdo visual.",
419
+ 'demo_input': "Uma paisagem tropical ao pôr do sol",
420
+ 'demo_type': 'text'
421
+ }
422
+ }
423
+
424
+ def handle_use_case_demo(models, use_case_key, use_case):
425
+ """Executa a demonstração de um caso de uso com entrada pré-definida"""
426
+ if use_case['demo_input'] is None:
427
+ st.warning("⚠️ Demonstração não disponível. Este modelo requer upload de imagem ou áudio.")
428
+ return
429
+
430
+ st.subheader("📊 Demonstração")
431
+ try:
432
+ model = models.get(use_case_key) or load_model(use_case_key)
433
+ if use_case['demo_type'] == 'text':
434
+ with st.spinner("Processando demonstração..."):
435
+ result = model(use_case['demo_input'])
436
+ display_results(result, use_case_key, input_text=use_case['demo_input'])
437
+ elif use_case['demo_type'] == 'qa':
438
+ with st.spinner("Processando demonstração..."):
439
+ result = model(
440
+ question=use_case['demo_input']['question'],
441
+ context=use_case['demo_input']['context']
442
+ )
443
+ st.success("🔍 Resposta encontrada:")
444
+ st.markdown(f"**Contexto:** {use_case['demo_input']['context']}")
445
+ st.markdown(f"**Pergunta:** {use_case['demo_input']['question']}")
446
+ st.markdown(f"**Resposta:** {result['answer']}")
447
+ st.markdown(f"**Confiança:** {result['score']:.2%}")
448
+ except Exception as e:
449
+ st.error(f"Erro ao executar demonstração: {str(e)}")
450
+ logging.error(f"Erro na demonstração do caso de uso {use_case_key}: {e}")
451
+ sentry_sdk.capture_exception(e)
452
+
453
+ def main():
454
+ # Inicializar banco de dados
455
+ init_db()
456
+
457
+ # Carregar configuração de autenticação
458
+ config = load_users_from_db()
459
+ if not config:
460
+ st.error("Falha ao carregar autenticação. Contate o administrador.")
461
+ return
462
+
463
+ authenticator = stauth.Authenticate(
464
+ config['credentials'],
465
+ config['cookie']['name'],
466
+ config['cookie']['key'],
467
+ config['cookie']['expiry_days'],
468
+ config['preauthorized']
469
+ )
470
+
471
+ # Tela de login
472
+ name, authentication_status, username = authenticator.login('Login', 'main')
473
+
474
+ if authentication_status:
475
+ # Gerenciar sessão
476
+ if "user_id" not in st.session_state:
477
+ st.session_state.user_id = username
478
+ st.session_state.role = config['credentials']['usernames'][username]['role']
479
+
480
+ # Carregar branding
481
+ branding = load_branding(username)
482
+ if branding['logo'] and os.path.exists(branding['logo']):
483
+ st.image(branding['logo'], width=150)
484
+ st.title(branding['title'])
485
+
486
+ authenticator.logout('Logout', 'sidebar')
487
+ admin_panel(authenticator)
488
+
489
+ # Tour guiado
490
+ if st.sidebar.button("Iniciar Tour"):
491
+ components.html("""
492
+ <script src="https://cdn.jsdelivr.net/npm/shepherd.js@10.0.1/dist/js/shepherd.min.js"></script>
493
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/shepherd.js@10.0.1/dist/css/shepherd.css"/>
494
+ <script>
495
+ const tour = new Shepherd.Tour({
496
+ defaultStepOptions: { scrollTo: true, cancelIcon: { enabled: true } },
497
+ steps: [
498
+ { id: 'login', title: 'Login', text: 'Faça login com suas credenciais.', attachTo: { element: '#login', on: 'bottom' } },
499
+ { id: 'tab1', title: 'Explorar Modelos', text: 'Teste modelos de IA.', attachTo: { element: '#tab1', on: 'bottom' } },
500
+ { id: 'tab2', title: 'Casos de Uso', text: 'Explore aplicações práticas.', attachTo: { element: '#tab2', on: 'bottom' } }
501
+ ]
502
+ });
503
+ tour.start();
504
+ </script>
505
+ """, height=0)
506
+
507
+ # Carregar modelos sob demanda
508
+ models = {}
509
+ model_categories = {
510
+ "📝 Processamento de Texto": [
511
+ ("Análise de Sentimento", "sentiment_analysis"),
512
+ ("Classificação de Texto", "text_classification"),
513
+ ("Resumo de Texto", "summarization"),
514
+ ("Perguntas e Respostas", "question_answering"),
515
+ ("Tradução (EN→PT)", "translation"),
516
+ ("Reconhecimento de Entidades", "ner"),
517
+ ("Geração de Texto", "text_generation")
518
+ ],
519
+ "🖼️ Processamento de Imagem": [
520
+ ("Classificação de Imagem", "image_classification"),
521
+ ("Detecção de Objetos", "object_detection"),
522
+ ("Segmentação de Imagem", "image_segmentation"),
523
+ ("Reconhecimento Facial", "facial_recognition")
524
+ ],
525
+ "🎵 Processamento de Áudio": [
526
+ ("Transcrição de Áudio", "speech_to_text"),
527
+ ("Classificação de Emoções", "audio_classification")
528
+ ],
529
+ "✨ Modelos Generativos": [
530
+ ("Texto para Imagem", "text_to_image")
531
+ ]
532
+ }
533
+
534
+ # Abas para navegação
535
+ tab1, tab2 = st.tabs(["Explorar Modelos", "Casos de Uso"])
536
+
537
+ with tab1:
538
+ st.sidebar.title("⚙️ Configurações")
539
+ selected_category = st.sidebar.selectbox(
540
+ "Categoria",
541
+ list(model_categories.keys()),
542
+ index=0
543
+ )
544
+
545
+ selected_model = st.sidebar.selectbox(
546
+ "Modelo",
547
+ [name for name, key in model_categories[selected_category]],
548
+ format_func=lambda x: x,
549
+ index=0
550
+ )
551
+
552
+ model_key = next(key for name, key in model_categories[selected_category] if name == selected_model)
553
+
554
+ # Carregar modelo com barra de progresso
555
+ if model_key not in models:
556
+ with st.spinner(f"Carregando modelo {selected_model}..."):
557
+ progress = st.progress(0)
558
+ models[model_key] = load_model(model_key)
559
+ progress.progress(100)
560
+
561
+ st.header(f"{selected_model}")
562
+
563
+ with st.expander("ℹ️ Sobre este modelo"):
564
+ model_info = {
565
+ 'sentiment_analysis': "Analisa o sentimento expresso em um texto (positivo/negativo/neutro).",
566
+ 'text_classification': "Classifica textos em categorias pré-definidas.",
567
+ 'summarization': "Gera um resumo conciso de um texto longo.",
568
+ 'question_answering': "Responde perguntas baseadas em um contexto fornecido.",
569
+ 'translation': "Traduz texto de inglês para português.",
570
+ 'ner': "Identifica e classifica entidades nomeadas (pessoas, lugares, organizações).",
571
+ 'text_generation': "Gera texto criativo continuando a partir de um prompt.",
572
+ 'image_classification': "Identifica objetos e cenas em imagens.",
573
+ 'object_detection': "Detecta e localiza múltiplos objetos em uma imagem.",
574
+ 'image_segmentation': "Segmenta diferentes elementos em uma imagem.",
575
+ 'facial_recognition': "Reconhece características faciais e emoções.",
576
+ 'speech_to_text': "Transcreve fala em texto.",
577
+ 'audio_classification': "Classifica emoções em arquivos de áudio.",
578
+ 'text_to_image': "Gera imagens a partir de descrições textuais."
579
+ }
580
+ st.info(model_info.get(model_key, "Informações detalhadas sobre este modelo."))
581
+
582
+ try:
583
+ if model_key in ['sentiment_analysis', 'text_classification', 'summarization',
584
+ 'translation', 'text_generation', 'ner']:
585
+ handle_text_models(models, model_key, selected_model)
586
+
587
+ elif model_key == 'question_answering':
588
+ handle_qa_model(models, model_key)
589
+
590
+ elif model_key in ['image_classification', 'object_detection',
591
+ 'image_segmentation', 'facial_recognition']:
592
+ handle_image_models(models, model_key, selected_model)
593
+
594
+ elif model_key in ['speech_to_text', 'audio_classification']:
595
+ handle_audio_models(models, model_key)
596
+
597
+ elif model_key == 'text_to_image':
598
+ handle_generative_models(models, model_key)
599
+
600
+ except Exception as e:
601
+ st.error(f"Erro inesperado durante a execução: {str(e)}")
602
+ logging.error(f"Erro durante a execução do modelo {model_key}: {e}")
603
+ sentry_sdk.capture_exception(e)
604
+
605
+ with tab2:
606
+ st.header("Casos de Uso")
607
+ st.markdown("Explore casos práticos de aplicação dos modelos para resolver problemas reais.")
608
+
609
+ use_cases = get_use_cases()
610
+ selected_use_case = st.selectbox(
611
+ "Selecione um caso de uso",
612
+ list(use_cases.keys()),
613
+ format_func=lambda x: use_cases[x]['title']
614
+ )
615
+
616
+ use_case = use_cases[selected_use_case]
617
+
618
+ st.subheader(use_case['title'])
619
+ with st.expander("ℹ️ Detalhes do Caso de Uso"):
620
+ st.markdown(f"**Descrição**: {use_case['description']}")
621
+ st.markdown(f"**Exemplo Prático**: {use_case['example']}")
622
+ st.markdown(f"**Benefício**: {use_case['benefit']}")
623
+
624
+ if use_case['demo_input'] is not None:
625
+ if st.button("🚀 Executar Demonstração", key=f"demo_{selected_use_case}"):
626
+ handle_use_case_demo(models, selected_use_case, use_case)
627
+
628
+ def handle_text_models(models, model_key, model_name):
629
+ """Manipula modelos de texto"""
630
+ input_text = st.text_area(
631
+ f"Digite o texto para {model_name.lower()}:",
632
+ height=200,
633
+ placeholder="Cole ou digite seu texto aqui...",
634
+ key=f"text_input_{model_key}"
635
+ )
636
+
637
+ advanced_params = {}
638
+ if model_key == 'summarization':
639
+ with st.expander("⚙️ Parâmetros Avançados"):
640
+ advanced_params['max_length'] = st.slider("Comprimento máximo", 50, 300, 150)
641
+ advanced_params['min_length'] = st.slider("Comprimento mínimo", 10, 100, 30)
642
+
643
+ if model_key == 'text_generation':
644
+ with st.expander("⚙️ Parâmetros Avançados"):
645
+ advanced_params['max_length'] = st.slider("Comprimento do texto", 50, 500, 100)
646
+ advanced_params['temperature'] = st.slider("Criatividade", 0.1, 1.0, 0.7)
647
+ advanced_params['num_return_sequences'] = st.slider("Número de resultados", 1, 5, 1)
648
+
649
+ if st.button(f"🚀 Executar {model_name}", type="primary", key=f"btn_{model_key}"):
650
+ if input_text.strip():
651
+ with st.spinner("Processando..."):
652
+ try:
653
+ model = models.get(model_key) or load_model(model_key)
654
+ if model_key == 'ner':
655
+ result = model(input_text)
656
+ elif model_key == 'text_generation':
657
+ result = model(
658
+ input_text,
659
+ max_new_tokens=advanced_params.get('max_length', 100),
660
+ do_sample=True,
661
+ temperature=advanced_params.get('temperature', 0.7),
662
+ top_k=50,
663
+ top_p=0.95,
664
+ num_return_sequences=advanced_params.get('num_return_sequences', 1)
665
+ )
666
+ else:
667
+ result = model(input_text, **advanced_params)
668
+
669
+ display_results(result, model_key, input_text=input_text)
670
+
671
+ except Exception as e:
672
+ st.error(f"Erro ao processar texto: {str(e)}")
673
+ logging.error(f"Erro no modelo {model_key}: {e}")
674
+ sentry_sdk.capture_exception(e)
675
+ else:
676
+ st.warning("⚠️ Por favor, insira um texto válido.")
677
+
678
+ def handle_qa_model(models, model_key):
679
+ """Manipula modelo de Q&A"""
680
+ col1, col2 = st.columns(2)
681
+
682
+ with col1:
683
+ context = st.text_area(
684
+ "Contexto:",
685
+ height=200,
686
+ placeholder="Cole o texto que contém a informação...",
687
+ key="qa_context"
688
+ )
689
+
690
+ with col2:
691
+ question = st.text_area(
692
+ "Pergunta:",
693
+ height=150,
694
+ placeholder="Faça sua pergunta sobre o contexto...",
695
+ key="qa_question"
696
+ )
697
+
698
+ with st.expander("⚙️ Parâmetros Avançados"):
699
+ confidence_threshold = st.slider("Limite de confiança", 0.0, 1.0, 0.5, 0.01)
700
+
701
+ if st.button("🚀 Executar Pergunta e Resposta", type="primary", key="btn_qa"):
702
+ if context.strip() and question.strip():
703
+ with st.spinner("Buscando resposta..."):
704
+ try:
705
+ model = models.get(model_key) or load_model(model_key)
706
+ result = model(question=question, context=context)
707
+
708
+ if result['score'] < confidence_threshold:
709
+ st.warning(f"⚠️ Confiança baixa na resposta ({result['score']:.2%})")
710
+
711
+ st.success("🔍 Resposta encontrada:")
712
+ st.markdown(f"**Contexto:** {context}")
713
+ st.markdown(f"**Pergunta:** {question}")
714
+ st.markdown(f"**Resposta:** {result['answer']}")
715
+ st.markdown(f"**Confiança:** {result['score']:.2%}")
716
+
717
+ except Exception as e:
718
+ st.error(f"Erro ao processar Q&A: {str(e)}")
719
+ logging.error(f"Erro no modelo Q&A: {e}")
720
+ sentry_sdk.capture_exception(e)
721
+ else:
722
+ st.warning("⚠️ Por favor, forneça tanto o contexto quanto a pergunta.")
723
+
724
+ def handle_image_models(models, model_key, model_name):
725
+ """Manipula modelos de imagem"""
726
+ uploaded_file = st.file_uploader(
727
+ "Carregue uma imagem",
728
+ type=["jpg", "png", "jpeg", "bmp"],
729
+ help="Formatos suportados: JPG, PNG, JPEG, BMP",
730
+ key=f"img_upload_{model_key}"
731
+ )
732
+
733
+ if uploaded_file is not None:
734
+ if not validate_image_file(uploaded_file):
735
+ st.error("⚠️ Formato de arquivo inválido ou arquivo corrompido.")
736
+ return
737
+
738
+ col1, col2 = st.columns(2)
739
+
740
+ with col1:
741
+ st.subheader("🖼️ Imagem Original")
742
+ image = process_image_file(uploaded_file)
743
+ if image:
744
+ st.image(image)
745
+
746
+ with col2:
747
+ st.subheader("📊 Resultados")
748
+ if st.button(f"🚀 Executar {model_name}", type="primary", key=f"btn_img_{model_key}"):
749
+ if image:
750
+ with st.spinner("Analisando imagem..."):
751
+ try:
752
+ model = models.get(model_key) or load_model(model_key)
753
+ result = model(image)
754
+ display_results(result, model_key)
755
+ except Exception as e:
756
+ st.error(f"Erro ao processar imagem: {str(e)}")
757
+ logging.error(f"Erro no modelo {model_key}: {e}")
758
+ sentry_sdk.capture_exception(e)
759
+
760
+ def handle_audio_models(models, model_key):
761
+ """Manipula modelos de áudio"""
762
+ model_name = "Transcrição de Áudio" if model_key == 'speech_to_text' else "Classificação de Áudio"
763
+
764
+ uploaded_file = st.file_uploader(
765
+ f"Carregue um arquivo de áudio para {model_name}",
766
+ type=["wav", "mp3", "flac", "m4a"],
767
+ help="Formatos suportados: WAV, MP3, FLAC, M4A",
768
+ key=f"audio_upload_{model_key}"
769
+ )
770
+
771
+ if uploaded_file is not None:
772
+ if not validate_audio_file(uploaded_file):
773
+ st.error("⚠️ Formato de arquivo inválido ou não suportado.")
774
+ return
775
+
776
+ st.audio(uploaded_file, format="audio/wav")
777
+
778
+ if st.button(f"🚀 Executar {model_name}", type="primary", key=f"btn_audio_{model_key}"):
779
+ with st.spinner("Processando áudio..."):
780
+ try:
781
+ audio_array = process_audio_file(uploaded_file)
782
+ if audio_array is not None:
783
+ model = models.get(model_key) or load_model(model_key)
784
+ result = model(audio_array)
785
+ display_results(result, model_key)
786
+ else:
787
+ st.error("Não foi possível processar o arquivo de áudio.")
788
+ except Exception as e:
789
+ st.error(f"Erro ao processar áudio: {str(e)}")
790
+ logging.error(f"Erro no modelo {model_key}: {e}")
791
+ sentry_sdk.capture_exception(e)
792
+
793
+ def handle_generative_models(models, model_key):
794
+ """Manipula modelos generativos"""
795
+ prompt = st.text_area(
796
+ "Descrição da imagem:",
797
+ height=150,
798
+ placeholder="Descreva a imagem que deseja gerar...",
799
+ key="text_to_image_prompt"
800
+ )
801
+
802
+ with st.expander("⚙️ Parâmetros Avançados"):
803
+ cols = st.columns(2)
804
+ with cols[0]:
805
+ width = st.slider("Largura", 256, 1024, 512, 64)
806
+ with cols[1]:
807
+ height = st.slider("Altura", 256, 1024, 512, 64)
808
+ num_images = st.slider("Número de imagens", 1, 4, 1)
809
+ guidance_scale = st.slider("Escala de orientação", 1.0, 20.0, 7.5)
810
+
811
+ if st.button("🚀 Gerar Imagem", type="primary", key="btn_text_to_image"):
812
+ if prompt.strip():
813
+ with st.spinner("Criando imagem..."):
814
+ try:
815
+ model = models.get(model_key) or load_model(model_key)
816
+ result = model(
817
+ prompt,
818
+ height=height,
819
+ width=width,
820
+ num_images_per_prompt=num_images,
821
+ guidance_scale=guidance_scale
822
+ )
823
+ display_results(result, model_key)
824
+ except Exception as e:
825
+ st.error(f"Erro ao gerar imagem: {str(e)}")
826
+ logging.error(f"Erro no modelo text-to-image: {e}")
827
+ sentry_sdk.capture_exception(e)
828
+ else:
829
+ st.warning("⚠️ Por favor, insira uma descrição para a imagem.")
830
+
831
+ if __name__ == "__main__":
832
+ main()