@cognitiondesk/widget 1.1.0 → 1.2.1

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.
package/dist/widget.es.js CHANGED
@@ -1,4 +1,4 @@
1
- const u = "https://mounaji-backendv3.onrender.com", b = `
1
+ const u = "https://mounaji-backendv3.onrender.com", _ = `
2
2
  :host {
3
3
  all: initial;
4
4
  font-family: system-ui, sans-serif;
@@ -244,16 +244,17 @@ const u = "https://mounaji-backendv3.onrender.com", b = `
244
244
  font-size: 16px;
245
245
  }
246
246
  }
247
- `, h = {
247
+ `, p = {
248
248
  chat: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>',
249
249
  close: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',
250
250
  send: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>'
251
251
  };
252
- function _() {
252
+ function b() {
253
253
  return "cd_" + Math.random().toString(36).slice(2) + Date.now().toString(36);
254
254
  }
255
- class m {
255
+ class f {
256
256
  constructor(e = {}) {
257
+ var t, a, s, i, r;
257
258
  if (!e.apiKey) throw new Error("[CognitionDesk] apiKey is required");
258
259
  this._cfg = {
259
260
  apiKey: e.apiKey,
@@ -268,9 +269,17 @@ class m {
268
269
  welcomeMessage: e.welcomeMessage || "Hello! How can I help you today?",
269
270
  placeholder: e.placeholder || "Type a message…",
270
271
  position: e.position || "bottom-right",
271
- streaming: e.streaming !== !1
272
+ streaming: e.streaming !== !1,
272
273
  // default: true
273
- }, this._sessionId = _(), this._messages = [], this._open = !1, this._loading = !1, this._container = null, this._shadow = null, this._panel = null, this._messagesEl = null, this._textarea = null, this._sendBtn = null, this._viewportHandler = null;
274
+ // Rate limiting can be overridden by inline config or merged from server config
275
+ rateLimiting: {
276
+ enabled: ((t = e.rateLimiting) == null ? void 0 : t.enabled) !== !1,
277
+ maxMessagesPerSession: ((a = e.rateLimiting) == null ? void 0 : a.maxMessagesPerSession) ?? 0,
278
+ maxMessagesPerMinute: ((s = e.rateLimiting) == null ? void 0 : s.maxMessagesPerMinute) ?? 0,
279
+ limitReachedMessage: ((i = e.rateLimiting) == null ? void 0 : i.limitReachedMessage) || "You've reached the message limit for this session.",
280
+ rateLimitMessage: ((r = e.rateLimiting) == null ? void 0 : r.rateLimitMessage) || "You're sending messages too quickly. Please wait a moment."
281
+ }
282
+ }, this._sessionId = b(), this._messageCount = 0, this._minuteCount = 0, this._minuteStart = Date.now(), this._messages = [], this._open = !1, this._loading = !1, this._container = null, this._shadow = null, this._panel = null, this._messagesEl = null, this._textarea = null, this._sendBtn = null, this._viewportHandler = null;
274
283
  }
275
284
  // ── Public API ──────────────────────────────────────────────────────────────
276
285
  /**
@@ -280,10 +289,10 @@ class m {
280
289
  */
281
290
  mount(e) {
282
291
  const t = () => {
283
- const s = e || document.body;
284
- this._container = document.createElement("div"), this._container.setAttribute("data-cognitiondesk", ""), s.appendChild(this._container), this._shadow = this._container.attachShadow({ mode: "open" });
285
- const a = document.createElement("style");
286
- a.textContent = b, this._shadow.appendChild(a), this._buildDOM(), this._applyTheme(), this._syncViewportMetrics(), this._bindViewportMetrics(), this._bindEvents(), this._cfg.welcomeMessage && this._appendMessage("assistant", this._cfg.welcomeMessage, !1);
292
+ const a = e || document.body;
293
+ this._container = document.createElement("div"), this._container.setAttribute("data-cognitiondesk", ""), a.appendChild(this._container), this._shadow = this._container.attachShadow({ mode: "open" });
294
+ const s = document.createElement("style");
295
+ s.textContent = _, this._shadow.appendChild(s), this._buildDOM(), this._applyTheme(), this._syncViewportMetrics(), this._bindViewportMetrics(), this._bindEvents(), this._cfg.welcomeMessage && this._appendMessage("assistant", this._cfg.welcomeMessage, !1);
287
296
  };
288
297
  return this._cfg.widgetId ? this._fetchWidgetConfig().then(() => t()).catch(() => t()) : (t(), Promise.resolve(this));
289
298
  }
@@ -295,11 +304,11 @@ class m {
295
304
  var t;
296
305
  const e = `${this._cfg.backendUrl}/platforms/web-widget/public/${this._cfg.widgetId}`;
297
306
  try {
298
- const s = await fetch(e);
299
- if (!s.ok) return;
300
- const { config: a } = await s.json();
301
- if (!a) return;
302
- a.botName && !this._userSet("botName") && (this._cfg.botName = a.botName), a.welcomeMessage && !this._userSet("welcomeMessage") && (this._cfg.welcomeMessage = a.welcomeMessage), a.primaryColor && !this._userSet("primaryColor") && (this._cfg.primaryColor = a.primaryColor), a.placeholder && !this._userSet("placeholder") && (this._cfg.placeholder = a.placeholder), a.assistantId && !this._cfg.assistantId && (this._cfg.assistantId = a.assistantId), (t = a.avatar) != null && t.value && !this._userSet("botEmoji") && (this._cfg.botEmoji = a.avatar.value);
307
+ const a = await fetch(e);
308
+ if (!a.ok) return;
309
+ const { config: s } = await a.json();
310
+ if (!s) return;
311
+ s.botName && !this._userSet("botName") && (this._cfg.botName = s.botName), s.welcomeMessage && !this._userSet("welcomeMessage") && (this._cfg.welcomeMessage = s.welcomeMessage), s.primaryColor && !this._userSet("primaryColor") && (this._cfg.primaryColor = s.primaryColor), s.placeholder && !this._userSet("placeholder") && (this._cfg.placeholder = s.placeholder), s.assistantId && !this._cfg.assistantId && (this._cfg.assistantId = s.assistantId), (t = s.avatar) != null && t.value && !this._userSet("botEmoji") && (this._cfg.botEmoji = s.avatar.value), s.rateLimiting && (this._cfg.rateLimiting = { ...this._cfg.rateLimiting, ...s.rateLimiting });
303
312
  } catch {
304
313
  }
305
314
  }
@@ -329,11 +338,11 @@ class m {
329
338
  const e = document.createElement("div");
330
339
  e.className = "cd-root";
331
340
  const t = document.createElement("button");
332
- t.className = "cd-widget-btn", t.innerHTML = h.chat, t.setAttribute("aria-label", "Open chat"), t.style.setProperty("--cd-primary", this._cfg.primaryColor), this._toggleBtn = t;
333
- const s = document.createElement("div");
334
- s.className = "cd-panel", this._panel = s;
341
+ t.className = "cd-widget-btn", t.innerHTML = p.chat, t.setAttribute("aria-label", "Open chat"), t.style.setProperty("--cd-primary", this._cfg.primaryColor), this._toggleBtn = t;
335
342
  const a = document.createElement("div");
336
- a.className = "cd-header", a.innerHTML = `
343
+ a.className = "cd-panel", this._panel = a;
344
+ const s = document.createElement("div");
345
+ s.className = "cd-header", s.innerHTML = `
337
346
  <div class="cd-header-avatar">${this._cfg.botEmoji}</div>
338
347
  <div class="cd-header-info">
339
348
  <div class="cd-header-name">${this._escHtml(this._cfg.botName)}</div>
@@ -342,22 +351,22 @@ class m {
342
351
  </div>
343
352
  </div>
344
353
  `;
345
- const r = document.createElement("button");
346
- r.className = "cd-close-btn", r.innerHTML = h.close, r.setAttribute("aria-label", "Close chat"), a.appendChild(r), this._closeBtn = r;
347
- const i = document.createElement("div");
348
- i.className = "cd-messages", i.setAttribute("role", "log"), i.setAttribute("aria-live", "polite"), this._messagesEl = i;
349
- const n = document.createElement("div");
350
- n.className = "cd-input-area";
351
- const o = document.createElement("textarea");
352
- o.className = "cd-textarea", o.rows = 1, o.placeholder = this._cfg.placeholder, this._textarea = o;
354
+ const i = document.createElement("button");
355
+ i.className = "cd-close-btn", i.innerHTML = p.close, i.setAttribute("aria-label", "Close chat"), s.appendChild(i), this._closeBtn = i;
356
+ const r = document.createElement("div");
357
+ r.className = "cd-messages", r.setAttribute("role", "log"), r.setAttribute("aria-live", "polite"), this._messagesEl = r;
358
+ const o = document.createElement("div");
359
+ o.className = "cd-input-area";
360
+ const n = document.createElement("textarea");
361
+ n.className = "cd-textarea", n.rows = 1, n.placeholder = this._cfg.placeholder, this._textarea = n;
353
362
  const d = document.createElement("button");
354
- d.className = "cd-send-btn", d.innerHTML = h.send, d.setAttribute("aria-label", "Send"), this._sendBtn = d, n.appendChild(o), n.appendChild(d);
363
+ d.className = "cd-send-btn", d.innerHTML = p.send, d.setAttribute("aria-label", "Send"), this._sendBtn = d, o.appendChild(n), o.appendChild(d);
355
364
  const l = document.createElement("div");
356
- l.className = "cd-powered", l.innerHTML = 'Powered by <a href="https://cognitiondesk.com" target="_blank" rel="noopener">CognitionDesk</a>', s.appendChild(a), s.appendChild(i), s.appendChild(n), s.appendChild(l), e.appendChild(t), e.appendChild(s), this._shadow.appendChild(e), this._root = e;
365
+ l.className = "cd-powered", l.innerHTML = 'Powered by <a href="https://cognitiondesk.com" target="_blank" rel="noopener">CognitionDesk</a>', a.appendChild(s), a.appendChild(r), a.appendChild(o), a.appendChild(l), e.appendChild(t), e.appendChild(a), this._shadow.appendChild(e), this._root = e;
357
366
  }
358
367
  _applyTheme() {
359
- var t, s, a;
360
- (this._cfg.theme === "auto" ? window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" : this._cfg.theme) === "dark" && ((t = this._root) == null || t.classList.add("cd-dark")), (s = this._root) == null || s.style.setProperty("--cd-primary", this._cfg.primaryColor), (a = this._panel) == null || a.style.setProperty("--cd-primary", this._cfg.primaryColor);
368
+ var t, a, s;
369
+ (this._cfg.theme === "auto" ? window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" : this._cfg.theme) === "dark" && ((t = this._root) == null || t.classList.add("cd-dark")), (a = this._root) == null || a.style.setProperty("--cd-primary", this._cfg.primaryColor), (s = this._panel) == null || s.style.setProperty("--cd-primary", this._cfg.primaryColor);
361
370
  }
362
371
  // ── Events ──────────────────────────────────────────────────────────────────
363
372
  _bindEvents() {
@@ -378,27 +387,44 @@ class m {
378
387
  _syncViewportMetrics() {
379
388
  const e = this._root || this._container;
380
389
  if (!e) return;
381
- const t = window.visualViewport, s = t ? t.height : window.innerHeight;
382
- e.style.setProperty("--cd-viewport-height", `${Math.round(s)}px`);
390
+ const t = window.visualViewport, a = t ? t.height : window.innerHeight;
391
+ e.style.setProperty("--cd-viewport-height", `${Math.round(a)}px`);
383
392
  }
384
393
  // ── Chat ────────────────────────────────────────────────────────────────────
385
394
  async _sendMessage() {
395
+ var a;
386
396
  const e = this._textarea.value.trim();
387
- if (!(!e || this._loading)) {
388
- this._textarea.value = "", this._textarea.style.height = "auto", this._loading = !0, this._sendBtn.disabled = !0, this._messages.push({ role: "user", content: e }), this._appendMessage("user", e, !1);
389
- try {
390
- if (this._cfg.streaming)
391
- await this._callApiStream();
392
- else {
393
- const t = this._showTyping(), s = await this._callApi();
394
- this._removeTyping(t), this._messages.push({ role: "assistant", content: s }), this._appendMessage("assistant", s, !0);
395
- }
396
- } catch (t) {
397
- this._appendMessage("error", "Sorry, something went wrong. Please try again.", !1), console.error("[CognitionDesk]", t);
398
- } finally {
399
- this._loading = !1, this._sendBtn.disabled = !1, this._textarea.focus();
397
+ if (!e || this._loading) return;
398
+ const t = this._cfg.rateLimiting;
399
+ if (t != null && t.enabled) {
400
+ const s = Date.now();
401
+ if (s - this._minuteStart >= 6e4 && (this._minuteCount = 0, this._minuteStart = s), t.maxMessagesPerSession > 0 && this._messageCount >= t.maxMessagesPerSession) {
402
+ this._appendMessage("error", t.limitReachedMessage, !1), this._textarea.disabled = !0, this._sendBtn.disabled = !0;
403
+ return;
404
+ }
405
+ if (t.maxMessagesPerMinute > 0 && this._minuteCount >= t.maxMessagesPerMinute) {
406
+ this._appendMessage("error", t.rateLimitMessage, !1);
407
+ return;
400
408
  }
401
409
  }
410
+ this._textarea.value = "", this._textarea.style.height = "auto", this._loading = !0, this._sendBtn.disabled = !0, this._messages.push({ role: "user", content: e }), this._appendMessage("user", e, !1), this._messageCount += 1, this._minuteCount += 1;
411
+ try {
412
+ if (this._cfg.streaming)
413
+ await this._callApiStream();
414
+ else {
415
+ const s = this._showTyping(), i = await this._callApi();
416
+ this._removeTyping(s), this._messages.push({ role: "assistant", content: i }), this._appendMessage("assistant", i, !0);
417
+ }
418
+ } catch (s) {
419
+ if (s.status === 429 || (a = s.message) != null && a.includes("429")) {
420
+ const i = s.rateLimitMessage || (t == null ? void 0 : t.rateLimitMessage) || "You're sending messages too quickly. Please wait a moment.";
421
+ this._appendMessage("error", i, !1);
422
+ } else
423
+ this._appendMessage("error", "Sorry, something went wrong. Please try again.", !1);
424
+ console.error("[CognitionDesk]", s);
425
+ } finally {
426
+ this._loading = !1, (t == null ? void 0 : t.maxMessagesPerSession) > 0 && this._messageCount >= t.maxMessagesPerSession || (this._sendBtn.disabled = !1), this._textarea.focus();
427
+ }
402
428
  }
403
429
  _buildRequestBody() {
404
430
  return {
@@ -420,56 +446,56 @@ class m {
420
446
  body: JSON.stringify({ ...this._buildRequestBody(), stream: !0 })
421
447
  });
422
448
  if (!t.ok) {
423
- const o = await t.json().catch(() => ({}));
424
- throw new Error(o.message || `HTTP ${t.status}`);
449
+ const n = await t.json().catch(() => ({})), d = new Error(n.message || `HTTP ${t.status}`);
450
+ throw d.status = t.status, d.rateLimitMessage = n.message, d;
425
451
  }
426
- const s = document.createElement("div");
427
- s.className = "cd-msg assistant", s.innerHTML = '<div class="cd-typing"><span></span><span></span><span></span></div>', this._messagesEl.appendChild(s), this._scrollToBottom();
428
- const a = t.body.getReader(), r = new TextDecoder();
429
- let i = "", n = !1;
452
+ const a = document.createElement("div");
453
+ a.className = "cd-msg assistant", a.innerHTML = '<div class="cd-typing"><span></span><span></span><span></span></div>', this._messagesEl.appendChild(a), this._scrollToBottom();
454
+ const s = t.body.getReader(), i = new TextDecoder();
455
+ let r = "", o = !1;
430
456
  try {
431
457
  for (; ; ) {
432
- const { done: o, value: d } = await a.read();
433
- if (o) break;
434
- const l = r.decode(d, { stream: !0 });
458
+ const { done: n, value: d } = await s.read();
459
+ if (n) break;
460
+ const l = i.decode(d, { stream: !0 });
435
461
  for (const g of l.split(`
436
462
  `)) {
437
463
  if (!g.startsWith("data: ")) continue;
438
- const f = g.slice(6).trim();
439
- if (f === "[DONE]") break;
440
- let p;
464
+ const m = g.slice(6).trim();
465
+ if (m === "[DONE]") break;
466
+ let h;
441
467
  try {
442
- p = JSON.parse(f);
468
+ h = JSON.parse(m);
443
469
  } catch {
444
470
  continue;
445
471
  }
446
- p.type === "content" && p.data && (n || (s.innerHTML = "", n = !0), i += p.data, s.innerHTML = this._renderMarkdown(i), this._scrollToBottom());
472
+ h.type === "content" && h.data && (o || (a.innerHTML = "", o = !0), r += h.data, a.innerHTML = this._renderMarkdown(r), this._scrollToBottom());
447
473
  }
448
474
  }
449
475
  } finally {
450
- a.releaseLock();
476
+ s.releaseLock();
451
477
  }
452
- n || (s.innerHTML = this._renderMarkdown("No response")), i && this._messages.push({ role: "assistant", content: i });
478
+ o || (a.innerHTML = this._renderMarkdown("No response")), r && this._messages.push({ role: "assistant", content: r });
453
479
  }
454
480
  /** Non-streaming fallback path */
455
481
  async _callApi() {
456
- var a, r, i;
482
+ var s, i, r;
457
483
  const e = `${this._cfg.backendUrl}/chat-apiKeyAuth/chat`, t = await fetch(e, {
458
484
  method: "POST",
459
485
  headers: { "Content-Type": "application/json", "x-api-key": this._cfg.apiKey },
460
486
  body: JSON.stringify(this._buildRequestBody())
461
487
  });
462
488
  if (!t.ok) {
463
- const n = await t.json().catch(() => ({}));
464
- throw new Error(n.message || `HTTP ${t.status}`);
489
+ const o = await t.json().catch(() => ({})), n = new Error(o.message || `HTTP ${t.status}`);
490
+ throw n.status = t.status, n.rateLimitMessage = o.message, n;
465
491
  }
466
- const s = await t.json();
467
- return s.response || s.message || s.content || ((i = (r = (a = s.choices) == null ? void 0 : a[0]) == null ? void 0 : r.message) == null ? void 0 : i.content) || "No response";
492
+ const a = await t.json();
493
+ return a.response || a.message || a.content || ((r = (i = (s = a.choices) == null ? void 0 : s[0]) == null ? void 0 : i.message) == null ? void 0 : r.content) || "No response";
468
494
  }
469
495
  // ── DOM helpers ─────────────────────────────────────────────────────────────
470
- _appendMessage(e, t, s = !0) {
471
- const a = document.createElement("div");
472
- a.className = `cd-msg ${e}`, s && e !== "user" ? a.innerHTML = this._renderMarkdown(t) : a.textContent = t, this._messagesEl.appendChild(a), this._scrollToBottom();
496
+ _appendMessage(e, t, a = !0) {
497
+ const s = document.createElement("div");
498
+ s.className = `cd-msg ${e}`, a && e !== "user" ? s.innerHTML = this._renderMarkdown(t) : s.textContent = t, this._messagesEl.appendChild(s), this._scrollToBottom();
473
499
  }
474
500
  /** Minimal, safe markdown → HTML renderer (no dependencies). */
475
501
  _renderMarkdown(e) {
@@ -477,7 +503,7 @@ class m {
477
503
  let t = this._escHtml(e);
478
504
  return t = t.replace(
479
505
  /```[\w]*\n?([\s\S]*?)```/g,
480
- (s, a) => `<pre style="background:#0f172a;color:#e2e8f0;padding:10px;border-radius:6px;font-size:12px;overflow-x:auto;margin:6px 0;white-space:pre-wrap">${a.trim()}</pre>`
506
+ (a, s) => `<pre style="background:#0f172a;color:#e2e8f0;padding:10px;border-radius:6px;font-size:12px;overflow-x:auto;margin:6px 0;white-space:pre-wrap">${s.trim()}</pre>`
481
507
  ), t = t.replace(/`([^`]+)`/g, '<code style="background:rgba(0,0,0,.08);padding:1px 5px;border-radius:4px;font-size:.9em">$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(
482
508
  /\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g,
483
509
  '<a href="$2" target="_blank" rel="noopener" style="color:var(--cd-primary,#2563eb)">$1</a>'
@@ -497,8 +523,8 @@ class m {
497
523
  return String(e).replace(/[&<>"']/g, (t) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[t]);
498
524
  }
499
525
  }
500
- function w(c) {
501
- const e = new m(c);
526
+ function x(c) {
527
+ const e = new f(c);
502
528
  return e.mount(), e;
503
529
  }
504
530
  if (typeof document < "u") {
@@ -506,9 +532,9 @@ if (typeof document < "u") {
506
532
  document.querySelectorAll("script[data-api-key]").forEach((e) => {
507
533
  const t = e.getAttribute("data-api-key");
508
534
  if (!t) return;
509
- const s = e.getAttribute("data-widget-id") || void 0, a = e.getAttribute("data-streaming"), r = new m({
535
+ const a = e.getAttribute("data-widget-id") || void 0, s = e.getAttribute("data-streaming"), i = new f({
510
536
  apiKey: t,
511
- widgetId: s,
537
+ widgetId: a,
512
538
  assistantId: e.getAttribute("data-assistant-id") || void 0,
513
539
  theme: e.getAttribute("data-theme") || "light",
514
540
  primaryColor: e.getAttribute("data-primary-color") || "#2563eb",
@@ -517,15 +543,15 @@ if (typeof document < "u") {
517
543
  welcomeMessage: e.getAttribute("data-welcome-message") || void 0,
518
544
  placeholder: e.getAttribute("data-placeholder") || void 0,
519
545
  backendUrl: e.getAttribute("data-backend-url") || void 0,
520
- streaming: a === null ? !0 : a !== "false"
546
+ streaming: s === null ? !0 : s !== "false"
521
547
  });
522
- Promise.resolve(r.mount());
548
+ Promise.resolve(i.mount());
523
549
  });
524
550
  };
525
551
  document.readyState === "loading" ? document.addEventListener("DOMContentLoaded", c) : c();
526
552
  }
527
553
  export {
528
- m as CognitionDeskWidget,
529
- m as default,
530
- w as init
554
+ f as CognitionDeskWidget,
555
+ f as default,
556
+ x as init
531
557
  };
@@ -1,4 +1,4 @@
1
- (function(d,l){typeof exports=="object"&&typeof module<"u"?l(exports):typeof define=="function"&&define.amd?define(["exports"],l):(d=typeof globalThis<"u"?globalThis:d||self,l(d.CognitionDeskWidget={}))})(this,function(d){"use strict";const l="https://mounaji-backendv3.onrender.com",_=`
1
+ (function(c,l){typeof exports=="object"&&typeof module<"u"?l(exports):typeof define=="function"&&define.amd?define(["exports"],l):(c=typeof globalThis<"u"?globalThis:c||self,l(c.CognitionDeskWidget={}))})(this,function(c){"use strict";const l="https://mounaji-backendv3.onrender.com",b=`
2
2
  :host {
3
3
  all: initial;
4
4
  font-family: system-ui, sans-serif;
@@ -244,7 +244,7 @@
244
244
  font-size: 16px;
245
245
  }
246
246
  }
247
- `,m={chat:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>',close:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',send:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>'};function w(){return"cd_"+Math.random().toString(36).slice(2)+Date.now().toString(36)}class g{constructor(e={}){if(!e.apiKey)throw new Error("[CognitionDesk] apiKey is required");this._cfg={apiKey:e.apiKey,widgetId:e.widgetId||null,assistantId:e.assistantId||null,backendUrl:e.backendUrl||l,primaryColor:e.primaryColor||"#2563eb",theme:e.theme||"light",botName:e.botName||"AI Assistant",botEmoji:e.botEmoji||"🤖",welcomeMessage:e.welcomeMessage||"Hello! How can I help you today?",placeholder:e.placeholder||"Type a message…",position:e.position||"bottom-right",streaming:e.streaming!==!1},this._sessionId=w(),this._messages=[],this._open=!1,this._loading=!1,this._container=null,this._shadow=null,this._panel=null,this._messagesEl=null,this._textarea=null,this._sendBtn=null,this._viewportHandler=null}mount(e){const t=()=>{const a=e||document.body;this._container=document.createElement("div"),this._container.setAttribute("data-cognitiondesk",""),a.appendChild(this._container),this._shadow=this._container.attachShadow({mode:"open"});const s=document.createElement("style");s.textContent=_,this._shadow.appendChild(s),this._buildDOM(),this._applyTheme(),this._syncViewportMetrics(),this._bindViewportMetrics(),this._bindEvents(),this._cfg.welcomeMessage&&this._appendMessage("assistant",this._cfg.welcomeMessage,!1)};return this._cfg.widgetId?this._fetchWidgetConfig().then(()=>t()).catch(()=>t()):(t(),Promise.resolve(this))}async _fetchWidgetConfig(){var t;const e=`${this._cfg.backendUrl}/platforms/web-widget/public/${this._cfg.widgetId}`;try{const a=await fetch(e);if(!a.ok)return;const{config:s}=await a.json();if(!s)return;s.botName&&!this._userSet("botName")&&(this._cfg.botName=s.botName),s.welcomeMessage&&!this._userSet("welcomeMessage")&&(this._cfg.welcomeMessage=s.welcomeMessage),s.primaryColor&&!this._userSet("primaryColor")&&(this._cfg.primaryColor=s.primaryColor),s.placeholder&&!this._userSet("placeholder")&&(this._cfg.placeholder=s.placeholder),s.assistantId&&!this._cfg.assistantId&&(this._cfg.assistantId=s.assistantId),(t=s.avatar)!=null&&t.value&&!this._userSet("botEmoji")&&(this._cfg.botEmoji=s.avatar.value)}catch{}}_userSet(e){return!1}unmount(){this._unbindViewportMetrics(),this._container&&(this._container.remove(),this._container=null)}open(){var e,t;this._open=!0,this._syncViewportMetrics(),(e=this._panel)==null||e.classList.add("open"),(t=this._textarea)==null||t.focus()}close(){var e;this._open=!1,(e=this._panel)==null||e.classList.remove("open")}toggle(){this._open?this.close():this.open()}clearHistory(){this._messages=[],this._messagesEl&&(this._messagesEl.innerHTML=""),this._cfg.welcomeMessage&&this._appendMessage("assistant",this._cfg.welcomeMessage)}_buildDOM(){const e=document.createElement("div");e.className="cd-root";const t=document.createElement("button");t.className="cd-widget-btn",t.innerHTML=m.chat,t.setAttribute("aria-label","Open chat"),t.style.setProperty("--cd-primary",this._cfg.primaryColor),this._toggleBtn=t;const a=document.createElement("div");a.className="cd-panel",this._panel=a;const s=document.createElement("div");s.className="cd-header",s.innerHTML=`
247
+ `,f={chat:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>',close:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',send:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>'};function w(){return"cd_"+Math.random().toString(36).slice(2)+Date.now().toString(36)}class g{constructor(e={}){var t,a,s,i,n;if(!e.apiKey)throw new Error("[CognitionDesk] apiKey is required");this._cfg={apiKey:e.apiKey,widgetId:e.widgetId||null,assistantId:e.assistantId||null,backendUrl:e.backendUrl||l,primaryColor:e.primaryColor||"#2563eb",theme:e.theme||"light",botName:e.botName||"AI Assistant",botEmoji:e.botEmoji||"🤖",welcomeMessage:e.welcomeMessage||"Hello! How can I help you today?",placeholder:e.placeholder||"Type a message…",position:e.position||"bottom-right",streaming:e.streaming!==!1,rateLimiting:{enabled:((t=e.rateLimiting)==null?void 0:t.enabled)!==!1,maxMessagesPerSession:((a=e.rateLimiting)==null?void 0:a.maxMessagesPerSession)??0,maxMessagesPerMinute:((s=e.rateLimiting)==null?void 0:s.maxMessagesPerMinute)??0,limitReachedMessage:((i=e.rateLimiting)==null?void 0:i.limitReachedMessage)||"You've reached the message limit for this session.",rateLimitMessage:((n=e.rateLimiting)==null?void 0:n.rateLimitMessage)||"You're sending messages too quickly. Please wait a moment."}},this._sessionId=w(),this._messageCount=0,this._minuteCount=0,this._minuteStart=Date.now(),this._messages=[],this._open=!1,this._loading=!1,this._container=null,this._shadow=null,this._panel=null,this._messagesEl=null,this._textarea=null,this._sendBtn=null,this._viewportHandler=null}mount(e){const t=()=>{const a=e||document.body;this._container=document.createElement("div"),this._container.setAttribute("data-cognitiondesk",""),a.appendChild(this._container),this._shadow=this._container.attachShadow({mode:"open"});const s=document.createElement("style");s.textContent=b,this._shadow.appendChild(s),this._buildDOM(),this._applyTheme(),this._syncViewportMetrics(),this._bindViewportMetrics(),this._bindEvents(),this._cfg.welcomeMessage&&this._appendMessage("assistant",this._cfg.welcomeMessage,!1)};return this._cfg.widgetId?this._fetchWidgetConfig().then(()=>t()).catch(()=>t()):(t(),Promise.resolve(this))}async _fetchWidgetConfig(){var t;const e=`${this._cfg.backendUrl}/platforms/web-widget/public/${this._cfg.widgetId}`;try{const a=await fetch(e);if(!a.ok)return;const{config:s}=await a.json();if(!s)return;s.botName&&!this._userSet("botName")&&(this._cfg.botName=s.botName),s.welcomeMessage&&!this._userSet("welcomeMessage")&&(this._cfg.welcomeMessage=s.welcomeMessage),s.primaryColor&&!this._userSet("primaryColor")&&(this._cfg.primaryColor=s.primaryColor),s.placeholder&&!this._userSet("placeholder")&&(this._cfg.placeholder=s.placeholder),s.assistantId&&!this._cfg.assistantId&&(this._cfg.assistantId=s.assistantId),(t=s.avatar)!=null&&t.value&&!this._userSet("botEmoji")&&(this._cfg.botEmoji=s.avatar.value),s.rateLimiting&&(this._cfg.rateLimiting={...this._cfg.rateLimiting,...s.rateLimiting})}catch{}}_userSet(e){return!1}unmount(){this._unbindViewportMetrics(),this._container&&(this._container.remove(),this._container=null)}open(){var e,t;this._open=!0,this._syncViewportMetrics(),(e=this._panel)==null||e.classList.add("open"),(t=this._textarea)==null||t.focus()}close(){var e;this._open=!1,(e=this._panel)==null||e.classList.remove("open")}toggle(){this._open?this.close():this.open()}clearHistory(){this._messages=[],this._messagesEl&&(this._messagesEl.innerHTML=""),this._cfg.welcomeMessage&&this._appendMessage("assistant",this._cfg.welcomeMessage)}_buildDOM(){const e=document.createElement("div");e.className="cd-root";const t=document.createElement("button");t.className="cd-widget-btn",t.innerHTML=f.chat,t.setAttribute("aria-label","Open chat"),t.style.setProperty("--cd-primary",this._cfg.primaryColor),this._toggleBtn=t;const a=document.createElement("div");a.className="cd-panel",this._panel=a;const s=document.createElement("div");s.className="cd-header",s.innerHTML=`
248
248
  <div class="cd-header-avatar">${this._cfg.botEmoji}</div>
249
249
  <div class="cd-header-info">
250
250
  <div class="cd-header-name">${this._escHtml(this._cfg.botName)}</div>
@@ -252,5 +252,5 @@
252
252
  <span class="cd-status-dot"></span> Online
253
253
  </div>
254
254
  </div>
255
- `;const r=document.createElement("button");r.className="cd-close-btn",r.innerHTML=m.close,r.setAttribute("aria-label","Close chat"),s.appendChild(r),this._closeBtn=r;const i=document.createElement("div");i.className="cd-messages",i.setAttribute("role","log"),i.setAttribute("aria-live","polite"),this._messagesEl=i;const n=document.createElement("div");n.className="cd-input-area";const o=document.createElement("textarea");o.className="cd-textarea",o.rows=1,o.placeholder=this._cfg.placeholder,this._textarea=o;const c=document.createElement("button");c.className="cd-send-btn",c.innerHTML=m.send,c.setAttribute("aria-label","Send"),this._sendBtn=c,n.appendChild(o),n.appendChild(c);const h=document.createElement("div");h.className="cd-powered",h.innerHTML='Powered by <a href="https://cognitiondesk.com" target="_blank" rel="noopener">CognitionDesk</a>',a.appendChild(s),a.appendChild(i),a.appendChild(n),a.appendChild(h),e.appendChild(t),e.appendChild(a),this._shadow.appendChild(e),this._root=e}_applyTheme(){var t,a,s;(this._cfg.theme==="auto"?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":this._cfg.theme)==="dark"&&((t=this._root)==null||t.classList.add("cd-dark")),(a=this._root)==null||a.style.setProperty("--cd-primary",this._cfg.primaryColor),(s=this._panel)==null||s.style.setProperty("--cd-primary",this._cfg.primaryColor)}_bindEvents(){this._toggleBtn.addEventListener("click",()=>this.toggle()),this._closeBtn.addEventListener("click",()=>this.close()),this._sendBtn.addEventListener("click",()=>this._sendMessage()),this._textarea.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),this._sendMessage())}),this._textarea.addEventListener("input",()=>{this._textarea.style.height="auto",this._textarea.style.height=Math.min(this._textarea.scrollHeight,120)+"px"}),this._textarea.addEventListener("focus",()=>{this._syncViewportMetrics()})}_bindViewportMetrics(){this._viewportHandler||(this._viewportHandler=()=>this._syncViewportMetrics(),window.addEventListener("resize",this._viewportHandler,{passive:!0}),window.addEventListener("orientationchange",this._viewportHandler),window.visualViewport&&(window.visualViewport.addEventListener("resize",this._viewportHandler),window.visualViewport.addEventListener("scroll",this._viewportHandler)))}_unbindViewportMetrics(){this._viewportHandler&&(window.removeEventListener("resize",this._viewportHandler),window.removeEventListener("orientationchange",this._viewportHandler),window.visualViewport&&(window.visualViewport.removeEventListener("resize",this._viewportHandler),window.visualViewport.removeEventListener("scroll",this._viewportHandler)),this._viewportHandler=null)}_syncViewportMetrics(){const e=this._root||this._container;if(!e)return;const t=window.visualViewport,a=t?t.height:window.innerHeight;e.style.setProperty("--cd-viewport-height",`${Math.round(a)}px`)}async _sendMessage(){const e=this._textarea.value.trim();if(!(!e||this._loading)){this._textarea.value="",this._textarea.style.height="auto",this._loading=!0,this._sendBtn.disabled=!0,this._messages.push({role:"user",content:e}),this._appendMessage("user",e,!1);try{if(this._cfg.streaming)await this._callApiStream();else{const t=this._showTyping(),a=await this._callApi();this._removeTyping(t),this._messages.push({role:"assistant",content:a}),this._appendMessage("assistant",a,!0)}}catch(t){this._appendMessage("error","Sorry, something went wrong. Please try again.",!1),console.error("[CognitionDesk]",t)}finally{this._loading=!1,this._sendBtn.disabled=!1,this._textarea.focus()}}}_buildRequestBody(){return{messages:this._messages,assistantId:this._cfg.assistantId||void 0,userContext:{sessionId:this._sessionId,assistantId:this._cfg.assistantId||void 0,widgetId:this._cfg.widgetId||void 0,platform:"cognitiondesk-widget"}}}async _callApiStream(){const e=`${this._cfg.backendUrl}/chat-apiKeyAuth/stream`,t=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":this._cfg.apiKey},body:JSON.stringify({...this._buildRequestBody(),stream:!0})});if(!t.ok){const o=await t.json().catch(()=>({}));throw new Error(o.message||`HTTP ${t.status}`)}const a=document.createElement("div");a.className="cd-msg assistant",a.innerHTML='<div class="cd-typing"><span></span><span></span><span></span></div>',this._messagesEl.appendChild(a),this._scrollToBottom();const s=t.body.getReader(),r=new TextDecoder;let i="",n=!1;try{for(;;){const{done:o,value:c}=await s.read();if(o)break;const h=r.decode(c,{stream:!0});for(const u of h.split(`
256
- `)){if(!u.startsWith("data: "))continue;const b=u.slice(6).trim();if(b==="[DONE]")break;let f;try{f=JSON.parse(b)}catch{continue}f.type==="content"&&f.data&&(n||(a.innerHTML="",n=!0),i+=f.data,a.innerHTML=this._renderMarkdown(i),this._scrollToBottom())}}}finally{s.releaseLock()}n||(a.innerHTML=this._renderMarkdown("No response")),i&&this._messages.push({role:"assistant",content:i})}async _callApi(){var s,r,i;const e=`${this._cfg.backendUrl}/chat-apiKeyAuth/chat`,t=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":this._cfg.apiKey},body:JSON.stringify(this._buildRequestBody())});if(!t.ok){const n=await t.json().catch(()=>({}));throw new Error(n.message||`HTTP ${t.status}`)}const a=await t.json();return a.response||a.message||a.content||((i=(r=(s=a.choices)==null?void 0:s[0])==null?void 0:r.message)==null?void 0:i.content)||"No response"}_appendMessage(e,t,a=!0){const s=document.createElement("div");s.className=`cd-msg ${e}`,a&&e!=="user"?s.innerHTML=this._renderMarkdown(t):s.textContent=t,this._messagesEl.appendChild(s),this._scrollToBottom()}_renderMarkdown(e){if(!e)return"";let t=this._escHtml(e);return t=t.replace(/```[\w]*\n?([\s\S]*?)```/g,(a,s)=>`<pre style="background:#0f172a;color:#e2e8f0;padding:10px;border-radius:6px;font-size:12px;overflow-x:auto;margin:6px 0;white-space:pre-wrap">${s.trim()}</pre>`),t=t.replace(/`([^`]+)`/g,'<code style="background:rgba(0,0,0,.08);padding:1px 5px;border-radius:4px;font-size:.9em">$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(/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g,'<a href="$2" target="_blank" rel="noopener" style="color:var(--cd-primary,#2563eb)">$1</a>'),t=t.replace(/\n/g,"<br>"),t}_showTyping(){const e=document.createElement("div");return e.className="cd-msg assistant",e.innerHTML='<div class="cd-typing"><span></span><span></span><span></span></div>',this._messagesEl.appendChild(e),this._scrollToBottom(),e}_removeTyping(e){e==null||e.remove()}_scrollToBottom(){this._messagesEl&&(this._messagesEl.scrollTop=this._messagesEl.scrollHeight)}_escHtml(e){return String(e).replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[t])}}function v(p){const e=new g(p);return e.mount(),e}if(typeof document<"u"){const p=()=>{document.querySelectorAll("script[data-api-key]").forEach(e=>{const t=e.getAttribute("data-api-key");if(!t)return;const a=e.getAttribute("data-widget-id")||void 0,s=e.getAttribute("data-streaming"),r=new g({apiKey:t,widgetId:a,assistantId:e.getAttribute("data-assistant-id")||void 0,theme:e.getAttribute("data-theme")||"light",primaryColor:e.getAttribute("data-primary-color")||"#2563eb",botName:e.getAttribute("data-bot-name")||"AI Assistant",botEmoji:e.getAttribute("data-bot-emoji")||void 0,welcomeMessage:e.getAttribute("data-welcome-message")||void 0,placeholder:e.getAttribute("data-placeholder")||void 0,backendUrl:e.getAttribute("data-backend-url")||void 0,streaming:s===null?!0:s!=="false"});Promise.resolve(r.mount())})};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",p):p()}d.CognitionDeskWidget=g,d.default=g,d.init=v,Object.defineProperties(d,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
255
+ `;const i=document.createElement("button");i.className="cd-close-btn",i.innerHTML=f.close,i.setAttribute("aria-label","Close chat"),s.appendChild(i),this._closeBtn=i;const n=document.createElement("div");n.className="cd-messages",n.setAttribute("role","log"),n.setAttribute("aria-live","polite"),this._messagesEl=n;const o=document.createElement("div");o.className="cd-input-area";const r=document.createElement("textarea");r.className="cd-textarea",r.rows=1,r.placeholder=this._cfg.placeholder,this._textarea=r;const d=document.createElement("button");d.className="cd-send-btn",d.innerHTML=f.send,d.setAttribute("aria-label","Send"),this._sendBtn=d,o.appendChild(r),o.appendChild(d);const p=document.createElement("div");p.className="cd-powered",p.innerHTML='Powered by <a href="https://cognitiondesk.com" target="_blank" rel="noopener">CognitionDesk</a>',a.appendChild(s),a.appendChild(n),a.appendChild(o),a.appendChild(p),e.appendChild(t),e.appendChild(a),this._shadow.appendChild(e),this._root=e}_applyTheme(){var t,a,s;(this._cfg.theme==="auto"?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":this._cfg.theme)==="dark"&&((t=this._root)==null||t.classList.add("cd-dark")),(a=this._root)==null||a.style.setProperty("--cd-primary",this._cfg.primaryColor),(s=this._panel)==null||s.style.setProperty("--cd-primary",this._cfg.primaryColor)}_bindEvents(){this._toggleBtn.addEventListener("click",()=>this.toggle()),this._closeBtn.addEventListener("click",()=>this.close()),this._sendBtn.addEventListener("click",()=>this._sendMessage()),this._textarea.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),this._sendMessage())}),this._textarea.addEventListener("input",()=>{this._textarea.style.height="auto",this._textarea.style.height=Math.min(this._textarea.scrollHeight,120)+"px"}),this._textarea.addEventListener("focus",()=>{this._syncViewportMetrics()})}_bindViewportMetrics(){this._viewportHandler||(this._viewportHandler=()=>this._syncViewportMetrics(),window.addEventListener("resize",this._viewportHandler,{passive:!0}),window.addEventListener("orientationchange",this._viewportHandler),window.visualViewport&&(window.visualViewport.addEventListener("resize",this._viewportHandler),window.visualViewport.addEventListener("scroll",this._viewportHandler)))}_unbindViewportMetrics(){this._viewportHandler&&(window.removeEventListener("resize",this._viewportHandler),window.removeEventListener("orientationchange",this._viewportHandler),window.visualViewport&&(window.visualViewport.removeEventListener("resize",this._viewportHandler),window.visualViewport.removeEventListener("scroll",this._viewportHandler)),this._viewportHandler=null)}_syncViewportMetrics(){const e=this._root||this._container;if(!e)return;const t=window.visualViewport,a=t?t.height:window.innerHeight;e.style.setProperty("--cd-viewport-height",`${Math.round(a)}px`)}async _sendMessage(){var a;const e=this._textarea.value.trim();if(!e||this._loading)return;const t=this._cfg.rateLimiting;if(t!=null&&t.enabled){const s=Date.now();if(s-this._minuteStart>=6e4&&(this._minuteCount=0,this._minuteStart=s),t.maxMessagesPerSession>0&&this._messageCount>=t.maxMessagesPerSession){this._appendMessage("error",t.limitReachedMessage,!1),this._textarea.disabled=!0,this._sendBtn.disabled=!0;return}if(t.maxMessagesPerMinute>0&&this._minuteCount>=t.maxMessagesPerMinute){this._appendMessage("error",t.rateLimitMessage,!1);return}}this._textarea.value="",this._textarea.style.height="auto",this._loading=!0,this._sendBtn.disabled=!0,this._messages.push({role:"user",content:e}),this._appendMessage("user",e,!1),this._messageCount+=1,this._minuteCount+=1;try{if(this._cfg.streaming)await this._callApiStream();else{const s=this._showTyping(),i=await this._callApi();this._removeTyping(s),this._messages.push({role:"assistant",content:i}),this._appendMessage("assistant",i,!0)}}catch(s){if(s.status===429||(a=s.message)!=null&&a.includes("429")){const i=s.rateLimitMessage||(t==null?void 0:t.rateLimitMessage)||"You're sending messages too quickly. Please wait a moment.";this._appendMessage("error",i,!1)}else this._appendMessage("error","Sorry, something went wrong. Please try again.",!1);console.error("[CognitionDesk]",s)}finally{this._loading=!1,(t==null?void 0:t.maxMessagesPerSession)>0&&this._messageCount>=t.maxMessagesPerSession||(this._sendBtn.disabled=!1),this._textarea.focus()}}_buildRequestBody(){return{messages:this._messages,assistantId:this._cfg.assistantId||void 0,userContext:{sessionId:this._sessionId,assistantId:this._cfg.assistantId||void 0,widgetId:this._cfg.widgetId||void 0,platform:"cognitiondesk-widget"}}}async _callApiStream(){const e=`${this._cfg.backendUrl}/chat-apiKeyAuth/stream`,t=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":this._cfg.apiKey},body:JSON.stringify({...this._buildRequestBody(),stream:!0})});if(!t.ok){const r=await t.json().catch(()=>({})),d=new Error(r.message||`HTTP ${t.status}`);throw d.status=t.status,d.rateLimitMessage=r.message,d}const a=document.createElement("div");a.className="cd-msg assistant",a.innerHTML='<div class="cd-typing"><span></span><span></span><span></span></div>',this._messagesEl.appendChild(a),this._scrollToBottom();const s=t.body.getReader(),i=new TextDecoder;let n="",o=!1;try{for(;;){const{done:r,value:d}=await s.read();if(r)break;const p=i.decode(d,{stream:!0});for(const u of p.split(`
256
+ `)){if(!u.startsWith("data: "))continue;const _=u.slice(6).trim();if(_==="[DONE]")break;let m;try{m=JSON.parse(_)}catch{continue}m.type==="content"&&m.data&&(o||(a.innerHTML="",o=!0),n+=m.data,a.innerHTML=this._renderMarkdown(n),this._scrollToBottom())}}}finally{s.releaseLock()}o||(a.innerHTML=this._renderMarkdown("No response")),n&&this._messages.push({role:"assistant",content:n})}async _callApi(){var s,i,n;const e=`${this._cfg.backendUrl}/chat-apiKeyAuth/chat`,t=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":this._cfg.apiKey},body:JSON.stringify(this._buildRequestBody())});if(!t.ok){const o=await t.json().catch(()=>({})),r=new Error(o.message||`HTTP ${t.status}`);throw r.status=t.status,r.rateLimitMessage=o.message,r}const a=await t.json();return a.response||a.message||a.content||((n=(i=(s=a.choices)==null?void 0:s[0])==null?void 0:i.message)==null?void 0:n.content)||"No response"}_appendMessage(e,t,a=!0){const s=document.createElement("div");s.className=`cd-msg ${e}`,a&&e!=="user"?s.innerHTML=this._renderMarkdown(t):s.textContent=t,this._messagesEl.appendChild(s),this._scrollToBottom()}_renderMarkdown(e){if(!e)return"";let t=this._escHtml(e);return t=t.replace(/```[\w]*\n?([\s\S]*?)```/g,(a,s)=>`<pre style="background:#0f172a;color:#e2e8f0;padding:10px;border-radius:6px;font-size:12px;overflow-x:auto;margin:6px 0;white-space:pre-wrap">${s.trim()}</pre>`),t=t.replace(/`([^`]+)`/g,'<code style="background:rgba(0,0,0,.08);padding:1px 5px;border-radius:4px;font-size:.9em">$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(/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g,'<a href="$2" target="_blank" rel="noopener" style="color:var(--cd-primary,#2563eb)">$1</a>'),t=t.replace(/\n/g,"<br>"),t}_showTyping(){const e=document.createElement("div");return e.className="cd-msg assistant",e.innerHTML='<div class="cd-typing"><span></span><span></span><span></span></div>',this._messagesEl.appendChild(e),this._scrollToBottom(),e}_removeTyping(e){e==null||e.remove()}_scrollToBottom(){this._messagesEl&&(this._messagesEl.scrollTop=this._messagesEl.scrollHeight)}_escHtml(e){return String(e).replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[t])}}function x(h){const e=new g(h);return e.mount(),e}if(typeof document<"u"){const h=()=>{document.querySelectorAll("script[data-api-key]").forEach(e=>{const t=e.getAttribute("data-api-key");if(!t)return;const a=e.getAttribute("data-widget-id")||void 0,s=e.getAttribute("data-streaming"),i=new g({apiKey:t,widgetId:a,assistantId:e.getAttribute("data-assistant-id")||void 0,theme:e.getAttribute("data-theme")||"light",primaryColor:e.getAttribute("data-primary-color")||"#2563eb",botName:e.getAttribute("data-bot-name")||"AI Assistant",botEmoji:e.getAttribute("data-bot-emoji")||void 0,welcomeMessage:e.getAttribute("data-welcome-message")||void 0,placeholder:e.getAttribute("data-placeholder")||void 0,backendUrl:e.getAttribute("data-backend-url")||void 0,streaming:s===null?!0:s!=="false"});Promise.resolve(i.mount())})};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",h):h()}c.CognitionDeskWidget=g,c.default=g,c.init=x,Object.defineProperties(c,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cognitiondesk/widget",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "Embed a CognitionDesk AI chat widget into any website. Authenticate with an API key from your CognitionDesk dashboard.",
5
5
  "keywords": ["chat", "widget", "ai", "cognitiondesk", "chatbot", "react", "streaming", "mounaji"],
6
6
  "license": "MIT",
package/src/react.jsx CHANGED
@@ -42,6 +42,11 @@ export const CognitionDeskWidget = forwardRef(function CognitionDeskWidget(props
42
42
  defaultOpen = false,
43
43
  streaming = true,
44
44
  backendUrl,
45
+ // Rate limiting — 0 means unlimited
46
+ maxMessagesPerSession = 0,
47
+ maxMessagesPerMinute = 0,
48
+ limitReachedMessage,
49
+ rateLimitMessage,
45
50
  onOpen,
46
51
  onClose,
47
52
  } = props;
@@ -75,6 +80,13 @@ export const CognitionDeskWidget = forwardRef(function CognitionDeskWidget(props
75
80
  placeholder,
76
81
  streaming,
77
82
  ...(backendUrl ? { backendUrl } : {}),
83
+ rateLimiting: {
84
+ enabled: true,
85
+ maxMessagesPerSession,
86
+ maxMessagesPerMinute,
87
+ ...(limitReachedMessage ? { limitReachedMessage } : {}),
88
+ ...(rateLimitMessage ? { rateLimitMessage } : {}),
89
+ },
78
90
  });
79
91
 
80
92
  // Patch open/close to fire callbacks
package/src/widget.js CHANGED
@@ -287,9 +287,20 @@ export default class CognitionDeskWidget {
287
287
  placeholder: config.placeholder || 'Type a message…',
288
288
  position: config.position || 'bottom-right',
289
289
  streaming: config.streaming !== false, // default: true
290
+ // Rate limiting — can be overridden by inline config or merged from server config
291
+ rateLimiting: {
292
+ enabled: config.rateLimiting?.enabled !== false,
293
+ maxMessagesPerSession: config.rateLimiting?.maxMessagesPerSession ?? 0,
294
+ maxMessagesPerMinute: config.rateLimiting?.maxMessagesPerMinute ?? 0,
295
+ limitReachedMessage: config.rateLimiting?.limitReachedMessage || "You've reached the message limit for this session.",
296
+ rateLimitMessage: config.rateLimiting?.rateLimitMessage || "You're sending messages too quickly. Please wait a moment.",
297
+ },
290
298
  };
291
299
 
292
- this._sessionId = generateSessionId();
300
+ this._sessionId = generateSessionId();
301
+ this._messageCount = 0; // total sent this session
302
+ this._minuteCount = 0; // sent in current minute window
303
+ this._minuteStart = Date.now(); // start of current minute window
293
304
  this._messages = []; // { role: 'user'|'assistant', content: string }[]
294
305
  this._open = false;
295
306
  this._loading = false;
@@ -360,6 +371,10 @@ export default class CognitionDeskWidget {
360
371
  if (config.placeholder && !this._userSet('placeholder')) this._cfg.placeholder = config.placeholder;
361
372
  if (config.assistantId && !this._cfg.assistantId) this._cfg.assistantId = config.assistantId;
362
373
  if (config.avatar?.value && !this._userSet('botEmoji')) this._cfg.botEmoji = config.avatar.value;
374
+ // Merge rate limiting from server config (server is authoritative)
375
+ if (config.rateLimiting) {
376
+ this._cfg.rateLimiting = { ...this._cfg.rateLimiting, ...config.rateLimiting };
377
+ }
363
378
  } catch {
364
379
  // Silently ignore — widget falls back to inline config
365
380
  }
@@ -560,6 +575,27 @@ export default class CognitionDeskWidget {
560
575
  const text = this._textarea.value.trim();
561
576
  if (!text || this._loading) return;
562
577
 
578
+ // ── Client-side rate limit check (mirrors server enforcement) ──
579
+ const rl = this._cfg.rateLimiting;
580
+ if (rl?.enabled) {
581
+ const now = Date.now();
582
+ // Reset minute window
583
+ if (now - this._minuteStart >= 60_000) {
584
+ this._minuteCount = 0;
585
+ this._minuteStart = now;
586
+ }
587
+ if (rl.maxMessagesPerSession > 0 && this._messageCount >= rl.maxMessagesPerSession) {
588
+ this._appendMessage('error', rl.limitReachedMessage, false);
589
+ this._textarea.disabled = true;
590
+ this._sendBtn.disabled = true;
591
+ return;
592
+ }
593
+ if (rl.maxMessagesPerMinute > 0 && this._minuteCount >= rl.maxMessagesPerMinute) {
594
+ this._appendMessage('error', rl.rateLimitMessage, false);
595
+ return;
596
+ }
597
+ }
598
+
563
599
  this._textarea.value = '';
564
600
  this._textarea.style.height = 'auto';
565
601
  this._loading = true;
@@ -567,6 +603,8 @@ export default class CognitionDeskWidget {
567
603
 
568
604
  this._messages.push({ role: 'user', content: text });
569
605
  this._appendMessage('user', text, false);
606
+ this._messageCount += 1;
607
+ this._minuteCount += 1;
570
608
 
571
609
  try {
572
610
  if (this._cfg.streaming) {
@@ -579,11 +617,19 @@ export default class CognitionDeskWidget {
579
617
  this._appendMessage('assistant', response, true);
580
618
  }
581
619
  } catch (err) {
582
- this._appendMessage('error', 'Sorry, something went wrong. Please try again.', false);
620
+ // Handle server-side 429 rate limit response
621
+ if (err.status === 429 || err.message?.includes('429')) {
622
+ const msg = err.rateLimitMessage || rl?.rateLimitMessage || "You're sending messages too quickly. Please wait a moment.";
623
+ this._appendMessage('error', msg, false);
624
+ } else {
625
+ this._appendMessage('error', 'Sorry, something went wrong. Please try again.', false);
626
+ }
583
627
  console.error('[CognitionDesk]', err);
584
628
  } finally {
585
629
  this._loading = false;
586
- this._sendBtn.disabled = false;
630
+ if (!(rl?.maxMessagesPerSession > 0 && this._messageCount >= rl.maxMessagesPerSession)) {
631
+ this._sendBtn.disabled = false;
632
+ }
587
633
  this._textarea.focus();
588
634
  }
589
635
  }
@@ -613,7 +659,10 @@ export default class CognitionDeskWidget {
613
659
 
614
660
  if (!res.ok) {
615
661
  const errData = await res.json().catch(() => ({}));
616
- throw new Error(errData.message || `HTTP ${res.status}`);
662
+ const err = new Error(errData.message || `HTTP ${res.status}`);
663
+ err.status = res.status;
664
+ err.rateLimitMessage = errData.message;
665
+ throw err;
617
666
  }
618
667
 
619
668
  // Create an empty assistant bubble to stream into
@@ -673,7 +722,10 @@ export default class CognitionDeskWidget {
673
722
 
674
723
  if (!res.ok) {
675
724
  const errData = await res.json().catch(() => ({}));
676
- throw new Error(errData.message || `HTTP ${res.status}`);
725
+ const err = new Error(errData.message || `HTTP ${res.status}`);
726
+ err.status = res.status;
727
+ err.rateLimitMessage = errData.message;
728
+ throw err;
677
729
  }
678
730
 
679
731
  const data = await res.json();