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

@ -0,0 +1,77 @@
import { z } from "zod"
import { api } from "@/convex/_generated/api"
import type { Id } from "@/convex/_generated/dataModel"
import { createCorsPreflight, jsonWithCors } from "@/server/cors"
import { createConvexClient, ConvexConfigurationError } from "@/server/convex-client"
import { checkRateLimit, RATE_LIMITS, rateLimitHeaders } from "@/server/rate-limit"
const readSchema = z.object({
machineToken: z.string().min(1),
ticketId: z.string().min(1),
messageIds: z.array(z.string().min(1)).min(1).max(50),
})
const CORS_METHODS = "POST, OPTIONS"
export async function OPTIONS(request: Request) {
return createCorsPreflight(request.headers.get("origin"), CORS_METHODS)
}
// POST /api/machines/chat/read
// Marca mensagens do chat como lidas pela maquina (zera unreadByMachine na sessao ativa)
export async function POST(request: Request) {
const origin = request.headers.get("origin")
let client
try {
client = createConvexClient()
} catch (error) {
if (error instanceof ConvexConfigurationError) {
return jsonWithCors({ error: error.message }, 500, origin, CORS_METHODS)
}
throw error
}
let payload
try {
const raw = await request.json()
payload = readSchema.parse(raw)
} catch (error) {
return jsonWithCors(
{ error: "Payload invalido", details: error instanceof Error ? error.message : String(error) },
400,
origin,
CORS_METHODS
)
}
const rateLimit = checkRateLimit(
`chat-read:${payload.machineToken}`,
RATE_LIMITS.CHAT_MESSAGES.maxRequests,
RATE_LIMITS.CHAT_MESSAGES.windowMs
)
if (!rateLimit.allowed) {
return jsonWithCors(
{ error: "Rate limit exceeded", retryAfterMs: rateLimit.retryAfterMs },
429,
origin,
CORS_METHODS,
rateLimitHeaders(rateLimit)
)
}
try {
const result = await client.mutation(api.liveChat.markMachineMessagesRead, {
machineToken: payload.machineToken,
ticketId: payload.ticketId as Id<"tickets">,
messageIds: payload.messageIds as unknown as Id<"ticketChatMessages">[],
})
return jsonWithCors(result, 200, origin, CORS_METHODS, rateLimitHeaders(rateLimit))
} catch (error) {
console.error("[machines.chat.read] Falha ao marcar mensagens como lidas", error)
const details = error instanceof Error ? error.message : String(error)
return jsonWithCors({ error: "Falha ao marcar mensagens como lidas", details }, 500, origin, CORS_METHODS)
}
}

View file

@ -382,17 +382,24 @@ export function ChatWidget() {
// Se aumentou o número de sessões APOS a montagem inicial, é uma nova sessão - abrir o widget expandido
if (currentCount > prevCount && hasRestoredStateRef.current) {
setIsOpen(true)
setIsMinimized(false)
// O estado do widget e definido com base nas nao lidas.
// Selecionar a sessão mais recente (última da lista ou primeira se única)
const newestSession = activeSessions[activeSessions.length - 1] ?? activeSessions[0]
const hasUnreadForAgent = (newestSession?.unreadCount ?? 0) > 0
if (!isOpen) {
setIsOpen(true)
setIsMinimized(!hasUnreadForAgent)
} else if (isMinimized && hasUnreadForAgent) {
setIsMinimized(false)
}
if (newestSession) {
setActiveTicketId(newestSession.ticketId)
}
}
prevSessionCountRef.current = currentCount
}, [activeSessions])
}, [activeSessions, isOpen, isMinimized])
// Scroll para última mensagem
useEffect(() => {