Otimizações de performance e correções no chat ao vivo

- Corrigir acentuações (sessão, sessões, duração)
- Auto-minimizar chat nativo quando sessão termina
- Corrigir race condition em markMachineMessagesRead (Promise.all)
- Adicionar paginação no cron autoEndInactiveSessions (.take(50))
- Otimizar listMachineMessages com limite de 100 mensagens
- Corrigir memory leak no ChatWidget (limite de 200 mensagens)
- Exibir estado offline quando não há sessão ativa

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
esdrasrenan 2025-12-07 15:14:47 -03:00
parent 115c5128a6
commit 0e0bd9a49c
3 changed files with 88 additions and 44 deletions

View file

@ -8,6 +8,7 @@ import { Send, X, Loader2, MessageCircle, Paperclip, FileText, Image as ImageIco
import type { ChatMessage, ChatMessagesResponse, SendMessageResponse } from "./types"
const STORE_FILENAME = "machine-agent.json"
const MAX_MESSAGES_IN_MEMORY = 200 // Limite de mensagens para evitar memory leak
// Tipos de arquivo permitidos
const ALLOWED_EXTENSIONS = [
@ -52,6 +53,7 @@ export function ChatWidget({ ticketId }: ChatWidgetProps) {
const messagesEndRef = useRef<HTMLDivElement>(null)
const lastFetchRef = useRef<number>(0)
const pollIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
const hadSessionRef = useRef<boolean>(false)
// Scroll para o final quando novas mensagens chegam
const scrollToBottom = useCallback(() => {
@ -62,6 +64,14 @@ export function ChatWidget({ ticketId }: ChatWidgetProps) {
scrollToBottom()
}, [messages, scrollToBottom])
// Auto-minimizar quando a sessão termina (hasSession muda de true para false)
useEffect(() => {
if (hadSessionRef.current && !hasSession) {
setIsMinimized(true)
}
hadSessionRef.current = hasSession
}, [hasSession])
// Carregar configuracao do store
const loadConfig = useCallback(async () => {
try {
@ -72,7 +82,7 @@ export function ChatWidget({ ticketId }: ChatWidgetProps) {
const config = await store.get<{ apiBaseUrl: string }>("config")
if (!token || !config?.apiBaseUrl) {
setError("Maquina nao registrada")
setError("Máquina não registrada")
setIsLoading(false)
return null
}
@ -99,15 +109,17 @@ export function ChatWidget({ ticketId }: ChatWidgetProps) {
if (response.messages.length > 0) {
if (since) {
// Adicionar apenas novas mensagens
// Adicionar apenas novas mensagens (com limite para evitar memory leak)
setMessages(prev => {
const existingIds = new Set(prev.map(m => m.id))
const newMsgs = response.messages.filter(m => !existingIds.has(m.id))
return [...prev, ...newMsgs]
const combined = [...prev, ...newMsgs]
// Manter apenas as últimas MAX_MESSAGES_IN_MEMORY mensagens
return combined.slice(-MAX_MESSAGES_IN_MEMORY)
})
} else {
// Primeira carga
setMessages(response.messages)
// Primeira carga (já limitada)
setMessages(response.messages.slice(-MAX_MESSAGES_IN_MEMORY))
}
lastFetchRef.current = Math.max(...response.messages.map(m => m.createdAt))
}
@ -178,7 +190,9 @@ export function ChatWidget({ ticketId }: ChatWidgetProps) {
if (prev.some(m => m.id === event.payload.message.id)) {
return prev
}
return [...prev, event.payload.message]
const combined = [...prev, event.payload.message]
// Manter apenas as últimas MAX_MESSAGES_IN_MEMORY mensagens
return combined.slice(-MAX_MESSAGES_IN_MEMORY)
})
}
}
@ -324,15 +338,23 @@ export function ChatWidget({ ticketId }: ChatWidgetProps) {
)
}
// Quando não há sessão, mostrar versão minimizada com indicador de offline
if (!hasSession) {
return (
<div className="flex h-screen flex-col items-center justify-center bg-white p-4">
<p className="text-sm text-slate-500">Nenhuma sessao de chat ativa</p>
<div className="flex h-screen flex-col items-center justify-end bg-transparent p-4">
<div className="flex items-center gap-2 rounded-full bg-slate-200 px-4 py-2 text-slate-600 shadow-lg">
<MessageCircle className="size-4" />
<span className="text-sm font-medium">
{ticketInfo ? `Chat #${ticketInfo.ref}` : "Chat"}
</span>
<span className="size-2 rounded-full bg-slate-400" />
<span className="text-xs text-slate-500">Offline</span>
</div>
</div>
)
}
// Versao minimizada (chip compacto igual web)
// Versão minimizada (chip compacto igual web)
if (isMinimized) {
return (
<div className="flex h-screen flex-col items-center justify-end bg-transparent p-4">
@ -403,7 +425,7 @@ export function ChatWidget({ ticketId }: ChatWidgetProps) {
Nenhuma mensagem ainda
</p>
<p className="mt-1 text-xs text-slate-400">
O agente iniciara a conversa em breve
O agente iniciará a conversa em breve
</p>
</div>
) : (