Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,72 +1,67 @@
|
|
1 |
"""
|
2 |
-
🤖 N8n Assistant - Versão
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
-
|
7 |
-
- Mantida toda funcionalidade do sistema
|
8 |
"""
|
9 |
|
10 |
import os
|
11 |
import yaml
|
12 |
import json
|
13 |
import logging
|
14 |
-
import time
|
15 |
from typing import Optional, Tuple
|
|
|
16 |
import gradio as gr
|
17 |
|
18 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
logging.basicConfig(level=logging.INFO)
|
20 |
-
logger = logging.getLogger(
|
21 |
|
22 |
-
#
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
from huggingface_hub import snapshot_download
|
28 |
-
logger.info("✅ Bibliotecas importadas com sucesso")
|
29 |
-
except ImportError as e:
|
30 |
-
logger.error(f"❌ Erro ao importar bibliotecas: {e}")
|
31 |
-
raise
|
32 |
|
|
|
|
|
33 |
|
|
|
|
|
|
|
34 |
class N8nAssistant:
|
35 |
-
"""Assistente N8n
|
36 |
-
|
37 |
def __init__(self):
|
38 |
self.index = None
|
39 |
self.query_engine = None
|
40 |
self.docs_dir = None
|
41 |
self.inicializado = False
|
42 |
-
|
43 |
-
|
44 |
-
"""Configurar OpenAI"""
|
45 |
-
try:
|
46 |
-
api_key = os.getenv("OPENAI_API_KEY")
|
47 |
-
if not api_key:
|
48 |
-
logger.error("❌ OPENAI_API_KEY não encontrada")
|
49 |
-
return False
|
50 |
-
|
51 |
-
os.environ["OPENAI_API_KEY"] = api_key
|
52 |
-
logger.info("✅ OpenAI configurada")
|
53 |
-
return True
|
54 |
-
except Exception as e:
|
55 |
-
logger.error(f"❌ Erro OpenAI: {e}")
|
56 |
-
return False
|
57 |
|
|
|
58 |
def extrair_conteudo_arquivos(self, pasta: str) -> str:
|
59 |
-
"""
|
60 |
texto_final = ""
|
61 |
-
|
62 |
if not os.path.exists(pasta):
|
63 |
logger.error(f"❌ Pasta não encontrada: {pasta}")
|
64 |
return ""
|
65 |
|
66 |
-
for root,
|
67 |
for file in files:
|
68 |
caminho_arquivo = os.path.join(root, file)
|
69 |
-
|
70 |
try:
|
71 |
if file.endswith(('.yml', '.yaml')):
|
72 |
with open(caminho_arquivo, 'r', encoding='utf-8') as f:
|
@@ -92,28 +87,27 @@ class N8nAssistant:
|
|
92 |
return texto_final
|
93 |
|
94 |
def gerar_documentacao(self, pasta_origem: str) -> bool:
|
95 |
-
"""
|
96 |
try:
|
97 |
texto = self.extrair_conteudo_arquivos(pasta_origem)
|
98 |
-
|
99 |
if not texto.strip():
|
100 |
-
logger.warning("⚠️ Nenhum conteúdo encontrado")
|
101 |
return False
|
102 |
-
|
103 |
with open("documentacao.txt", 'w', encoding='utf-8') as f:
|
104 |
f.write(texto)
|
105 |
-
|
106 |
-
logger.info("✅ Documentação
|
107 |
return True
|
108 |
-
|
109 |
except Exception as e:
|
110 |
logger.error(f"❌ Erro ao gerar documentação: {e}")
|
111 |
return False
|
112 |
|
113 |
def baixar_docs(self) -> bool:
|
114 |
-
"""
|
115 |
try:
|
116 |
-
logger.info("📥 Baixando documentação...")
|
117 |
self.docs_dir = snapshot_download(
|
118 |
repo_id="Jeice/n8n-docs-v2",
|
119 |
repo_type="dataset"
|
@@ -121,94 +115,144 @@ class N8nAssistant:
|
|
121 |
logger.info("✅ Download concluído")
|
122 |
return True
|
123 |
except Exception as e:
|
124 |
-
logger.error(f"❌ Erro no download: {e}")
|
125 |
return False
|
126 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
127 |
def criar_index(self) -> bool:
|
128 |
-
"""
|
129 |
try:
|
130 |
-
# Carregar documentos
|
131 |
if not os.path.exists("documentacao.txt"):
|
132 |
logger.error("❌ documentacao.txt não encontrado")
|
133 |
return False
|
134 |
-
|
135 |
-
documents = SimpleDirectoryReader(
|
136 |
-
|
|
|
|
|
137 |
if not documents:
|
138 |
logger.error("❌ Nenhum documento carregado")
|
139 |
return False
|
140 |
-
|
141 |
-
#
|
142 |
-
|
143 |
-
model="gpt-3.5-turbo",
|
144 |
-
temperature=0.1,
|
145 |
-
system_prompt=(
|
146 |
-
"Você é um assistente especialista em n8n. "
|
147 |
-
"Responda sempre em português do Brasil, de forma clara e objetiva, "
|
148 |
-
"baseado exclusivamente na documentação fornecida. "
|
149 |
-
"Se não souber, diga que não há informações suficientes."
|
150 |
-
)
|
151 |
-
)
|
152 |
-
Settings.embed_model = OpenAIEmbedding()
|
153 |
-
|
154 |
-
# Criar índice
|
155 |
-
logger.info("🧠 Criando índice...")
|
156 |
self.index = VectorStoreIndex.from_documents(documents)
|
157 |
self.query_engine = self.index.as_query_engine()
|
158 |
-
|
159 |
-
logger.info("✅ Índice criado")
|
160 |
return True
|
161 |
-
|
162 |
except Exception as e:
|
163 |
logger.error(f"❌ Erro ao criar índice: {e}")
|
164 |
return False
|
165 |
|
|
|
166 |
def inicializar(self) -> Tuple[bool, str]:
|
167 |
-
"""
|
168 |
try:
|
169 |
-
# 1
|
170 |
-
if not self.setup_openai():
|
171 |
-
return False, "Erro na configuração OpenAI"
|
172 |
-
|
173 |
-
# 2. Baixar docs
|
174 |
if not self.baixar_docs():
|
175 |
-
return False, "Erro
|
176 |
-
|
177 |
-
#
|
178 |
if not self.gerar_documentacao(self.docs_dir):
|
179 |
-
return False, "Erro ao processar documentação"
|
180 |
-
|
181 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
182 |
if not self.criar_index():
|
183 |
-
return False, "Erro ao criar índice"
|
184 |
-
|
185 |
self.inicializado = True
|
186 |
-
return True,
|
187 |
-
|
|
|
|
|
|
|
188 |
except Exception as e:
|
189 |
logger.error(f"❌ Erro na inicialização: {e}")
|
190 |
return False, f"Erro: {str(e)}"
|
191 |
|
192 |
def responder(self, pergunta: str) -> str:
|
193 |
-
"""
|
194 |
if not pergunta or not pergunta.strip():
|
195 |
return "⚠️ Por favor, digite uma pergunta."
|
196 |
-
|
197 |
if not self.inicializado or not self.query_engine:
|
198 |
return "❌ Sistema não inicializado. Recarregue a página."
|
199 |
-
|
200 |
try:
|
201 |
-
logger.info(f"🤔 Pergunta: {pergunta[:
|
202 |
response = self.query_engine.query(pergunta)
|
203 |
return str(response)
|
204 |
-
|
205 |
except Exception as e:
|
206 |
logger.error(f"❌ Erro ao responder: {e}")
|
207 |
return f"❌ Erro ao processar pergunta: {str(e)}"
|
208 |
|
209 |
|
210 |
-
#
|
211 |
-
|
|
|
|
|
212 |
assistant = N8nAssistant()
|
213 |
sucesso, mensagem = assistant.inicializar()
|
214 |
|
@@ -217,37 +261,28 @@ if sucesso:
|
|
217 |
else:
|
218 |
logger.error(f"❌ {mensagem}")
|
219 |
|
220 |
-
|
|
|
|
|
221 |
def processar_pergunta(pergunta: str) -> str:
|
222 |
-
"""Wrapper para Gradio"""
|
223 |
if not sucesso:
|
224 |
return f"❌ Sistema não inicializado: {mensagem}"
|
225 |
return assistant.responder(pergunta)
|
226 |
|
227 |
-
|
228 |
-
# Interface Gradio - SEM COMPONENTE IMAGE PROBLEMÁTICO
|
229 |
-
with gr.Blocks(theme=gr.themes.Soft(), title="N8n Assistant") as demo:
|
230 |
-
|
231 |
-
# Cabeçalho
|
232 |
gr.Markdown(
|
233 |
f"""
|
234 |
-
# 🤖 N8n Assistant
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
"""
|
239 |
-
#**Status:** {'✅ Sistema Pronto' if sucesso else '❌ ' + mensagem}
|
240 |
)
|
241 |
-
|
242 |
-
# Layout principal - SEM IMAGEM
|
243 |
with gr.Row():
|
244 |
with gr.Column(scale=1):
|
245 |
-
# REMOVIDO: componente gr.Image que causava erro 404
|
246 |
gr.Markdown("### 🤖 N8n Bot")
|
247 |
-
|
248 |
with gr.Column(scale=4):
|
249 |
gr.Markdown("## Como posso ajudar você com o n8n?")
|
250 |
-
|
251 |
with gr.Row():
|
252 |
with gr.Column(scale=3):
|
253 |
input_box = gr.Textbox(
|
@@ -255,19 +290,16 @@ with gr.Blocks(theme=gr.themes.Soft(), title="N8n Assistant") as demo:
|
|
255 |
placeholder="Ex: Como criar um workflow no n8n?",
|
256 |
lines=3
|
257 |
)
|
258 |
-
|
259 |
with gr.Row():
|
260 |
enviar_btn = gr.Button("🚀 Perguntar", variant="primary")
|
261 |
limpar_btn = gr.Button("🧹 Limpar")
|
262 |
-
|
263 |
with gr.Column(scale=4):
|
264 |
output_box = gr.Textbox(
|
265 |
label="Resposta",
|
266 |
placeholder="Sua resposta aparecerá aqui...",
|
267 |
lines=12
|
268 |
)
|
269 |
-
|
270 |
-
# Exemplos
|
271 |
with gr.Accordion("💡 Exemplos de Perguntas", open=False):
|
272 |
gr.Markdown(
|
273 |
"""
|
@@ -281,32 +313,10 @@ with gr.Blocks(theme=gr.themes.Soft(), title="N8n Assistant") as demo:
|
|
281 |
- Quais nodes usar para automação de email?
|
282 |
"""
|
283 |
)
|
284 |
-
|
285 |
-
# Eventos
|
286 |
-
enviar_btn.click(
|
287 |
-
fn=processar_pergunta,
|
288 |
-
inputs=input_box,
|
289 |
-
outputs=output_box
|
290 |
-
)
|
291 |
-
|
292 |
-
limpar_btn.click(
|
293 |
-
lambda: ("", ""),
|
294 |
-
None,
|
295 |
-
[input_box, output_box]
|
296 |
-
)
|
297 |
-
|
298 |
-
input_box.submit(
|
299 |
-
fn=processar_pergunta,
|
300 |
-
inputs=input_box,
|
301 |
-
outputs=output_box
|
302 |
-
)
|
303 |
|
|
|
|
|
|
|
304 |
|
305 |
-
# Lançar aplicação
|
306 |
if __name__ == "__main__":
|
307 |
-
demo.launch(
|
308 |
-
server_name="0.0.0.0",
|
309 |
-
server_port=7860,
|
310 |
-
show_error=True
|
311 |
-
)
|
312 |
-
|
|
|
1 |
"""
|
2 |
+
🤖 N8n Assistant - Versão Open Source (GRÁTIS)
|
3 |
+
- Sem OpenAI
|
4 |
+
- LLM: microsoft/Phi-3.5-mini-instruct (fallback flan-t5-base)
|
5 |
+
- Embeddings: all-MiniLM-L6-v2 (fallback L3-v2)
|
6 |
+
- Compatível com Hugging Face Spaces (CPU)
|
|
|
7 |
"""
|
8 |
|
9 |
import os
|
10 |
import yaml
|
11 |
import json
|
12 |
import logging
|
|
|
13 |
from typing import Optional, Tuple
|
14 |
+
|
15 |
import gradio as gr
|
16 |
|
17 |
+
# LlamaIndex (open source stacks)
|
18 |
+
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
|
19 |
+
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
|
20 |
+
from llama_index.llms.huggingface import HuggingFaceLLM
|
21 |
+
|
22 |
+
from huggingface_hub import snapshot_download
|
23 |
+
|
24 |
+
# ------------------------------------------------------------
|
25 |
+
# Logging
|
26 |
+
# ------------------------------------------------------------
|
27 |
logging.basicConfig(level=logging.INFO)
|
28 |
+
logger = logging.getLogger("n8n-assistant")
|
29 |
|
30 |
+
# ------------------------------------------------------------
|
31 |
+
# Configs de modelos (primários + fallbacks)
|
32 |
+
# ------------------------------------------------------------
|
33 |
+
PRIMARY_LLM = "microsoft/Phi-3.5-mini-instruct"
|
34 |
+
FALLBACK_LLM = "google/flan-t5-base" # muito leve
|
|
|
|
|
|
|
|
|
|
|
35 |
|
36 |
+
PRIMARY_EMB = "sentence-transformers/all-MiniLM-L6-v2"
|
37 |
+
FALLBACK_EMB = "sentence-transformers/paraphrase-MiniLM-L3-v2"
|
38 |
|
39 |
+
# ------------------------------------------------------------
|
40 |
+
# Classe principal
|
41 |
+
# ------------------------------------------------------------
|
42 |
class N8nAssistant:
|
43 |
+
"""Assistente N8n open-source e funcional"""
|
44 |
+
|
45 |
def __init__(self):
|
46 |
self.index = None
|
47 |
self.query_engine = None
|
48 |
self.docs_dir = None
|
49 |
self.inicializado = False
|
50 |
+
self.llm_model_used = None
|
51 |
+
self.emb_model_used = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
|
53 |
+
# --------- Utilitários de dados ----------
|
54 |
def extrair_conteudo_arquivos(self, pasta: str) -> str:
|
55 |
+
"""Extrai conteúdo textual dos arquivos .yml/.yaml/.json/.md/.txt"""
|
56 |
texto_final = ""
|
57 |
+
|
58 |
if not os.path.exists(pasta):
|
59 |
logger.error(f"❌ Pasta não encontrada: {pasta}")
|
60 |
return ""
|
61 |
|
62 |
+
for root, _, files in os.walk(pasta):
|
63 |
for file in files:
|
64 |
caminho_arquivo = os.path.join(root, file)
|
|
|
65 |
try:
|
66 |
if file.endswith(('.yml', '.yaml')):
|
67 |
with open(caminho_arquivo, 'r', encoding='utf-8') as f:
|
|
|
87 |
return texto_final
|
88 |
|
89 |
def gerar_documentacao(self, pasta_origem: str) -> bool:
|
90 |
+
"""Gera um único arquivo 'documentacao.txt' com todo o conteúdo unificado"""
|
91 |
try:
|
92 |
texto = self.extrair_conteudo_arquivos(pasta_origem)
|
|
|
93 |
if not texto.strip():
|
94 |
+
logger.warning("⚠️ Nenhum conteúdo encontrado para documentação")
|
95 |
return False
|
96 |
+
|
97 |
with open("documentacao.txt", 'w', encoding='utf-8') as f:
|
98 |
f.write(texto)
|
99 |
+
|
100 |
+
logger.info("✅ Documentação consolidada em documentacao.txt")
|
101 |
return True
|
102 |
+
|
103 |
except Exception as e:
|
104 |
logger.error(f"❌ Erro ao gerar documentação: {e}")
|
105 |
return False
|
106 |
|
107 |
def baixar_docs(self) -> bool:
|
108 |
+
"""Baixa a documentação do HF dataset"""
|
109 |
try:
|
110 |
+
logger.info("📥 Baixando documentação do dataset Jeice/n8n-docs-v2 ...")
|
111 |
self.docs_dir = snapshot_download(
|
112 |
repo_id="Jeice/n8n-docs-v2",
|
113 |
repo_type="dataset"
|
|
|
115 |
logger.info("✅ Download concluído")
|
116 |
return True
|
117 |
except Exception as e:
|
118 |
+
logger.error(f"❌ Erro no download do dataset: {e}")
|
119 |
return False
|
120 |
|
121 |
+
# --------- Configuração de modelos ----------
|
122 |
+
def configurar_embeddings(self) -> bool:
|
123 |
+
"""Configura embeddings HuggingFace com fallback"""
|
124 |
+
for emb in (PRIMARY_EMB, FALLBACK_EMB):
|
125 |
+
try:
|
126 |
+
Settings.embed_model = HuggingFaceEmbedding(model_name=emb)
|
127 |
+
self.emb_model_used = emb
|
128 |
+
logger.info(f"✅ Embeddings configurados: {emb}")
|
129 |
+
return True
|
130 |
+
except Exception as e:
|
131 |
+
logger.warning(f"⚠️ Falha ao carregar embeddings {emb}: {e}")
|
132 |
+
logger.error("❌ Não foi possível configurar embeddings")
|
133 |
+
return False
|
134 |
+
|
135 |
+
def configurar_llm(self) -> bool:
|
136 |
+
"""Configura LLM HuggingFace com fallback, otimizado para CPU"""
|
137 |
+
# parâmetros neutros/seguros para CPU
|
138 |
+
gen_kwargs = {
|
139 |
+
"temperature": 0.2,
|
140 |
+
"do_sample": True,
|
141 |
+
"top_p": 0.9
|
142 |
+
}
|
143 |
+
# tentar primário depois fallback
|
144 |
+
for model_name in (PRIMARY_LLM, FALLBACK_LLM):
|
145 |
+
try:
|
146 |
+
llm = HuggingFaceLLM(
|
147 |
+
model_name=model_name,
|
148 |
+
tokenizer_name=model_name,
|
149 |
+
context_window=4096,
|
150 |
+
max_new_tokens=512,
|
151 |
+
generate_kwargs=gen_kwargs,
|
152 |
+
# device_map="auto" funciona em CPU/GPU no Space
|
153 |
+
device_map="auto",
|
154 |
+
model_kwargs={
|
155 |
+
# dtype padrão (evitar float16 em CPU)
|
156 |
+
"torch_dtype": "auto"
|
157 |
+
},
|
158 |
+
# system_prompt para orientar o estilo de resposta
|
159 |
+
system_prompt=(
|
160 |
+
"Você é um assistente especialista em n8n. "
|
161 |
+
"Responda sempre em português do Brasil, de forma clara e objetiva, "
|
162 |
+
"baseado exclusivamente na documentação fornecida. "
|
163 |
+
"Se não souber, diga que não há informações suficientes."
|
164 |
+
),
|
165 |
+
)
|
166 |
+
Settings.llm = llm
|
167 |
+
self.llm_model_used = model_name
|
168 |
+
logger.info(f"✅ LLM configurado: {model_name}")
|
169 |
+
return True
|
170 |
+
except Exception as e:
|
171 |
+
logger.warning(f"⚠️ Falha ao carregar LLM {model_name}: {e}")
|
172 |
+
|
173 |
+
logger.error("❌ Não foi possível configurar o LLM")
|
174 |
+
return False
|
175 |
+
|
176 |
+
# --------- Indexação ----------
|
177 |
def criar_index(self) -> bool:
|
178 |
+
"""Cria o índice vetorial a partir de documentacao.txt"""
|
179 |
try:
|
|
|
180 |
if not os.path.exists("documentacao.txt"):
|
181 |
logger.error("❌ documentacao.txt não encontrado")
|
182 |
return False
|
183 |
+
|
184 |
+
documents = SimpleDirectoryReader(
|
185 |
+
input_files=["documentacao.txt"]
|
186 |
+
).load_data()
|
187 |
+
|
188 |
if not documents:
|
189 |
logger.error("❌ Nenhum documento carregado")
|
190 |
return False
|
191 |
+
|
192 |
+
# Criar índice + query engine
|
193 |
+
logger.info("🧠 Criando índice (VectorStoreIndex) ...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
194 |
self.index = VectorStoreIndex.from_documents(documents)
|
195 |
self.query_engine = self.index.as_query_engine()
|
196 |
+
logger.info("✅ Índice criado e query_engine pronto")
|
|
|
197 |
return True
|
198 |
+
|
199 |
except Exception as e:
|
200 |
logger.error(f"❌ Erro ao criar índice: {e}")
|
201 |
return False
|
202 |
|
203 |
+
# --------- Orquestração ----------
|
204 |
def inicializar(self) -> Tuple[bool, str]:
|
205 |
+
"""Pipeline completo de inicialização (open-source)"""
|
206 |
try:
|
207 |
+
# 1) Baixar docs
|
|
|
|
|
|
|
|
|
208 |
if not self.baixar_docs():
|
209 |
+
return False, "Erro ao baixar a documentação (dataset)"
|
210 |
+
|
211 |
+
# 2) Consolidar documentação
|
212 |
if not self.gerar_documentacao(self.docs_dir):
|
213 |
+
return False, "Erro ao processar/consolidar a documentação"
|
214 |
+
|
215 |
+
# 3) Configurar embeddings e LLM (open source)
|
216 |
+
if not self.configurar_embeddings():
|
217 |
+
return False, "Erro ao configurar embeddings"
|
218 |
+
if not self.configurar_llm():
|
219 |
+
return False, "Erro ao configurar LLM"
|
220 |
+
|
221 |
+
# 4) Criar índice
|
222 |
if not self.criar_index():
|
223 |
+
return False, "Erro ao criar o índice"
|
224 |
+
|
225 |
self.inicializado = True
|
226 |
+
return True, (
|
227 |
+
f"Sistema inicializado com sucesso | "
|
228 |
+
f"LLM: {self.llm_model_used} | Embeddings: {self.emb_model_used}"
|
229 |
+
)
|
230 |
+
|
231 |
except Exception as e:
|
232 |
logger.error(f"❌ Erro na inicialização: {e}")
|
233 |
return False, f"Erro: {str(e)}"
|
234 |
|
235 |
def responder(self, pergunta: str) -> str:
|
236 |
+
"""Executa a consulta no query_engine"""
|
237 |
if not pergunta or not pergunta.strip():
|
238 |
return "⚠️ Por favor, digite uma pergunta."
|
239 |
+
|
240 |
if not self.inicializado or not self.query_engine:
|
241 |
return "❌ Sistema não inicializado. Recarregue a página."
|
242 |
+
|
243 |
try:
|
244 |
+
logger.info(f"🤔 Pergunta: {pergunta[:120]}...")
|
245 |
response = self.query_engine.query(pergunta)
|
246 |
return str(response)
|
|
|
247 |
except Exception as e:
|
248 |
logger.error(f"❌ Erro ao responder: {e}")
|
249 |
return f"❌ Erro ao processar pergunta: {str(e)}"
|
250 |
|
251 |
|
252 |
+
# ------------------------------------------------------------
|
253 |
+
# Bootstrap
|
254 |
+
# ------------------------------------------------------------
|
255 |
+
logger.info("🚀 Inicializando N8n Assistant (Open Source)...")
|
256 |
assistant = N8nAssistant()
|
257 |
sucesso, mensagem = assistant.inicializar()
|
258 |
|
|
|
261 |
else:
|
262 |
logger.error(f"❌ {mensagem}")
|
263 |
|
264 |
+
# ------------------------------------------------------------
|
265 |
+
# Gradio UI
|
266 |
+
# ------------------------------------------------------------
|
267 |
def processar_pergunta(pergunta: str) -> str:
|
|
|
268 |
if not sucesso:
|
269 |
return f"❌ Sistema não inicializado: {mensagem}"
|
270 |
return assistant.responder(pergunta)
|
271 |
|
272 |
+
with gr.Blocks(theme=gr.themes.Soft(), title="N8n Assistant (Open Source)") as demo:
|
|
|
|
|
|
|
|
|
273 |
gr.Markdown(
|
274 |
f"""
|
275 |
+
# 🤖 N8n Assistant (Open Source)
|
276 |
+
Assistente para dúvidas sobre **n8n** baseado na documentação oficial e em modelos **open-source**.
|
277 |
+
**Status:** {'✅ Sistema Pronto' if sucesso else '❌ ' + mensagem}
|
|
|
278 |
"""
|
|
|
279 |
)
|
280 |
+
|
|
|
281 |
with gr.Row():
|
282 |
with gr.Column(scale=1):
|
|
|
283 |
gr.Markdown("### 🤖 N8n Bot")
|
|
|
284 |
with gr.Column(scale=4):
|
285 |
gr.Markdown("## Como posso ajudar você com o n8n?")
|
|
|
286 |
with gr.Row():
|
287 |
with gr.Column(scale=3):
|
288 |
input_box = gr.Textbox(
|
|
|
290 |
placeholder="Ex: Como criar um workflow no n8n?",
|
291 |
lines=3
|
292 |
)
|
|
|
293 |
with gr.Row():
|
294 |
enviar_btn = gr.Button("🚀 Perguntar", variant="primary")
|
295 |
limpar_btn = gr.Button("🧹 Limpar")
|
|
|
296 |
with gr.Column(scale=4):
|
297 |
output_box = gr.Textbox(
|
298 |
label="Resposta",
|
299 |
placeholder="Sua resposta aparecerá aqui...",
|
300 |
lines=12
|
301 |
)
|
302 |
+
|
|
|
303 |
with gr.Accordion("💡 Exemplos de Perguntas", open=False):
|
304 |
gr.Markdown(
|
305 |
"""
|
|
|
313 |
- Quais nodes usar para automação de email?
|
314 |
"""
|
315 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
316 |
|
317 |
+
enviar_btn.click(fn=processar_pergunta, inputs=input_box, outputs=output_box)
|
318 |
+
limpar_btn.click(lambda: ("", ""), None, [input_box, output_box])
|
319 |
+
input_box.submit(fn=processar_pergunta, inputs=input_box, outputs=output_box)
|
320 |
|
|
|
321 |
if __name__ == "__main__":
|
322 |
+
demo.launch(server_name="0.0.0.0", server_port=7860, show_error=True)
|
|
|
|
|
|
|
|
|
|