import { ConvexClient } from "convex/browser" import type { FunctionReference } from "convex/server" import { Store } from "@tauri-apps/plugin-store" import { appLocalDataDir, join } from "@tauri-apps/api/path" import type { ChatMessage } from "./types" const STORE_FILENAME = "machine-agent.json" const DEFAULT_CONVEX_URL = import.meta.env.VITE_CONVEX_URL?.trim() || "https://convex.esdrasrenan.com.br" type MachineStoreConfig = { apiBaseUrl?: string appUrl?: string convexUrl?: string } type MachineStoreData = { token?: string config?: MachineStoreConfig } type ClientCache = { client: ConvexClient token: string convexUrl: string } let cached: ClientCache | null = null type MachineUpdatePayload = { hasActiveSessions: boolean sessions: Array<{ ticketId: string; ticketRef: number; unreadCount: number; lastActivityAt: number }> totalUnread: number } // Nomes das functions no Convex (formato module:function) const FN_CHECK_UPDATES = "liveChat:checkMachineUpdates" as const const FN_LIST_MESSAGES = "liveChat:listMachineMessages" as const const FN_POST_MESSAGE = "liveChat:postMachineMessage" as const const FN_MARK_READ = "liveChat:markMachineMessagesRead" as const const FN_UPLOAD_URL = "liveChat:generateMachineUploadUrl" as const async function loadStore(): Promise { const appData = await appLocalDataDir() const storePath = await join(appData, STORE_FILENAME) const store = await Store.load(storePath) const token = await store.get("token") const config = await store.get("config") return { token: token ?? undefined, config: config ?? undefined } } function resolveConvexUrl(config?: MachineStoreConfig): string { const fromConfig = config?.convexUrl?.trim() if (fromConfig) return fromConfig.replace(/\/+$/, "") return DEFAULT_CONVEX_URL } function resolveApiBaseUrl(config?: MachineStoreConfig): string { const fromConfig = config?.apiBaseUrl?.trim() if (fromConfig) return fromConfig.replace(/\/+$/, "") return "https://tickets.esdrasrenan.com.br" } export async function getMachineStoreConfig() { const data = await loadStore() if (!data.token) { throw new Error("Token de máquina não encontrado no store") } const apiBaseUrl = resolveApiBaseUrl(data.config) const appUrl = data.config?.appUrl?.trim() || apiBaseUrl return { token: data.token, apiBaseUrl, appUrl, convexUrl: resolveConvexUrl(data.config) } } async function ensureClient(): Promise { const data = await loadStore() if (!data.token) { throw new Error("Token de máquina não encontrado no store") } const convexUrl = resolveConvexUrl(data.config) if (cached && cached.token === data.token && cached.convexUrl === convexUrl) { return cached } const client = new ConvexClient(convexUrl) cached = { client, token: data.token, convexUrl } return cached } export async function subscribeMachineUpdates( callback: (payload: MachineUpdatePayload) => void, onError?: (error: Error) => void ): Promise<() => void> { const { client, token } = await ensureClient() const sub = client.onUpdate( FN_CHECK_UPDATES as unknown as FunctionReference<"query">, { machineToken: token }, (value) => callback(value), (err) => onError?.(err) ) return () => { sub.unsubscribe() } } export async function subscribeMachineMessages( ticketId: string, callback: (payload: { messages: ChatMessage[]; hasSession: boolean }) => void, onError?: (error: Error) => void ): Promise<() => void> { const { client, token } = await ensureClient() const sub = client.onUpdate( FN_LIST_MESSAGES as unknown as FunctionReference<"query">, { machineToken: token, ticketId, }, (value) => callback(value), (err) => onError?.(err) ) return () => { sub.unsubscribe() } } export async function sendMachineMessage(input: { ticketId: string body: string attachments?: Array<{ storageId: string name: string size?: number type?: string }> }) { const { client, token } = await ensureClient() return client.mutation(FN_POST_MESSAGE as unknown as FunctionReference<"mutation">, { machineToken: token, ticketId: input.ticketId, body: input.body, attachments: input.attachments?.map((att) => ({ storageId: att.storageId, name: att.name, size: att.size, type: att.type, })), }) } export async function markMachineMessagesRead(ticketId: string, messageIds: string[]) { if (messageIds.length === 0) return const { client, token } = await ensureClient() await client.mutation(FN_MARK_READ as unknown as FunctionReference<"mutation">, { machineToken: token, ticketId, messageIds, }) } export async function generateMachineUploadUrl(opts: { fileName: string fileType: string fileSize: number }) { const { client, token } = await ensureClient() return client.action(FN_UPLOAD_URL as unknown as FunctionReference<"action">, { machineToken: token, fileName: opts.fileName, fileType: opts.fileType, fileSize: opts.fileSize, }) } export async function uploadToConvexStorage(uploadUrl: string, file: Blob | ArrayBuffer, contentType: string) { const response = await fetch(uploadUrl, { method: "POST", headers: { "Content-Type": contentType }, body: file, }) if (!response.ok) { const body = await response.text() throw new Error(`Upload falhou: ${response.status} ${body}`) } const json = await response.json().catch(() => ({})) return json.storageId || json.storage_id }