Mapa do FlowBuilder (Chatbot Visual)
Rota:
/chat-flow| Arquivo principal:src/app/(dashboard)/chat-flow/page.tsxComponentes:src/components/flow-builder/
Estrutura do FlowBuilder
Arquivos principais
src/components/flow-builder/
├── flow-editor.tsx ← Canvas visual (React Flow / @xyflow/react)
├── node-form.tsx ← Formulário de configuração de cada nó (~2800 linhas)
├── custom-node.tsx ← Componente visual de cada nó no canvas
├── time-table-editor.tsx ← Editor de tabela de horários
└── lib/
├── types.ts ← Interfaces (FlowNodeData, FlowInteraction, etc)
├── flow-adapter.ts ← Conversão React ↔ Vue format
└── default-flow.ts ← Fluxo padrão ao criar novo
Tipos de nós
type FlowNodeData.type = "start" | "configurations" | "node" | "timeTable"| Tipo | Função | Editável |
|---|---|---|
start | Ponto de entrada do fluxo | Limitado |
configurations | Configurações globais (welcome msg, keyword, timeout) | Sim |
node | Passo do fluxo com interações e condições | Sim |
timeTable | Ramificação por horário | Sim |
Tipos de interações (actions que o bot executa)
| Tipo | Função |
|---|---|
| message | Enviar texto |
| media | Enviar imagem/vídeo/documento |
| delay | Aguardar X segundos |
| button | Enviar botões interativos |
| list | Enviar lista de opções |
| chatgpt / claude / gemini | Consultar IA |
| typebot | Redirecionar para Typebot |
| n8n | Chamar webhook n8n |
| tag | Aplicar etiqueta ao contato |
| kanban | Mover para lane do kanban |
| chatflow | Redirecionar para outro fluxo |
| opportunity | Criar oportunidade no funil |
| transfer | Transferir para fila/agente |
| chatBotBlock | Bloquear o chatbot |
| webhook / webhookAll | Chamar webhook externo |
| contact | Enviar vCard |
| location | Enviar localização |
| sticker | Enviar figurinha |
| videoLink | Link de videoconferência |
| schedule | Agendar mensagem |
| reasons | Mostrar motivos de fechamento |
| sms | Enviar SMS |
| vapi | Chamada VAPI |
| notes | Criar nota interna |
| appointment | Consulta de agenda |
Tipos de condições (routing)
| Tipo | Função |
|---|---|
| US | Qualquer resposta → próximo nó |
| R + equals | Resposta igual a X |
| R + contains | Resposta contém X |
| R + startsWith | Resposta começa com X |
| R + endsWith | Resposta termina com X |
| R + regex | Resposta casa com regex |
| T | Baseado em horário (timeTable) |
Estrutura do JSON salvo (flow field em ChatFlows)
{
"name": "Meu Fluxo",
"nodeList": [
{
"id": "start",
"name": "Início",
"type": "start",
"left": "26px", "top": "100px",
"ico": "mdi-play",
"viewOnly": true
},
{
"id": "configurations",
"type": "configurations",
"configurations": {
"welcomeMessage": { "message": "Olá! Como posso ajudar?" },
"keyword": { "message": "*" },
"notResponseMessage": { "time": 10, "type": 1, "destiny": "" },
"notOptionsSelectMessage": { "message": "Opção inválida." },
"maxRetryBotMessage": { "number": 3, "type": 1, "destiny": "" }
}
},
{
"id": "nodeC",
"name": "Boas vindas!",
"type": "node",
"interactions": [
{
"id": "int-001",
"type": "MessageField",
"data": { "body": "Olá! Escolha uma opção:" }
}
],
"conditions": [
{
"id": "cond-001",
"type": "R",
"comparisonType": "equals",
"condition": ["1"],
"nextStepId": "nodeD",
"action": 0
}
]
}
],
"lineList": [
{ "from": "start", "to": "nodeC", "paintStyle": { "strokeWidth": 3, "stroke": "#5c67f2" } }
]
}Conversão React ↔ Vue (flow-adapter.ts)
O FlowBuilder usa internamente um formato React normalizado. Ao salvar, converte para o formato Vue (PascalCase) que o backend entende.
// Vue format (salvo no backend)
"type": "MessageField" → React: "message"
"type": "ButtonField" → React: "button"
"type": "MediaField" → React: "media"
// etc.
// Regra ao salvar (denormaliseFlowData):
// Nós start e configurations → SEM interactions/conditions/actions no JSON
// Nós node → COM interactions e conditions (convertidas para PascalCase)Keyword no start block (nossa melhoria)
O keyword está no nó configurations.configurations.keyword.message.
Na UI (node-form.tsx, buscar hasKeyword):
- Toggle “Qualquer mensagem” → define
keyword.message = "*" - Toggle “Palavra-chave” → input para texto específico
O backend lê configurations.keyword.message para decidir se ativa o fluxo.
Configurações globais (nó configurations)
| Campo | Função |
|---|---|
| welcomeMessage.message | Mensagem inicial ao entrar no bot |
| keyword.message | Palavra que ativa o bot (* = qualquer) |
| notOptionsSelectMessage.message | Mensagem para opção inválida |
| notResponseMessage.time | Minutos sem resposta antes de encerrar |
| maxRetryBotMessage.number | Máximo de tentativas erradas |
| farewellMessage.message | Mensagem de despedida |
| outOpenHours | Comportamento fora do horário |
| firstInteraction | Comportamento na primeira interação |
| autoDistributeTickets | Distribuição automática |
Ativar fluxo para um canal
- Em Sessões → editar canal → campo “ChatFlow”
- Ou em
Whatsapps.chatFlowIdno banco
Ativar por keyword
O backend verifica configurations.keyword.message contra o body da mensagem:
keyword === "*"→ ativa para qualquer mensagem (precisa do patch wildcard no backend)keyword === "oi"→ ativa apenas quando a msg for “oi” (case insensitive)
⚠️ PATCH PENDENTE: O backend obfuscado ainda pode não suportar
keyword === '*'. O arquivoc45_zpro.jsfoi renomeado na última atualização do fornecedor. Quando encontrado, aplicar:if (keyword === '*' || body.includes(keyword))