Ajusta auto-minimização do chat web e unifica realtime no desktop

This commit is contained in:
rever-tecnologia 2025-12-11 17:46:33 -03:00
parent c65e37e232
commit 3d45fe3b04
10 changed files with 279 additions and 635 deletions

View file

@ -1,14 +1,10 @@
import { useCallback, useEffect, useRef, useState } from "react"
import { open } from "@tauri-apps/plugin-dialog"
import { invoke } from "@tauri-apps/api/core"
import { listen } from "@tauri-apps/api/event"
import { Send, X, Loader2, MessageCircle, Paperclip, FileText, Image as ImageIcon, File, User, ChevronUp, Minimize2 } from "lucide-react"
import type { ChatMessage } from "./types"
import {
subscribeMachineMessages,
sendMachineMessage,
markMachineMessagesRead,
getMachineStoreConfig,
} from "./convexMachineClient"
import type { ChatMessage, ChatMessagesResponse, NewMessageEvent } from "./types"
import { getMachineStoreConfig } from "./machineStore"
const MAX_MESSAGES_IN_MEMORY = 200 // Limite de mensagens para evitar memory leak
@ -56,7 +52,6 @@ export function ChatWidget({ ticketId, ticketRef }: ChatWidgetProps) {
const [unreadCount, setUnreadCount] = useState(0)
const messagesEndRef = useRef<HTMLDivElement>(null)
const messagesSubRef = useRef<(() => void) | null>(null)
const hadSessionRef = useRef<boolean>(false)
// Scroll para o final quando novas mensagens chegam
@ -86,8 +81,78 @@ export function ChatWidget({ ticketId, ticketRef }: ChatWidgetProps) {
isMinimizedRef.current = isMinimized
}, [isMinimized])
// Inicializacao via Convex (WS) - NAO depende de isMinimized para evitar resubscriptions
const configRef = useRef<{ apiBaseUrl: string; token: string } | null>(null)
const ensureConfig = useCallback(async () => {
const cfg = configRef.current ?? (await getMachineStoreConfig())
configRef.current = cfg
return cfg
}, [])
const loadMessages = useCallback(async () => {
try {
const cfg = await ensureConfig()
const result = await invoke<ChatMessagesResponse>("fetch_chat_messages", {
baseUrl: cfg.apiBaseUrl,
token: cfg.token,
ticketId,
since: null,
})
setHasSession(result.hasSession)
hadSessionRef.current = hadSessionRef.current || result.hasSession
setUnreadCount(result.unreadCount ?? 0)
setMessages(result.messages.slice(-MAX_MESSAGES_IN_MEMORY))
if (result.messages.length > 0) {
const first = result.messages[0]
setTicketInfo((prevInfo) =>
prevInfo ?? {
ref: ticketRef ?? 0,
subject: "",
agentName: first.authorName ?? "Suporte",
}
)
}
setError(null)
} catch (err) {
const message = err instanceof Error ? err.message : String(err)
setError(message || "Erro ao carregar mensagens.")
} finally {
setIsLoading(false)
}
}, [ensureConfig, ticketId, ticketRef])
// Carregar mensagens na montagem / troca de ticket
useEffect(() => {
setIsLoading(true)
setMessages([])
setUnreadCount(0)
loadMessages()
}, [loadMessages])
// Recarregar quando o Rust sinalizar novas mensagens para este ticket
useEffect(() => {
let unlisten: (() => void) | null = null
listen<NewMessageEvent>("raven://chat/new-message", (event) => {
const sessions = event.payload?.sessions ?? []
if (sessions.some((s) => s.ticketId === ticketId)) {
loadMessages()
}
})
.then((u) => {
unlisten = u
})
.catch((err) => console.error("Falha ao registrar listener new-message:", err))
return () => {
unlisten?.()
}
}, [ticketId, loadMessages])
// Inicializacao via Convex (WS) - NAO depende de isMinimized para evitar resubscriptions
/* useEffect(() => {
setIsLoading(true)
setMessages([])
messagesSubRef.current?.()
@ -128,7 +193,7 @@ export function ChatWidget({ ticketId, ticketRef }: ChatWidgetProps) {
messagesSubRef.current?.()
messagesSubRef.current = null
}
}, [ticketId]) // Removido isMinimized - evita memory leak de resubscriptions
}, [ticketId]) */ // Removido isMinimized - evita memory leak de resubscriptions
// Sincroniza estado de minimizado com o tamanho da janela (apenas em resizes reais, nao na montagem)
// O estado inicial isMinimized=true e definido no useState e nao deve ser sobrescrito na montagem
@ -158,10 +223,19 @@ export function ChatWidget({ ticketId, ticketRef }: ChatWidgetProps) {
if (unreadCount === 0) return
const unreadIds = messages.filter(m => !m.isFromMachine).map(m => m.id as string)
if (unreadIds.length > 0) {
markMachineMessagesRead(ticketId, unreadIds).catch(err => console.error("mark read falhou", err))
ensureConfig()
.then((cfg) =>
invoke("mark_chat_messages_read", {
baseUrl: cfg.apiBaseUrl,
token: cfg.token,
ticketId,
messageIds: unreadIds,
})
)
.catch((err) => console.error("mark read falhou", err))
}
// Nao setamos unreadCount aqui - o backend vai zerar unreadByMachine e a subscription vai atualizar
}, [isMinimized, messages, ticketId, unreadCount])
}, [isMinimized, messages, ticketId, unreadCount, ensureConfig])
// Selecionar arquivo para anexar
const handleAttach = async () => {
@ -217,7 +291,10 @@ export function ChatWidget({ ticketId, ticketRef }: ChatWidgetProps) {
try {
const bodyToSend = messageText || (attachmentsToSend.length > 0 ? "[Anexo]" : "")
await sendMachineMessage({
const cfg = await ensureConfig()
await invoke("send_chat_message", {
baseUrl: cfg.apiBaseUrl,
token: cfg.token,
ticketId,
body: bodyToSend,
attachments: attachmentsToSend.length > 0 ? attachmentsToSend : undefined,