Implementa sistema de chat em tempo real entre agente e cliente

- Adiciona tabela liveChatSessions no schema Convex
- Cria convex/liveChat.ts com mutations e queries para chat
- Adiciona API routes para maquinas (sessions, messages, poll)
- Cria modulo chat.rs no Tauri com ChatRuntime e polling
- Adiciona comandos de chat no lib.rs (start/stop polling, open/close window)
- Cria componentes React do chat widget (ChatWidget, types)
- Adiciona botao "Iniciar Chat" no dashboard (ticket-chat-panel)
- Implementa menu de chat no system tray
- Polling de 2 segundos para maior responsividade
- Janela de chat flutuante, frameless, always-on-top

🤖 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 01:00:27 -03:00
parent 0c8d53c0b6
commit ba91c1e0f5
15 changed files with 2004 additions and 15 deletions

View file

@ -0,0 +1,126 @@
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"
const getMessagesSchema = z.object({
machineToken: z.string().min(1),
ticketId: z.string().min(1),
since: z.number().optional(),
limit: z.number().optional(),
})
const postMessageSchema = z.object({
machineToken: z.string().min(1),
ticketId: z.string().min(1),
body: z.string().min(1).max(4000),
attachments: z
.array(
z.object({
storageId: z.string(),
name: z.string(),
size: z.number().optional(),
type: z.string().optional(),
})
)
.optional(),
})
const CORS_METHODS = "POST, OPTIONS"
export async function OPTIONS(request: Request) {
return createCorsPreflight(request.headers.get("origin"), CORS_METHODS)
}
// POST /api/machines/chat/messages
// action=list: Lista mensagens de um chat
// action=send: Envia nova mensagem
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 raw
try {
raw = await request.json()
} catch {
return jsonWithCors({ error: "JSON invalido" }, 400, origin, CORS_METHODS)
}
const action = raw.action ?? "list"
if (action === "list") {
let payload
try {
payload = getMessagesSchema.parse(raw)
} catch (error) {
return jsonWithCors(
{ error: "Payload invalido", details: error instanceof Error ? error.message : String(error) },
400,
origin,
CORS_METHODS
)
}
try {
const result = await client.query(api.liveChat.listMachineMessages, {
machineToken: payload.machineToken,
ticketId: payload.ticketId as Id<"tickets">,
since: payload.since,
limit: payload.limit,
})
return jsonWithCors(result, 200, origin, CORS_METHODS)
} catch (error) {
console.error("[machines.chat.messages] Falha ao listar mensagens", error)
const details = error instanceof Error ? error.message : String(error)
return jsonWithCors({ error: "Falha ao listar mensagens", details }, 500, origin, CORS_METHODS)
}
}
if (action === "send") {
let payload
try {
payload = postMessageSchema.parse(raw)
} catch (error) {
return jsonWithCors(
{ error: "Payload invalido", details: error instanceof Error ? error.message : String(error) },
400,
origin,
CORS_METHODS
)
}
try {
const result = await client.mutation(api.liveChat.postMachineMessage, {
machineToken: payload.machineToken,
ticketId: payload.ticketId as Id<"tickets">,
body: payload.body,
attachments: payload.attachments as
| Array<{
storageId: Id<"_storage">
name: string
size?: number
type?: string
}>
| undefined,
})
return jsonWithCors(result, 200, origin, CORS_METHODS)
} catch (error) {
console.error("[machines.chat.messages] Falha ao enviar mensagem", error)
const details = error instanceof Error ? error.message : String(error)
return jsonWithCors({ error: "Falha ao enviar mensagem", details }, 500, origin, CORS_METHODS)
}
}
return jsonWithCors({ error: "Acao invalida" }, 400, origin, CORS_METHODS)
}