234 lines
12 KiB
Markdown
234 lines
12 KiB
Markdown
# Secretária Virtual — Roadmap Completo (Opção 3 Híbrida)
|
||
|
||
## Arquitetura Alvo
|
||
|
||
```
|
||
Plugin Alemão (satélite) Motor NewWhats
|
||
┌──────────────────────────────┐ ┌──────────────────────────────┐
|
||
│ webhook-receiver.js │ POST ask │ ProtocolEngine (brain.ts) │
|
||
│ └─ context-builder.js │ ───────────► │ system prompt dinâmico │
|
||
│ └─ DB local (clientes, │ │ histórico + summarização │
|
||
│ planos, promoções) │ ◄─────────── │ fallback chain AI providers │
|
||
│ │ reply │ sec_conversations/messages │
|
||
│ HandoffButton (UI) │ │ │
|
||
│ └─ per-chat mode: │ PATCH mode │ chat_handoff_mode │
|
||
│ ia | humano | pausado │ ───────────► │ (campo por conversa) │
|
||
└──────────────────────────────┘ └──────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## LEGENDA DE STATUS
|
||
- ✅ Implementado e funcionando
|
||
- 🔲 Pendente — não iniciado
|
||
- 🚧 Parcialmente feito
|
||
|
||
---
|
||
|
||
## BLOCO 1 — Handoff Humano ↔ IA (Botão Flutuante)
|
||
|
||
> Botão flutuante acima do input bar no chat. Um clique pausa a IA e o humano
|
||
> assume. Outro clique devolve o controle à IA. Se o humano ficar 15 min sem
|
||
> responder (ou a conexão cair), a IA reassume automaticamente.
|
||
|
||
### 1.1 — Motor: campo de modo por conversa
|
||
|
||
- 🔲 Adicionar coluna `handoff_mode` ENUM(`ia`, `humano`, `pausado`) DEFAULT `ia` na tabela `sec_conversations` (migration Knex)
|
||
- 🔲 Adicionar coluna `handoff_human_at` TIMESTAMP NULL na tabela `sec_conversations` (registra quando humano assumiu)
|
||
- 🔲 Rodar migration no motor (`plugins/secretaria/migrate.ts`)
|
||
|
||
### 1.2 — Motor: rotas de handoff (ext-api)
|
||
|
||
- 🔲 Criar `PATCH /ext/v1/sec/conversations/:id/handoff` — body `{ mode: 'ia'|'humano' }` — atualiza `handoff_mode` e `handoff_human_at`
|
||
- 🔲 Em `POST /ext/v1/secretaria/ask` — verificar `handoff_mode` antes de chamar brain: se `humano`, retornar `{ skipped: true }` sem chamar IA
|
||
|
||
### 1.3 — Motor: timer de retomada automática (15 min)
|
||
|
||
- 🔲 Criar job BullMQ `handoff-timeout` no motor: ao mudar para modo `humano`, enfileirar job com delay de 15 min
|
||
- 🔲 Job verifica se `handoff_mode` ainda é `humano` E `handoff_human_at` há mais de 15 min → muda para `ia` → emite evento WebSocket `conversation.handoff` para o satélite
|
||
- 🔲 Ao receber mensagem humana (`PATCH /handoff mode=humano`), cancelar job anterior e enfileirar novo (reset do timer)
|
||
- 🔲 Se conexão WS do satélite cair (`disconnect`), mudar todas as conversas desse satélite em modo `humano` para `ia` automaticamente
|
||
|
||
### 1.4 — Frontend: NwService métodos
|
||
|
||
- 🔲 Adicionar `NwService.getHandoffMode(chatId)` → `GET /nw/v1/sec/conversations/:id/handoff` (ou campo incluso no chat)
|
||
- 🔲 Adicionar `NwService.setHandoffMode(chatId, mode)` → `PATCH /nw/v1/sec/conversations/:id/handoff`
|
||
|
||
### 1.5 — Frontend: HandoffButton component (WhatsAppInbox.tsx)
|
||
|
||
- 🔲 Criar componente `HandoffButton` — botão flutuante posicionado `absolute bottom-[72px] right-4 z-20`
|
||
- 🔲 Estado local `handoffMode: 'ia' | 'humano'` por chat — carrega ao abrir o chat
|
||
- 🔲 Visual quando modo `ia`: ícone `Bot` verde com tooltip "IA respondendo — clique para assumir"
|
||
- 🔲 Visual quando modo `humano`: ícone `UserCheck` laranja pulsando com tooltip "Você está respondendo — clique para devolver à IA" + contador regressivo de 15 min
|
||
- 🔲 Ao clicar: chama `NwService.setHandoffMode`, atualiza estado local otimisticamente
|
||
- 🔲 Mostrar o botão apenas se sessão CONNECTED e chat selecionado
|
||
- 🔲 Esconder o botão se auto-reply mode = `off` (secretária desligada globalmente)
|
||
|
||
### 1.6 — Frontend: exibição visual no chat
|
||
|
||
- 🔲 Injetar mensagem de sistema no fluxo de mensagens ao mudar de modo: "🤖 IA assumiu o atendimento" / "👤 Atendente humano assumiu"
|
||
- 🔲 Header do chat mostrar badge discreto: `IA` (verde) ou `Humano` (laranja)
|
||
|
||
### 1.7 — Frontend: retomada automática via WebSocket
|
||
|
||
- 🔲 Escutar evento WS `conversation.handoff` → atualizar `handoffMode` local sem recarregar a página
|
||
- 🔲 Exibir toast "Secretária retomou o atendimento automaticamente (15 min sem resposta)"
|
||
|
||
### 1.8 — Motor: emitir evento WS ao retomar
|
||
|
||
- 🔲 Após job BullMQ retomar modo `ia`, emitir via WS `{ event: 'conversation.handoff', data: { chatId, mode: 'ia', reason: 'timeout' } }` para o satélite correto
|
||
|
||
### 1.9 — Webhook receiver: respeitar handoff
|
||
|
||
- 🔲 Em `webhook-receiver.js`, antes de chamar `/secretaria/ask`, consultar modo da conversa
|
||
- 🔲 Se modo `humano` → não chamar IA, apenas registrar mensagem recebida
|
||
- 🔲 Resetar timer de 15 min ao detectar mensagem do atendente humano (mensagem `fromMe: true`)
|
||
|
||
---
|
||
|
||
## BLOCO 2 — Injeção de Contexto Local (Opção 3 Híbrida)
|
||
|
||
> Hoje a secretária fica cega ao DB do projeto. O plugin precisa enriquecer
|
||
> cada chamada ao motor com dados reais do cliente.
|
||
|
||
### 2.1 — Motor: ampliar brain.chat() para aceitar contexto
|
||
|
||
- 🔲 Modificar `brain.ts` → `chat(convId, message, opts?: { contextData?: object; systemExtra?: string })`
|
||
- 🔲 Serializar `contextData` como bloco de texto e appendar ao system prompt antes da chamada à IA
|
||
- 🔲 Modificar `POST /ext/v1/secretaria/ask` → extrair `context` e `system_prompt_extra` do body e repassar ao brain
|
||
|
||
### 2.2 — Plugin: criar context-builder.js
|
||
|
||
- 🔲 Criar `sistema-nuvem/plugins/newwhats/context-builder.js`
|
||
- 🔲 Implementar `buildContext(phone)`:
|
||
- Buscar cliente pelo telefone no DB alemão (tabela `users` ou `clients`)
|
||
- Buscar status de assinatura do clube
|
||
- Buscar último pedido / histórico recente (últimos 3)
|
||
- Buscar promoções ativas do dia
|
||
- Buscar vagas abertas (caso relevante)
|
||
- 🔲 Retornar objeto `{ cliente, plano, ultimos_pedidos, promocoes_ativas }`
|
||
|
||
### 2.3 — Plugin: webhook-receiver usa context-builder
|
||
|
||
- 🔲 Modificar `webhook-receiver.js`: extrair número do remetente de `data.from`
|
||
- 🔲 Chamar `context-builder.js` com o número antes de chamar `/secretaria/ask`
|
||
- 🔲 Enviar `context` e `system_prompt_extra` no payload do `ask`
|
||
|
||
---
|
||
|
||
## BLOCO 3 — Tool Calling (IA que age, não só fala)
|
||
|
||
> Sem tools, a IA só gera texto. Com tools ela pode agendar, buscar e escalar.
|
||
|
||
### 3.1 — Motor: suporte a tools no ProtocolEngine
|
||
|
||
- ✅ Criado `plugins/secretaria/tools.ts` com interface `ToolDef { name, description, parameters, execute() }`
|
||
- ✅ Tools implementadas: `listar_horarios`, `agendar_horario`, `escalar_humano`, `encerrar_protocolo`
|
||
- ✅ `brain.chat()` aceita `tools?: string[]` — resolve via `resolveTools()` e habilita tool calling
|
||
- ✅ Loop de tool calling por provider: OpenAI (tool_calls), Anthropic (tool_use), Gemini (functionCall) — máx 5 iterações
|
||
- ✅ Fallback gracioso: se nenhum provider com tools disponível, usa modo texto normal
|
||
- ✅ `POST /ext/v1/secretaria/ask` aceita `tools: string[]` e repassa ao brain com hooks + tenantId
|
||
|
||
### 3.2 — Plugin: integração com tools do motor
|
||
|
||
- ✅ `webhook-receiver.js` inclui `tools: ['listar_horarios', 'agendar_horario', 'escalar_humano', 'encerrar_protocolo']` no payload do ask
|
||
- ✅ `escalar_humano` aciona handoff_mode=humano + emite `ext:handoff` via HookBus → chega ao frontend via WS
|
||
- ℹ️ `buscar_cliente` / `verificar_plano` resolvidos via `context-builder.js` (Bloco 2) — contexto pré-computado injetado no system prompt, não como tool call (mais simples e confiável)
|
||
|
||
---
|
||
|
||
## BLOCO 4 — Painel Admin: Melhorias
|
||
|
||
### 4.1 — Visualização de conversa
|
||
|
||
- ✅ Sub-tab Conversas: clicar em protocolo abre drawer lateral com todas as mensagens
|
||
- ✅ Botão "Finalizar protocolo" no drawer → `POST /sec/conversations/:id/finalize`
|
||
- ✅ Badge visual em conversas com `handoff_mode = humano` (ícone laranja)
|
||
- ✅ Badge visual em conversas escaladas (`status = escalated`)
|
||
|
||
### 4.2 — Chat de teste inline
|
||
|
||
- ✅ Sub-tab "Testar" no painel Secretária: campo de texto + botão enviar
|
||
- ✅ Chama `/secretaria/ask` com um chatId de teste fictício e exibe resposta
|
||
- ✅ Útil para testar prompts sem precisar de celular conectado
|
||
|
||
### 4.3 — Segurança das API Keys
|
||
|
||
- ✅ `GET /ext/v1/sec/keys` mascarar valores: `sk-...****` (mostrar só prefixo + últimos 4 chars)
|
||
- ✅ Campo de senha no painel com placeholder `••••••••` quando já salvo
|
||
|
||
---
|
||
|
||
## BLOCO 5 — Escalação Real
|
||
|
||
### 5.1 — Detecção e notificação
|
||
|
||
- ✅ Motor: `escalar_humano` tool emite `ext:escalated` com protocolNumber + motivo
|
||
- ✅ Motor: ws-bridge encaminha `ext:escalated` → `conversation.escalated` ao satélite
|
||
- ✅ Motor: listener em `buildExtRoutes` envia WA ao `admin_notify_phone` configurado
|
||
- ✅ Motor: `GET/PUT /sec/keys` inclui `admin_notify_phone` (em claro, não mascarado)
|
||
- ✅ Painel: toast laranja `⚠️ Escalação` ao receber `conversation.escalated` via WS
|
||
- ✅ Painel: badge "Escalada" vermelho nas conversas (implementado no Bloco 4)
|
||
- ✅ Painel: campo "WhatsApp do administrador" na aba Config
|
||
|
||
---
|
||
|
||
## RESUMO PRIORIZADO
|
||
|
||
| # | Bloco | Tarefa | Impacto | Esforço |
|
||
|---|---|---|---|---|
|
||
| 1 | Handoff | Migration: `handoff_mode` + `handoff_human_at` | 🔴 Crítico | Trivial |
|
||
| 2 | Handoff | `PATCH /sec/conversations/:id/handoff` + skip no ask | 🔴 Crítico | Pequeno |
|
||
| 3 | Handoff | `HandoffButton` React component | 🔴 Crítico | Pequeno |
|
||
| 4 | Handoff | Timer 15 min via BullMQ + retomada automática | 🔴 Crítico | Médio |
|
||
| 5 | Handoff | Evento WS `conversation.handoff` | 🟠 Alto | Pequeno |
|
||
| 6 | Handoff | Webhook-receiver respeitar modo humano | 🟠 Alto | Pequeno |
|
||
| 7 | Contexto | `context-builder.js` no plugin | 🔴 Crítico | Médio |
|
||
| 8 | Contexto | Motor aceitar `context` no brain.chat() | 🔴 Crítico | Pequeno |
|
||
| 9 | Contexto | Webhook receiver enviar contexto no ask | 🟠 Alto | Pequeno |
|
||
| 10 | Painel | Visualizar mensagens do protocolo + finalizar | 🟡 Médio | Pequeno |
|
||
| 11 | Painel | Mascarar API keys | 🟡 Médio | Trivial |
|
||
| 12 | Tools | Tool calling no ProtocolEngine | 🟠 Alto | Grande |
|
||
| 13 | Tools | Tools locais no plugin (buscar_cliente, etc.) | 🟠 Alto | Médio |
|
||
| 14 | Escalação | Notificação real ao admin + handoff automático | 🟡 Médio | Médio |
|
||
| 15 | Painel | Chat de teste inline | 🟢 Baixo | Pequeno |
|
||
|
||
---
|
||
|
||
## FLUXO ALVO COMPLETO
|
||
|
||
```
|
||
Mensagem recebida no WA
|
||
│
|
||
▼
|
||
webhook-receiver.js
|
||
├─ extrai phone do remetente
|
||
├─ context-builder.js → { cliente, plano, pedidos, promoções }
|
||
├─ verifica handoff_mode da conversa
|
||
│ ├─ modo "humano" → não chama IA, reseta timer 15min, fim
|
||
│ └─ modo "ia" → continua
|
||
└─ POST /secretaria/ask { chatId, message, context, tools: [...] }
|
||
│
|
||
▼
|
||
Motor ProtocolEngine.chat()
|
||
├─ monta system prompt = persona + knowledge + rules + calendar + context_local
|
||
├─ chama IA com tool definitions
|
||
│ ├─ IA chama tool → executa → retorna resultado → IA gera resposta
|
||
│ └─ IA responde diretamente
|
||
└─ retorna { reply, conversationId, protocolNumber }
|
||
│
|
||
▼
|
||
webhook-receiver.js envia reply via WA ao cliente
|
||
|
||
Humano clica HandoffButton
|
||
├─ PATCH /sec/conversations/:id/handoff { mode: "humano" }
|
||
├─ BullMQ enfileira job com delay 15min
|
||
├─ UI exibe badge laranja + contador regressivo
|
||
└─ próximas mensagens WA → webhook ignora IA
|
||
|
||
15 min sem resposta humana (ou WS cai)
|
||
├─ BullMQ job dispara → mode = "ia"
|
||
├─ Motor emite WS conversation.handoff
|
||
└─ UI atualiza badge → toast "Secretária retomou"
|
||
```
|