diff --git a/index.js b/index.js index c5cc351..eec1b4a 100644 --- a/index.js +++ b/index.js @@ -12,6 +12,7 @@ import { makeEvolutionWebhook } from "./src/controllers/evolution.js"; import { makeGetConversationState } from "./src/controllers/conversationState.js"; import { makeListMessages } from "./src/controllers/messages.js"; import { makeSearchProducts } from "./src/controllers/products.js"; +import { makeDeleteConversation, makeDeleteUser, makeListUsers, makeRetryLast } from "./src/controllers/admin.js"; async function configureUndiciDispatcher() { // Node 18+ usa undici debajo de fetch. Esto suele arreglar timeouts “fantasma” por keep-alive/pooling. @@ -77,8 +78,12 @@ app.post("/sim/send", makeSimSend()); app.get("/conversations", makeGetConversations(() => TENANT_ID)); app.get("/conversations/state", makeGetConversationState(() => TENANT_ID)); +app.delete("/conversations/:chat_id", makeDeleteConversation(() => TENANT_ID)); +app.post("/conversations/:chat_id/retry-last", makeRetryLast(() => TENANT_ID)); app.get("/messages", makeListMessages(() => TENANT_ID)); app.get("/products", makeSearchProducts(() => TENANT_ID)); +app.get("/users", makeListUsers(() => TENANT_ID)); +app.delete("/users/:chat_id", makeDeleteUser(() => TENANT_ID)); app.get("/runs", makeListRuns(() => TENANT_ID)); diff --git a/public/app.js b/public/app.js index efb63db..8c203e2 100644 --- a/public/app.js +++ b/public/app.js @@ -2,6 +2,7 @@ import "./components/ops-shell.js"; import "./components/conversation-list.js"; import "./components/run-timeline.js"; import "./components/chat-simulator.js"; +import "./components/debug-panel.js"; import { connectSSE } from "./lib/sse.js"; connectSSE(); diff --git a/public/components/chat-simulator.js b/public/components/chat-simulator.js index a6e16ef..6c14d33 100644 --- a/public/components/chat-simulator.js +++ b/public/components/chat-simulator.js @@ -5,6 +5,8 @@ class ChatSimulator extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); + this._lastPayload = null; + this._sending = false; this.shadowRoot.innerHTML = `
@@ -35,19 +33,15 @@ class ChatSimulator extends HTMLElement {
- +
-
Chat
-
- +
Enviar mensaje
+
+
- - -
-
Última respuesta (raw)
-
+
`; } @@ -59,8 +53,54 @@ class ChatSimulator extends HTMLElement { const evoPushEl = this.shadowRoot.getElementById("pushName"); const evoTextEl = this.shadowRoot.getElementById("evoText"); const sendEvoEl = this.shadowRoot.getElementById("sendEvo"); + const retryEl = this.shadowRoot.getElementById("retry"); + const statusEl = this.shadowRoot.getElementById("status"); - const sendAction = async () => { + const genId = () => + (self.crypto?.randomUUID?.() || `${Date.now()}${Math.random()}`) + .replace(/-/g, "") + .slice(0, 22) + .toUpperCase(); + + const buildPayload = ({ text, from, to, instance, pushName }) => { + const nowSec = Math.floor(Date.now() / 1000); + return { + body: { + event: "messages.upsert", + instance, + data: { + key: { + remoteJid: from, + fromMe: false, + id: genId(), + participant: "", + addressingMode: "pn", + }, + pushName: pushName || "test_lucas", + status: "DELIVERY_ACK", + message: { conversation: text }, + messageType: "conversation", + messageTimestamp: nowSec, + instanceId: genId(), + source: "sim", + to, + }, + date_time: new Date().toISOString(), + sender: from, + server_url: "http://localhost", + apikey: "SIM", + }, + }; + }; + + const setSending = (v) => { + this._sending = Boolean(v); + sendEvoEl.disabled = this._sending; + retryEl.disabled = this._sending; + statusEl.textContent = this._sending ? "Enviando…" : "—"; + }; + + const sendAction = async ({ payloadOverride = null } = {}) => { const instance = evoInstanceEl.value.trim() || "Piaf"; const from = evoFromEl.value.trim() || "5491133230322@s.whatsapp.net"; // cliente const to = evoToEl.value.trim() || "5491137887040@s.whatsapp.net"; // canal/destino @@ -72,57 +112,53 @@ class ChatSimulator extends HTMLElement { return; } - const nowSec = Math.floor(Date.now() / 1000); - const genId = () => - (self.crypto?.randomUUID?.() || `${Date.now()}${Math.random()}`) - .replace(/-/g, "") - .slice(0, 22) - .toUpperCase(); - - const payload = { - body: { - event: "messages.upsert", - instance, - data: { - key: { - // remoteJid debe ser el cliente (buyer) - remoteJid: from, - fromMe: false, - id: genId(), - participant: "", - addressingMode: "pn", - }, - pushName: pushName || "SimUser", - status: "DELIVERY_ACK", - message: { conversation: text }, - messageType: "conversation", - messageTimestamp: nowSec, - instanceId: genId(), - source: "sim", - }, - date_time: new Date().toISOString(), - sender: from, - server_url: "http://localhost", - apikey: "SIM", - }, - }; - - const data = await api.simEvolution(payload); - this.shadowRoot.getElementById("raw").textContent = JSON.stringify(data, null, 2); - console.log("[evolution sim] webhook response:", data); - - if (!data.ok) { - this.append("bot", "Error en Evolution Sim."); - return; - } - + // Optimistic: que aparezca en la columna izquierda al instante + emit("conversation:upsert", { + chat_id: from, + from: pushName || "test_lucas", + state: "IDLE", + intent: "other", + status: "ok", + last_activity: new Date().toISOString(), + last_run_id: null, + }); emit("ui:selectedChat", { chat_id: from }); - this.append("user", text); - this.append("bot", `[Evolution] enviado (sim): ${text}`); - evoTextEl.value = ""; + + const payload = payloadOverride || buildPayload({ text, from, to, instance, pushName }); + this._lastPayload = { ...payload, body: { ...payload.body, data: { ...payload.body.data, key: { ...payload.body.data.key, id: genId() } } } }; + setSending(true); + try { + const data = await api.simEvolution(payload); + console.log("[evolution sim] webhook response:", data); + + if (!data.ok) { + statusEl.textContent = "Error enviando (ver consola)"; + return; + } + + evoTextEl.value = ""; + evoTextEl.focus(); + } catch (e) { + statusEl.textContent = `Error: ${String(e?.message || e)}`; + } finally { + setSending(false); + } }; sendEvoEl.onclick = sendAction; + retryEl.onclick = () => { + const chat_id = evoFromEl.value.trim() || "5491133230322@s.whatsapp.net"; + setSending(true); + api + .retryLast(chat_id) + .then((r) => { + if (!r?.ok) statusEl.textContent = `Retry error: ${r?.error || "unknown"}`; + else statusEl.textContent = "Retry enviado."; + }) + .catch((e) => (statusEl.textContent = `Retry error: ${String(e?.message || e)}`)) + .finally(() => setSending(false)); + }; + retryEl.disabled = false; evoTextEl.addEventListener("keydown", (e) => { if (e.key === "Enter" && !e.shiftKey) { @@ -144,12 +180,8 @@ class ChatSimulator extends HTMLElement { } append(who, text) { - const log = this.shadowRoot.getElementById("log"); - const el = document.createElement("div"); - el.className = "msg " + (who === "user" ? "user" : "bot"); - el.textContent = text; - log.appendChild(el); - log.scrollTop = log.scrollHeight; + void who; + void text; } } diff --git a/public/components/conversation-list.js b/public/components/conversation-list.js index e4fc541..34b73aa 100644 --- a/public/components/conversation-list.js +++ b/public/components/conversation-list.js @@ -7,6 +7,9 @@ class ConversationList extends HTMLElement { this.attachShadow({ mode: "open" }); this.conversations = []; this.selected = null; + this.tab = "chats"; // chats | users + this.users = []; + this.selectedUser = null; this.shadowRoot.innerHTML = ` -
+
+
Chats
+
Usuarios
+
+ +
- +
+