feat(ext-api): API REST /secretaria/* (agents/nodes/calendar/numbers/conversations)
Expõe o contrato de administração esperado pelo painel da Secretária no satélite (antes só havia /secretaria/ask|numbers + /sec/* parcial → 404 em agents/calendar): - agents: GET/POST/PUT/DELETE - brain nodes: GET/POST por agente (/agents/:agentId/nodes) + PUT/DELETE (/nodes/:id) - calendar: GET/POST/PUT/DELETE - numbers: POST/PUT/DELETE (GET já existia) - conversations (teste): list/create/patch/delete, /messages, /chat (brain.chat), /finalize (brain.finalizeProtocol) Backed pelas tabelas sec_* (globais/single-tenant no motor). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -889,6 +889,268 @@ function buildExtRoutes(prisma, manager, db, config, hooks, io) {
|
|||||||
res.status(500).json({ error: err.message });
|
res.status(500).json({ error: err.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
// Secretária — API REST de administração (/secretaria/*) do painel do satélite.
|
||||||
|
// As tabelas sec_* são GLOBAIS no motor (single-tenant); estes endpoints
|
||||||
|
// expõem o contrato completo (agents/nodes/calendar/numbers/conversations)
|
||||||
|
// esperado pelo frontend, reaproveitando a lógica dos /sec/*.
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
// ── Agents ──────────────────────────────────────────────────────────────────
|
||||||
|
router.get('/secretaria/agents', async (_req, res) => {
|
||||||
|
try {
|
||||||
|
res.json(await db('sec_agents').orderBy('created_at'));
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
router.post('/secretaria/agents', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const b = req.body ?? {};
|
||||||
|
const [a] = await db('sec_agents').insert({
|
||||||
|
id: uuid(), name: b.name ?? 'Agente', description: b.description ?? null,
|
||||||
|
model: b.model ?? 'gemini-2.0-flash', provider: b.provider ?? 'gemini',
|
||||||
|
temperature: b.temperature ?? 0.7, context_window: b.context_window ?? 10, active: true,
|
||||||
|
}).returning('*');
|
||||||
|
res.status(201).json(a);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
router.put('/secretaria/agents/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const b = req.body ?? {}, patch = { updated_at: new Date() };
|
||||||
|
for (const k of ['name', 'description', 'model', 'provider', 'temperature', 'context_window', 'active'])
|
||||||
|
if (k in b)
|
||||||
|
patch[k] = b[k];
|
||||||
|
const [a] = await db('sec_agents').where({ id: req.params['id'] }).update(patch).returning('*');
|
||||||
|
res.json(a);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
router.delete('/secretaria/agents/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const id = req.params['id'];
|
||||||
|
await db('sec_brain_nodes').where({ agent_id: id }).delete();
|
||||||
|
await db('sec_agents').where({ id }).delete();
|
||||||
|
res.json({ ok: true });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// ── Brain nodes (por agente) ──────────────────────────────────────────────
|
||||||
|
router.get('/secretaria/agents/:agentId/nodes', async (req, res) => {
|
||||||
|
try {
|
||||||
|
res.json(await db('sec_brain_nodes').where({ agent_id: req.params['agentId'] }).orderBy('sort_order'));
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
router.post('/secretaria/agents/:agentId/nodes', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const b = req.body ?? {};
|
||||||
|
const [n] = await db('sec_brain_nodes').insert({
|
||||||
|
id: uuid(), agent_id: req.params['agentId'], type: b.type, title: b.title ?? '', content: b.content ?? '',
|
||||||
|
node_model: b.node_model ?? null, active: true, sort_order: b.sort_order ?? 99,
|
||||||
|
}).returning('*');
|
||||||
|
res.status(201).json(n);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
router.put('/secretaria/nodes/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const b = req.body ?? {}, patch = { updated_at: new Date() };
|
||||||
|
for (const k of ['type', 'title', 'content', 'node_model', 'active', 'sort_order'])
|
||||||
|
if (k in b)
|
||||||
|
patch[k] = b[k];
|
||||||
|
const [n] = await db('sec_brain_nodes').where({ id: req.params['id'] }).update(patch).returning('*');
|
||||||
|
res.json(n);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
router.delete('/secretaria/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 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// ── Calendar ──────────────────────────────────────────────────────────────
|
||||||
|
router.get('/secretaria/calendar', async (_req, res) => {
|
||||||
|
try {
|
||||||
|
res.json(await db('sec_calendar').orderBy('date').orderBy('time_start'));
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
router.post('/secretaria/calendar', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const b = req.body ?? {};
|
||||||
|
const [s] = await db('sec_calendar').insert({
|
||||||
|
id: uuid(), title: b.title ?? null, date: b.date, time_start: b.time_start, time_end: b.time_end,
|
||||||
|
attendee_name: b.attendee_name ?? null, attendee_phone: b.attendee_phone ?? null,
|
||||||
|
status: b.status ?? 'available', notes: b.notes ?? null,
|
||||||
|
}).returning('*');
|
||||||
|
res.status(201).json(s);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
router.put('/secretaria/calendar/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const b = req.body ?? {}, patch = { updated_at: new Date() };
|
||||||
|
for (const k of ['title', 'date', 'time_start', 'time_end', 'attendee_name', 'attendee_phone', 'status', 'notes'])
|
||||||
|
if (k in b)
|
||||||
|
patch[k] = b[k];
|
||||||
|
const [s] = await db('sec_calendar').where({ id: req.params['id'] }).update(patch).returning('*');
|
||||||
|
res.json(s);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
router.delete('/secretaria/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 (GET acima) — mutações ─────────────────────────────────────────
|
||||||
|
router.post('/secretaria/numbers', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const b = req.body ?? {};
|
||||||
|
const [n] = await db('sec_numbers').insert({
|
||||||
|
id: uuid(), instance_id: b.instance_id ?? null, phone: b.phone ?? null, label: b.label ?? null,
|
||||||
|
role: b.role ?? null, area: b.area ?? null, priority: b.priority ?? 0, active: b.active ?? true, notes: b.notes ?? null,
|
||||||
|
}).returning('*');
|
||||||
|
res.status(201).json(n);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
router.put('/secretaria/numbers/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const b = req.body ?? {}, patch = { updated_at: new Date() };
|
||||||
|
for (const k of ['instance_id', 'phone', 'label', 'role', 'area', 'priority', 'active', 'notes'])
|
||||||
|
if (k in b)
|
||||||
|
patch[k] = b[k];
|
||||||
|
const [n] = await db('sec_numbers').where({ id: req.params['id'] }).update(patch).returning('*');
|
||||||
|
res.json(n);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
router.delete('/secretaria/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 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// ── Conversations (teste do painel) ────────────────────────────────────────
|
||||||
|
router.get('/secretaria/conversations', async (req, res) => {
|
||||||
|
try {
|
||||||
|
let q = db('sec_conversations')
|
||||||
|
.select('id', 'agent_id', 'protocol_number', 'contact_name', 'status', 'handoff_mode', 'summary', 'created_at', 'updated_at')
|
||||||
|
.orderBy('updated_at', 'desc').limit(100);
|
||||||
|
const agentId = req.query['agent_id'];
|
||||||
|
if (agentId)
|
||||||
|
q = q.where({ agent_id: agentId });
|
||||||
|
res.json(await q);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
router.post('/secretaria/conversations', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const b = req.body ?? {};
|
||||||
|
const [c] = await db('sec_conversations').insert({
|
||||||
|
id: uuid(), agent_id: b.agent_id, contact_name: b.contact_name ?? 'Teste',
|
||||||
|
protocol_number: brain_1.ProtocolEngine.generateProtocolNumber(), status: 'active', handoff_mode: 'ia',
|
||||||
|
}).returning('*');
|
||||||
|
res.status(201).json(c);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
router.patch('/secretaria/conversations/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const b = req.body ?? {}, patch = { updated_at: new Date() };
|
||||||
|
for (const k of ['status', 'contact_name', 'handoff_mode', 'summary'])
|
||||||
|
if (k in b)
|
||||||
|
patch[k] = b[k];
|
||||||
|
const [c] = await db('sec_conversations').where({ id: req.params['id'] }).update(patch).returning('*');
|
||||||
|
res.json(c);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
router.delete('/secretaria/conversations/:id', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const id = req.params['id'];
|
||||||
|
await db('sec_messages').where({ conversation_id: id }).delete();
|
||||||
|
await db('sec_conversations').where({ id }).delete();
|
||||||
|
res.json({ ok: true });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
router.get('/secretaria/conversations/:id/messages', async (req, res) => {
|
||||||
|
try {
|
||||||
|
res.json(await db('sec_messages').where({ conversation_id: req.params['id'] }).orderBy('created_at'));
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
router.post('/secretaria/conversations/:id/chat', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const message = String((req.body ?? {}).message ?? '').trim();
|
||||||
|
if (!message) {
|
||||||
|
res.status(400).json({ error: 'message obrigatório' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const brain = new brain_1.ProtocolEngine(db, config);
|
||||||
|
const reply = await brain.chat(req.params['id'], message, { tenantId: req.extTenantId, hooks });
|
||||||
|
res.json({ reply });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
router.post('/secretaria/conversations/:id/finalize', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const brain = new brain_1.ProtocolEngine(db, config);
|
||||||
|
res.json(await brain.finalizeProtocol(req.params['id']));
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
// ── GET /sec/handoff/:chatId ──────────────────────────────────────────────
|
// ── GET /sec/handoff/:chatId ──────────────────────────────────────────────
|
||||||
// Retorna o modo de handoff atual para um chat específico.
|
// Retorna o modo de handoff atual para um chat específico.
|
||||||
router.get('/sec/handoff/:chatId', async (req, res) => {
|
router.get('/sec/handoff/:chatId', async (req, res) => {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -935,6 +935,184 @@ export function buildExtRoutes(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
// Secretária — API REST de administração (/secretaria/*) do painel do satélite.
|
||||||
|
// As tabelas sec_* são GLOBAIS no motor (single-tenant); estes endpoints
|
||||||
|
// expõem o contrato completo (agents/nodes/calendar/numbers/conversations)
|
||||||
|
// esperado pelo frontend, reaproveitando a lógica dos /sec/*.
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
// ── Agents ──────────────────────────────────────────────────────────────────
|
||||||
|
router.get('/secretaria/agents', async (_req: Request, res: Response) => {
|
||||||
|
try { res.json(await db('sec_agents').orderBy('created_at')) }
|
||||||
|
catch (err: any) { res.status(500).json({ error: err.message }) }
|
||||||
|
})
|
||||||
|
router.post('/secretaria/agents', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const b = req.body ?? {}
|
||||||
|
const [a] = await db('sec_agents').insert({
|
||||||
|
id: uuid(), name: b.name ?? 'Agente', description: b.description ?? null,
|
||||||
|
model: b.model ?? 'gemini-2.0-flash', provider: b.provider ?? 'gemini',
|
||||||
|
temperature: b.temperature ?? 0.7, context_window: b.context_window ?? 10, active: true,
|
||||||
|
}).returning('*')
|
||||||
|
res.status(201).json(a)
|
||||||
|
} catch (err: any) { res.status(500).json({ error: err.message }) }
|
||||||
|
})
|
||||||
|
router.put('/secretaria/agents/:id', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const b = req.body ?? {}, patch: any = { updated_at: new Date() }
|
||||||
|
for (const k of ['name', 'description', 'model', 'provider', 'temperature', 'context_window', 'active']) if (k in b) patch[k] = b[k]
|
||||||
|
const [a] = await db('sec_agents').where({ id: req.params['id'] }).update(patch).returning('*')
|
||||||
|
res.json(a)
|
||||||
|
} catch (err: any) { res.status(500).json({ error: err.message }) }
|
||||||
|
})
|
||||||
|
router.delete('/secretaria/agents/:id', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const id = req.params['id']
|
||||||
|
await db('sec_brain_nodes').where({ agent_id: id }).delete()
|
||||||
|
await db('sec_agents').where({ id }).delete()
|
||||||
|
res.json({ ok: true })
|
||||||
|
} catch (err: any) { res.status(500).json({ error: err.message }) }
|
||||||
|
})
|
||||||
|
|
||||||
|
// ── Brain nodes (por agente) ──────────────────────────────────────────────
|
||||||
|
router.get('/secretaria/agents/:agentId/nodes', async (req: Request, res: Response) => {
|
||||||
|
try { res.json(await db('sec_brain_nodes').where({ agent_id: req.params['agentId'] }).orderBy('sort_order')) }
|
||||||
|
catch (err: any) { res.status(500).json({ error: err.message }) }
|
||||||
|
})
|
||||||
|
router.post('/secretaria/agents/:agentId/nodes', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const b = req.body ?? {}
|
||||||
|
const [n] = await db('sec_brain_nodes').insert({
|
||||||
|
id: uuid(), agent_id: req.params['agentId'], type: b.type, title: b.title ?? '', content: b.content ?? '',
|
||||||
|
node_model: b.node_model ?? null, active: true, sort_order: b.sort_order ?? 99,
|
||||||
|
}).returning('*')
|
||||||
|
res.status(201).json(n)
|
||||||
|
} catch (err: any) { res.status(500).json({ error: err.message }) }
|
||||||
|
})
|
||||||
|
router.put('/secretaria/nodes/:id', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const b = req.body ?? {}, patch: any = { updated_at: new Date() }
|
||||||
|
for (const k of ['type', 'title', 'content', 'node_model', 'active', 'sort_order']) if (k in b) patch[k] = b[k]
|
||||||
|
const [n] = await db('sec_brain_nodes').where({ id: req.params['id'] }).update(patch).returning('*')
|
||||||
|
res.json(n)
|
||||||
|
} catch (err: any) { res.status(500).json({ error: err.message }) }
|
||||||
|
})
|
||||||
|
router.delete('/secretaria/nodes/:id', async (req: Request, res: Response) => {
|
||||||
|
try { await db('sec_brain_nodes').where({ id: req.params['id'] }).delete(); res.json({ ok: true }) }
|
||||||
|
catch (err: any) { res.status(500).json({ error: err.message }) }
|
||||||
|
})
|
||||||
|
|
||||||
|
// ── Calendar ──────────────────────────────────────────────────────────────
|
||||||
|
router.get('/secretaria/calendar', async (_req: Request, res: Response) => {
|
||||||
|
try { res.json(await db('sec_calendar').orderBy('date').orderBy('time_start')) }
|
||||||
|
catch (err: any) { res.status(500).json({ error: err.message }) }
|
||||||
|
})
|
||||||
|
router.post('/secretaria/calendar', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const b = req.body ?? {}
|
||||||
|
const [s] = await db('sec_calendar').insert({
|
||||||
|
id: uuid(), title: b.title ?? null, date: b.date, time_start: b.time_start, time_end: b.time_end,
|
||||||
|
attendee_name: b.attendee_name ?? null, attendee_phone: b.attendee_phone ?? null,
|
||||||
|
status: b.status ?? 'available', notes: b.notes ?? null,
|
||||||
|
}).returning('*')
|
||||||
|
res.status(201).json(s)
|
||||||
|
} catch (err: any) { res.status(500).json({ error: err.message }) }
|
||||||
|
})
|
||||||
|
router.put('/secretaria/calendar/:id', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const b = req.body ?? {}, patch: any = { updated_at: new Date() }
|
||||||
|
for (const k of ['title', 'date', 'time_start', 'time_end', 'attendee_name', 'attendee_phone', 'status', 'notes']) if (k in b) patch[k] = b[k]
|
||||||
|
const [s] = await db('sec_calendar').where({ id: req.params['id'] }).update(patch).returning('*')
|
||||||
|
res.json(s)
|
||||||
|
} catch (err: any) { res.status(500).json({ error: err.message }) }
|
||||||
|
})
|
||||||
|
router.delete('/secretaria/calendar/:id', async (req: Request, res: Response) => {
|
||||||
|
try { await db('sec_calendar').where({ id: req.params['id'] }).delete(); res.json({ ok: true }) }
|
||||||
|
catch (err: any) { res.status(500).json({ error: err.message }) }
|
||||||
|
})
|
||||||
|
|
||||||
|
// ── Numbers (GET acima) — mutações ─────────────────────────────────────────
|
||||||
|
router.post('/secretaria/numbers', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const b = req.body ?? {}
|
||||||
|
const [n] = await db('sec_numbers').insert({
|
||||||
|
id: uuid(), instance_id: b.instance_id ?? null, phone: b.phone ?? null, label: b.label ?? null,
|
||||||
|
role: b.role ?? null, area: b.area ?? null, priority: b.priority ?? 0, active: b.active ?? true, notes: b.notes ?? null,
|
||||||
|
}).returning('*')
|
||||||
|
res.status(201).json(n)
|
||||||
|
} catch (err: any) { res.status(500).json({ error: err.message }) }
|
||||||
|
})
|
||||||
|
router.put('/secretaria/numbers/:id', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const b = req.body ?? {}, patch: any = { updated_at: new Date() }
|
||||||
|
for (const k of ['instance_id', 'phone', 'label', 'role', 'area', 'priority', 'active', 'notes']) if (k in b) patch[k] = b[k]
|
||||||
|
const [n] = await db('sec_numbers').where({ id: req.params['id'] }).update(patch).returning('*')
|
||||||
|
res.json(n)
|
||||||
|
} catch (err: any) { res.status(500).json({ error: err.message }) }
|
||||||
|
})
|
||||||
|
router.delete('/secretaria/numbers/:id', async (req: Request, res: Response) => {
|
||||||
|
try { await db('sec_numbers').where({ id: req.params['id'] }).delete(); res.json({ ok: true }) }
|
||||||
|
catch (err: any) { res.status(500).json({ error: err.message }) }
|
||||||
|
})
|
||||||
|
|
||||||
|
// ── Conversations (teste do painel) ────────────────────────────────────────
|
||||||
|
router.get('/secretaria/conversations', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
let q = db('sec_conversations')
|
||||||
|
.select('id', 'agent_id', 'protocol_number', 'contact_name', 'status', 'handoff_mode', 'summary', 'created_at', 'updated_at')
|
||||||
|
.orderBy('updated_at', 'desc').limit(100)
|
||||||
|
const agentId = req.query['agent_id'] as string | undefined
|
||||||
|
if (agentId) q = q.where({ agent_id: agentId })
|
||||||
|
res.json(await q)
|
||||||
|
} catch (err: any) { res.status(500).json({ error: err.message }) }
|
||||||
|
})
|
||||||
|
router.post('/secretaria/conversations', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const b = req.body ?? {}
|
||||||
|
const [c] = await db('sec_conversations').insert({
|
||||||
|
id: uuid(), agent_id: b.agent_id, contact_name: b.contact_name ?? 'Teste',
|
||||||
|
protocol_number: ProtocolEngine.generateProtocolNumber(), status: 'active', handoff_mode: 'ia',
|
||||||
|
}).returning('*')
|
||||||
|
res.status(201).json(c)
|
||||||
|
} catch (err: any) { res.status(500).json({ error: err.message }) }
|
||||||
|
})
|
||||||
|
router.patch('/secretaria/conversations/:id', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const b = req.body ?? {}, patch: any = { updated_at: new Date() }
|
||||||
|
for (const k of ['status', 'contact_name', 'handoff_mode', 'summary']) if (k in b) patch[k] = b[k]
|
||||||
|
const [c] = await db('sec_conversations').where({ id: req.params['id'] }).update(patch).returning('*')
|
||||||
|
res.json(c)
|
||||||
|
} catch (err: any) { res.status(500).json({ error: err.message }) }
|
||||||
|
})
|
||||||
|
router.delete('/secretaria/conversations/:id', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const id = req.params['id']
|
||||||
|
await db('sec_messages').where({ conversation_id: id }).delete()
|
||||||
|
await db('sec_conversations').where({ id }).delete()
|
||||||
|
res.json({ ok: true })
|
||||||
|
} catch (err: any) { res.status(500).json({ error: err.message }) }
|
||||||
|
})
|
||||||
|
router.get('/secretaria/conversations/:id/messages', async (req: Request, res: Response) => {
|
||||||
|
try { res.json(await db('sec_messages').where({ conversation_id: req.params['id'] }).orderBy('created_at')) }
|
||||||
|
catch (err: any) { res.status(500).json({ error: err.message }) }
|
||||||
|
})
|
||||||
|
router.post('/secretaria/conversations/:id/chat', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const message = String((req.body ?? {}).message ?? '').trim()
|
||||||
|
if (!message) { res.status(400).json({ error: 'message obrigatório' }); return }
|
||||||
|
const brain = new ProtocolEngine(db, config)
|
||||||
|
const reply = await brain.chat(req.params['id']!, message, { tenantId: req.extTenantId, hooks })
|
||||||
|
res.json({ reply })
|
||||||
|
} catch (err: any) { res.status(500).json({ error: err.message }) }
|
||||||
|
})
|
||||||
|
router.post('/secretaria/conversations/:id/finalize', async (req: Request, res: Response) => {
|
||||||
|
try {
|
||||||
|
const brain = new ProtocolEngine(db, config)
|
||||||
|
res.json(await brain.finalizeProtocol(req.params['id']!))
|
||||||
|
} catch (err: any) { res.status(500).json({ error: err.message }) }
|
||||||
|
})
|
||||||
|
|
||||||
// ── GET /sec/handoff/:chatId ──────────────────────────────────────────────
|
// ── GET /sec/handoff/:chatId ──────────────────────────────────────────────
|
||||||
// Retorna o modo de handoff atual para um chat específico.
|
// Retorna o modo de handoff atual para um chat específico.
|
||||||
router.get('/sec/handoff/:chatId', async (req: Request, res: Response) => {
|
router.get('/sec/handoff/:chatId', async (req: Request, res: Response) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user