Jeice commited on
Commit
47fce7f
·
verified ·
1 Parent(s): cd99364

versaoMelhorada

Browse files
Files changed (1) hide show
  1. app.py +632 -128
app.py CHANGED
@@ -1,156 +1,660 @@
1
- from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
2
- from llama_index.llms.openai import OpenAI
3
- from llama_index.embeddings.openai import OpenAIEmbedding
4
- from huggingface_hub import snapshot_download
5
- import gradio as gr
 
 
 
 
 
 
 
 
6
  import os
7
  import yaml
8
  import json
 
 
 
 
 
 
 
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
- #Configure sua API da OpenAI (adicione no Hugging Face em Settings > Secrets)
12
- os.environ["OPENAI_API_KEY"] = os.getenv("OPENAI_API_KEY")
13
-
14
-
15
- #Função para extrair conteúdo de YAML, JSON, TXT e MD
16
- def extrair_conteudo_dos_arquivos(pasta):
17
- texto_final = ""
18
-
19
- for root, dirs, files in os.walk(pasta):
20
- for file in files:
21
- caminho_arquivo = os.path.join(root, file)
22
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  try:
24
- if file.endswith(('.yml', '.yaml')):
25
- with open(caminho_arquivo, 'r', encoding='utf-8') as f:
26
- data = yaml.safe_load(f)
27
- texto = yaml.dump(data, allow_unicode=True)
28
- texto_final += f"\n\n### Arquivo: {file}\n{texto}"
29
-
30
- elif file.endswith('.json'):
31
- with open(caminho_arquivo, 'r', encoding='utf-8') as f:
32
- data = json.load(f)
33
- texto = json.dumps(data, indent=2, ensure_ascii=False)
34
- texto_final += f"\n\n### Arquivo: {file}\n{texto}"
35
-
36
- elif file.endswith(('.md', '.txt')):
37
- with open(caminho_arquivo, 'r', encoding='utf-8') as f:
38
- texto = f.read()
39
- texto_final += f"\n\n### Arquivo: {file}\n{texto}"
40
-
41
  else:
42
- continue
43
-
44
  except Exception as e:
45
- print(f"Erro ao ler {file}: {e}")
46
-
47
- return texto_final
48
-
49
-
50
- #Função para gerar o arquivo documentacao.txt
51
- def gerar_arquivo_documentacao(pasta_origem, arquivo_destino="documentacao.txt"):
52
- texto = extrair_conteudo_dos_arquivos(pasta_origem)
53
-
54
- if texto.strip() == "":
55
- print("⚠️ Nenhum conteúdo encontrado nos arquivos!")
56
- else:
57
- with open(arquivo_destino, 'w', encoding='utf-8') as f:
58
- f.write(texto)
59
- print(f"✅ Arquivo {arquivo_destino} gerado com sucesso.")
60
-
61
-
62
- #Baixar a documentação do Hugging Face Dataset
63
- docs_dir = snapshot_download(
64
- repo_id="Jeice/n8n-docs-v2",
65
- repo_type="dataset"
66
- )
67
-
68
-
69
- #Gerar o arquivo de documentação consolidado
70
- gerar_arquivo_documentacao(docs_dir)
71
-
72
-
73
- #Carregar o documento gerado
74
- documents = SimpleDirectoryReader(input_files=["documentacao.txt"]).load_data()
75
-
76
-
77
- #Definir o LLM (agora com Settings no lugar de ServiceContext)
78
- Settings.llm = OpenAI(
79
- model="gpt-3.5-turbo",
80
- temperature=0.1, # mais precisão nas respostas
81
- system_prompt=(
82
- "Você é um assistente com vasta experiência na plataforma n8n. "
83
- "Responda sempre em português do Brasil, de forma clara, direta e objetiva, "
84
- "baseado exclusivamente na documentação fornecida. Se não souber, diga que "
85
- "não há informações suficientes na documentação do seu modelo."
86
- )
87
- )
88
- Settings.embed_model = OpenAIEmbedding()
89
-
90
- #Criar o índice vetorial
91
- index = VectorStoreIndex.from_documents(documents)
92
-
93
-
94
- #Criar engine de consulta
95
- query_engine = index.as_query_engine()
96
-
97
-
98
- #Função do chatbot
99
- def chatbot(input_text):
100
- response = query_engine.query(input_text)
101
- return str(response)
102
-
103
-
104
- #Interface Gradio — Bonita e Funcional
105
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  gr.Markdown(
107
- """
108
- # 🤖 Bot de Dúvidas sobre o **n8n**
109
- Este agente responde dúvidas sobre o **n8n**, baseado na documentação oficial carregada.
 
 
 
 
 
110
  """,
 
111
  )
112
-
 
113
  with gr.Row():
 
114
  with gr.Column(scale=1):
115
  gr.Image(
116
  value="https://n8n.io/images/n8n-logo.png",
117
- width=150,
118
  show_label=False,
119
  interactive=False,
120
  )
121
- with gr.Column(scale=5):
 
 
 
 
 
 
 
 
 
 
 
122
  gr.Markdown(
123
- "## Pergunte qualquer coisa sobre o n8n! <br> Exemplo: `Como criar um workflow no n8n?`"
 
 
 
 
124
  )
125
-
126
- with gr.Row():
127
- input_text = gr.Textbox(
128
- label="Digite sua pergunta",
129
- placeholder="Exemplo: Como criar um workflow no n8n?",
130
- lines=2,
131
- )
132
- output_text = gr.Textbox(
133
- label="Resposta do Bot",
134
- placeholder="Aqui aparecerá a resposta...",
135
- lines=10
136
- )
137
-
138
- with gr.Row():
139
- submit_btn = gr.Button("🚀 Perguntar")
140
- clear_btn = gr.Button("🧹 Limpar")
141
-
142
- with gr.Accordion("📚 Exemplos de Perguntas", open=False):
 
 
 
 
 
 
 
143
  gr.Markdown(
144
  """
145
- - Como criar um workflow no n8n?
 
 
 
 
 
146
  - Para que serve o node HTTP Request?
147
- - Quais são os nodes para integração com Google Sheets?
148
- - Como configurar autenticação no n8n?
 
 
 
 
 
 
 
 
 
 
149
  """
150
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
- submit_btn.click(fn=chatbot, inputs=input_text, outputs=output_text)
153
- clear_btn.click(lambda: ("", ""), None, [input_text, output_text])
154
 
155
- demo.launch()
 
 
 
 
 
 
 
 
156
 
 
1
+ """
2
+ 🤖 N8n Assistant - Versão Otimizada
3
+ Chatbot inteligente para dúvidas sobre n8n com arquitetura robusta
4
+
5
+ Melhorias implementadas:
6
+ - Tratamento completo de erros
7
+ - Sistema de logging
8
+ - Arquitetura orientada a objetos
9
+ - Interface melhorada com feedback visual
10
+ - Sistema de cache para performance
11
+ - Validações robustas
12
+ """
13
+
14
  import os
15
  import yaml
16
  import json
17
+ import logging
18
+ import pickle
19
+ import hashlib
20
+ import time
21
+ from pathlib import Path
22
+ from typing import Optional, Tuple
23
+ import gradio as gr
24
 
25
+ # Configurar logging
26
+ logging.basicConfig(
27
+ level=logging.INFO,
28
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
29
+ )
30
+ logger = logging.getLogger(__name__)
31
+
32
+ # Importações com tratamento de erro
33
+ try:
34
+ from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
35
+ from llama_index.llms.openai import OpenAI
36
+ from llama_index.embeddings.openai import OpenAIEmbedding
37
+ from huggingface_hub import snapshot_download
38
+ logger.info("Todas as bibliotecas importadas com sucesso")
39
+ except ImportError as e:
40
+ logger.error(f"Erro ao importar bibliotecas: {e}")
41
+ raise
42
+
43
+
44
+ class N8nChatbotPro:
45
+ """
46
+ Chatbot profissional para n8n com sistema de cache e tratamento robusto de erros
47
+ """
48
+
49
+ def __init__(self):
50
+ self.index = None
51
+ self.query_engine = None
52
+ self.docs_dir = None
53
+ self.cache_dir = Path("cache")
54
+ self.cache_dir.mkdir(exist_ok=True)
55
+ self.metricas = {
56
+ 'total_perguntas': 0,
57
+ 'tempo_inicializacao': 0,
58
+ 'cache_hits': 0
59
+ }
60
+
61
+ logger.info("Inicializando N8nChatbotPro...")
62
+
63
+ def setup_openai(self) -> bool:
64
+ """Configurar OpenAI com validação robusta"""
65
+ try:
66
+ api_key = os.getenv("OPENAI_API_KEY")
67
+ if not api_key:
68
+ logger.error("❌ OPENAI_API_KEY não encontrada nas variáveis de ambiente")
69
+ return False
70
+
71
+ if len(api_key) < 20: # Validação básica do formato
72
+ logger.error("❌ OPENAI_API_KEY parece inválida (muito curta)")
73
+ return False
74
+
75
+ os.environ["OPENAI_API_KEY"] = api_key
76
+ logger.info("✅ OpenAI API configurada com sucesso")
77
+ return True
78
+
79
+ except Exception as e:
80
+ logger.error(f"❌ Erro ao configurar OpenAI: {e}")
81
+ return False
82
+
83
+ def calcular_hash_dataset(self, pasta: str) -> str:
84
+ """Calcular hash MD5 do dataset para verificar mudanças"""
85
+ try:
86
+ hash_md5 = hashlib.md5()
87
+ arquivos_processados = 0
88
+
89
+ for root, dirs, files in os.walk(pasta):
90
+ for file in sorted(files): # Ordenar para hash consistente
91
+ caminho_arquivo = os.path.join(root, file)
92
+ try:
93
+ with open(caminho_arquivo, 'rb') as f:
94
+ for chunk in iter(lambda: f.read(4096), b""):
95
+ hash_md5.update(chunk)
96
+ arquivos_processados += 1
97
+ except Exception as e:
98
+ logger.warning(f"Erro ao processar {file} para hash: {e}")
99
+ continue
100
+
101
+ hash_resultado = hash_md5.hexdigest()
102
+ logger.info(f"Hash calculado para {arquivos_processados} arquivos: {hash_resultado[:8]}...")
103
+ return hash_resultado
104
+
105
+ except Exception as e:
106
+ logger.error(f"Erro ao calcular hash: {e}")
107
+ return ""
108
+
109
+ def salvar_cache(self, index: VectorStoreIndex, hash_dataset: str) -> bool:
110
+ """Salvar índice e hash em cache"""
111
+ try:
112
+ cache_index_path = self.cache_dir / "index.pkl"
113
+ cache_hash_path = self.cache_dir / "dataset_hash.txt"
114
+ cache_metadata_path = self.cache_dir / "metadata.json"
115
+
116
+ # Salvar índice
117
+ with open(cache_index_path, 'wb') as f:
118
+ pickle.dump(index, f)
119
+
120
+ # Salvar hash
121
+ with open(cache_hash_path, 'w') as f:
122
+ f.write(hash_dataset)
123
+
124
+ # Salvar metadata
125
+ metadata = {
126
+ 'timestamp': time.time(),
127
+ 'hash': hash_dataset,
128
+ 'version': '1.0'
129
+ }
130
+ with open(cache_metadata_path, 'w') as f:
131
+ json.dump(metadata, f)
132
+
133
+ logger.info("✅ Cache salvo com sucesso")
134
+ return True
135
+
136
+ except Exception as e:
137
+ logger.warning(f"⚠️ Erro ao salvar cache: {e}")
138
+ return False
139
+
140
+ def carregar_cache(self, hash_atual: str) -> Optional[VectorStoreIndex]:
141
+ """Carregar índice do cache se hash for igual"""
142
+ try:
143
+ cache_index_path = self.cache_dir / "index.pkl"
144
+ cache_hash_path = self.cache_dir / "dataset_hash.txt"
145
+
146
+ if not (cache_index_path.exists() and cache_hash_path.exists()):
147
+ logger.info("📁 Cache não encontrado")
148
+ return None
149
+
150
+ # Verificar hash
151
+ with open(cache_hash_path, 'r') as f:
152
+ hash_cache = f.read().strip()
153
+
154
+ if hash_cache != hash_atual:
155
+ logger.info("🔄 Dataset foi atualizado, cache inválido")
156
+ return None
157
+
158
+ # Carregar índice
159
+ with open(cache_index_path, 'rb') as f:
160
+ index = pickle.load(f)
161
+
162
+ self.metricas['cache_hits'] += 1
163
+ logger.info("⚡ Cache carregado com sucesso")
164
+ return index
165
+
166
+ except Exception as e:
167
+ logger.warning(f"⚠️ Erro ao carregar cache: {e}")
168
+ return None
169
+
170
+ def extrair_conteudo_dos_arquivos(self, pasta: str) -> Tuple[str, dict]:
171
+ """Extrair conteúdo de arquivos com estatísticas detalhadas"""
172
+ texto_final = ""
173
+ estatisticas = {
174
+ 'yaml': 0,
175
+ 'json': 0,
176
+ 'markdown': 0,
177
+ 'txt': 0,
178
+ 'erros': 0,
179
+ 'total_caracteres': 0
180
+ }
181
+
182
+ if not os.path.exists(pasta):
183
+ logger.error(f"❌ Pasta não encontrada: {pasta}")
184
+ return "", estatisticas
185
+
186
+ logger.info(f"📂 Processando arquivos em: {pasta}")
187
+
188
+ for root, dirs, files in os.walk(pasta):
189
+ for file in files:
190
+ caminho_arquivo = os.path.join(root, file)
191
+
192
+ try:
193
+ conteudo_arquivo = ""
194
+
195
+ if file.endswith(('.yml', '.yaml')):
196
+ with open(caminho_arquivo, 'r', encoding='utf-8') as f:
197
+ data = yaml.safe_load(f)
198
+ conteudo_arquivo = yaml.dump(data, allow_unicode=True)
199
+ estatisticas['yaml'] += 1
200
+
201
+ elif file.endswith('.json'):
202
+ with open(caminho_arquivo, 'r', encoding='utf-8') as f:
203
+ data = json.load(f)
204
+ conteudo_arquivo = json.dumps(data, indent=2, ensure_ascii=False)
205
+ estatisticas['json'] += 1
206
+
207
+ elif file.endswith('.md'):
208
+ with open(caminho_arquivo, 'r', encoding='utf-8') as f:
209
+ conteudo_arquivo = f.read()
210
+ estatisticas['markdown'] += 1
211
+
212
+ elif file.endswith('.txt'):
213
+ with open(caminho_arquivo, 'r', encoding='utf-8') as f:
214
+ conteudo_arquivo = f.read()
215
+ estatisticas['txt'] += 1
216
+
217
+ else:
218
+ continue
219
+
220
+ if conteudo_arquivo:
221
+ texto_final += f"\n\n### Arquivo: {file}\n{conteudo_arquivo}"
222
+ estatisticas['total_caracteres'] += len(conteudo_arquivo)
223
+
224
+ except Exception as e:
225
+ logger.warning(f"⚠️ Erro ao ler {file}: {e}")
226
+ estatisticas['erros'] += 1
227
+ continue
228
 
229
+ total_arquivos = sum([estatisticas[k] for k in ['yaml', 'json', 'markdown', 'txt']])
230
+ logger.info(f" Processados {total_arquivos} arquivos ({estatisticas['total_caracteres']:,} caracteres)")
231
+
232
+ return texto_final, estatisticas
233
+
234
+ def gerar_arquivo_documentacao(self, pasta_origem: str, arquivo_destino: str = "documentacao.txt") -> Tuple[bool, dict]:
235
+ """Gerar arquivo de documentação com estatísticas"""
236
+ try:
237
+ logger.info("📝 Gerando arquivo de documentação...")
238
+ texto, estatisticas = self.extrair_conteudo_dos_arquivos(pasta_origem)
239
+
240
+ if not texto.strip():
241
+ logger.warning("⚠️ Nenhum conteúdo encontrado nos arquivos!")
242
+ return False, estatisticas
243
+
244
+ with open(arquivo_destino, 'w', encoding='utf-8') as f:
245
+ f.write(texto)
246
+
247
+ # Verificar tamanho do arquivo gerado
248
+ tamanho_arquivo = os.path.getsize(arquivo_destino)
249
+ logger.info(f"✅ Arquivo {arquivo_destino} gerado ({tamanho_arquivo:,} bytes)")
250
+
251
+ return True, estatisticas
252
+
253
+ except Exception as e:
254
+ logger.error(f"❌ Erro ao gerar documentação: {e}")
255
+ return False, {}
256
+
257
+ def baixar_documentacao(self) -> bool:
258
+ """Baixar documentação do Hugging Face com retry"""
259
+ max_tentativas = 3
260
+
261
+ for tentativa in range(max_tentativas):
262
  try:
263
+ logger.info(f"📥 Baixando documentação do n8n (tentativa {tentativa + 1}/{max_tentativas})...")
264
+
265
+ self.docs_dir = snapshot_download(
266
+ repo_id="Jeice/n8n-docs-v2",
267
+ repo_type="dataset",
268
+ cache_dir="./hf_cache"
269
+ )
270
+
271
+ # Verificar se o download foi bem-sucedido
272
+ if os.path.exists(self.docs_dir) and os.listdir(self.docs_dir):
273
+ logger.info(f" Documentação baixada em: {self.docs_dir}")
274
+ return True
 
 
 
 
 
275
  else:
276
+ logger.warning("⚠️ Diretório vazio após download")
277
+
278
  except Exception as e:
279
+ logger.warning(f"⚠️ Tentativa {tentativa + 1} falhou: {e}")
280
+ if tentativa == max_tentativas - 1:
281
+ logger.error("❌ Todas as tentativas de download falharam")
282
+ return False
283
+ time.sleep(2) # Aguardar antes da próxima tentativa
284
+
285
+ return False
286
+
287
+ def configurar_llm(self) -> bool:
288
+ """Configurar LLM com parâmetros otimizados"""
289
+ try:
290
+ Settings.llm = OpenAI(
291
+ model="gpt-3.5-turbo",
292
+ temperature=0.1, # Baixa temperatura para respostas mais precisas
293
+ max_tokens=1000, # Limite de tokens para controlar custos
294
+ system_prompt=(
295
+ "Você é um assistente especialista na plataforma n8n com vasta experiência. "
296
+ "Responda sempre em português do Brasil, de forma clara, direta e objetiva. "
297
+ "Base suas respostas exclusivamente na documentação fornecida. "
298
+ "Se não houver informações suficientes na documentação, seja honesto e "
299
+ "diga que não há informações suficientes para responder adequadamente. "
300
+ "Forneça exemplos práticos quando possível e estruture suas respostas "
301
+ "de forma didática e fácil de entender."
302
+ )
303
+ )
304
+
305
+ Settings.embed_model = OpenAIEmbedding(
306
+ model="text-embedding-ada-002"
307
+ )
308
+
309
+ logger.info("✅ LLM configurado com sucesso")
310
+ return True
311
+
312
+ except Exception as e:
313
+ logger.error(f"❌ Erro ao configurar LLM: {e}")
314
+ return False
315
+
316
+ def inicializar_index_com_cache(self) -> Tuple[bool, str]:
317
+ """Inicializar índice com sistema de cache inteligente"""
318
+ inicio_tempo = time.time()
319
+
320
+ try:
321
+ # Calcular hash do dataset
322
+ hash_dataset = self.calcular_hash_dataset(self.docs_dir)
323
+ if not hash_dataset:
324
+ return False, "Erro ao calcular hash do dataset"
325
+
326
+ # Tentar carregar do cache
327
+ cached_index = self.carregar_cache(hash_dataset)
328
+ if cached_index:
329
+ self.index = cached_index
330
+
331
+ # Configurar LLM (necessário mesmo com cache)
332
+ if not self.configurar_llm():
333
+ return False, "Erro ao configurar LLM"
334
+
335
+ self.query_engine = self.index.as_query_engine(
336
+ similarity_top_k=5, # Retornar top 5 documentos mais similares
337
+ response_mode="compact" # Modo compacto para respostas mais diretas
338
+ )
339
+
340
+ tempo_cache = time.time() - inicio_tempo
341
+ self.metricas['tempo_inicializacao'] = tempo_cache
342
+ return True, f"Índice carregado do cache em {tempo_cache:.2f}s"
343
+
344
+ # Se não há cache válido, processar normalmente
345
+ logger.info("🔄 Processando documentação (sem cache válido)...")
346
+
347
+ # Gerar arquivo de documentação
348
+ sucesso, estatisticas = self.gerar_arquivo_documentacao(self.docs_dir)
349
+ if not sucesso:
350
+ return False, "Erro ao processar documentação"
351
+
352
+ # Verificar se arquivo foi criado
353
+ if not os.path.exists("documentacao.txt"):
354
+ return False, "Arquivo documentacao.txt não foi criado"
355
+
356
+ # Carregar documentos
357
+ logger.info("📚 Carregando documentos...")
358
+ documents = SimpleDirectoryReader(input_files=["documentacao.txt"]).load_data()
359
+
360
+ if not documents:
361
+ return False, "Nenhum documento foi carregado"
362
+
363
+ logger.info(f"✅ {len(documents)} documentos carregados")
364
+
365
+ # Configurar LLM
366
+ if not self.configurar_llm():
367
+ return False, "Erro ao configurar LLM"
368
+
369
+ # Criar índice vetorial
370
+ logger.info("🧠 Criando índice vetorial...")
371
+ self.index = VectorStoreIndex.from_documents(
372
+ documents,
373
+ show_progress=True
374
+ )
375
+
376
+ # Configurar query engine
377
+ self.query_engine = self.index.as_query_engine(
378
+ similarity_top_k=5,
379
+ response_mode="compact"
380
+ )
381
+
382
+ # Salvar no cache
383
+ self.salvar_cache(self.index, hash_dataset)
384
+
385
+ tempo_total = time.time() - inicio_tempo
386
+ self.metricas['tempo_inicializacao'] = tempo_total
387
+
388
+ return True, f"Índice criado em {tempo_total:.2f}s ({estatisticas})"
389
+
390
+ except Exception as e:
391
+ logger.error(f"❌ Erro ao inicializar índice: {e}")
392
+ return False, f"Erro na inicialização: {str(e)}"
393
+
394
+ def setup(self) -> Tuple[bool, str]:
395
+ """Configurar todo o sistema com validações completas"""
396
+ try:
397
+ logger.info("🚀 Iniciando configuração do sistema...")
398
+
399
+ # 1. Configurar OpenAI
400
+ if not self.setup_openai():
401
+ return False, "❌ Falha na configuração da OpenAI API"
402
+
403
+ # 2. Baixar documentação
404
+ if not self.baixar_documentacao():
405
+ return False, "❌ Falha no download da documentação"
406
+
407
+ # 3. Inicializar índice com cache
408
+ sucesso, mensagem = self.inicializar_index_com_cache()
409
+ if not sucesso:
410
+ return False, f"❌ {mensagem}"
411
+
412
+ logger.info("✅ Sistema configurado com sucesso!")
413
+ return True, f"✅ {mensagem}"
414
+
415
+ except Exception as e:
416
+ logger.error(f"❌ Erro crítico na configuração: {e}")
417
+ return False, f"❌ Erro crítico: {str(e)}"
418
+
419
+ def chatbot(self, input_text: str) -> str:
420
+ """Função principal do chatbot com validações e métricas"""
421
+ # Validações de entrada
422
+ if not input_text or not input_text.strip():
423
+ return "⚠️ Por favor, digite uma pergunta válida."
424
+
425
+ if len(input_text.strip()) < 3:
426
+ return "⚠️ Pergunta muito curta. Por favor, seja mais específico."
427
+
428
+ if not self.query_engine:
429
+ return "❌ Sistema não inicializado. Tente recarregar a página."
430
+
431
+ try:
432
+ inicio = time.time()
433
+ self.metricas['total_perguntas'] += 1
434
+
435
+ # Log da pergunta (primeiros 100 caracteres)
436
+ pergunta_log = input_text[:100] + "..." if len(input_text) > 100 else input_text
437
+ logger.info(f"🤔 Pergunta #{self.metricas['total_perguntas']}: {pergunta_log}")
438
+
439
+ # Processar pergunta
440
+ response = self.query_engine.query(input_text)
441
+
442
+ # Calcular tempo de resposta
443
+ tempo_resposta = time.time() - inicio
444
+ logger.info(f"⚡ Resposta gerada em {tempo_resposta:.2f}s")
445
+
446
+ # Formatar resposta
447
+ resposta_formatada = str(response).strip()
448
+
449
+ # Adicionar informações de debug se necessário
450
+ if len(resposta_formatada) < 50:
451
+ resposta_formatada += f"\n\n_Tempo de resposta: {tempo_resposta:.2f}s_"
452
+
453
+ return resposta_formatada
454
+
455
+ except Exception as e:
456
+ logger.error(f"❌ Erro ao processar pergunta: {e}")
457
+ return f"❌ Erro ao processar sua pergunta: {str(e)}\n\nTente reformular sua pergunta ou recarregar a página."
458
+
459
+ def obter_estatisticas(self) -> str:
460
+ """Obter estatísticas do sistema"""
461
+ return f"""
462
+ 📊 **Estatísticas do Sistema:**
463
+ - Total de perguntas: {self.metricas['total_perguntas']}
464
+ - Tempo de inicialização: {self.metricas['tempo_inicializacao']:.2f}s
465
+ - Cache hits: {self.metricas['cache_hits']}
466
+ - Status do índice: {'✅ Ativo' if self.index else '❌ Inativo'}
467
+ """
468
+
469
+
470
+ # Inicialização global
471
+ logger.info("🤖 Inicializando N8n Assistant...")
472
+ bot = N8nChatbotPro()
473
+
474
+ # Configurar sistema
475
+ logger.info("⚙️ Configurando sistema...")
476
+ success, message = bot.setup()
477
+
478
+ if success:
479
+ logger.info(f"✅ Sistema pronto: {message}")
480
+ else:
481
+ logger.error(f"❌ Falha na inicialização: {message}")
482
+
483
+
484
+ def responder_pergunta(input_text: str) -> str:
485
+ """Wrapper para interface Gradio"""
486
+ if not success:
487
+ return f"❌ Sistema não inicializado: {message}"
488
+ return bot.chatbot(input_text)
489
+
490
+
491
+ def obter_info_sistema() -> str:
492
+ """Obter informações do sistema"""
493
+ if not success:
494
+ return f"❌ Sistema não inicializado: {message}"
495
+ return bot.obter_estatisticas()
496
+
497
+
498
+ # Interface Gradio Premium
499
+ with gr.Blocks(
500
+ theme=gr.themes.Soft(),
501
+ title="N8n Assistant Pro",
502
+ css="""
503
+ .status-success { color: #28a745; font-weight: bold; }
504
+ .status-error { color: #dc3545; font-weight: bold; }
505
+ .metrics { background-color: #f8f9fa; padding: 10px; border-radius: 5px; }
506
+ """
507
+ ) as demo:
508
+
509
+ # Cabeçalho
510
  gr.Markdown(
511
+ f"""
512
+ # 🤖 N8n Assistant Pro
513
+
514
+ Assistente inteligente especializado em **n8n** com documentação oficial atualizada.
515
+
516
+ <div class="{'status-success' if success else 'status-error'}">
517
+ **Status do Sistema:** {message}
518
+ </div>
519
  """,
520
+ elem_classes=["status-success" if success else "status-error"]
521
  )
522
+
523
+ # Layout principal
524
  with gr.Row():
525
+ # Coluna da logo
526
  with gr.Column(scale=1):
527
  gr.Image(
528
  value="https://n8n.io/images/n8n-logo.png",
529
+ width=120,
530
  show_label=False,
531
  interactive=False,
532
  )
533
+
534
+ # Botão de estatísticas
535
+ if success:
536
+ stats_btn = gr.Button("📊 Ver Estatísticas", size="sm")
537
+ stats_output = gr.Textbox(
538
+ label="Estatísticas do Sistema",
539
+ visible=False,
540
+ lines=6
541
+ )
542
+
543
+ # Coluna principal
544
+ with gr.Column(scale=4):
545
  gr.Markdown(
546
+ """
547
+ ## Como posso ajudar você com o n8n hoje?
548
+
549
+ Digite sua pergunta abaixo e receba respostas baseadas na documentação oficial.
550
+ """
551
  )
552
+
553
+ # Área de chat
554
+ with gr.Row():
555
+ with gr.Column(scale=3):
556
+ input_text = gr.Textbox(
557
+ label="Sua pergunta sobre n8n",
558
+ placeholder="Ex: Como criar um workflow no n8n?",
559
+ lines=3,
560
+ max_lines=8
561
+ )
562
+
563
+ with gr.Row():
564
+ submit_btn = gr.Button("🚀 Perguntar", variant="primary", scale=2)
565
+ clear_btn = gr.Button("🧹 Limpar", scale=1)
566
+
567
+ with gr.Column(scale=4):
568
+ output_text = gr.Textbox(
569
+ label="Resposta do Assistant",
570
+ placeholder="Sua resposta aparecerá aqui...",
571
+ lines=15,
572
+ max_lines=25
573
+ )
574
+
575
+ # Seção de exemplos
576
+ with gr.Accordion("💡 Exemplos de Perguntas", open=False):
577
  gr.Markdown(
578
  """
579
+ **Workflows e Automação:**
580
+ - Como criar meu primeiro workflow no n8n?
581
+ - Quais são as melhores práticas para workflows?
582
+ - Como usar condições e loops nos workflows?
583
+
584
+ **Nodes e Integrações:**
585
  - Para que serve o node HTTP Request?
586
+ - Como integrar n8n com Google Sheets?
587
+ - Quais nodes usar para automação de email?
588
+
589
+ **Configuração e Deploy:**
590
+ - Como configurar webhooks no n8n?
591
+ - Como fazer deploy do n8n em produção?
592
+ - Como configurar autenticação OAuth?
593
+
594
+ **Troubleshooting:**
595
+ - Por que meu workflow não está executando?
596
+ - Como debugar erros nos nodes?
597
+ - Como otimizar performance dos workflows?
598
  """
599
  )
600
+
601
+ # Seção de informações técnicas
602
+ with gr.Accordion("ℹ️ Informações Técnicas", open=False):
603
+ gr.Markdown(
604
+ f"""
605
+ **Sobre este Assistant:**
606
+ - 🤖 **Modelo**: GPT-3.5-turbo otimizado para n8n
607
+ - 📚 **Base de conhecimento**: Documentação oficial n8n v2
608
+ - ⚡ **Sistema de cache**: Ativo para respostas mais rápidas
609
+ - 🔄 **Última atualização**: Documentação sincronizada automaticamente
610
+ - 🛡️ **Tratamento de erros**: Sistema robusto com fallbacks
611
+
612
+ **Status da Inicialização:**
613
+ - ✅ Configuração OpenAI: {'Sucesso' if success else 'Falha'}
614
+ - ✅ Download documentação: {'Sucesso' if success else 'Falha'}
615
+ - ✅ Índice vetorial: {'Ativo' if success else 'Inativo'}
616
+ - ✅ Cache system: {'Ativo' if success else 'Inativo'}
617
+
618
+ **Métricas de Performance:**
619
+ {bot.obter_estatisticas() if success else 'Sistema não inicializado'}
620
+ """
621
+ )
622
+
623
+ # Eventos da interface
624
+ submit_btn.click(
625
+ fn=responder_pergunta,
626
+ inputs=input_text,
627
+ outputs=output_text
628
+ )
629
+
630
+ clear_btn.click(
631
+ lambda: ("", ""),
632
+ None,
633
+ [input_text, output_text]
634
+ )
635
+
636
+ # Enter para enviar
637
+ input_text.submit(
638
+ fn=responder_pergunta,
639
+ inputs=input_text,
640
+ outputs=output_text
641
+ )
642
+
643
+ # Botão de estatísticas
644
+ if success:
645
+ stats_btn.click(
646
+ fn=lambda: gr.update(visible=True, value=bot.obter_estatisticas()),
647
+ outputs=stats_output
648
+ )
649
 
 
 
650
 
651
+ # Configuração de lançamento
652
+ if __name__ == "__main__":
653
+ demo.launch(
654
+ server_name="0.0.0.0",
655
+ server_port=7860,
656
+ show_error=True,
657
+ show_api=False,
658
+ quiet=False
659
+ )
660