fix(desktop-chat): estabiliza janelas e melhora multi-conversas
All checks were successful
CI/CD Web + Desktop / Detect changes (push) Successful in 7s
Quality Checks / Lint, Test and Build (push) Successful in 4m41s
CI/CD Web + Desktop / Deploy Convex functions (push) Has been skipped
CI/CD Web + Desktop / Deploy (VPS Linux) (push) Successful in 5m30s

This commit is contained in:
esdrasrenan 2025-12-17 01:44:28 -03:00
parent 380b2e44e9
commit 3f9461a18f
3 changed files with 134 additions and 84 deletions

View file

@ -12,9 +12,9 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { open as openDialog } from "@tauri-apps/plugin-dialog"
import { openUrl as openExternal } from "@tauri-apps/plugin-opener"
import { invoke } from "@tauri-apps/api/core"
import { Send, X, Loader2, MessageCircle, Paperclip, FileText, Image as ImageIcon, File, User, ChevronUp, Minimize2, Eye, Download, Check } from "lucide-react"
import { Send, X, Loader2, MessageCircle, Paperclip, FileText, Image as ImageIcon, File, User, ChevronUp, Minimize2, Eye, Download, Check, MessagesSquare } from "lucide-react"
import type { Id } from "@convex/_generated/dataModel"
import { useMachineMessages, usePostMachineMessage, useMarkMachineMessagesRead, type MachineMessage } from "./useConvexMachineQueries"
import { useMachineMessages, useMachineSessions, usePostMachineMessage, useMarkMachineMessagesRead, type MachineMessage } from "./useConvexMachineQueries"
import { useConvexMachine } from "./ConvexMachineProvider"
const MAX_MESSAGES_IN_MEMORY = 200
@ -178,7 +178,7 @@ function MessageAttachment({
<button
onClick={handleView}
className="flex size-7 items-center justify-center rounded-full bg-white/20 hover:bg-white/30"
title="Visualizar"
aria-label="Visualizar anexo"
>
<Eye className="size-4 text-white" />
</button>
@ -186,7 +186,7 @@ function MessageAttachment({
onClick={handleDownload}
disabled={downloading}
className="flex size-7 items-center justify-center rounded-full bg-white/20 hover:bg-white/30 disabled:opacity-60"
title="Baixar"
aria-label="Baixar anexo"
>
{downloading ? (
<Loader2 className="size-4 animate-spin text-white" />
@ -204,7 +204,11 @@ function MessageAttachment({
return (
<div className={`flex items-center gap-2 rounded-lg p-2 text-xs ${isAgent ? "bg-white/10" : "bg-slate-100"}`}>
{getFileIcon(attachment.name)}
<button onClick={handleView} className="flex-1 truncate text-left hover:underline" title="Visualizar">
<button
onClick={handleView}
className="flex-1 truncate text-left hover:underline"
aria-label={`Visualizar anexo ${attachment.name}`}
>
{attachment.name}
</button>
{sizeLabel && <span className="text-xs opacity-60">({sizeLabel})</span>}
@ -212,7 +216,7 @@ function MessageAttachment({
<button
onClick={handleView}
className={`flex size-7 items-center justify-center rounded-md ${isAgent ? "hover:bg-white/10" : "hover:bg-slate-200"}`}
title="Visualizar"
aria-label="Visualizar anexo"
>
<Eye className="size-4" />
</button>
@ -220,7 +224,7 @@ function MessageAttachment({
onClick={handleDownload}
disabled={downloading}
className={`flex size-7 items-center justify-center rounded-md disabled:opacity-60 ${isAgent ? "hover:bg-white/10" : "hover:bg-slate-200"}`}
title="Baixar"
aria-label="Baixar anexo"
>
{downloading ? (
<Loader2 className="size-4 animate-spin" />
@ -250,6 +254,7 @@ export function ChatWidget({ ticketId, ticketRef }: ChatWidgetProps) {
// Convex hooks
const { apiBaseUrl, machineToken } = useConvexMachine()
const { sessions: machineSessions = [] } = useMachineSessions()
const { messages: convexMessages, hasSession, unreadCount, isLoading } = useMachineMessages(
ticketId as Id<"tickets">,
{ limit: MAX_MESSAGES_IN_MEMORY }
@ -276,6 +281,22 @@ export function ChatWidget({ ticketId, ticketRef }: ChatWidgetProps) {
const unreadAgentMessageIds = useMemo(() => getUnreadAgentMessageIds(messages, unreadCount), [messages, unreadCount])
const firstUnreadAgentMessageId = unreadAgentMessageIds[0] ?? null
const otherUnreadCount = useMemo(() => {
if (machineSessions.length <= 1) return 0
return machineSessions.reduce((sum, session) => {
return sum + (session.ticketId === ticketId ? 0 : session.unreadCount)
}, 0)
}, [machineSessions, ticketId])
const handleOpenHub = useCallback(async () => {
try {
await invoke("open_hub_window")
await invoke("set_hub_minimized", { minimized: false })
} catch (err) {
console.error("Erro ao abrir hub:", err)
}
}, [])
const updateIsAtBottom = useCallback(() => {
const el = messagesContainerRef.current
if (!el) return
@ -562,7 +583,7 @@ export function ChatWidget({ ticketId, ticketRef }: ChatWidgetProps) {
<button
onClick={handleClose}
className="ml-1 rounded-full p-1 text-slate-600 hover:bg-slate-300/60"
title="Fechar"
aria-label="Fechar chat"
>
<X className="size-4" />
</button>
@ -621,17 +642,31 @@ export function ChatWidget({ ticketId, ticketRef }: ChatWidgetProps) {
</div>
</div>
<div className="flex items-center gap-1">
{machineSessions.length > 1 && (
<button
onClick={handleOpenHub}
className="relative rounded-md p-1.5 text-slate-500 hover:bg-slate-100"
aria-label="Abrir lista de chats"
>
<MessagesSquare className="size-4" />
{otherUnreadCount > 0 && (
<span className="absolute -right-1 -top-1 flex size-5 items-center justify-center rounded-full bg-red-500 text-[10px] font-bold text-white">
{otherUnreadCount > 9 ? "9+" : otherUnreadCount}
</span>
)}
</button>
)}
<button
onClick={handleMinimize}
className="rounded-md p-1.5 text-slate-500 hover:bg-slate-100"
title="Minimizar"
aria-label="Minimizar chat"
>
<Minimize2 className="size-4" />
</button>
<button
onClick={handleClose}
className="rounded-md p-1.5 text-slate-500 hover:bg-slate-100"
title="Fechar"
aria-label="Fechar chat"
>
<X className="size-4" />
</button>
@ -772,6 +807,7 @@ export function ChatWidget({ ticketId, ticketRef }: ChatWidgetProps) {
<button
onClick={() => handleRemoveAttachment(att.storageId)}
className="ml-1 rounded p-0.5 text-slate-400 hover:bg-slate-200 hover:text-slate-600"
aria-label={`Remover anexo ${att.name}`}
>
<X className="size-3" />
</button>
@ -792,7 +828,7 @@ export function ChatWidget({ ticketId, ticketRef }: ChatWidgetProps) {
onClick={handleAttach}
disabled={isUploading || isSending}
className="flex size-9 items-center justify-center rounded-lg text-slate-500 transition hover:bg-slate-100 hover:text-slate-700 disabled:opacity-50"
title="Anexar arquivo"
aria-label="Anexar arquivo"
>
{isUploading ? (
<Loader2 className="size-4 animate-spin" />
@ -804,6 +840,7 @@ export function ChatWidget({ ticketId, ticketRef }: ChatWidgetProps) {
onClick={handleSend}
disabled={(!inputValue.trim() && pendingAttachments.length === 0) || isSending}
className="flex size-9 items-center justify-center rounded-lg bg-black text-white transition hover:bg-black/90 disabled:opacity-50"
aria-label="Enviar mensagem"
>
{isSending ? (
<Loader2 className="size-4 animate-spin" />