import React, { useState, useEffect, useRef, useCallback } from 'react' import { motion, AnimatePresence } from 'framer-motion' import { format, parseISO } from 'date-fns' import { ptBR } from 'date-fns/locale' import { MessageSquare, Brain, CalendarDays, Bot, Plus, Trash2, Pencil, ChevronDown, Check, Send, X, Loader2, ToggleLeft, ToggleRight, Sparkles, BookOpen, ShieldCheck, Calendar, AlertTriangle, CheckCircle, Clock, User, RefreshCw, MoreVertical, Power, Cpu, Phone, Shield, Star, Repeat, UserCheck, Building2, Stethoscope, Maximize2, Save, HelpCircle, } from 'lucide-react' import { useSecretariaStore } from '../../store/secretariaStore' import type { SecAgent, BrainNode, SecConversation, BrainNodeType, CalendarSlot, SecNumber, NumberRole } from '../../services/secretariaApi' // ─── Thin Nav ───────────────────────────────────────────────────────────────── type NavTab = 'conversations' | 'brain' | 'calendar' | 'agents' | 'numbers' const NAV_ITEMS: { id: NavTab; icon: React.ElementType; label: string }[] = [ { id: 'conversations', icon: MessageSquare, label: 'Conversas' }, { id: 'brain', icon: Brain, label: 'Cérebro' }, { id: 'calendar', icon: CalendarDays, label: 'Agenda' }, { id: 'agents', icon: Bot, label: 'Agentes' }, { id: 'numbers', icon: Phone, label: 'Números' }, ] function ThinNav({ active, onChange, onHelp }: { active: NavTab; onChange: (t: NavTab) => void; onHelp: () => void }) { return (
{NAV_ITEMS.map(({ id, icon: Icon, label }) => ( ))}
{/* Ajuda — modelos de como usar e configurar a secretária */}
) } // ─── Agent Dropdown ─────────────────────────────────────────────────────────── function AgentDropdown({ agents, selected, onChange, }: { agents: SecAgent[]; selected: SecAgent | null; onChange: (a: SecAgent) => void }) { const [open, setOpen] = useState(false) const ref = useRef(null) useEffect(() => { const h = (e: MouseEvent) => { if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false) } document.addEventListener('mousedown', h) return () => document.removeEventListener('mousedown', h) }, []) return (
{open && ( {agents.map((a) => ( ))} )}
) } // ─── Conversation List ──────────────────────────────────────────────────────── const STATUS_META = { active: { label: 'Ativa', color: 'text-green-400', dot: 'bg-green-400' }, closed: { label: 'Encerrada', color: 'text-slate-500', dot: 'bg-slate-600' }, escalated: { label: 'Escalada', color: 'text-yellow-400', dot: 'bg-yellow-400' }, } function ConversationList({ conversations, selected, onSelect, onCreate, onDelete, loadingConversations, selectedAgent, }: { conversations: SecConversation[] selected: SecConversation | null onSelect: (c: SecConversation) => void onCreate: () => void onDelete: (id: string) => void loadingConversations: boolean selectedAgent: SecAgent | null }) { return (
Sessões de Teste
{loadingConversations ? (
) : conversations.length === 0 ? (

{selectedAgent ? 'Nenhuma sessão. Clique em + para iniciar.' : 'Selecione um agente.'}

) : (
{conversations.map((c) => { const m = STATUS_META[c.status] const isActive = c.id === selected?.id return (
onSelect(c)} >

{c.contact_name}

{m.label} {format(parseISO(c.updated_at), "dd/MM HH:mm", { locale: ptBR })}
) })}
)}
) } // ─── Modelos por provider ───────────────────────────────────────────────────── const MODELS_BY_PROVIDER: Record = { openai: [ { label: 'GPT-4o', value: 'gpt-4o' }, { label: 'GPT-4o Mini', value: 'gpt-4o-mini' }, { label: 'GPT-3.5 Turbo', value: 'gpt-3.5-turbo' }, ], anthropic: [ { label: 'Claude Sonnet 4.5', value: 'claude-sonnet-4-5' }, { label: 'Claude 3.5 Haiku', value: 'claude-3-5-haiku-20241022' }, { label: 'Claude 3.5 Sonnet', value: 'claude-3-5-sonnet-20241022' }, ], gemini: [ { label: 'Gemini 2.0 Flash', value: 'gemini-2.0-flash' }, { label: 'Gemini 2.0 Flash Lite', value: 'gemini-2.0-flash-lite' }, { label: 'Gemini 2.5 Pro', value: 'gemini-2.5-pro-preview-03-25' }, { label: 'Gemini 1.5 Pro', value: 'gemini-1.5-pro-latest' }, ], ollama: [ { label: 'Llama 3.1', value: 'llama3.1' }, { label: 'Mistral', value: 'mistral' }, { label: 'Gemma 2', value: 'gemma2' }, { label: 'Qwen 2.5', value: 'qwen2.5' }, ], } const ALL_MODELS = Object.values(MODELS_BY_PROVIDER).flat() function shortModelLabel(model: string | null): string { if (!model) return '' const found = ALL_MODELS.find(m => m.value === model) if (found) return found.label.split(' ').slice(0, 3).join(' ') // fallback: última parte do nome return model.split('-').slice(-2).join('-') } // ─── Brain Nodes Panel ──────────────────────────────────────────────────────── const NODE_META: Record = { persona: { label: 'Persona', icon: Sparkles, color: 'text-purple-400', bg: 'bg-purple-500/10' }, knowledge: { label: 'Conhecimento', icon: BookOpen, color: 'text-blue-400', bg: 'bg-blue-500/10' }, rules: { label: 'Regras', icon: ShieldCheck, color: 'text-orange-400', bg: 'bg-orange-500/10' }, calendar: { label: 'Agenda', icon: Calendar, color: 'text-green-400', bg: 'bg-green-500/10' }, escalation: { label: 'Escalada', icon: AlertTriangle, color: 'text-red-400', bg: 'bg-red-500/10' }, } function BrainPanel({ nodes, loadingNodes, selectedAgent, onAdd, onUpdate, onDelete, onOpen, openNodeId, }: { nodes: BrainNode[] loadingNodes: boolean selectedAgent: SecAgent | null onAdd: (type: BrainNodeType) => void onUpdate: (id: string, d: Partial) => void onDelete: (id: string) => void onOpen: (node: BrainNode) => void openNodeId: string | null }) { const [editingId, setEditingId] = useState(null) const [editContent, setEditContent] = useState('') const [editTitle, setEditTitle] = useState('') const [showAddMenu, setShowAddMenu] = useState(false) const [modelPickerFor, setModelPickerFor] = useState(null) const startEdit = (node: BrainNode) => { setEditingId(node.id) setEditTitle(node.title) setEditContent(node.content) } const saveEdit = (node: BrainNode) => { onUpdate(node.id, { title: editTitle, content: editContent }) setEditingId(null) } // Modelos disponíveis baseados no provider do agente selecionado const providerModels = selectedAgent ? (MODELS_BY_PROVIDER[selectedAgent.provider] ?? ALL_MODELS) : ALL_MODELS return (
Nós do Cérebro
{showAddMenu && ( {(Object.keys(NODE_META) as BrainNodeType[]).map((type) => { const m = NODE_META[type] const Icon = m.icon return ( ) })} )}
{loadingNodes ? (
) : nodes.length === 0 ? (

{selectedAgent ? 'Nenhum nó. Clique em + para adicionar.' : 'Selecione um agente.'}

) : (
{nodes.map((node) => { const m = NODE_META[node.type as BrainNodeType] ?? NODE_META.knowledge const Icon = m.icon const isEditing = editingId === node.id const isPickingModel = modelPickerFor === node.id const hasModel = !!node.node_model const isOpen = openNodeId === node.id return (
{/* Node Header */}
{isEditing ? ( setEditTitle(e.target.value)} className="w-full text-xs font-bold bg-transparent text-white border-b border-white/20 focus:outline-none" /> ) : (

{node.title}

)}

{m.label}

{/* Abrir no editor principal */} {/* Toggle active */} {isEditing ? ( <> ) : ( <> )}
{/* Model picker row — apenas para nó persona (sobrepõe o modelo do agente) */} {node.type === 'persona' && (
{isPickingModel && ( {/* Opção: usar padrão do agente */}
{providerModels.map((mdl) => ( ))} )}
)} {/* Node Content */} {isEditing ? (