@pindai-ai/chat-widget 3.0.0 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,18 +1,18 @@
1
- var d = (u, e, t) => new Promise((i, s) => {
1
+ var d = (u, e, i) => new Promise((t, s) => {
2
2
  var a = (l) => {
3
3
  try {
4
- r(t.next(l));
4
+ r(i.next(l));
5
5
  } catch (o) {
6
6
  s(o);
7
7
  }
8
8
  }, n = (l) => {
9
9
  try {
10
- r(t.throw(l));
10
+ r(i.throw(l));
11
11
  } catch (o) {
12
12
  s(o);
13
13
  }
14
- }, r = (l) => l.done ? i(l.value) : Promise.resolve(l.value).then(a, n);
15
- r((t = t.apply(u, e)).next());
14
+ }, r = (l) => l.done ? t(l.value) : Promise.resolve(l.value).then(a, n);
15
+ r((i = i.apply(u, e)).next());
16
16
  });
17
17
  const g = {
18
18
  en: {
@@ -65,7 +65,8 @@ const g = {
65
65
  humanAgentLabel: "Support Agent",
66
66
  actionIndicator: "Performing action...",
67
67
  thinkingIndicator: "Thinking...",
68
- poweredBy: "Powered by"
68
+ poweredBy: "Powered by",
69
+ waitingForAgent: "Waiting for a human agent..."
69
70
  },
70
71
  id: {
71
72
  // Widget UI
@@ -117,7 +118,8 @@ const g = {
117
118
  humanAgentLabel: "Agen Dukungan",
118
119
  actionIndicator: "Sedang memproses...",
119
120
  thinkingIndicator: "Sedang berpikir...",
120
- poweredBy: "Dibuat dengan"
121
+ poweredBy: "Dibuat dengan",
122
+ waitingForAgent: "Menunggu agen manusia..."
121
123
  }
122
124
  };
123
125
  class y {
@@ -136,13 +138,13 @@ class y {
136
138
  * @param {object} params - Parameters to substitute in the translation
137
139
  * @returns {string} Translated string
138
140
  */
139
- t(e, t = {}) {
141
+ t(e, i = {}) {
140
142
  var s;
141
- let i = ((s = g[this.locale]) == null ? void 0 : s[e]) || g.en[e] || e;
142
- return Object.keys(t).forEach((a) => {
143
+ let t = ((s = g[this.locale]) == null ? void 0 : s[e]) || g.en[e] || e;
144
+ return Object.keys(i).forEach((a) => {
143
145
  const n = new RegExp(`\\{${a}\\}`, "g");
144
- i = i.replace(n, t[a]);
145
- }), i;
146
+ t = t.replace(n, i[a]);
147
+ }), t;
146
148
  }
147
149
  /**
148
150
  * Change the current locale
@@ -169,12 +171,12 @@ class y {
169
171
  class w {
170
172
  constructor(e) {
171
173
  this.agentId = e.agentId || null, this.embedSecret = e.embedSecret || null, this.apiBaseUrl = e.apiBaseUrl ? e.apiBaseUrl.replace(/\/$/, "") : null;
172
- const t = e.webhookUrl || e.n8nUrl, i = !!(this.agentId && this.embedSecret && this.apiBaseUrl);
173
- if (!i && !!!t)
174
+ const i = e.webhookUrl || e.n8nUrl, t = !!(this.agentId && this.embedSecret && this.apiBaseUrl);
175
+ if (!t && !!!i)
174
176
  throw new Error(
175
177
  'PindaiChatWidget: Provide either (agentId + embedSecret + apiBaseUrl) for Pindai Agent-API mode, or "webhookUrl" for generic webhook mode.'
176
178
  );
177
- this.webhookUrl = i ? `${this.apiBaseUrl}/v1/chat/${this.agentId}/message` : t, this._agentMode = i, this._embedToken = null, this.mode = e.mode || "widget", this.locale = e.locale || "id", this.i18n = new y(this.locale), this.title = e.title || this.i18n.t("title"), this.initialMessage = e.initialMessage || this.i18n.t("initialMessage"), this.launcherIconUrl = e.launcherIconUrl || this._getDefaultLauncherIcon(), this.logoUrl = e.logoUrl || "https://pindai.ai/logo.png", this.showLogo = e.showLogo !== !1, this.avatarUrl = e.avatarUrl || null, this.launcherColor = e.launcherColor || "#2563eb", this.sendButtonColor = e.sendButtonColor || "#2563eb", this.accentColor = e.accentColor || "#2563eb", this.theme = e.theme || "light", this.buttonAlignment = e.buttonAlignment || "bottom-right";
179
+ this.webhookUrl = t ? `${this.apiBaseUrl}/v1/chat/${this.agentId}/message` : i, this._agentMode = t, this._embedToken = null, this.mode = e.mode || "widget", this.locale = e.locale || "id", this.i18n = new y(this.locale), this.title = e.title || this.i18n.t("title"), this.initialMessage = e.initialMessage || this.i18n.t("initialMessage"), this.launcherIconUrl = e.launcherIconUrl || this._getDefaultLauncherIcon(), this.logoUrl = e.logoUrl || "https://pindai.ai/logo.png", this.showLogo = e.showLogo !== !1, this.avatarUrl = e.avatarUrl || null, this.accentColor = e.accentColor || "#2563eb", this.launcherColor = e.launcherColor || this.accentColor, this.sendButtonColor = e.sendButtonColor || this.accentColor, this.theme = e.theme || "light", this.buttonAlignment = e.buttonAlignment || "bottom-right";
178
180
  const a = e.bubbleMessages || (e.bubbleText ? [e.bubbleText] : null);
179
181
  this.bubbleMessages = a && a.length > 0 ? a : null, this.bubbleDelay = typeof e.bubbleDelay == "number" ? e.bubbleDelay : 3e3, this.bubbleInterval = typeof e.bubbleInterval == "number" ? e.bubbleInterval : 5e3, this.showBubbleOnce = e.showBubbleOnce !== !1, this._bubbleDismissed = !1, this._bubbleMsgIndex = 0, this._bubbleRotateTimer = null, this._bubbleDelayTimer = null, this.customFooter = e.customFooter || null, this.showBranding = e.showBranding !== !1, this.showActionIndicators = e.showActionIndicators !== !1, this.visitorName = e.visitorName || null, this.visitorEmail = e.visitorEmail || null, this.enableStreaming = this._agentMode && e.enableStreaming !== !1, this.enableFileUpload = e.enableFileUpload !== !1, this.allowedFileTypes = e.allowedFileTypes || [
180
182
  "image/jpeg",
@@ -193,7 +195,20 @@ class w {
193
195
  this.i18n.t("quickReply4")
194
196
  ], this.enableHistory = e.enableHistory !== !1, this.maxHistoryItems = e.maxHistoryItems || 50;
195
197
  const n = this._agentMode ? this.agentId : this.webhookUrl;
196
- this.historyKey = `pindai-chat-history-${n}`, this.stateKey = `pindai-chat-state-${n}`, this.maxRetries = e.maxRetries || 3, this.retryDelay = e.retryDelay || 1e3, this.requestTimeout = e.requestTimeout || 3e4, this.rateLimit = e.rateLimit || 5, this.rateLimitWindow = e.rateLimitWindow || 6e4, this.messageTimes = [], this.container = null, this.launcher = null, this.bubblePopup = null, this.chatWindow = null, this.messageList = null, this.input = null, this.button = null, this.closeButton = null, this.pollingUrl = this._agentMode ? `${this.apiBaseUrl}/v1/chat/${this.agentId}/messages` : e.pollingUrl || null, this.pollingEnabled = !!(this.pollingUrl || e.pollingUrl), this.pollingInterval = e.pollingInterval || 3e3, this._pollingTimer = null, this._lastMessageTimestamp = null, this.sessionId = `web-session-${Date.now()}-${Math.random()}`, this.isLoading = !1, this.isOpen = !1, this.isOnline = navigator.onLine, this.loadState(), this._setupOfflineDetection(), this._applyTheme(), this.mode === "fullscreen" ? this._initChatWindow() : this._initLauncher();
198
+ this.historyKey = `pindai-chat-history-${n}`, this.stateKey = `pindai-chat-state-${n}`, this.maxRetries = e.maxRetries || 3, this.retryDelay = e.retryDelay || 1e3, this.requestTimeout = e.requestTimeout || 3e4, this.rateLimit = e.rateLimit || 5, this.rateLimitWindow = e.rateLimitWindow || 6e4, this.messageTimes = [], this.container = null, this.launcher = null, this.bubblePopup = null, this.chatWindow = null, this.messageList = null, this.input = null, this.button = null, this.closeButton = null, this.pollingUrl = this._agentMode ? `${this.apiBaseUrl}/v1/chat/${this.agentId}/messages` : e.pollingUrl || null, this.pollingEnabled = !!(this.pollingUrl || e.pollingUrl), this.pollingInterval = e.pollingInterval || 3e3, this._pollingTimer = null, this._lastMessageTimestamp = null;
199
+ const r = this._agentMode ? `pindai-session-id-${this.agentId}` : null, l = r ? (() => {
200
+ try {
201
+ return localStorage.getItem(r);
202
+ } catch (o) {
203
+ return null;
204
+ }
205
+ })() : null;
206
+ if (this.sessionId = l || `web-session-${Date.now()}-${Math.random()}`, r && !l)
207
+ try {
208
+ localStorage.setItem(r, this.sessionId);
209
+ } catch (o) {
210
+ }
211
+ this.isLoading = !1, this.isOpen = !1, this.isOnline = navigator.onLine, this.loadState(), this._setupOfflineDetection(), this._applyTheme(), this.mode === "fullscreen" ? this._initChatWindow() : this._initLauncher();
197
212
  }
198
213
  // ─────────────────────────────────────────────────────────────────────────
199
214
  // Auth helpers (Agent-API mode)
@@ -202,10 +217,10 @@ class w {
202
217
  * Convert hex string to Uint8Array
203
218
  */
204
219
  _hexToBytes(e) {
205
- const t = new Uint8Array(e.length / 2);
206
- for (let i = 0; i < e.length; i += 2)
207
- t[i / 2] = parseInt(e.slice(i, i + 2), 16);
208
- return t;
220
+ const i = new Uint8Array(e.length / 2);
221
+ for (let t = 0; t < e.length; t += 2)
222
+ i[t / 2] = parseInt(e.slice(t, t + 2), 16);
223
+ return i;
209
224
  }
210
225
  /**
211
226
  * Generate HMAC-SHA256 embed token using native crypto.subtle
@@ -220,8 +235,8 @@ class w {
220
235
  { name: "HMAC", hash: "SHA-256" },
221
236
  !1,
222
237
  ["sign"]
223
- ), t = new TextEncoder().encode(`${this.agentId}:${this.sessionId}`), i = yield crypto.subtle.sign("HMAC", e, t);
224
- return this._embedToken = Array.from(new Uint8Array(i)).map((s) => s.toString(16).padStart(2, "0")).join(""), this._embedToken;
238
+ ), i = new TextEncoder().encode(`${this.agentId}:${this.sessionId}`), t = yield crypto.subtle.sign("HMAC", e, i);
239
+ return this._embedToken = Array.from(new Uint8Array(t)).map((s) => s.toString(16).padStart(2, "0")).join(""), this._embedToken;
225
240
  });
226
241
  }
227
242
  // ─────────────────────────────────────────────────────────────────────────
@@ -231,13 +246,13 @@ class w {
231
246
  if (this.theme === "dark")
232
247
  document.documentElement.setAttribute("data-pindai-theme", "dark");
233
248
  else if (this.theme === "auto") {
234
- const e = window.matchMedia("(prefers-color-scheme: dark)"), t = (i) => {
249
+ const e = window.matchMedia("(prefers-color-scheme: dark)"), i = (t) => {
235
250
  document.documentElement.setAttribute(
236
251
  "data-pindai-theme",
237
- i.matches ? "dark" : "light"
252
+ t.matches ? "dark" : "light"
238
253
  );
239
254
  };
240
- t(e), e.addEventListener("change", t);
255
+ i(e), e.addEventListener("change", i);
241
256
  }
242
257
  }
243
258
  // ─────────────────────────────────────────────────────────────────────────
@@ -259,12 +274,12 @@ class w {
259
274
  }, this.bubbleDelay);
260
275
  }
261
276
  this.launcher = document.createElement("div"), this.launcher.className = "n8n-chat-launcher", this.launcher.style.backgroundColor = this.launcherColor, this.launcher.setAttribute("role", "button"), this.launcher.setAttribute("aria-label", this.i18n.t("ariaOpenChat")), this.launcher.setAttribute("tabindex", "0");
262
- const t = document.createElement("img");
263
- t.src = this.launcherIconUrl, t.alt = "", t.onerror = () => {
264
- t.onerror = null, t.src = this._getDefaultLauncherIcon();
277
+ const i = document.createElement("img");
278
+ i.src = this.launcherIconUrl, i.alt = "", i.onerror = () => {
279
+ i.onerror = null, i.src = this._getDefaultLauncherIcon();
265
280
  };
266
- const i = document.createElement("span");
267
- i.className = "n8n-chat-unread-badge", i.style.display = "none", i.textContent = "0", this.launcher.appendChild(t), this.launcher.appendChild(i), e.appendChild(this.launcher), document.body.appendChild(e), this.launcher.addEventListener("click", () => this.toggleChatWindow()), this.launcher.addEventListener("keydown", (s) => {
281
+ const t = document.createElement("span");
282
+ t.className = "n8n-chat-unread-badge", t.style.display = "none", t.textContent = "0", this.launcher.appendChild(i), this.launcher.appendChild(t), e.appendChild(this.launcher), document.body.appendChild(e), this.launcher.addEventListener("click", () => this.toggleChatWindow()), this.launcher.addEventListener("keydown", (s) => {
268
283
  (s.key === "Enter" || s.key === " ") && (s.preventDefault(), this.toggleChatWindow());
269
284
  });
270
285
  }
@@ -291,7 +306,7 @@ class w {
291
306
  // Chat Window
292
307
  // ─────────────────────────────────────────────────────────────────────────
293
308
  _initChatWindow() {
294
- this.container = document.createElement("div"), this.container.className = `n8n-chat-widget ${this.mode === "fullscreen" ? "n8n-chat-widget--fullscreen" : ""}`, this.container.setAttribute("role", "dialog"), this.container.setAttribute("aria-modal", "true"), this.container.setAttribute("aria-label", this.title);
309
+ this.container = document.createElement("div"), this.container.className = `n8n-chat-widget ${this.mode === "fullscreen" ? "n8n-chat-widget--fullscreen" : ""}`, this.container.setAttribute("role", "dialog"), this.container.setAttribute("aria-modal", "true"), this.container.setAttribute("aria-label", this.title), this.container.style.setProperty("--pindai-primary", this.accentColor), this.container.style.setProperty("--pindai-primary-dark", this.accentColor);
295
310
  const e = this._buildFooterHtml();
296
311
  this.container.innerHTML = `
297
312
  <div class="n8n-chat-header">
@@ -320,11 +335,11 @@ class w {
320
335
  </button>
321
336
  </div>
322
337
  ${this.enableFileUpload ? '<div class="n8n-chat-file-preview" style="display: none;"></div>' : ""}
323
- `, this._injectHeaderImage(), this.mode === "widget" ? document.body.appendChild(this.container) : (document.body.innerHTML = "", document.body.appendChild(this.container), document.body.style.margin = "0"), this.messageList = this.container.querySelector(".n8n-chat-messages"), this.input = this.container.querySelector('input[type="text"]'), this.button = this.container.querySelector(".n8n-chat-send-btn"), this.closeButton = this.container.querySelector(".n8n-chat-close-btn"), this.button.addEventListener("click", (t) => {
324
- t.preventDefault(), this.sendMessage();
325
- }), this.input.addEventListener("keypress", (t) => {
326
- t.key === "Enter" && (t.preventDefault(), this.sendMessage());
327
- }), this.mode === "fullscreen" ? this.closeButton.style.display = "none" : this.closeButton.addEventListener("click", () => this.toggleChatWindow()), this.enableFileUpload && this.container.querySelector('input[type="file"]').addEventListener("change", (i) => this._handleFileSelect(i)), this._setupKeyboardNavigation(), this.loadHistory(), this.messageList.children.length === 0 && this.addMessage(this.initialMessage, "ai");
338
+ `, this._injectHeaderImage(), this.mode === "widget" ? document.body.appendChild(this.container) : (document.body.innerHTML = "", document.body.appendChild(this.container), document.body.style.margin = "0"), this.messageList = this.container.querySelector(".n8n-chat-messages"), this.input = this.container.querySelector('input[type="text"]'), this.button = this.container.querySelector(".n8n-chat-send-btn"), this.closeButton = this.container.querySelector(".n8n-chat-close-btn"), this.button.addEventListener("click", (i) => {
339
+ i.preventDefault(), this.sendMessage();
340
+ }), this.input.addEventListener("keypress", (i) => {
341
+ i.key === "Enter" && (i.preventDefault(), this.sendMessage());
342
+ }), this.mode === "fullscreen" ? this.closeButton.style.display = "none" : this.closeButton.addEventListener("click", () => this.toggleChatWindow()), this.enableFileUpload && this.container.querySelector('input[type="file"]').addEventListener("change", (t) => this._handleFileSelect(t)), this._setupKeyboardNavigation(), this.loadHistory(), this.messageList.children.length === 0 && this.addMessage(this.initialMessage, "ai");
328
343
  }
329
344
  /**
330
345
  * Inject the header avatar/logo as a real DOM element so we can attach
@@ -335,17 +350,17 @@ class w {
335
350
  _injectHeaderImage() {
336
351
  const e = this.container.querySelector(".n8n-chat-header-content");
337
352
  if (!e) return;
338
- const t = e.querySelector(".n8n-chat-title");
353
+ const i = e.querySelector(".n8n-chat-title");
339
354
  if (this.avatarUrl) {
340
- const i = document.createElement("img");
341
- i.className = "n8n-chat-header-avatar", i.alt = "", i.src = this.avatarUrl, i.onerror = () => {
342
- i.onerror = null, i.remove();
343
- }, e.insertBefore(i, t);
355
+ const t = document.createElement("img");
356
+ t.className = "n8n-chat-header-avatar", t.alt = "", t.src = this.avatarUrl, t.onerror = () => {
357
+ t.onerror = null, t.remove();
358
+ }, e.insertBefore(t, i);
344
359
  } else if (this.showLogo) {
345
- const i = document.createElement("img");
346
- i.className = "n8n-chat-logo", i.alt = "", i.src = this.logoUrl, i.onerror = () => {
347
- i.onerror = null, i.remove();
348
- }, e.insertBefore(i, t);
360
+ const t = document.createElement("img");
361
+ t.className = "n8n-chat-logo", t.alt = "", t.src = this.logoUrl, t.onerror = () => {
362
+ t.onerror = null, t.remove();
363
+ }, e.insertBefore(t, i);
349
364
  }
350
365
  }
351
366
  /**
@@ -373,6 +388,28 @@ class w {
373
388
  _escapeHtml(e) {
374
389
  return String(e).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
375
390
  }
391
+ /**
392
+ * Safe markdown renderer for AI messages.
393
+ * Supports: bold, italic, inline code, fenced code blocks, headings,
394
+ * unordered/ordered lists, links. Always escapes HTML first to prevent XSS.
395
+ */
396
+ _renderMarkdown(e) {
397
+ const i = [];
398
+ let t = String(e).replace(/```([\s\S]*?)```/g, (s, a) => {
399
+ const n = i.length;
400
+ return i.push(`<pre><code>${this._escapeHtml(a.replace(/^\n/, "").replace(/\n$/, ""))}</code></pre>`), `\0CODE${n}\0`;
401
+ });
402
+ return t = this._escapeHtml(t), t = t.replace(/`([^`]+)`/g, "<code>$1</code>"), t = t.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>"), t = t.replace(/__([^_]+)__/g, "<strong>$1</strong>"), t = t.replace(/\*([^*]+)\*/g, "<em>$1</em>"), t = t.replace(/_([^_]+)_/g, "<em>$1</em>"), t = t.replace(/^### (.+)$/gm, "<h5>$1</h5>"), t = t.replace(/^## (.+)$/gm, "<h4>$1</h4>"), t = t.replace(/^# (.+)$/gm, "<h3>$1</h3>"), t = t.replace(/^(?:[*-] .+\n?)+/gm, (s) => `<ul>${s.trim().split(`
403
+ `).map(
404
+ (n) => `<li>${n.replace(/^[*-] /, "")}</li>`
405
+ ).join("")}</ul>`), t = t.replace(/^(?:\d+\. .+\n?)+/gm, (s) => `<ol>${s.trim().split(`
406
+ `).map(
407
+ (n) => `<li>${n.replace(/^\d+\. /, "")}</li>`
408
+ ).join("")}</ol>`), t = t.replace(
409
+ /\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g,
410
+ '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>'
411
+ ), t = t.replace(/\n/g, "<br>"), t = t.replace(/\x00CODE(\d+)\x00/g, (s, a) => i[parseInt(a)]), t;
412
+ }
376
413
  // ─────────────────────────────────────────────────────────────────────────
377
414
  // Toggle open/close
378
415
  // ─────────────────────────────────────────────────────────────────────────
@@ -391,8 +428,8 @@ class w {
391
428
  // Timestamp formatting
392
429
  // ─────────────────────────────────────────────────────────────────────────
393
430
  _formatTimestamp(e) {
394
- const i = /* @__PURE__ */ new Date() - e;
395
- return i < 6e4 ? this.i18n.t("justNow") : i < 36e5 ? this.i18n.t("minutesAgo", { minutes: Math.floor(i / 6e4) }) : e.toLocaleTimeString(this.locale === "id" ? "id-ID" : "en-US", {
431
+ const t = /* @__PURE__ */ new Date() - e;
432
+ return t < 6e4 ? this.i18n.t("justNow") : t < 36e5 ? this.i18n.t("minutesAgo", { minutes: Math.floor(t / 6e4) }) : e.toLocaleTimeString(this.locale === "id" ? "id-ID" : "en-US", {
396
433
  hour: "2-digit",
397
434
  minute: "2-digit"
398
435
  });
@@ -400,13 +437,13 @@ class w {
400
437
  // ─────────────────────────────────────────────────────────────────────────
401
438
  // Messages
402
439
  // ─────────────────────────────────────────────────────────────────────────
403
- addMessage(e, t, i = /* @__PURE__ */ new Date()) {
440
+ addMessage(e, i, t = /* @__PURE__ */ new Date()) {
404
441
  const s = document.createElement("div");
405
- s.className = `n8n-chat-bubble n8n-chat-${t}-message`;
442
+ s.className = `n8n-chat-bubble n8n-chat-${i}-message`;
406
443
  const a = document.createElement("div");
407
- a.className = "n8n-chat-message-text", a.textContent = e;
444
+ a.className = "n8n-chat-message-text", i === "ai" ? a.innerHTML = this._renderMarkdown(e) : a.textContent = e;
408
445
  const n = document.createElement("div");
409
- n.className = "n8n-chat-message-timestamp", n.textContent = this._formatTimestamp(i), n.setAttribute("data-timestamp", i.toISOString()), s.appendChild(a), s.appendChild(n), this.messageList.appendChild(s), this.messageList.scrollTop = this.messageList.scrollHeight, this.saveToHistory(e, t, i), !this.isOpen && t === "ai" && this._incrementUnread();
446
+ n.className = "n8n-chat-message-timestamp", n.textContent = this._formatTimestamp(t), n.setAttribute("data-timestamp", t.toISOString()), s.appendChild(a), s.appendChild(n), this.messageList.appendChild(s), this.messageList.scrollTop = this.messageList.scrollHeight, this.saveToHistory(e, i, t), !this.isOpen && i === "ai" && this._incrementUnread();
410
447
  }
411
448
  /**
412
449
  * Create a streaming message bubble (text filled incrementally via SSE).
@@ -415,69 +452,69 @@ class w {
415
452
  _createStreamingBubble() {
416
453
  const e = document.createElement("div");
417
454
  e.className = "n8n-chat-bubble n8n-chat-ai-message n8n-chat-bubble--streaming";
418
- const t = document.createElement("div");
419
- t.className = "n8n-chat-message-text", t.textContent = "";
420
- const i = document.createElement("span");
421
- i.className = "n8n-chat-stream-cursor";
455
+ const i = document.createElement("div");
456
+ i.className = "n8n-chat-message-text", i.textContent = "";
457
+ const t = document.createElement("span");
458
+ t.className = "n8n-chat-stream-cursor";
422
459
  const s = document.createElement("div");
423
- return s.className = "n8n-chat-message-timestamp", s.textContent = this._formatTimestamp(/* @__PURE__ */ new Date()), e.appendChild(t), e.appendChild(i), e.appendChild(s), this.messageList.appendChild(e), this.messageList.scrollTop = this.messageList.scrollHeight, { bubble: e, textNode: t, cursor: i, timeNode: s };
460
+ return s.className = "n8n-chat-message-timestamp", s.textContent = this._formatTimestamp(/* @__PURE__ */ new Date()), e.appendChild(i), e.appendChild(t), e.appendChild(s), this.messageList.appendChild(e), this.messageList.scrollTop = this.messageList.scrollHeight, { bubble: e, textNode: i, cursor: t, timeNode: s };
424
461
  }
425
- addMessageWithoutSaving(e, t, i) {
462
+ addMessageWithoutSaving(e, i, t) {
426
463
  const s = document.createElement("div");
427
- s.className = `n8n-chat-bubble n8n-chat-${t}-message`;
464
+ s.className = `n8n-chat-bubble n8n-chat-${i}-message`;
428
465
  const a = document.createElement("div");
429
- a.className = "n8n-chat-message-text", a.textContent = e;
466
+ a.className = "n8n-chat-message-text", i === "ai" ? a.innerHTML = this._renderMarkdown(e) : a.textContent = e;
430
467
  const n = document.createElement("div");
431
- n.className = "n8n-chat-message-timestamp", n.textContent = this._formatTimestamp(i), n.setAttribute("data-timestamp", i.toISOString()), s.appendChild(a), s.appendChild(n), this.messageList.appendChild(s), this.messageList.scrollTop = this.messageList.scrollHeight;
468
+ n.className = "n8n-chat-message-timestamp", n.textContent = this._formatTimestamp(t), n.setAttribute("data-timestamp", t.toISOString()), s.appendChild(a), s.appendChild(n), this.messageList.appendChild(s), this.messageList.scrollTop = this.messageList.scrollHeight;
432
469
  }
433
470
  // ─────────────────────────────────────────────────────────────────────────
434
471
  // Typing indicator
435
472
  // ─────────────────────────────────────────────────────────────────────────
436
473
  _showTypingIndicator(e) {
437
- let t = this.messageList.querySelector(".n8n-chat-typing-indicator");
474
+ let i = this.messageList.querySelector(".n8n-chat-typing-indicator");
438
475
  if (e) {
439
- if (!t) {
440
- t = document.createElement("div"), t.className = "n8n-chat-bubble n8n-chat-ai-message n8n-chat-typing-indicator";
441
- const i = "<span></span><span></span><span></span>";
442
- this.showActionIndicators ? t.innerHTML = `
443
- <div class="n8n-chat-typing-dots">${i}</div>
476
+ if (!i) {
477
+ i = document.createElement("div"), i.className = "n8n-chat-bubble n8n-chat-ai-message n8n-chat-typing-indicator";
478
+ const t = "<span></span><span></span><span></span>";
479
+ this.showActionIndicators ? i.innerHTML = `
480
+ <div class="n8n-chat-typing-dots">${t}</div>
444
481
  <span class="n8n-chat-typing-label">${this.i18n.t("thinkingIndicator")}</span>
445
- ` : t.innerHTML = i, t.setAttribute("aria-label", this.i18n.t("typingIndicator")), this.messageList.appendChild(t), this.messageList.scrollTop = this.messageList.scrollHeight;
482
+ ` : i.innerHTML = t, i.setAttribute("aria-label", this.i18n.t("typingIndicator")), this.messageList.appendChild(i), this.messageList.scrollTop = this.messageList.scrollHeight;
446
483
  }
447
- } else t && t.remove();
484
+ } else i && i.remove();
448
485
  }
449
486
  // ─────────────────────────────────────────────────────────────────────────
450
487
  // File upload
451
488
  // ─────────────────────────────────────────────────────────────────────────
452
489
  _handleFileSelect(e) {
453
- Array.from(e.target.files).forEach((i) => {
454
- if (!this.allowedFileTypes.includes(i.type)) {
455
- this.addMessage(this.i18n.t("fileTypeNotSupported", { filename: i.name }), "ai");
490
+ Array.from(e.target.files).forEach((t) => {
491
+ if (!this.allowedFileTypes.includes(t.type)) {
492
+ this.addMessage(this.i18n.t("fileTypeNotSupported", { filename: t.name }), "ai");
456
493
  return;
457
494
  }
458
- if (i.size > this.maxFileSize) {
495
+ if (t.size > this.maxFileSize) {
459
496
  const s = this.maxFileSize / 1024 / 1024;
460
- this.addMessage(this.i18n.t("fileTooLarge", { filename: i.name, maxSize: s }), "ai");
497
+ this.addMessage(this.i18n.t("fileTooLarge", { filename: t.name, maxSize: s }), "ai");
461
498
  return;
462
499
  }
463
500
  if (this.uploadedFiles.length >= this.maxFiles) {
464
501
  this.addMessage(this.i18n.t("maxFilesExceeded", { maxFiles: this.maxFiles }), "ai");
465
502
  return;
466
503
  }
467
- this.uploadedFiles.push(i), this._renderFilePreview(i);
504
+ this.uploadedFiles.push(t), this._renderFilePreview(t);
468
505
  }), e.target.value = "";
469
506
  }
470
507
  _renderFilePreview(e) {
471
- const t = this.container.querySelector(".n8n-chat-file-preview");
472
- if (!t) return;
473
- t.style.display = "flex";
474
- const i = document.createElement("div");
475
- i.className = "n8n-chat-file-item", i.innerHTML = `
508
+ const i = this.container.querySelector(".n8n-chat-file-preview");
509
+ if (!i) return;
510
+ i.style.display = "flex";
511
+ const t = document.createElement("div");
512
+ t.className = "n8n-chat-file-item", t.innerHTML = `
476
513
  <span class="n8n-chat-file-name">${this._escapeHtml(e.name)}</span>
477
514
  <button class="n8n-chat-file-remove" aria-label="${this.i18n.t("ariaRemoveFile")}">&times;</button>
478
- `, i.querySelector(".n8n-chat-file-remove").addEventListener("click", () => {
479
- this.uploadedFiles = this.uploadedFiles.filter((s) => s.name !== e.name), i.remove(), this.uploadedFiles.length === 0 && (t.style.display = "none");
480
- }), t.appendChild(i);
515
+ `, t.querySelector(".n8n-chat-file-remove").addEventListener("click", () => {
516
+ this.uploadedFiles = this.uploadedFiles.filter((s) => s.name !== e.name), t.remove(), this.uploadedFiles.length === 0 && (i.style.display = "none");
517
+ }), i.appendChild(t);
481
518
  }
482
519
  // ─────────────────────────────────────────────────────────────────────────
483
520
  // Send message — entry point
@@ -488,8 +525,8 @@ class w {
488
525
  if (!(!e && this.uploadedFiles.length === 0 || this.isLoading)) {
489
526
  try {
490
527
  this._checkRateLimit();
491
- } catch (t) {
492
- this.addMessage(t.message, "ai");
528
+ } catch (i) {
529
+ this.addMessage(i.message, "ai");
493
530
  return;
494
531
  }
495
532
  if (!this.isOnline) {
@@ -501,16 +538,16 @@ class w {
501
538
  if (this._agentMode && this.enableStreaming && this.uploadedFiles.length === 0)
502
539
  yield this._sendWithStreaming(e);
503
540
  else {
504
- const t = yield this._sendMessageWithRetry(e, this.uploadedFiles);
505
- this._showTypingIndicator(!1), this.addMessage(t, "ai"), this.showQuickReplies && this.quickReplies.length > 0 && this._renderQuickReplies();
541
+ const i = yield this._sendMessageWithRetry(e, this.uploadedFiles);
542
+ this._showTypingIndicator(!1), this.addMessage(i, "ai"), this.showQuickReplies && this.quickReplies.length > 0 && this._renderQuickReplies();
506
543
  }
507
- } catch (t) {
508
- this._showTypingIndicator(!1), this.addMessage(this._getErrorMessage(t), "ai");
544
+ } catch (i) {
545
+ this._showTypingIndicator(!1), this.addMessage(this._getErrorMessage(i), "ai");
509
546
  } finally {
510
547
  if (this.isLoading = !1, this.button.disabled = !1, this.input.disabled = !1, this.input.focus(), this.uploadedFiles.length > 0) {
511
548
  this.uploadedFiles = [];
512
- const t = this.container.querySelector(".n8n-chat-file-preview");
513
- t && (t.innerHTML = "", t.style.display = "none");
549
+ const i = this.container.querySelector(".n8n-chat-file-preview");
550
+ i && (i.innerHTML = "", i.style.display = "none");
514
551
  }
515
552
  }
516
553
  }
@@ -521,13 +558,13 @@ class w {
521
558
  // ─────────────────────────────────────────────────────────────────────────
522
559
  _sendWithStreaming(e) {
523
560
  return d(this, null, function* () {
524
- const t = yield this._generateEmbedToken(), i = new URLSearchParams({
561
+ const i = yield this._generateEmbedToken(), t = new URLSearchParams({
525
562
  message: e,
526
563
  sessionId: this.sessionId,
527
- embedToken: t
564
+ embedToken: i
528
565
  });
529
- this.visitorName && i.set("visitor_name", this.visitorName), this.visitorEmail && i.set("visitor_email", this.visitorEmail);
530
- const s = `${this.apiBaseUrl}/v1/chat/${this.agentId}/stream?${i}`;
566
+ this.visitorName && t.set("visitor_name", this.visitorName), this.visitorEmail && t.set("visitor_email", this.visitorEmail);
567
+ const s = `${this.apiBaseUrl}/v1/chat/${this.agentId}/stream?${t}`;
531
568
  return new Promise((a, n) => {
532
569
  this._showTypingIndicator(!1);
533
570
  const { bubble: r, textNode: l, cursor: o, timeNode: c } = this._createStreamingBubble();
@@ -536,7 +573,7 @@ class w {
536
573
  p.onmessage = (f) => {
537
574
  try {
538
575
  const m = JSON.parse(f.data);
539
- m.delta && (h += m.delta, l.textContent = h, this.messageList.scrollTop = this.messageList.scrollHeight), m.type === "done" && (p.close(), o.remove(), r.classList.remove("n8n-chat-bubble--streaming"), c.textContent = this._formatTimestamp(/* @__PURE__ */ new Date()), b = m.status || "active", this.saveToHistory(h, "ai"), b && b !== "active" && (this._startPolling(), this.addMessage(this.i18n.t("humanAgentJoined"), "ai")), this.showQuickReplies && this.quickReplies.length > 0 && this._renderQuickReplies(), a(h));
576
+ m.delta && (h += m.delta, l.textContent = h, this.messageList.scrollTop = this.messageList.scrollHeight), m.type === "done" && (p.close(), o.remove(), r.classList.remove("n8n-chat-bubble--streaming"), c.textContent = this._formatTimestamp(/* @__PURE__ */ new Date()), l.innerHTML = this._renderMarkdown(h), b = m.status || "active", this.saveToHistory(h, "ai"), b && b !== "active" && (this._startPolling(), this._showHandoffBanner(!0), this.addMessage(this.i18n.t("humanAgentJoined"), "ai")), this.showQuickReplies && this.quickReplies.length > 0 && this._renderQuickReplies(), a(h));
540
577
  } catch (m) {
541
578
  }
542
579
  }, p.onerror = () => {
@@ -549,14 +586,14 @@ class w {
549
586
  // POST with retry (agent-api mode + legacy mode)
550
587
  // ─────────────────────────────────────────────────────────────────────────
551
588
  _sendMessageWithRetry(s) {
552
- return d(this, arguments, function* (e, t = [], i = 0) {
589
+ return d(this, arguments, function* (e, i = [], t = 0) {
553
590
  try {
554
591
  const a = new AbortController(), n = setTimeout(() => a.abort(), this.requestTimeout), r = new FormData();
555
592
  if (r.append("sessionId", this.sessionId), r.append("message", e), this._agentMode) {
556
593
  const c = yield this._generateEmbedToken();
557
594
  r.append("embedToken", c), this.visitorName && r.append("visitor_name", this.visitorName), this.visitorEmail && r.append("visitor_email", this.visitorEmail);
558
595
  } else this.embedToken && r.append("embedToken", this.embedToken);
559
- t.forEach((c, h) => {
596
+ i.forEach((c, h) => {
560
597
  r.append(`file${h}`, c);
561
598
  });
562
599
  const l = yield fetch(this.webhookUrl, {
@@ -565,39 +602,39 @@ class w {
565
602
  signal: a.signal
566
603
  });
567
604
  if (clearTimeout(n), !l.ok) {
568
- if (l.status >= 500 && i < this.maxRetries)
569
- return yield this._delay(this.retryDelay * (i + 1)), this._sendMessageWithRetry(e, t, i + 1);
605
+ if (l.status >= 500 && t < this.maxRetries)
606
+ return yield this._delay(this.retryDelay * (t + 1)), this._sendMessageWithRetry(e, i, t + 1);
570
607
  const c = yield l.json().catch(() => ({}));
571
608
  throw new Error(c.message || `Network error: ${l.statusText}`);
572
609
  }
573
610
  const o = yield l.json();
574
611
  if (!o.response)
575
612
  throw new Error(this.i18n.t("errorInvalidResponse"));
576
- return o.status && o.status !== "active" && this.pollingUrl ? (this._startPolling(), this.addMessage(this.i18n.t("humanAgentJoined"), "ai")) : this._stopPolling(), o.response;
613
+ return o.status && o.status !== "active" && this.pollingUrl ? (this._startPolling(), this._showHandoffBanner(!0), this.addMessage(this.i18n.t("humanAgentJoined"), "ai")) : this._stopPolling(), o.response;
577
614
  } catch (a) {
578
615
  if (a.name === "AbortError")
579
616
  throw new Error(this.i18n.t("errorTimeout"));
580
- if ((a.message.includes("NetworkError") || a.message.includes("Failed to fetch")) && i < this.maxRetries)
581
- return yield this._delay(this.retryDelay * (i + 1)), this._sendMessageWithRetry(e, t, i + 1);
617
+ if ((a.message.includes("NetworkError") || a.message.includes("Failed to fetch")) && t < this.maxRetries)
618
+ return yield this._delay(this.retryDelay * (t + 1)), this._sendMessageWithRetry(e, i, t + 1);
582
619
  throw a;
583
620
  }
584
621
  });
585
622
  }
586
623
  _delay(e) {
587
- return new Promise((t) => setTimeout(t, e));
624
+ return new Promise((i) => setTimeout(i, e));
588
625
  }
589
626
  _getErrorMessage(e) {
590
- const t = e.message || "";
591
- return t.includes("timeout") || t.includes("Timeout") ? this.i18n.t("errorTimeout") : t.includes("NetworkError") || t.includes("Failed to fetch") ? this.i18n.t("errorNetwork") : t.includes("500") || t.includes("503") ? this.i18n.t("errorServer") : t === this.i18n.t("streamingError") ? t : this.i18n.t("errorGeneric");
627
+ const i = e.message || "";
628
+ return i.includes("timeout") || i.includes("Timeout") ? this.i18n.t("errorTimeout") : i.includes("NetworkError") || i.includes("Failed to fetch") ? this.i18n.t("errorNetwork") : i.includes("500") || i.includes("503") ? this.i18n.t("errorServer") : i === this.i18n.t("streamingError") ? i : this.i18n.t("errorGeneric");
592
629
  }
593
630
  // ─────────────────────────────────────────────────────────────────────────
594
631
  // Rate limiting
595
632
  // ─────────────────────────────────────────────────────────────────────────
596
633
  _checkRateLimit() {
597
634
  const e = Date.now();
598
- if (this.messageTimes = this.messageTimes.filter((t) => e - t < this.rateLimitWindow), this.messageTimes.length >= this.rateLimit) {
599
- const t = Math.ceil((this.rateLimitWindow - (e - this.messageTimes[0])) / 1e3);
600
- throw new Error(this.i18n.t("errorRateLimit", { seconds: t }));
635
+ if (this.messageTimes = this.messageTimes.filter((i) => e - i < this.rateLimitWindow), this.messageTimes.length >= this.rateLimit) {
636
+ const i = Math.ceil((this.rateLimitWindow - (e - this.messageTimes[0])) / 1e3);
637
+ throw new Error(this.i18n.t("errorRateLimit", { seconds: i }));
601
638
  }
602
639
  this.messageTimes.push(e);
603
640
  }
@@ -606,15 +643,15 @@ class w {
606
643
  // ─────────────────────────────────────────────────────────────────────────
607
644
  _renderQuickReplies(e = this.quickReplies) {
608
645
  if (!this.showQuickReplies || e.length === 0) return;
609
- const t = this.messageList.querySelector(".n8n-chat-quick-replies");
610
- t && t.remove();
611
- const i = document.createElement("div");
612
- i.className = "n8n-chat-quick-replies", e.forEach((s) => {
646
+ const i = this.messageList.querySelector(".n8n-chat-quick-replies");
647
+ i && i.remove();
648
+ const t = document.createElement("div");
649
+ t.className = "n8n-chat-quick-replies", e.forEach((s) => {
613
650
  const a = document.createElement("button");
614
651
  a.className = "n8n-chat-quick-reply-btn", a.textContent = s, a.addEventListener("click", () => {
615
- this.input.value = s, this.sendMessage(), i.remove();
616
- }), i.appendChild(a);
617
- }), this.messageList.appendChild(i), this.messageList.scrollTop = this.messageList.scrollHeight;
652
+ this.input.value = s, this.sendMessage(), t.remove();
653
+ }), t.appendChild(a);
654
+ }), this.messageList.appendChild(t), this.messageList.scrollTop = this.messageList.scrollHeight;
618
655
  }
619
656
  // ─────────────────────────────────────────────────────────────────────────
620
657
  // Notification badge
@@ -638,19 +675,19 @@ class w {
638
675
  try {
639
676
  const e = localStorage.getItem(this.historyKey);
640
677
  if (!e) return;
641
- JSON.parse(e).forEach((t) => {
642
- this.addMessageWithoutSaving(t.text, t.sender, new Date(t.timestamp));
678
+ JSON.parse(e).forEach((i) => {
679
+ this.addMessageWithoutSaving(i.text, i.sender, new Date(i.timestamp));
643
680
  });
644
681
  } catch (e) {
645
682
  console.warn("Failed to load chat history:", e);
646
683
  }
647
684
  }
648
- saveToHistory(e, t, i = /* @__PURE__ */ new Date()) {
685
+ saveToHistory(e, i, t = /* @__PURE__ */ new Date()) {
649
686
  if (this.enableHistory)
650
687
  try {
651
688
  const s = localStorage.getItem(this.historyKey);
652
689
  let a = s ? JSON.parse(s) : [];
653
- a.push({ text: e, sender: t, timestamp: i.toISOString() }), a.length > this.maxHistoryItems && (a = a.slice(-this.maxHistoryItems)), localStorage.setItem(this.historyKey, JSON.stringify(a));
690
+ a.push({ text: e, sender: i, timestamp: t.toISOString() }), a.length > this.maxHistoryItems && (a = a.slice(-this.maxHistoryItems)), localStorage.setItem(this.historyKey, JSON.stringify(a));
654
691
  } catch (s) {
655
692
  console.warn("Failed to save chat history:", s);
656
693
  }
@@ -662,8 +699,8 @@ class w {
662
699
  try {
663
700
  const e = localStorage.getItem(this.stateKey);
664
701
  if (e) {
665
- const t = JSON.parse(e);
666
- this._bubbleDismissed = this.showBubbleOnce && t.bubbleDismissed || !1;
702
+ const i = JSON.parse(e);
703
+ this._bubbleDismissed = this.showBubbleOnce && i.bubbleDismissed || !1;
667
704
  }
668
705
  } catch (e) {
669
706
  console.warn("Failed to load chat state:", e);
@@ -694,8 +731,8 @@ class w {
694
731
  if (!this.container) return;
695
732
  const e = this.container.querySelector(".n8n-chat-offline-indicator");
696
733
  if (!this.isOnline && !e) {
697
- const t = document.createElement("div");
698
- t.className = "n8n-chat-offline-indicator", t.innerHTML = `
734
+ const i = document.createElement("div");
735
+ i.className = "n8n-chat-offline-indicator", i.innerHTML = `
699
736
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
700
737
  <line x1="1" y1="1" x2="23" y2="23"></line>
701
738
  <path d="M16.72 11.06A10.94 10.94 0 0 1 19 12.55"></path>
@@ -706,7 +743,7 @@ class w {
706
743
  <line x1="12" y1="20" x2="12.01" y2="20"></line>
707
744
  </svg>
708
745
  <span>${this.i18n.t("offline")}</span>
709
- `, this.container.insertBefore(t, this.messageList);
746
+ `, this.container.insertBefore(i, this.messageList);
710
747
  } else this.isOnline && e && e.remove();
711
748
  this.button && (this.button.disabled = !this.isOnline || this.isLoading);
712
749
  }
@@ -718,10 +755,10 @@ class w {
718
755
  e.key === "Escape" && this.isOpen && this.mode === "widget" && this.toggleChatWindow();
719
756
  }), this.container.addEventListener("keydown", (e) => {
720
757
  if (e.key !== "Tab") return;
721
- const t = this.container.querySelectorAll(
758
+ const i = this.container.querySelectorAll(
722
759
  'button:not([disabled]), input:not([disabled]), [tabindex]:not([tabindex="-1"])'
723
- ), i = t[0], s = t[t.length - 1];
724
- e.shiftKey && document.activeElement === i ? (e.preventDefault(), s.focus()) : !e.shiftKey && document.activeElement === s && (e.preventDefault(), i.focus());
760
+ ), t = i[0], s = i[i.length - 1];
761
+ e.shiftKey && document.activeElement === t ? (e.preventDefault(), s.focus()) : !e.shiftKey && document.activeElement === s && (e.preventDefault(), t.focus());
725
762
  });
726
763
  }
727
764
  // ─────────────────────────────────────────────────────────────────────────
@@ -736,24 +773,42 @@ class w {
736
773
  const s = yield this._generateEmbedToken();
737
774
  e.searchParams.set("embedToken", s);
738
775
  } else this.embedToken && e.searchParams.set("embedToken", this.embedToken);
739
- const t = yield fetch(e.toString());
740
- if (!t.ok) return;
741
- const i = yield t.json();
742
- if (i.messages && i.messages.length > 0) {
743
- i.messages.forEach((a) => {
776
+ const i = yield fetch(e.toString());
777
+ if (!i.ok) return;
778
+ const t = yield i.json();
779
+ if (t.messages && t.messages.length > 0) {
780
+ t.messages.forEach((a) => {
744
781
  a.role === "human_agent" && this.addMessage(a.content, "ai");
745
782
  });
746
- const s = i.messages[i.messages.length - 1];
747
- this._lastMessageTimestamp = s.created_at, this.isOpen || (this.unreadCount += i.messages.filter((a) => a.role === "human_agent").length, this._updateUnreadBadge());
783
+ const s = t.messages[t.messages.length - 1];
784
+ this._lastMessageTimestamp = s.created_at, this.isOpen || (this.unreadCount += t.messages.filter((a) => a.role === "human_agent").length, this._updateUnreadBadge());
748
785
  }
749
- (i.status === "active" || i.status === "resolved") && this._stopPolling();
786
+ (t.status === "active" || t.status === "resolved") && this._stopPolling();
750
787
  } catch (e) {
751
788
  }
752
789
  }), this.pollingInterval));
753
790
  });
754
791
  }
755
792
  _stopPolling() {
756
- this._pollingTimer && (clearInterval(this._pollingTimer), this._pollingTimer = null);
793
+ this._pollingTimer && (clearInterval(this._pollingTimer), this._pollingTimer = null, this._showHandoffBanner(!1));
794
+ }
795
+ /**
796
+ * Show or hide the "Waiting for a human agent" banner above the message list.
797
+ * @param {boolean} show - true to show, false to remove.
798
+ */
799
+ _showHandoffBanner(e) {
800
+ if (!this.messageList) return;
801
+ const i = this.messageList.parentElement;
802
+ if (!i) return;
803
+ const t = i.querySelector(".pindai-chat-handoff-banner");
804
+ if (e && !t) {
805
+ const s = document.createElement("div");
806
+ s.className = "pindai-chat-handoff-banner";
807
+ const a = document.createElement("span");
808
+ a.className = "pindai-chat-handoff-pulse";
809
+ const n = document.createElement("span");
810
+ n.textContent = this.i18n.t("waitingForAgent"), s.appendChild(a), s.appendChild(n), i.insertBefore(s, this.messageList);
811
+ } else !e && t && t.remove();
757
812
  }
758
813
  // ─────────────────────────────────────────────────────────────────────────
759
814
  // Legacy aliases (keep public API stable)
@@ -773,8 +828,8 @@ class w {
773
828
  renderFilePreview(e) {
774
829
  return this._renderFilePreview(e);
775
830
  }
776
- sendMessageWithRetry(e, t, i) {
777
- return this._sendMessageWithRetry(e, t, i);
831
+ sendMessageWithRetry(e, i, t) {
832
+ return this._sendMessageWithRetry(e, i, t);
778
833
  }
779
834
  checkRateLimit() {
780
835
  return this._checkRateLimit();