280 lines
11 KiB
TypeScript
280 lines
11 KiB
TypeScript
import { create } from 'zustand'
|
|
import {
|
|
secAgentApi, secNodeApi, secConvApi, secCalApi, secNumberApi,
|
|
type SecAgent, type BrainNode, type SecConversation, type SecMessage, type CalendarSlot, type SecNumber,
|
|
} from '../services/secretariaApi'
|
|
|
|
interface SecretariaState {
|
|
// Data
|
|
agents: SecAgent[]
|
|
selectedAgent: SecAgent | null
|
|
nodes: BrainNode[]
|
|
conversations: SecConversation[]
|
|
selectedConversation: SecConversation | null
|
|
messages: SecMessage[]
|
|
calendarSlots: CalendarSlot[]
|
|
|
|
// Loading flags
|
|
loadingAgents: boolean
|
|
loadingNodes: boolean
|
|
loadingConversations: boolean
|
|
loadingMessages: boolean
|
|
sendingMessage: boolean
|
|
loadingCalendar: boolean
|
|
|
|
// ── Agents ────────────────────────────────────────────────────────────────
|
|
loadAgents: () => Promise<void>
|
|
selectAgent: (a: SecAgent | null) => void
|
|
createAgent: (d: Partial<SecAgent>) => Promise<SecAgent>
|
|
updateAgent: (id: string, d: Partial<SecAgent>) => Promise<void>
|
|
deleteAgent: (id: string) => Promise<void>
|
|
|
|
// ── Brain Nodes ───────────────────────────────────────────────────────────
|
|
loadNodes: (agentId: string) => Promise<void>
|
|
createNode: (agentId: string, d: Partial<BrainNode>) => Promise<void>
|
|
updateNode: (id: string, d: Partial<BrainNode>) => Promise<void>
|
|
deleteNode: (id: string) => Promise<void>
|
|
|
|
// ── Conversations ─────────────────────────────────────────────────────────
|
|
loadConversations: (agentId?: string) => Promise<void>
|
|
selectConversation: (c: SecConversation | null) => void
|
|
createConversation: (agentId: string, contactName: string) => Promise<SecConversation>
|
|
deleteConversation: (id: string) => Promise<void>
|
|
updateConversationStatus: (id: string, status: SecConversation['status']) => Promise<void>
|
|
|
|
// ── Messages ──────────────────────────────────────────────────────────────
|
|
loadMessages: (convId: string) => Promise<void>
|
|
sendMessage: (convId: string, text: string) => Promise<void>
|
|
|
|
// ── Calendar ──────────────────────────────────────────────────────────────
|
|
loadCalendar: (params?: { from?: string; to?: string; status?: string }) => Promise<void>
|
|
createCalendarSlot: (d: Partial<CalendarSlot>) => Promise<void>
|
|
updateCalendarSlot: (id: string, d: Partial<CalendarSlot>) => Promise<void>
|
|
deleteCalendarSlot: (id: string) => Promise<void>
|
|
|
|
// ── Numbers ───────────────────────────────────────────────────────────────
|
|
numbers: SecNumber[]
|
|
loadingNumbers: boolean
|
|
loadNumbers: () => Promise<void>
|
|
createNumber: (d: Partial<SecNumber>) => Promise<void>
|
|
updateNumber: (id: string, d: Partial<SecNumber>) => Promise<void>
|
|
deleteNumber: (id: string) => Promise<void>
|
|
}
|
|
|
|
export const useSecretariaStore = create<SecretariaState>((set, get) => ({
|
|
agents: [], selectedAgent: null, nodes: [],
|
|
conversations: [], selectedConversation: null, messages: [],
|
|
calendarSlots: [], numbers: [],
|
|
loadingAgents: false, loadingNodes: false, loadingConversations: false,
|
|
loadingMessages: false, sendingMessage: false, loadingCalendar: false, loadingNumbers: false,
|
|
|
|
// ── Agents ────────────────────────────────────────────────────────────────
|
|
|
|
loadAgents: async () => {
|
|
set({ loadingAgents: true })
|
|
try {
|
|
const agents = await secAgentApi.list()
|
|
set({ agents })
|
|
// Auto-seleciona o primeiro agente se nenhum selecionado
|
|
if (!get().selectedAgent && agents.length > 0) {
|
|
get().selectAgent(agents[0])
|
|
}
|
|
} finally {
|
|
set({ loadingAgents: false })
|
|
}
|
|
},
|
|
|
|
selectAgent: (a) => {
|
|
set({ selectedAgent: a, nodes: [], conversations: [], selectedConversation: null, messages: [] })
|
|
if (a) {
|
|
get().loadNodes(a.id)
|
|
get().loadConversations(a.id)
|
|
}
|
|
},
|
|
|
|
createAgent: async (d) => {
|
|
const agent = await secAgentApi.create(d)
|
|
set((s) => ({ agents: [...s.agents, agent] }))
|
|
return agent
|
|
},
|
|
|
|
updateAgent: async (id, d) => {
|
|
const updated = await secAgentApi.update(id, d)
|
|
set((s) => ({
|
|
agents: s.agents.map((a) => (a.id === id ? updated : a)),
|
|
selectedAgent: s.selectedAgent?.id === id ? updated : s.selectedAgent,
|
|
}))
|
|
},
|
|
|
|
deleteAgent: async (id) => {
|
|
await secAgentApi.delete(id)
|
|
set((s) => ({
|
|
agents: s.agents.filter((a) => a.id !== id),
|
|
selectedAgent: s.selectedAgent?.id === id ? null : s.selectedAgent,
|
|
}))
|
|
},
|
|
|
|
// ── Brain Nodes ───────────────────────────────────────────────────────────
|
|
|
|
loadNodes: async (agentId) => {
|
|
set({ loadingNodes: true })
|
|
try {
|
|
const nodes = await secNodeApi.list(agentId)
|
|
set({ nodes })
|
|
} finally {
|
|
set({ loadingNodes: false })
|
|
}
|
|
},
|
|
|
|
createNode: async (agentId, d) => {
|
|
const node = await secNodeApi.create(agentId, d)
|
|
set((s) => ({ nodes: [...s.nodes, node] }))
|
|
},
|
|
|
|
updateNode: async (id, d) => {
|
|
const updated = await secNodeApi.update(id, d)
|
|
set((s) => ({ nodes: s.nodes.map((n) => (n.id === id ? updated : n)) }))
|
|
},
|
|
|
|
deleteNode: async (id) => {
|
|
await secNodeApi.delete(id)
|
|
set((s) => ({ nodes: s.nodes.filter((n) => n.id !== id) }))
|
|
},
|
|
|
|
// ── Conversations ─────────────────────────────────────────────────────────
|
|
|
|
loadConversations: async (agentId) => {
|
|
set({ loadingConversations: true })
|
|
try {
|
|
const conversations = await secConvApi.list(agentId)
|
|
set({ conversations })
|
|
} finally {
|
|
set({ loadingConversations: false })
|
|
}
|
|
},
|
|
|
|
selectConversation: (c) => {
|
|
set({ selectedConversation: c, messages: [] })
|
|
if (c) get().loadMessages(c.id)
|
|
},
|
|
|
|
createConversation: async (agentId, contactName) => {
|
|
const conv = await secConvApi.create(agentId, contactName)
|
|
set((s) => ({ conversations: [conv, ...s.conversations] }))
|
|
return conv
|
|
},
|
|
|
|
deleteConversation: async (id) => {
|
|
await secConvApi.delete(id)
|
|
set((s) => ({
|
|
conversations: s.conversations.filter((c) => c.id !== id),
|
|
selectedConversation: s.selectedConversation?.id === id ? null : s.selectedConversation,
|
|
}))
|
|
},
|
|
|
|
updateConversationStatus: async (id, status) => {
|
|
const updated = await secConvApi.patch(id, { status })
|
|
set((s) => ({
|
|
conversations: s.conversations.map((c) => (c.id === id ? updated : c)),
|
|
selectedConversation: s.selectedConversation?.id === id ? updated : s.selectedConversation,
|
|
}))
|
|
},
|
|
|
|
// ── Messages ──────────────────────────────────────────────────────────────
|
|
|
|
loadMessages: async (convId) => {
|
|
set({ loadingMessages: true })
|
|
try {
|
|
const messages = await secConvApi.messages(convId)
|
|
set({ messages })
|
|
} finally {
|
|
set({ loadingMessages: false })
|
|
}
|
|
},
|
|
|
|
sendMessage: async (convId, text) => {
|
|
// Adiciona mensagem do usuário imediatamente (optimistic)
|
|
const tempId = `temp-${Date.now()}`
|
|
const userMsg: SecMessage = {
|
|
id: tempId, conversation_id: convId, role: 'user', content: text, created_at: new Date().toISOString(),
|
|
}
|
|
set((s) => ({ messages: [...s.messages, userMsg], sendingMessage: true }))
|
|
|
|
try {
|
|
const { reply } = await secConvApi.chat(convId, text)
|
|
const aiMsg: SecMessage = {
|
|
id: `ai-${Date.now()}`, conversation_id: convId, role: 'assistant', content: reply,
|
|
created_at: new Date().toISOString(),
|
|
}
|
|
set((s) => ({ messages: [...s.messages, aiMsg] }))
|
|
|
|
// Atualiza updated_at da conversa na lista
|
|
set((s) => ({
|
|
conversations: s.conversations.map((c) =>
|
|
c.id === convId ? { ...c, updated_at: new Date().toISOString() } : c,
|
|
),
|
|
}))
|
|
} finally {
|
|
set({ sendingMessage: false })
|
|
}
|
|
},
|
|
|
|
// ── Calendar ──────────────────────────────────────────────────────────────
|
|
|
|
loadCalendar: async (params) => {
|
|
set({ loadingCalendar: true })
|
|
try {
|
|
const calendarSlots = await secCalApi.list(params)
|
|
set({ calendarSlots })
|
|
} finally {
|
|
set({ loadingCalendar: false })
|
|
}
|
|
},
|
|
|
|
createCalendarSlot: async (d) => {
|
|
const slot = await secCalApi.create(d)
|
|
set((s) => ({ calendarSlots: [...s.calendarSlots, slot].sort((a, b) => a.date.localeCompare(b.date)) }))
|
|
},
|
|
|
|
updateCalendarSlot: async (id, d) => {
|
|
const updated = await secCalApi.update(id, d)
|
|
set((s) => ({ calendarSlots: s.calendarSlots.map((sl) => (sl.id === id ? updated : sl)) }))
|
|
},
|
|
|
|
deleteCalendarSlot: async (id) => {
|
|
await secCalApi.delete(id)
|
|
set((s) => ({ calendarSlots: s.calendarSlots.filter((sl) => sl.id !== id) }))
|
|
},
|
|
|
|
// ── Numbers ───────────────────────────────────────────────────────────────
|
|
|
|
loadNumbers: async () => {
|
|
set({ loadingNumbers: true })
|
|
try {
|
|
const numbers = await secNumberApi.list()
|
|
set({ numbers })
|
|
} finally {
|
|
set({ loadingNumbers: false })
|
|
}
|
|
},
|
|
|
|
createNumber: async (d) => {
|
|
const num = await secNumberApi.assign(d)
|
|
set((s) => ({
|
|
// upsert: substitui se já existia o mesmo instance_id
|
|
numbers: [...s.numbers.filter((n) => n.instance_id !== num.instance_id), num]
|
|
.sort((a, b) => a.priority - b.priority),
|
|
}))
|
|
},
|
|
|
|
updateNumber: async (id, d) => {
|
|
const updated = await secNumberApi.update(id, d)
|
|
set((s) => ({ numbers: s.numbers.map((n) => (n.id === id ? updated : n)).sort((a, b) => a.priority - b.priority) }))
|
|
},
|
|
|
|
deleteNumber: async (id) => {
|
|
await secNumberApi.delete(id)
|
|
set((s) => ({ numbers: s.numbers.filter((n) => n.id !== id) }))
|
|
},
|
|
}))
|