Files
scoreodonto.com/frontend/views/newwhats/services/secretariaApi.ts
T
VPS 4 Builder cfeac891fb feat(wa-inbox): botão liga/desliga da Secretária por sessão na thin sidebar
Botão Power na thin sidebar do /wa-inbox age SÓ na sessão/número aberto (verde=on,
vermelho pulsante=off). Abre modal com textarea de motivo (obrigatório p/ desligar),
histórico de quem ligou/desligou e aviso de rate-limit (1x/hora). secSessionPowerApi
+ SessaoSecretariaPower.tsx.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 19:39:41 +02:00

188 lines
8.0 KiB
TypeScript

// Satélite: `api` axios-like sobre o nwClient. As rotas internas da secretária
// foram montadas sob a ext em /api/ext/v1/secretaria (proxy /api/nw/v1/secretaria).
import { nw } from './nwClient'
const _qs = (params?: Record<string, any>) => {
if (!params) return ''
const p = new URLSearchParams()
Object.entries(params).forEach(([k, v]) => { if (v != null) p.set(k, String(v)) })
const s = p.toString()
return s ? `?${s}` : ''
}
const api = {
get: <T = any>(u: string, cfg?: { params?: Record<string, any> }) => nw.get(u + _qs(cfg?.params)).then((data: any) => ({ data: data as T })),
post: <T = any>(u: string, body?: any) => nw.post(u, body).then((data: any) => ({ data: data as T })),
put: <T = any>(u: string, body?: any) => nw.put(u, body).then((data: any) => ({ data: data as T })),
patch: <T = any>(u: string, body?: any) => nw.patch(u, body).then((data: any) => ({ data: data as T })),
delete:<T = any>(u: string) => nw.delete(u).then((data: any) => ({ data: data as T })),
}
const BASE = '/secretaria'
// ─── Types ────────────────────────────────────────────────────────────────────
export interface SecAgent {
id: string
name: string
description: string | null
model: string
provider: string
temperature: number
context_window: number
active: boolean
created_at: string
updated_at: string
}
export type BrainNodeType = 'persona' | 'knowledge' | 'rules' | 'calendar' | 'escalation'
export interface BrainNode {
id: string
agent_id: string
type: BrainNodeType
title: string
content: string
active: boolean
sort_order: number
node_model: string | null // modelo específico para este nó (opcional)
created_at: string
updated_at: string
}
export interface SecConversation {
id: string
agent_id: string
contact_name: string
protocol_number: string
status: 'active' | 'closed' | 'escalated'
summary: string | null
created_at: string
updated_at: string
}
export interface SecMessage {
id: string
conversation_id: string
role: 'user' | 'assistant' | 'system'
content: string
created_at: string
}
export interface CalendarSlot {
id: string
title: string
date: string
time_start: string
time_end: string
instance_id?: string | null // número/sessão dona do slot (null = global)
attendee_name: string | null
attendee_phone: string | null
status: 'available' | 'booked' | 'cancelled'
notes: string | null
created_at: string
}
// ─── Power (liga/desliga global — kill-switch auto_reply) ──────────────────────
export const secPowerApi = {
get: () => api.get<{ enabled: boolean }>(`${BASE}/power`).then((r) => r.data),
set: (enabled: boolean) => api.post<{ enabled: boolean }>(`${BASE}/power`, { enabled }).then((r) => r.data),
}
// Liga/desliga a Secretária POR SESSÃO (instância atual do /wa-inbox).
export interface SessionPower {
enabled: boolean
reason: string | null
changed_by: string | null
changed_at: string | null
can_toggle: boolean
next_toggle_at: string | null
history: { enabled: boolean; reason: string | null; by: string | null; at: string }[]
}
export const secSessionPowerApi = {
get: (instanceId: string) => api.get<SessionPower>(`${BASE}/session-power`, { params: { instance_id: instanceId } }).then((r) => r.data),
set: (instanceId: string, enabled: boolean, reason: string, by: string) =>
api.post<{ ok: boolean; enabled: boolean }>(`${BASE}/session-power`, { instance_id: instanceId, enabled, reason, by }).then((r) => r.data),
}
// ─── Agents ───────────────────────────────────────────────────────────────────
export const secAgentApi = {
list: () => api.get<SecAgent[]>(`${BASE}/agents`).then((r) => r.data),
create: (d: Partial<SecAgent>) => api.post<SecAgent>(`${BASE}/agents`, d).then((r) => r.data),
update: (id: string, d: Partial<SecAgent>) => api.put<SecAgent>(`${BASE}/agents/${id}`, d).then((r) => r.data),
delete: (id: string) => api.delete(`${BASE}/agents/${id}`).then((r) => r.data),
}
// ─── Brain Nodes ──────────────────────────────────────────────────────────────
export const secNodeApi = {
list: (agentId: string) => api.get<BrainNode[]>(`${BASE}/agents/${agentId}/nodes`).then((r) => r.data),
create: (agentId: string, d: Partial<BrainNode>) =>
api.post<BrainNode>(`${BASE}/agents/${agentId}/nodes`, d).then((r) => r.data),
update: (id: string, d: Partial<BrainNode>) => api.put<BrainNode>(`${BASE}/nodes/${id}`, d).then((r) => r.data),
delete: (id: string) => api.delete(`${BASE}/nodes/${id}`).then((r) => r.data),
}
// ─── Conversations ────────────────────────────────────────────────────────────
export const secConvApi = {
list: (agentId?: string) =>
api.get<SecConversation[]>(`${BASE}/conversations`, { params: agentId ? { agent_id: agentId } : {} }).then((r) => r.data),
create: (agentId: string, contactName: string) =>
api.post<SecConversation>(`${BASE}/conversations`, { agent_id: agentId, contact_name: contactName }).then((r) => r.data),
patch: (id: string, d: Partial<SecConversation>) =>
api.patch<SecConversation>(`${BASE}/conversations/${id}`, d).then((r) => r.data),
delete: (id: string) => api.delete(`${BASE}/conversations/${id}`).then((r) => r.data),
messages: (id: string) => api.get<SecMessage[]>(`${BASE}/conversations/${id}/messages`).then((r) => r.data),
chat: (id: string, message: string) =>
api.post<{ reply: string }>(`${BASE}/conversations/${id}/chat`, { message }).then((r) => r.data),
finalize: (id: string) =>
api.post<{ summary: string; protocol_number: string }>(`${BASE}/conversations/${id}/finalize`).then((r) => r.data),
}
// ─── Numbers ──────────────────────────────────────────────────────────────────
export type NumberRole =
| 'secretary_virtual'
| 'clinic'
| 'doctor'
| 'specialist'
| 'manager'
| 'reserve'
| 'human_secretary'
export interface SecNumber {
id: string
instance_id: string
clinica_id?: string | null
agent_id: string | null // agente/cérebro deste número (separação por sessão)
label: string
role: NumberRole
area: string | null
priority: number
active: boolean
notes: string | null
created_at: string
updated_at: string
}
export const secNumberApi = {
list: () => api.get<SecNumber[]>(`${BASE}/numbers`).then((r) => r.data),
// upsert por instance_id — cria ou atualiza o papel da instância
assign: (d: Partial<SecNumber>) => api.post<SecNumber>(`${BASE}/numbers`, d).then((r) => r.data),
update: (id: string, d: Partial<SecNumber>) => api.put<SecNumber>(`${BASE}/numbers/${id}`, d).then((r) => r.data),
delete: (id: string) => api.delete(`${BASE}/numbers/${id}`).then((r) => r.data),
}
// ─── Calendar ─────────────────────────────────────────────────────────────────
export const secCalApi = {
list: (params?: { from?: string; to?: string; status?: string; instance_id?: string }) =>
api.get<CalendarSlot[]>(`${BASE}/calendar`, { params }).then((r) => r.data),
create: (d: Partial<CalendarSlot>) => api.post<CalendarSlot>(`${BASE}/calendar`, d).then((r) => r.data),
update: (id: string, d: Partial<CalendarSlot>) => api.put<CalendarSlot>(`${BASE}/calendar/${id}`, d).then((r) => r.data),
delete: (id: string) => api.delete(`${BASE}/calendar/${id}`).then((r) => r.data),
}