import { useCallback, useEffect, useRef, useState } from "react" import { invoke } from "@tauri-apps/api/core" import { Store } from "@tauri-apps/plugin-store" import { appLocalDataDir, join } from "@tauri-apps/api/path" import { MessageCircle, X, Minus, Send, Loader2, ChevronLeft, ChevronDown, ChevronRight } from "lucide-react" import { cn } from "../lib/utils" import type { ChatSession, ChatMessage, ChatMessagesResponse, SendMessageResponse, ChatHistorySession } from "../chat/types" const STORE_FILENAME = "machine-agent.json" interface ChatFloatingWidgetProps { sessions: ChatSession[] totalUnread: number isOpen: boolean onToggle: () => void onMinimize: () => void } export function ChatFloatingWidget({ sessions, totalUnread, isOpen, onToggle, onMinimize, }: ChatFloatingWidgetProps) { const [selectedTicketId, setSelectedTicketId] = useState(null) const [messages, setMessages] = useState([]) const [inputValue, setInputValue] = useState("") const [isLoading, setIsLoading] = useState(false) const [isSending, setIsSending] = useState(false) const [historyExpanded, setHistoryExpanded] = useState(false) const [historySessions] = useState([]) const messagesEndRef = useRef(null) const lastFetchRef = useRef(0) const pollIntervalRef = useRef | null>(null) // Selecionar ticket mais recente automaticamente useEffect(() => { if (sessions.length > 0 && !selectedTicketId) { // Ordenar por lastActivityAt e pegar o mais recente const sorted = [...sessions].sort((a, b) => b.lastActivityAt - a.lastActivityAt) setSelectedTicketId(sorted[0].ticketId) } }, [sessions, selectedTicketId]) // Scroll para o final quando novas mensagens chegam const scrollToBottom = useCallback(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }) }, []) useEffect(() => { scrollToBottom() }, [messages, scrollToBottom]) // Carregar configuracao do store const loadConfig = useCallback(async () => { try { const appData = await appLocalDataDir() const storePath = await join(appData, STORE_FILENAME) const store = await Store.load(storePath) const token = await store.get("token") const config = await store.get<{ apiBaseUrl: string }>("config") if (!token || !config?.apiBaseUrl) { return null } return { token, baseUrl: config.apiBaseUrl } } catch { return null } }, []) // Buscar mensagens const fetchMessages = useCallback(async (baseUrl: string, token: string, ticketId: string, since?: number) => { try { const response = await invoke("fetch_chat_messages", { baseUrl, token, ticketId, since: since ?? null, }) if (response.messages.length > 0) { if (since) { setMessages(prev => { const existingIds = new Set(prev.map(m => m.id)) const newMsgs = response.messages.filter(m => !existingIds.has(m.id)) return [...prev, ...newMsgs] }) } else { setMessages(response.messages) } lastFetchRef.current = Math.max(...response.messages.map(m => m.createdAt)) } return response } catch (err) { console.error("Erro ao buscar mensagens:", err) return null } }, []) // Inicializar e fazer polling quando ticket selecionado useEffect(() => { if (!selectedTicketId || !isOpen) return let mounted = true const init = async () => { setIsLoading(true) const config = await loadConfig() if (!config || !mounted) { setIsLoading(false) return } const { baseUrl, token } = config // Buscar mensagens iniciais await fetchMessages(baseUrl, token, selectedTicketId) if (!mounted) return setIsLoading(false) // Iniciar polling (2 segundos) pollIntervalRef.current = setInterval(async () => { await fetchMessages(baseUrl, token, selectedTicketId, lastFetchRef.current) }, 2000) } init() return () => { mounted = false if (pollIntervalRef.current) { clearInterval(pollIntervalRef.current) pollIntervalRef.current = null } } }, [selectedTicketId, isOpen, loadConfig, fetchMessages]) // Limpar mensagens quando trocar de ticket useEffect(() => { setMessages([]) lastFetchRef.current = 0 }, [selectedTicketId]) // Enviar mensagem const handleSend = async () => { if (!inputValue.trim() || isSending || !selectedTicketId) return const messageText = inputValue.trim() setInputValue("") setIsSending(true) try { const config = await loadConfig() if (!config) { setIsSending(false) return } const response = await invoke("send_chat_message", { baseUrl: config.baseUrl, token: config.token, ticketId: selectedTicketId, body: messageText, }) setMessages(prev => [...prev, { id: response.messageId, body: messageText, authorName: "Voce", isFromMachine: true, createdAt: response.createdAt, attachments: [], }]) lastFetchRef.current = response.createdAt } catch (err) { console.error("Erro ao enviar mensagem:", err) setInputValue(messageText) } finally { setIsSending(false) } } const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault() handleSend() } } const currentSession = sessions.find(s => s.ticketId === selectedTicketId) // Botao flutuante (fechado) // DEBUG: Log do estado do widget console.log("[ChatFloatingWidget] Estado:", { isOpen, totalUnread, sessionsCount: sessions.length, sessions: sessions.map(s => ({ id: s.sessionId, ticketId: s.ticketId, unread: s.unreadCount })) }) if (!isOpen) { return (
{/* DEBUG: Indicador visual do estado */}
unread: {totalUnread} | sessions: {sessions.length}
) } // Widget expandido return (
{/* Header */}
{sessions.length > 1 && selectedTicketId && ( )}

{currentSession?.agentName ?? "Suporte"}

{currentSession && (

Chamado #{currentSession.ticketRef}

)}
{/* Tabs de tickets (se houver mais de 1) */} {sessions.length > 1 && (
{sessions.slice(0, 3).map((session) => ( ))} {sessions.length > 3 && ( +{sessions.length - 3} )}
)}
{/* Selecao de ticket (se nenhum selecionado e ha multiplos) */} {!selectedTicketId && sessions.length > 1 ? (

Selecione um chamado:

{sessions.map((session) => ( ))}
) : ( <> {/* Area de mensagens */}
{/* Historico de sessoes anteriores */} {historySessions.length > 0 && (
{historyExpanded && (
{historySessions.map((session) => (

{session.agentName}

{session.messages.length} mensagens

))}
)}
)} {isLoading ? (

Carregando...

) : messages.length === 0 ? (

Nenhuma mensagem ainda

O agente iniciara a conversa em breve

) : (
{messages.map((msg) => (
{!msg.isFromMachine && (

{msg.authorName}

)}

{msg.body}

{formatTime(msg.createdAt)}

))}
)}
{/* Input */}