From 829823ac3dc0dc69bd08981f1085a1d6b1cd582e Mon Sep 17 00:00:00 2001
From: Lucas Tettamanti <757326+lkzwieder@users.noreply.github.com>
Date: Mon, 5 Jan 2026 10:58:54 -0300
Subject: [PATCH] styling and create customer woo bug handled
---
index.js | 2 +
public/components/chat-simulator.js | 7 +-
public/components/run-timeline.js | 100 +++++++++++++++++++++-------
public/lib/api.js | 7 ++
src/controllers/messages.js | 18 +++++
src/db/repo.js | 21 ++++++
src/handlers/messages.js | 12 ++++
7 files changed, 141 insertions(+), 26 deletions(-)
create mode 100644 src/controllers/messages.js
create mode 100644 src/handlers/messages.js
diff --git a/index.js b/index.js
index 991bb93..19e712e 100644
--- a/index.js
+++ b/index.js
@@ -10,6 +10,7 @@ import { makeListRuns, makeGetRunById } from "./src/controllers/runs.js";
import { makeSimSend } from "./src/controllers/sim.js";
import { makeEvolutionWebhook } from "./src/controllers/evolution.js";
import { makeGetConversationState } from "./src/controllers/conversationState.js";
+import { makeListMessages } from "./src/controllers/messages.js";
async function configureUndiciDispatcher() {
// Node 18+ usa undici debajo de fetch. Esto suele arreglar timeouts “fantasma” por keep-alive/pooling.
@@ -75,6 +76,7 @@ app.post("/sim/send", makeSimSend());
app.get("/conversations", makeGetConversations(() => TENANT_ID));
app.get("/conversations/state", makeGetConversationState(() => TENANT_ID));
+app.get("/messages", makeListMessages(() => TENANT_ID));
app.get("/runs", makeListRuns(() => TENANT_ID));
diff --git a/public/components/chat-simulator.js b/public/components/chat-simulator.js
index 7881c7b..a6e16ef 100644
--- a/public/components/chat-simulator.js
+++ b/public/components/chat-simulator.js
@@ -16,9 +16,10 @@ class ChatSimulator extends HTMLElement {
button { cursor:pointer; }
button.primary { background:#1f6feb; border-color:#1f6feb; }
.chatlog { display:flex; flex-direction:column; gap:8px; max-height:280px; overflow:auto; padding-right:6px; margin-top:8px; }
- .msg { max-width:90%; padding:8px 10px; border-radius:12px; border:1px solid #253245; background:#0f1520; font-size:13px; }
- .msg.user { align-self:flex-end; }
- .msg.bot { align-self:flex-start; border-color:#1f6feb; }
+ /* WhatsApp-ish dark theme bubbles */
+ .msg { max-width:90%; padding:8px 10px; border-radius:14px; border:1px solid #253245; font-size:13px; line-height:1.35; white-space:pre-wrap; word-break:break-word; box-shadow: 0 1px 0 rgba(0,0,0,.35); }
+ .msg.user { align-self:flex-end; background:#0f2a1a; border-color:#1f6f43; color:#e7eef7; }
+ .msg.bot { align-self:flex-start; background:#111b2a; border-color:#2a3a55; color:#e7eef7; }
pre { white-space:pre-wrap; word-break:break-word; background:#0f1520; border:1px solid #253245; border-radius:10px; padding:10px; margin:0; font-size:12px; color:#d7e2ef; }
diff --git a/public/components/run-timeline.js b/public/components/run-timeline.js
index 7276418..1fbf60b 100644
--- a/public/components/run-timeline.js
+++ b/public/components/run-timeline.js
@@ -6,6 +6,8 @@ class RunTimeline extends HTMLElement {
super();
this.attachShadow({ mode: "open" });
this.chatId = null;
+ this.items = [];
+ this.selectedDebug = null;
this.shadowRoot.innerHTML = `
@@ -21,29 +35,33 @@ class RunTimeline extends HTMLElement {
Conversación
—
Seleccioná una conversación.
+
+
+
+
+
-
Último run (raw)
-
—
+
Debug (click en burbuja roja para ver detalles)
+
—
-
`;
}
connectedCallback() {
+ this.shadowRoot.getElementById("refresh").onclick = () => this.loadMessages();
+
this._unsubSel = on("ui:selectedChat", async ({ chat_id }) => {
this.chatId = chat_id;
- await this.loadLatest();
+ await this.loadMessages();
});
this._unsubRun = on("run:created", (run) => {
if (this.chatId && run.chat_id === this.chatId) {
- this.show(run);
+ // nuevo run => refrescar mensajes para ver los bubbles actualizados
+ this.loadMessages();
}
});
}
@@ -53,29 +71,65 @@ class RunTimeline extends HTMLElement {
this._unsubRun?.();
}
- async loadLatest() {
+ async loadMessages() {
this.shadowRoot.getElementById("chat").textContent = this.chatId || "—";
this.shadowRoot.getElementById("meta").textContent = "Cargando…";
+ this.shadowRoot.getElementById("count").textContent = "";
- const data = await api.runs({ chat_id: this.chatId, limit: 1 });
- const run = (data.items || [])[0];
-
- if (!run) {
- this.shadowRoot.getElementById("meta").textContent = "Sin runs aún.";
- this.shadowRoot.getElementById("run").textContent = "—";
- this.shadowRoot.getElementById("inv").textContent = "—";
+ if (!this.chatId) {
+ this.shadowRoot.getElementById("meta").textContent = "Seleccioná una conversación.";
+ this.shadowRoot.getElementById("log").innerHTML = "";
+ this.shadowRoot.getElementById("debug").textContent = "—";
return;
}
- this.show(run);
+
+ const data = await api.messages({ chat_id: this.chatId, limit: 200 });
+ this.items = data.items || [];
+ this.render();
}
- show(run) {
- this.shadowRoot.getElementById("chat").textContent = run.chat_id;
- this.shadowRoot.getElementById("meta").textContent =
- `run_id=${run.run_id} • ${run.latency_ms}ms • ${new Date(run.ts).toLocaleString()}`;
+ isErrorMsg(m) {
+ const txt = String(m?.text || "");
+ return Boolean(m?.payload?.error) || txt.startsWith("[ERROR]") || txt.toLowerCase().includes("internal_error");
+ }
- this.shadowRoot.getElementById("run").textContent = JSON.stringify(run, null, 2);
- this.shadowRoot.getElementById("inv").textContent = JSON.stringify(run.invariants, null, 2);
+ render() {
+ const meta = this.shadowRoot.getElementById("meta");
+ const count = this.shadowRoot.getElementById("count");
+ const log = this.shadowRoot.getElementById("log");
+ const dbg = this.shadowRoot.getElementById("debug");
+
+ meta.textContent = `Mostrando historial (últimos ${this.items.length}).`;
+ count.textContent = this.items.length ? `${this.items.length} msgs` : "";
+
+ log.innerHTML = "";
+
+ for (const m of this.items) {
+ const who = m.direction === "in" ? "user" : "bot";
+ const isErr = this.isErrorMsg(m);
+ const bubble = document.createElement("div");
+ bubble.className = `bubble ${isErr ? "err" : who}`;
+ bubble.textContent = m.text || (isErr ? "Error" : "—");
+
+ const metaEl = document.createElement("span");
+ metaEl.className = "meta";
+ metaEl.textContent = `${new Date(m.ts).toLocaleString()} • ${m.provider} • ${m.message_id}`;
+ bubble.appendChild(metaEl);
+
+ if (isErr) {
+ bubble.title = "Click para ver detalles (JSON)";
+ bubble.onclick = () => {
+ dbg.textContent = JSON.stringify(m, null, 2);
+ };
+ }
+
+ log.appendChild(bubble);
+ }
+
+ // auto-scroll
+ log.scrollTop = log.scrollHeight;
+
+ if (!this.items.length) dbg.textContent = "—";
}
}
diff --git a/public/lib/api.js b/public/lib/api.js
index db9dff5..ec27145 100644
--- a/public/lib/api.js
+++ b/public/lib/api.js
@@ -7,6 +7,13 @@ export const api = {
return fetch(u).then(r => r.json());
},
+ async messages({ chat_id, limit = 200 } = {}) {
+ const u = new URL("/messages", location.origin);
+ if (chat_id) u.searchParams.set("chat_id", chat_id);
+ u.searchParams.set("limit", String(limit));
+ return fetch(u).then(r => r.json());
+ },
+
async runs({ chat_id, limit = 1 } = {}) {
const u = new URL("/runs", location.origin);
if (chat_id) u.searchParams.set("chat_id", chat_id);
diff --git a/src/controllers/messages.js b/src/controllers/messages.js
new file mode 100644
index 0000000..f9a3e1d
--- /dev/null
+++ b/src/controllers/messages.js
@@ -0,0 +1,18 @@
+import { handleListMessages } from "../handlers/messages.js";
+
+export const makeListMessages = (tenantIdOrFn) => async (req, res) => {
+ try {
+ const tenantId = typeof tenantIdOrFn === "function" ? tenantIdOrFn() : tenantIdOrFn;
+ const items = await handleListMessages({
+ tenantId,
+ chat_id: req.query.chat_id || null,
+ limit: req.query.limit || "200",
+ });
+ res.json({ items });
+ } catch (err) {
+ console.error(err);
+ res.status(500).json({ ok: false, error: "internal_error" });
+ }
+};
+
+
diff --git a/src/db/repo.js b/src/db/repo.js
index 482ddf6..04a5809 100644
--- a/src/db/repo.js
+++ b/src/db/repo.js
@@ -278,6 +278,27 @@ export async function getRecentMessagesForLLM({
}));
}
+export async function listMessages({ tenant_id, wa_chat_id, limit = 200 }) {
+ const lim = Math.max(1, Math.min(500, parseInt(limit, 10) || 200));
+ const q = `
+ select provider, message_id, direction, ts, text, payload, run_id
+ from wa_messages
+ where tenant_id=$1 and wa_chat_id=$2
+ order by ts asc
+ limit $3
+ `;
+ const { rows } = await pool.query(q, [tenant_id, wa_chat_id, lim]);
+ return rows.map((r) => ({
+ provider: r.provider,
+ message_id: r.message_id,
+ direction: r.direction,
+ ts: r.ts,
+ text: r.text,
+ payload: r.payload,
+ run_id: r.run_id,
+ }));
+}
+
export async function getTenantByKey(key) {
const { rows } = await pool.query(`select id, key, name from tenants where key=$1`, [key]);
return rows[0] || null;
diff --git a/src/handlers/messages.js b/src/handlers/messages.js
new file mode 100644
index 0000000..12e2541
--- /dev/null
+++ b/src/handlers/messages.js
@@ -0,0 +1,12 @@
+import { listMessages } from "../db/repo.js";
+
+export async function handleListMessages({ tenantId, chat_id, limit = "200" }) {
+ if (!chat_id) return [];
+ return listMessages({
+ tenant_id: tenantId,
+ wa_chat_id: String(chat_id),
+ limit: parseInt(limit, 10) || 200,
+ });
+}
+
+