213990d51f
Núcleo desta mudança (separação por sessão): - migrate.ts: sec_numbers.agent_id (FK→sec_agents, ON DELETE SET NULL) e sec_calendar.instance_id, com índices (aditivo/idempotente). - ext-api routes.ts: runtime resolve o agente pelo NÚMERO que recebeu a mensagem (sec_numbers[instanceId].agent_id, fallback: 1º ativo); rotas admin /secretaria/numbers e /secretaria/calendar aceitam os novos campos. - brain.ts/tools.ts: instanceId no contexto; sec_calendar interno filtrado por sessão; parse de jid robusto (.pop(), suporta 2 e 3 partes). - secretaria/routes.ts: paridade (agent_id em numbers, instance_id no calendar). Testado em dev (newwhats.dev refrescado): agentes resolvidos por instância; fallback e FK ON DELETE SET NULL verificados via ext-api. Obs.: por estarem no mesmo working tree não-commitado do newwhats.local, ext-api/routes.ts e secretaria/tools.ts também trazem o trabalho acumulado da integração do satélite (ações de inbox: delete/react/typing/labels/forward e admin /secretaria/*; tools identificar_numero/cadastrar_paciente do odonto). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
312 lines
13 KiB
JavaScript
312 lines
13 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.createSecretariaRoutes = createSecretariaRoutes;
|
|
const express_1 = require("express");
|
|
const brain_1 = require("./brain");
|
|
function uuid() {
|
|
try {
|
|
return crypto.randomUUID();
|
|
}
|
|
catch {
|
|
return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
}
|
|
}
|
|
function createSecretariaRoutes(db, config) {
|
|
const router = (0, express_1.Router)();
|
|
const brain = new brain_1.ProtocolEngine(db, config);
|
|
// ── Agents ───────────────────────────────────────────────────────────────
|
|
router.get('/agents', async (_req, res) => {
|
|
try {
|
|
const agents = await db('sec_agents').orderBy('created_at');
|
|
res.json(agents);
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
router.post('/agents', async (req, res) => {
|
|
try {
|
|
const { name, description, model, provider, temperature, context_window } = req.body;
|
|
const [agent] = await db('sec_agents').insert({
|
|
id: uuid(), name, description, model: model ?? 'gpt-4o-mini',
|
|
provider: provider ?? 'openai', temperature: temperature ?? 0.7,
|
|
context_window: context_window ?? 8, active: true,
|
|
}).returning('*');
|
|
res.status(201).json(agent);
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
router.put('/agents/:id', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { name, description, model, provider, temperature, context_window, active } = req.body;
|
|
const [agent] = await db('sec_agents').where({ id })
|
|
.update({ name, description, model, provider, temperature, context_window, active, updated_at: new Date() })
|
|
.returning('*');
|
|
res.json(agent);
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
router.delete('/agents/:id', async (req, res) => {
|
|
try {
|
|
await db('sec_agents').where({ id: req.params.id }).delete();
|
|
res.json({ ok: true });
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
// ── Brain Nodes ───────────────────────────────────────────────────────────
|
|
router.get('/agents/:agentId/nodes', async (req, res) => {
|
|
try {
|
|
const nodes = await db('sec_brain_nodes')
|
|
.where({ agent_id: req.params.agentId })
|
|
.orderBy('sort_order');
|
|
res.json(nodes);
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
router.post('/agents/:agentId/nodes', async (req, res) => {
|
|
try {
|
|
const { type, title, content, sort_order } = req.body;
|
|
const [node] = await db('sec_brain_nodes').insert({
|
|
id: uuid(), agent_id: req.params.agentId,
|
|
type, title, content, active: true,
|
|
sort_order: sort_order ?? 99,
|
|
}).returning('*');
|
|
res.status(201).json(node);
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
router.put('/nodes/:id', async (req, res) => {
|
|
try {
|
|
const { title, content, active, sort_order } = req.body;
|
|
const [node] = await db('sec_brain_nodes').where({ id: req.params.id })
|
|
.update({ title, content, active, sort_order, updated_at: new Date() })
|
|
.returning('*');
|
|
res.json(node);
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
router.delete('/nodes/:id', async (req, res) => {
|
|
try {
|
|
await db('sec_brain_nodes').where({ id: req.params.id }).delete();
|
|
res.json({ ok: true });
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
// ── Conversations ────────────────────────────────────────────────────────
|
|
router.get('/conversations', async (req, res) => {
|
|
try {
|
|
const { agent_id } = req.query;
|
|
let q = db('sec_conversations').orderBy('updated_at', 'desc');
|
|
if (agent_id)
|
|
q = q.where({ agent_id: agent_id });
|
|
const convs = await q;
|
|
res.json(convs);
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
router.post('/conversations', async (req, res) => {
|
|
try {
|
|
const { agent_id, contact_name } = req.body;
|
|
const [conv] = await db('sec_conversations').insert({
|
|
id: uuid(), agent_id,
|
|
contact_name: contact_name ?? 'Usuário Teste',
|
|
protocol_number: brain_1.ProtocolEngine.generateProtocolNumber(),
|
|
status: 'active',
|
|
}).returning('*');
|
|
res.status(201).json(conv);
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
router.patch('/conversations/:id', async (req, res) => {
|
|
try {
|
|
const { status, contact_name } = req.body;
|
|
const [conv] = await db('sec_conversations').where({ id: req.params.id })
|
|
.update({ status, contact_name, updated_at: new Date() }).returning('*');
|
|
res.json(conv);
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
router.delete('/conversations/:id', async (req, res) => {
|
|
try {
|
|
await db('sec_conversations').where({ id: req.params.id }).delete();
|
|
res.json({ ok: true });
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
// ── Messages ──────────────────────────────────────────────────────────────
|
|
router.get('/conversations/:id/messages', async (req, res) => {
|
|
try {
|
|
const messages = await db('sec_messages')
|
|
.where({ conversation_id: req.params.id })
|
|
.orderBy('created_at');
|
|
res.json(messages);
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
// Enviar mensagem → aciona o cérebro → retorna resposta da IA
|
|
router.post('/conversations/:id/chat', async (req, res) => {
|
|
try {
|
|
const { message } = req.body;
|
|
if (!message?.trim())
|
|
return res.status(400).json({ error: 'message is required' });
|
|
const reply = await brain.chat(String(req.params.id), message.trim());
|
|
res.json({ reply });
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
// Finalizar protocolo → gera resumo completo → apaga mensagens → fecha conversa
|
|
router.post('/conversations/:id/finalize', async (req, res) => {
|
|
try {
|
|
const result = await brain.finalizeProtocol(String(req.params.id));
|
|
res.json(result);
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
// ── Calendar ──────────────────────────────────────────────────────────────
|
|
router.get('/calendar', async (req, res) => {
|
|
try {
|
|
const qs = (k) => { const v = req.query[k]; return v ? String(v) : undefined; };
|
|
const from = qs('from'), to = qs('to'), status = qs('status'), instanceId = qs('instance_id');
|
|
let q = db('sec_calendar').orderBy('date').orderBy('time_start');
|
|
if (from)
|
|
q = q.where('date', '>=', from);
|
|
if (to)
|
|
q = q.where('date', '<=', to);
|
|
if (status)
|
|
q = q.where({ status });
|
|
// Escopo por sessão: slots do número + globais (instance_id NULL).
|
|
if (instanceId)
|
|
q = q.where((qb) => qb.whereNull('instance_id').orWhere('instance_id', instanceId));
|
|
res.json(await q);
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
router.post('/calendar', async (req, res) => {
|
|
try {
|
|
const { title, date, time_start, time_end, attendee_name, attendee_phone, notes, instance_id } = req.body;
|
|
const [slot] = await db('sec_calendar').insert({
|
|
id: uuid(), title, date, time_start, time_end, instance_id: instance_id ?? null,
|
|
attendee_name, attendee_phone, notes, status: 'available',
|
|
}).returning('*');
|
|
res.status(201).json(slot);
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
router.put('/calendar/:id', async (req, res) => {
|
|
try {
|
|
const { title, date, time_start, time_end, attendee_name, attendee_phone, status, notes, instance_id } = req.body;
|
|
const patch = { title, date, time_start, time_end, attendee_name, attendee_phone, status, notes, updated_at: new Date() };
|
|
if ('instance_id' in req.body)
|
|
patch.instance_id = instance_id ?? null;
|
|
const [slot] = await db('sec_calendar').where({ id: req.params.id })
|
|
.update(patch)
|
|
.returning('*');
|
|
res.json(slot);
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
router.delete('/calendar/:id', async (req, res) => {
|
|
try {
|
|
await db('sec_calendar').where({ id: req.params.id }).delete();
|
|
res.json({ ok: true });
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
// ── Numbers ───────────────────────────────────────────────────────────────
|
|
router.get('/numbers', async (_req, res) => {
|
|
try {
|
|
const numbers = await db('sec_numbers').orderBy('priority').orderBy('label');
|
|
res.json(numbers);
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
router.post('/numbers', async (req, res) => {
|
|
try {
|
|
const { instance_id, label, role, area, priority, notes, agent_id } = req.body;
|
|
if (!instance_id)
|
|
return res.status(400).json({ error: 'instance_id é obrigatório' });
|
|
// Upsert: se já existe um registro para esse instance_id, atualiza o role
|
|
const existing = await db('sec_numbers').where({ instance_id }).first();
|
|
if (existing) {
|
|
const patch = { label, role: role ?? 'clinic', area: area ?? null, priority: priority ?? 10, notes: notes ?? null, updated_at: new Date() };
|
|
if ('agent_id' in req.body)
|
|
patch.agent_id = agent_id ?? null;
|
|
const [num] = await db('sec_numbers').where({ instance_id }).update(patch).returning('*');
|
|
return res.json(num);
|
|
}
|
|
const [num] = await db('sec_numbers').insert({
|
|
id: uuid(), instance_id, label, role: role ?? 'clinic', agent_id: agent_id ?? null,
|
|
area: area ?? null, priority: priority ?? 10, active: true, notes: notes ?? null,
|
|
}).returning('*');
|
|
res.status(201).json(num);
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
router.put('/numbers/:id', async (req, res) => {
|
|
try {
|
|
const { label, role, area, instance_id, priority, active, notes, agent_id } = req.body;
|
|
const patch = { label, role, area, instance_id, priority, active, notes, updated_at: new Date() };
|
|
if ('agent_id' in req.body)
|
|
patch.agent_id = agent_id ?? null;
|
|
const [num] = await db('sec_numbers').where({ id: req.params.id })
|
|
.update(patch)
|
|
.returning('*');
|
|
res.json(num);
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
router.delete('/numbers/:id', async (req, res) => {
|
|
try {
|
|
await db('sec_numbers').where({ id: req.params.id }).delete();
|
|
res.json({ ok: true });
|
|
}
|
|
catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
return router;
|
|
}
|
|
//# sourceMappingURL=routes.js.map
|