Jeice commited on
Commit
7a1233d
·
verified ·
1 Parent(s): a2e1bcb

corrigido1

Browse files
Files changed (1) hide show
  1. app.py +145 -489
app.py CHANGED
@@ -1,32 +1,24 @@
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
@@ -35,626 +27,290 @@ try:
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
 
 
1
  """
2
+ 🤖 N8n Assistant - Versão Corrigida e Simplificada
3
+ Chatbot inteligente para dúvidas sobre n8n - Compatível com Hugging Face Spaces
4
+
5
+ CORREÇÕES APLICADAS:
6
+ - Requirements.txt sem versões específicas (evita conflitos)
7
+ - Código simplificado mas robusto
8
+ - Tratamento de erros essencial
9
+ - Interface limpa e funcional
 
 
10
  """
11
 
12
  import os
13
  import yaml
14
  import json
15
  import logging
 
 
16
  import time
 
17
  from typing import Optional, Tuple
18
  import gradio as gr
19
 
20
  # Configurar logging
21
+ logging.basicConfig(level=logging.INFO)
 
 
 
22
  logger = logging.getLogger(__name__)
23
 
24
  # Importações com tratamento de erro
 
27
  from llama_index.llms.openai import OpenAI
28
  from llama_index.embeddings.openai import OpenAIEmbedding
29
  from huggingface_hub import snapshot_download
30
+ logger.info(" Bibliotecas importadas com sucesso")
31
  except ImportError as e:
32
+ logger.error(f"Erro ao importar bibliotecas: {e}")
33
  raise
34
 
35
 
36
+ class N8nAssistant:
37
+ """Assistente N8n simplificado e funcional"""
 
 
38
 
39
  def __init__(self):
40
  self.index = None
41
  self.query_engine = None
42
  self.docs_dir = None
43
+ self.inicializado = False
 
 
 
 
 
 
 
 
44
 
45
  def setup_openai(self) -> bool:
46
+ """Configurar OpenAI"""
47
  try:
48
  api_key = os.getenv("OPENAI_API_KEY")
49
  if not api_key:
50
+ logger.error("❌ OPENAI_API_KEY não encontrada")
 
 
 
 
51
  return False
52
 
53
  os.environ["OPENAI_API_KEY"] = api_key
54
+ logger.info("✅ OpenAI configurada")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  return True
 
56
  except Exception as e:
57
+ logger.error(f" Erro OpenAI: {e}")
58
  return False
59
 
60
+ def extrair_conteudo_arquivos(self, pasta: str) -> str:
61
+ """Extrair conteúdo dos arquivos"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  texto_final = ""
 
 
 
 
 
 
 
 
63
 
64
  if not os.path.exists(pasta):
65
  logger.error(f"❌ Pasta não encontrada: {pasta}")
66
+ return ""
67
 
 
 
68
  for root, dirs, files in os.walk(pasta):
69
  for file in files:
70
  caminho_arquivo = os.path.join(root, file)
71
 
72
  try:
 
 
73
  if file.endswith(('.yml', '.yaml')):
74
  with open(caminho_arquivo, 'r', encoding='utf-8') as f:
75
  data = yaml.safe_load(f)
76
+ texto = yaml.dump(data, allow_unicode=True)
77
+ texto_final += f"\n\n### Arquivo: {file}\n{texto}"
78
+
79
  elif file.endswith('.json'):
80
  with open(caminho_arquivo, 'r', encoding='utf-8') as f:
81
  data = json.load(f)
82
+ texto = json.dumps(data, indent=2, ensure_ascii=False)
83
+ texto_final += f"\n\n### Arquivo: {file}\n{texto}"
84
+
85
+ elif file.endswith(('.md', '.txt')):
 
 
 
 
 
86
  with open(caminho_arquivo, 'r', encoding='utf-8') as f:
87
+ texto = f.read()
88
+ texto_final += f"\n\n### Arquivo: {file}\n{texto}"
 
 
 
 
 
 
 
89
 
90
  except Exception as e:
91
  logger.warning(f"⚠️ Erro ao ler {file}: {e}")
 
92
  continue
93
 
94
+ return texto_final
 
 
 
95
 
96
+ def gerar_documentacao(self, pasta_origem: str) -> bool:
97
+ """Gerar arquivo de documentação"""
98
  try:
99
+ texto = self.extrair_conteudo_arquivos(pasta_origem)
 
100
 
101
  if not texto.strip():
102
+ logger.warning("⚠️ Nenhum conteúdo encontrado")
103
+ return False
104
 
105
+ with open("documentacao.txt", 'w', encoding='utf-8') as f:
106
  f.write(texto)
107
 
108
+ logger.info("✅ Documentação gerada")
109
+ return True
 
 
 
110
 
111
  except Exception as e:
112
  logger.error(f"❌ Erro ao gerar documentação: {e}")
113
+ return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
+ def baixar_docs(self) -> bool:
116
+ """Baixar documentação do HF"""
117
  try:
118
+ logger.info("📥 Baixando documentação...")
119
+ self.docs_dir = snapshot_download(
120
+ repo_id="Jeice/n8n-docs-v2",
121
+ repo_type="dataset"
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  )
123
+ logger.info("✅ Download concluído")
 
124
  return True
 
125
  except Exception as e:
126
+ logger.error(f"❌ Erro no download: {e}")
127
  return False
128
 
129
+ def criar_index(self) -> bool:
130
+ """Criar índice vetorial"""
 
 
131
  try:
132
+ # Carregar documentos
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  if not os.path.exists("documentacao.txt"):
134
+ logger.error(" documentacao.txt não encontrado")
135
+ return False
136
 
 
 
137
  documents = SimpleDirectoryReader(input_files=["documentacao.txt"]).load_data()
138
 
139
  if not documents:
140
+ logger.error("❌ Nenhum documento carregado")
141
+ return False
 
142
 
143
  # Configurar LLM
144
+ Settings.llm = OpenAI(
145
+ model="gpt-3.5-turbo",
146
+ temperature=0.1,
147
+ system_prompt=(
148
+ "Você é um assistente especialista em n8n. "
149
+ "Responda sempre em português do Brasil, de forma clara e objetiva, "
150
+ "baseado exclusivamente na documentação fornecida. "
151
+ "Se não souber, diga que não há informações suficientes."
152
+ )
 
 
 
 
 
153
  )
154
+ Settings.embed_model = OpenAIEmbedding()
155
 
156
+ # Criar índice
157
+ logger.info("🧠 Criando índice...")
158
+ self.index = VectorStoreIndex.from_documents(documents)
159
+ self.query_engine = self.index.as_query_engine()
 
160
 
161
+ logger.info("✅ Índice criado")
162
+ return True
163
 
164
  except Exception as e:
165
+ logger.error(f"❌ Erro ao criar índice: {e}")
166
+ return False
167
 
168
+ def inicializar(self) -> Tuple[bool, str]:
169
+ """Inicializar sistema completo"""
170
  try:
 
 
171
  # 1. Configurar OpenAI
172
  if not self.setup_openai():
173
+ return False, "Erro na configuração OpenAI"
174
+
175
+ # 2. Baixar docs
176
+ if not self.baixar_docs():
177
+ return False, "Erro no download da documentação"
178
 
179
+ # 3. Gerar documentação
180
+ if not self.gerar_documentacao(self.docs_dir):
181
+ return False, "Erro ao processar documentação"
182
 
183
+ # 4. Criar índice
184
+ if not self.criar_index():
185
+ return False, "Erro ao criar índice"
 
186
 
187
+ self.inicializado = True
188
+ return True, "Sistema inicializado com sucesso"
189
 
190
  except Exception as e:
191
+ logger.error(f"❌ Erro na inicialização: {e}")
192
+ return False, f"Erro: {str(e)}"
193
 
194
+ def responder(self, pergunta: str) -> str:
195
+ """Responder pergunta"""
196
+ if not pergunta or not pergunta.strip():
197
+ return "⚠️ Por favor, digite uma pergunta."
 
198
 
199
+ if not self.inicializado or not self.query_engine:
200
+ return " Sistema não inicializado. Recarregue a página."
 
 
 
201
 
202
  try:
203
+ logger.info(f"🤔 Pergunta: {pergunta[:50]}...")
204
+ response = self.query_engine.query(pergunta)
205
+ return str(response)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
 
207
  except Exception as e:
208
+ logger.error(f"❌ Erro ao responder: {e}")
209
+ return f"❌ Erro ao processar pergunta: {str(e)}"
210
 
 
 
 
 
 
 
 
 
 
211
 
212
+ # Inicializar sistema
213
+ logger.info("🚀 Inicializando N8n Assistant...")
214
+ assistant = N8nAssistant()
215
+ sucesso, mensagem = assistant.inicializar()
216
 
217
+ if sucesso:
218
+ logger.info(f" {mensagem}")
 
 
 
 
 
 
 
 
219
  else:
220
+ logger.error(f"❌ {mensagem}")
 
 
 
 
 
 
 
221
 
222
 
223
+ def processar_pergunta(pergunta: str) -> str:
224
+ """Wrapper para Gradio"""
225
+ if not sucesso:
226
+ return f"❌ Sistema não inicializado: {mensagem}"
227
+ return assistant.responder(pergunta)
228
 
229
 
230
+ # Interface Gradio
231
+ with gr.Blocks(theme=gr.themes.Soft(), title="N8n Assistant") as demo:
 
 
 
 
 
 
 
 
232
 
233
  # Cabeçalho
234
  gr.Markdown(
235
  f"""
236
+ # 🤖 N8n Assistant
237
 
238
+ Assistente inteligente para dúvidas sobre **n8n** baseado na documentação oficial.
239
 
240
+ **Status:** {'✅ Sistema Pronto' if sucesso else '' + mensagem}
241
+ """
 
 
 
242
  )
243
 
244
  # Layout principal
245
  with gr.Row():
 
246
  with gr.Column(scale=1):
247
  gr.Image(
248
  value="https://n8n.io/images/n8n-logo.png",
249
  width=120,
250
  show_label=False,
251
+ interactive=False
252
  )
 
 
 
 
 
 
 
 
 
253
 
 
254
  with gr.Column(scale=4):
255
+ gr.Markdown("## Como posso ajudar você com o n8n?")
 
 
 
 
 
 
256
 
 
257
  with gr.Row():
258
  with gr.Column(scale=3):
259
+ input_box = gr.Textbox(
260
+ label="Sua pergunta",
261
  placeholder="Ex: Como criar um workflow no n8n?",
262
+ lines=3
 
263
  )
264
 
265
  with gr.Row():
266
+ enviar_btn = gr.Button("🚀 Perguntar", variant="primary")
267
+ limpar_btn = gr.Button("🧹 Limpar")
268
 
269
  with gr.Column(scale=4):
270
+ output_box = gr.Textbox(
271
+ label="Resposta",
272
  placeholder="Sua resposta aparecerá aqui...",
273
+ lines=12
 
274
  )
275
 
276
+ # Exemplos
277
  with gr.Accordion("💡 Exemplos de Perguntas", open=False):
278
  gr.Markdown(
279
  """
280
+ - Como criar um workflow no n8n?
 
 
 
 
 
281
  - Para que serve o node HTTP Request?
282
  - Como integrar n8n com Google Sheets?
 
 
 
283
  - Como configurar webhooks no n8n?
284
+ - Quais são as melhores práticas para workflows?
 
 
 
 
285
  - Como debugar erros nos nodes?
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
286
  """
287
  )
288
 
289
+ # Eventos
290
+ enviar_btn.click(
291
+ fn=processar_pergunta,
292
+ inputs=input_box,
293
+ outputs=output_box
294
  )
295
 
296
+ limpar_btn.click(
297
  lambda: ("", ""),
298
  None,
299
+ [input_box, output_box]
300
  )
301
 
302
+ input_box.submit(
303
+ fn=processar_pergunta,
304
+ inputs=input_box,
305
+ outputs=output_box
 
306
  )
 
 
 
 
 
 
 
307
 
308
 
309
+ # Lançar aplicação
310
  if __name__ == "__main__":
311
  demo.launch(
312
  server_name="0.0.0.0",
313
  server_port=7860,
314
+ show_error=True
 
 
315
  )
316