@cognitiondesk/widget 1.1.0 β†’ 1.2.0

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/react.es.js CHANGED
@@ -1,6 +1,6 @@
1
- import { jsx as C } from "react/jsx-runtime";
2
- import { forwardRef as H, useRef as y, useImperativeHandle as T, useEffect as k } from "react";
3
- const L = "https://mounaji-backendv3.onrender.com", N = `
1
+ import { jsx as S } from "react/jsx-runtime";
2
+ import { forwardRef as N, useRef as M, useImperativeHandle as I, useEffect as C } from "react";
3
+ const P = "https://mounaji-backendv3.onrender.com", B = `
4
4
  :host {
5
5
  all: initial;
6
6
  font-family: system-ui, sans-serif;
@@ -246,33 +246,42 @@ const L = "https://mounaji-backendv3.onrender.com", N = `
246
246
  font-size: 16px;
247
247
  }
248
248
  }
249
- `, w = {
249
+ `, x = {
250
250
  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>',
251
251
  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>',
252
252
  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>'
253
253
  };
254
- function I() {
254
+ function z() {
255
255
  return "cd_" + Math.random().toString(36).slice(2) + Date.now().toString(36);
256
256
  }
257
- let S = class {
258
- constructor(e = {}) {
259
- if (!e.apiKey) throw new Error("[CognitionDesk] apiKey is required");
257
+ let D = class {
258
+ constructor(t = {}) {
259
+ var e, a, s, r, n;
260
+ if (!t.apiKey) throw new Error("[CognitionDesk] apiKey is required");
260
261
  this._cfg = {
261
- apiKey: e.apiKey,
262
- widgetId: e.widgetId || null,
263
- assistantId: e.assistantId || null,
264
- backendUrl: e.backendUrl || L,
265
- primaryColor: e.primaryColor || "#2563eb",
266
- theme: e.theme || "light",
262
+ apiKey: t.apiKey,
263
+ widgetId: t.widgetId || null,
264
+ assistantId: t.assistantId || null,
265
+ backendUrl: t.backendUrl || P,
266
+ primaryColor: t.primaryColor || "#2563eb",
267
+ theme: t.theme || "light",
267
268
  // 'light' | 'dark' | 'auto'
268
- botName: e.botName || "AI Assistant",
269
- botEmoji: e.botEmoji || "πŸ€–",
270
- welcomeMessage: e.welcomeMessage || "Hello! How can I help you today?",
271
- placeholder: e.placeholder || "Type a message…",
272
- position: e.position || "bottom-right",
273
- streaming: e.streaming !== !1
269
+ botName: t.botName || "AI Assistant",
270
+ botEmoji: t.botEmoji || "πŸ€–",
271
+ welcomeMessage: t.welcomeMessage || "Hello! How can I help you today?",
272
+ placeholder: t.placeholder || "Type a message…",
273
+ position: t.position || "bottom-right",
274
+ streaming: t.streaming !== !1,
274
275
  // default: true
275
- }, this._sessionId = I(), 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;
276
+ // Rate limiting β€” can be overridden by inline config or merged from server config
277
+ rateLimiting: {
278
+ enabled: ((e = t.rateLimiting) == null ? void 0 : e.enabled) !== !1,
279
+ maxMessagesPerSession: ((a = t.rateLimiting) == null ? void 0 : a.maxMessagesPerSession) ?? 0,
280
+ maxMessagesPerMinute: ((s = t.rateLimiting) == null ? void 0 : s.maxMessagesPerMinute) ?? 0,
281
+ limitReachedMessage: ((r = t.rateLimiting) == null ? void 0 : r.limitReachedMessage) || "You've reached the message limit for this session.",
282
+ rateLimitMessage: ((n = t.rateLimiting) == null ? void 0 : n.rateLimitMessage) || "You're sending messages too quickly. Please wait a moment."
283
+ }
284
+ }, this._sessionId = z(), 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;
276
285
  }
277
286
  // ── Public API ──────────────────────────────────────────────────────────────
278
287
  /**
@@ -280,45 +289,45 @@ let S = class {
280
289
  * If `widgetId` was provided, fetches the server-side config first (async).
281
290
  * Returns a Promise so callers can await full initialisation.
282
291
  */
283
- mount(e) {
284
- const t = () => {
285
- const a = e || document.body;
292
+ mount(t) {
293
+ const e = () => {
294
+ const a = t || document.body;
286
295
  this._container = document.createElement("div"), this._container.setAttribute("data-cognitiondesk", ""), a.appendChild(this._container), this._shadow = this._container.attachShadow({ mode: "open" });
287
296
  const s = document.createElement("style");
288
- s.textContent = N, this._shadow.appendChild(s), this._buildDOM(), this._applyTheme(), this._syncViewportMetrics(), this._bindViewportMetrics(), this._bindEvents(), this._cfg.welcomeMessage && this._appendMessage("assistant", this._cfg.welcomeMessage, !1);
297
+ 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);
289
298
  };
290
- return this._cfg.widgetId ? this._fetchWidgetConfig().then(() => t()).catch(() => t()) : (t(), Promise.resolve(this));
299
+ return this._cfg.widgetId ? this._fetchWidgetConfig().then(() => e()).catch(() => e()) : (e(), Promise.resolve(this));
291
300
  }
292
301
  /**
293
302
  * Fetch public widget config from the platform and merge into this._cfg.
294
303
  * Only fields not already overridden by the constructor are applied.
295
304
  */
296
305
  async _fetchWidgetConfig() {
297
- var t;
298
- const e = `${this._cfg.backendUrl}/platforms/web-widget/public/${this._cfg.widgetId}`;
306
+ var e;
307
+ const t = `${this._cfg.backendUrl}/platforms/web-widget/public/${this._cfg.widgetId}`;
299
308
  try {
300
- const a = await fetch(e);
309
+ const a = await fetch(t);
301
310
  if (!a.ok) return;
302
311
  const { config: s } = await a.json();
303
312
  if (!s) return;
304
- 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);
313
+ 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), (e = s.avatar) != null && e.value && !this._userSet("botEmoji") && (this._cfg.botEmoji = s.avatar.value), s.rateLimiting && (this._cfg.rateLimiting = { ...this._cfg.rateLimiting, ...s.rateLimiting });
305
314
  } catch {
306
315
  }
307
316
  }
308
317
  // eslint-disable-next-line no-unused-vars
309
- _userSet(e) {
318
+ _userSet(t) {
310
319
  return !1;
311
320
  }
312
321
  unmount() {
313
322
  this._unbindViewportMetrics(), this._container && (this._container.remove(), this._container = null);
314
323
  }
315
324
  open() {
316
- var e, t;
317
- this._open = !0, this._syncViewportMetrics(), (e = this._panel) == null || e.classList.add("open"), (t = this._textarea) == null || t.focus();
325
+ var t, e;
326
+ this._open = !0, this._syncViewportMetrics(), (t = this._panel) == null || t.classList.add("open"), (e = this._textarea) == null || e.focus();
318
327
  }
319
328
  close() {
320
- var e;
321
- this._open = !1, (e = this._panel) == null || e.classList.remove("open");
329
+ var t;
330
+ this._open = !1, (t = this._panel) == null || t.classList.remove("open");
322
331
  }
323
332
  toggle() {
324
333
  this._open ? this.close() : this.open();
@@ -328,10 +337,10 @@ let S = class {
328
337
  }
329
338
  // ── DOM ─────────────────────────────────────────────────────────────────────
330
339
  _buildDOM() {
331
- const e = document.createElement("div");
332
- e.className = "cd-root";
333
- const t = document.createElement("button");
334
- t.className = "cd-widget-btn", t.innerHTML = w.chat, t.setAttribute("aria-label", "Open chat"), t.style.setProperty("--cd-primary", this._cfg.primaryColor), this._toggleBtn = t;
340
+ const t = document.createElement("div");
341
+ t.className = "cd-root";
342
+ const e = document.createElement("button");
343
+ e.className = "cd-widget-btn", e.innerHTML = x.chat, e.setAttribute("aria-label", "Open chat"), e.style.setProperty("--cd-primary", this._cfg.primaryColor), this._toggleBtn = e;
335
344
  const a = document.createElement("div");
336
345
  a.className = "cd-panel", this._panel = a;
337
346
  const s = document.createElement("div");
@@ -344,27 +353,27 @@ let S = class {
344
353
  </div>
345
354
  </div>
346
355
  `;
347
- const o = document.createElement("button");
348
- o.className = "cd-close-btn", o.innerHTML = w.close, o.setAttribute("aria-label", "Close chat"), s.appendChild(o), this._closeBtn = o;
349
- const r = document.createElement("div");
350
- r.className = "cd-messages", r.setAttribute("role", "log"), r.setAttribute("aria-live", "polite"), this._messagesEl = r;
356
+ const r = document.createElement("button");
357
+ r.className = "cd-close-btn", r.innerHTML = x.close, r.setAttribute("aria-label", "Close chat"), s.appendChild(r), this._closeBtn = r;
351
358
  const n = document.createElement("div");
352
- n.className = "cd-input-area";
353
- const d = document.createElement("textarea");
354
- d.className = "cd-textarea", d.rows = 1, d.placeholder = this._cfg.placeholder, this._textarea = d;
359
+ n.className = "cd-messages", n.setAttribute("role", "log"), n.setAttribute("aria-live", "polite"), this._messagesEl = n;
360
+ const d = document.createElement("div");
361
+ d.className = "cd-input-area";
362
+ const o = document.createElement("textarea");
363
+ o.className = "cd-textarea", o.rows = 1, o.placeholder = this._cfg.placeholder, this._textarea = o;
355
364
  const c = document.createElement("button");
356
- c.className = "cd-send-btn", c.innerHTML = w.send, c.setAttribute("aria-label", "Send"), this._sendBtn = c, n.appendChild(d), n.appendChild(c);
365
+ c.className = "cd-send-btn", c.innerHTML = x.send, c.setAttribute("aria-label", "Send"), this._sendBtn = c, d.appendChild(o), d.appendChild(c);
357
366
  const l = document.createElement("div");
358
- 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(n), a.appendChild(l), e.appendChild(t), e.appendChild(a), this._shadow.appendChild(e), this._root = e;
367
+ l.className = "cd-powered", l.innerHTML = 'Powered by <a href="https://cognitiondesk.com" target="_blank" rel="noopener">CognitionDesk</a>', a.appendChild(s), a.appendChild(n), a.appendChild(d), a.appendChild(l), t.appendChild(e), t.appendChild(a), this._shadow.appendChild(t), this._root = t;
359
368
  }
360
369
  _applyTheme() {
361
- var t, a, s;
362
- (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);
370
+ var e, a, s;
371
+ (this._cfg.theme === "auto" ? window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light" : this._cfg.theme) === "dark" && ((e = this._root) == null || e.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);
363
372
  }
364
373
  // ── Events ──────────────────────────────────────────────────────────────────
365
374
  _bindEvents() {
366
- this._toggleBtn.addEventListener("click", () => this.toggle()), this._closeBtn.addEventListener("click", () => this.close()), this._sendBtn.addEventListener("click", () => this._sendMessage()), this._textarea.addEventListener("keydown", (e) => {
367
- e.key === "Enter" && !e.shiftKey && (e.preventDefault(), this._sendMessage());
375
+ this._toggleBtn.addEventListener("click", () => this.toggle()), this._closeBtn.addEventListener("click", () => this.close()), this._sendBtn.addEventListener("click", () => this._sendMessage()), this._textarea.addEventListener("keydown", (t) => {
376
+ t.key === "Enter" && !t.shiftKey && (t.preventDefault(), this._sendMessage());
368
377
  }), this._textarea.addEventListener("input", () => {
369
378
  this._textarea.style.height = "auto", this._textarea.style.height = Math.min(this._textarea.scrollHeight, 120) + "px";
370
379
  }), this._textarea.addEventListener("focus", () => {
@@ -378,28 +387,45 @@ let S = class {
378
387
  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);
379
388
  }
380
389
  _syncViewportMetrics() {
381
- const e = this._root || this._container;
382
- if (!e) return;
383
- const t = window.visualViewport, a = t ? t.height : window.innerHeight;
384
- e.style.setProperty("--cd-viewport-height", `${Math.round(a)}px`);
390
+ const t = this._root || this._container;
391
+ if (!t) return;
392
+ const e = window.visualViewport, a = e ? e.height : window.innerHeight;
393
+ t.style.setProperty("--cd-viewport-height", `${Math.round(a)}px`);
385
394
  }
386
395
  // ── Chat ────────────────────────────────────────────────────────────────────
387
396
  async _sendMessage() {
388
- const e = this._textarea.value.trim();
389
- if (!(!e || this._loading)) {
390
- 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);
391
- try {
392
- if (this._cfg.streaming)
393
- await this._callApiStream();
394
- else {
395
- const t = this._showTyping(), a = await this._callApi();
396
- this._removeTyping(t), this._messages.push({ role: "assistant", content: a }), this._appendMessage("assistant", a, !0);
397
- }
398
- } catch (t) {
399
- this._appendMessage("error", "Sorry, something went wrong. Please try again.", !1), console.error("[CognitionDesk]", t);
400
- } finally {
401
- this._loading = !1, this._sendBtn.disabled = !1, this._textarea.focus();
397
+ var a;
398
+ const t = this._textarea.value.trim();
399
+ if (!t || this._loading) return;
400
+ const e = this._cfg.rateLimiting;
401
+ if (e != null && e.enabled) {
402
+ const s = Date.now();
403
+ if (s - this._minuteStart >= 6e4 && (this._minuteCount = 0, this._minuteStart = s), e.maxMessagesPerSession > 0 && this._messageCount >= e.maxMessagesPerSession) {
404
+ this._appendMessage("error", e.limitReachedMessage, !1), this._textarea.disabled = !0, this._sendBtn.disabled = !0;
405
+ return;
402
406
  }
407
+ if (e.maxMessagesPerMinute > 0 && this._minuteCount >= e.maxMessagesPerMinute) {
408
+ this._appendMessage("error", e.rateLimitMessage, !1);
409
+ return;
410
+ }
411
+ }
412
+ this._textarea.value = "", this._textarea.style.height = "auto", this._loading = !0, this._sendBtn.disabled = !0, this._messages.push({ role: "user", content: t }), this._appendMessage("user", t, !1), this._messageCount += 1, this._minuteCount += 1;
413
+ try {
414
+ if (this._cfg.streaming)
415
+ await this._callApiStream();
416
+ else {
417
+ const s = this._showTyping(), r = await this._callApi();
418
+ this._removeTyping(s), this._messages.push({ role: "assistant", content: r }), this._appendMessage("assistant", r, !0);
419
+ }
420
+ } catch (s) {
421
+ if (s.status === 429 || (a = s.message) != null && a.includes("429")) {
422
+ const r = s.rateLimitMessage || (e == null ? void 0 : e.rateLimitMessage) || "You're sending messages too quickly. Please wait a moment.";
423
+ this._appendMessage("error", r, !1);
424
+ } else
425
+ this._appendMessage("error", "Sorry, something went wrong. Please try again.", !1);
426
+ console.error("[CognitionDesk]", s);
427
+ } finally {
428
+ this._loading = !1, (e == null ? void 0 : e.maxMessagesPerSession) > 0 && this._messageCount >= e.maxMessagesPerSession || (this._sendBtn.disabled = !1), this._textarea.focus();
403
429
  }
404
430
  }
405
431
  _buildRequestBody() {
@@ -416,107 +442,112 @@ let S = class {
416
442
  }
417
443
  /** Streaming path β€” uses SSE endpoint */
418
444
  async _callApiStream() {
419
- const e = `${this._cfg.backendUrl}/chat-apiKeyAuth/stream`, t = await fetch(e, {
445
+ const t = `${this._cfg.backendUrl}/chat-apiKeyAuth/stream`, e = await fetch(t, {
420
446
  method: "POST",
421
447
  headers: { "Content-Type": "application/json", "x-api-key": this._cfg.apiKey },
422
448
  body: JSON.stringify({ ...this._buildRequestBody(), stream: !0 })
423
449
  });
424
- if (!t.ok) {
425
- const d = await t.json().catch(() => ({}));
426
- throw new Error(d.message || `HTTP ${t.status}`);
450
+ if (!e.ok) {
451
+ const o = await e.json().catch(() => ({})), c = new Error(o.message || `HTTP ${e.status}`);
452
+ throw c.status = e.status, c.rateLimitMessage = o.message, c;
427
453
  }
428
454
  const a = document.createElement("div");
429
455
  a.className = "cd-msg assistant", a.innerHTML = '<div class="cd-typing"><span></span><span></span><span></span></div>', this._messagesEl.appendChild(a), this._scrollToBottom();
430
- const s = t.body.getReader(), o = new TextDecoder();
431
- let r = "", n = !1;
456
+ const s = e.body.getReader(), r = new TextDecoder();
457
+ let n = "", d = !1;
432
458
  try {
433
459
  for (; ; ) {
434
- const { done: d, value: c } = await s.read();
435
- if (d) break;
436
- const l = o.decode(c, { stream: !0 });
460
+ const { done: o, value: c } = await s.read();
461
+ if (o) break;
462
+ const l = r.decode(c, { stream: !0 });
437
463
  for (const g of l.split(`
438
464
  `)) {
439
465
  if (!g.startsWith("data: ")) continue;
440
- const f = g.slice(6).trim();
441
- if (f === "[DONE]") break;
466
+ const m = g.slice(6).trim();
467
+ if (m === "[DONE]") break;
442
468
  let h;
443
469
  try {
444
- h = JSON.parse(f);
470
+ h = JSON.parse(m);
445
471
  } catch {
446
472
  continue;
447
473
  }
448
- h.type === "content" && h.data && (n || (a.innerHTML = "", n = !0), r += h.data, a.innerHTML = this._renderMarkdown(r), this._scrollToBottom());
474
+ h.type === "content" && h.data && (d || (a.innerHTML = "", d = !0), n += h.data, a.innerHTML = this._renderMarkdown(n), this._scrollToBottom());
449
475
  }
450
476
  }
451
477
  } finally {
452
478
  s.releaseLock();
453
479
  }
454
- n || (a.innerHTML = this._renderMarkdown("No response")), r && this._messages.push({ role: "assistant", content: r });
480
+ d || (a.innerHTML = this._renderMarkdown("No response")), n && this._messages.push({ role: "assistant", content: n });
455
481
  }
456
482
  /** Non-streaming fallback path */
457
483
  async _callApi() {
458
- var s, o, r;
459
- const e = `${this._cfg.backendUrl}/chat-apiKeyAuth/chat`, t = await fetch(e, {
484
+ var s, r, n;
485
+ const t = `${this._cfg.backendUrl}/chat-apiKeyAuth/chat`, e = await fetch(t, {
460
486
  method: "POST",
461
487
  headers: { "Content-Type": "application/json", "x-api-key": this._cfg.apiKey },
462
488
  body: JSON.stringify(this._buildRequestBody())
463
489
  });
464
- if (!t.ok) {
465
- const n = await t.json().catch(() => ({}));
466
- throw new Error(n.message || `HTTP ${t.status}`);
490
+ if (!e.ok) {
491
+ const d = await e.json().catch(() => ({})), o = new Error(d.message || `HTTP ${e.status}`);
492
+ throw o.status = e.status, o.rateLimitMessage = d.message, o;
467
493
  }
468
- const a = await t.json();
469
- return a.response || a.message || a.content || ((r = (o = (s = a.choices) == null ? void 0 : s[0]) == null ? void 0 : o.message) == null ? void 0 : r.content) || "No response";
494
+ const a = await e.json();
495
+ return a.response || a.message || a.content || ((n = (r = (s = a.choices) == null ? void 0 : s[0]) == null ? void 0 : r.message) == null ? void 0 : n.content) || "No response";
470
496
  }
471
497
  // ── DOM helpers ─────────────────────────────────────────────────────────────
472
- _appendMessage(e, t, a = !0) {
498
+ _appendMessage(t, e, a = !0) {
473
499
  const s = document.createElement("div");
474
- s.className = `cd-msg ${e}`, a && e !== "user" ? s.innerHTML = this._renderMarkdown(t) : s.textContent = t, this._messagesEl.appendChild(s), this._scrollToBottom();
500
+ s.className = `cd-msg ${t}`, a && t !== "user" ? s.innerHTML = this._renderMarkdown(e) : s.textContent = e, this._messagesEl.appendChild(s), this._scrollToBottom();
475
501
  }
476
502
  /** Minimal, safe markdown β†’ HTML renderer (no dependencies). */
477
- _renderMarkdown(e) {
478
- if (!e) return "";
479
- let t = this._escHtml(e);
480
- return t = t.replace(
503
+ _renderMarkdown(t) {
504
+ if (!t) return "";
505
+ let e = this._escHtml(t);
506
+ return e = e.replace(
481
507
  /```[\w]*\n?([\s\S]*?)```/g,
482
508
  (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>`
483
- ), 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(
509
+ ), e = e.replace(/`([^`]+)`/g, '<code style="background:rgba(0,0,0,.08);padding:1px 5px;border-radius:4px;font-size:.9em">$1</code>'), e = e.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>"), e = e.replace(/__([^_]+)__/g, "<strong>$1</strong>"), e = e.replace(/\*([^*]+)\*/g, "<em>$1</em>"), e = e.replace(/_([^_]+)_/g, "<em>$1</em>"), e = e.replace(
484
510
  /\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g,
485
511
  '<a href="$2" target="_blank" rel="noopener" style="color:var(--cd-primary,#2563eb)">$1</a>'
486
- ), t = t.replace(/\n/g, "<br>"), t;
512
+ ), e = e.replace(/\n/g, "<br>"), e;
487
513
  }
488
514
  _showTyping() {
489
- const e = document.createElement("div");
490
- 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;
515
+ const t = document.createElement("div");
516
+ return t.className = "cd-msg assistant", t.innerHTML = '<div class="cd-typing"><span></span><span></span><span></span></div>', this._messagesEl.appendChild(t), this._scrollToBottom(), t;
491
517
  }
492
- _removeTyping(e) {
493
- e == null || e.remove();
518
+ _removeTyping(t) {
519
+ t == null || t.remove();
494
520
  }
495
521
  _scrollToBottom() {
496
522
  this._messagesEl && (this._messagesEl.scrollTop = this._messagesEl.scrollHeight);
497
523
  }
498
- _escHtml(e) {
499
- return String(e).replace(/[&<>"']/g, (t) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[t]);
524
+ _escHtml(t) {
525
+ return String(t).replace(/[&<>"']/g, (e) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[e]);
500
526
  }
501
527
  };
502
- const D = H(function(e, t) {
528
+ const V = N(function(t, e) {
503
529
  const {
504
530
  apiKey: a,
505
531
  widgetId: s,
506
- assistantId: o,
507
- theme: r = "light",
508
- primaryColor: n = "#2563eb",
509
- botName: d = "AI Assistant",
532
+ assistantId: r,
533
+ theme: n = "light",
534
+ primaryColor: d = "#2563eb",
535
+ botName: o = "AI Assistant",
510
536
  botEmoji: c = "πŸ€–",
511
537
  welcomeMessage: l = "Hello! How can I help you today?",
512
538
  placeholder: g = "Type a message…",
513
- defaultOpen: f = !1,
539
+ defaultOpen: m = !1,
514
540
  streaming: h = !0,
515
- backendUrl: x,
516
- onOpen: m,
541
+ backendUrl: w,
542
+ // Rate limiting β€” 0 means unlimited
543
+ maxMessagesPerSession: L = 0,
544
+ maxMessagesPerMinute: H = 0,
545
+ limitReachedMessage: v,
546
+ rateLimitMessage: y,
547
+ onOpen: f,
517
548
  onClose: u
518
- } = e, p = y(null), v = y(null);
519
- return T(t, () => ({
549
+ } = t, p = M(null), k = M(null);
550
+ return I(e, () => ({
520
551
  open: () => {
521
552
  var i;
522
553
  return (i = p.current) == null ? void 0 : i.open();
@@ -533,44 +564,51 @@ const D = H(function(e, t) {
533
564
  var i;
534
565
  return (i = p.current) == null ? void 0 : i.clearHistory();
535
566
  }
536
- }), []), k(() => {
567
+ }), []), C(() => {
537
568
  if (!a) {
538
569
  console.error("[CognitionDesk] apiKey prop is required");
539
570
  return;
540
571
  }
541
- const i = new S({
572
+ const i = new D({
542
573
  apiKey: a,
543
574
  widgetId: s,
544
- assistantId: o,
545
- theme: r,
546
- primaryColor: n,
547
- botName: d,
575
+ assistantId: r,
576
+ theme: n,
577
+ primaryColor: d,
578
+ botName: o,
548
579
  botEmoji: c,
549
580
  welcomeMessage: l,
550
581
  placeholder: g,
551
582
  streaming: h,
552
- ...x ? { backendUrl: x } : {}
583
+ ...w ? { backendUrl: w } : {},
584
+ rateLimiting: {
585
+ enabled: !0,
586
+ maxMessagesPerSession: L,
587
+ maxMessagesPerMinute: H,
588
+ ...v ? { limitReachedMessage: v } : {},
589
+ ...y ? { rateLimitMessage: y } : {}
590
+ }
553
591
  });
554
- if (m || u) {
555
- const _ = i.open.bind(i), M = i.close.bind(i);
592
+ if (f || u) {
593
+ const _ = i.open.bind(i), T = i.close.bind(i);
556
594
  i.open = (...b) => {
557
- _(...b), m == null || m();
595
+ _(...b), f == null || f();
558
596
  }, i.close = (...b) => {
559
- M(...b), u == null || u();
597
+ T(...b), u == null || u();
560
598
  };
561
599
  }
562
- return Promise.resolve(i.mount(v.current)).then(() => {
563
- p.current = i, f && i.open();
600
+ return Promise.resolve(i.mount(k.current)).then(() => {
601
+ p.current = i, m && i.open();
564
602
  }), () => {
565
603
  i.unmount(), p.current = null;
566
604
  };
567
- }, [a, s, o]), k(() => {
605
+ }, [a, s, r]), C(() => {
568
606
  var _;
569
607
  const i = p.current;
570
- i && (i._cfg.theme = r, i._cfg.primaryColor = n, (_ = i._applyTheme) == null || _.call(i));
571
- }, [r, n]), /* @__PURE__ */ C("div", { ref: v, "data-cognitiondesk-root": "" });
608
+ i && (i._cfg.theme = n, i._cfg.primaryColor = d, (_ = i._applyTheme) == null || _.call(i));
609
+ }, [n, d]), /* @__PURE__ */ S("div", { ref: k, "data-cognitiondesk-root": "" });
572
610
  });
573
611
  export {
574
- D as CognitionDeskWidget,
575
- S as CognitionDeskWidgetCore
612
+ V as CognitionDeskWidget,
613
+ D as CognitionDeskWidgetCore
576
614
  };
@@ -1,4 +1,4 @@
1
- (function(c,u){typeof exports=="object"&&typeof module<"u"?u(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],u):(c=typeof globalThis<"u"?globalThis:c||self,u(c.CognitionDeskWidgetReact={},c.jsxRuntime,c.React))})(this,function(c,u,g){"use strict";const C="https://mounaji-backendv3.onrender.com",H=`
1
+ (function(l,f){typeof exports=="object"&&typeof module<"u"?f(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],f):(l=typeof globalThis<"u"?globalThis:l||self,f(l.CognitionDeskWidgetReact={},l.jsxRuntime,l.React))})(this,function(l,f,g){"use strict";const H="https://mounaji-backendv3.onrender.com",T=`
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
- `,v={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 T(){return"cd_"+Math.random().toString(36).slice(2)+Date.now().toString(36)}let k=class{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||C,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=T(),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=H,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=v.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
+ `,v={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 S(){return"cd_"+Math.random().toString(36).slice(2)+Date.now().toString(36)}let k=class{constructor(t={}){var e,a,s,r,n;if(!t.apiKey)throw new Error("[CognitionDesk] apiKey is required");this._cfg={apiKey:t.apiKey,widgetId:t.widgetId||null,assistantId:t.assistantId||null,backendUrl:t.backendUrl||H,primaryColor:t.primaryColor||"#2563eb",theme:t.theme||"light",botName:t.botName||"AI Assistant",botEmoji:t.botEmoji||"πŸ€–",welcomeMessage:t.welcomeMessage||"Hello! How can I help you today?",placeholder:t.placeholder||"Type a message…",position:t.position||"bottom-right",streaming:t.streaming!==!1,rateLimiting:{enabled:((e=t.rateLimiting)==null?void 0:e.enabled)!==!1,maxMessagesPerSession:((a=t.rateLimiting)==null?void 0:a.maxMessagesPerSession)??0,maxMessagesPerMinute:((s=t.rateLimiting)==null?void 0:s.maxMessagesPerMinute)??0,limitReachedMessage:((r=t.rateLimiting)==null?void 0:r.limitReachedMessage)||"You've reached the message limit for this session.",rateLimitMessage:((n=t.rateLimiting)==null?void 0:n.rateLimitMessage)||"You're sending messages too quickly. Please wait a moment."}},this._sessionId=S(),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(t){const e=()=>{const a=t||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=T,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(()=>e()).catch(()=>e()):(e(),Promise.resolve(this))}async _fetchWidgetConfig(){var e;const t=`${this._cfg.backendUrl}/platforms/web-widget/public/${this._cfg.widgetId}`;try{const a=await fetch(t);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),(e=s.avatar)!=null&&e.value&&!this._userSet("botEmoji")&&(this._cfg.botEmoji=s.avatar.value),s.rateLimiting&&(this._cfg.rateLimiting={...this._cfg.rateLimiting,...s.rateLimiting})}catch{}}_userSet(t){return!1}unmount(){this._unbindViewportMetrics(),this._container&&(this._container.remove(),this._container=null)}open(){var t,e;this._open=!0,this._syncViewportMetrics(),(t=this._panel)==null||t.classList.add("open"),(e=this._textarea)==null||e.focus()}close(){var t;this._open=!1,(t=this._panel)==null||t.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 t=document.createElement("div");t.className="cd-root";const e=document.createElement("button");e.className="cd-widget-btn",e.innerHTML=v.chat,e.setAttribute("aria-label","Open chat"),e.style.setProperty("--cd-primary",this._cfg.primaryColor),this._toggleBtn=e;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 o=document.createElement("button");o.className="cd-close-btn",o.innerHTML=v.close,o.setAttribute("aria-label","Close chat"),s.appendChild(o),this._closeBtn=o;const r=document.createElement("div");r.className="cd-messages",r.setAttribute("role","log"),r.setAttribute("aria-live","polite"),this._messagesEl=r;const n=document.createElement("div");n.className="cd-input-area";const d=document.createElement("textarea");d.className="cd-textarea",d.rows=1,d.placeholder=this._cfg.placeholder,this._textarea=d;const l=document.createElement("button");l.className="cd-send-btn",l.innerHTML=v.send,l.setAttribute("aria-label","Send"),this._sendBtn=l,n.appendChild(d),n.appendChild(l);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(r),a.appendChild(n),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(){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 d=await t.json().catch(()=>({}));throw new Error(d.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(),o=new TextDecoder;let r="",n=!1;try{for(;;){const{done:d,value:l}=await s.read();if(d)break;const p=o.decode(l,{stream:!0});for(const m of p.split(`
256
- `)){if(!m.startsWith("data: "))continue;const _=m.slice(6).trim();if(_==="[DONE]")break;let f;try{f=JSON.parse(_)}catch{continue}f.type==="content"&&f.data&&(n||(a.innerHTML="",n=!0),r+=f.data,a.innerHTML=this._renderMarkdown(r),this._scrollToBottom())}}}finally{s.releaseLock()}n||(a.innerHTML=this._renderMarkdown("No response")),r&&this._messages.push({role:"assistant",content:r})}async _callApi(){var s,o,r;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||((r=(o=(s=a.choices)==null?void 0:s[0])==null?void 0:o.message)==null?void 0:r.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])}};const L=g.forwardRef(function(e,t){const{apiKey:a,widgetId:s,assistantId:o,theme:r="light",primaryColor:n="#2563eb",botName:d="AI Assistant",botEmoji:l="πŸ€–",welcomeMessage:p="Hello! How can I help you today?",placeholder:m="Type a message…",defaultOpen:_=!1,streaming:f=!0,backendUrl:E,onOpen:b,onClose:x}=e,h=g.useRef(null),M=g.useRef(null);return g.useImperativeHandle(t,()=>({open:()=>{var i;return(i=h.current)==null?void 0:i.open()},close:()=>{var i;return(i=h.current)==null?void 0:i.close()},toggle:()=>{var i;return(i=h.current)==null?void 0:i.toggle()},clearHistory:()=>{var i;return(i=h.current)==null?void 0:i.clearHistory()}}),[]),g.useEffect(()=>{if(!a){console.error("[CognitionDesk] apiKey prop is required");return}const i=new k({apiKey:a,widgetId:s,assistantId:o,theme:r,primaryColor:n,botName:d,botEmoji:l,welcomeMessage:p,placeholder:m,streaming:f,...E?{backendUrl:E}:{}});if(b||x){const w=i.open.bind(i),I=i.close.bind(i);i.open=(...y)=>{w(...y),b==null||b()},i.close=(...y)=>{I(...y),x==null||x()}}return Promise.resolve(i.mount(M.current)).then(()=>{h.current=i,_&&i.open()}),()=>{i.unmount(),h.current=null}},[a,s,o]),g.useEffect(()=>{var w;const i=h.current;i&&(i._cfg.theme=r,i._cfg.primaryColor=n,(w=i._applyTheme)==null||w.call(i))},[r,n]),u.jsx("div",{ref:M,"data-cognitiondesk-root":""})});c.CognitionDeskWidget=L,c.CognitionDeskWidgetCore=k,Object.defineProperty(c,Symbol.toStringTag,{value:"Module"})});
255
+ `;const r=document.createElement("button");r.className="cd-close-btn",r.innerHTML=v.close,r.setAttribute("aria-label","Close chat"),s.appendChild(r),this._closeBtn=r;const n=document.createElement("div");n.className="cd-messages",n.setAttribute("role","log"),n.setAttribute("aria-live","polite"),this._messagesEl=n;const d=document.createElement("div");d.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=v.send,c.setAttribute("aria-label","Send"),this._sendBtn=c,d.appendChild(o),d.appendChild(c);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(d),a.appendChild(p),t.appendChild(e),t.appendChild(a),this._shadow.appendChild(t),this._root=t}_applyTheme(){var e,a,s;(this._cfg.theme==="auto"?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":this._cfg.theme)==="dark"&&((e=this._root)==null||e.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",t=>{t.key==="Enter"&&!t.shiftKey&&(t.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 t=this._root||this._container;if(!t)return;const e=window.visualViewport,a=e?e.height:window.innerHeight;t.style.setProperty("--cd-viewport-height",`${Math.round(a)}px`)}async _sendMessage(){var a;const t=this._textarea.value.trim();if(!t||this._loading)return;const e=this._cfg.rateLimiting;if(e!=null&&e.enabled){const s=Date.now();if(s-this._minuteStart>=6e4&&(this._minuteCount=0,this._minuteStart=s),e.maxMessagesPerSession>0&&this._messageCount>=e.maxMessagesPerSession){this._appendMessage("error",e.limitReachedMessage,!1),this._textarea.disabled=!0,this._sendBtn.disabled=!0;return}if(e.maxMessagesPerMinute>0&&this._minuteCount>=e.maxMessagesPerMinute){this._appendMessage("error",e.rateLimitMessage,!1);return}}this._textarea.value="",this._textarea.style.height="auto",this._loading=!0,this._sendBtn.disabled=!0,this._messages.push({role:"user",content:t}),this._appendMessage("user",t,!1),this._messageCount+=1,this._minuteCount+=1;try{if(this._cfg.streaming)await this._callApiStream();else{const s=this._showTyping(),r=await this._callApi();this._removeTyping(s),this._messages.push({role:"assistant",content:r}),this._appendMessage("assistant",r,!0)}}catch(s){if(s.status===429||(a=s.message)!=null&&a.includes("429")){const r=s.rateLimitMessage||(e==null?void 0:e.rateLimitMessage)||"You're sending messages too quickly. Please wait a moment.";this._appendMessage("error",r,!1)}else this._appendMessage("error","Sorry, something went wrong. Please try again.",!1);console.error("[CognitionDesk]",s)}finally{this._loading=!1,(e==null?void 0:e.maxMessagesPerSession)>0&&this._messageCount>=e.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 t=`${this._cfg.backendUrl}/chat-apiKeyAuth/stream`,e=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":this._cfg.apiKey},body:JSON.stringify({...this._buildRequestBody(),stream:!0})});if(!e.ok){const o=await e.json().catch(()=>({})),c=new Error(o.message||`HTTP ${e.status}`);throw c.status=e.status,c.rateLimitMessage=o.message,c}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=e.body.getReader(),r=new TextDecoder;let n="",d=!1;try{for(;;){const{done:o,value:c}=await s.read();if(o)break;const p=r.decode(c,{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&&(d||(a.innerHTML="",d=!0),n+=m.data,a.innerHTML=this._renderMarkdown(n),this._scrollToBottom())}}}finally{s.releaseLock()}d||(a.innerHTML=this._renderMarkdown("No response")),n&&this._messages.push({role:"assistant",content:n})}async _callApi(){var s,r,n;const t=`${this._cfg.backendUrl}/chat-apiKeyAuth/chat`,e=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":this._cfg.apiKey},body:JSON.stringify(this._buildRequestBody())});if(!e.ok){const d=await e.json().catch(()=>({})),o=new Error(d.message||`HTTP ${e.status}`);throw o.status=e.status,o.rateLimitMessage=d.message,o}const a=await e.json();return a.response||a.message||a.content||((n=(r=(s=a.choices)==null?void 0:s[0])==null?void 0:r.message)==null?void 0:n.content)||"No response"}_appendMessage(t,e,a=!0){const s=document.createElement("div");s.className=`cd-msg ${t}`,a&&t!=="user"?s.innerHTML=this._renderMarkdown(e):s.textContent=e,this._messagesEl.appendChild(s),this._scrollToBottom()}_renderMarkdown(t){if(!t)return"";let e=this._escHtml(t);return e=e.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>`),e=e.replace(/`([^`]+)`/g,'<code style="background:rgba(0,0,0,.08);padding:1px 5px;border-radius:4px;font-size:.9em">$1</code>'),e=e.replace(/\*\*([^*]+)\*\*/g,"<strong>$1</strong>"),e=e.replace(/__([^_]+)__/g,"<strong>$1</strong>"),e=e.replace(/\*([^*]+)\*/g,"<em>$1</em>"),e=e.replace(/_([^_]+)_/g,"<em>$1</em>"),e=e.replace(/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g,'<a href="$2" target="_blank" rel="noopener" style="color:var(--cd-primary,#2563eb)">$1</a>'),e=e.replace(/\n/g,"<br>"),e}_showTyping(){const t=document.createElement("div");return t.className="cd-msg assistant",t.innerHTML='<div class="cd-typing"><span></span><span></span><span></span></div>',this._messagesEl.appendChild(t),this._scrollToBottom(),t}_removeTyping(t){t==null||t.remove()}_scrollToBottom(){this._messagesEl&&(this._messagesEl.scrollTop=this._messagesEl.scrollHeight)}_escHtml(t){return String(t).replace(/[&<>"']/g,e=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[e])}};const N=g.forwardRef(function(t,e){const{apiKey:a,widgetId:s,assistantId:r,theme:n="light",primaryColor:d="#2563eb",botName:o="AI Assistant",botEmoji:c="πŸ€–",welcomeMessage:p="Hello! How can I help you today?",placeholder:u="Type a message…",defaultOpen:_=!1,streaming:m=!0,backendUrl:M,maxMessagesPerSession:P=0,maxMessagesPerMinute:j=0,limitReachedMessage:C,rateLimitMessage:E,onOpen:b,onClose:x}=t,h=g.useRef(null),L=g.useRef(null);return g.useImperativeHandle(e,()=>({open:()=>{var i;return(i=h.current)==null?void 0:i.open()},close:()=>{var i;return(i=h.current)==null?void 0:i.close()},toggle:()=>{var i;return(i=h.current)==null?void 0:i.toggle()},clearHistory:()=>{var i;return(i=h.current)==null?void 0:i.clearHistory()}}),[]),g.useEffect(()=>{if(!a){console.error("[CognitionDesk] apiKey prop is required");return}const i=new k({apiKey:a,widgetId:s,assistantId:r,theme:n,primaryColor:d,botName:o,botEmoji:c,welcomeMessage:p,placeholder:u,streaming:m,...M?{backendUrl:M}:{},rateLimiting:{enabled:!0,maxMessagesPerSession:P,maxMessagesPerMinute:j,...C?{limitReachedMessage:C}:{},...E?{rateLimitMessage:E}:{}}});if(b||x){const w=i.open.bind(i),B=i.close.bind(i);i.open=(...y)=>{w(...y),b==null||b()},i.close=(...y)=>{B(...y),x==null||x()}}return Promise.resolve(i.mount(L.current)).then(()=>{h.current=i,_&&i.open()}),()=>{i.unmount(),h.current=null}},[a,s,r]),g.useEffect(()=>{var w;const i=h.current;i&&(i._cfg.theme=n,i._cfg.primaryColor=d,(w=i._applyTheme)==null||w.call(i))},[n,d]),f.jsx("div",{ref:L,"data-cognitiondesk-root":""})});l.CognitionDeskWidget=N,l.CognitionDeskWidgetCore=k,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"})});
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.0",
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();